mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-27 07:03:15 -04:00
Compare commits
563 Commits
v0.27.0
...
notgull/sp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d28c867b61 | ||
|
|
cfd35d2708 | ||
|
|
1b9c576f53 | ||
|
|
52532d2579 | ||
|
|
4b28f10470 | ||
|
|
daf1304879 | ||
|
|
c6e57430bb | ||
|
|
284f4c0558 | ||
|
|
859a6f109c | ||
|
|
a232f7bc77 | ||
|
|
48831f7910 | ||
|
|
114e6999d3 | ||
|
|
2f197315d9 | ||
|
|
557464489e | ||
|
|
623b3e5070 | ||
|
|
a97255e0fc | ||
|
|
3a817647d5 | ||
|
|
56035e1f13 | ||
|
|
08fc4099e8 | ||
|
|
4d4d6e5052 | ||
|
|
cf5f4de19e | ||
|
|
21df84b7f4 | ||
|
|
dd13ccda4c | ||
|
|
df8805c0d2 | ||
|
|
db1ca45a17 | ||
|
|
ff731197dc | ||
|
|
f526a47152 | ||
|
|
f204467838 | ||
|
|
6641dfa412 | ||
|
|
c1168b4f58 | ||
|
|
f8b7c4b78f | ||
|
|
3830b492c4 | ||
|
|
8862ce0163 | ||
|
|
569c44a632 | ||
|
|
ef2ec904ce | ||
|
|
98d3391f2d | ||
|
|
d0a1917603 | ||
|
|
b36d8d1e52 | ||
|
|
a5b08fc48c | ||
|
|
0482d9cfce | ||
|
|
10a785019c | ||
|
|
0cc19716f3 | ||
|
|
572d7ee77c | ||
|
|
b0c59c8416 | ||
|
|
d7c7ba1d6c | ||
|
|
aec608f93c | ||
|
|
d1717b6a01 | ||
|
|
6b29253797 | ||
|
|
30775f4982 | ||
|
|
41070d7c67 | ||
|
|
6df972d108 | ||
|
|
73910d20cc | ||
|
|
16d860736b | ||
|
|
2ee44246ae | ||
|
|
14b418a3a7 | ||
|
|
c86b0daf7f | ||
|
|
40b61d2d92 | ||
|
|
22311802b5 | ||
|
|
169cd39f93 | ||
|
|
bdeb2574dc | ||
|
|
4fe38d8067 | ||
|
|
816798bfd1 | ||
|
|
f99c810bec | ||
|
|
d39528aa69 | ||
|
|
787b2d7362 | ||
|
|
37b6243289 | ||
|
|
8ea1da7879 | ||
|
|
b1209bc253 | ||
|
|
021fd23c34 | ||
|
|
dd127463c5 | ||
|
|
ac247cd081 | ||
|
|
ea1bfd254d | ||
|
|
178f5fda05 | ||
|
|
42dbc4748e | ||
|
|
8b3de7cedf | ||
|
|
8b0ffb7e7d | ||
|
|
c55a2c779b | ||
|
|
c5a422eed6 | ||
|
|
1893b0ec42 | ||
|
|
5e106b4dbb | ||
|
|
5a1d3e4656 | ||
|
|
8f6de4ef4b | ||
|
|
ad1843aea6 | ||
|
|
ca1674519a | ||
|
|
f78edc7ef1 | ||
|
|
4f295e0c94 | ||
|
|
658f49b014 | ||
|
|
34e42ff94d | ||
|
|
ba654bb61e | ||
|
|
f5c691467b | ||
|
|
a87cfb62c3 | ||
|
|
25d6a1d46d | ||
|
|
e0fea25b06 | ||
|
|
843d7904d6 | ||
|
|
28a811bbba | ||
|
|
61a873d79a | ||
|
|
be4a660011 | ||
|
|
34dd2cdba9 | ||
|
|
775c8ece70 | ||
|
|
c12c7b82e8 | ||
|
|
8cc5cb9d9b | ||
|
|
9a28bb4b49 | ||
|
|
4f6fd44c6c | ||
|
|
5a43ea8cd6 | ||
|
|
e9a25a4c91 | ||
|
|
674657efb6 | ||
|
|
7d5bee767c | ||
|
|
745cfaab2c | ||
|
|
a8f49dc8ef | ||
|
|
e5310ade08 | ||
|
|
37946e0a3a | ||
|
|
86b737f5e7 | ||
|
|
e37585e5bc | ||
|
|
4aeeb24745 | ||
|
|
8cd3aaa8a2 | ||
|
|
2c15de7cf9 | ||
|
|
0a7ea61834 | ||
|
|
4ee11018c2 | ||
|
|
4f669ebbd2 | ||
|
|
7761b2b16c | ||
|
|
ae41e3265f | ||
|
|
8702a09333 | ||
|
|
8b5c84f404 | ||
|
|
a676d0018b | ||
|
|
04ca85a909 | ||
|
|
cc33212479 | ||
|
|
f2c5127f27 | ||
|
|
af93167237 | ||
|
|
7f6b16a6af | ||
|
|
bf5806a9b2 | ||
|
|
2a9c593e01 | ||
|
|
becdd0dbd2 | ||
|
|
3eea505440 | ||
|
|
b863283c38 | ||
|
|
73718c9f2f | ||
|
|
f735f028a1 | ||
|
|
da947992ac | ||
|
|
e9784127df | ||
|
|
0be2bb0a8c | ||
|
|
075996b1fa | ||
|
|
a7241b3db3 | ||
|
|
17296e9878 | ||
|
|
b3c87caa7c | ||
|
|
81a1d9c396 | ||
|
|
5612626944 | ||
|
|
d3ca685b77 | ||
|
|
7bed5eecfd | ||
|
|
14140607d1 | ||
|
|
eab982c402 | ||
|
|
21701a33de | ||
|
|
c89e6df758 | ||
|
|
e9210555c1 | ||
|
|
0994b5ceb8 | ||
|
|
bcce5134e1 | ||
|
|
d333dd8664 | ||
|
|
52af1b4a77 | ||
|
|
3c9f9da19e | ||
|
|
075dfcea19 | ||
|
|
92b7dcccc1 | ||
|
|
5a3be586f4 | ||
|
|
12dbbf8012 | ||
|
|
53ca5af48f | ||
|
|
c235bd154a | ||
|
|
f4e71a1d9c | ||
|
|
62ed51a138 | ||
|
|
b2a2ec91ae | ||
|
|
d37d1a03b2 | ||
|
|
772b21ce09 | ||
|
|
2edcd09704 | ||
|
|
d35c3bea42 | ||
|
|
89a184ed84 | ||
|
|
36d4907da8 | ||
|
|
98b3508aca | ||
|
|
c0db53a516 | ||
|
|
52b7205b75 | ||
|
|
41dbbc27a0 | ||
|
|
c346fb7e61 | ||
|
|
6a041f84ba | ||
|
|
acfeff5327 | ||
|
|
b9e1e96eaa | ||
|
|
880238a24f | ||
|
|
f5b4d6938f | ||
|
|
c65e2247a1 | ||
|
|
801fddbfcf | ||
|
|
3ad64fb811 | ||
|
|
9bf4493a21 | ||
|
|
48f6582eb4 | ||
|
|
ef34692148 | ||
|
|
c48116a8fd | ||
|
|
b938fe9df5 | ||
|
|
b7e3649e8b | ||
|
|
844269d017 | ||
|
|
e41fac825c | ||
|
|
bbeacc46d5 | ||
|
|
61581ebb4f | ||
|
|
93f1000a05 | ||
|
|
1ea41a2ee2 | ||
|
|
42c9b7e40e | ||
|
|
0960635895 | ||
|
|
789a497980 | ||
|
|
fac6110cb6 | ||
|
|
0363be4776 | ||
|
|
f5dd1c008c | ||
|
|
48a1e84906 | ||
|
|
ee0db52ac4 | ||
|
|
c7cf0cfd83 | ||
|
|
8393d98940 | ||
|
|
af247eac0f | ||
|
|
b2b4564a5f | ||
|
|
cb58c49a90 | ||
|
|
ffb46dd61f | ||
|
|
8c8fb39fcd | ||
|
|
2422ea39d0 | ||
|
|
878d832d24 | ||
|
|
e2e01e1fc6 | ||
|
|
c8b685ddbc | ||
|
|
f10ae52385 | ||
|
|
e731041c15 | ||
|
|
9df7fc47a1 | ||
|
|
992aeb0ca0 | ||
|
|
0caba93b51 | ||
|
|
c00c1e9eb7 | ||
|
|
83950acd5a | ||
|
|
b99403b1b9 | ||
|
|
4f0ce7201d | ||
|
|
e648169861 | ||
|
|
8fdd81ecef | ||
|
|
7a2a2341c2 | ||
|
|
d68d9eab38 | ||
|
|
a06ea45c0f | ||
|
|
67b041e231 | ||
|
|
6dfc78fb50 | ||
|
|
477619c0a7 | ||
|
|
5f1a4b65ad | ||
|
|
bb9b629bc3 | ||
|
|
0c8cf94a70 | ||
|
|
1dfca5a395 | ||
|
|
7541220a41 | ||
|
|
7e11912d22 | ||
|
|
86baa1c99a | ||
|
|
d9f04780cc | ||
|
|
67d3fd28f7 | ||
|
|
a3cba838ea | ||
|
|
48abf52aac | ||
|
|
68ef9f707e | ||
|
|
9979441c82 | ||
|
|
309e6aa85a | ||
|
|
8b8556798e | ||
|
|
2d96480a89 | ||
|
|
6caff77abb | ||
|
|
af6c343d0e | ||
|
|
119462795a | ||
|
|
f801c4a00b | ||
|
|
f9758528f6 | ||
|
|
778d70c001 | ||
|
|
dc973883c9 | ||
|
|
2233edb9a0 | ||
|
|
bd2f1e8312 | ||
|
|
e9ebf1e5f4 | ||
|
|
5f7955cb2b | ||
|
|
793c535b01 | ||
|
|
ed26dd58fd | ||
|
|
584aab4cd0 | ||
|
|
8100a6a584 | ||
|
|
cad3277550 | ||
|
|
3c3a863cc9 | ||
|
|
8a7e18aaf0 | ||
|
|
57fad2ce15 | ||
|
|
7a58fe58ce | ||
|
|
38f28d5836 | ||
|
|
189a0080a6 | ||
|
|
b5aa96bea4 | ||
|
|
19e3906369 | ||
|
|
9ac3259a79 | ||
|
|
2b2dd6b65d | ||
|
|
75173118b0 | ||
|
|
e33d2bee6c | ||
|
|
ae7497e18f | ||
|
|
935146d299 | ||
|
|
755c533b08 | ||
|
|
ae9b02e097 | ||
|
|
e6c7cc297d | ||
|
|
b74cee8df1 | ||
|
|
e5eb253698 | ||
|
|
ec11b4877f | ||
|
|
9e46dffcc5 | ||
|
|
7501039d57 | ||
|
|
289ce32d77 | ||
|
|
0d366ffbda | ||
|
|
a6f414d732 | ||
|
|
c47d0846fa | ||
|
|
461efaf99f | ||
|
|
420840278b | ||
|
|
f5e73b0af4 | ||
|
|
f40b5f0dad | ||
|
|
c91402efb9 | ||
|
|
43acf7f42f | ||
|
|
c62e64060b | ||
|
|
f7a84a5b50 | ||
|
|
89aa7cc06e | ||
|
|
b166e1ff13 | ||
|
|
97434d8d80 | ||
|
|
06fb089633 | ||
|
|
4d6dbea74c | ||
|
|
c5941d105f | ||
|
|
b63164645b | ||
|
|
5b5ebc25d8 | ||
|
|
d7ec899d69 | ||
|
|
5379d60e4d | ||
|
|
44e2f95331 | ||
|
|
3b2d1a7643 | ||
|
|
50b17a3907 | ||
|
|
af26f01b95 | ||
|
|
c4d70d75c1 | ||
|
|
db8de03142 | ||
|
|
ff0ce9d065 | ||
|
|
42e492cde8 | ||
|
|
5e0e1e96bc | ||
|
|
bd890e69aa | ||
|
|
bca57ed0b4 | ||
|
|
4652d48105 | ||
|
|
96c0b267e2 | ||
|
|
81fd39485f | ||
|
|
6178acede8 | ||
|
|
a320702a71 | ||
|
|
b0106898f7 | ||
|
|
924f3323b5 | ||
|
|
b97df599c5 | ||
|
|
0defd747c8 | ||
|
|
e23186db8e | ||
|
|
059abb06fc | ||
|
|
bc216b8f67 | ||
|
|
864a1d5924 | ||
|
|
05444628e6 | ||
|
|
66ff52b012 | ||
|
|
7929999c1c | ||
|
|
7094a223af | ||
|
|
b2a46d0439 | ||
|
|
4748890935 | ||
|
|
6300cf915e | ||
|
|
9a9c9b15ba | ||
|
|
7ce86c3d2a | ||
|
|
a444637b18 | ||
|
|
f0d88c52a3 | ||
|
|
e17977d7c7 | ||
|
|
a7a8ff0bbb | ||
|
|
fc046add78 | ||
|
|
ab4a4a89e6 | ||
|
|
f7a400ddf6 | ||
|
|
07d39abddd | ||
|
|
0c8bf25ae4 | ||
|
|
b5785ba785 | ||
|
|
9797ed86f0 | ||
|
|
e220a75556 | ||
|
|
29d3729ac8 | ||
|
|
4a36741f9c | ||
|
|
ab46aa5b79 | ||
|
|
12fb37d827 | ||
|
|
c88a4ab221 | ||
|
|
8f7f3efc0d | ||
|
|
eb2d3894ef | ||
|
|
3f4f580181 | ||
|
|
d3aeff8838 | ||
|
|
0786d534f4 | ||
|
|
b4b2389d0a | ||
|
|
964e342f69 | ||
|
|
a134a59917 | ||
|
|
fbba203c4a | ||
|
|
61bd8b8254 | ||
|
|
587fa67571 | ||
|
|
7500a88230 | ||
|
|
82d0380ea6 | ||
|
|
642ce2bfa7 | ||
|
|
5bbe87960e | ||
|
|
cf77f82ae3 | ||
|
|
72cf4e577f | ||
|
|
4f3eacf01e | ||
|
|
31ebc5caf4 | ||
|
|
d273518ce9 | ||
|
|
2ade772ab0 | ||
|
|
4ac2006cbc | ||
|
|
ba5ad3be13 | ||
|
|
8092fa2440 | ||
|
|
8bb004a1d9 | ||
|
|
de5327477a | ||
|
|
8f959714cc | ||
|
|
035eebb19a | ||
|
|
b5af6bb266 | ||
|
|
1805124c54 | ||
|
|
0f64589dba | ||
|
|
5438a2a524 | ||
|
|
918430979f | ||
|
|
f3f46cb3f6 | ||
|
|
3c3be71a77 | ||
|
|
a7986b077f | ||
|
|
8a0edde5c8 | ||
|
|
a9e168e10d | ||
|
|
bd9cc2a9da | ||
|
|
596c0edf0f | ||
|
|
92592ec605 | ||
|
|
25c4e2e451 | ||
|
|
ad52c72e41 | ||
|
|
d15feb5cfa | ||
|
|
9938327066 | ||
|
|
f980ed7b83 | ||
|
|
2496098890 | ||
|
|
60e91b187a | ||
|
|
2486f0f1a1 | ||
|
|
fbea75d31f | ||
|
|
d4c9535af9 | ||
|
|
f0fcb346b0 | ||
|
|
77f8e511e9 | ||
|
|
3217eaa416 | ||
|
|
b18295a1ce | ||
|
|
fb9695d56d | ||
|
|
08bdca19b1 | ||
|
|
79ac236721 | ||
|
|
b870a11a99 | ||
|
|
2af1550bbb | ||
|
|
ed796dcd15 | ||
|
|
a006cd7dc8 | ||
|
|
a31f71ee07 | ||
|
|
0f89aac9f6 | ||
|
|
82df9531f4 | ||
|
|
265152355e | ||
|
|
37c0f615cf | ||
|
|
5ba6bdef49 | ||
|
|
69d6076310 | ||
|
|
7029ce6ecd | ||
|
|
1eb1a13a77 | ||
|
|
3fd73848dd | ||
|
|
4e1c46fe9e | ||
|
|
180a4c7a16 | ||
|
|
13613931cf | ||
|
|
483c1d40ae | ||
|
|
1b4045dcb2 | ||
|
|
8f8da0f8bb | ||
|
|
23b821285c | ||
|
|
c984476687 | ||
|
|
42c395e49d | ||
|
|
422c6b1987 | ||
|
|
b457329003 | ||
|
|
930df0ec45 | ||
|
|
e1b7fda409 | ||
|
|
e423802ed3 | ||
|
|
a82f66826b | ||
|
|
0f2fbe373b | ||
|
|
7341ee80ea | ||
|
|
d448d3e14f | ||
|
|
a867032e1e | ||
|
|
b711a11549 | ||
|
|
809162fbd0 | ||
|
|
de782504ab | ||
|
|
1886949efe | ||
|
|
b1a5fae1f5 | ||
|
|
a88d2e079d | ||
|
|
067535eb38 | ||
|
|
7d626d9dfd | ||
|
|
62ce14a013 | ||
|
|
6f60c7a6cc | ||
|
|
6cf0bf76da | ||
|
|
9225b2812e | ||
|
|
08ce3af3e1 | ||
|
|
490abcad14 | ||
|
|
4b22ca8daf | ||
|
|
66ca445caa | ||
|
|
2f52c23fa9 | ||
|
|
ee88e38f13 | ||
|
|
5e77d70245 | ||
|
|
58ec458877 | ||
|
|
94e4c394e7 | ||
|
|
f43ce2a131 | ||
|
|
402cbd55f9 | ||
|
|
da7422c6e1 | ||
|
|
8934d2765d | ||
|
|
89eea64a4a | ||
|
|
9f781bc422 | ||
|
|
4ed4e918f3 | ||
|
|
2e4d79f57a | ||
|
|
bf92f3e97b | ||
|
|
2a58b785fe | ||
|
|
32784af3c4 | ||
|
|
94688a62f0 | ||
|
|
28e34c2e1b | ||
|
|
9ae7498a8a | ||
|
|
1786c877ec | ||
|
|
101ac8908c | ||
|
|
ba4bf03675 | ||
|
|
a63b066ed5 | ||
|
|
f77f858e9b | ||
|
|
6d0cf6a275 | ||
|
|
12df8b6c0c | ||
|
|
65baae75c4 | ||
|
|
418cc44e93 | ||
|
|
ce6c6e8c95 | ||
|
|
bdcbd7d1f9 | ||
|
|
05484c5888 | ||
|
|
50bbc85dc3 | ||
|
|
8669c2e8df | ||
|
|
97d4c7b303 | ||
|
|
08f9e374e0 | ||
|
|
a7a7cc64cd | ||
|
|
04d9e081b8 | ||
|
|
8fc24c959a | ||
|
|
2fb15dbe8a | ||
|
|
92fdf5ba85 | ||
|
|
4f06cfcf5b | ||
|
|
462bb4d324 | ||
|
|
f6ca8515ab | ||
|
|
71094e5703 | ||
|
|
bb0f965c57 | ||
|
|
4d48c76da9 | ||
|
|
fafdedfb7d | ||
|
|
25b129362f | ||
|
|
48b843e42d | ||
|
|
58f2455aa9 | ||
|
|
155f1f9720 | ||
|
|
3b56b0e76f | ||
|
|
5d2aca90bd | ||
|
|
ba49db2cb9 | ||
|
|
a4695c5397 | ||
|
|
92ddb3483e | ||
|
|
fec52b028e | ||
|
|
d8c0ee733b | ||
|
|
fb248eaadc | ||
|
|
da7bf8e29b | ||
|
|
a6a8b12537 | ||
|
|
340f951d10 | ||
|
|
05dd31b8ea | ||
|
|
0fca8b088d | ||
|
|
ab56e9f57d | ||
|
|
97d2aaa953 | ||
|
|
29419d6c38 | ||
|
|
e517e468f8 | ||
|
|
d67c928120 | ||
|
|
112965b4ff | ||
|
|
e0018d0710 | ||
|
|
1ca8b65e85 | ||
|
|
a43a15b4a0 | ||
|
|
66aa6c945d | ||
|
|
dfecdc5762 | ||
|
|
8729119536 | ||
|
|
ec7e935248 | ||
|
|
fd72000a9a | ||
|
|
e91ee811cb | ||
|
|
b3b80166ce | ||
|
|
f9b41fd819 | ||
|
|
0d9c39029c | ||
|
|
da2cef97a3 | ||
|
|
76f158d310 | ||
|
|
2e4338bb8d | ||
|
|
ec2888b8b7 | ||
|
|
fa83bace12 | ||
|
|
ee7dc48e3b | ||
|
|
11d4a301e4 | ||
|
|
ad41eaf151 | ||
|
|
9b71df9f97 | ||
|
|
b1c9e4a6fa | ||
|
|
cdbaf4816a | ||
|
|
6b7ceedc91 | ||
|
|
c53a574bff | ||
|
|
95246d81c1 | ||
|
|
bf537009d9 |
34
.github/CODEOWNERS
vendored
Normal file
34
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Android
|
||||||
|
/src/platform/android.rs @msiglreith @MarijnS95
|
||||||
|
/src/platform_impl/android @msiglreith @MarijnS95
|
||||||
|
|
||||||
|
# iOS
|
||||||
|
/src/platform/ios.rs @madsmtm
|
||||||
|
/src/platform_impl/ios @madsmtm
|
||||||
|
|
||||||
|
# Unix
|
||||||
|
/src/platform_impl/linux/mod.rs @kchibisov
|
||||||
|
|
||||||
|
# Wayland
|
||||||
|
/src/platform/wayland.rs @kchibisov
|
||||||
|
/src/platform_impl/linux/wayland @kchibisov
|
||||||
|
|
||||||
|
# X11
|
||||||
|
/src/platform/x11.rs @kchibisov @notgull
|
||||||
|
/src/platform_impl/linux/x11 @kchibisov @notgull
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
/src/platform/macos.rs @madsmtm
|
||||||
|
/src/platform_impl/macos @madsmtm
|
||||||
|
|
||||||
|
# Web
|
||||||
|
/src/platform/web.rs @daxpedda
|
||||||
|
/src/platform_impl/web @daxpedda
|
||||||
|
|
||||||
|
# Windows
|
||||||
|
/src/platform/windows.rs @msiglreith
|
||||||
|
/src/platform_impl/windows @msiglreith
|
||||||
|
|
||||||
|
# Orbital (Redox OS)
|
||||||
|
/src/platform/orbital.rs @jackpot51
|
||||||
|
/src/platform_impl/orbital @jackpot51
|
||||||
231
.github/workflows/ci.yml
vendored
231
.github/workflows/ci.yml
vendored
@@ -6,117 +6,198 @@ on:
|
|||||||
branches: [master]
|
branches: [master]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
Check_Formatting:
|
fmt:
|
||||||
|
name: Check formatting
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
- uses: hecrj/setup-rust-action@v1
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
rust-version: stable
|
|
||||||
components: rustfmt
|
components: rustfmt
|
||||||
- name: Check Formatting
|
- name: Check Formatting
|
||||||
run: cargo +stable fmt --all -- --check
|
run: cargo fmt -- --check
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
name: Tests
|
name: Test ${{ matrix.toolchain }} ${{ matrix.platform.name }}
|
||||||
|
runs-on: ${{ matrix.platform.os }}
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
rust_version: [stable, nightly]
|
toolchain: [stable, nightly, '1.70.0']
|
||||||
platform:
|
platform:
|
||||||
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
|
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
|
||||||
- { target: x86_64-pc-windows-msvc, os: windows-latest, }
|
- { name: 'Windows 64bit MSVC', target: x86_64-pc-windows-msvc, os: windows-latest, }
|
||||||
- { target: i686-pc-windows-msvc, os: windows-latest, }
|
- { name: 'Windows 32bit MSVC', target: i686-pc-windows-msvc, os: windows-latest, }
|
||||||
- { target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu }
|
- { name: 'Windows 64bit GNU', target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu }
|
||||||
- { target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu }
|
- { name: 'Windows 32bit GNU', target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu }
|
||||||
- { target: i686-unknown-linux-gnu, os: ubuntu-latest, }
|
- { name: 'Linux 32bit', target: i686-unknown-linux-gnu, os: ubuntu-latest, }
|
||||||
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
|
- { name: 'Linux 64bit', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
|
||||||
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: x11 }
|
- { name: 'X11', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=x11' }
|
||||||
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: --no-default-features, features: "wayland,wayland-dlopen" }
|
- { name: 'Wayland', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=wayland,wayland-dlopen' }
|
||||||
- { target: aarch64-linux-android, os: ubuntu-latest, cmd: 'apk --' }
|
- { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--features=android-native-activity', cmd: 'apk --' }
|
||||||
- { target: x86_64-apple-darwin, os: macos-latest, }
|
- { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, }
|
||||||
- { target: x86_64-apple-ios, os: macos-latest, }
|
- { name: 'macOS', target: x86_64-apple-darwin, os: macos-latest, }
|
||||||
- { target: aarch64-apple-ios, os: macos-latest, }
|
- { name: 'iOS x86_64', target: x86_64-apple-ios, os: macos-latest, }
|
||||||
# We're using Windows rather than Ubuntu to run the wasm tests because caching cargo-web
|
- { name: 'iOS Aarch64', target: aarch64-apple-ios, os: macos-latest, }
|
||||||
# doesn't currently work on Linux.
|
- { name: 'web', target: wasm32-unknown-unknown, os: ubuntu-latest, }
|
||||||
- { target: wasm32-unknown-unknown, os: windows-latest, }
|
exclude:
|
||||||
|
# Android is tested on stable-3
|
||||||
|
- toolchain: '1.70.0'
|
||||||
|
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
|
||||||
|
include:
|
||||||
|
- toolchain: '1.70.0'
|
||||||
|
platform: { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--package=winit --features=android-native-activity', cmd: 'apk --' }
|
||||||
|
- toolchain: 'nightly'
|
||||||
|
platform: {
|
||||||
|
name: 'web Atomic',
|
||||||
|
target: wasm32-unknown-unknown,
|
||||||
|
os: ubuntu-latest,
|
||||||
|
options: '-Zbuild-std=panic_abort,std',
|
||||||
|
rustflags: '-Ctarget-feature=+atomics,+bulk-memory',
|
||||||
|
components: rust-src,
|
||||||
|
}
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
# Set more verbose terminal output
|
||||||
|
CARGO_TERM_VERBOSE: true
|
||||||
RUST_BACKTRACE: 1
|
RUST_BACKTRACE: 1
|
||||||
CARGO_INCREMENTAL: 0
|
|
||||||
PKG_CONFIG_ALLOW_CROSS: 1
|
# Faster compilation and error on warnings
|
||||||
RUSTFLAGS: "-C debuginfo=0 --deny warnings"
|
RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings ${{ matrix.platform.rustflags }}'
|
||||||
OPTIONS: ${{ matrix.platform.options }}
|
|
||||||
FEATURES: ${{ format(',{0}', matrix.platform.features ) }}
|
OPTIONS: --target=${{ matrix.platform.target }} ${{ matrix.platform.options }}
|
||||||
CMD: ${{ matrix.platform.cmd }}
|
CMD: ${{ matrix.platform.cmd }}
|
||||||
RUSTDOCFLAGS: -Dwarnings
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.platform.os }}
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
# Used to cache cargo-web
|
|
||||||
- name: Cache cargo folder
|
|
||||||
uses: actions/cache@v1
|
|
||||||
with:
|
|
||||||
path: ~/.cargo
|
|
||||||
key: ${{ matrix.platform.target }}-cargo-${{ matrix.rust_version }}
|
|
||||||
|
|
||||||
- uses: hecrj/setup-rust-action@v1
|
- name: Restore cache of cargo folder
|
||||||
|
# We use `restore` and later `save`, so that we can create the key after
|
||||||
|
# the cache has been downloaded.
|
||||||
|
#
|
||||||
|
# This could be avoided if we added Cargo.lock to the repository.
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
with:
|
with:
|
||||||
rust-version: ${{ matrix.rust_version }}${{ matrix.platform.host }}
|
# https://doc.rust-lang.org/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci
|
||||||
targets: ${{ matrix.platform.target }}
|
path: |
|
||||||
components: clippy
|
~/.cargo/registry/index/
|
||||||
|
~/.cargo/registry/cache/
|
||||||
|
~/.cargo/git/db/
|
||||||
|
key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-never-intended-to-be-found
|
||||||
|
restore-keys: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}
|
||||||
|
|
||||||
|
- name: Generate lockfile
|
||||||
|
# Also updates the crates.io index
|
||||||
|
run: cargo generate-lockfile
|
||||||
|
|
||||||
- name: Setup NDK path
|
|
||||||
shell: bash
|
|
||||||
# "Temporary" workaround until https://github.com/actions/virtual-environments/issues/5879#issuecomment-1195156618
|
|
||||||
# gets looked into.
|
|
||||||
run: echo "ANDROID_NDK_ROOT=$ANDROID_NDK_LATEST_HOME" >> $GITHUB_ENV
|
|
||||||
- name: Install Linux dependencies
|
|
||||||
if: (matrix.platform.os == 'ubuntu-latest')
|
|
||||||
run: sudo apt-get update && sudo apt-get install pkg-config cmake libfreetype6-dev libfontconfig1-dev
|
|
||||||
- name: Install GCC Multilib
|
- name: Install GCC Multilib
|
||||||
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
|
if: (matrix.platform.os == 'ubuntu-latest') && contains(matrix.platform.target, 'i686')
|
||||||
run: sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install g++-multilib gcc-multilib libfreetype6-dev:i386 libfontconfig1-dev:i386
|
run: sudo apt-get update && sudo apt-get install gcc-multilib
|
||||||
- name: Install cargo-apk
|
|
||||||
|
- name: Cache cargo-apk
|
||||||
if: contains(matrix.platform.target, 'android')
|
if: contains(matrix.platform.target, 'android')
|
||||||
run: cargo install cargo-apk
|
id: cargo-apk-cache
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ~/.cargo/bin/cargo-apk
|
||||||
|
# Change this key if we update the required cargo-apk version
|
||||||
|
key: cargo-apk-v0-9-7
|
||||||
|
|
||||||
|
- uses: dtolnay/rust-toolchain@master
|
||||||
|
if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true')
|
||||||
|
with:
|
||||||
|
toolchain: stable
|
||||||
|
|
||||||
|
- name: Install cargo-apk
|
||||||
|
if: contains(matrix.platform.target, 'android') && (steps.cargo-apk-cache.outputs.cache-hit != 'true')
|
||||||
|
run: cargo install cargo-apk --version=^0.9.7 --locked
|
||||||
|
|
||||||
|
- uses: dtolnay/rust-toolchain@master
|
||||||
|
with:
|
||||||
|
toolchain: ${{ matrix.toolchain }}${{ matrix.platform.host }}
|
||||||
|
targets: ${{ matrix.platform.target }}
|
||||||
|
components: clippy, ${{ matrix.platform.components }}
|
||||||
|
|
||||||
- name: Check documentation
|
- name: Check documentation
|
||||||
shell: bash
|
run: cargo doc --no-deps $OPTIONS --document-private-items
|
||||||
run: cargo $CMD doc --no-deps --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES --document-private-items
|
env:
|
||||||
|
RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }}'
|
||||||
|
|
||||||
- name: Build
|
- name: Build crate
|
||||||
shell: bash
|
run: cargo $CMD build -p winit $OPTIONS
|
||||||
run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
|
|
||||||
|
|
||||||
- name: Build tests
|
- name: Build tests
|
||||||
shell: bash
|
if: >
|
||||||
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
|
!contains(matrix.platform.target, 'redox') &&
|
||||||
|
matrix.toolchain != '1.70.0'
|
||||||
|
run: cargo $CMD test -p winit --no-run $OPTIONS
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
shell: bash
|
if: >
|
||||||
if: (
|
|
||||||
!contains(matrix.platform.target, 'android') &&
|
!contains(matrix.platform.target, 'android') &&
|
||||||
!contains(matrix.platform.target, 'ios') &&
|
!contains(matrix.platform.target, 'ios') &&
|
||||||
!contains(matrix.platform.target, 'wasm32'))
|
!contains(matrix.platform.target, 'wasm32') &&
|
||||||
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES
|
!contains(matrix.platform.target, 'redox') &&
|
||||||
|
matrix.toolchain != '1.70.0'
|
||||||
|
run: cargo $CMD test $OPTIONS
|
||||||
|
|
||||||
- name: Lint with clippy
|
- name: Lint with clippy
|
||||||
shell: bash
|
if: (matrix.toolchain == 'stable') && !contains(matrix.platform.options, '--no-default-features')
|
||||||
if: (matrix.rust_version != 'nightly') && !contains(matrix.platform.options, '--no-default-features')
|
run: cargo clippy --all-targets $OPTIONS -- -Dwarnings
|
||||||
run: cargo clippy --all-targets --target ${{ matrix.platform.target }} $OPTIONS --features $FEATURES -- -Dwarnings
|
|
||||||
|
|
||||||
- name: Build with serde enabled
|
|
||||||
shell: bash
|
|
||||||
run: cargo $CMD build --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES
|
|
||||||
|
|
||||||
- name: Build tests with serde enabled
|
- name: Build tests with serde enabled
|
||||||
shell: bash
|
if: >
|
||||||
run: cargo $CMD test --no-run --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES
|
!contains(matrix.platform.target, 'redox') &&
|
||||||
|
matrix.toolchain != '1.70.0'
|
||||||
|
run: cargo $CMD test --no-run $OPTIONS -p winit --features serde
|
||||||
|
|
||||||
- name: Run tests with serde enabled
|
- name: Run tests with serde enabled
|
||||||
shell: bash
|
if: >
|
||||||
if: (
|
|
||||||
!contains(matrix.platform.target, 'android') &&
|
!contains(matrix.platform.target, 'android') &&
|
||||||
!contains(matrix.platform.target, 'ios') &&
|
!contains(matrix.platform.target, 'ios') &&
|
||||||
!contains(matrix.platform.target, 'wasm32'))
|
!contains(matrix.platform.target, 'wasm32') &&
|
||||||
run: cargo $CMD test --verbose --target ${{ matrix.platform.target }} $OPTIONS --features serde,$FEATURES
|
!contains(matrix.platform.target, 'redox') &&
|
||||||
|
matrix.toolchain != '1.70.0'
|
||||||
|
run: cargo $CMD test $OPTIONS --features serde
|
||||||
|
|
||||||
|
- name: Check docs.rs documentation
|
||||||
|
if: matrix.toolchain == 'nightly'
|
||||||
|
run: cargo doc --no-deps $OPTIONS --features=rwh_04,rwh_05,rwh_06,serde,mint,android-native-activity
|
||||||
|
env:
|
||||||
|
RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }} --cfg=docsrs'
|
||||||
|
|
||||||
|
# See restore step above
|
||||||
|
- name: Save cache of cargo folder
|
||||||
|
uses: actions/cache/save@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry/index/
|
||||||
|
~/.cargo/registry/cache/
|
||||||
|
~/.cargo/git/db/
|
||||||
|
key: cargo-${{ matrix.toolchain }}-${{ matrix.platform.name }}-${{ hashFiles('Cargo.lock') }}
|
||||||
|
|
||||||
|
cargo-deny:
|
||||||
|
name: Run cargo-deny on ${{ matrix.platform.name }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
# TODO: remove this matrix when https://github.com/EmbarkStudios/cargo-deny/issues/324 is resolved
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- { name: 'Android', target: aarch64-linux-android }
|
||||||
|
- { name: 'iOS', target: aarch64-apple-ios }
|
||||||
|
- { name: 'Linux', target: x86_64-unknown-linux-gnu }
|
||||||
|
- { name: 'macOS', target: x86_64-apple-darwin }
|
||||||
|
- { name: 'Redox OS', target: x86_64-unknown-redox }
|
||||||
|
- { name: 'web', target: wasm32-unknown-unknown }
|
||||||
|
- { name: 'Windows', target: x86_64-pc-windows-gnu }
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: EmbarkStudios/cargo-deny-action@v1
|
||||||
|
with:
|
||||||
|
command: check
|
||||||
|
log-level: error
|
||||||
|
arguments: --all-features --target ${{ matrix.platform.target }}
|
||||||
|
|||||||
50
.github/workflows/docs.yml
vendored
Normal file
50
.github/workflows/docs.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
name: Docs
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docs:
|
||||||
|
name: Documentation
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}winit
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- uses: dtolnay/rust-toolchain@master
|
||||||
|
with:
|
||||||
|
toolchain: nightly
|
||||||
|
|
||||||
|
- name: Run Rustdoc
|
||||||
|
env:
|
||||||
|
RUSTDOCFLAGS: --crate-version master --cfg=docsrs
|
||||||
|
run: |
|
||||||
|
cargo doc --no-deps -Z rustdoc-map -Z rustdoc-scrape-examples --features=rwh_04,rwh_05,rwh_06,serde,mint,android-native-activity
|
||||||
|
|
||||||
|
- name: Setup Pages
|
||||||
|
uses: actions/configure-pages@v4
|
||||||
|
|
||||||
|
- name: Fix permissions
|
||||||
|
run: |
|
||||||
|
chmod -c -R +rX "target/doc" | while read line; do
|
||||||
|
echo "::warning title=Invalid file permissions automatically fixed::$line"
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-pages-artifact@v3
|
||||||
|
with:
|
||||||
|
path: target/doc
|
||||||
|
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v4
|
||||||
18
.github/workflows/publish.yml
vendored
18
.github/workflows/publish.yml
vendored
@@ -1,18 +0,0 @@
|
|||||||
name: Publish
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
Publish:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: hecrj/setup-rust-action@v1
|
|
||||||
with:
|
|
||||||
rust-version: stable
|
|
||||||
components: rustfmt
|
|
||||||
- name: Publish to crates.io
|
|
||||||
run: cargo publish --token ${{ secrets.cratesio_token }}
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,4 +7,4 @@ rls/
|
|||||||
*.ts
|
*.ts
|
||||||
*.js
|
*.js
|
||||||
#*#
|
#*#
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
|||||||
[submodule "deps/apk-builder"]
|
|
||||||
path = deps/apk-builder
|
|
||||||
url = https://github.com/rust-windowing/android-rs-glue
|
|
||||||
427
CHANGELOG.md
427
CHANGELOG.md
@@ -2,12 +2,432 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
Please keep one empty line before and after all headers. (This is required for `git` to produce a conflict when a release is made while a PR is open and the PR's changelog entry would go into the wrong section).
|
Please keep one empty line before and after all headers. (This is required for
|
||||||
|
`git` to produce a conflict when a release is made while a PR is open and the
|
||||||
|
PR's changelog entry would go into the wrong section).
|
||||||
|
|
||||||
And please only add new entries to the top of this list, right below the `# Unreleased` header.
|
And please only add new entries to the top of this list, right below the `#
|
||||||
|
Unreleased` header.
|
||||||
|
|
||||||
# Unreleased
|
# Unreleased
|
||||||
|
|
||||||
|
- On X11, fix swapped instance and general class names.
|
||||||
|
- **Breaking:** Removed unnecessary generic parameter `T` from `EventLoopWindowTarget`.
|
||||||
|
- On Windows, macOS, X11, Wayland and Web, implement setting images as cursors. See the `custom_cursors.rs` example.
|
||||||
|
- **Breaking:** Remove `Window::set_cursor_icon`
|
||||||
|
- Add `WindowBuilder::with_cursor` and `Window::set_cursor` which takes a `CursorIcon` or `CustomCursor`
|
||||||
|
- Add `CustomCursor`
|
||||||
|
- Add `CustomCursor::from_rgba` to allow creating cursor images from RGBA data.
|
||||||
|
- Add `CustomCursorExtWebSys::from_url` to allow loading cursor images from URLs.
|
||||||
|
- Add `CustomCursorExtWebSys::from_animation` to allow creating animated cursors from other `CustomCursor`s.
|
||||||
|
- On macOS, add services menu.
|
||||||
|
- **Breaking:** On Web, remove queuing fullscreen request in absence of transient activation.
|
||||||
|
- On Web, fix setting cursor icon overriding cursor visibility.
|
||||||
|
- **Breaking:** On Web, return `RawWindowHandle::WebCanvas` instead of `RawWindowHandle::Web`.
|
||||||
|
- **Breaking:** On Web, macOS and iOS, return `HandleError::Unavailable` when a window handle is not available.
|
||||||
|
- **Breaking:** Bump MSRV from `1.65` to `1.70`.
|
||||||
|
- On Web, add the ability to toggle calling `Event.preventDefault()` on `Window`.
|
||||||
|
- **Breaking:** Remove `WindowAttributes::fullscreen()` and expose as field directly.
|
||||||
|
- **Breaking:** Rename `VideoMode` to `VideoModeHandle` to represent that it doesn't hold static data.
|
||||||
|
- **Breaking:** No longer export `platform::x11::XNotSupported`.
|
||||||
|
- **Breaking:** Renamed `platform::x11::XWindowType` to `platform::x11::WindowType`.
|
||||||
|
- Add the `OwnedDisplayHandle` type for allowing safe display handle usage outside of trivial cases.
|
||||||
|
- **Breaking:** Rename `TouchpadMagnify` to `PinchGesture`, `SmartMagnify` to `DoubleTapGesture` and `TouchpadRotate` to `RotationGesture` to represent the action rather than the intent.
|
||||||
|
- on iOS, add detection support for `PinchGesture`, `DoubleTapGesture` and `RotationGesture`.
|
||||||
|
- on Windows: add `with_system_backdrop`, `with_border_color`, `with_title_background_color`, `with_title_text_color` and `with_corner_preference`
|
||||||
|
- On Windows, Remove `WS_CAPTION`, `WS_BORDER` and `WS_EX_WINDOWEDGE` styles for child windows without decorations.
|
||||||
|
- On Windows, fixed a race condition when sending an event through the loop proxy.
|
||||||
|
- **Breaking:** Removed `EventLoopError::AlreadyRunning`, which can't happen as it is already prevented by the type system.
|
||||||
|
- On Wayland, disable `Occluded` event handling.
|
||||||
|
- Added `EventLoop::builder`, which is intended to replace the (now deprecated) `EventLoopBuilder::new`.
|
||||||
|
- **Breaking:** Changed the signature of `EventLoop::with_user_event` to return a builder.
|
||||||
|
- **Breaking:** Removed `EventLoopBuilder::with_user_event`, the functionality is now available in `EventLoop::with_user_event`.
|
||||||
|
- Add `Window::builder`, which is intended to replace the (now deprecated) `WindowBuilder::new`.
|
||||||
|
- On X11, reload dpi on `_XSETTINGS_SETTINGS` update.
|
||||||
|
- On X11, fix deadlock when adjusting DPI and resizing at the same time.
|
||||||
|
- On Wayland, fix `Focused(false)` being send when other seats still have window focused.
|
||||||
|
- On Wayland, fix `Window::set_{min,max}_inner_size` not always applied.
|
||||||
|
- On Windows, fix inconsistent resizing behavior with multi-monitor setups when repositioning outside the event loop.
|
||||||
|
- On Wayland, fix `WAYLAND_SOCKET` not used when detecting platform.
|
||||||
|
- **Breaking:** Move some types to the `winit-core` crate. Most types are
|
||||||
|
re-exported verbatim; however:
|
||||||
|
- `Event`, `WindowEvent` and `KeyEvent` now take a generic that `winit`
|
||||||
|
implements.
|
||||||
|
- `ActivationToken` and `InnerSizeWriter` expose new methods to make them
|
||||||
|
useful.
|
||||||
|
- `InnerSizeWriter::request_inner_size` returns an `InnerSizeIgnored` error
|
||||||
|
instead of an `ExternalError`.
|
||||||
|
|
||||||
|
# 0.29.10
|
||||||
|
|
||||||
|
- On Web, account for canvas being focused already before event loop starts.
|
||||||
|
- On Web, increase cursor position accuracy.
|
||||||
|
|
||||||
|
# 0.29.9
|
||||||
|
|
||||||
|
- On X11, fix `NotSupported` error not propagated when creating event loop.
|
||||||
|
- On Wayland, fix resize not issued when scale changes
|
||||||
|
- On X11 and Wayland, fix arrow up on keypad reported as `ArrowLeft`.
|
||||||
|
- On macOS, report correct logical key when Ctrl or Cmd is pressed.
|
||||||
|
|
||||||
|
# 0.29.8
|
||||||
|
|
||||||
|
- On X11, fix IME input lagging behind.
|
||||||
|
- On X11, fix `ModifiersChanged` not sent from xdotool-like input
|
||||||
|
- On X11, fix keymap not updated from xmodmap.
|
||||||
|
- On X11, reduce the amount of time spent fetching screen resources.
|
||||||
|
- On Wayland, fix `Window::request_inner_size` being overwritten by resize.
|
||||||
|
- On Wayland, fix `Window::inner_size` not using the correct rounding.
|
||||||
|
|
||||||
|
# 0.29.7
|
||||||
|
|
||||||
|
- On X11, fix `Xft.dpi` reload during runtime.
|
||||||
|
- On X11, fix window minimize.
|
||||||
|
|
||||||
|
# 0.29.6
|
||||||
|
|
||||||
|
- On Web, fix context menu not being disabled by `with_prevent_default(true)`.
|
||||||
|
- On Wayland, fix `WindowEvent::Destroyed` not being delivered after destroying window.
|
||||||
|
- Fix `EventLoopExtRunOnDemand::run_on_demand` not working for consequent invocation
|
||||||
|
|
||||||
|
# 0.29.5
|
||||||
|
|
||||||
|
- On macOS, remove spurious error logging when handling `Fn`.
|
||||||
|
- On X11, fix an issue where floating point data from the server is
|
||||||
|
misinterpreted during a drag and drop operation.
|
||||||
|
- On X11, fix a bug where focusing the window would panic.
|
||||||
|
- On macOS, fix `refresh_rate_millihertz`.
|
||||||
|
- On Wayland, disable Client Side Decorations when `wl_subcompositor` is not supported.
|
||||||
|
- On X11, fix `Xft.dpi` detection from Xresources.
|
||||||
|
- On Windows, fix consecutive calls to `window.set_fullscreen(Some(Fullscreen::Borderless(None)))` resulting in losing previous window state when eventually exiting fullscreen using `window.set_fullscreen(None)`.
|
||||||
|
- On Wayland, fix resize being sent on focus change.
|
||||||
|
- On Windows, fix `set_ime_cursor_area`.
|
||||||
|
|
||||||
|
# 0.29.4
|
||||||
|
|
||||||
|
- Fix crash when running iOS app on macOS.
|
||||||
|
- On X11, check common alternative cursor names when loading cursor.
|
||||||
|
- On X11, reload the DPI after a property change event.
|
||||||
|
- On Windows, fix so `drag_window` and `drag_resize_window` can be called from another thread.
|
||||||
|
- On Windows, fix `set_control_flow` in `AboutToWait` not being taken in account.
|
||||||
|
- On macOS, send a `Resized` event after each `ScaleFactorChanged` event.
|
||||||
|
- On Wayland, fix `wl_surface` being destroyed before associated objects.
|
||||||
|
- On macOS, fix assertion when pressing `Fn` key.
|
||||||
|
- On Windows, add `WindowBuilderExtWindows::with_clip_children` to control `WS_CLIPCHILDREN` style.
|
||||||
|
|
||||||
|
# 0.29.3
|
||||||
|
|
||||||
|
- On Wayland, apply correct scale to `PhysicalSize` passed in `WindowBuilder::with_inner_size` when possible.
|
||||||
|
- On Wayland, fix `RedrawRequsted` being always sent without decorations and `sctk-adwaita` feature.
|
||||||
|
- On Wayland, ignore resize requests when the window is fully tiled.
|
||||||
|
- On Wayland, use `configure_bounds` to constrain `with_inner_size` when compositor wants users to pick size.
|
||||||
|
- On Windows, fix deadlock when accessing the state during `Cursor{Enter,Leave}`.
|
||||||
|
- On Windows, add support for `Window::set_transparent`.
|
||||||
|
- On macOS, fix deadlock when entering a nested event loop from an event handler.
|
||||||
|
- On macOS, add support for `Window::set_blur`.
|
||||||
|
|
||||||
|
# 0.29.2
|
||||||
|
|
||||||
|
- **Breaking:** Bump MSRV from `1.60` to `1.65`.
|
||||||
|
- **Breaking:** Add `Event::MemoryWarning`; implemented on iOS/Android.
|
||||||
|
- **Breaking:** Bump `ndk` version to `0.8.0`, ndk-sys to `0.5.0`, `android-activity` to `0.5.0`.
|
||||||
|
- **Breaking:** Change default `ControlFlow` from `Poll` to `Wait`.
|
||||||
|
- **Breaking:** Move `Event::RedrawRequested` to `WindowEvent::RedrawRequested`.
|
||||||
|
- **Breaking:** Moved `ControlFlow::Exit` to `EventLoopWindowTarget::exit()` and `EventLoopWindowTarget::exiting()` and removed `ControlFlow::ExitWithCode(_)` entirely.
|
||||||
|
- **Breaking:** Moved `ControlFlow` to `EventLoopWindowTarget::set_control_flow()` and `EventLoopWindowTarget::control_flow()`.
|
||||||
|
- **Breaking:** `EventLoop::new` and `EventLoopBuilder::build` now return `Result<Self, EventLoopError>`
|
||||||
|
- **Breaking:** `WINIT_UNIX_BACKEND` was removed in favor of standard `WAYLAND_DISPLAY` and `DISPLAY` variables.
|
||||||
|
- **Breaking:** on Wayland, dispatching user created Wayland queue won't wake up the loop unless winit has event to send back.
|
||||||
|
- **Breaking:** remove `DeviceEvent::Text`.
|
||||||
|
- **Breaking:** Remove lifetime parameter from `Event` and `WindowEvent`.
|
||||||
|
- **Breaking:** Rename `Window::set_inner_size` to `Window::request_inner_size` and indicate if the size was applied immediately.
|
||||||
|
- **Breaking:** `ActivationTokenDone` event which could be requested with the new `startup_notify` module, see its docs for more.
|
||||||
|
- **Breaking:** `ScaleFactorChanged` now contains a writer instead of a reference to update inner size.
|
||||||
|
- **Breaking** `run() -> !` has been replaced by `run() -> Result<(), EventLoopError>` for returning errors without calling `std::process::exit()` ([#2767](https://github.com/rust-windowing/winit/pull/2767))
|
||||||
|
- **Breaking** Removed `EventLoopExtRunReturn` / `run_return` in favor of `EventLoopExtPumpEvents` / `pump_events` and `EventLoopExtRunOnDemand` / `run_on_demand` ([#2767](https://github.com/rust-windowing/winit/pull/2767))
|
||||||
|
- `RedrawRequested` is no longer guaranteed to be emitted after `MainEventsCleared`, it is now platform-specific when the event is emitted after being requested via `redraw_request()`.
|
||||||
|
- On Windows, `RedrawRequested` is now driven by `WM_PAINT` messages which are requested via `redraw_request()`
|
||||||
|
- **Breaking** `LoopDestroyed` renamed to `LoopExiting` ([#2900](https://github.com/rust-windowing/winit/issues/2900))
|
||||||
|
- **Breaking** `RedrawEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
|
||||||
|
- **Breaking** `MainEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
|
||||||
|
- **Breaking:** Remove all deprecated `modifiers` fields.
|
||||||
|
- **Breaking:** Rename `DeviceEventFilter` to `DeviceEvents` reversing the behavior of variants.
|
||||||
|
- **Breaking** Add `AboutToWait` event which is emitted when the event loop is about to block and wait for new events ([#2900](https://github.com/rust-windowing/winit/issues/2900))
|
||||||
|
- **Breaking:** Rename `EventLoopWindowTarget::set_device_event_filter` to `listen_device_events`.
|
||||||
|
- **Breaking:** Rename `Window::set_ime_position` to `Window::set_ime_cursor_area` adding a way to set exclusive zone.
|
||||||
|
- **Breaking:** `with_x11_visual` now takes the visual ID instead of the bare pointer.
|
||||||
|
- **Breaking** `MouseButton` now supports `Back` and `Forward` variants, emitted from mouse events on Wayland, X11, Windows, macOS and Web.
|
||||||
|
- **Breaking:** On Web, `instant` is now replaced by `web_time`.
|
||||||
|
- **Breaking:** On Web, dropped support for Safari versions below 13.1.
|
||||||
|
- **Breaking:** On Web, the canvas output bitmap size is no longer adjusted.
|
||||||
|
- **Breaking:** On Web, the canvas size is not controlled by Winit anymore and external changes to the canvas size will be reported through `WindowEvent::Resized`.
|
||||||
|
- **Breaking:** Updated `bitflags` crate version to `2`, which changes the API on exposed types.
|
||||||
|
- **Breaking:** `CursorIcon::Arrow` was removed.
|
||||||
|
- **Breaking:** `CursorIcon::Hand` is now named `CursorIcon::Pointer`.
|
||||||
|
- **Breaking:** `CursorIcon` is now used from the `cursor-icon` crate.
|
||||||
|
- **Breaking:** `WindowExtWebSys::canvas()` now returns an `Option`.
|
||||||
|
- **Breaking:** Overhaul keyboard input handling.
|
||||||
|
- Replace `KeyboardInput` with `KeyEvent` and `RawKeyEvent`.
|
||||||
|
- Change `WindowEvent::KeyboardInput` to contain a `KeyEvent`.
|
||||||
|
- Change `Event::Key` to contain a `RawKeyEvent`.
|
||||||
|
- Remove `Event::ReceivedCharacter`. In its place, you should use
|
||||||
|
`KeyEvent.text` in combination with `WindowEvent::Ime`.
|
||||||
|
- Replace `VirtualKeyCode` with the `Key` enum.
|
||||||
|
- Replace `ScanCode` with the `KeyCode` enum.
|
||||||
|
- Rename `ModifiersState::LOGO` to `SUPER` and `ModifiersState::CTRL` to `CONTROL`.
|
||||||
|
- Add `PhysicalKey` wrapping `KeyCode` and `NativeKeyCode`.
|
||||||
|
- Add `KeyCode` to refer to keys (roughly) by their physical location.
|
||||||
|
- Add `NativeKeyCode` to represent raw `KeyCode`s which Winit doesn't
|
||||||
|
understand.
|
||||||
|
- Add `Key` to represent the keys after they've been interpreted by the
|
||||||
|
active (software) keyboard layout.
|
||||||
|
- Add `NamedKey` to represent the categorized keys.
|
||||||
|
- Add `NativeKey` to represent raw `Key`s which Winit doesn't understand.
|
||||||
|
- Add `KeyLocation` to tell apart `Key`s which usually "mean" the same thing,
|
||||||
|
but can appear simultaneously in different spots on the same keyboard
|
||||||
|
layout.
|
||||||
|
- Add `Window::reset_dead_keys` to enable application-controlled cancellation
|
||||||
|
of dead key sequences.
|
||||||
|
- Add `KeyEventExtModifierSupplement` to expose additional (and less
|
||||||
|
portable) interpretations of a given key-press.
|
||||||
|
- Add `PhysicalKeyExtScancode`, which lets you convert between scancodes and
|
||||||
|
`PhysicalKey`.
|
||||||
|
- `ModifiersChanged` now uses dedicated `Modifiers` struct.
|
||||||
|
- Removed platform-specific extensions that should be retrieved through `raw-window-handle` trait implementations instead:
|
||||||
|
- `platform::windows::HINSTANCE`.
|
||||||
|
- `WindowExtWindows::hinstance`.
|
||||||
|
- `WindowExtWindows::hwnd`.
|
||||||
|
- `WindowExtIOS::ui_window`.
|
||||||
|
- `WindowExtIOS::ui_view_controller`.
|
||||||
|
- `WindowExtIOS::ui_view`.
|
||||||
|
- `WindowExtMacOS::ns_window`.
|
||||||
|
- `WindowExtMacOS::ns_view`.
|
||||||
|
- `EventLoopWindowTargetExtWayland::wayland_display`.
|
||||||
|
- `WindowExtWayland::wayland_surface`.
|
||||||
|
- `WindowExtWayland::wayland_display`.
|
||||||
|
- `WindowExtX11::xlib_window`.
|
||||||
|
- `WindowExtX11::xlib_display`.
|
||||||
|
- `WindowExtX11::xlib_screen_id`.
|
||||||
|
- `WindowExtX11::xcb_connection`.
|
||||||
|
- Reexport `raw-window-handle` in `window` module.
|
||||||
|
- Add `ElementState::is_pressed`.
|
||||||
|
- Add `Window::pre_present_notify` to notify winit before presenting to the windowing system.
|
||||||
|
- Add `Window::set_blur` to request a blur behind the window; implemented on Wayland for now.
|
||||||
|
- Add `Window::show_window_menu` to request a titlebar/system menu; implemented on Wayland/Windows for now.
|
||||||
|
- Implement `AsFd`/`AsRawFd` for `EventLoop<T>` on X11 and Wayland.
|
||||||
|
- Implement `PartialOrd` and `Ord` for `MouseButton`.
|
||||||
|
- Implement `PartialOrd` and `Ord` on types in the `dpi` module.
|
||||||
|
- Make `WindowBuilder` `Send + Sync`.
|
||||||
|
- Make iOS `MonitorHandle` and `VideoMode` usable from other threads.
|
||||||
|
- Make iOS windows usable from other threads.
|
||||||
|
- On Android, add force data to touch events.
|
||||||
|
- On Android, added `EventLoopBuilderExtAndroid::handle_volume_keys` to indicate that the application will handle the volume keys manually.
|
||||||
|
- On Android, fix `DeviceId` to contain device id's.
|
||||||
|
- On Orbital, fix `ModifiersChanged` not being sent.
|
||||||
|
- On Wayland, `Window::outer_size` now accounts for **client side** decorations.
|
||||||
|
- On Wayland, add `Window::drag_resize_window` method.
|
||||||
|
- On Wayland, remove `WINIT_WAYLAND_CSD_THEME` variable.
|
||||||
|
- On Wayland, fix `TouchPhase::Canceled` being sent for moved events.
|
||||||
|
- On Wayland, fix forward compatibility issues.
|
||||||
|
- On Wayland, fix initial window size not restored for maximized/fullscreened on startup window.
|
||||||
|
- On Wayland, fix maximized startup not taking full size on GNOME.
|
||||||
|
- On Wayland, fix maximized window creation and window geometry handling.
|
||||||
|
- On Wayland, fix window not checking that it actually got initial configure event.
|
||||||
|
- On Wayland, make double clicking and moving the CSD frame more reliable.
|
||||||
|
- On Wayland, support `Occluded` event with xdg-shell v6
|
||||||
|
- On Wayland, use frame callbacks to throttle `RedrawRequested` events so redraws will align with compositor.
|
||||||
|
- On Web, `ControlFlow::WaitUntil` now uses the Prioritized Task Scheduling API. `setTimeout()`, with a trick to circumvent throttling to 4ms, is used as a fallback.
|
||||||
|
- On Web, `EventLoopProxy` now implements `Send`.
|
||||||
|
- On Web, `Window` now implements `Send` and `Sync`.
|
||||||
|
- On Web, account for CSS `padding`, `border`, and `margin` when getting or setting the canvas position.
|
||||||
|
- On Web, add Fullscreen API compatibility for Safari.
|
||||||
|
- On Web, add `DeviceEvent::Motion`, `DeviceEvent::MouseWheel`, `DeviceEvent::Button` and `DeviceEvent::Key` support.
|
||||||
|
- On Web, add `EventLoopWindowTargetExtWebSys` and `PollStrategy`, which allows to set different strategies for `ControlFlow::Poll`. By default the Prioritized Task Scheduling API is used, but an option to use `Window.requestIdleCallback` is available as well. Both use `setTimeout()`, with a trick to circumvent throttling to 4ms, as a fallback.
|
||||||
|
- On Web, add `WindowBuilderExtWebSys::with_append()` to append the canvas element to the web page on creation.
|
||||||
|
- On Web, allow event loops to be recreated with `spawn`.
|
||||||
|
- On Web, enable event propagation.
|
||||||
|
- On Web, fix `ControlFlow::WaitUntil` to never wake up **before** the given time.
|
||||||
|
- On Web, fix `DeviceEvent::MouseMotion` only being emitted for each canvas instead of the whole window.
|
||||||
|
- On Web, fix `Window:::set_fullscreen` doing nothing when called outside the event loop but during transient activation.
|
||||||
|
- On Web, fix pen treated as mouse input.
|
||||||
|
- On Web, fix pointer button events not being processed when a buttons is already pressed.
|
||||||
|
- On Web, fix scale factor resize suggestion always overwriting the canvas size.
|
||||||
|
- On Web, fix some `WindowBuilder` methods doing nothing.
|
||||||
|
- On Web, fix some `Window` methods using incorrect HTML attributes instead of CSS properties.
|
||||||
|
- On Web, fix the bfcache by not using the `beforeunload` event and map bfcache loading/unloading to `Suspended`/`Resumed` events.
|
||||||
|
- On Web, fix touch input not gaining or loosing focus.
|
||||||
|
- On Web, fix touch location to be as accurate as mouse position.
|
||||||
|
- On Web, handle coalesced pointer events, which increases the resolution of pointer inputs.
|
||||||
|
- On Web, implement `Window::focus_window()`.
|
||||||
|
- On Web, implement `Window::set_(min|max)_inner_size()`.
|
||||||
|
- On Web, implement `WindowEvent::Occluded`.
|
||||||
|
- On Web, never return a `MonitorHandle`.
|
||||||
|
- On Web, prevent clicks on the canvas to select text.
|
||||||
|
- On Web, remove any fullscreen requests from the queue when an external fullscreen activation was detected.
|
||||||
|
- On Web, remove unnecessary `Window::is_dark_mode()`, which was replaced with `Window::theme()`.
|
||||||
|
- On Web, respect `EventLoopWindowTarget::listen_device_events()` settings.
|
||||||
|
- On Web, scale factor and dark mode detection are now more robust.
|
||||||
|
- On Web, send mouse position on button release as well.
|
||||||
|
- On Web, take all transient activations on the canvas and window into account to queue a fullscreen request.
|
||||||
|
- On Web, use `Window.requestAnimationFrame()` to throttle `RedrawRequested` events.
|
||||||
|
- On Web, use the correct canvas size when calculating the new size during scale factor change, instead of using the output bitmap size.
|
||||||
|
- On Web: fix `Window::request_redraw` not waking the event loop when called from outside the loop.
|
||||||
|
- On Web: fix position of touch events to be relative to the canvas.
|
||||||
|
- On Windows, add `drag_resize_window` method support.
|
||||||
|
- On Windows, add horizontal MouseWheel `DeviceEvent`.
|
||||||
|
- On Windows, added `WindowBuilderExtWindows::with_class_name` to customize the internal class name.
|
||||||
|
- On Windows, fix IME APIs not working when from non event loop thread.
|
||||||
|
- On Windows, fix `CursorEnter/Left` not being sent when grabbing the mouse.
|
||||||
|
- On Windows, fix `RedrawRequested` not being delivered when calling `Window::request_redraw` from `RedrawRequested`.
|
||||||
|
- On Windows, port to `windows-sys` version 0.48.0.
|
||||||
|
- On X11, add a `with_embedded_parent_window` function to the window builder to allow embedding a window into another window.
|
||||||
|
- On X11, fix event loop not waking up on `ControlFlow::Poll` and `ControlFlow::WaitUntil`.
|
||||||
|
- On X11, fix false positive flagging of key repeats when pressing different keys with no release between presses.
|
||||||
|
- On X11, set `visual_id` in returned `raw-window-handle`.
|
||||||
|
- On iOS, add ability to change the status bar style.
|
||||||
|
- On iOS, add force data to touch events when using the Apple Pencil.
|
||||||
|
- On iOS, always wake the event loop when transitioning from `ControlFlow::Poll` to `ControlFlow::Poll`.
|
||||||
|
- On iOS, send events `WindowEvent::Occluded(false)`, `WindowEvent::Occluded(true)` when application enters/leaves foreground.
|
||||||
|
- On macOS, add tabbing APIs on `WindowExtMacOS` and `EventLoopWindowTargetExtMacOS`.
|
||||||
|
- On macOS, fix assertion when pressing `Globe` key.
|
||||||
|
- On macOS, fix crash in `window.set_minimized(false)`.
|
||||||
|
- On macOS, fix crash when dropping `Window`.
|
||||||
|
|
||||||
|
# 0.28.7
|
||||||
|
|
||||||
|
- Fix window size sometimes being invalid when resizing on macOS 14 Sonoma.
|
||||||
|
|
||||||
|
# 0.28.6
|
||||||
|
|
||||||
|
- On macOS, fixed memory leak when getting monitor handle.
|
||||||
|
- On macOS, fix `Backspace` being emitted when clearing preedit with it.
|
||||||
|
|
||||||
|
# 0.28.5
|
||||||
|
|
||||||
|
- On macOS, fix `key_up` being ignored when `Ime` is disabled.
|
||||||
|
|
||||||
|
# 0.28.4
|
||||||
|
|
||||||
|
- On macOS, fix empty marked text blocking regular input.
|
||||||
|
- On macOS, fix potential panic when getting refresh rate.
|
||||||
|
- On macOS, fix crash when calling `Window::set_ime_position` from another thread.
|
||||||
|
|
||||||
|
# 0.28.3
|
||||||
|
|
||||||
|
- Fix macOS memory leaks.
|
||||||
|
|
||||||
|
# 0.28.2
|
||||||
|
|
||||||
|
- Implement `HasRawDisplayHandle` for `EventLoop`.
|
||||||
|
- On macOS, set resize increments only for live resizes.
|
||||||
|
- On Wayland, fix rare crash on DPI change
|
||||||
|
- Web: Added support for `Window::theme`.
|
||||||
|
- On Wayland, fix rounding issues when doing resize.
|
||||||
|
- On macOS, fix wrong focused state on startup.
|
||||||
|
- On Windows, fix crash on setting taskbar when using Visual Studio debugger.
|
||||||
|
- On macOS, resize simple fullscreen windows on windowDidChangeScreen events.
|
||||||
|
|
||||||
|
# 0.28.1
|
||||||
|
|
||||||
|
- On Wayland, fix crash when dropping a window in multi-window setup.
|
||||||
|
|
||||||
|
# 0.28.0
|
||||||
|
|
||||||
|
- On macOS, fixed `Ime::Commit` persisting for all input after interacting with `Ime`.
|
||||||
|
- On macOS, added `WindowExtMacOS::option_as_alt` and `WindowExtMacOS::set_option_as_alt`.
|
||||||
|
- On Windows, fix window size for maximized, undecorated windows.
|
||||||
|
- On Windows and macOS, add `WindowBuilder::with_active`.
|
||||||
|
- Add `Window::is_minimized`.
|
||||||
|
- On X11, fix errors handled during `register_xlib_error_hook` invocation bleeding into winit.
|
||||||
|
- Add `Window::has_focus`.
|
||||||
|
- On Windows, fix `Window::set_minimized(false)` not working for windows minimized by `Win + D` hotkey.
|
||||||
|
- **Breaking:** On Web, touch input no longer fires `WindowEvent::Cursor*`, `WindowEvent::MouseInput`, or `DeviceEvent::MouseMotion` like other platforms, but instead it fires `WindowEvent::Touch`.
|
||||||
|
- **Breaking:** Removed platform specific `WindowBuilder::with_parent` API in favor of `WindowBuilder::with_parent_window`.
|
||||||
|
- On Windows, retain `WS_MAXIMIZE` window style when un-minimizing a maximized window.
|
||||||
|
- On Windows, fix left mouse button release event not being sent after `Window::drag_window`.
|
||||||
|
- On macOS, run most actions on the main thread, which is strictly more correct, but might make multithreaded applications block slightly more.
|
||||||
|
- On macOS, fix panic when getting current monitor without any monitor attached.
|
||||||
|
- On Windows and MacOS, add API to enable/disable window buttons (close, minimize, ...etc).
|
||||||
|
- On Windows, macOS, X11 and Wayland, add `Window::set_theme`.
|
||||||
|
- **Breaking:** Remove `WindowExtWayland::wayland_set_csd_theme` and `WindowBuilderExtX11::with_gtk_theme_variant`.
|
||||||
|
- On Windows, revert window background to an empty brush to avoid white flashes when changing scaling.
|
||||||
|
- **Breaking:** Removed `Window::set_always_on_top` and related APIs in favor of `Window::set_window_level`.
|
||||||
|
- On Windows, MacOS and X11, add always on bottom APIs.
|
||||||
|
- On Windows, fix the value in `MouseButton::Other`.
|
||||||
|
- On macOS, add `WindowExtMacOS::is_document_edited` and `WindowExtMacOS::set_document_edited` APIs.
|
||||||
|
- **Breaking:** Removed `WindowBuilderExtIOS::with_root_view_class`; instead, you should use `[[view layer] addSublayer: ...]` to add an instance of the desired layer class (e.g. `CAEAGLLayer` or `CAMetalLayer`). See `vulkano-win` or `wgpu` for examples of this.
|
||||||
|
- On MacOS and Windows, add `Window::set_content_protected`.
|
||||||
|
- On MacOS, add `EventLoopBuilderExtMacOS::with_activate_ignoring_other_apps`.
|
||||||
|
- On Windows, fix icons specified on `WindowBuilder` not taking effect for windows created after the first one.
|
||||||
|
- On Windows and macOS, add `Window::title` to query the current window title.
|
||||||
|
- On Windows, fix focusing menubar when pressing `Alt`.
|
||||||
|
- On MacOS, made `accepts_first_mouse` configurable.
|
||||||
|
- Migrated `WindowBuilderExtUnix::with_resize_increments` to `WindowBuilder`.
|
||||||
|
- Added `Window::resize_increments`/`Window::set_resize_increments` to update resize increments at runtime for X11/macOS.
|
||||||
|
- macOS/iOS: Use `objc2` instead of `objc` internally.
|
||||||
|
- **Breaking:** Bump MSRV from `1.57` to `1.60`.
|
||||||
|
- **Breaking:** Split the `platform::unix` module into `platform::x11` and `platform::wayland`. The extension types are similarly renamed.
|
||||||
|
- **Breaking:**: Removed deprecated method `platform::unix::WindowExtUnix::is_ready`.
|
||||||
|
- Removed `parking_lot` dependency.
|
||||||
|
- **Breaking:** On macOS, add support for two-finger touchpad magnification and rotation gestures with new events `WindowEvent::TouchpadMagnify` and `WindowEvent::TouchpadRotate`. Also add support for touchpad smart-magnification gesture with a new event `WindowEvent::SmartMagnify`.
|
||||||
|
- **Breaking:** On web, the `WindowBuilderExtWebSys::with_prevent_default` setting (enabled by default), now additionally prevents scrolling of the webpage in mobile browsers, previously it only disabled scrolling on desktop.
|
||||||
|
- On Wayland, `wayland-csd-adwaita` now uses `ab_glyph` instead of `crossfont` to render the title for decorations.
|
||||||
|
- On Wayland, a new `wayland-csd-adwaita-crossfont` feature was added to use `crossfont` instead of `ab_glyph` for decorations.
|
||||||
|
- On Wayland, if not otherwise specified use upstream automatic CSD theme selection.
|
||||||
|
- On X11, added `WindowExtX11::with_parent` to create child windows.
|
||||||
|
- Added support for `WindowBuilder::with_theme` and `Window::theme` to support per-window dark/light/system theme configuration on macos, windows and wayland.
|
||||||
|
- On macOS, added support for `WindowEvent::ThemeChanged`.
|
||||||
|
- **Breaking:** Removed `WindowBuilderExtWindows::with_theme` and `WindowBuilderExtWayland::with_wayland_csd_theme` in favour of `WindowBuilder::with_theme`.
|
||||||
|
- **Breaking:** Removed `WindowExtWindows::theme` in favour of `Window::theme`.
|
||||||
|
- Enabled `doc_auto_cfg` when generating docs on docs.rs for feature labels.
|
||||||
|
- **Breaking:** On Android, switched to using [`android-activity`](https://github.com/rib/android-activity) crate as a glue layer instead of [`ndk-glue`](https://github.com/rust-windowing/android-ndk-rs/tree/master/ndk-glue). See [README.md#Android](https://github.com/rust-windowing/winit#Android) for more details. ([#2444](https://github.com/rust-windowing/winit/pull/2444))
|
||||||
|
- **Breaking:** Removed support for `raw-window-handle` version `0.4`
|
||||||
|
- On Wayland, `RedrawRequested` not emitted during resize.
|
||||||
|
- Add a `set_wait_timeout` function to `ControlFlow` to allow waiting for a `Duration`.
|
||||||
|
- **Breaking:** Remove the unstable `xlib_xconnection()` function from the private interface.
|
||||||
|
- Added Orbital support for Redox OS
|
||||||
|
- On X11, added `drag_resize_window` method.
|
||||||
|
- Added `Window::set_transparent` to provide a hint about transparency of the window on Wayland and macOS.
|
||||||
|
- On macOS, fix the mouse buttons other than left/right/middle being reported as middle.
|
||||||
|
- On Wayland, support fractional scaling via the wp-fractional-scale protocol.
|
||||||
|
- On web, fix removal of mouse event listeners from the global object upon window distruction.
|
||||||
|
- Add WindowAttributes getter to WindowBuilder to allow introspection of default values.
|
||||||
|
- Added `Window::set_ime_purpose` for setting the IME purpose, currently implemented on Wayland only.
|
||||||
|
|
||||||
|
# 0.27.5
|
||||||
|
|
||||||
|
- On Wayland, fix byte offset in `Ime::Preedit` pointing to invalid bytes.
|
||||||
|
|
||||||
|
# 0.27.4
|
||||||
|
|
||||||
|
- On Windows, emit `ReceivedCharacter` events on system keybindings.
|
||||||
|
- On Windows, fixed focus event emission on minimize.
|
||||||
|
- On X11, fixed IME crashing during reload.
|
||||||
|
|
||||||
|
# 0.27.3
|
||||||
|
|
||||||
|
- On Windows, added `WindowExtWindows::set_undecorated_shadow` and `WindowBuilderExtWindows::with_undecorated_shadow` to draw the drop shadow behind a borderless window.
|
||||||
|
- On Windows, fixed default window features (ie snap, animations, shake, etc.) when decorations are disabled.
|
||||||
|
- On Windows, fixed ALT+Space shortcut to open window menu.
|
||||||
|
- On Wayland, fixed `Ime::Preedit` not being sent on IME reset.
|
||||||
|
- Fixed unbound version specified for `raw-window-handle` leading to compilation failures.
|
||||||
|
- Empty `Ime::Preedit` event will be sent before `Ime::Commit` to help clearing preedit.
|
||||||
|
- On X11, fixed IME context picking by querying for supported styles beforehand.
|
||||||
|
|
||||||
|
# 0.27.2 (2022-8-12)
|
||||||
|
|
||||||
|
- On macOS, fixed touch phase reporting when scrolling.
|
||||||
|
- On X11, fix min, max and resize increment hints not persisting for resizable windows (e.g. on DPI change).
|
||||||
|
- On Windows, respect min/max inner sizes when creating the window.
|
||||||
|
- For backwards compatibility, `Window` now (additionally) implements the old version (`0.4`) of the `HasRawWindowHandle` trait
|
||||||
|
- On Windows, added support for `EventLoopWindowTarget::set_device_event_filter`.
|
||||||
|
- On Wayland, fix user requested `WindowEvent::RedrawRequested` being delayed by a frame.
|
||||||
|
|
||||||
|
# 0.27.1 (2022-07-30)
|
||||||
|
|
||||||
|
- The minimum supported Rust version was lowered to `1.57.0` and now explicitly tested.
|
||||||
|
- On X11, fix crash on start due to inability to create an IME context without any preedit.
|
||||||
|
|
||||||
# 0.27.0 (2022-07-26)
|
# 0.27.0 (2022-07-26)
|
||||||
|
|
||||||
- On Windows, fix hiding a maximized window.
|
- On Windows, fix hiding a maximized window.
|
||||||
@@ -80,6 +500,7 @@ And please only add new entries to the top of this list, right below the `# Unre
|
|||||||
- On Android, upgrade `ndk` and `ndk-glue` dependencies to the recently released `0.7.0`.
|
- On Android, upgrade `ndk` and `ndk-glue` dependencies to the recently released `0.7.0`.
|
||||||
- All platforms can now be relied on to emit a `Resumed` event. Applications are recommended to lazily initialize graphics state and windows on first resume for portability.
|
- All platforms can now be relied on to emit a `Resumed` event. Applications are recommended to lazily initialize graphics state and windows on first resume for portability.
|
||||||
- **Breaking:**: Reverse horizontal scrolling sign in `MouseScrollDelta` to match the direction of vertical scrolling. A positive X value now means moving the content to the right. The meaning of vertical scrolling stays the same: a positive Y value means moving the content down.
|
- **Breaking:**: Reverse horizontal scrolling sign in `MouseScrollDelta` to match the direction of vertical scrolling. A positive X value now means moving the content to the right. The meaning of vertical scrolling stays the same: a positive Y value means moving the content down.
|
||||||
|
- On MacOS, fix deadlock when calling `set_maximized` from event loop.
|
||||||
|
|
||||||
# 0.26.1 (2022-01-05)
|
# 0.26.1 (2022-01-05)
|
||||||
|
|
||||||
@@ -181,7 +602,7 @@ And please only add new entries to the top of this list, right below the `# Unre
|
|||||||
|
|
||||||
# 0.23.0 (2020-10-02)
|
# 0.23.0 (2020-10-02)
|
||||||
|
|
||||||
- On iOS, fixed support for the "Debug View Heirarchy" feature in Xcode.
|
- On iOS, fixed support for the "Debug View Hierarchy" feature in Xcode.
|
||||||
- On all platforms, `available_monitors` and `primary_monitor` are now on `EventLoopWindowTarget` rather than `EventLoop` to list monitors event in the event loop.
|
- On all platforms, `available_monitors` and `primary_monitor` are now on `EventLoopWindowTarget` rather than `EventLoop` to list monitors event in the event loop.
|
||||||
- On Unix, X11 and Wayland are now optional features (enabled by default)
|
- On Unix, X11 and Wayland are now optional features (enabled by default)
|
||||||
- On X11, fix deadlock when calling `set_fullscreen_inner`.
|
- On X11, fix deadlock when calling `set_fullscreen_inner`.
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ may be worth creating a separate crate that extends Winit's API to add that func
|
|||||||
When reporting an issue, in order to help the maintainers understand what the problem is, please make
|
When reporting an issue, in order to help the maintainers understand what the problem is, please make
|
||||||
your description of the issue as detailed as possible:
|
your description of the issue as detailed as possible:
|
||||||
|
|
||||||
- if it is a bug, please provide clear explanation of what happens, what should happen, and how to
|
- if it is a bug, please provide a clear explanation of what happens, what should happen, and how to
|
||||||
reproduce the issue, ideally by providing a minimal program exhibiting the problem
|
reproduce the issue, ideally by providing a minimal program exhibiting the problem
|
||||||
- if it is a feature request, please provide a clear argumentation about why you believe this feature
|
- if it is a feature request, please provide a clear argumentation about why you believe this feature
|
||||||
should be supported by winit
|
should be supported by winit
|
||||||
@@ -20,7 +20,8 @@ your description of the issue as detailed as possible:
|
|||||||
|
|
||||||
When making a code contribution to winit, before opening your pull request, please make sure that:
|
When making a code contribution to winit, before opening your pull request, please make sure that:
|
||||||
|
|
||||||
- you tested your modifications on all the platforms impacted, or if not possible detail which platforms
|
- your patch builds with Winit's minimal supported rust version - Rust 1.70.
|
||||||
|
- you tested your modifications on all the platforms impacted, or if not possible, detail which platforms
|
||||||
were not tested, and what should be tested, so that a maintainer or another contributor can test them
|
were not tested, and what should be tested, so that a maintainer or another contributor can test them
|
||||||
- you updated any relevant documentation in winit
|
- you updated any relevant documentation in winit
|
||||||
- you left comments in your code explaining any part that is not straightforward, so that the
|
- you left comments in your code explaining any part that is not straightforward, so that the
|
||||||
@@ -33,7 +34,7 @@ When making a code contribution to winit, before opening your pull request, plea
|
|||||||
relevant sections in [`FEATURES.md`](https://github.com/rust-windowing/winit/blob/master/FEATURES.md#features)
|
relevant sections in [`FEATURES.md`](https://github.com/rust-windowing/winit/blob/master/FEATURES.md#features)
|
||||||
should be updated.
|
should be updated.
|
||||||
|
|
||||||
Once your PR is open, you can ask for review by a maintainer of your platform. Winit's merging policy
|
Once your PR is open, you can ask for a review by a maintainer of your platform. Winit's merging policy
|
||||||
is that a PR must be approved by at least two maintainers of winit before being merged, including
|
is that a PR must be approved by at least two maintainers of winit before being merged, including
|
||||||
at least a maintainer of the platform (a maintainer making a PR themselves counts as approving it).
|
at least a maintainer of the platform (a maintainer making a PR themselves counts as approving it).
|
||||||
|
|
||||||
@@ -43,20 +44,28 @@ Once your PR is deemed ready, the merging maintainer will take care of resolving
|
|||||||
|
|
||||||
## Maintainers & Testers
|
## Maintainers & Testers
|
||||||
|
|
||||||
The current [list of testers and contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors)
|
The current maintainers are listed in the [CODEOWNERS](.github/CODEOWNERS) file.
|
||||||
can be found on the Wiki.
|
|
||||||
|
|
||||||
If you are interested in contributing or testing on a platform, please add yourself to that table!
|
If you are interested in being pinged when testing is needed for a specific platform, please add yourself to the [Testers and Contributors](https://github.com/rust-windowing/winit/wiki/Testers-and-Contributors) table!
|
||||||
|
|
||||||
## Making a new release
|
## Release process
|
||||||
|
|
||||||
If you believe a new release is warranted, you can make a pull-request with:
|
Given that winit is a widely used library, we should be able to make a patch
|
||||||
- An updated version number (remember to change the version everywhere it is used).
|
releases at any time we want without blocking the development of new features.
|
||||||
- A new section in the changelog (below the `# Unreleased` section).
|
|
||||||
|
|
||||||
This gives contributors an opportunity to squeeze in an extra PR or two that they feel is valuable
|
To achieve these goals, a new branch is created for every new release. Releases and later patch releases are committed and tagged in this branch.
|
||||||
enough to warrant blocking the release a little.
|
|
||||||
|
|
||||||
Once the PR is merged, a maintainer will create a new tag matching the version name (e.g. `v0.26.1`),
|
The exact steps for an exemplary `0.2.0` release might look like this:
|
||||||
and a CI job will automatically release the new version. Remember that the release date in the
|
1. Initially, the version on the latest master is `0.1.0`
|
||||||
changelog must be kept in check with the actual release date.
|
2. A new `v0.2.x` branch is created for the release
|
||||||
|
3. In the branch, the version is bumped to `v0.2.0`
|
||||||
|
4. The new commit in the branch is tagged `v0.2.0`
|
||||||
|
5. The version is pushed to crates.io
|
||||||
|
6. A GitHub release is created for the `v0.2.0` tag
|
||||||
|
7. On master, the version is bumped to `0.2.0`, and the CHANGELOG is updated
|
||||||
|
|
||||||
|
When doing a patch release, the process is similar:
|
||||||
|
1. Initially, the version of the latest release is `0.2.0`
|
||||||
|
2. Checkout the `v0.2.x` branch
|
||||||
|
3. Cherry-pick the required non-breaking changes into the `v0.2.x`
|
||||||
|
4. Follow steps 3-7 of the regular release example
|
||||||
|
|||||||
156
Cargo.toml
156
Cargo.toml
@@ -1,148 +1,16 @@
|
|||||||
[package]
|
|
||||||
name = "winit"
|
|
||||||
version = "0.27.0"
|
|
||||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
|
||||||
description = "Cross-platform window creation library."
|
|
||||||
edition = "2021"
|
|
||||||
keywords = ["windowing"]
|
|
||||||
license = "Apache-2.0"
|
|
||||||
readme = "README.md"
|
|
||||||
repository = "https://github.com/rust-windowing/winit"
|
|
||||||
documentation = "https://docs.rs/winit"
|
|
||||||
categories = ["gui"]
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
features = ["serde"]
|
|
||||||
default-target = "x86_64-unknown-linux-gnu"
|
|
||||||
# These are all tested in CI
|
|
||||||
targets = [
|
|
||||||
# Windows
|
|
||||||
"i686-pc-windows-msvc",
|
|
||||||
"x86_64-pc-windows-msvc",
|
|
||||||
# macOS
|
|
||||||
"x86_64-apple-darwin",
|
|
||||||
# Unix (X11 & Wayland)
|
|
||||||
"i686-unknown-linux-gnu",
|
|
||||||
"x86_64-unknown-linux-gnu",
|
|
||||||
# iOS
|
|
||||||
"x86_64-apple-ios",
|
|
||||||
# Android
|
|
||||||
"aarch64-linux-android",
|
|
||||||
# WebAssembly
|
|
||||||
"wasm32-unknown-unknown",
|
|
||||||
]
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
|
|
||||||
x11 = ["x11-dl", "mio", "percent-encoding", "parking_lot"]
|
|
||||||
wayland = ["wayland-client", "wayland-protocols", "sctk"]
|
|
||||||
wayland-dlopen = ["sctk/dlopen", "wayland-client/dlopen"]
|
|
||||||
wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/title"]
|
|
||||||
wayland-csd-adwaita-notitle = ["sctk-adwaita"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
instant = { version = "0.1", features = ["wasm-bindgen"] }
|
|
||||||
once_cell = "1.12"
|
|
||||||
log = "0.4"
|
|
||||||
serde = { version = "1", optional = true, features = ["serde_derive"] }
|
|
||||||
raw-window-handle = "0.5.0"
|
|
||||||
bitflags = "1"
|
|
||||||
mint = { version = "0.5.6", optional = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
image = { version = "0.24.0", default-features = false, features = ["png"] }
|
|
||||||
simple_logger = "2.1.0"
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "android")'.dependencies]
|
|
||||||
# Coordinate the next winit release with android-ndk-rs: https://github.com/rust-windowing/winit/issues/1995
|
|
||||||
ndk = "0.7.0"
|
|
||||||
ndk-glue = "0.7.0"
|
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
|
|
||||||
objc = "0.2.7"
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
|
||||||
cocoa = "0.24"
|
|
||||||
core-foundation = "0.9"
|
|
||||||
core-graphics = "0.22"
|
|
||||||
dispatch = "0.2.0"
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies]
|
|
||||||
parking_lot = "0.12"
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
|
|
||||||
version = "0.36"
|
|
||||||
features = [
|
|
||||||
"Win32_Devices_HumanInterfaceDevice",
|
|
||||||
"Win32_Foundation",
|
|
||||||
"Win32_Globalization",
|
|
||||||
"Win32_Graphics_Dwm",
|
|
||||||
"Win32_Graphics_Gdi",
|
|
||||||
"Win32_Media",
|
|
||||||
"Win32_System_Com_StructuredStorage",
|
|
||||||
"Win32_System_Com",
|
|
||||||
"Win32_System_LibraryLoader",
|
|
||||||
"Win32_System_Ole",
|
|
||||||
"Win32_System_SystemInformation",
|
|
||||||
"Win32_System_SystemServices",
|
|
||||||
"Win32_System_Threading",
|
|
||||||
"Win32_System_WindowsProgramming",
|
|
||||||
"Win32_UI_Accessibility",
|
|
||||||
"Win32_UI_Controls",
|
|
||||||
"Win32_UI_HiDpi",
|
|
||||||
"Win32_UI_Input_Ime",
|
|
||||||
"Win32_UI_Input_KeyboardAndMouse",
|
|
||||||
"Win32_UI_Input_Pointer",
|
|
||||||
"Win32_UI_Input_Touch",
|
|
||||||
"Win32_UI_Shell",
|
|
||||||
"Win32_UI_TextServices",
|
|
||||||
"Win32_UI_WindowsAndMessaging",
|
|
||||||
]
|
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
|
|
||||||
wayland-client = { version = "0.29.4", default_features = false, features = ["use_system_lib"], optional = true }
|
|
||||||
wayland-protocols = { version = "0.29.4", features = [ "staging_protocols"], optional = true }
|
|
||||||
sctk = { package = "smithay-client-toolkit", version = "0.16.0", default_features = false, features = ["calloop"], optional = true }
|
|
||||||
sctk-adwaita = { version = "0.4.1", optional = true }
|
|
||||||
mio = { version = "0.8", features = ["os-ext"], optional = true }
|
|
||||||
x11-dl = { version = "2.18.5", optional = true }
|
|
||||||
percent-encoding = { version = "2.0", optional = true }
|
|
||||||
parking_lot = { version = "0.12.0", optional = true }
|
|
||||||
libc = "0.2.64"
|
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies.web_sys]
|
|
||||||
package = "web-sys"
|
|
||||||
version = "0.3.22"
|
|
||||||
features = [
|
|
||||||
'console',
|
|
||||||
"AddEventListenerOptions",
|
|
||||||
'CssStyleDeclaration',
|
|
||||||
'BeforeUnloadEvent',
|
|
||||||
'Document',
|
|
||||||
'DomRect',
|
|
||||||
'Element',
|
|
||||||
'Event',
|
|
||||||
'EventTarget',
|
|
||||||
'FocusEvent',
|
|
||||||
'HtmlCanvasElement',
|
|
||||||
'HtmlElement',
|
|
||||||
'KeyboardEvent',
|
|
||||||
'MediaQueryList',
|
|
||||||
'MediaQueryListEvent',
|
|
||||||
'MouseEvent',
|
|
||||||
'Node',
|
|
||||||
'PointerEvent',
|
|
||||||
'Window',
|
|
||||||
'WheelEvent'
|
|
||||||
]
|
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen]
|
|
||||||
version = "0.2.45"
|
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
|
||||||
console_log = "0.2"
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"run-wasm",
|
"run-wasm",
|
||||||
|
"winit",
|
||||||
|
"winit-core"
|
||||||
]
|
]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
bitflags = "2"
|
||||||
|
cfg_aliases = "0.2.0"
|
||||||
|
cursor-icon = "1.1.0"
|
||||||
|
serde = { version = "1", features = ["serde_derive"] }
|
||||||
|
smol_str = "0.2.0"
|
||||||
|
web-time = "1"
|
||||||
|
winit-core = { path = "./winit-core", default-features = false, features = ["std"] }
|
||||||
|
|||||||
140
FEATURES.md
140
FEATURES.md
@@ -1,18 +1,21 @@
|
|||||||
# Winit Scope
|
# Winit Scope
|
||||||
|
|
||||||
Winit aims to expose an interface that abstracts over window creation and input handling, and can
|
Winit aims to expose an interface that abstracts over window creation and input handling and can
|
||||||
be used to create both games and applications. It supports the main graphical platforms:
|
be used to create both games and applications. It supports the following main graphical platforms:
|
||||||
- Desktop
|
- Desktop
|
||||||
- Windows
|
- Windows 7+ (10+ is tested regularly)
|
||||||
- macOS
|
- macOS 10.7+ (10.14+ is tested regularly)
|
||||||
- Unix
|
- Unix
|
||||||
- via X11
|
- via X11
|
||||||
- via Wayland
|
- via Wayland
|
||||||
|
- Redox OS, via Orbital
|
||||||
- Mobile
|
- Mobile
|
||||||
- iOS
|
- iOS
|
||||||
- Android
|
- Android
|
||||||
- Web
|
- Web
|
||||||
- via WASM
|
- Chrome
|
||||||
|
- Firefox
|
||||||
|
- Safari 13.1+
|
||||||
|
|
||||||
Most platforms expose capabilities that cannot be meaningfully transposed onto others. Winit does not
|
Most platforms expose capabilities that cannot be meaningfully transposed onto others. Winit does not
|
||||||
aim to support every single feature of every platform, but rather to abstract over the common features
|
aim to support every single feature of every platform, but rather to abstract over the common features
|
||||||
@@ -42,10 +45,10 @@ be released and the library will enter maintenance mode. For the most part, new
|
|||||||
be added past this point. New platform features may be accepted and exposed through point releases.
|
be added past this point. New platform features may be accepted and exposed through point releases.
|
||||||
|
|
||||||
### Tier upgrades
|
### Tier upgrades
|
||||||
Some platform features could in theory be exposed across multiple platforms, but have not gone
|
Some platform features could, in theory, be exposed across multiple platforms, but have not gone
|
||||||
through the implementation work necessary to function on all platforms. When one of these features
|
through the implementation work necessary to function on all platforms. When one of these features
|
||||||
gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature.
|
gets implemented across all platforms, a PR can be opened to upgrade the feature to a core feature.
|
||||||
If that gets accepted, the platform-specific functions gets deprecated and become permanently
|
If that gets accepted, the platform-specific functions get deprecated and become permanently
|
||||||
exposed through the core, cross-platform API.
|
exposed through the core, cross-platform API.
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
@@ -85,7 +88,7 @@ If your PR makes notable changes to Winit's features, please update this section
|
|||||||
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
|
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
|
||||||
creation.
|
creation.
|
||||||
- **Exclusive fullscreen**: Winit allows changing the video mode of the monitor
|
- **Exclusive fullscreen**: Winit allows changing the video mode of the monitor
|
||||||
for fullscreen windows, and if applicable, captures the monitor for exclusive
|
for fullscreen windows and, if applicable, captures the monitor for exclusive
|
||||||
use by this application.
|
use by this application.
|
||||||
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
|
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
|
||||||
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
|
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
|
||||||
@@ -102,7 +105,8 @@ If your PR makes notable changes to Winit's features, please update this section
|
|||||||
- **Mouse set location**: Forcibly changing the location of the pointer.
|
- **Mouse set location**: Forcibly changing the location of the pointer.
|
||||||
- **Cursor locking**: Locking the cursor inside the window so it cannot move.
|
- **Cursor locking**: Locking the cursor inside the window so it cannot move.
|
||||||
- **Cursor confining**: Confining the cursor to the window bounds so it cannot leave them.
|
- **Cursor confining**: Confining the cursor to the window bounds so it cannot leave them.
|
||||||
- **Cursor icon**: Changing the cursor icon, or hiding the cursor.
|
- **Cursor icon**: Changing the cursor icon or hiding the cursor.
|
||||||
|
- **Cursor image**: Changing the cursor to your own image.
|
||||||
- **Cursor hittest**: Handle or ignore mouse events for a window.
|
- **Cursor hittest**: Handle or ignore mouse events for a window.
|
||||||
- **Touch events**: Single-touch events.
|
- **Touch events**: Single-touch events.
|
||||||
- **Touch pressure**: Touch events contain information about the amount of force being applied.
|
- **Touch pressure**: Touch events contain information about the amount of force being applied.
|
||||||
@@ -116,11 +120,17 @@ If your PR makes notable changes to Winit's features, please update this section
|
|||||||
|
|
||||||
## Platform
|
## Platform
|
||||||
### Windows
|
### Windows
|
||||||
|
* Setting the name of the internal window class
|
||||||
* Setting the taskbar icon
|
* Setting the taskbar icon
|
||||||
* Setting the parent window
|
* Setting the parent window
|
||||||
* Setting a menu bar
|
* Setting a menu bar
|
||||||
* `WS_EX_NOREDIRECTIONBITMAP` support
|
* `WS_EX_NOREDIRECTIONBITMAP` support
|
||||||
* Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme
|
* Theme the title bar according to Windows 10 Dark Mode setting or set a preferred theme
|
||||||
|
* Changing a system-drawn backdrop
|
||||||
|
* Setting the window border color
|
||||||
|
* Setting the title bar background color
|
||||||
|
* Setting the title color
|
||||||
|
* Setting the corner rounding preference
|
||||||
|
|
||||||
### macOS
|
### macOS
|
||||||
* Window activation policy
|
* Window activation policy
|
||||||
@@ -129,6 +139,8 @@ If your PR makes notable changes to Winit's features, please update this section
|
|||||||
* Hidden titlebar
|
* Hidden titlebar
|
||||||
* Hidden titlebar buttons
|
* Hidden titlebar buttons
|
||||||
* Full-size content view
|
* Full-size content view
|
||||||
|
* Accepts first mouse
|
||||||
|
* Set a preferred theme and get current theme.
|
||||||
|
|
||||||
### Unix
|
### Unix
|
||||||
* Window urgency
|
* Window urgency
|
||||||
@@ -136,24 +148,21 @@ If your PR makes notable changes to Winit's features, please update this section
|
|||||||
* X11 Override Redirect Flag
|
* X11 Override Redirect Flag
|
||||||
* GTK Theme Variant
|
* GTK Theme Variant
|
||||||
* Base window size
|
* Base window size
|
||||||
|
* Setting the X11 parent window
|
||||||
|
|
||||||
### iOS
|
### iOS
|
||||||
* `winit` has a minimum OS requirement of iOS 8
|
* `winit` has a minimum OS requirement of iOS 8
|
||||||
* Get the `UIWindow` object pointer
|
|
||||||
* Get the `UIViewController` object pointer
|
|
||||||
* Get the `UIView` object pointer
|
|
||||||
* Get the `UIScreen` object pointer
|
* Get the `UIScreen` object pointer
|
||||||
* Setting the `UIView` hidpi factor
|
* Setting the `UIView` hidpi factor
|
||||||
* Valid orientations
|
* Valid orientations
|
||||||
* Home indicator visibility
|
* Home indicator visibility
|
||||||
* Status bar visibility
|
* Status bar visibility and style
|
||||||
* Deferrring system gestures
|
* Deferring system gestures
|
||||||
* Support for custom `UIView` derived class
|
|
||||||
* Getting the device idiom
|
* Getting the device idiom
|
||||||
* Getting the preferred video mode
|
* Getting the preferred video mode
|
||||||
|
|
||||||
### Web
|
### Web
|
||||||
* Get if systems preferred color scheme is "dark"
|
* Get if the systems preferred color scheme is "dark"
|
||||||
|
|
||||||
## Usability
|
## Usability
|
||||||
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
|
* `serde`: Enables serialization/deserialization of certain types with Serde. (Maintainer: @Osspial)
|
||||||
@@ -163,68 +172,71 @@ If your PR makes notable changes to Winit's features, please update this section
|
|||||||
Legend:
|
Legend:
|
||||||
|
|
||||||
- ✔️: Works as intended
|
- ✔️: Works as intended
|
||||||
- ▢: Mostly works but some bugs are known
|
- ▢: Mostly works, but some bugs are known
|
||||||
- ❌: Missing feature or large bugs making it unusable
|
- ❌: Missing feature or large bugs making it unusable
|
||||||
- **N/A**: Not applicable for this platform
|
- **N/A**: Not applicable for this platform
|
||||||
- ❓: Unknown status
|
- ❓: Unknown status
|
||||||
|
|
||||||
### Windowing
|
### Windowing
|
||||||
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |WASM |
|
|Feature |Windows|MacOS |Linux x11 |Linux Wayland |Android|iOS |Web |Redox OS|
|
||||||
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
|-------------------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|
||||||
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |
|
|Window initialization |✔️ |✔️ |▢[#5] |✔️ |▢[#33]|▢[#33] |✔️ |✔️ |
|
||||||
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|
|
|Providing pointer to init OpenGL |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |
|
||||||
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|
|
|Providing pointer to init Vulkan |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A** |
|
||||||
|Window decorations |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|
|Window decorations |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|✔️ |
|
||||||
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|
|Window decorations toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|
||||||
|Window resizing |✔️ |▢[#219]|✔️ |▢[#306] |**N/A**|**N/A**|✔️ |
|
|Window resizing |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|
||||||
|Window resize increments |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|
|
|Window resize increments |❌ |✔️ |✔️ |❌ |**N/A**|**N/A**|**N/A**|**N/A** |
|
||||||
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |
|
|Window transparency |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|N/A |✔️ |
|
||||||
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|
|Window blur |❌ |❌ |❌ |✔️ |**N/A**|**N/A**|N/A |❌ |
|
||||||
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|
|Window maximization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|
||||||
|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|
|
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|
||||||
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|
|Window minimization |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A**|**N/A** |
|
||||||
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |
|
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |**N/A** |
|
||||||
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|
|
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |✔️ |**N/A** |
|
||||||
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ |
|
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |**N/A**|**N/A** |
|
||||||
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|
|
|HiDPI support |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |
|
||||||
|
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |**N/A**|**N/A** |
|
||||||
|
|
||||||
### System information
|
### System information
|
||||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|
||||||
|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- |
|
|---------------- | ----- | ---- | ------- | ----------- | ----- | ------- | -------- | ------ |
|
||||||
|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|✔️ |**N/A**|
|
|Monitor list |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
|
||||||
|Video mode query |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |**N/A**|
|
|Video mode query |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A**|❌ |
|
||||||
|
|
||||||
### Input handling
|
### Input handling
|
||||||
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|
||||||
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|
||||||
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |✔️ |
|
||||||
|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|
|
|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|**N/A** |
|
||||||
|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |
|
|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |❌ |
|
||||||
|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|
|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|
||||||
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|
||||||
|Cursor hittest |✔️ |✔️ |❌ |✔️ |**N/A**|**N/A**|❌ |
|
|Cursor image |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|
||||||
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |
|
|Cursor hittest |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|
||||||
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |❌ |
|
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A** |
|
||||||
|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |
|
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |**N/A** |
|
||||||
|Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ |
|
|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |**N/A** |
|
||||||
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ |
|
|Keyboard events |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
|
||||||
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |
|
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |▢[#720] |**N/A**|**N/A**|❓ |**N/A** |
|
||||||
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |
|
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |**N/A** |
|
||||||
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |
|
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |**N/A** |
|
||||||
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |**N/A** |
|
||||||
|
|Drag window with cursor |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
|
||||||
|
|Resize with cursor |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |**N/A** |
|
||||||
|
|
||||||
### Pending API Reworks
|
### Pending API Reworks
|
||||||
Changes in the API that have been agreed upon but aren't implemented across all platforms.
|
Changes in the API that have been agreed upon but aren't implemented across all platforms.
|
||||||
|
|
||||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|
||||||
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|
||||||
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |❓ |
|
|New API for HiDPI ([#315] [#319]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |❓ |
|
||||||
|Event Loop 2.0 ([#459]) |✔️ |✔️ |❌ |✔️ |❌ |✔️ |❓ |
|
|Event Loop 2.0 ([#459]) |✔️ |✔️ |✔️ |✔️ |✔️ |✔️ |❓ |✔️ |
|
||||||
|Keyboard Input ([#812]) |❌ |❌ |❌ |❌ |❌ |❌ |❓ |
|
|Keyboard Input 2.0 ([#753]) |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
|
||||||
|
|
||||||
### Completed API Reworks
|
### Completed API Reworks
|
||||||
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Web |Redox OS|
|
||||||
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|
|------------------------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- | ------ |
|
||||||
|
|
||||||
[#165]: https://github.com/rust-windowing/winit/issues/165
|
[#165]: https://github.com/rust-windowing/winit/issues/165
|
||||||
[#219]: https://github.com/rust-windowing/winit/issues/219
|
[#219]: https://github.com/rust-windowing/winit/issues/219
|
||||||
@@ -239,5 +251,5 @@ Changes in the API that have been agreed upon but aren't implemented across all
|
|||||||
[#720]: https://github.com/rust-windowing/winit/issues/720
|
[#720]: https://github.com/rust-windowing/winit/issues/720
|
||||||
[#721]: https://github.com/rust-windowing/winit/issues/721
|
[#721]: https://github.com/rust-windowing/winit/issues/721
|
||||||
[#750]: https://github.com/rust-windowing/winit/issues/750
|
[#750]: https://github.com/rust-windowing/winit/issues/750
|
||||||
|
[#753]: https://github.com/rust-windowing/winit/issues/753
|
||||||
[#804]: https://github.com/rust-windowing/winit/issues/804
|
[#804]: https://github.com/rust-windowing/winit/issues/804
|
||||||
[#812]: https://github.com/rust-windowing/winit/issues/812
|
|
||||||
|
|||||||
@@ -2,22 +2,24 @@
|
|||||||
|
|
||||||
The winit maintainers would like to recognize the following former winit
|
The winit maintainers would like to recognize the following former winit
|
||||||
contributors, without whom winit would not exist in its current form. We thank
|
contributors, without whom winit would not exist in its current form. We thank
|
||||||
them deeply for their time and efforts, and wish them best of luck in their
|
them deeply for their time and efforts and wish them the best of luck in their
|
||||||
future endeavors:
|
future endeavors:
|
||||||
|
|
||||||
* [@tomaka]: For creating the winit project and guiding it through its early
|
* [@tomaka]: For creating the winit project and guiding it through its early
|
||||||
years of existence.
|
years of existence.
|
||||||
* [@vberger]: For diligently creating the Wayland backend, and being its
|
* [@vberger]: For diligently creating the Wayland backend and being its
|
||||||
extremely helpful and benevolent maintainer for years.
|
extremely helpful and benevolent maintainer for years.
|
||||||
* [@francesca64]: For taking over the responsibility of maintaining almost every
|
* [@francesca64]: For taking over the responsibility of maintaining almost every
|
||||||
winit backend, and standardizing HiDPI support across all of them.
|
winit backend and standardizing HiDPI support across all of them.
|
||||||
* [@Osspial]: For heroically landing EventLoop 2.0, and valiantly ushering in a
|
* [@Osspial]: For heroically landing EventLoop 2.0 and valiantly ushering in a
|
||||||
vastly more sustainable era of winit.
|
vastly more sustainable era of winit.
|
||||||
* [@goddessfreya]: For selflessly taking over maintainership of glutin, and her
|
* [@goddessfreya]: For selflessly taking over maintainership of glutin and her
|
||||||
stellar dedication to improving both winit and glutin.
|
stellar dedication to improving both winit and glutin.
|
||||||
|
* [@ArturKovacs]: For consistently maintaining the macOS backend and for his immense involvement in designing and implementing the new keyboard API.
|
||||||
|
|
||||||
[@tomaka]: https://github.com/tomaka
|
[@tomaka]: https://github.com/tomaka
|
||||||
[@vberger]: https://github.com/vberger
|
[@vberger]: https://github.com/vberger
|
||||||
[@francesca64]: https://github.com/francesca64
|
[@francesca64]: https://github.com/francesca64
|
||||||
[@Osspial]: https://github.com/Osspial
|
[@Osspial]: https://github.com/Osspial
|
||||||
[@goddessfreya]: https://github.com/goddessfreya
|
[@goddessfreya]: https://github.com/goddessfreya
|
||||||
|
[@ArturKovacs]: https://github.com/ArturKovacs
|
||||||
|
|||||||
164
README.md
164
README.md
@@ -2,63 +2,37 @@
|
|||||||
|
|
||||||
[](https://crates.io/crates/winit)
|
[](https://crates.io/crates/winit)
|
||||||
[](https://docs.rs/winit)
|
[](https://docs.rs/winit)
|
||||||
|
[](https://rust-windowing.github.io/winit/winit/index.html)
|
||||||
[](https://github.com/rust-windowing/winit/actions)
|
[](https://github.com/rust-windowing/winit/actions)
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
winit = "0.27.0"
|
winit = "0.29.10"
|
||||||
```
|
```
|
||||||
|
|
||||||
## [Documentation](https://docs.rs/winit)
|
## [Documentation](https://docs.rs/winit)
|
||||||
|
|
||||||
For features _within_ the scope of winit, see [FEATURES.md](FEATURES.md).
|
For features _within_ the scope of winit, see [FEATURES.md](FEATURES.md).
|
||||||
|
|
||||||
For features _outside_ the scope of winit, see [Missing features provided by other crates](https://github.com/rust-windowing/winit/wiki/Missing-features-provided-by-other-crates) in the wiki.
|
For features _outside_ the scope of winit, see [Are we GUI Yet?](https://areweguiyet.com/) and [Are we game yet?](https://arewegameyet.rs/), depending on what kind of project you're looking to do.
|
||||||
|
|
||||||
## Contact Us
|
## Contact Us
|
||||||
|
|
||||||
Join us in any of these:
|
Join us in our [](https://matrix.to/#/#rust-windowing:matrix.org) room. If you don't get an answer there, try [](https://web.libera.chat/#winit).
|
||||||
|
|
||||||
[](https://matrix.to/#/#rust-windowing:matrix.org)
|
The maintainers have a meeting every friday at UTC 14. The meeting notes can be found [here](https://hackmd.io/@winit-meetings).
|
||||||
[](https://web.libera.chat/#winit)
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Winit is a window creation and management library. It can create windows and lets you handle
|
Winit is a window creation and management library. It can create windows and lets you handle
|
||||||
events (for example: the window being resized, a key being pressed, a mouse movement, etc.)
|
events (for example: the window being resized, a key being pressed, a mouse movement, etc.)
|
||||||
produced by window.
|
produced by the window.
|
||||||
|
|
||||||
Winit is designed to be a low-level brick in a hierarchy of libraries. Consequently, in order to
|
Winit is designed to be a low-level brick in a hierarchy of libraries. Consequently, in order to
|
||||||
show something on the window you need to use the platform-specific getters provided by winit, or
|
show something on the window you need to use the platform-specific getters provided by winit, or
|
||||||
another library.
|
another library.
|
||||||
|
|
||||||
```rust
|
|
||||||
use winit::{
|
|
||||||
event::{Event, WindowEvent},
|
|
||||||
event_loop::{ControlFlow, EventLoop},
|
|
||||||
window::WindowBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let event_loop = EventLoop::new();
|
|
||||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
|
||||||
*control_flow = ControlFlow::Wait;
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::CloseRequested,
|
|
||||||
window_id,
|
|
||||||
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Winit is only officially supported on the latest stable version of the Rust compiler.
|
|
||||||
|
|
||||||
### Cargo Features
|
### Cargo Features
|
||||||
|
|
||||||
Winit provides the following features, which can be enabled in your `Cargo.toml` file:
|
Winit provides the following features, which can be enabled in your `Cargo.toml` file:
|
||||||
@@ -67,17 +41,41 @@ Winit provides the following features, which can be enabled in your `Cargo.toml`
|
|||||||
* `wayland` (enabled by default): On Unix platform, compiles with the Wayland backend
|
* `wayland` (enabled by default): On Unix platform, compiles with the Wayland backend
|
||||||
* `mint`: Enables mint (math interoperability standard types) conversions.
|
* `mint`: Enables mint (math interoperability standard types) conversions.
|
||||||
|
|
||||||
|
## MSRV Policy
|
||||||
|
|
||||||
|
This crate's Minimum Supported Rust Version (MSRV) is **1.70**. Changes to
|
||||||
|
the MSRV will be accompanied by a minor version bump.
|
||||||
|
|
||||||
|
As a **tentative** policy, the upper bound of the MSRV is given by the following
|
||||||
|
formula:
|
||||||
|
|
||||||
|
```
|
||||||
|
min(sid, stable - 3)
|
||||||
|
```
|
||||||
|
|
||||||
|
Where `sid` is the current version of `rustc` provided by [Debian Sid], and
|
||||||
|
`stable` is the latest stable version of Rust. This bound may be broken in case of a major ecosystem shift or a security vulnerability.
|
||||||
|
|
||||||
|
[Debian Sid]: https://packages.debian.org/sid/rustc
|
||||||
|
|
||||||
|
The exception is for the Android platform, where a higher Rust version
|
||||||
|
must be used for certain Android features. In this case, the MSRV will be
|
||||||
|
capped at the latest stable version of Rust minus three. This inconsistency is
|
||||||
|
not reflected in Cargo metadata, as it is not powerful enough to expose this
|
||||||
|
restriction.
|
||||||
|
|
||||||
|
All crates in the [`rust-windowing`] organizations have the
|
||||||
|
same MSRV policy.
|
||||||
|
|
||||||
|
[`rust-windowing`]: https://github.com/rust-windowing
|
||||||
|
|
||||||
### Platform-specific usage
|
### Platform-specific usage
|
||||||
|
|
||||||
#### Wayland
|
#### Wayland
|
||||||
|
|
||||||
Note that windows don't appear on Wayland until you draw/present to them.
|
Note that windows don't appear on Wayland until you draw/present to them.
|
||||||
|
|
||||||
`winit` doesn't do drawing, try the examples in [`glutin`] instead.
|
#### Web
|
||||||
|
|
||||||
[`glutin`]: https://github.com/rust-windowing/glutin
|
|
||||||
|
|
||||||
#### WebAssembly
|
|
||||||
|
|
||||||
To run the web example: `cargo run-wasm --example web`
|
To run the web example: `cargo run-wasm --example web`
|
||||||
|
|
||||||
@@ -88,7 +86,7 @@ either [provide Winit with a `<canvas>` element][web with_canvas], or [let Winit
|
|||||||
create a `<canvas>` element which you can then retrieve][web canvas getter] and
|
create a `<canvas>` element which you can then retrieve][web canvas getter] and
|
||||||
insert it into the DOM yourself.
|
insert it into the DOM yourself.
|
||||||
|
|
||||||
For example code using Winit with WebAssembly, check out the [web example]. For
|
For the example code using Winit on Web, check out the [web example]. For
|
||||||
information on using Rust on WebAssembly, check out the [Rust and WebAssembly
|
information on using Rust on WebAssembly, check out the [Rust and WebAssembly
|
||||||
book].
|
book].
|
||||||
|
|
||||||
@@ -99,36 +97,69 @@ book].
|
|||||||
|
|
||||||
#### Android
|
#### Android
|
||||||
|
|
||||||
This library makes use of the [ndk-rs](https://github.com/rust-windowing/android-ndk-rs) crates, refer to that repo for more documentation.
|
The Android backend builds on (and exposes types from) the [`ndk`](https://docs.rs/ndk/latest/ndk/) crate.
|
||||||
|
|
||||||
The `ndk-glue` version needs to match the version used by `winit`. Otherwise, the application will not start correctly as `ndk-glue`'s internal `NativeActivity` static is not the same due to version mismatch.
|
Native Android applications need some form of "glue" crate that is responsible
|
||||||
|
for defining the main entry point for your Rust application as well as tracking
|
||||||
|
various life-cycle events and synchronizing with the main JVM thread.
|
||||||
|
|
||||||
`winit` compatibility table with `ndk-glue`:
|
Winit uses the [android-activity](https://github.com/rib/android-activity) as a
|
||||||
|
glue crate (prior to `0.28` it used
|
||||||
|
[ndk-glue](https://github.com/rust-windowing/android-ndk-rs/tree/master/ndk-glue)).
|
||||||
|
|
||||||
| winit | ndk-glue |
|
The version of the glue crate that your application depends on _must_ match the
|
||||||
| :---: | :------------------: |
|
version that Winit depends on because the glue crate is responsible for your
|
||||||
| 0.24 | `ndk-glue = "0.2.0"` |
|
application's main entry point. If Cargo resolves multiple versions, they will
|
||||||
| 0.25 | `ndk-glue = "0.3.0"` |
|
clash.
|
||||||
| 0.26 | `ndk-glue = "0.5.0"` |
|
|
||||||
| 0.27 | `ndk-glue = "0.7.0"` |
|
|
||||||
|
|
||||||
Running on an Android device needs a dynamic system library, add this to Cargo.toml:
|
`winit` glue compatibility table:
|
||||||
|
|
||||||
|
| winit | ndk-glue |
|
||||||
|
| :---: | :--------------------------: |
|
||||||
|
| 0.29 | `android-activity = "0.5"` |
|
||||||
|
| 0.28 | `android-activity = "0.4"` |
|
||||||
|
| 0.27 | `ndk-glue = "0.7"` |
|
||||||
|
| 0.26 | `ndk-glue = "0.5"` |
|
||||||
|
| 0.25 | `ndk-glue = "0.3"` |
|
||||||
|
| 0.24 | `ndk-glue = "0.2"` |
|
||||||
|
|
||||||
|
The recommended way to avoid a conflict with the glue version is to avoid explicitly
|
||||||
|
depending on the `android-activity` crate, and instead consume the API that
|
||||||
|
is re-exported by Winit under `winit::platform::android::activity::*`
|
||||||
|
|
||||||
|
Running on an Android device needs a dynamic system library. Add this to Cargo.toml:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[[example]]
|
[lib]
|
||||||
name = "request_redraw_threaded"
|
name = "main"
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
```
|
```
|
||||||
|
|
||||||
And add this to the example file to add the native activity glue:
|
All Android applications are based on an `Activity` subclass, and the
|
||||||
```rust
|
`android-activity` crate is designed to support different choices for this base
|
||||||
#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))]
|
class. Your application _must_ specify the base class it needs via a feature flag:
|
||||||
fn main() {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
And run the application with `cargo apk run --example request_redraw_threaded`
|
| Base Class | Feature Flag | Notes |
|
||||||
|
| :--------------: | :---------------: | :-----: |
|
||||||
|
| `NativeActivity` | `android-native-activity` | Built-in to Android - it is possible to use without compiling any Java or Kotlin code. Java or Kotlin code may be needed to subclass `NativeActivity` to access some platform features. It does not derive from the [`AndroidAppCompat`] base class.|
|
||||||
|
| [`GameActivity`] | `android-game-activity` | Derives from [`AndroidAppCompat`], a defacto standard `Activity` base class that helps support a wider range of Android versions. Requires a build system that can compile Java or Kotlin and fetch Android dependencies from a [Maven repository][agdk_jetpack] (or link with an embedded [release][agdk_releases] of [`GameActivity`]) |
|
||||||
|
|
||||||
|
[`GameActivity`]: https://developer.android.com/games/agdk/game-activity
|
||||||
|
[`GameTextInput`]: https://developer.android.com/games/agdk/add-support-for-text-input
|
||||||
|
[`AndroidAppCompat`]: https://developer.android.com/reference/androidx/appcompat/app/AppCompatActivity
|
||||||
|
[agdk_jetpack]: https://developer.android.com/jetpack/androidx/releases/games
|
||||||
|
[agdk_releases]: https://developer.android.com/games/agdk/download#agdk-libraries
|
||||||
|
[Gradle]: https://developer.android.com/studio/build
|
||||||
|
|
||||||
|
For more details, refer to these `android-activity` [example applications](https://github.com/rust-mobile/android-activity/tree/main/examples).
|
||||||
|
|
||||||
|
##### Converting from `ndk-glue` to `android-activity`
|
||||||
|
|
||||||
|
If your application is currently based on `NativeActivity` via the `ndk-glue` crate and building with `cargo apk`, then the minimal changes would be:
|
||||||
|
1. Remove `ndk-glue` from your `Cargo.toml`
|
||||||
|
2. Enable the `"android-native-activity"` feature for Winit: `winit = { version = "0.29.10", features = [ "android-native-activity" ] }`
|
||||||
|
3. Add an `android_main` entrypoint (as above), instead of using the '`[ndk_glue::main]` proc macro from `ndk-macros` (optionally add a dependency on `android_logger` and initialize logging as above).
|
||||||
|
4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your event loop (as shown above).
|
||||||
|
|
||||||
#### MacOS
|
#### MacOS
|
||||||
|
|
||||||
@@ -137,8 +168,21 @@ doing anything; this includes creating windows, fetching monitors, drawing,
|
|||||||
and so on, see issues [#2238], [#2051] and [#2087].
|
and so on, see issues [#2238], [#2051] and [#2087].
|
||||||
|
|
||||||
If you encounter problems, you should try doing your initialization inside
|
If you encounter problems, you should try doing your initialization inside
|
||||||
`Event::NewEvents(StartCause::Init)`.
|
`Event::Resumed`.
|
||||||
|
|
||||||
|
#### iOS
|
||||||
|
|
||||||
|
Similar to macOS, iOS's main `UIApplicationMain` does some init work that's required
|
||||||
|
by all UI-related code (see issue [#1705]). It would be best to consider creating your windows
|
||||||
|
inside `Event::Resumed`.
|
||||||
|
|
||||||
|
|
||||||
[#2238]: https://github.com/rust-windowing/winit/issues/2238
|
[#2238]: https://github.com/rust-windowing/winit/issues/2238
|
||||||
[#2051]: https://github.com/rust-windowing/winit/issues/2051
|
[#2051]: https://github.com/rust-windowing/winit/issues/2051
|
||||||
[#2087]: https://github.com/rust-windowing/winit/issues/2087
|
[#2087]: https://github.com/rust-windowing/winit/issues/2087
|
||||||
|
[#1705]: https://github.com/rust-windowing/winit/issues/1705
|
||||||
|
|
||||||
|
#### Redox OS
|
||||||
|
|
||||||
|
Redox OS has some functionality not yet present that will be implemented when
|
||||||
|
its orbital display server provides it.
|
||||||
|
|||||||
15
clippy.toml
Normal file
15
clippy.toml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
disallowed-methods = [
|
||||||
|
{ path = "web_sys::window", reason = "is not available in every context" },
|
||||||
|
{ path = "web_sys::HtmlCanvasElement::width", reason = "Winit shouldn't touch the internal canvas size" },
|
||||||
|
{ path = "web_sys::HtmlCanvasElement::height", reason = "Winit shouldn't touch the internal canvas size" },
|
||||||
|
{ path = "web_sys::HtmlCanvasElement::set_width", reason = "Winit shouldn't touch the internal canvas size" },
|
||||||
|
{ path = "web_sys::HtmlCanvasElement::set_height", reason = "Winit shouldn't touch the internal canvas size" },
|
||||||
|
{ path = "web_sys::Window::document", reason = "cache this to reduce calls to JS" },
|
||||||
|
{ path = "web_sys::Window::get_computed_style", reason = "cache this to reduce calls to JS" },
|
||||||
|
{ path = "web_sys::HtmlElement::style", reason = "cache this to reduce calls to JS" },
|
||||||
|
{ path = "web_sys::Element::request_fullscreen", reason = "Doesn't account for compatibility with Safari" },
|
||||||
|
{ path = "web_sys::Document::exit_fullscreen", reason = "Doesn't account for compatibility with Safari" },
|
||||||
|
{ path = "web_sys::Document::fullscreen_element", reason = "Doesn't account for compatibility with Safari" },
|
||||||
|
{ path = "icrate::AppKit::NSView::visibleRect", reason = "We expose a render target to the user, and visibility is not really relevant to that (and can break if you don't use the rectangle position as well). Use `frame` instead." },
|
||||||
|
{ path = "icrate::AppKit::NSWindow::setFrameTopLeftPoint", reason = "Not sufficient when working with Winit's coordinate system, use `flip_window_screen_coordinates` instead" },
|
||||||
|
]
|
||||||
65
deny.toml
Normal file
65
deny.toml
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# https://embarkstudios.github.io/cargo-deny/
|
||||||
|
# cargo install cargo-deny
|
||||||
|
# cargo update && cargo deny --all-features --log-level error --target aarch64-apple-ios check
|
||||||
|
# Note: running just `cargo deny check` without a `--target` will result in
|
||||||
|
# false positives due to https://github.com/EmbarkStudios/cargo-deny/issues/324
|
||||||
|
targets = [
|
||||||
|
{ triple = "aarch64-apple-ios" },
|
||||||
|
{ triple = "aarch64-linux-android" },
|
||||||
|
{ triple = "i686-pc-windows-gnu" },
|
||||||
|
{ triple = "i686-pc-windows-msvc" },
|
||||||
|
{ triple = "i686-unknown-linux-gnu" },
|
||||||
|
{ triple = "wasm32-unknown-unknown" },
|
||||||
|
{ triple = "x86_64-apple-darwin" },
|
||||||
|
{ triple = "x86_64-apple-ios" },
|
||||||
|
{ triple = "x86_64-pc-windows-gnu" },
|
||||||
|
{ triple = "x86_64-pc-windows-msvc" },
|
||||||
|
{ triple = "x86_64-unknown-linux-gnu" },
|
||||||
|
{ triple = "x86_64-unknown-redox" },
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
[advisories]
|
||||||
|
vulnerability = "deny"
|
||||||
|
unmaintained = "warn"
|
||||||
|
yanked = "deny"
|
||||||
|
ignore = []
|
||||||
|
|
||||||
|
|
||||||
|
[bans]
|
||||||
|
multiple-versions = "deny"
|
||||||
|
wildcards = "allow" # at least until https://github.com/EmbarkStudios/cargo-deny/issues/241 is fixed
|
||||||
|
deny = []
|
||||||
|
skip = [
|
||||||
|
{ name = "raw-window-handle" }, # we intentionally have multiple versions of this
|
||||||
|
{ name = "bitflags" }, # the ecosystem is in the process of migrating.
|
||||||
|
{ name = "libloading" }, # x11rb uses a different version until the next update
|
||||||
|
]
|
||||||
|
skip-tree = [
|
||||||
|
{ name = "run-wasm", version = "0.1.0" }
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
[licenses]
|
||||||
|
private = { ignore = true }
|
||||||
|
unlicensed = "deny"
|
||||||
|
allow-osi-fsf-free = "neither"
|
||||||
|
confidence-threshold = 0.92 # We want really high confidence when inferring licenses from text
|
||||||
|
copyleft = "deny"
|
||||||
|
allow = [
|
||||||
|
"Apache-2.0 WITH LLVM-exception", # https://spdx.org/licenses/LLVM-exception.html
|
||||||
|
"Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)
|
||||||
|
"BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd)
|
||||||
|
"BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised)
|
||||||
|
"BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained
|
||||||
|
"CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/
|
||||||
|
"ISC", # https://tldrlegal.com/license/-isc-license
|
||||||
|
"LicenseRef-UFL-1.0", # https://tldrlegal.com/license/ubuntu-font-license,-1.0 - no official SPDX, see https://github.com/emilk/egui/issues/2321
|
||||||
|
"MIT-0", # https://choosealicense.com/licenses/mit-0/
|
||||||
|
"MIT", # https://tldrlegal.com/license/mit-license
|
||||||
|
"MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11. Used by webpki-roots on Linux.
|
||||||
|
"OFL-1.1", # https://spdx.org/licenses/OFL-1.1.html
|
||||||
|
"OpenSSL", # https://www.openssl.org/source/license.html - used on Linux
|
||||||
|
"Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html
|
||||||
|
"Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib)
|
||||||
|
]
|
||||||
11
docs/res/ATTRIBUTION.md
Normal file
11
docs/res/ATTRIBUTION.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Image Attribution
|
||||||
|
|
||||||
|
These images are used in the documentation of `winit`.
|
||||||
|
|
||||||
|
## keyboard_*.svg
|
||||||
|
|
||||||
|
These files are a modified version of "[ANSI US QWERTY (Windows)](https://commons.wikimedia.org/wiki/File:ANSI_US_QWERTY_(Windows).svg)"
|
||||||
|
by [Tomiĉo] (https://commons.wikimedia.org/wiki/User:Tomi%C4%89o). It was
|
||||||
|
originally released under the [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en)
|
||||||
|
License. Minor modifications have been made by [John Nunley](https://github.com/notgull),
|
||||||
|
which have been released under the same license as a derivative work.
|
||||||
1
docs/res/keyboard_left_shift_key.svg
Normal file
1
docs/res/keyboard_left_shift_key.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 73 KiB |
1
docs/res/keyboard_numpad_1_key.svg
Normal file
1
docs/res/keyboard_numpad_1_key.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 73 KiB |
1
docs/res/keyboard_right_shift_key.svg
Normal file
1
docs/res/keyboard_right_shift_key.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 73 KiB |
1
docs/res/keyboard_standard_1_key.svg
Normal file
1
docs/res/keyboard_standard_1_key.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 73 KiB |
@@ -1,90 +0,0 @@
|
|||||||
#![allow(clippy::single_match)]
|
|
||||||
|
|
||||||
use simple_logger::SimpleLogger;
|
|
||||||
use winit::{
|
|
||||||
event::{ElementState, Event, KeyboardInput, WindowEvent},
|
|
||||||
event_loop::EventLoop,
|
|
||||||
window::{CursorIcon, WindowBuilder},
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
SimpleLogger::new().init().unwrap();
|
|
||||||
let event_loop = EventLoop::new();
|
|
||||||
|
|
||||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
|
||||||
window.set_title("A fantastic window!");
|
|
||||||
|
|
||||||
let mut cursor_idx = 0;
|
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
|
||||||
control_flow.set_wait();
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent {
|
|
||||||
event:
|
|
||||||
WindowEvent::KeyboardInput {
|
|
||||||
input:
|
|
||||||
KeyboardInput {
|
|
||||||
state: ElementState::Pressed,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
println!("Setting cursor to \"{:?}\"", CURSORS[cursor_idx]);
|
|
||||||
window.set_cursor_icon(CURSORS[cursor_idx]);
|
|
||||||
if cursor_idx < CURSORS.len() - 1 {
|
|
||||||
cursor_idx += 1;
|
|
||||||
} else {
|
|
||||||
cursor_idx = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::CloseRequested,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
control_flow.set_exit();
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const CURSORS: &[CursorIcon] = &[
|
|
||||||
CursorIcon::Default,
|
|
||||||
CursorIcon::Crosshair,
|
|
||||||
CursorIcon::Hand,
|
|
||||||
CursorIcon::Arrow,
|
|
||||||
CursorIcon::Move,
|
|
||||||
CursorIcon::Text,
|
|
||||||
CursorIcon::Wait,
|
|
||||||
CursorIcon::Help,
|
|
||||||
CursorIcon::Progress,
|
|
||||||
CursorIcon::NotAllowed,
|
|
||||||
CursorIcon::ContextMenu,
|
|
||||||
CursorIcon::Cell,
|
|
||||||
CursorIcon::VerticalText,
|
|
||||||
CursorIcon::Alias,
|
|
||||||
CursorIcon::Copy,
|
|
||||||
CursorIcon::NoDrop,
|
|
||||||
CursorIcon::Grab,
|
|
||||||
CursorIcon::Grabbing,
|
|
||||||
CursorIcon::AllScroll,
|
|
||||||
CursorIcon::ZoomIn,
|
|
||||||
CursorIcon::ZoomOut,
|
|
||||||
CursorIcon::EResize,
|
|
||||||
CursorIcon::NResize,
|
|
||||||
CursorIcon::NeResize,
|
|
||||||
CursorIcon::NwResize,
|
|
||||||
CursorIcon::SResize,
|
|
||||||
CursorIcon::SeResize,
|
|
||||||
CursorIcon::SwResize,
|
|
||||||
CursorIcon::WResize,
|
|
||||||
CursorIcon::EwResize,
|
|
||||||
CursorIcon::NsResize,
|
|
||||||
CursorIcon::NeswResize,
|
|
||||||
CursorIcon::NwseResize,
|
|
||||||
CursorIcon::ColResize,
|
|
||||||
CursorIcon::RowResize,
|
|
||||||
];
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
#![allow(clippy::single_match)]
|
|
||||||
|
|
||||||
use simple_logger::SimpleLogger;
|
|
||||||
use winit::{
|
|
||||||
event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent},
|
|
||||||
event_loop::EventLoop,
|
|
||||||
window::{CursorGrabMode, WindowBuilder},
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
SimpleLogger::new().init().unwrap();
|
|
||||||
let event_loop = EventLoop::new();
|
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
|
||||||
.with_title("Super Cursor Grab'n'Hide Simulator 9000")
|
|
||||||
.build(&event_loop)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut modifiers = ModifiersState::default();
|
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
|
||||||
control_flow.set_wait();
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent { event, .. } => match event {
|
|
||||||
WindowEvent::CloseRequested => control_flow.set_exit(),
|
|
||||||
WindowEvent::KeyboardInput {
|
|
||||||
input:
|
|
||||||
KeyboardInput {
|
|
||||||
state: ElementState::Released,
|
|
||||||
virtual_keycode: Some(key),
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
use winit::event::VirtualKeyCode::*;
|
|
||||||
let result = match key {
|
|
||||||
Escape => {
|
|
||||||
control_flow.set_exit();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
G => window.set_cursor_grab(CursorGrabMode::Confined),
|
|
||||||
L => window.set_cursor_grab(CursorGrabMode::Locked),
|
|
||||||
A => window.set_cursor_grab(CursorGrabMode::None),
|
|
||||||
H => {
|
|
||||||
window.set_cursor_visible(modifiers.shift());
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
_ => Ok(()),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(err) = result {
|
|
||||||
println!("error: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WindowEvent::ModifiersChanged(m) => modifiers = m,
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
Event::DeviceEvent { event, .. } => match event {
|
|
||||||
DeviceEvent::MouseMotion { delta } => println!("mouse moved: {:?}", delta),
|
|
||||||
DeviceEvent::Button { button, state } => match state {
|
|
||||||
ElementState::Pressed => println!("mouse button {} pressed", button),
|
|
||||||
ElementState::Released => println!("mouse button {} released", button),
|
|
||||||
},
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
#![allow(clippy::single_match)]
|
|
||||||
|
|
||||||
use simple_logger::SimpleLogger;
|
|
||||||
use winit::{
|
|
||||||
event::{
|
|
||||||
ElementState, Event, KeyboardInput, MouseButton, StartCause, VirtualKeyCode, WindowEvent,
|
|
||||||
},
|
|
||||||
event_loop::EventLoop,
|
|
||||||
window::{Window, WindowBuilder, WindowId},
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
SimpleLogger::new().init().unwrap();
|
|
||||||
let event_loop = EventLoop::new();
|
|
||||||
|
|
||||||
let window_1 = WindowBuilder::new().build(&event_loop).unwrap();
|
|
||||||
let window_2 = WindowBuilder::new().build(&event_loop).unwrap();
|
|
||||||
|
|
||||||
let mut switched = false;
|
|
||||||
let mut entered_id = window_2.id();
|
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| match event {
|
|
||||||
Event::NewEvents(StartCause::Init) => {
|
|
||||||
eprintln!("Switch which window is to be dragged by pressing \"x\".")
|
|
||||||
}
|
|
||||||
Event::WindowEvent { event, window_id } => match event {
|
|
||||||
WindowEvent::CloseRequested => control_flow.set_exit(),
|
|
||||||
WindowEvent::MouseInput {
|
|
||||||
state: ElementState::Pressed,
|
|
||||||
button: MouseButton::Left,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let window = if (window_id == window_1.id() && switched)
|
|
||||||
|| (window_id == window_2.id() && !switched)
|
|
||||||
{
|
|
||||||
&window_2
|
|
||||||
} else {
|
|
||||||
&window_1
|
|
||||||
};
|
|
||||||
|
|
||||||
window.drag_window().unwrap()
|
|
||||||
}
|
|
||||||
WindowEvent::CursorEntered { .. } => {
|
|
||||||
entered_id = window_id;
|
|
||||||
name_windows(entered_id, switched, &window_1, &window_2)
|
|
||||||
}
|
|
||||||
WindowEvent::KeyboardInput {
|
|
||||||
input:
|
|
||||||
KeyboardInput {
|
|
||||||
state: ElementState::Released,
|
|
||||||
virtual_keycode: Some(VirtualKeyCode::X),
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
switched = !switched;
|
|
||||||
name_windows(entered_id, switched, &window_1, &window_2);
|
|
||||||
println!("Switched!")
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
_ => (),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn name_windows(window_id: WindowId, switched: bool, window_1: &Window, window_2: &Window) {
|
|
||||||
let (drag_target, other) =
|
|
||||||
if (window_id == window_1.id() && switched) || (window_id == window_2.id() && !switched) {
|
|
||||||
(&window_2, &window_1)
|
|
||||||
} else {
|
|
||||||
(&window_1, &window_2)
|
|
||||||
};
|
|
||||||
drag_target.set_title("drag target");
|
|
||||||
other.set_title("winit window");
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
#![allow(clippy::single_match)]
|
|
||||||
|
|
||||||
use simple_logger::SimpleLogger;
|
|
||||||
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
|
|
||||||
use winit::event_loop::EventLoop;
|
|
||||||
use winit::window::{Fullscreen, WindowBuilder};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
SimpleLogger::new().init().unwrap();
|
|
||||||
let event_loop = EventLoop::new();
|
|
||||||
|
|
||||||
let mut decorations = true;
|
|
||||||
let mut minimized = false;
|
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
|
||||||
.with_title("Hello world!")
|
|
||||||
.build(&event_loop)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut monitor_index = 0;
|
|
||||||
let mut monitor = event_loop
|
|
||||||
.available_monitors()
|
|
||||||
.next()
|
|
||||||
.expect("no monitor found!");
|
|
||||||
println!("Monitor: {:?}", monitor.name());
|
|
||||||
|
|
||||||
let mut mode_index = 0;
|
|
||||||
let mut mode = monitor.video_modes().next().expect("no mode found");
|
|
||||||
println!("Mode: {}", mode);
|
|
||||||
|
|
||||||
println!("Keys:");
|
|
||||||
println!("- Esc\tExit");
|
|
||||||
println!("- F\tToggle exclusive fullscreen mode");
|
|
||||||
println!("- B\tToggle borderless mode");
|
|
||||||
println!("- S\tNext screen");
|
|
||||||
println!("- M\tNext mode for this screen");
|
|
||||||
println!("- D\tToggle window decorations");
|
|
||||||
println!("- X\tMaximize window");
|
|
||||||
println!("- Z\tMinimize window");
|
|
||||||
|
|
||||||
event_loop.run(move |event, elwt, control_flow| {
|
|
||||||
control_flow.set_wait();
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent { event, .. } => match event {
|
|
||||||
WindowEvent::CloseRequested => control_flow.set_exit(),
|
|
||||||
WindowEvent::KeyboardInput {
|
|
||||||
input:
|
|
||||||
KeyboardInput {
|
|
||||||
virtual_keycode: Some(virtual_code),
|
|
||||||
state: ElementState::Pressed,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
|
||||||
} => match virtual_code {
|
|
||||||
VirtualKeyCode::Escape => control_flow.set_exit(),
|
|
||||||
VirtualKeyCode::F | VirtualKeyCode::B if window.fullscreen().is_some() => {
|
|
||||||
window.set_fullscreen(None);
|
|
||||||
}
|
|
||||||
VirtualKeyCode::F => {
|
|
||||||
let fullscreen = Some(Fullscreen::Exclusive(mode.clone()));
|
|
||||||
println!("Setting mode: {fullscreen:?}");
|
|
||||||
window.set_fullscreen(fullscreen);
|
|
||||||
}
|
|
||||||
VirtualKeyCode::B => {
|
|
||||||
let fullscreen = Some(Fullscreen::Borderless(Some(monitor.clone())));
|
|
||||||
println!("Setting mode: {fullscreen:?}");
|
|
||||||
window.set_fullscreen(fullscreen);
|
|
||||||
}
|
|
||||||
VirtualKeyCode::S => {
|
|
||||||
monitor_index += 1;
|
|
||||||
if let Some(mon) = elwt.available_monitors().nth(monitor_index) {
|
|
||||||
monitor = mon;
|
|
||||||
} else {
|
|
||||||
monitor_index = 0;
|
|
||||||
monitor = elwt.available_monitors().next().expect("no monitor found!");
|
|
||||||
}
|
|
||||||
println!("Monitor: {:?}", monitor.name());
|
|
||||||
|
|
||||||
mode_index = 0;
|
|
||||||
mode = monitor.video_modes().next().expect("no mode found");
|
|
||||||
println!("Mode: {}", mode);
|
|
||||||
}
|
|
||||||
VirtualKeyCode::M => {
|
|
||||||
mode_index += 1;
|
|
||||||
if let Some(m) = monitor.video_modes().nth(mode_index) {
|
|
||||||
mode = m;
|
|
||||||
} else {
|
|
||||||
mode_index = 0;
|
|
||||||
mode = monitor.video_modes().next().expect("no mode found");
|
|
||||||
}
|
|
||||||
println!("Mode: {}", mode);
|
|
||||||
}
|
|
||||||
VirtualKeyCode::D => {
|
|
||||||
decorations = !decorations;
|
|
||||||
window.set_decorations(decorations);
|
|
||||||
}
|
|
||||||
VirtualKeyCode::X => {
|
|
||||||
let is_maximized = window.is_maximized();
|
|
||||||
window.set_maximized(!is_maximized);
|
|
||||||
}
|
|
||||||
VirtualKeyCode::Z => {
|
|
||||||
minimized = !minimized;
|
|
||||||
window.set_minimized(minimized);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
#![allow(clippy::single_match)]
|
|
||||||
|
|
||||||
use simple_logger::SimpleLogger;
|
|
||||||
use winit::{
|
|
||||||
event::{Event, KeyboardInput, WindowEvent},
|
|
||||||
event_loop::EventLoop,
|
|
||||||
window::WindowBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
SimpleLogger::new().init().unwrap();
|
|
||||||
let event_loop = EventLoop::new();
|
|
||||||
|
|
||||||
let _window = WindowBuilder::new()
|
|
||||||
.with_title("Your faithful window")
|
|
||||||
.build(&event_loop)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut close_requested = false;
|
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
|
||||||
use winit::event::{
|
|
||||||
ElementState::Released,
|
|
||||||
VirtualKeyCode::{N, Y},
|
|
||||||
};
|
|
||||||
control_flow.set_wait();
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent { event, .. } => {
|
|
||||||
match event {
|
|
||||||
WindowEvent::CloseRequested => {
|
|
||||||
// `CloseRequested` is sent when the close button on the window is pressed (or
|
|
||||||
// through whatever other mechanisms the window manager provides for closing a
|
|
||||||
// window). If you don't handle this event, the close button won't actually do
|
|
||||||
// anything.
|
|
||||||
|
|
||||||
// A common thing to do here is prompt the user if they have unsaved work.
|
|
||||||
// Creating a proper dialog box for that is far beyond the scope of this
|
|
||||||
// example, so here we'll just respond to the Y and N keys.
|
|
||||||
println!("Are you ready to bid your window farewell? [Y/N]");
|
|
||||||
close_requested = true;
|
|
||||||
|
|
||||||
// In applications where you can safely close the window without further
|
|
||||||
// action from the user, this is generally where you'd handle cleanup before
|
|
||||||
// closing the window. How to close the window is detailed in the handler for
|
|
||||||
// the Y key.
|
|
||||||
}
|
|
||||||
WindowEvent::KeyboardInput {
|
|
||||||
input:
|
|
||||||
KeyboardInput {
|
|
||||||
virtual_keycode: Some(virtual_code),
|
|
||||||
state: Released,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
match virtual_code {
|
|
||||||
Y => {
|
|
||||||
if close_requested {
|
|
||||||
// This is where you'll want to do any cleanup you need.
|
|
||||||
println!("Buh-bye!");
|
|
||||||
|
|
||||||
// For a single-window application like this, you'd normally just
|
|
||||||
// break out of the event loop here. If you wanted to keep running the
|
|
||||||
// event loop (i.e. if it's a multi-window application), you need to
|
|
||||||
// drop the window. That closes it, and results in `Destroyed` being
|
|
||||||
// sent.
|
|
||||||
control_flow.set_exit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
N => {
|
|
||||||
if close_requested {
|
|
||||||
println!("Your window will continue to stay by your side.");
|
|
||||||
close_requested = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
#![allow(clippy::single_match)]
|
|
||||||
|
|
||||||
use log::LevelFilter;
|
|
||||||
use simple_logger::SimpleLogger;
|
|
||||||
use winit::{
|
|
||||||
dpi::PhysicalPosition,
|
|
||||||
event::{ElementState, Event, Ime, VirtualKeyCode, WindowEvent},
|
|
||||||
event_loop::{ControlFlow, EventLoop},
|
|
||||||
window::WindowBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
SimpleLogger::new()
|
|
||||||
.with_level(LevelFilter::Trace)
|
|
||||||
.init()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
println!("IME position will system default");
|
|
||||||
println!("Click to set IME position to cursor's");
|
|
||||||
println!("Press F2 to toggle IME. See the documentation of `set_ime_allowed` for more info");
|
|
||||||
|
|
||||||
let event_loop = EventLoop::new();
|
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
|
||||||
.with_inner_size(winit::dpi::LogicalSize::new(256f64, 128f64))
|
|
||||||
.build(&event_loop)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut ime_allowed = true;
|
|
||||||
window.set_ime_allowed(ime_allowed);
|
|
||||||
|
|
||||||
let mut may_show_ime = false;
|
|
||||||
let mut cursor_position = PhysicalPosition::new(0.0, 0.0);
|
|
||||||
let mut ime_pos = PhysicalPosition::new(0.0, 0.0);
|
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
|
||||||
*control_flow = ControlFlow::Wait;
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::CloseRequested,
|
|
||||||
..
|
|
||||||
} => *control_flow = ControlFlow::Exit,
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::CursorMoved { position, .. },
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
cursor_position = position;
|
|
||||||
}
|
|
||||||
Event::WindowEvent {
|
|
||||||
event:
|
|
||||||
WindowEvent::MouseInput {
|
|
||||||
state: ElementState::Released,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
println!(
|
|
||||||
"Setting ime position to {}, {}",
|
|
||||||
cursor_position.x, cursor_position.y
|
|
||||||
);
|
|
||||||
ime_pos = cursor_position;
|
|
||||||
if may_show_ime {
|
|
||||||
window.set_ime_position(ime_pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::Ime(event),
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
println!("{:?}", event);
|
|
||||||
may_show_ime = event != Ime::Disabled;
|
|
||||||
if may_show_ime {
|
|
||||||
window.set_ime_position(ime_pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::ReceivedCharacter(ch),
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
println!("ch: {:?}", ch);
|
|
||||||
}
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::KeyboardInput { input, .. },
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
println!("key: {:?}", input);
|
|
||||||
|
|
||||||
if input.state == ElementState::Pressed
|
|
||||||
&& input.virtual_keycode == Some(VirtualKeyCode::F2)
|
|
||||||
{
|
|
||||||
ime_allowed = !ime_allowed;
|
|
||||||
window.set_ime_allowed(ime_allowed);
|
|
||||||
println!("\nIME: {}\n", ime_allowed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
#![allow(clippy::single_match)]
|
|
||||||
|
|
||||||
use simple_logger::SimpleLogger;
|
|
||||||
use winit::{event_loop::EventLoop, window::WindowBuilder};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
SimpleLogger::new().init().unwrap();
|
|
||||||
let event_loop = EventLoop::new();
|
|
||||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
|
||||||
|
|
||||||
dbg!(window.available_monitors().collect::<Vec<_>>());
|
|
||||||
dbg!(window.primary_monitor());
|
|
||||||
}
|
|
||||||
@@ -1,197 +0,0 @@
|
|||||||
#![allow(clippy::single_match)]
|
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
fn main() {
|
|
||||||
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
|
|
||||||
|
|
||||||
use simple_logger::SimpleLogger;
|
|
||||||
use winit::{
|
|
||||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
|
||||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
|
||||||
event_loop::EventLoop,
|
|
||||||
window::{CursorGrabMode, CursorIcon, Fullscreen, WindowBuilder},
|
|
||||||
};
|
|
||||||
|
|
||||||
const WINDOW_COUNT: usize = 3;
|
|
||||||
const WINDOW_SIZE: PhysicalSize<u32> = PhysicalSize::new(600, 400);
|
|
||||||
|
|
||||||
SimpleLogger::new().init().unwrap();
|
|
||||||
let event_loop = EventLoop::new();
|
|
||||||
let mut window_senders = HashMap::with_capacity(WINDOW_COUNT);
|
|
||||||
for _ in 0..WINDOW_COUNT {
|
|
||||||
let window = WindowBuilder::new()
|
|
||||||
.with_inner_size(WINDOW_SIZE)
|
|
||||||
.build(&event_loop)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut video_modes: Vec<_> = window.current_monitor().unwrap().video_modes().collect();
|
|
||||||
let mut video_mode_id = 0usize;
|
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel();
|
|
||||||
window_senders.insert(window.id(), tx);
|
|
||||||
thread::spawn(move || {
|
|
||||||
while let Ok(event) = rx.recv() {
|
|
||||||
match event {
|
|
||||||
WindowEvent::Moved { .. } => {
|
|
||||||
// We need to update our chosen video mode if the window
|
|
||||||
// was moved to an another monitor, so that the window
|
|
||||||
// appears on this monitor instead when we go fullscreen
|
|
||||||
let previous_video_mode = video_modes.get(video_mode_id).cloned();
|
|
||||||
video_modes = window.current_monitor().unwrap().video_modes().collect();
|
|
||||||
video_mode_id = video_mode_id.min(video_modes.len());
|
|
||||||
let video_mode = video_modes.get(video_mode_id);
|
|
||||||
|
|
||||||
// Different monitors may support different video modes,
|
|
||||||
// and the index we chose previously may now point to a
|
|
||||||
// completely different video mode, so notify the user
|
|
||||||
if video_mode != previous_video_mode.as_ref() {
|
|
||||||
println!(
|
|
||||||
"Window moved to another monitor, picked video mode: {}",
|
|
||||||
video_modes.get(video_mode_id).unwrap()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[allow(deprecated)]
|
|
||||||
WindowEvent::KeyboardInput {
|
|
||||||
input:
|
|
||||||
KeyboardInput {
|
|
||||||
state: ElementState::Released,
|
|
||||||
virtual_keycode: Some(key),
|
|
||||||
modifiers,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
window.set_title(&format!("{:?}", key));
|
|
||||||
let state = !modifiers.shift();
|
|
||||||
use VirtualKeyCode::*;
|
|
||||||
match key {
|
|
||||||
A => window.set_always_on_top(state),
|
|
||||||
C => window.set_cursor_icon(match state {
|
|
||||||
true => CursorIcon::Progress,
|
|
||||||
false => CursorIcon::Default,
|
|
||||||
}),
|
|
||||||
D => window.set_decorations(!state),
|
|
||||||
// Cycle through video modes
|
|
||||||
Right | Left => {
|
|
||||||
video_mode_id = match key {
|
|
||||||
Left => video_mode_id.saturating_sub(1),
|
|
||||||
Right => (video_modes.len() - 1).min(video_mode_id + 1),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
println!("Picking video mode: {}", video_modes[video_mode_id]);
|
|
||||||
}
|
|
||||||
F => window.set_fullscreen(match (state, modifiers.alt()) {
|
|
||||||
(true, false) => Some(Fullscreen::Borderless(None)),
|
|
||||||
(true, true) => {
|
|
||||||
Some(Fullscreen::Exclusive(video_modes[video_mode_id].clone()))
|
|
||||||
}
|
|
||||||
(false, _) => None,
|
|
||||||
}),
|
|
||||||
L if state => {
|
|
||||||
if let Err(err) = window.set_cursor_grab(CursorGrabMode::Locked) {
|
|
||||||
println!("error: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
G if state => {
|
|
||||||
if let Err(err) = window.set_cursor_grab(CursorGrabMode::Confined) {
|
|
||||||
println!("error: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
G | L if !state => {
|
|
||||||
if let Err(err) = window.set_cursor_grab(CursorGrabMode::None) {
|
|
||||||
println!("error: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
H => window.set_cursor_visible(!state),
|
|
||||||
I => {
|
|
||||||
println!("Info:");
|
|
||||||
println!("-> outer_position : {:?}", window.outer_position());
|
|
||||||
println!("-> inner_position : {:?}", window.inner_position());
|
|
||||||
println!("-> outer_size : {:?}", window.outer_size());
|
|
||||||
println!("-> inner_size : {:?}", window.inner_size());
|
|
||||||
println!("-> fullscreen : {:?}", window.fullscreen());
|
|
||||||
}
|
|
||||||
L => window.set_min_inner_size(match state {
|
|
||||||
true => Some(WINDOW_SIZE),
|
|
||||||
false => None,
|
|
||||||
}),
|
|
||||||
M => window.set_maximized(state),
|
|
||||||
P => window.set_outer_position({
|
|
||||||
let mut position = window.outer_position().unwrap();
|
|
||||||
let sign = if state { 1 } else { -1 };
|
|
||||||
position.x += 10 * sign;
|
|
||||||
position.y += 10 * sign;
|
|
||||||
position
|
|
||||||
}),
|
|
||||||
Q => window.request_redraw(),
|
|
||||||
R => window.set_resizable(state),
|
|
||||||
S => window.set_inner_size(match state {
|
|
||||||
true => PhysicalSize::new(
|
|
||||||
WINDOW_SIZE.width + 100,
|
|
||||||
WINDOW_SIZE.height + 100,
|
|
||||||
),
|
|
||||||
false => WINDOW_SIZE,
|
|
||||||
}),
|
|
||||||
W => {
|
|
||||||
if let Size::Physical(size) = WINDOW_SIZE.into() {
|
|
||||||
window
|
|
||||||
.set_cursor_position(Position::Physical(
|
|
||||||
PhysicalPosition::new(
|
|
||||||
size.width as i32 / 2,
|
|
||||||
size.height as i32 / 2,
|
|
||||||
),
|
|
||||||
))
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Z => {
|
|
||||||
window.set_visible(false);
|
|
||||||
thread::sleep(Duration::from_secs(1));
|
|
||||||
window.set_visible(true);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
event_loop.run(move |event, _event_loop, control_flow| {
|
|
||||||
match !window_senders.is_empty() {
|
|
||||||
true => control_flow.set_wait(),
|
|
||||||
false => control_flow.set_exit(),
|
|
||||||
};
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent { event, window_id } => match event {
|
|
||||||
WindowEvent::CloseRequested
|
|
||||||
| WindowEvent::Destroyed
|
|
||||||
| WindowEvent::KeyboardInput {
|
|
||||||
input:
|
|
||||||
KeyboardInput {
|
|
||||||
state: ElementState::Released,
|
|
||||||
virtual_keycode: Some(VirtualKeyCode::Escape),
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
window_senders.remove(&window_id);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
if let Some(tx) = window_senders.get(&window_id) {
|
|
||||||
if let Some(event) = event.to_static() {
|
|
||||||
tx.send(event).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
fn main() {
|
|
||||||
panic!("Example not supported on Wasm");
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
#![allow(clippy::single_match)]
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use simple_logger::SimpleLogger;
|
|
||||||
use winit::{
|
|
||||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
|
||||||
event_loop::EventLoop,
|
|
||||||
window::Window,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
SimpleLogger::new().init().unwrap();
|
|
||||||
let event_loop = EventLoop::new();
|
|
||||||
|
|
||||||
let mut windows = HashMap::new();
|
|
||||||
for _ in 0..3 {
|
|
||||||
let window = Window::new(&event_loop).unwrap();
|
|
||||||
println!("Opened a new window: {:?}", window.id());
|
|
||||||
windows.insert(window.id(), window);
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("Press N to open a new window.");
|
|
||||||
|
|
||||||
event_loop.run(move |event, event_loop, control_flow| {
|
|
||||||
control_flow.set_wait();
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent { event, window_id } => {
|
|
||||||
match event {
|
|
||||||
WindowEvent::CloseRequested => {
|
|
||||||
println!("Window {:?} has received the signal to close", window_id);
|
|
||||||
|
|
||||||
// This drops the window, causing it to close.
|
|
||||||
windows.remove(&window_id);
|
|
||||||
|
|
||||||
if windows.is_empty() {
|
|
||||||
control_flow.set_exit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WindowEvent::KeyboardInput {
|
|
||||||
input:
|
|
||||||
KeyboardInput {
|
|
||||||
state: ElementState::Pressed,
|
|
||||||
virtual_keycode: Some(VirtualKeyCode::N),
|
|
||||||
..
|
|
||||||
},
|
|
||||||
is_synthetic: false,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let window = Window::new(event_loop).unwrap();
|
|
||||||
println!("Opened a new window: {:?}", window.id());
|
|
||||||
windows.insert(window.id(), window);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
#![allow(clippy::single_match)]
|
|
||||||
|
|
||||||
use simple_logger::SimpleLogger;
|
|
||||||
use winit::{
|
|
||||||
event::{ElementState, Event, WindowEvent},
|
|
||||||
event_loop::EventLoop,
|
|
||||||
window::WindowBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
SimpleLogger::new().init().unwrap();
|
|
||||||
let event_loop = EventLoop::new();
|
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
|
||||||
.with_title("A fantastic window!")
|
|
||||||
.build(&event_loop)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
|
||||||
println!("{:?}", event);
|
|
||||||
|
|
||||||
control_flow.set_wait();
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent { event, .. } => match event {
|
|
||||||
WindowEvent::CloseRequested => control_flow.set_exit(),
|
|
||||||
WindowEvent::MouseInput {
|
|
||||||
state: ElementState::Released,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
Event::RedrawRequested(_) => {
|
|
||||||
println!("\nredrawing!\n");
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
#![allow(clippy::single_match)]
|
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
fn main() {
|
|
||||||
use std::{thread, time};
|
|
||||||
|
|
||||||
use simple_logger::SimpleLogger;
|
|
||||||
use winit::{
|
|
||||||
event::{Event, WindowEvent},
|
|
||||||
event_loop::EventLoop,
|
|
||||||
window::WindowBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
SimpleLogger::new().init().unwrap();
|
|
||||||
let event_loop = EventLoop::new();
|
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
|
||||||
.with_title("A fantastic window!")
|
|
||||||
.build(&event_loop)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
thread::spawn(move || loop {
|
|
||||||
thread::sleep(time::Duration::from_secs(1));
|
|
||||||
window.request_redraw();
|
|
||||||
});
|
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
|
||||||
println!("{:?}", event);
|
|
||||||
|
|
||||||
control_flow.set_wait();
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::CloseRequested,
|
|
||||||
..
|
|
||||||
} => control_flow.set_exit(),
|
|
||||||
Event::RedrawRequested(_) => {
|
|
||||||
println!("\nredrawing!\n");
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
fn main() {
|
|
||||||
unimplemented!() // `Window` can't be sent between threads
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
#![allow(clippy::single_match)]
|
|
||||||
|
|
||||||
use instant::Instant;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use simple_logger::SimpleLogger;
|
|
||||||
use winit::{
|
|
||||||
event::{Event, StartCause, WindowEvent},
|
|
||||||
event_loop::EventLoop,
|
|
||||||
window::WindowBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
SimpleLogger::new().init().unwrap();
|
|
||||||
let event_loop = EventLoop::new();
|
|
||||||
|
|
||||||
let _window = WindowBuilder::new()
|
|
||||||
.with_title("A fantastic window!")
|
|
||||||
.build(&event_loop)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let timer_length = Duration::new(1, 0);
|
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
|
||||||
println!("{:?}", event);
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::NewEvents(StartCause::Init) => {
|
|
||||||
control_flow.set_wait_until(Instant::now() + timer_length);
|
|
||||||
}
|
|
||||||
Event::NewEvents(StartCause::ResumeTimeReached { .. }) => {
|
|
||||||
control_flow.set_wait_until(Instant::now() + timer_length);
|
|
||||||
println!("\nTimer\n");
|
|
||||||
}
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::CloseRequested,
|
|
||||||
..
|
|
||||||
} => control_flow.set_exit(),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
#![allow(clippy::single_match)]
|
|
||||||
|
|
||||||
use simple_logger::SimpleLogger;
|
|
||||||
use winit::{
|
|
||||||
event::{Event, WindowEvent},
|
|
||||||
event_loop::EventLoop,
|
|
||||||
window::WindowBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
SimpleLogger::new().init().unwrap();
|
|
||||||
let event_loop = EventLoop::new();
|
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
|
||||||
.with_decorations(false)
|
|
||||||
.with_transparent(true)
|
|
||||||
.build(&event_loop)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
window.set_title("A fantastic window!");
|
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
|
||||||
control_flow.set_wait();
|
|
||||||
println!("{:?}", event);
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::CloseRequested,
|
|
||||||
..
|
|
||||||
} => control_flow.set_exit(),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
#![allow(clippy::single_match)]
|
|
||||||
|
|
||||||
use winit::{
|
|
||||||
event::{Event, WindowEvent},
|
|
||||||
event_loop::EventLoop,
|
|
||||||
window::WindowBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn main() {
|
|
||||||
let event_loop = EventLoop::new();
|
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
|
||||||
.with_title("A fantastic window!")
|
|
||||||
.build(&event_loop)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
let log_list = wasm::create_log_list(&window);
|
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
|
||||||
control_flow.set_wait();
|
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
wasm::log_event(&log_list, &event);
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::CloseRequested,
|
|
||||||
window_id,
|
|
||||||
} if window_id == window.id() => control_flow.set_exit(),
|
|
||||||
Event::MainEventsCleared => {
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
mod wasm {
|
|
||||||
use wasm_bindgen::prelude::*;
|
|
||||||
use winit::{event::Event, window::Window};
|
|
||||||
|
|
||||||
#[wasm_bindgen(start)]
|
|
||||||
pub fn run() {
|
|
||||||
console_log::init_with_level(log::Level::Debug).expect("error initializing logger");
|
|
||||||
|
|
||||||
#[allow(clippy::main_recursion)]
|
|
||||||
super::main();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_log_list(window: &Window) -> web_sys::Element {
|
|
||||||
use winit::platform::web::WindowExtWebSys;
|
|
||||||
|
|
||||||
let canvas = window.canvas();
|
|
||||||
|
|
||||||
let window = web_sys::window().unwrap();
|
|
||||||
let document = window.document().unwrap();
|
|
||||||
let body = document.body().unwrap();
|
|
||||||
|
|
||||||
// Set a background color for the canvas to make it easier to tell the where the canvas is for debugging purposes.
|
|
||||||
canvas.style().set_css_text("background-color: crimson;");
|
|
||||||
body.append_child(&canvas).unwrap();
|
|
||||||
|
|
||||||
let log_header = document.create_element("h2").unwrap();
|
|
||||||
log_header.set_text_content(Some("Event Log"));
|
|
||||||
body.append_child(&log_header).unwrap();
|
|
||||||
|
|
||||||
let log_list = document.create_element("ul").unwrap();
|
|
||||||
body.append_child(&log_list).unwrap();
|
|
||||||
log_list
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn log_event(log_list: &web_sys::Element, event: &Event<()>) {
|
|
||||||
log::debug!("{:?}", event);
|
|
||||||
|
|
||||||
// Getting access to browser logs requires a lot of setup on mobile devices.
|
|
||||||
// So we implement this basic logging system into the page to give developers an easy alternative.
|
|
||||||
// As a bonus its also kind of handy on desktop.
|
|
||||||
if let Event::WindowEvent { event, .. } = &event {
|
|
||||||
let window = web_sys::window().unwrap();
|
|
||||||
let document = window.document().unwrap();
|
|
||||||
let log = document.create_element("li").unwrap();
|
|
||||||
log.set_text_content(Some(&format!("{:?}", event)));
|
|
||||||
log_list
|
|
||||||
.insert_before(&log, log_list.first_child().as_ref())
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
#![allow(clippy::single_match)]
|
|
||||||
|
|
||||||
use simple_logger::SimpleLogger;
|
|
||||||
use winit::{
|
|
||||||
event::{Event, WindowEvent},
|
|
||||||
event_loop::EventLoop,
|
|
||||||
window::WindowBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
SimpleLogger::new().init().unwrap();
|
|
||||||
let event_loop = EventLoop::new();
|
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
|
||||||
.with_title("A fantastic window!")
|
|
||||||
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
|
|
||||||
.build(&event_loop)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
|
||||||
control_flow.set_wait();
|
|
||||||
println!("{:?}", event);
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::CloseRequested,
|
|
||||||
window_id,
|
|
||||||
} if window_id == window.id() => control_flow.set_exit(),
|
|
||||||
Event::MainEventsCleared => {
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
#![allow(clippy::single_match)]
|
|
||||||
|
|
||||||
// This example is used by developers to test various window functions.
|
|
||||||
|
|
||||||
use simple_logger::SimpleLogger;
|
|
||||||
use winit::{
|
|
||||||
dpi::{LogicalSize, PhysicalSize},
|
|
||||||
event::{DeviceEvent, ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
|
||||||
event_loop::{DeviceEventFilter, EventLoop},
|
|
||||||
window::{Fullscreen, WindowBuilder},
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
SimpleLogger::new().init().unwrap();
|
|
||||||
let event_loop = EventLoop::new();
|
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
|
||||||
.with_title("A fantastic window!")
|
|
||||||
.with_inner_size(LogicalSize::new(100.0, 100.0))
|
|
||||||
.build(&event_loop)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
eprintln!("debugging keys:");
|
|
||||||
eprintln!(" (E) Enter exclusive fullscreen");
|
|
||||||
eprintln!(" (F) Toggle borderless fullscreen");
|
|
||||||
eprintln!(" (P) Toggle borderless fullscreen on system's preffered monitor");
|
|
||||||
eprintln!(" (M) Toggle minimized");
|
|
||||||
eprintln!(" (Q) Quit event loop");
|
|
||||||
eprintln!(" (V) Toggle visibility");
|
|
||||||
eprintln!(" (X) Toggle maximized");
|
|
||||||
|
|
||||||
let mut minimized = false;
|
|
||||||
let mut visible = true;
|
|
||||||
|
|
||||||
event_loop.set_device_event_filter(DeviceEventFilter::Never);
|
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
|
||||||
control_flow.set_wait();
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::DeviceEvent {
|
|
||||||
event:
|
|
||||||
DeviceEvent::Key(KeyboardInput {
|
|
||||||
virtual_keycode: Some(key),
|
|
||||||
state: ElementState::Pressed,
|
|
||||||
..
|
|
||||||
}),
|
|
||||||
..
|
|
||||||
} => match key {
|
|
||||||
VirtualKeyCode::M => {
|
|
||||||
if minimized {
|
|
||||||
minimized = !minimized;
|
|
||||||
window.set_minimized(minimized);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
VirtualKeyCode::V => {
|
|
||||||
if !visible {
|
|
||||||
visible = !visible;
|
|
||||||
window.set_visible(visible);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
Event::WindowEvent {
|
|
||||||
event:
|
|
||||||
WindowEvent::KeyboardInput {
|
|
||||||
input:
|
|
||||||
KeyboardInput {
|
|
||||||
virtual_keycode: Some(key),
|
|
||||||
state: ElementState::Pressed,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
|
||||||
} => match key {
|
|
||||||
VirtualKeyCode::E => {
|
|
||||||
fn area(size: PhysicalSize<u32>) -> u32 {
|
|
||||||
size.width * size.height
|
|
||||||
}
|
|
||||||
|
|
||||||
let monitor = window.current_monitor().unwrap();
|
|
||||||
if let Some(mode) = monitor
|
|
||||||
.video_modes()
|
|
||||||
.max_by(|a, b| area(a.size()).cmp(&area(b.size())))
|
|
||||||
{
|
|
||||||
window.set_fullscreen(Some(Fullscreen::Exclusive(mode)));
|
|
||||||
} else {
|
|
||||||
eprintln!("no video modes available");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
VirtualKeyCode::F => {
|
|
||||||
if window.fullscreen().is_some() {
|
|
||||||
window.set_fullscreen(None);
|
|
||||||
} else {
|
|
||||||
let monitor = window.current_monitor();
|
|
||||||
window.set_fullscreen(Some(Fullscreen::Borderless(monitor)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
VirtualKeyCode::P => {
|
|
||||||
if window.fullscreen().is_some() {
|
|
||||||
window.set_fullscreen(None);
|
|
||||||
} else {
|
|
||||||
window.set_fullscreen(Some(Fullscreen::Borderless(None)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
VirtualKeyCode::M => {
|
|
||||||
minimized = !minimized;
|
|
||||||
window.set_minimized(minimized);
|
|
||||||
}
|
|
||||||
VirtualKeyCode::Q => {
|
|
||||||
control_flow.set_exit();
|
|
||||||
}
|
|
||||||
VirtualKeyCode::V => {
|
|
||||||
visible = !visible;
|
|
||||||
window.set_visible(visible);
|
|
||||||
}
|
|
||||||
VirtualKeyCode::X => {
|
|
||||||
let is_maximized = window.is_maximized();
|
|
||||||
window.set_maximized(!is_maximized);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::CloseRequested,
|
|
||||||
window_id,
|
|
||||||
} if window_id == window.id() => control_flow.set_exit(),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
#![allow(clippy::single_match)]
|
|
||||||
|
|
||||||
// Limit this example to only compatible platforms.
|
|
||||||
#[cfg(any(
|
|
||||||
target_os = "windows",
|
|
||||||
target_os = "macos",
|
|
||||||
target_os = "linux",
|
|
||||||
target_os = "dragonfly",
|
|
||||||
target_os = "freebsd",
|
|
||||||
target_os = "netbsd",
|
|
||||||
target_os = "openbsd",
|
|
||||||
target_os = "android",
|
|
||||||
))]
|
|
||||||
fn main() {
|
|
||||||
use std::{thread::sleep, time::Duration};
|
|
||||||
|
|
||||||
use simple_logger::SimpleLogger;
|
|
||||||
use winit::{
|
|
||||||
event::{Event, WindowEvent},
|
|
||||||
event_loop::EventLoop,
|
|
||||||
platform::run_return::EventLoopExtRunReturn,
|
|
||||||
window::WindowBuilder,
|
|
||||||
};
|
|
||||||
let mut event_loop = EventLoop::new();
|
|
||||||
|
|
||||||
SimpleLogger::new().init().unwrap();
|
|
||||||
let _window = WindowBuilder::new()
|
|
||||||
.with_title("A fantastic window!")
|
|
||||||
.build(&event_loop)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut quit = false;
|
|
||||||
|
|
||||||
while !quit {
|
|
||||||
event_loop.run_return(|event, _, control_flow| {
|
|
||||||
control_flow.set_wait();
|
|
||||||
|
|
||||||
if let Event::WindowEvent { event, .. } = &event {
|
|
||||||
// Print only Window events to reduce noise
|
|
||||||
println!("{:?}", event);
|
|
||||||
}
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::CloseRequested,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
quit = true;
|
|
||||||
}
|
|
||||||
Event::MainEventsCleared => {
|
|
||||||
control_flow.set_exit();
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sleep for 1/60 second to simulate rendering
|
|
||||||
println!("rendering");
|
|
||||||
sleep(Duration::from_millis(16));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(target_os = "ios", target_arch = "wasm32"))]
|
|
||||||
fn main() {
|
|
||||||
println!("This platform doesn't support run_return.");
|
|
||||||
}
|
|
||||||
@@ -2,8 +2,7 @@
|
|||||||
name = "run-wasm"
|
name = "run-wasm"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cargo-run-wasm = "0.1.0"
|
cargo-run-wasm = "0.2.0"
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
cargo_run_wasm::run_wasm();
|
cargo_run_wasm::run_wasm_with_css("body { margin: 0px; }");
|
||||||
}
|
}
|
||||||
|
|||||||
82
src/error.rs
82
src/error.rs
@@ -1,82 +0,0 @@
|
|||||||
use std::{error, fmt};
|
|
||||||
|
|
||||||
use crate::platform_impl;
|
|
||||||
|
|
||||||
/// An error whose cause it outside Winit's control.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum ExternalError {
|
|
||||||
/// The operation is not supported by the backend.
|
|
||||||
NotSupported(NotSupportedError),
|
|
||||||
/// The OS cannot perform the operation.
|
|
||||||
Os(OsError),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The error type for when the requested operation is not supported by the backend.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct NotSupportedError {
|
|
||||||
_marker: (),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The error type for when the OS cannot perform the requested operation.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct OsError {
|
|
||||||
line: u32,
|
|
||||||
file: &'static str,
|
|
||||||
error: platform_impl::OsError,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NotSupportedError {
|
|
||||||
#[inline]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub(crate) fn new() -> NotSupportedError {
|
|
||||||
NotSupportedError { _marker: () }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OsError {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub(crate) fn new(line: u32, file: &'static str, error: platform_impl::OsError) -> OsError {
|
|
||||||
OsError { line, file, error }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused_macros)]
|
|
||||||
macro_rules! os_error {
|
|
||||||
($error:expr) => {{
|
|
||||||
crate::error::OsError::new(line!(), file!(), $error)
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for OsError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
|
||||||
f.pad(&format!(
|
|
||||||
"os error at {}:{}: {}",
|
|
||||||
self.file, self.line, self.error
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for ExternalError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
|
||||||
match self {
|
|
||||||
ExternalError::NotSupported(e) => e.fmt(f),
|
|
||||||
ExternalError::Os(e) => e.fmt(f),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for NotSupportedError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
|
||||||
f.debug_struct("NotSupportedError").finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for NotSupportedError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
|
||||||
f.pad("the requested operation is not supported by Winit")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl error::Error for OsError {}
|
|
||||||
impl error::Error for ExternalError {}
|
|
||||||
impl error::Error for NotSupportedError {}
|
|
||||||
1281
src/event.rs
1281
src/event.rs
File diff suppressed because it is too large
Load Diff
@@ -1,410 +0,0 @@
|
|||||||
//! The [`EventLoop`] struct and assorted supporting types, including
|
|
||||||
//! [`ControlFlow`].
|
|
||||||
//!
|
|
||||||
//! If you want to send custom events to the event loop, use
|
|
||||||
//! [`EventLoop::create_proxy`] to acquire an [`EventLoopProxy`] and call its
|
|
||||||
//! [`send_event`](`EventLoopProxy::send_event`) method.
|
|
||||||
//!
|
|
||||||
//! See the root-level documentation for information on how to create and use an event loop to
|
|
||||||
//! handle events.
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::ops::Deref;
|
|
||||||
use std::{error, fmt};
|
|
||||||
|
|
||||||
use instant::Instant;
|
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
use raw_window_handle::{HasRawDisplayHandle, RawDisplayHandle};
|
|
||||||
|
|
||||||
use crate::{event::Event, monitor::MonitorHandle, platform_impl};
|
|
||||||
|
|
||||||
/// Provides a way to retrieve events from the system and from the windows that were registered to
|
|
||||||
/// the events loop.
|
|
||||||
///
|
|
||||||
/// An `EventLoop` can be seen more or less as a "context". Calling [`EventLoop::new`]
|
|
||||||
/// initializes everything that will be required to create windows. For example on Linux creating
|
|
||||||
/// an event loop opens a connection to the X or Wayland server.
|
|
||||||
///
|
|
||||||
/// To wake up an `EventLoop` from a another thread, see the [`EventLoopProxy`] docs.
|
|
||||||
///
|
|
||||||
/// Note that this cannot be shared across threads (due to platform-dependant logic
|
|
||||||
/// forbidding it), as such it is neither [`Send`] nor [`Sync`]. If you need cross-thread access, the
|
|
||||||
/// [`Window`] created from this _can_ be sent to an other thread, and the
|
|
||||||
/// [`EventLoopProxy`] allows you to wake up an `EventLoop` from another thread.
|
|
||||||
///
|
|
||||||
/// [`Window`]: crate::window::Window
|
|
||||||
pub struct EventLoop<T: 'static> {
|
|
||||||
pub(crate) event_loop: platform_impl::EventLoop<T>,
|
|
||||||
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Target that associates windows with an [`EventLoop`].
|
|
||||||
///
|
|
||||||
/// This type exists to allow you to create new windows while Winit executes
|
|
||||||
/// your callback. [`EventLoop`] will coerce into this type (`impl<T> Deref for
|
|
||||||
/// EventLoop<T>`), so functions that take this as a parameter can also take
|
|
||||||
/// `&EventLoop`.
|
|
||||||
pub struct EventLoopWindowTarget<T: 'static> {
|
|
||||||
pub(crate) p: platform_impl::EventLoopWindowTarget<T>,
|
|
||||||
pub(crate) _marker: PhantomData<*mut ()>, // Not Send nor Sync
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Object that allows building the event loop.
|
|
||||||
///
|
|
||||||
/// This is used to make specifying options that affect the whole application
|
|
||||||
/// easier. But note that constructing multiple event loops is not supported.
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct EventLoopBuilder<T: 'static> {
|
|
||||||
pub(crate) platform_specific: platform_impl::PlatformSpecificEventLoopAttributes,
|
|
||||||
_p: PhantomData<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventLoopBuilder<()> {
|
|
||||||
/// Start building a new event loop.
|
|
||||||
#[inline]
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::with_user_event()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopBuilder<T> {
|
|
||||||
/// Start building a new event loop, with the given type as the user event
|
|
||||||
/// type.
|
|
||||||
#[inline]
|
|
||||||
pub fn with_user_event() -> Self {
|
|
||||||
Self {
|
|
||||||
platform_specific: Default::default(),
|
|
||||||
_p: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Builds a new event loop.
|
|
||||||
///
|
|
||||||
/// ***For cross-platform compatibility, the [`EventLoop`] must be created on the main thread,
|
|
||||||
/// and only once per application.***
|
|
||||||
///
|
|
||||||
/// Attempting to create the event loop on a different thread, or multiple event loops in
|
|
||||||
/// the same application, will panic. This restriction isn't
|
|
||||||
/// strictly necessary on all platforms, but is imposed to eliminate any nasty surprises when
|
|
||||||
/// porting to platforms that require it. `EventLoopBuilderExt::any_thread` functions are exposed
|
|
||||||
/// in the relevant [`platform`] module if the target platform supports creating an event loop on
|
|
||||||
/// any thread.
|
|
||||||
///
|
|
||||||
/// Calling this function will result in display backend initialisation.
|
|
||||||
///
|
|
||||||
/// ## Platform-specific
|
|
||||||
///
|
|
||||||
/// - **Linux:** Backend type can be controlled using an environment variable
|
|
||||||
/// `WINIT_UNIX_BACKEND`. Legal values are `x11` and `wayland`.
|
|
||||||
/// If it is not set, winit will try to connect to a Wayland connection, and if that fails,
|
|
||||||
/// will fall back on X11. If this variable is set with any other value, winit will panic.
|
|
||||||
///
|
|
||||||
/// [`platform`]: crate::platform
|
|
||||||
#[inline]
|
|
||||||
pub fn build(&mut self) -> EventLoop<T> {
|
|
||||||
static EVENT_LOOP_CREATED: OnceCell<()> = OnceCell::new();
|
|
||||||
if EVENT_LOOP_CREATED.set(()).is_err() {
|
|
||||||
panic!("Creating EventLoop multiple times is not supported.");
|
|
||||||
}
|
|
||||||
// Certain platforms accept a mutable reference in their API.
|
|
||||||
#[allow(clippy::unnecessary_mut_passed)]
|
|
||||||
EventLoop {
|
|
||||||
event_loop: platform_impl::EventLoop::new(&mut self.platform_specific),
|
|
||||||
_marker: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> fmt::Debug for EventLoop<T> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.pad("EventLoop { .. }")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> fmt::Debug for EventLoopWindowTarget<T> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.pad("EventLoopWindowTarget { .. }")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set by the user callback given to the [`EventLoop::run`] method.
|
|
||||||
///
|
|
||||||
/// Indicates the desired behavior of the event loop after [`Event::RedrawEventsCleared`] is emitted.
|
|
||||||
///
|
|
||||||
/// Defaults to [`Poll`].
|
|
||||||
///
|
|
||||||
/// ## Persistency
|
|
||||||
///
|
|
||||||
/// Almost every change is persistent between multiple calls to the event loop closure within a
|
|
||||||
/// given run loop. The only exception to this is [`ExitWithCode`] which, once set, cannot be unset.
|
|
||||||
/// Changes are **not** persistent between multiple calls to `run_return` - issuing a new call will
|
|
||||||
/// reset the control flow to [`Poll`].
|
|
||||||
///
|
|
||||||
/// [`ExitWithCode`]: Self::ExitWithCode
|
|
||||||
/// [`Poll`]: Self::Poll
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub enum ControlFlow {
|
|
||||||
/// When the current loop iteration finishes, immediately begin a new iteration regardless of
|
|
||||||
/// whether or not new events are available to process.
|
|
||||||
///
|
|
||||||
/// ## Platform-specific
|
|
||||||
///
|
|
||||||
/// - **Web:** Events are queued and usually sent when `requestAnimationFrame` fires but sometimes
|
|
||||||
/// the events in the queue may be sent before the next `requestAnimationFrame` callback, for
|
|
||||||
/// example when the scaling of the page has changed. This should be treated as an implementation
|
|
||||||
/// detail which should not be relied on.
|
|
||||||
Poll,
|
|
||||||
/// When the current loop iteration finishes, suspend the thread until another event arrives.
|
|
||||||
Wait,
|
|
||||||
/// When the current loop iteration finishes, suspend the thread until either another event
|
|
||||||
/// arrives or the given time is reached.
|
|
||||||
///
|
|
||||||
/// Useful for implementing efficient timers. Applications which want to render at the display's
|
|
||||||
/// native refresh rate should instead use [`Poll`] and the VSync functionality of a graphics API
|
|
||||||
/// to reduce odds of missed frames.
|
|
||||||
///
|
|
||||||
/// [`Poll`]: Self::Poll
|
|
||||||
WaitUntil(Instant),
|
|
||||||
/// Send a [`LoopDestroyed`] event and stop the event loop. This variant is *sticky* - once set,
|
|
||||||
/// `control_flow` cannot be changed from `ExitWithCode`, and any future attempts to do so will
|
|
||||||
/// result in the `control_flow` parameter being reset to `ExitWithCode`.
|
|
||||||
///
|
|
||||||
/// The contained number will be used as exit code. The [`Exit`] constant is a shortcut for this
|
|
||||||
/// with exit code 0.
|
|
||||||
///
|
|
||||||
/// ## Platform-specific
|
|
||||||
///
|
|
||||||
/// - **Android / iOS / WASM:** The supplied exit code is unused.
|
|
||||||
/// - **Unix:** On most Unix-like platforms, only the 8 least significant bits will be used,
|
|
||||||
/// which can cause surprises with negative exit values (`-42` would end up as `214`). See
|
|
||||||
/// [`std::process::exit`].
|
|
||||||
///
|
|
||||||
/// [`LoopDestroyed`]: Event::LoopDestroyed
|
|
||||||
/// [`Exit`]: ControlFlow::Exit
|
|
||||||
ExitWithCode(i32),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ControlFlow {
|
|
||||||
/// Alias for [`ExitWithCode`]`(0)`.
|
|
||||||
///
|
|
||||||
/// [`ExitWithCode`]: Self::ExitWithCode
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
pub const Exit: Self = Self::ExitWithCode(0);
|
|
||||||
|
|
||||||
/// Sets this to [`Poll`].
|
|
||||||
///
|
|
||||||
/// [`Poll`]: Self::Poll
|
|
||||||
pub fn set_poll(&mut self) {
|
|
||||||
*self = Self::Poll;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets this to [`Wait`].
|
|
||||||
///
|
|
||||||
/// [`Wait`]: Self::Wait
|
|
||||||
pub fn set_wait(&mut self) {
|
|
||||||
*self = Self::Wait;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets this to [`WaitUntil`]`(instant)`.
|
|
||||||
///
|
|
||||||
/// [`WaitUntil`]: Self::WaitUntil
|
|
||||||
pub fn set_wait_until(&mut self, instant: Instant) {
|
|
||||||
*self = Self::WaitUntil(instant);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets this to [`ExitWithCode`]`(code)`.
|
|
||||||
///
|
|
||||||
/// [`ExitWithCode`]: Self::ExitWithCode
|
|
||||||
pub fn set_exit_with_code(&mut self, code: i32) {
|
|
||||||
*self = Self::ExitWithCode(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets this to [`Exit`].
|
|
||||||
///
|
|
||||||
/// [`Exit`]: Self::Exit
|
|
||||||
pub fn set_exit(&mut self) {
|
|
||||||
*self = Self::Exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ControlFlow {
|
|
||||||
#[inline(always)]
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Poll
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventLoop<()> {
|
|
||||||
/// Alias for [`EventLoopBuilder::new().build()`].
|
|
||||||
///
|
|
||||||
/// [`EventLoopBuilder::new().build()`]: EventLoopBuilder::build
|
|
||||||
#[inline]
|
|
||||||
pub fn new() -> EventLoop<()> {
|
|
||||||
EventLoopBuilder::new().build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for EventLoop<()> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoop<T> {
|
|
||||||
#[deprecated = "Use `EventLoopBuilder::<T>::with_user_event().build()` instead."]
|
|
||||||
pub fn with_user_event() -> EventLoop<T> {
|
|
||||||
EventLoopBuilder::<T>::with_user_event().build()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Hijacks the calling thread and initializes the winit event loop with the provided
|
|
||||||
/// closure. Since the closure is `'static`, it must be a `move` closure if it needs to
|
|
||||||
/// access any data from the calling context.
|
|
||||||
///
|
|
||||||
/// See the [`ControlFlow`] docs for information on how changes to `&mut ControlFlow` impact the
|
|
||||||
/// event loop's behavior.
|
|
||||||
///
|
|
||||||
/// Any values not passed to this function will *not* be dropped.
|
|
||||||
///
|
|
||||||
/// ## Platform-specific
|
|
||||||
///
|
|
||||||
/// - **X11 / Wayland:** The program terminates with exit code 1 if the display server
|
|
||||||
/// disconnects.
|
|
||||||
///
|
|
||||||
/// [`ControlFlow`]: crate::event_loop::ControlFlow
|
|
||||||
#[inline]
|
|
||||||
pub fn run<F>(self, event_handler: F) -> !
|
|
||||||
where
|
|
||||||
F: 'static + FnMut(Event<'_, T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
|
|
||||||
{
|
|
||||||
self.event_loop.run(event_handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates an [`EventLoopProxy`] that can be used to dispatch user events to the main event loop.
|
|
||||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
|
||||||
EventLoopProxy {
|
|
||||||
event_loop_proxy: self.event_loop.create_proxy(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Deref for EventLoop<T> {
|
|
||||||
type Target = EventLoopWindowTarget<T>;
|
|
||||||
fn deref(&self) -> &EventLoopWindowTarget<T> {
|
|
||||||
self.event_loop.window_target()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopWindowTarget<T> {
|
|
||||||
/// Returns the list of all the monitors available on the system.
|
|
||||||
#[inline]
|
|
||||||
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
|
|
||||||
self.p
|
|
||||||
.available_monitors()
|
|
||||||
.into_iter()
|
|
||||||
.map(|inner| MonitorHandle { inner })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the primary monitor of the system.
|
|
||||||
///
|
|
||||||
/// Returns `None` if it can't identify any monitor as a primary one.
|
|
||||||
///
|
|
||||||
/// ## Platform-specific
|
|
||||||
///
|
|
||||||
/// **Wayland:** Always returns `None`.
|
|
||||||
#[inline]
|
|
||||||
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
|
|
||||||
self.p.primary_monitor()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Change [`DeviceEvent`] filter mode.
|
|
||||||
///
|
|
||||||
/// Since the [`DeviceEvent`] capture can lead to high CPU usage for unfocused windows, winit
|
|
||||||
/// will ignore them by default for unfocused windows on Linux/BSD. This method allows changing
|
|
||||||
/// this filter at runtime to explicitly capture them again.
|
|
||||||
///
|
|
||||||
/// ## Platform-specific
|
|
||||||
///
|
|
||||||
/// - **Wayland / Windows / macOS / iOS / Android / Web:** Unsupported.
|
|
||||||
///
|
|
||||||
/// [`DeviceEvent`]: crate::event::DeviceEvent
|
|
||||||
pub fn set_device_event_filter(&self, _filter: DeviceEventFilter) {
|
|
||||||
#[cfg(any(
|
|
||||||
target_os = "linux",
|
|
||||||
target_os = "dragonfly",
|
|
||||||
target_os = "freebsd",
|
|
||||||
target_os = "netbsd",
|
|
||||||
target_os = "openbsd"
|
|
||||||
))]
|
|
||||||
self.p.set_device_event_filter(_filter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl<T> HasRawDisplayHandle for EventLoopWindowTarget<T> {
|
|
||||||
/// Returns a [`raw_window_handle::RawDisplayHandle`] for the event loop.
|
|
||||||
fn raw_display_handle(&self) -> RawDisplayHandle {
|
|
||||||
self.p.raw_display_handle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used to send custom events to [`EventLoop`].
|
|
||||||
pub struct EventLoopProxy<T: 'static> {
|
|
||||||
event_loop_proxy: platform_impl::EventLoopProxy<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> Clone for EventLoopProxy<T> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
event_loop_proxy: self.event_loop_proxy.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> EventLoopProxy<T> {
|
|
||||||
/// Send an event to the [`EventLoop`] from which this proxy was created. This emits a
|
|
||||||
/// `UserEvent(event)` event in the event loop, where `event` is the value passed to this
|
|
||||||
/// function.
|
|
||||||
///
|
|
||||||
/// Returns an `Err` if the associated [`EventLoop`] no longer exists.
|
|
||||||
///
|
|
||||||
/// [`UserEvent(event)`]: Event::UserEvent
|
|
||||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
|
||||||
self.event_loop_proxy.send_event(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> fmt::Debug for EventLoopProxy<T> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.pad("EventLoopProxy { .. }")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The error that is returned when an [`EventLoopProxy`] attempts to wake up an [`EventLoop`] that
|
|
||||||
/// no longer exists.
|
|
||||||
///
|
|
||||||
/// Contains the original event given to [`EventLoopProxy::send_event`].
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub struct EventLoopClosed<T>(pub T);
|
|
||||||
|
|
||||||
impl<T> fmt::Display for EventLoopClosed<T> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.write_str("Tried to wake up a closed `EventLoop`")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: fmt::Debug> error::Error for EventLoopClosed<T> {}
|
|
||||||
|
|
||||||
/// Filter controlling the propagation of device events.
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
|
||||||
pub enum DeviceEventFilter {
|
|
||||||
/// Always filter out device events.
|
|
||||||
Always,
|
|
||||||
/// Filter out device events while the window is not focused.
|
|
||||||
Unfocused,
|
|
||||||
/// Report all device events regardless of window focus.
|
|
||||||
Never,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for DeviceEventFilter {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::Unfocused
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
#![cfg(any(target_os = "android"))]
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
event_loop::{EventLoop, EventLoopWindowTarget},
|
|
||||||
window::{Window, WindowBuilder},
|
|
||||||
};
|
|
||||||
use ndk::configuration::Configuration;
|
|
||||||
use ndk_glue::Rect;
|
|
||||||
|
|
||||||
/// Additional methods on [`EventLoop`] that are specific to Android.
|
|
||||||
pub trait EventLoopExtAndroid {}
|
|
||||||
|
|
||||||
impl<T> EventLoopExtAndroid for EventLoop<T> {}
|
|
||||||
|
|
||||||
/// Additional methods on [`EventLoopWindowTarget`] that are specific to Android.
|
|
||||||
pub trait EventLoopWindowTargetExtAndroid {}
|
|
||||||
|
|
||||||
/// Additional methods on [`Window`] that are specific to Android.
|
|
||||||
pub trait WindowExtAndroid {
|
|
||||||
fn content_rect(&self) -> Rect;
|
|
||||||
|
|
||||||
fn config(&self) -> Configuration;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowExtAndroid for Window {
|
|
||||||
fn content_rect(&self) -> Rect {
|
|
||||||
self.window.content_rect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn config(&self) -> Configuration {
|
|
||||||
self.window.config()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopWindowTargetExtAndroid for EventLoopWindowTarget<T> {}
|
|
||||||
|
|
||||||
/// Additional methods on [`WindowBuilder`] that are specific to Android.
|
|
||||||
pub trait WindowBuilderExtAndroid {}
|
|
||||||
|
|
||||||
impl WindowBuilderExtAndroid for WindowBuilder {}
|
|
||||||
@@ -1,274 +0,0 @@
|
|||||||
#![cfg(target_os = "macos")]
|
|
||||||
|
|
||||||
use std::os::raw::c_void;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
dpi::LogicalSize,
|
|
||||||
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
|
|
||||||
monitor::MonitorHandle,
|
|
||||||
window::{Window, WindowBuilder},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Additional methods on [`Window`] that are specific to MacOS.
|
|
||||||
pub trait WindowExtMacOS {
|
|
||||||
/// Returns a pointer to the cocoa `NSWindow` that is used by this window.
|
|
||||||
///
|
|
||||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
|
||||||
fn ns_window(&self) -> *mut c_void;
|
|
||||||
|
|
||||||
/// Returns a pointer to the cocoa `NSView` that is used by this window.
|
|
||||||
///
|
|
||||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
|
||||||
fn ns_view(&self) -> *mut c_void;
|
|
||||||
|
|
||||||
/// Returns whether or not the window is in simple fullscreen mode.
|
|
||||||
fn simple_fullscreen(&self) -> bool;
|
|
||||||
|
|
||||||
/// Toggles a fullscreen mode that doesn't require a new macOS space.
|
|
||||||
/// Returns a boolean indicating whether the transition was successful (this
|
|
||||||
/// won't work if the window was already in the native fullscreen).
|
|
||||||
///
|
|
||||||
/// This is how fullscreen used to work on macOS in versions before Lion.
|
|
||||||
/// And allows the user to have a fullscreen window without using another
|
|
||||||
/// space or taking control over the entire monitor.
|
|
||||||
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool;
|
|
||||||
|
|
||||||
/// Returns whether or not the window has shadow.
|
|
||||||
fn has_shadow(&self) -> bool;
|
|
||||||
|
|
||||||
/// Sets whether or not the window has shadow.
|
|
||||||
fn set_has_shadow(&self, has_shadow: bool);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowExtMacOS for Window {
|
|
||||||
#[inline]
|
|
||||||
fn ns_window(&self) -> *mut c_void {
|
|
||||||
self.window.ns_window()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn ns_view(&self) -> *mut c_void {
|
|
||||||
self.window.ns_view()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn simple_fullscreen(&self) -> bool {
|
|
||||||
self.window.simple_fullscreen()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
|
|
||||||
self.window.set_simple_fullscreen(fullscreen)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn has_shadow(&self) -> bool {
|
|
||||||
self.window.has_shadow()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_has_shadow(&self, has_shadow: bool) {
|
|
||||||
self.window.set_has_shadow(has_shadow)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Corresponds to `NSApplicationActivationPolicy`.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
||||||
pub enum ActivationPolicy {
|
|
||||||
/// Corresponds to `NSApplicationActivationPolicyRegular`.
|
|
||||||
Regular,
|
|
||||||
/// Corresponds to `NSApplicationActivationPolicyAccessory`.
|
|
||||||
Accessory,
|
|
||||||
/// Corresponds to `NSApplicationActivationPolicyProhibited`.
|
|
||||||
Prohibited,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ActivationPolicy {
|
|
||||||
fn default() -> Self {
|
|
||||||
ActivationPolicy::Regular
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on [`WindowBuilder`] that are specific to MacOS.
|
|
||||||
///
|
|
||||||
/// **Note:** Properties dealing with the titlebar will be overwritten by the [`WindowBuilder::with_decorations`] method:
|
|
||||||
/// - `with_titlebar_transparent`
|
|
||||||
/// - `with_title_hidden`
|
|
||||||
/// - `with_titlebar_hidden`
|
|
||||||
/// - `with_titlebar_buttons_hidden`
|
|
||||||
/// - `with_fullsize_content_view`
|
|
||||||
pub trait WindowBuilderExtMacOS {
|
|
||||||
/// Enables click-and-drag behavior for the entire window, not just the titlebar.
|
|
||||||
fn with_movable_by_window_background(self, movable_by_window_background: bool)
|
|
||||||
-> WindowBuilder;
|
|
||||||
/// Makes the titlebar transparent and allows the content to appear behind it.
|
|
||||||
fn with_titlebar_transparent(self, titlebar_transparent: bool) -> WindowBuilder;
|
|
||||||
/// Hides the window title.
|
|
||||||
fn with_title_hidden(self, title_hidden: bool) -> WindowBuilder;
|
|
||||||
/// Hides the window titlebar.
|
|
||||||
fn with_titlebar_hidden(self, titlebar_hidden: bool) -> WindowBuilder;
|
|
||||||
/// Hides the window titlebar buttons.
|
|
||||||
fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> WindowBuilder;
|
|
||||||
/// Makes the window content appear behind the titlebar.
|
|
||||||
fn with_fullsize_content_view(self, fullsize_content_view: bool) -> WindowBuilder;
|
|
||||||
/// Build window with `resizeIncrements` property. Values must not be 0.
|
|
||||||
fn with_resize_increments(self, increments: LogicalSize<f64>) -> WindowBuilder;
|
|
||||||
fn with_disallow_hidpi(self, disallow_hidpi: bool) -> WindowBuilder;
|
|
||||||
fn with_has_shadow(self, has_shadow: bool) -> WindowBuilder;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowBuilderExtMacOS for WindowBuilder {
|
|
||||||
#[inline]
|
|
||||||
fn with_movable_by_window_background(
|
|
||||||
mut self,
|
|
||||||
movable_by_window_background: bool,
|
|
||||||
) -> WindowBuilder {
|
|
||||||
self.platform_specific.movable_by_window_background = movable_by_window_background;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> WindowBuilder {
|
|
||||||
self.platform_specific.titlebar_transparent = titlebar_transparent;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> WindowBuilder {
|
|
||||||
self.platform_specific.titlebar_hidden = titlebar_hidden;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> WindowBuilder {
|
|
||||||
self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_title_hidden(mut self, title_hidden: bool) -> WindowBuilder {
|
|
||||||
self.platform_specific.title_hidden = title_hidden;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> WindowBuilder {
|
|
||||||
self.platform_specific.fullsize_content_view = fullsize_content_view;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_resize_increments(mut self, increments: LogicalSize<f64>) -> WindowBuilder {
|
|
||||||
self.platform_specific.resize_increments = Some(increments);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> WindowBuilder {
|
|
||||||
self.platform_specific.disallow_hidpi = disallow_hidpi;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_has_shadow(mut self, has_shadow: bool) -> WindowBuilder {
|
|
||||||
self.platform_specific.has_shadow = has_shadow;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait EventLoopBuilderExtMacOS {
|
|
||||||
/// Sets the activation policy for the application.
|
|
||||||
///
|
|
||||||
/// It is set to [`ActivationPolicy::Regular`] by default.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// Set the activation policy to "accessory".
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use winit::event_loop::EventLoopBuilder;
|
|
||||||
/// #[cfg(target_os = "macos")]
|
|
||||||
/// use winit::platform::macos::{EventLoopBuilderExtMacOS, ActivationPolicy};
|
|
||||||
///
|
|
||||||
/// let mut builder = EventLoopBuilder::new();
|
|
||||||
/// #[cfg(target_os = "macos")]
|
|
||||||
/// builder.with_activation_policy(ActivationPolicy::Accessory);
|
|
||||||
/// # if false { // We can't test this part
|
|
||||||
/// let event_loop = builder.build();
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self;
|
|
||||||
|
|
||||||
/// Used to control whether a default menubar menu is created.
|
|
||||||
///
|
|
||||||
/// Menu creation is enabled by default.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// Disable creating a default menubar.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use winit::event_loop::EventLoopBuilder;
|
|
||||||
/// #[cfg(target_os = "macos")]
|
|
||||||
/// use winit::platform::macos::EventLoopBuilderExtMacOS;
|
|
||||||
///
|
|
||||||
/// let mut builder = EventLoopBuilder::new();
|
|
||||||
/// #[cfg(target_os = "macos")]
|
|
||||||
/// builder.with_default_menu(false);
|
|
||||||
/// # if false { // We can't test this part
|
|
||||||
/// let event_loop = builder.build();
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
fn with_default_menu(&mut self, enable: bool) -> &mut Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopBuilderExtMacOS for EventLoopBuilder<T> {
|
|
||||||
#[inline]
|
|
||||||
fn with_activation_policy(&mut self, activation_policy: ActivationPolicy) -> &mut Self {
|
|
||||||
self.platform_specific.activation_policy = activation_policy;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_default_menu(&mut self, enable: bool) -> &mut Self {
|
|
||||||
self.platform_specific.default_menu = enable;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on [`MonitorHandle`] that are specific to MacOS.
|
|
||||||
pub trait MonitorHandleExtMacOS {
|
|
||||||
/// Returns the identifier of the monitor for Cocoa.
|
|
||||||
fn native_id(&self) -> u32;
|
|
||||||
/// Returns a pointer to the NSScreen representing this monitor.
|
|
||||||
fn ns_screen(&self) -> Option<*mut c_void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MonitorHandleExtMacOS for MonitorHandle {
|
|
||||||
#[inline]
|
|
||||||
fn native_id(&self) -> u32 {
|
|
||||||
self.inner.native_identifier()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ns_screen(&self) -> Option<*mut c_void> {
|
|
||||||
self.inner.ns_screen().map(|s| s as *mut c_void)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on [`EventLoopWindowTarget`] that are specific to macOS.
|
|
||||||
pub trait EventLoopWindowTargetExtMacOS {
|
|
||||||
/// Hide the entire application. In most applications this is typically triggered with Command-H.
|
|
||||||
fn hide_application(&self);
|
|
||||||
/// Hide the other applications. In most applications this is typically triggered with Command+Option-H.
|
|
||||||
fn hide_other_applications(&self);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
|
|
||||||
fn hide_application(&self) {
|
|
||||||
self.p.hide_application()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hide_other_applications(&self) {
|
|
||||||
self.p.hide_other_applications()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
//! Contains traits with platform-specific methods in them.
|
|
||||||
//!
|
|
||||||
//! Contains the follow OS-specific modules:
|
|
||||||
//!
|
|
||||||
//! - `android`
|
|
||||||
//! - `ios`
|
|
||||||
//! - `macos`
|
|
||||||
//! - `unix`
|
|
||||||
//! - `windows`
|
|
||||||
//! - `web`
|
|
||||||
//!
|
|
||||||
//! And the following platform-specific module:
|
|
||||||
//!
|
|
||||||
//! - `run_return` (available on `windows`, `unix`, `macos`, and `android`)
|
|
||||||
//!
|
|
||||||
//! However only the module corresponding to the platform you're compiling to will be available.
|
|
||||||
|
|
||||||
pub mod android;
|
|
||||||
pub mod ios;
|
|
||||||
pub mod macos;
|
|
||||||
pub mod unix;
|
|
||||||
pub mod web;
|
|
||||||
pub mod windows;
|
|
||||||
|
|
||||||
pub mod run_return;
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
#![cfg(any(
|
|
||||||
target_os = "windows",
|
|
||||||
target_os = "macos",
|
|
||||||
target_os = "android",
|
|
||||||
target_os = "linux",
|
|
||||||
target_os = "dragonfly",
|
|
||||||
target_os = "freebsd",
|
|
||||||
target_os = "netbsd",
|
|
||||||
target_os = "openbsd"
|
|
||||||
))]
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
event::Event,
|
|
||||||
event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Additional methods on [`EventLoop`] to return control flow to the caller.
|
|
||||||
pub trait EventLoopExtRunReturn {
|
|
||||||
/// A type provided by the user that can be passed through [`Event::UserEvent`].
|
|
||||||
type UserEvent;
|
|
||||||
|
|
||||||
/// Initializes the `winit` event loop.
|
|
||||||
///
|
|
||||||
/// Unlike [`EventLoop::run`], this function accepts non-`'static` (i.e. non-`move`) closures
|
|
||||||
/// and returns control flow to the caller when `control_flow` is set to [`ControlFlow::Exit`].
|
|
||||||
///
|
|
||||||
/// # Caveats
|
|
||||||
///
|
|
||||||
/// Despite its appearance at first glance, this is *not* a perfect replacement for
|
|
||||||
/// `poll_events`. For example, this function will not return on Windows or macOS while a
|
|
||||||
/// window is getting resized, resulting in all application logic outside of the
|
|
||||||
/// `event_handler` closure not running until the resize operation ends. Other OS operations
|
|
||||||
/// may also result in such freezes. This behavior is caused by fundamental limitations in the
|
|
||||||
/// underlying OS APIs, which cannot be hidden by `winit` without severe stability repercussions.
|
|
||||||
///
|
|
||||||
/// You are strongly encouraged to use `run`, unless the use of this is absolutely necessary.
|
|
||||||
///
|
|
||||||
/// ## Platform-specific
|
|
||||||
///
|
|
||||||
/// - **X11 / Wayland:** This function returns `1` upon disconnection from
|
|
||||||
/// the display server.
|
|
||||||
fn run_return<F>(&mut self, event_handler: F) -> i32
|
|
||||||
where
|
|
||||||
F: FnMut(
|
|
||||||
Event<'_, Self::UserEvent>,
|
|
||||||
&EventLoopWindowTarget<Self::UserEvent>,
|
|
||||||
&mut ControlFlow,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopExtRunReturn for EventLoop<T> {
|
|
||||||
type UserEvent = T;
|
|
||||||
|
|
||||||
fn run_return<F>(&mut self, event_handler: F) -> i32
|
|
||||||
where
|
|
||||||
F: FnMut(
|
|
||||||
Event<'_, Self::UserEvent>,
|
|
||||||
&EventLoopWindowTarget<Self::UserEvent>,
|
|
||||||
&mut ControlFlow,
|
|
||||||
),
|
|
||||||
{
|
|
||||||
self.event_loop.run_return(event_handler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,463 +0,0 @@
|
|||||||
#![cfg(any(
|
|
||||||
target_os = "linux",
|
|
||||||
target_os = "dragonfly",
|
|
||||||
target_os = "freebsd",
|
|
||||||
target_os = "netbsd",
|
|
||||||
target_os = "openbsd"
|
|
||||||
))]
|
|
||||||
|
|
||||||
use std::os::raw;
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
use std::{ptr, sync::Arc};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
|
|
||||||
monitor::MonitorHandle,
|
|
||||||
window::{Window, WindowBuilder},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
use crate::dpi::Size;
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
use crate::platform_impl::{x11::ffi::XVisualInfo, x11::XConnection, XLIB_ERROR_HOOKS};
|
|
||||||
use crate::platform_impl::{
|
|
||||||
ApplicationName, Backend, EventLoopWindowTarget as LinuxEventLoopWindowTarget,
|
|
||||||
Window as LinuxWindow,
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: stupid hack so that glutin can do its work
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
pub use crate::platform_impl::x11;
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
|
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
pub use crate::window::Theme;
|
|
||||||
|
|
||||||
/// The first argument in the provided hook will be the pointer to `XDisplay`
|
|
||||||
/// and the second one the pointer to [`XErrorEvent`]. The returned `bool` is an
|
|
||||||
/// indicator whether the error was handled by the callback.
|
|
||||||
///
|
|
||||||
/// [`XErrorEvent`]: https://linux.die.net/man/3/xerrorevent
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
pub type XlibErrorHook =
|
|
||||||
Box<dyn Fn(*mut std::ffi::c_void, *mut std::ffi::c_void) -> bool + Send + Sync>;
|
|
||||||
|
|
||||||
/// Hook to winit's xlib error handling callback.
|
|
||||||
///
|
|
||||||
/// This method is provided as a safe way to handle the errors comming from X11 when using xlib
|
|
||||||
/// in external crates, like glutin for GLX access. Trying to handle errors by speculating with
|
|
||||||
/// `XSetErrorHandler` is [`unsafe`].
|
|
||||||
///
|
|
||||||
/// [`unsafe`]: https://www.remlab.net/op/xlib.shtml
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
pub fn register_xlib_error_hook(hook: XlibErrorHook) {
|
|
||||||
// Append new hook.
|
|
||||||
unsafe {
|
|
||||||
XLIB_ERROR_HOOKS.lock().push(hook);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on [`EventLoopWindowTarget`] that are specific to Unix.
|
|
||||||
pub trait EventLoopWindowTargetExtUnix {
|
|
||||||
/// True if the [`EventLoopWindowTarget`] uses Wayland.
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
fn is_wayland(&self) -> bool;
|
|
||||||
|
|
||||||
/// True if the [`EventLoopWindowTarget`] uses X11.
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn is_x11(&self) -> bool;
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
|
|
||||||
|
|
||||||
/// Returns a pointer to the `wl_display` object of wayland that is used by this
|
|
||||||
/// [`EventLoopWindowTarget`].
|
|
||||||
///
|
|
||||||
/// Returns `None` if the [`EventLoop`] doesn't use wayland (if it uses xlib for example).
|
|
||||||
///
|
|
||||||
/// The pointer will become invalid when the winit [`EventLoop`] is destroyed.
|
|
||||||
///
|
|
||||||
/// [`EventLoop`]: crate::event_loop::EventLoop
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
fn wayland_display(&self) -> Option<*mut raw::c_void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopWindowTargetExtUnix for EventLoopWindowTarget<T> {
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
fn is_wayland(&self) -> bool {
|
|
||||||
self.p.is_wayland()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn is_x11(&self) -> bool {
|
|
||||||
!self.p.is_wayland()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
|
|
||||||
match self.p {
|
|
||||||
LinuxEventLoopWindowTarget::X(ref e) => Some(e.x_connection().clone()),
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
fn wayland_display(&self) -> Option<*mut raw::c_void> {
|
|
||||||
match self.p {
|
|
||||||
LinuxEventLoopWindowTarget::Wayland(ref p) => {
|
|
||||||
Some(p.display().get_display_ptr() as *mut _)
|
|
||||||
}
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on [`EventLoopBuilder`] that are specific to Unix.
|
|
||||||
pub trait EventLoopBuilderExtUnix {
|
|
||||||
/// Force using X11.
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn with_x11(&mut self) -> &mut Self;
|
|
||||||
|
|
||||||
/// Force using Wayland.
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
fn with_wayland(&mut self) -> &mut Self;
|
|
||||||
|
|
||||||
/// Whether to allow the event loop to be created off of the main thread.
|
|
||||||
///
|
|
||||||
/// By default, the window is only allowed to be created on the main
|
|
||||||
/// thread, to make platform compatibility easier.
|
|
||||||
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopBuilderExtUnix for EventLoopBuilder<T> {
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn with_x11(&mut self) -> &mut Self {
|
|
||||||
self.platform_specific.forced_backend = Some(Backend::X);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
fn with_wayland(&mut self) -> &mut Self {
|
|
||||||
self.platform_specific.forced_backend = Some(Backend::Wayland);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_any_thread(&mut self, any_thread: bool) -> &mut Self {
|
|
||||||
self.platform_specific.any_thread = any_thread;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on [`Window`] that are specific to Unix.
|
|
||||||
pub trait WindowExtUnix {
|
|
||||||
/// Returns the ID of the [`Window`] xlib object that is used by this window.
|
|
||||||
///
|
|
||||||
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn xlib_window(&self) -> Option<raw::c_ulong>;
|
|
||||||
|
|
||||||
/// Returns a pointer to the `Display` object of xlib that is used by this window.
|
|
||||||
///
|
|
||||||
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
|
|
||||||
///
|
|
||||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn xlib_display(&self) -> Option<*mut raw::c_void>;
|
|
||||||
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn xlib_screen_id(&self) -> Option<raw::c_int>;
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
|
|
||||||
|
|
||||||
/// This function returns the underlying `xcb_connection_t` of an xlib `Display`.
|
|
||||||
///
|
|
||||||
/// Returns `None` if the window doesn't use xlib (if it uses wayland for example).
|
|
||||||
///
|
|
||||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn xcb_connection(&self) -> Option<*mut raw::c_void>;
|
|
||||||
|
|
||||||
/// Returns a pointer to the `wl_surface` object of wayland that is used by this window.
|
|
||||||
///
|
|
||||||
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
|
|
||||||
///
|
|
||||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
fn wayland_surface(&self) -> Option<*mut raw::c_void>;
|
|
||||||
|
|
||||||
/// Returns a pointer to the `wl_display` object of wayland that is used by this window.
|
|
||||||
///
|
|
||||||
/// Returns `None` if the window doesn't use wayland (if it uses xlib for example).
|
|
||||||
///
|
|
||||||
/// The pointer will become invalid when the [`Window`] is destroyed.
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
fn wayland_display(&self) -> Option<*mut raw::c_void>;
|
|
||||||
|
|
||||||
/// Updates [`Theme`] of window decorations.
|
|
||||||
///
|
|
||||||
/// You can also use `WINIT_WAYLAND_CSD_THEME` env variable to set the theme.
|
|
||||||
/// Possible values for env variable are: "dark" and light"
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
fn wayland_set_csd_theme(&self, config: Theme);
|
|
||||||
|
|
||||||
/// Check if the window is ready for drawing
|
|
||||||
///
|
|
||||||
/// It is a remnant of a previous implementation detail for the
|
|
||||||
/// wayland backend, and is no longer relevant.
|
|
||||||
///
|
|
||||||
/// Always return `true`.
|
|
||||||
#[deprecated]
|
|
||||||
fn is_ready(&self) -> bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowExtUnix for Window {
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn xlib_window(&self) -> Option<raw::c_ulong> {
|
|
||||||
match self.window {
|
|
||||||
LinuxWindow::X(ref w) => Some(w.xlib_window()),
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn xlib_display(&self) -> Option<*mut raw::c_void> {
|
|
||||||
match self.window {
|
|
||||||
LinuxWindow::X(ref w) => Some(w.xlib_display()),
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn xlib_screen_id(&self) -> Option<raw::c_int> {
|
|
||||||
match self.window {
|
|
||||||
LinuxWindow::X(ref w) => Some(w.xlib_screen_id()),
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
|
|
||||||
match self.window {
|
|
||||||
LinuxWindow::X(ref w) => Some(w.xlib_xconnection()),
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn xcb_connection(&self) -> Option<*mut raw::c_void> {
|
|
||||||
match self.window {
|
|
||||||
LinuxWindow::X(ref w) => Some(w.xcb_connection()),
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
fn wayland_surface(&self) -> Option<*mut raw::c_void> {
|
|
||||||
match self.window {
|
|
||||||
LinuxWindow::Wayland(ref w) => Some(w.surface().as_ref().c_ptr() as *mut _),
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
fn wayland_display(&self) -> Option<*mut raw::c_void> {
|
|
||||||
match self.window {
|
|
||||||
LinuxWindow::Wayland(ref w) => Some(w.display().get_display_ptr() as *mut _),
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
fn wayland_set_csd_theme(&self, theme: Theme) {
|
|
||||||
match self.window {
|
|
||||||
LinuxWindow::Wayland(ref w) => w.set_csd_theme(theme),
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn is_ready(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on [`WindowBuilder`] that are specific to Unix.
|
|
||||||
pub trait WindowBuilderExtUnix {
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn with_x11_visual<T>(self, visual_infos: *const T) -> Self;
|
|
||||||
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn with_x11_screen(self, screen_id: i32) -> Self;
|
|
||||||
|
|
||||||
/// Build window with the given `general` and `instance` names.
|
|
||||||
///
|
|
||||||
/// On Wayland, the `general` name sets an application ID, which should match the `.desktop`
|
|
||||||
/// file destributed with your program. The `instance` is a `no-op`.
|
|
||||||
///
|
|
||||||
/// On X11, the `general` sets general class of `WM_CLASS(STRING)`, while `instance` set the
|
|
||||||
/// instance part of it. The resulted property looks like `WM_CLASS(STRING) = "general", "instance"`.
|
|
||||||
///
|
|
||||||
/// For details about application ID conventions, see the
|
|
||||||
/// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id)
|
|
||||||
fn with_name(self, general: impl Into<String>, instance: impl Into<String>) -> Self;
|
|
||||||
|
|
||||||
/// Build window with override-redirect flag; defaults to false. Only relevant on X11.
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn with_override_redirect(self, override_redirect: bool) -> Self;
|
|
||||||
|
|
||||||
/// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. Only relevant on X11.
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn with_x11_window_type(self, x11_window_type: Vec<XWindowType>) -> Self;
|
|
||||||
|
|
||||||
/// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only relevant on X11.
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn with_gtk_theme_variant(self, variant: String) -> Self;
|
|
||||||
|
|
||||||
/// Build window with certain decoration [`Theme`]
|
|
||||||
///
|
|
||||||
/// You can also use `WINIT_WAYLAND_CSD_THEME` env variable to set the theme.
|
|
||||||
/// Possible values for env variable are: "dark" and light"
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
fn with_wayland_csd_theme(self, theme: Theme) -> Self;
|
|
||||||
|
|
||||||
/// Build window with resize increment hint. Only implemented on X11.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # use winit::dpi::{LogicalSize, PhysicalSize};
|
|
||||||
/// # use winit::window::WindowBuilder;
|
|
||||||
/// # use winit::platform::unix::WindowBuilderExtUnix;
|
|
||||||
/// // Specify the size in logical dimensions like this:
|
|
||||||
/// WindowBuilder::new().with_resize_increments(LogicalSize::new(400.0, 200.0));
|
|
||||||
///
|
|
||||||
/// // Or specify the size in physical dimensions like this:
|
|
||||||
/// WindowBuilder::new().with_resize_increments(PhysicalSize::new(400, 200));
|
|
||||||
/// ```
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn with_resize_increments<S: Into<Size>>(self, increments: S) -> Self;
|
|
||||||
|
|
||||||
/// Build window with base size hint. Only implemented on X11.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # use winit::dpi::{LogicalSize, PhysicalSize};
|
|
||||||
/// # use winit::window::WindowBuilder;
|
|
||||||
/// # use winit::platform::unix::WindowBuilderExtUnix;
|
|
||||||
/// // Specify the size in logical dimensions like this:
|
|
||||||
/// WindowBuilder::new().with_base_size(LogicalSize::new(400.0, 200.0));
|
|
||||||
///
|
|
||||||
/// // Or specify the size in physical dimensions like this:
|
|
||||||
/// WindowBuilder::new().with_base_size(PhysicalSize::new(400, 200));
|
|
||||||
/// ```
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowBuilderExtUnix for WindowBuilder {
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn with_x11_visual<T>(mut self, visual_infos: *const T) -> Self {
|
|
||||||
{
|
|
||||||
self.platform_specific.visual_infos =
|
|
||||||
Some(unsafe { ptr::read(visual_infos as *const XVisualInfo) });
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn with_x11_screen(mut self, screen_id: i32) -> Self {
|
|
||||||
self.platform_specific.screen_id = Some(screen_id);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_name(mut self, general: impl Into<String>, instance: impl Into<String>) -> Self {
|
|
||||||
self.platform_specific.name = Some(ApplicationName::new(general.into(), instance.into()));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn with_override_redirect(mut self, override_redirect: bool) -> Self {
|
|
||||||
self.platform_specific.override_redirect = override_redirect;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn with_x11_window_type(mut self, x11_window_types: Vec<XWindowType>) -> Self {
|
|
||||||
self.platform_specific.x11_window_types = x11_window_types;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn with_gtk_theme_variant(mut self, variant: String) -> Self {
|
|
||||||
self.platform_specific.gtk_theme_variant = Some(variant);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
fn with_wayland_csd_theme(mut self, theme: Theme) -> Self {
|
|
||||||
self.platform_specific.csd_theme = Some(theme);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn with_resize_increments<S: Into<Size>>(mut self, increments: S) -> Self {
|
|
||||||
self.platform_specific.resize_increments = Some(increments.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn with_base_size<S: Into<Size>>(mut self, base_size: S) -> Self {
|
|
||||||
self.platform_specific.base_size = Some(base_size.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on `MonitorHandle` that are specific to Linux.
|
|
||||||
pub trait MonitorHandleExtUnix {
|
|
||||||
/// Returns the inner identifier of the monitor.
|
|
||||||
fn native_id(&self) -> u32;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MonitorHandleExtUnix for MonitorHandle {
|
|
||||||
#[inline]
|
|
||||||
fn native_id(&self) -> u32 {
|
|
||||||
self.inner.native_identifier()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
#![cfg(target_arch = "wasm32")]
|
|
||||||
|
|
||||||
//! The web target does not automatically insert the canvas element object into the web page, to
|
|
||||||
//! allow end users to determine how the page should be laid out. Use the [`WindowExtWebSys`] trait
|
|
||||||
//! to retrieve the canvas from the Window. Alternatively, use the [`WindowBuilderExtWebSys`] trait
|
|
||||||
//! to provide your own canvas.
|
|
||||||
|
|
||||||
use crate::event::Event;
|
|
||||||
use crate::event_loop::ControlFlow;
|
|
||||||
use crate::event_loop::EventLoop;
|
|
||||||
use crate::event_loop::EventLoopWindowTarget;
|
|
||||||
use crate::window::WindowBuilder;
|
|
||||||
|
|
||||||
use web_sys::HtmlCanvasElement;
|
|
||||||
|
|
||||||
pub trait WindowExtWebSys {
|
|
||||||
fn canvas(&self) -> HtmlCanvasElement;
|
|
||||||
|
|
||||||
/// Whether the browser reports the preferred color scheme to be "dark".
|
|
||||||
fn is_dark_mode(&self) -> bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait WindowBuilderExtWebSys {
|
|
||||||
fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self;
|
|
||||||
|
|
||||||
/// Whether `event.preventDefault` should be automatically called to prevent event propagation
|
|
||||||
/// when appropriate.
|
|
||||||
///
|
|
||||||
/// For example, mouse wheel events are only handled by the canvas by default. This avoids
|
|
||||||
/// the default behavior of scrolling the page.
|
|
||||||
fn with_prevent_default(self, prevent_default: bool) -> Self;
|
|
||||||
|
|
||||||
/// Whether the canvas should be focusable using the tab key. This is necessary to capture
|
|
||||||
/// canvas keyboard events.
|
|
||||||
fn with_focusable(self, focusable: bool) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowBuilderExtWebSys for WindowBuilder {
|
|
||||||
fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
|
|
||||||
self.platform_specific.canvas = canvas;
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_prevent_default(mut self, prevent_default: bool) -> Self {
|
|
||||||
self.platform_specific.prevent_default = prevent_default;
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_focusable(mut self, focusable: bool) -> Self {
|
|
||||||
self.platform_specific.focusable = focusable;
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on `EventLoop` that are specific to the web.
|
|
||||||
pub trait EventLoopExtWebSys {
|
|
||||||
/// A type provided by the user that can be passed through `Event::UserEvent`.
|
|
||||||
type UserEvent;
|
|
||||||
|
|
||||||
/// Initializes the winit event loop.
|
|
||||||
///
|
|
||||||
/// Unlike `run`, this returns immediately, and doesn't throw an exception in order to
|
|
||||||
/// satisfy its `!` return type.
|
|
||||||
fn spawn<F>(self, event_handler: F)
|
|
||||||
where
|
|
||||||
F: 'static
|
|
||||||
+ FnMut(
|
|
||||||
Event<'_, Self::UserEvent>,
|
|
||||||
&EventLoopWindowTarget<Self::UserEvent>,
|
|
||||||
&mut ControlFlow,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopExtWebSys for EventLoop<T> {
|
|
||||||
type UserEvent = T;
|
|
||||||
|
|
||||||
fn spawn<F>(self, event_handler: F)
|
|
||||||
where
|
|
||||||
F: 'static
|
|
||||||
+ FnMut(
|
|
||||||
Event<'_, Self::UserEvent>,
|
|
||||||
&EventLoopWindowTarget<Self::UserEvent>,
|
|
||||||
&mut ControlFlow,
|
|
||||||
),
|
|
||||||
{
|
|
||||||
self.event_loop.spawn(event_handler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,945 +0,0 @@
|
|||||||
#![cfg(target_os = "android")]
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
collections::VecDeque,
|
|
||||||
sync::{mpsc, RwLock},
|
|
||||||
time::{Duration, Instant},
|
|
||||||
};
|
|
||||||
|
|
||||||
use ndk::{
|
|
||||||
configuration::Configuration,
|
|
||||||
event::{InputEvent, KeyAction, Keycode, MotionAction},
|
|
||||||
looper::{ForeignLooper, Poll, ThreadLooper},
|
|
||||||
native_window::NativeWindow,
|
|
||||||
};
|
|
||||||
use ndk_glue::{Event, LockReadGuard, Rect};
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use raw_window_handle::{
|
|
||||||
AndroidDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
|
||||||
error,
|
|
||||||
event::{self, VirtualKeyCode},
|
|
||||||
event_loop::{self, ControlFlow},
|
|
||||||
monitor,
|
|
||||||
window::{self, CursorGrabMode},
|
|
||||||
};
|
|
||||||
|
|
||||||
static CONFIG: Lazy<RwLock<Configuration>> = Lazy::new(|| {
|
|
||||||
RwLock::new(Configuration::from_asset_manager(
|
|
||||||
#[allow(deprecated)] // TODO: rust-windowing/winit#2196
|
|
||||||
&ndk_glue::native_activity().asset_manager(),
|
|
||||||
))
|
|
||||||
});
|
|
||||||
// If this is `Some()` a `Poll::Wake` is considered an `EventSource::Internal` with the event
|
|
||||||
// contained in the `Option`. The event is moved outside of the `Option` replacing it with a
|
|
||||||
// `None`.
|
|
||||||
//
|
|
||||||
// This allows us to inject event into the event loop without going through `ndk-glue` and
|
|
||||||
// calling unsafe function that should only be called by Android.
|
|
||||||
static INTERNAL_EVENT: Lazy<RwLock<Option<InternalEvent>>> = Lazy::new(|| RwLock::new(None));
|
|
||||||
|
|
||||||
enum InternalEvent {
|
|
||||||
RedrawRequested,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum EventSource {
|
|
||||||
Callback,
|
|
||||||
InputQueue,
|
|
||||||
User,
|
|
||||||
Internal(InternalEvent),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ndk_keycode_to_virtualkeycode(keycode: Keycode) -> Option<event::VirtualKeyCode> {
|
|
||||||
match keycode {
|
|
||||||
Keycode::A => Some(VirtualKeyCode::A),
|
|
||||||
Keycode::B => Some(VirtualKeyCode::B),
|
|
||||||
Keycode::C => Some(VirtualKeyCode::C),
|
|
||||||
Keycode::D => Some(VirtualKeyCode::D),
|
|
||||||
Keycode::E => Some(VirtualKeyCode::E),
|
|
||||||
Keycode::F => Some(VirtualKeyCode::F),
|
|
||||||
Keycode::G => Some(VirtualKeyCode::G),
|
|
||||||
Keycode::H => Some(VirtualKeyCode::H),
|
|
||||||
Keycode::I => Some(VirtualKeyCode::I),
|
|
||||||
Keycode::J => Some(VirtualKeyCode::J),
|
|
||||||
Keycode::K => Some(VirtualKeyCode::K),
|
|
||||||
Keycode::L => Some(VirtualKeyCode::L),
|
|
||||||
Keycode::M => Some(VirtualKeyCode::M),
|
|
||||||
Keycode::N => Some(VirtualKeyCode::N),
|
|
||||||
Keycode::O => Some(VirtualKeyCode::O),
|
|
||||||
Keycode::P => Some(VirtualKeyCode::P),
|
|
||||||
Keycode::Q => Some(VirtualKeyCode::Q),
|
|
||||||
Keycode::R => Some(VirtualKeyCode::R),
|
|
||||||
Keycode::S => Some(VirtualKeyCode::S),
|
|
||||||
Keycode::T => Some(VirtualKeyCode::T),
|
|
||||||
Keycode::U => Some(VirtualKeyCode::U),
|
|
||||||
Keycode::V => Some(VirtualKeyCode::V),
|
|
||||||
Keycode::W => Some(VirtualKeyCode::W),
|
|
||||||
Keycode::X => Some(VirtualKeyCode::X),
|
|
||||||
Keycode::Y => Some(VirtualKeyCode::Y),
|
|
||||||
Keycode::Z => Some(VirtualKeyCode::Z),
|
|
||||||
|
|
||||||
Keycode::Keycode0 => Some(VirtualKeyCode::Key0),
|
|
||||||
Keycode::Keycode1 => Some(VirtualKeyCode::Key1),
|
|
||||||
Keycode::Keycode2 => Some(VirtualKeyCode::Key2),
|
|
||||||
Keycode::Keycode3 => Some(VirtualKeyCode::Key3),
|
|
||||||
Keycode::Keycode4 => Some(VirtualKeyCode::Key4),
|
|
||||||
Keycode::Keycode5 => Some(VirtualKeyCode::Key5),
|
|
||||||
Keycode::Keycode6 => Some(VirtualKeyCode::Key6),
|
|
||||||
Keycode::Keycode7 => Some(VirtualKeyCode::Key7),
|
|
||||||
Keycode::Keycode8 => Some(VirtualKeyCode::Key8),
|
|
||||||
Keycode::Keycode9 => Some(VirtualKeyCode::Key9),
|
|
||||||
|
|
||||||
Keycode::Numpad0 => Some(VirtualKeyCode::Numpad0),
|
|
||||||
Keycode::Numpad1 => Some(VirtualKeyCode::Numpad1),
|
|
||||||
Keycode::Numpad2 => Some(VirtualKeyCode::Numpad2),
|
|
||||||
Keycode::Numpad3 => Some(VirtualKeyCode::Numpad3),
|
|
||||||
Keycode::Numpad4 => Some(VirtualKeyCode::Numpad4),
|
|
||||||
Keycode::Numpad5 => Some(VirtualKeyCode::Numpad5),
|
|
||||||
Keycode::Numpad6 => Some(VirtualKeyCode::Numpad6),
|
|
||||||
Keycode::Numpad7 => Some(VirtualKeyCode::Numpad7),
|
|
||||||
Keycode::Numpad8 => Some(VirtualKeyCode::Numpad8),
|
|
||||||
Keycode::Numpad9 => Some(VirtualKeyCode::Numpad9),
|
|
||||||
|
|
||||||
Keycode::NumpadAdd => Some(VirtualKeyCode::NumpadAdd),
|
|
||||||
Keycode::NumpadSubtract => Some(VirtualKeyCode::NumpadSubtract),
|
|
||||||
Keycode::NumpadMultiply => Some(VirtualKeyCode::NumpadMultiply),
|
|
||||||
Keycode::NumpadDivide => Some(VirtualKeyCode::NumpadDivide),
|
|
||||||
Keycode::NumpadEnter => Some(VirtualKeyCode::NumpadEnter),
|
|
||||||
Keycode::NumpadEquals => Some(VirtualKeyCode::NumpadEquals),
|
|
||||||
Keycode::NumpadComma => Some(VirtualKeyCode::NumpadComma),
|
|
||||||
Keycode::NumpadDot => Some(VirtualKeyCode::NumpadDecimal),
|
|
||||||
Keycode::NumLock => Some(VirtualKeyCode::Numlock),
|
|
||||||
|
|
||||||
Keycode::DpadLeft => Some(VirtualKeyCode::Left),
|
|
||||||
Keycode::DpadRight => Some(VirtualKeyCode::Right),
|
|
||||||
Keycode::DpadUp => Some(VirtualKeyCode::Up),
|
|
||||||
Keycode::DpadDown => Some(VirtualKeyCode::Down),
|
|
||||||
|
|
||||||
Keycode::F1 => Some(VirtualKeyCode::F1),
|
|
||||||
Keycode::F2 => Some(VirtualKeyCode::F2),
|
|
||||||
Keycode::F3 => Some(VirtualKeyCode::F3),
|
|
||||||
Keycode::F4 => Some(VirtualKeyCode::F4),
|
|
||||||
Keycode::F5 => Some(VirtualKeyCode::F5),
|
|
||||||
Keycode::F6 => Some(VirtualKeyCode::F6),
|
|
||||||
Keycode::F7 => Some(VirtualKeyCode::F7),
|
|
||||||
Keycode::F8 => Some(VirtualKeyCode::F8),
|
|
||||||
Keycode::F9 => Some(VirtualKeyCode::F9),
|
|
||||||
Keycode::F10 => Some(VirtualKeyCode::F10),
|
|
||||||
Keycode::F11 => Some(VirtualKeyCode::F11),
|
|
||||||
Keycode::F12 => Some(VirtualKeyCode::F12),
|
|
||||||
|
|
||||||
Keycode::Space => Some(VirtualKeyCode::Space),
|
|
||||||
Keycode::Escape => Some(VirtualKeyCode::Escape),
|
|
||||||
Keycode::Enter => Some(VirtualKeyCode::Return), // not on the Numpad
|
|
||||||
Keycode::Tab => Some(VirtualKeyCode::Tab),
|
|
||||||
|
|
||||||
Keycode::PageUp => Some(VirtualKeyCode::PageUp),
|
|
||||||
Keycode::PageDown => Some(VirtualKeyCode::PageDown),
|
|
||||||
Keycode::MoveHome => Some(VirtualKeyCode::Home),
|
|
||||||
Keycode::MoveEnd => Some(VirtualKeyCode::End),
|
|
||||||
Keycode::Insert => Some(VirtualKeyCode::Insert),
|
|
||||||
|
|
||||||
Keycode::Del => Some(VirtualKeyCode::Back), // Backspace (above Enter)
|
|
||||||
Keycode::ForwardDel => Some(VirtualKeyCode::Delete), // Delete (below Insert)
|
|
||||||
|
|
||||||
Keycode::Copy => Some(VirtualKeyCode::Copy),
|
|
||||||
Keycode::Paste => Some(VirtualKeyCode::Paste),
|
|
||||||
Keycode::Cut => Some(VirtualKeyCode::Cut),
|
|
||||||
|
|
||||||
Keycode::VolumeUp => Some(VirtualKeyCode::VolumeUp),
|
|
||||||
Keycode::VolumeDown => Some(VirtualKeyCode::VolumeDown),
|
|
||||||
Keycode::VolumeMute => Some(VirtualKeyCode::Mute), // ???
|
|
||||||
Keycode::Mute => Some(VirtualKeyCode::Mute), // ???
|
|
||||||
Keycode::MediaPlayPause => Some(VirtualKeyCode::PlayPause),
|
|
||||||
Keycode::MediaStop => Some(VirtualKeyCode::MediaStop), // ??? simple "Stop"?
|
|
||||||
Keycode::MediaNext => Some(VirtualKeyCode::NextTrack),
|
|
||||||
Keycode::MediaPrevious => Some(VirtualKeyCode::PrevTrack),
|
|
||||||
|
|
||||||
Keycode::Plus => Some(VirtualKeyCode::Plus),
|
|
||||||
Keycode::Minus => Some(VirtualKeyCode::Minus),
|
|
||||||
Keycode::Equals => Some(VirtualKeyCode::Equals),
|
|
||||||
Keycode::Semicolon => Some(VirtualKeyCode::Semicolon),
|
|
||||||
Keycode::Slash => Some(VirtualKeyCode::Slash),
|
|
||||||
Keycode::Backslash => Some(VirtualKeyCode::Backslash),
|
|
||||||
Keycode::Comma => Some(VirtualKeyCode::Comma),
|
|
||||||
Keycode::Period => Some(VirtualKeyCode::Period),
|
|
||||||
Keycode::Apostrophe => Some(VirtualKeyCode::Apostrophe),
|
|
||||||
Keycode::Grave => Some(VirtualKeyCode::Grave),
|
|
||||||
Keycode::At => Some(VirtualKeyCode::At),
|
|
||||||
|
|
||||||
// TODO: Maybe mapping this to Snapshot makes more sense? See: "PrtScr/SysRq"
|
|
||||||
Keycode::Sysrq => Some(VirtualKeyCode::Sysrq),
|
|
||||||
// These are usually the same (Pause/Break)
|
|
||||||
Keycode::Break => Some(VirtualKeyCode::Pause),
|
|
||||||
// These are exactly the same
|
|
||||||
Keycode::ScrollLock => Some(VirtualKeyCode::Scroll),
|
|
||||||
|
|
||||||
Keycode::Yen => Some(VirtualKeyCode::Yen),
|
|
||||||
Keycode::Kana => Some(VirtualKeyCode::Kana),
|
|
||||||
|
|
||||||
Keycode::CtrlLeft => Some(VirtualKeyCode::LControl),
|
|
||||||
Keycode::CtrlRight => Some(VirtualKeyCode::RControl),
|
|
||||||
|
|
||||||
Keycode::ShiftLeft => Some(VirtualKeyCode::LShift),
|
|
||||||
Keycode::ShiftRight => Some(VirtualKeyCode::RShift),
|
|
||||||
|
|
||||||
Keycode::AltLeft => Some(VirtualKeyCode::LAlt),
|
|
||||||
Keycode::AltRight => Some(VirtualKeyCode::RAlt),
|
|
||||||
|
|
||||||
// Different names for the same keys
|
|
||||||
Keycode::MetaLeft => Some(VirtualKeyCode::LWin),
|
|
||||||
Keycode::MetaRight => Some(VirtualKeyCode::RWin),
|
|
||||||
|
|
||||||
Keycode::LeftBracket => Some(VirtualKeyCode::LBracket),
|
|
||||||
Keycode::RightBracket => Some(VirtualKeyCode::RBracket),
|
|
||||||
|
|
||||||
Keycode::Power => Some(VirtualKeyCode::Power),
|
|
||||||
Keycode::Sleep => Some(VirtualKeyCode::Sleep), // what about SoftSleep?
|
|
||||||
Keycode::Wakeup => Some(VirtualKeyCode::Wake),
|
|
||||||
|
|
||||||
Keycode::NavigateNext => Some(VirtualKeyCode::NavigateForward),
|
|
||||||
Keycode::NavigatePrevious => Some(VirtualKeyCode::NavigateBackward),
|
|
||||||
|
|
||||||
Keycode::Calculator => Some(VirtualKeyCode::Calculator),
|
|
||||||
Keycode::Explorer => Some(VirtualKeyCode::MyComputer), // "close enough"
|
|
||||||
Keycode::Envelope => Some(VirtualKeyCode::Mail), // "close enough"
|
|
||||||
|
|
||||||
Keycode::Star => Some(VirtualKeyCode::Asterisk), // ???
|
|
||||||
Keycode::AllApps => Some(VirtualKeyCode::Apps), // ???
|
|
||||||
Keycode::AppSwitch => Some(VirtualKeyCode::Apps), // ???
|
|
||||||
Keycode::Refresh => Some(VirtualKeyCode::WebRefresh), // ???
|
|
||||||
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn poll(poll: Poll) -> Option<EventSource> {
|
|
||||||
match poll {
|
|
||||||
Poll::Event { ident, .. } => match ident {
|
|
||||||
ndk_glue::NDK_GLUE_LOOPER_EVENT_PIPE_IDENT => Some(EventSource::Callback),
|
|
||||||
ndk_glue::NDK_GLUE_LOOPER_INPUT_QUEUE_IDENT => Some(EventSource::InputQueue),
|
|
||||||
_ => unreachable!(),
|
|
||||||
},
|
|
||||||
Poll::Timeout => None,
|
|
||||||
Poll::Wake => Some(
|
|
||||||
INTERNAL_EVENT
|
|
||||||
.write()
|
|
||||||
.unwrap()
|
|
||||||
.take()
|
|
||||||
.map_or(EventSource::User, EventSource::Internal),
|
|
||||||
),
|
|
||||||
Poll::Callback => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventLoop<T: 'static> {
|
|
||||||
window_target: event_loop::EventLoopWindowTarget<T>,
|
|
||||||
user_events_sender: mpsc::Sender<T>,
|
|
||||||
user_events_receiver: mpsc::Receiver<T>,
|
|
||||||
first_event: Option<EventSource>,
|
|
||||||
start_cause: event::StartCause,
|
|
||||||
looper: ThreadLooper,
|
|
||||||
running: bool,
|
|
||||||
window_lock: Option<LockReadGuard<NativeWindow>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) struct PlatformSpecificEventLoopAttributes {}
|
|
||||||
|
|
||||||
macro_rules! call_event_handler {
|
|
||||||
( $event_handler:expr, $window_target:expr, $cf:expr, $event:expr ) => {{
|
|
||||||
if let ControlFlow::ExitWithCode(code) = $cf {
|
|
||||||
$event_handler($event, $window_target, &mut ControlFlow::ExitWithCode(code));
|
|
||||||
} else {
|
|
||||||
$event_handler($event, $window_target, &mut $cf);
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> EventLoop<T> {
|
|
||||||
pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> Self {
|
|
||||||
let (user_events_sender, user_events_receiver) = mpsc::channel();
|
|
||||||
Self {
|
|
||||||
window_target: event_loop::EventLoopWindowTarget {
|
|
||||||
p: EventLoopWindowTarget {
|
|
||||||
_marker: std::marker::PhantomData,
|
|
||||||
},
|
|
||||||
_marker: std::marker::PhantomData,
|
|
||||||
},
|
|
||||||
user_events_sender,
|
|
||||||
user_events_receiver,
|
|
||||||
first_event: None,
|
|
||||||
start_cause: event::StartCause::Init,
|
|
||||||
looper: ThreadLooper::for_thread().unwrap(),
|
|
||||||
running: false,
|
|
||||||
window_lock: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run<F>(mut self, event_handler: F) -> !
|
|
||||||
where
|
|
||||||
F: 'static
|
|
||||||
+ FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget<T>, &mut ControlFlow),
|
|
||||||
{
|
|
||||||
let exit_code = self.run_return(event_handler);
|
|
||||||
::std::process::exit(exit_code);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_return<F>(&mut self, mut event_handler: F) -> i32
|
|
||||||
where
|
|
||||||
F: FnMut(event::Event<'_, T>, &event_loop::EventLoopWindowTarget<T>, &mut ControlFlow),
|
|
||||||
{
|
|
||||||
let mut control_flow = ControlFlow::default();
|
|
||||||
|
|
||||||
'event_loop: loop {
|
|
||||||
call_event_handler!(
|
|
||||||
event_handler,
|
|
||||||
self.window_target(),
|
|
||||||
control_flow,
|
|
||||||
event::Event::NewEvents(self.start_cause)
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut redraw = false;
|
|
||||||
let mut resized = false;
|
|
||||||
|
|
||||||
match self.first_event.take() {
|
|
||||||
Some(EventSource::Callback) => match ndk_glue::poll_events().unwrap() {
|
|
||||||
Event::WindowCreated => {
|
|
||||||
// Acquire a lock on the window to prevent Android from destroying
|
|
||||||
// it until we've notified and waited for the user in Event::Suspended.
|
|
||||||
// WARNING: ndk-glue is inherently racy (https://github.com/rust-windowing/winit/issues/2293)
|
|
||||||
// and may have already received onNativeWindowDestroyed while this thread hasn't yet processed
|
|
||||||
// the event, and would see a `None` lock+window in that case.
|
|
||||||
if let Some(next_window_lock) = ndk_glue::native_window() {
|
|
||||||
assert!(
|
|
||||||
self.window_lock.replace(next_window_lock).is_none(),
|
|
||||||
"Received `Event::WindowCreated` while we were already holding a lock"
|
|
||||||
);
|
|
||||||
call_event_handler!(
|
|
||||||
event_handler,
|
|
||||||
self.window_target(),
|
|
||||||
control_flow,
|
|
||||||
event::Event::Resumed
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
warn!("Received `Event::WindowCreated` while `ndk_glue::native_window()` provides no window");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::WindowResized => resized = true,
|
|
||||||
Event::WindowRedrawNeeded => redraw = true,
|
|
||||||
Event::WindowDestroyed => {
|
|
||||||
// Release the lock, allowing Android to clean up this surface
|
|
||||||
// WARNING: See above - if ndk-glue is racy, this event may be called
|
|
||||||
// without having a `self.window_lock` in place.
|
|
||||||
if self.window_lock.take().is_some() {
|
|
||||||
call_event_handler!(
|
|
||||||
event_handler,
|
|
||||||
self.window_target(),
|
|
||||||
control_flow,
|
|
||||||
event::Event::Suspended
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
warn!("Received `Event::WindowDestroyed` while we were not holding a window lock");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::Pause => self.running = false,
|
|
||||||
Event::Resume => self.running = true,
|
|
||||||
Event::ConfigChanged => {
|
|
||||||
#[allow(deprecated)] // TODO: rust-windowing/winit#2196
|
|
||||||
let am = ndk_glue::native_activity().asset_manager();
|
|
||||||
let config = Configuration::from_asset_manager(&am);
|
|
||||||
let old_scale_factor = MonitorHandle.scale_factor();
|
|
||||||
*CONFIG.write().unwrap() = config;
|
|
||||||
let scale_factor = MonitorHandle.scale_factor();
|
|
||||||
if (scale_factor - old_scale_factor).abs() < f64::EPSILON {
|
|
||||||
let mut size = MonitorHandle.size();
|
|
||||||
let event = event::Event::WindowEvent {
|
|
||||||
window_id: window::WindowId(WindowId),
|
|
||||||
event: event::WindowEvent::ScaleFactorChanged {
|
|
||||||
new_inner_size: &mut size,
|
|
||||||
scale_factor,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
call_event_handler!(
|
|
||||||
event_handler,
|
|
||||||
self.window_target(),
|
|
||||||
control_flow,
|
|
||||||
event
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::WindowHasFocus => {
|
|
||||||
call_event_handler!(
|
|
||||||
event_handler,
|
|
||||||
self.window_target(),
|
|
||||||
control_flow,
|
|
||||||
event::Event::WindowEvent {
|
|
||||||
window_id: window::WindowId(WindowId),
|
|
||||||
event: event::WindowEvent::Focused(true),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Event::WindowLostFocus => {
|
|
||||||
call_event_handler!(
|
|
||||||
event_handler,
|
|
||||||
self.window_target(),
|
|
||||||
control_flow,
|
|
||||||
event::Event::WindowEvent {
|
|
||||||
window_id: window::WindowId(WindowId),
|
|
||||||
event: event::WindowEvent::Focused(false),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
Some(EventSource::InputQueue) => {
|
|
||||||
if let Some(input_queue) = ndk_glue::input_queue().as_ref() {
|
|
||||||
while let Some(event) = input_queue.get_event().expect("get_event") {
|
|
||||||
if let Some(event) = input_queue.pre_dispatch(event) {
|
|
||||||
let mut handled = true;
|
|
||||||
let window_id = window::WindowId(WindowId);
|
|
||||||
let device_id = event::DeviceId(DeviceId);
|
|
||||||
match &event {
|
|
||||||
InputEvent::MotionEvent(motion_event) => {
|
|
||||||
let phase = match motion_event.action() {
|
|
||||||
MotionAction::Down | MotionAction::PointerDown => {
|
|
||||||
Some(event::TouchPhase::Started)
|
|
||||||
}
|
|
||||||
MotionAction::Up | MotionAction::PointerUp => {
|
|
||||||
Some(event::TouchPhase::Ended)
|
|
||||||
}
|
|
||||||
MotionAction::Move => Some(event::TouchPhase::Moved),
|
|
||||||
MotionAction::Cancel => {
|
|
||||||
Some(event::TouchPhase::Cancelled)
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
handled = false;
|
|
||||||
None // TODO mouse events
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Some(phase) = phase {
|
|
||||||
let pointers: Box<
|
|
||||||
dyn Iterator<Item = ndk::event::Pointer<'_>>,
|
|
||||||
> = match phase {
|
|
||||||
event::TouchPhase::Started
|
|
||||||
| event::TouchPhase::Ended => Box::new(
|
|
||||||
std::iter::once(motion_event.pointer_at_index(
|
|
||||||
motion_event.pointer_index(),
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
event::TouchPhase::Moved
|
|
||||||
| event::TouchPhase::Cancelled => {
|
|
||||||
Box::new(motion_event.pointers())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for pointer in pointers {
|
|
||||||
let location = PhysicalPosition {
|
|
||||||
x: pointer.x() as _,
|
|
||||||
y: pointer.y() as _,
|
|
||||||
};
|
|
||||||
let event = event::Event::WindowEvent {
|
|
||||||
window_id,
|
|
||||||
event: event::WindowEvent::Touch(
|
|
||||||
event::Touch {
|
|
||||||
device_id,
|
|
||||||
phase,
|
|
||||||
location,
|
|
||||||
id: pointer.pointer_id() as u64,
|
|
||||||
force: None,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
};
|
|
||||||
call_event_handler!(
|
|
||||||
event_handler,
|
|
||||||
self.window_target(),
|
|
||||||
control_flow,
|
|
||||||
event
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
InputEvent::KeyEvent(key) => {
|
|
||||||
let state = match key.action() {
|
|
||||||
KeyAction::Down => event::ElementState::Pressed,
|
|
||||||
KeyAction::Up => event::ElementState::Released,
|
|
||||||
_ => event::ElementState::Released,
|
|
||||||
};
|
|
||||||
#[allow(deprecated)]
|
|
||||||
let event = event::Event::WindowEvent {
|
|
||||||
window_id,
|
|
||||||
event: event::WindowEvent::KeyboardInput {
|
|
||||||
device_id,
|
|
||||||
input: event::KeyboardInput {
|
|
||||||
scancode: key.scan_code() as u32,
|
|
||||||
state,
|
|
||||||
virtual_keycode: ndk_keycode_to_virtualkeycode(
|
|
||||||
key.key_code(),
|
|
||||||
),
|
|
||||||
modifiers: event::ModifiersState::default(),
|
|
||||||
},
|
|
||||||
is_synthetic: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
call_event_handler!(
|
|
||||||
event_handler,
|
|
||||||
self.window_target(),
|
|
||||||
control_flow,
|
|
||||||
event
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
input_queue.finish_event(event, handled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(EventSource::User) => {
|
|
||||||
// try_recv only errors when empty (expected) or disconnect. But because Self
|
|
||||||
// contains a Sender it will never disconnect, so no error handling need.
|
|
||||||
while let Ok(event) = self.user_events_receiver.try_recv() {
|
|
||||||
call_event_handler!(
|
|
||||||
event_handler,
|
|
||||||
self.window_target(),
|
|
||||||
control_flow,
|
|
||||||
event::Event::UserEvent(event)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(EventSource::Internal(internal)) => match internal {
|
|
||||||
InternalEvent::RedrawRequested => redraw = true,
|
|
||||||
},
|
|
||||||
None => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
call_event_handler!(
|
|
||||||
event_handler,
|
|
||||||
self.window_target(),
|
|
||||||
control_flow,
|
|
||||||
event::Event::MainEventsCleared
|
|
||||||
);
|
|
||||||
|
|
||||||
if resized && self.running {
|
|
||||||
let size = MonitorHandle.size();
|
|
||||||
let event = event::Event::WindowEvent {
|
|
||||||
window_id: window::WindowId(WindowId),
|
|
||||||
event: event::WindowEvent::Resized(size),
|
|
||||||
};
|
|
||||||
call_event_handler!(event_handler, self.window_target(), control_flow, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
if redraw && self.running {
|
|
||||||
let event = event::Event::RedrawRequested(window::WindowId(WindowId));
|
|
||||||
call_event_handler!(event_handler, self.window_target(), control_flow, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
call_event_handler!(
|
|
||||||
event_handler,
|
|
||||||
self.window_target(),
|
|
||||||
control_flow,
|
|
||||||
event::Event::RedrawEventsCleared
|
|
||||||
);
|
|
||||||
|
|
||||||
match control_flow {
|
|
||||||
ControlFlow::ExitWithCode(code) => {
|
|
||||||
self.first_event = poll(
|
|
||||||
self.looper
|
|
||||||
.poll_once_timeout(Duration::from_millis(0))
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
self.start_cause = event::StartCause::WaitCancelled {
|
|
||||||
start: Instant::now(),
|
|
||||||
requested_resume: None,
|
|
||||||
};
|
|
||||||
break 'event_loop code;
|
|
||||||
}
|
|
||||||
ControlFlow::Poll => {
|
|
||||||
self.first_event = poll(
|
|
||||||
self.looper
|
|
||||||
.poll_all_timeout(Duration::from_millis(0))
|
|
||||||
.unwrap(),
|
|
||||||
);
|
|
||||||
self.start_cause = event::StartCause::Poll;
|
|
||||||
}
|
|
||||||
ControlFlow::Wait => {
|
|
||||||
self.first_event = poll(self.looper.poll_all().unwrap());
|
|
||||||
self.start_cause = event::StartCause::WaitCancelled {
|
|
||||||
start: Instant::now(),
|
|
||||||
requested_resume: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ControlFlow::WaitUntil(instant) => {
|
|
||||||
let start = Instant::now();
|
|
||||||
let duration = if instant <= start {
|
|
||||||
Duration::default()
|
|
||||||
} else {
|
|
||||||
instant - start
|
|
||||||
};
|
|
||||||
self.first_event = poll(self.looper.poll_all_timeout(duration).unwrap());
|
|
||||||
self.start_cause = if self.first_event.is_some() {
|
|
||||||
event::StartCause::WaitCancelled {
|
|
||||||
start,
|
|
||||||
requested_resume: Some(instant),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
event::StartCause::ResumeTimeReached {
|
|
||||||
start,
|
|
||||||
requested_resume: instant,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn window_target(&self) -> &event_loop::EventLoopWindowTarget<T> {
|
|
||||||
&self.window_target
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
|
||||||
EventLoopProxy {
|
|
||||||
user_events_sender: self.user_events_sender.clone(),
|
|
||||||
looper: ForeignLooper::for_thread().expect("called from event loop thread"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventLoopProxy<T: 'static> {
|
|
||||||
user_events_sender: mpsc::Sender<T>,
|
|
||||||
looper: ForeignLooper,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopProxy<T> {
|
|
||||||
pub fn send_event(&self, event: T) -> Result<(), event_loop::EventLoopClosed<T>> {
|
|
||||||
self.user_events_sender
|
|
||||||
.send(event)
|
|
||||||
.map_err(|mpsc::SendError(x)| event_loop::EventLoopClosed(x))?;
|
|
||||||
self.looper.wake();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Clone for EventLoopProxy<T> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
EventLoopProxy {
|
|
||||||
user_events_sender: self.user_events_sender.clone(),
|
|
||||||
looper: self.looper.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventLoopWindowTarget<T: 'static> {
|
|
||||||
_marker: std::marker::PhantomData<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> EventLoopWindowTarget<T> {
|
|
||||||
pub fn primary_monitor(&self) -> Option<monitor::MonitorHandle> {
|
|
||||||
Some(monitor::MonitorHandle {
|
|
||||||
inner: MonitorHandle,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
|
||||||
let mut v = VecDeque::with_capacity(1);
|
|
||||||
v.push_back(MonitorHandle);
|
|
||||||
v
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
|
||||||
RawDisplayHandle::Android(AndroidDisplayHandle::empty())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
|
||||||
pub struct WindowId;
|
|
||||||
|
|
||||||
impl WindowId {
|
|
||||||
pub const fn dummy() -> Self {
|
|
||||||
WindowId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<WindowId> for u64 {
|
|
||||||
fn from(_: WindowId) -> Self {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u64> for WindowId {
|
|
||||||
fn from(_: u64) -> Self {
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
|
||||||
pub struct DeviceId;
|
|
||||||
|
|
||||||
impl DeviceId {
|
|
||||||
pub const fn dummy() -> Self {
|
|
||||||
DeviceId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
|
||||||
pub struct PlatformSpecificWindowBuilderAttributes;
|
|
||||||
|
|
||||||
pub struct Window;
|
|
||||||
|
|
||||||
impl Window {
|
|
||||||
pub(crate) fn new<T: 'static>(
|
|
||||||
_el: &EventLoopWindowTarget<T>,
|
|
||||||
_window_attrs: window::WindowAttributes,
|
|
||||||
_: PlatformSpecificWindowBuilderAttributes,
|
|
||||||
) -> Result<Self, error::OsError> {
|
|
||||||
// FIXME this ignores requested window attributes
|
|
||||||
Ok(Self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn id(&self) -> WindowId {
|
|
||||||
WindowId
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn primary_monitor(&self) -> Option<monitor::MonitorHandle> {
|
|
||||||
Some(monitor::MonitorHandle {
|
|
||||||
inner: MonitorHandle,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
|
||||||
let mut v = VecDeque::with_capacity(1);
|
|
||||||
v.push_back(MonitorHandle);
|
|
||||||
v
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn current_monitor(&self) -> Option<monitor::MonitorHandle> {
|
|
||||||
Some(monitor::MonitorHandle {
|
|
||||||
inner: MonitorHandle,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scale_factor(&self) -> f64 {
|
|
||||||
MonitorHandle.scale_factor()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn request_redraw(&self) {
|
|
||||||
*INTERNAL_EVENT.write().unwrap() = Some(InternalEvent::RedrawRequested);
|
|
||||||
ForeignLooper::for_thread().unwrap().wake();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, error::NotSupportedError> {
|
|
||||||
Err(error::NotSupportedError::new())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, error::NotSupportedError> {
|
|
||||||
Err(error::NotSupportedError::new())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_outer_position(&self, _position: Position) {
|
|
||||||
// no effect
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
|
||||||
self.outer_size()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_inner_size(&self, _size: Size) {
|
|
||||||
warn!("Cannot set window size on Android");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
|
||||||
MonitorHandle.size()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_min_inner_size(&self, _: Option<Size>) {}
|
|
||||||
|
|
||||||
pub fn set_max_inner_size(&self, _: Option<Size>) {}
|
|
||||||
|
|
||||||
pub fn set_title(&self, _title: &str) {}
|
|
||||||
|
|
||||||
pub fn set_visible(&self, _visibility: bool) {}
|
|
||||||
|
|
||||||
pub fn is_visible(&self) -> Option<bool> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_resizable(&self, _resizeable: bool) {}
|
|
||||||
|
|
||||||
pub fn is_resizable(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_minimized(&self, _minimized: bool) {}
|
|
||||||
|
|
||||||
pub fn set_maximized(&self, _maximized: bool) {}
|
|
||||||
|
|
||||||
pub fn is_maximized(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_fullscreen(&self, _monitor: Option<window::Fullscreen>) {
|
|
||||||
warn!("Cannot set fullscreen on Android");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fullscreen(&self) -> Option<window::Fullscreen> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_decorations(&self, _decorations: bool) {}
|
|
||||||
|
|
||||||
pub fn is_decorated(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_always_on_top(&self, _always_on_top: bool) {}
|
|
||||||
|
|
||||||
pub fn set_window_icon(&self, _window_icon: Option<crate::icon::Icon>) {}
|
|
||||||
|
|
||||||
pub fn set_ime_position(&self, _position: Position) {}
|
|
||||||
|
|
||||||
pub fn set_ime_allowed(&self, _allowed: bool) {}
|
|
||||||
|
|
||||||
pub fn focus_window(&self) {}
|
|
||||||
|
|
||||||
pub fn request_user_attention(&self, _request_type: Option<window::UserAttentionType>) {}
|
|
||||||
|
|
||||||
pub fn set_cursor_icon(&self, _: window::CursorIcon) {}
|
|
||||||
|
|
||||||
pub fn set_cursor_position(&self, _: Position) -> Result<(), error::ExternalError> {
|
|
||||||
Err(error::ExternalError::NotSupported(
|
|
||||||
error::NotSupportedError::new(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), error::ExternalError> {
|
|
||||||
Err(error::ExternalError::NotSupported(
|
|
||||||
error::NotSupportedError::new(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_cursor_visible(&self, _: bool) {}
|
|
||||||
|
|
||||||
pub fn drag_window(&self) -> Result<(), error::ExternalError> {
|
|
||||||
Err(error::ExternalError::NotSupported(
|
|
||||||
error::NotSupportedError::new(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), error::ExternalError> {
|
|
||||||
Err(error::ExternalError::NotSupported(
|
|
||||||
error::NotSupportedError::new(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
|
||||||
if let Some(native_window) = ndk_glue::native_window() {
|
|
||||||
native_window.raw_window_handle()
|
|
||||||
} else {
|
|
||||||
panic!("Cannot get the native window, it's null and will always be null before Event::Resumed and after Event::Suspended. Make sure you only call this function between those events.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
|
||||||
RawDisplayHandle::Android(AndroidDisplayHandle::empty())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn config(&self) -> Configuration {
|
|
||||||
CONFIG.read().unwrap().clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn content_rect(&self) -> Rect {
|
|
||||||
ndk_glue::content_rect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
|
||||||
pub struct OsError;
|
|
||||||
|
|
||||||
use std::fmt::{self, Display, Formatter};
|
|
||||||
impl Display for OsError {
|
|
||||||
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
|
|
||||||
write!(fmt, "Android OS Error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
|
||||||
pub struct MonitorHandle;
|
|
||||||
|
|
||||||
impl MonitorHandle {
|
|
||||||
pub fn name(&self) -> Option<String> {
|
|
||||||
Some("Android Device".to_owned())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn size(&self) -> PhysicalSize<u32> {
|
|
||||||
if let Some(native_window) = ndk_glue::native_window().as_ref() {
|
|
||||||
let width = native_window.width() as _;
|
|
||||||
let height = native_window.height() as _;
|
|
||||||
PhysicalSize::new(width, height)
|
|
||||||
} else {
|
|
||||||
PhysicalSize::new(0, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn position(&self) -> PhysicalPosition<i32> {
|
|
||||||
(0, 0).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scale_factor(&self) -> f64 {
|
|
||||||
let config = CONFIG.read().unwrap();
|
|
||||||
config
|
|
||||||
.density()
|
|
||||||
.map(|dpi| dpi as f64 / 160.0)
|
|
||||||
.unwrap_or(1.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
|
||||||
// FIXME no way to get real refresh rate for now.
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn video_modes(&self) -> impl Iterator<Item = monitor::VideoMode> {
|
|
||||||
let size = self.size().into();
|
|
||||||
// FIXME this is not the real refresh rate
|
|
||||||
// (it is guaranteed to support 32 bit color though)
|
|
||||||
std::iter::once(monitor::VideoMode {
|
|
||||||
video_mode: VideoMode {
|
|
||||||
size,
|
|
||||||
bit_depth: 32,
|
|
||||||
refresh_rate_millihertz: 60000,
|
|
||||||
monitor: self.clone(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
|
||||||
pub struct VideoMode {
|
|
||||||
size: (u32, u32),
|
|
||||||
bit_depth: u16,
|
|
||||||
refresh_rate_millihertz: u32,
|
|
||||||
monitor: MonitorHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VideoMode {
|
|
||||||
pub fn size(&self) -> PhysicalSize<u32> {
|
|
||||||
self.size.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bit_depth(&self) -> u16 {
|
|
||||||
self.bit_depth
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn refresh_rate_millihertz(&self) -> u32 {
|
|
||||||
self.refresh_rate_millihertz
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn monitor(&self) -> monitor::MonitorHandle {
|
|
||||||
monitor::MonitorHandle {
|
|
||||||
inner: self.monitor.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,353 +0,0 @@
|
|||||||
use std::{
|
|
||||||
collections::VecDeque,
|
|
||||||
ffi::c_void,
|
|
||||||
fmt::{self, Debug},
|
|
||||||
marker::PhantomData,
|
|
||||||
mem, ptr,
|
|
||||||
sync::mpsc::{self, Receiver, Sender},
|
|
||||||
};
|
|
||||||
|
|
||||||
use raw_window_handle::{RawDisplayHandle, UiKitDisplayHandle};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
dpi::LogicalSize,
|
|
||||||
event::Event,
|
|
||||||
event_loop::{
|
|
||||||
ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootEventLoopWindowTarget,
|
|
||||||
},
|
|
||||||
monitor::MonitorHandle as RootMonitorHandle,
|
|
||||||
platform::ios::Idiom,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::platform_impl::platform::{
|
|
||||||
app_state,
|
|
||||||
ffi::{
|
|
||||||
id, kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes,
|
|
||||||
kCFRunLoopDefaultMode, kCFRunLoopEntry, kCFRunLoopExit, nil, CFIndex, CFRelease,
|
|
||||||
CFRunLoopActivity, CFRunLoopAddObserver, CFRunLoopAddSource, CFRunLoopGetMain,
|
|
||||||
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext,
|
|
||||||
CFRunLoopSourceCreate, CFRunLoopSourceInvalidate, CFRunLoopSourceRef,
|
|
||||||
CFRunLoopSourceSignal, CFRunLoopWakeUp, NSStringRust, UIApplicationMain,
|
|
||||||
UIUserInterfaceIdiom,
|
|
||||||
},
|
|
||||||
monitor, view, MonitorHandle,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum EventWrapper {
|
|
||||||
StaticEvent(Event<'static, Never>),
|
|
||||||
EventProxy(EventProxy),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum EventProxy {
|
|
||||||
DpiChangedProxy {
|
|
||||||
window_id: id,
|
|
||||||
suggested_size: LogicalSize<f64>,
|
|
||||||
scale_factor: f64,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventLoopWindowTarget<T: 'static> {
|
|
||||||
receiver: Receiver<T>,
|
|
||||||
sender_to_clone: Sender<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> EventLoopWindowTarget<T> {
|
|
||||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
|
||||||
// guaranteed to be on main thread
|
|
||||||
unsafe { monitor::uiscreens() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
|
|
||||||
// guaranteed to be on main thread
|
|
||||||
let monitor = unsafe { monitor::main_uiscreen() };
|
|
||||||
|
|
||||||
Some(RootMonitorHandle { inner: monitor })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
|
||||||
RawDisplayHandle::UiKit(UiKitDisplayHandle::empty())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventLoop<T: 'static> {
|
|
||||||
window_target: RootEventLoopWindowTarget<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) struct PlatformSpecificEventLoopAttributes {}
|
|
||||||
|
|
||||||
impl<T: 'static> EventLoop<T> {
|
|
||||||
pub(crate) fn new(_: &PlatformSpecificEventLoopAttributes) -> EventLoop<T> {
|
|
||||||
static mut SINGLETON_INIT: bool = false;
|
|
||||||
unsafe {
|
|
||||||
assert_main_thread!("`EventLoop` can only be created on the main thread on iOS");
|
|
||||||
assert!(
|
|
||||||
!SINGLETON_INIT,
|
|
||||||
"Only one `EventLoop` is supported on iOS. \
|
|
||||||
`EventLoopProxy` might be helpful"
|
|
||||||
);
|
|
||||||
SINGLETON_INIT = true;
|
|
||||||
view::create_delegate_class();
|
|
||||||
}
|
|
||||||
|
|
||||||
let (sender_to_clone, receiver) = mpsc::channel();
|
|
||||||
|
|
||||||
// this line sets up the main run loop before `UIApplicationMain`
|
|
||||||
setup_control_flow_observers();
|
|
||||||
|
|
||||||
EventLoop {
|
|
||||||
window_target: RootEventLoopWindowTarget {
|
|
||||||
p: EventLoopWindowTarget {
|
|
||||||
receiver,
|
|
||||||
sender_to_clone,
|
|
||||||
},
|
|
||||||
_marker: PhantomData,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run<F>(self, event_handler: F) -> !
|
|
||||||
where
|
|
||||||
F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
|
||||||
{
|
|
||||||
unsafe {
|
|
||||||
let application: *mut c_void = msg_send![class!(UIApplication), sharedApplication];
|
|
||||||
assert_eq!(
|
|
||||||
application,
|
|
||||||
ptr::null_mut(),
|
|
||||||
"\
|
|
||||||
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\
|
|
||||||
Note: `EventLoop::run` calls `UIApplicationMain` on iOS"
|
|
||||||
);
|
|
||||||
app_state::will_launch(Box::new(EventLoopHandler {
|
|
||||||
f: event_handler,
|
|
||||||
event_loop: self.window_target,
|
|
||||||
}));
|
|
||||||
|
|
||||||
UIApplicationMain(
|
|
||||||
0,
|
|
||||||
ptr::null(),
|
|
||||||
nil,
|
|
||||||
NSStringRust::alloc(nil).init_str("AppDelegate"),
|
|
||||||
);
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
|
||||||
EventLoopProxy::new(self.window_target.p.sender_to_clone.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn window_target(&self) -> &RootEventLoopWindowTarget<T> {
|
|
||||||
&self.window_target
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EventLoopExtIOS
|
|
||||||
impl<T: 'static> EventLoop<T> {
|
|
||||||
pub fn idiom(&self) -> Idiom {
|
|
||||||
// guaranteed to be on main thread
|
|
||||||
unsafe { self::get_idiom() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventLoopProxy<T> {
|
|
||||||
sender: Sender<T>,
|
|
||||||
source: CFRunLoopSourceRef,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl<T: Send> Send for EventLoopProxy<T> {}
|
|
||||||
|
|
||||||
impl<T> Clone for EventLoopProxy<T> {
|
|
||||||
fn clone(&self) -> EventLoopProxy<T> {
|
|
||||||
EventLoopProxy::new(self.sender.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Drop for EventLoopProxy<T> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
CFRunLoopSourceInvalidate(self.source);
|
|
||||||
CFRelease(self.source as _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopProxy<T> {
|
|
||||||
fn new(sender: Sender<T>) -> EventLoopProxy<T> {
|
|
||||||
unsafe {
|
|
||||||
// just wake up the eventloop
|
|
||||||
extern "C" fn event_loop_proxy_handler(_: *mut c_void) {}
|
|
||||||
|
|
||||||
// adding a Source to the main CFRunLoop lets us wake it up and
|
|
||||||
// process user events through the normal OS EventLoop mechanisms.
|
|
||||||
let rl = CFRunLoopGetMain();
|
|
||||||
// we want all the members of context to be zero/null, except one
|
|
||||||
let mut context: CFRunLoopSourceContext = mem::zeroed();
|
|
||||||
context.perform = Some(event_loop_proxy_handler);
|
|
||||||
let source =
|
|
||||||
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context);
|
|
||||||
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
|
|
||||||
CFRunLoopWakeUp(rl);
|
|
||||||
|
|
||||||
EventLoopProxy { sender, source }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
|
||||||
self.sender
|
|
||||||
.send(event)
|
|
||||||
.map_err(|::std::sync::mpsc::SendError(x)| EventLoopClosed(x))?;
|
|
||||||
unsafe {
|
|
||||||
// let the main thread know there's a new event
|
|
||||||
CFRunLoopSourceSignal(self.source);
|
|
||||||
let rl = CFRunLoopGetMain();
|
|
||||||
CFRunLoopWakeUp(rl);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_control_flow_observers() {
|
|
||||||
unsafe {
|
|
||||||
// begin is queued with the highest priority to ensure it is processed before other observers
|
|
||||||
extern "C" fn control_flow_begin_handler(
|
|
||||||
_: CFRunLoopObserverRef,
|
|
||||||
activity: CFRunLoopActivity,
|
|
||||||
_: *mut c_void,
|
|
||||||
) {
|
|
||||||
unsafe {
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
match activity {
|
|
||||||
kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(),
|
|
||||||
kCFRunLoopEntry => unimplemented!(), // not expected to ever happen
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
|
|
||||||
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end
|
|
||||||
// priority to be 0, in order to send MainEventsCleared before RedrawRequested. This value was
|
|
||||||
// chosen conservatively to guard against apple using different priorities for their redraw
|
|
||||||
// observers in different OS's or on different devices. If it so happens that it's too
|
|
||||||
// conservative, the main symptom would be non-redraw events coming in after `MainEventsCleared`.
|
|
||||||
//
|
|
||||||
// The value of `0x1e8480` was determined by inspecting stack traces and the associated
|
|
||||||
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
|
|
||||||
//
|
|
||||||
// Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4.
|
|
||||||
extern "C" fn control_flow_main_end_handler(
|
|
||||||
_: CFRunLoopObserverRef,
|
|
||||||
activity: CFRunLoopActivity,
|
|
||||||
_: *mut c_void,
|
|
||||||
) {
|
|
||||||
unsafe {
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
match activity {
|
|
||||||
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(),
|
|
||||||
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// end is queued with the lowest priority to ensure it is processed after other observers
|
|
||||||
extern "C" fn control_flow_end_handler(
|
|
||||||
_: CFRunLoopObserverRef,
|
|
||||||
activity: CFRunLoopActivity,
|
|
||||||
_: *mut c_void,
|
|
||||||
) {
|
|
||||||
unsafe {
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
match activity {
|
|
||||||
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(),
|
|
||||||
kCFRunLoopExit => unimplemented!(), // not expected to ever happen
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let main_loop = CFRunLoopGetMain();
|
|
||||||
|
|
||||||
let begin_observer = CFRunLoopObserverCreate(
|
|
||||||
ptr::null_mut(),
|
|
||||||
kCFRunLoopEntry | kCFRunLoopAfterWaiting,
|
|
||||||
1, // repeat = true
|
|
||||||
CFIndex::min_value(),
|
|
||||||
control_flow_begin_handler,
|
|
||||||
ptr::null_mut(),
|
|
||||||
);
|
|
||||||
CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode);
|
|
||||||
|
|
||||||
let main_end_observer = CFRunLoopObserverCreate(
|
|
||||||
ptr::null_mut(),
|
|
||||||
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
|
|
||||||
1, // repeat = true
|
|
||||||
0, // see comment on `control_flow_main_end_handler`
|
|
||||||
control_flow_main_end_handler,
|
|
||||||
ptr::null_mut(),
|
|
||||||
);
|
|
||||||
CFRunLoopAddObserver(main_loop, main_end_observer, kCFRunLoopDefaultMode);
|
|
||||||
|
|
||||||
let end_observer = CFRunLoopObserverCreate(
|
|
||||||
ptr::null_mut(),
|
|
||||||
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
|
|
||||||
1, // repeat = true
|
|
||||||
CFIndex::max_value(),
|
|
||||||
control_flow_end_handler,
|
|
||||||
ptr::null_mut(),
|
|
||||||
);
|
|
||||||
CFRunLoopAddObserver(main_loop, end_observer, kCFRunLoopDefaultMode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Never {}
|
|
||||||
|
|
||||||
pub trait EventHandler: Debug {
|
|
||||||
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow);
|
|
||||||
fn handle_user_events(&mut self, control_flow: &mut ControlFlow);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EventLoopHandler<F, T: 'static> {
|
|
||||||
f: F,
|
|
||||||
event_loop: RootEventLoopWindowTarget<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<F, T: 'static> Debug for EventLoopHandler<F, T> {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.debug_struct("EventLoopHandler")
|
|
||||||
.field("event_loop", &self.event_loop)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<F, T> EventHandler for EventLoopHandler<F, T>
|
|
||||||
where
|
|
||||||
F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
|
||||||
T: 'static,
|
|
||||||
{
|
|
||||||
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow) {
|
|
||||||
(self.f)(
|
|
||||||
event.map_nonuser_event().unwrap(),
|
|
||||||
&self.event_loop,
|
|
||||||
control_flow,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_user_events(&mut self, control_flow: &mut ControlFlow) {
|
|
||||||
for event in self.event_loop.p.receiver.try_iter() {
|
|
||||||
(self.f)(Event::UserEvent(event), &self.event_loop, control_flow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// must be called on main thread
|
|
||||||
pub unsafe fn get_idiom() -> Idiom {
|
|
||||||
let device: id = msg_send![class!(UIDevice), currentDevice];
|
|
||||||
let raw_idiom: UIUserInterfaceIdiom = msg_send![device, userInterfaceIdiom];
|
|
||||||
raw_idiom.into()
|
|
||||||
}
|
|
||||||
@@ -1,392 +0,0 @@
|
|||||||
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
|
|
||||||
|
|
||||||
use std::{convert::TryInto, ffi::CString, ops::BitOr, os::raw::*};
|
|
||||||
|
|
||||||
use objc::{runtime::Object, Encode, Encoding};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
dpi::LogicalSize,
|
|
||||||
platform::ios::{Idiom, ScreenEdge, ValidOrientations},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub type id = *mut Object;
|
|
||||||
pub const nil: id = 0 as id;
|
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "32")]
|
|
||||||
pub type CGFloat = f32;
|
|
||||||
#[cfg(target_pointer_width = "64")]
|
|
||||||
pub type CGFloat = f64;
|
|
||||||
|
|
||||||
pub type NSInteger = isize;
|
|
||||||
pub type NSUInteger = usize;
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct NSOperatingSystemVersion {
|
|
||||||
pub major: NSInteger,
|
|
||||||
pub minor: NSInteger,
|
|
||||||
pub patch: NSInteger,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
pub struct CGPoint {
|
|
||||||
pub x: CGFloat,
|
|
||||||
pub y: CGFloat,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
pub struct CGSize {
|
|
||||||
pub width: CGFloat,
|
|
||||||
pub height: CGFloat,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CGSize {
|
|
||||||
pub fn new(size: LogicalSize<f64>) -> CGSize {
|
|
||||||
CGSize {
|
|
||||||
width: size.width as _,
|
|
||||||
height: size.height as _,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
pub struct CGRect {
|
|
||||||
pub origin: CGPoint,
|
|
||||||
pub size: CGSize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CGRect {
|
|
||||||
pub fn new(origin: CGPoint, size: CGSize) -> CGRect {
|
|
||||||
CGRect { origin, size }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Encode for CGRect {
|
|
||||||
fn encode() -> Encoding {
|
|
||||||
unsafe {
|
|
||||||
if cfg!(target_pointer_width = "32") {
|
|
||||||
Encoding::from_str("{CGRect={CGPoint=ff}{CGSize=ff}}")
|
|
||||||
} else if cfg!(target_pointer_width = "64") {
|
|
||||||
Encoding::from_str("{CGRect={CGPoint=dd}{CGSize=dd}}")
|
|
||||||
} else {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[repr(isize)]
|
|
||||||
pub enum UITouchPhase {
|
|
||||||
Began = 0,
|
|
||||||
Moved,
|
|
||||||
Stationary,
|
|
||||||
Ended,
|
|
||||||
Cancelled,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[repr(isize)]
|
|
||||||
pub enum UIForceTouchCapability {
|
|
||||||
Unknown = 0,
|
|
||||||
Unavailable,
|
|
||||||
Available,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[repr(isize)]
|
|
||||||
pub enum UITouchType {
|
|
||||||
Direct = 0,
|
|
||||||
Indirect,
|
|
||||||
Pencil,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct UIEdgeInsets {
|
|
||||||
pub top: CGFloat,
|
|
||||||
pub left: CGFloat,
|
|
||||||
pub bottom: CGFloat,
|
|
||||||
pub right: CGFloat,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub struct UIUserInterfaceIdiom(NSInteger);
|
|
||||||
|
|
||||||
unsafe impl Encode for UIUserInterfaceIdiom {
|
|
||||||
fn encode() -> Encoding {
|
|
||||||
NSInteger::encode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UIUserInterfaceIdiom {
|
|
||||||
pub const Unspecified: UIUserInterfaceIdiom = UIUserInterfaceIdiom(-1);
|
|
||||||
pub const Phone: UIUserInterfaceIdiom = UIUserInterfaceIdiom(0);
|
|
||||||
pub const Pad: UIUserInterfaceIdiom = UIUserInterfaceIdiom(1);
|
|
||||||
pub const TV: UIUserInterfaceIdiom = UIUserInterfaceIdiom(2);
|
|
||||||
pub const CarPlay: UIUserInterfaceIdiom = UIUserInterfaceIdiom(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Idiom> for UIUserInterfaceIdiom {
|
|
||||||
fn from(idiom: Idiom) -> UIUserInterfaceIdiom {
|
|
||||||
match idiom {
|
|
||||||
Idiom::Unspecified => UIUserInterfaceIdiom::Unspecified,
|
|
||||||
Idiom::Phone => UIUserInterfaceIdiom::Phone,
|
|
||||||
Idiom::Pad => UIUserInterfaceIdiom::Pad,
|
|
||||||
Idiom::TV => UIUserInterfaceIdiom::TV,
|
|
||||||
Idiom::CarPlay => UIUserInterfaceIdiom::CarPlay,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<UIUserInterfaceIdiom> for Idiom {
|
|
||||||
fn from(ui_idiom: UIUserInterfaceIdiom) -> Idiom {
|
|
||||||
match ui_idiom {
|
|
||||||
UIUserInterfaceIdiom::Unspecified => Idiom::Unspecified,
|
|
||||||
UIUserInterfaceIdiom::Phone => Idiom::Phone,
|
|
||||||
UIUserInterfaceIdiom::Pad => Idiom::Pad,
|
|
||||||
UIUserInterfaceIdiom::TV => Idiom::TV,
|
|
||||||
UIUserInterfaceIdiom::CarPlay => Idiom::CarPlay,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub struct UIInterfaceOrientationMask(NSUInteger);
|
|
||||||
|
|
||||||
unsafe impl Encode for UIInterfaceOrientationMask {
|
|
||||||
fn encode() -> Encoding {
|
|
||||||
NSUInteger::encode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UIInterfaceOrientationMask {
|
|
||||||
pub const Portrait: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 1);
|
|
||||||
pub const PortraitUpsideDown: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 2);
|
|
||||||
pub const LandscapeLeft: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 4);
|
|
||||||
pub const LandscapeRight: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 3);
|
|
||||||
pub const Landscape: UIInterfaceOrientationMask =
|
|
||||||
UIInterfaceOrientationMask(Self::LandscapeLeft.0 | Self::LandscapeRight.0);
|
|
||||||
pub const AllButUpsideDown: UIInterfaceOrientationMask =
|
|
||||||
UIInterfaceOrientationMask(Self::Landscape.0 | Self::Portrait.0);
|
|
||||||
pub const All: UIInterfaceOrientationMask =
|
|
||||||
UIInterfaceOrientationMask(Self::AllButUpsideDown.0 | Self::PortraitUpsideDown.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BitOr for UIInterfaceOrientationMask {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn bitor(self, rhs: Self) -> Self {
|
|
||||||
UIInterfaceOrientationMask(self.0 | rhs.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UIInterfaceOrientationMask {
|
|
||||||
pub fn from_valid_orientations_idiom(
|
|
||||||
valid_orientations: ValidOrientations,
|
|
||||||
idiom: Idiom,
|
|
||||||
) -> UIInterfaceOrientationMask {
|
|
||||||
match (valid_orientations, idiom) {
|
|
||||||
(ValidOrientations::LandscapeAndPortrait, Idiom::Phone) => {
|
|
||||||
UIInterfaceOrientationMask::AllButUpsideDown
|
|
||||||
}
|
|
||||||
(ValidOrientations::LandscapeAndPortrait, _) => UIInterfaceOrientationMask::All,
|
|
||||||
(ValidOrientations::Landscape, _) => UIInterfaceOrientationMask::Landscape,
|
|
||||||
(ValidOrientations::Portrait, Idiom::Phone) => UIInterfaceOrientationMask::Portrait,
|
|
||||||
(ValidOrientations::Portrait, _) => {
|
|
||||||
UIInterfaceOrientationMask::Portrait
|
|
||||||
| UIInterfaceOrientationMask::PortraitUpsideDown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub struct UIRectEdge(NSUInteger);
|
|
||||||
|
|
||||||
unsafe impl Encode for UIRectEdge {
|
|
||||||
fn encode() -> Encoding {
|
|
||||||
NSUInteger::encode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ScreenEdge> for UIRectEdge {
|
|
||||||
fn from(screen_edge: ScreenEdge) -> UIRectEdge {
|
|
||||||
assert_eq!(
|
|
||||||
screen_edge.bits() & !ScreenEdge::ALL.bits(),
|
|
||||||
0,
|
|
||||||
"invalid `ScreenEdge`"
|
|
||||||
);
|
|
||||||
UIRectEdge(screen_edge.bits().into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<UIRectEdge> for ScreenEdge {
|
|
||||||
fn from(ui_rect_edge: UIRectEdge) -> ScreenEdge {
|
|
||||||
let bits: u8 = ui_rect_edge.0.try_into().expect("invalid `UIRectEdge`");
|
|
||||||
ScreenEdge::from_bits(bits).expect("invalid `ScreenEdge`")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub struct UIScreenOverscanCompensation(NSInteger);
|
|
||||||
|
|
||||||
unsafe impl Encode for UIScreenOverscanCompensation {
|
|
||||||
fn encode() -> Encoding {
|
|
||||||
NSInteger::encode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
impl UIScreenOverscanCompensation {
|
|
||||||
pub const Scale: UIScreenOverscanCompensation = UIScreenOverscanCompensation(0);
|
|
||||||
pub const InsetBounds: UIScreenOverscanCompensation = UIScreenOverscanCompensation(1);
|
|
||||||
pub const None: UIScreenOverscanCompensation = UIScreenOverscanCompensation(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[link(name = "UIKit", kind = "framework")]
|
|
||||||
#[link(name = "CoreFoundation", kind = "framework")]
|
|
||||||
extern "C" {
|
|
||||||
pub static kCFRunLoopDefaultMode: CFRunLoopMode;
|
|
||||||
pub static kCFRunLoopCommonModes: CFRunLoopMode;
|
|
||||||
|
|
||||||
pub fn UIApplicationMain(
|
|
||||||
argc: c_int,
|
|
||||||
argv: *const c_char,
|
|
||||||
principalClassName: id,
|
|
||||||
delegateClassName: id,
|
|
||||||
) -> c_int;
|
|
||||||
|
|
||||||
pub fn CFRunLoopGetMain() -> CFRunLoopRef;
|
|
||||||
pub fn CFRunLoopWakeUp(rl: CFRunLoopRef);
|
|
||||||
|
|
||||||
pub fn CFRunLoopObserverCreate(
|
|
||||||
allocator: CFAllocatorRef,
|
|
||||||
activities: CFOptionFlags,
|
|
||||||
repeats: Boolean,
|
|
||||||
order: CFIndex,
|
|
||||||
callout: CFRunLoopObserverCallBack,
|
|
||||||
context: *mut CFRunLoopObserverContext,
|
|
||||||
) -> CFRunLoopObserverRef;
|
|
||||||
pub fn CFRunLoopAddObserver(
|
|
||||||
rl: CFRunLoopRef,
|
|
||||||
observer: CFRunLoopObserverRef,
|
|
||||||
mode: CFRunLoopMode,
|
|
||||||
);
|
|
||||||
|
|
||||||
pub fn CFRunLoopTimerCreate(
|
|
||||||
allocator: CFAllocatorRef,
|
|
||||||
fireDate: CFAbsoluteTime,
|
|
||||||
interval: CFTimeInterval,
|
|
||||||
flags: CFOptionFlags,
|
|
||||||
order: CFIndex,
|
|
||||||
callout: CFRunLoopTimerCallBack,
|
|
||||||
context: *mut CFRunLoopTimerContext,
|
|
||||||
) -> CFRunLoopTimerRef;
|
|
||||||
pub fn CFRunLoopAddTimer(rl: CFRunLoopRef, timer: CFRunLoopTimerRef, mode: CFRunLoopMode);
|
|
||||||
pub fn CFRunLoopTimerSetNextFireDate(timer: CFRunLoopTimerRef, fireDate: CFAbsoluteTime);
|
|
||||||
pub fn CFRunLoopTimerInvalidate(time: CFRunLoopTimerRef);
|
|
||||||
|
|
||||||
pub fn CFRunLoopSourceCreate(
|
|
||||||
allocator: CFAllocatorRef,
|
|
||||||
order: CFIndex,
|
|
||||||
context: *mut CFRunLoopSourceContext,
|
|
||||||
) -> CFRunLoopSourceRef;
|
|
||||||
pub fn CFRunLoopAddSource(rl: CFRunLoopRef, source: CFRunLoopSourceRef, mode: CFRunLoopMode);
|
|
||||||
pub fn CFRunLoopSourceInvalidate(source: CFRunLoopSourceRef);
|
|
||||||
pub fn CFRunLoopSourceSignal(source: CFRunLoopSourceRef);
|
|
||||||
|
|
||||||
pub fn CFAbsoluteTimeGetCurrent() -> CFAbsoluteTime;
|
|
||||||
pub fn CFRelease(cftype: *const c_void);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Boolean = u8;
|
|
||||||
pub enum CFAllocator {}
|
|
||||||
pub type CFAllocatorRef = *mut CFAllocator;
|
|
||||||
pub enum CFRunLoop {}
|
|
||||||
pub type CFRunLoopRef = *mut CFRunLoop;
|
|
||||||
pub type CFRunLoopMode = CFStringRef;
|
|
||||||
pub enum CFRunLoopObserver {}
|
|
||||||
pub type CFRunLoopObserverRef = *mut CFRunLoopObserver;
|
|
||||||
pub enum CFRunLoopTimer {}
|
|
||||||
pub type CFRunLoopTimerRef = *mut CFRunLoopTimer;
|
|
||||||
pub enum CFRunLoopSource {}
|
|
||||||
pub type CFRunLoopSourceRef = *mut CFRunLoopSource;
|
|
||||||
pub enum CFString {}
|
|
||||||
pub type CFStringRef = *const CFString;
|
|
||||||
|
|
||||||
pub type CFHashCode = c_ulong;
|
|
||||||
pub type CFIndex = c_long;
|
|
||||||
pub type CFOptionFlags = c_ulong;
|
|
||||||
pub type CFRunLoopActivity = CFOptionFlags;
|
|
||||||
|
|
||||||
pub type CFAbsoluteTime = CFTimeInterval;
|
|
||||||
pub type CFTimeInterval = f64;
|
|
||||||
|
|
||||||
pub const kCFRunLoopEntry: CFRunLoopActivity = 0;
|
|
||||||
pub const kCFRunLoopBeforeWaiting: CFRunLoopActivity = 1 << 5;
|
|
||||||
pub const kCFRunLoopAfterWaiting: CFRunLoopActivity = 1 << 6;
|
|
||||||
pub const kCFRunLoopExit: CFRunLoopActivity = 1 << 7;
|
|
||||||
|
|
||||||
pub type CFRunLoopObserverCallBack =
|
|
||||||
extern "C" fn(observer: CFRunLoopObserverRef, activity: CFRunLoopActivity, info: *mut c_void);
|
|
||||||
pub type CFRunLoopTimerCallBack = extern "C" fn(timer: CFRunLoopTimerRef, info: *mut c_void);
|
|
||||||
|
|
||||||
pub enum CFRunLoopObserverContext {}
|
|
||||||
pub enum CFRunLoopTimerContext {}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct CFRunLoopSourceContext {
|
|
||||||
pub version: CFIndex,
|
|
||||||
pub info: *mut c_void,
|
|
||||||
pub retain: Option<extern "C" fn(*const c_void) -> *const c_void>,
|
|
||||||
pub release: Option<extern "C" fn(*const c_void)>,
|
|
||||||
pub copyDescription: Option<extern "C" fn(*const c_void) -> CFStringRef>,
|
|
||||||
pub equal: Option<extern "C" fn(*const c_void, *const c_void) -> Boolean>,
|
|
||||||
pub hash: Option<extern "C" fn(*const c_void) -> CFHashCode>,
|
|
||||||
pub schedule: Option<extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode)>,
|
|
||||||
pub cancel: Option<extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode)>,
|
|
||||||
pub perform: Option<extern "C" fn(*mut c_void)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is named NSStringRust rather than NSString because the "Debug View Heirarchy" feature of
|
|
||||||
// Xcode requires a non-ambiguous reference to NSString for unclear reasons. This makes Xcode happy
|
|
||||||
// so please test if you change the name back to NSString.
|
|
||||||
pub trait NSStringRust: Sized {
|
|
||||||
unsafe fn alloc(_: Self) -> id {
|
|
||||||
msg_send![class!(NSString), alloc]
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id;
|
|
||||||
unsafe fn stringByAppendingString_(self, other: id) -> id;
|
|
||||||
unsafe fn init_str(self, string: &str) -> Self;
|
|
||||||
unsafe fn UTF8String(self) -> *const c_char;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NSStringRust for id {
|
|
||||||
unsafe fn initWithUTF8String_(self, c_string: *const c_char) -> id {
|
|
||||||
msg_send![self, initWithUTF8String: c_string as id]
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn stringByAppendingString_(self, other: id) -> id {
|
|
||||||
msg_send![self, stringByAppendingString: other]
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn init_str(self, string: &str) -> id {
|
|
||||||
let cstring = CString::new(string).unwrap();
|
|
||||||
self.initWithUTF8String_(cstring.as_ptr())
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn UTF8String(self) -> *const c_char {
|
|
||||||
msg_send![self, UTF8String]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,313 +0,0 @@
|
|||||||
use std::{
|
|
||||||
collections::{BTreeSet, VecDeque},
|
|
||||||
fmt,
|
|
||||||
ops::{Deref, DerefMut},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
dpi::{PhysicalPosition, PhysicalSize},
|
|
||||||
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
|
||||||
platform_impl::platform::{
|
|
||||||
app_state,
|
|
||||||
ffi::{id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub struct VideoMode {
|
|
||||||
pub(crate) size: (u32, u32),
|
|
||||||
pub(crate) bit_depth: u16,
|
|
||||||
pub(crate) refresh_rate_millihertz: u32,
|
|
||||||
pub(crate) screen_mode: NativeDisplayMode,
|
|
||||||
pub(crate) monitor: MonitorHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
|
||||||
pub struct NativeDisplayMode(pub id);
|
|
||||||
|
|
||||||
unsafe impl Send for NativeDisplayMode {}
|
|
||||||
|
|
||||||
impl Drop for NativeDisplayMode {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
let _: () = msg_send![self.0, release];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for NativeDisplayMode {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
unsafe {
|
|
||||||
let _: id = msg_send![self.0, retain];
|
|
||||||
}
|
|
||||||
NativeDisplayMode(self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for VideoMode {
|
|
||||||
fn clone(&self) -> VideoMode {
|
|
||||||
VideoMode {
|
|
||||||
size: self.size,
|
|
||||||
bit_depth: self.bit_depth,
|
|
||||||
refresh_rate_millihertz: self.refresh_rate_millihertz,
|
|
||||||
screen_mode: self.screen_mode.clone(),
|
|
||||||
monitor: self.monitor.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VideoMode {
|
|
||||||
unsafe fn retained_new(uiscreen: id, screen_mode: id) -> VideoMode {
|
|
||||||
assert_main_thread!("`VideoMode` can only be created on the main thread on iOS");
|
|
||||||
let refresh_rate_millihertz = refresh_rate_millihertz(uiscreen);
|
|
||||||
let size: CGSize = msg_send![screen_mode, size];
|
|
||||||
let screen_mode: id = msg_send![screen_mode, retain];
|
|
||||||
let screen_mode = NativeDisplayMode(screen_mode);
|
|
||||||
VideoMode {
|
|
||||||
size: (size.width as u32, size.height as u32),
|
|
||||||
bit_depth: 32,
|
|
||||||
refresh_rate_millihertz,
|
|
||||||
screen_mode,
|
|
||||||
monitor: MonitorHandle::retained_new(uiscreen),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn size(&self) -> PhysicalSize<u32> {
|
|
||||||
self.size.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bit_depth(&self) -> u16 {
|
|
||||||
self.bit_depth
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn refresh_rate_millihertz(&self) -> u32 {
|
|
||||||
self.refresh_rate_millihertz
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn monitor(&self) -> RootMonitorHandle {
|
|
||||||
RootMonitorHandle {
|
|
||||||
inner: self.monitor.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
||||||
pub struct Inner {
|
|
||||||
uiscreen: id,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Inner {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
let _: () = msg_send![self.uiscreen, release];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
|
||||||
pub struct MonitorHandle {
|
|
||||||
inner: Inner,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for MonitorHandle {
|
|
||||||
type Target = Inner;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Inner {
|
|
||||||
unsafe {
|
|
||||||
assert_main_thread!(
|
|
||||||
"`MonitorHandle` methods can only be run on the main thread on iOS"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
&self.inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for MonitorHandle {
|
|
||||||
fn deref_mut(&mut self) -> &mut Inner {
|
|
||||||
unsafe {
|
|
||||||
assert_main_thread!(
|
|
||||||
"`MonitorHandle` methods can only be run on the main thread on iOS"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
&mut self.inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for MonitorHandle {}
|
|
||||||
unsafe impl Sync for MonitorHandle {}
|
|
||||||
|
|
||||||
impl Clone for MonitorHandle {
|
|
||||||
fn clone(&self) -> MonitorHandle {
|
|
||||||
MonitorHandle::retained_new(self.uiscreen)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for MonitorHandle {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
assert_main_thread!("`MonitorHandle` can only be dropped on the main thread on iOS");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for MonitorHandle {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
// TODO: Do this using the proper fmt API
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
struct MonitorHandle {
|
|
||||||
name: Option<String>,
|
|
||||||
size: PhysicalSize<u32>,
|
|
||||||
position: PhysicalPosition<i32>,
|
|
||||||
scale_factor: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
let monitor_id_proxy = MonitorHandle {
|
|
||||||
name: self.name(),
|
|
||||||
size: self.size(),
|
|
||||||
position: self.position(),
|
|
||||||
scale_factor: self.scale_factor(),
|
|
||||||
};
|
|
||||||
|
|
||||||
monitor_id_proxy.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MonitorHandle {
|
|
||||||
pub fn retained_new(uiscreen: id) -> MonitorHandle {
|
|
||||||
unsafe {
|
|
||||||
assert_main_thread!("`MonitorHandle` can only be cloned on the main thread on iOS");
|
|
||||||
let _: () = msg_send![uiscreen, retain];
|
|
||||||
}
|
|
||||||
MonitorHandle {
|
|
||||||
inner: Inner { uiscreen },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Inner {
|
|
||||||
pub fn name(&self) -> Option<String> {
|
|
||||||
unsafe {
|
|
||||||
let main = main_uiscreen();
|
|
||||||
if self.uiscreen == main.uiscreen {
|
|
||||||
Some("Primary".to_string())
|
|
||||||
} else if self.uiscreen == mirrored_uiscreen(&main).uiscreen {
|
|
||||||
Some("Mirrored".to_string())
|
|
||||||
} else {
|
|
||||||
uiscreens()
|
|
||||||
.iter()
|
|
||||||
.position(|rhs| rhs.uiscreen == self.uiscreen)
|
|
||||||
.map(|idx| idx.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn size(&self) -> PhysicalSize<u32> {
|
|
||||||
unsafe {
|
|
||||||
let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds];
|
|
||||||
PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn position(&self) -> PhysicalPosition<i32> {
|
|
||||||
unsafe {
|
|
||||||
let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds];
|
|
||||||
(bounds.origin.x as f64, bounds.origin.y as f64).into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scale_factor(&self) -> f64 {
|
|
||||||
unsafe {
|
|
||||||
let scale: CGFloat = msg_send![self.ui_screen(), nativeScale];
|
|
||||||
scale as f64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
|
||||||
Some(refresh_rate_millihertz(self.uiscreen))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
|
|
||||||
let mut modes = BTreeSet::new();
|
|
||||||
unsafe {
|
|
||||||
let available_modes: id = msg_send![self.uiscreen, availableModes];
|
|
||||||
let available_mode_count: NSUInteger = msg_send![available_modes, count];
|
|
||||||
|
|
||||||
for i in 0..available_mode_count {
|
|
||||||
let mode: id = msg_send![available_modes, objectAtIndex: i];
|
|
||||||
modes.insert(RootVideoMode {
|
|
||||||
video_mode: VideoMode::retained_new(self.uiscreen, mode),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
modes.into_iter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn refresh_rate_millihertz(uiscreen: id) -> u32 {
|
|
||||||
let refresh_rate_millihertz: NSInteger = unsafe {
|
|
||||||
let os_capabilities = app_state::os_capabilities();
|
|
||||||
if os_capabilities.maximum_frames_per_second {
|
|
||||||
msg_send![uiscreen, maximumFramesPerSecond]
|
|
||||||
} else {
|
|
||||||
// https://developer.apple.com/library/archive/technotes/tn2460/_index.html
|
|
||||||
// https://en.wikipedia.org/wiki/IPad_Pro#Model_comparison
|
|
||||||
//
|
|
||||||
// All iOS devices support 60 fps, and on devices where `maximumFramesPerSecond` is not
|
|
||||||
// supported, they are all guaranteed to have 60hz refresh rates. This does not
|
|
||||||
// correctly handle external displays. ProMotion displays support 120fps, but they were
|
|
||||||
// introduced at the same time as the `maximumFramesPerSecond` API.
|
|
||||||
//
|
|
||||||
// FIXME: earlier OSs could calculate the refresh rate using
|
|
||||||
// `-[CADisplayLink duration]`.
|
|
||||||
os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps");
|
|
||||||
60
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
refresh_rate_millihertz as u32 * 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
// MonitorHandleExtIOS
|
|
||||||
impl Inner {
|
|
||||||
pub fn ui_screen(&self) -> id {
|
|
||||||
self.uiscreen
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn preferred_video_mode(&self) -> RootVideoMode {
|
|
||||||
unsafe {
|
|
||||||
let mode: id = msg_send![self.uiscreen, preferredMode];
|
|
||||||
RootVideoMode {
|
|
||||||
video_mode: VideoMode::retained_new(self.uiscreen, mode),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// requires being run on main thread
|
|
||||||
pub unsafe fn main_uiscreen() -> MonitorHandle {
|
|
||||||
let uiscreen: id = msg_send![class!(UIScreen), mainScreen];
|
|
||||||
MonitorHandle::retained_new(uiscreen)
|
|
||||||
}
|
|
||||||
|
|
||||||
// requires being run on main thread
|
|
||||||
unsafe fn mirrored_uiscreen(monitor: &MonitorHandle) -> MonitorHandle {
|
|
||||||
let uiscreen: id = msg_send![monitor.uiscreen, mirroredScreen];
|
|
||||||
MonitorHandle::retained_new(uiscreen)
|
|
||||||
}
|
|
||||||
|
|
||||||
// requires being run on main thread
|
|
||||||
pub unsafe fn uiscreens() -> VecDeque<MonitorHandle> {
|
|
||||||
let screens: id = msg_send![class!(UIScreen), screens];
|
|
||||||
let count: NSUInteger = msg_send![screens, count];
|
|
||||||
let mut result = VecDeque::with_capacity(count as _);
|
|
||||||
let screens_enum: id = msg_send![screens, objectEnumerator];
|
|
||||||
loop {
|
|
||||||
let screen: id = msg_send![screens_enum, nextObject];
|
|
||||||
if screen == nil {
|
|
||||||
break result;
|
|
||||||
}
|
|
||||||
result.push_back(MonitorHandle::retained_new(screen));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,624 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use objc::{
|
|
||||||
declare::ClassDecl,
|
|
||||||
runtime::{Class, Object, Sel, BOOL, NO, YES},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
dpi::PhysicalPosition,
|
|
||||||
event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent},
|
|
||||||
platform::ios::MonitorHandleExtIOS,
|
|
||||||
platform_impl::platform::{
|
|
||||||
app_state::{self, OSCapabilities},
|
|
||||||
event_loop::{self, EventProxy, EventWrapper},
|
|
||||||
ffi::{
|
|
||||||
id, nil, CGFloat, CGPoint, CGRect, UIForceTouchCapability, UIInterfaceOrientationMask,
|
|
||||||
UIRectEdge, UITouchPhase, UITouchType,
|
|
||||||
},
|
|
||||||
window::PlatformSpecificWindowBuilderAttributes,
|
|
||||||
DeviceId,
|
|
||||||
},
|
|
||||||
window::{Fullscreen, WindowAttributes, WindowId as RootWindowId},
|
|
||||||
};
|
|
||||||
|
|
||||||
macro_rules! add_property {
|
|
||||||
(
|
|
||||||
$decl:ident,
|
|
||||||
$name:ident: $t:ty,
|
|
||||||
$setter_name:ident: |$object:ident| $after_set:expr,
|
|
||||||
$getter_name:ident,
|
|
||||||
) => {
|
|
||||||
add_property!(
|
|
||||||
$decl,
|
|
||||||
$name: $t,
|
|
||||||
$setter_name: true, |_, _|{}; |$object| $after_set,
|
|
||||||
$getter_name,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
(
|
|
||||||
$decl:ident,
|
|
||||||
$name:ident: $t:ty,
|
|
||||||
$setter_name:ident: $capability:expr, $err:expr; |$object:ident| $after_set:expr,
|
|
||||||
$getter_name:ident,
|
|
||||||
) => {
|
|
||||||
{
|
|
||||||
const VAR_NAME: &'static str = concat!("_", stringify!($name));
|
|
||||||
$decl.add_ivar::<$t>(VAR_NAME);
|
|
||||||
let setter = if $capability {
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
extern "C" fn $setter_name($object: &mut Object, _: Sel, value: $t) {
|
|
||||||
unsafe {
|
|
||||||
$object.set_ivar::<$t>(VAR_NAME, value);
|
|
||||||
}
|
|
||||||
$after_set
|
|
||||||
}
|
|
||||||
$setter_name
|
|
||||||
} else {
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
extern "C" fn $setter_name($object: &mut Object, _: Sel, value: $t) {
|
|
||||||
unsafe {
|
|
||||||
$object.set_ivar::<$t>(VAR_NAME, value);
|
|
||||||
}
|
|
||||||
$err(&app_state::os_capabilities(), "ignoring")
|
|
||||||
}
|
|
||||||
$setter_name
|
|
||||||
};
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
extern "C" fn $getter_name($object: &Object, _: Sel) -> $t {
|
|
||||||
unsafe { *$object.get_ivar::<$t>(VAR_NAME) }
|
|
||||||
}
|
|
||||||
$decl.add_method(
|
|
||||||
sel!($setter_name:),
|
|
||||||
setter as extern "C" fn(&mut Object, Sel, $t),
|
|
||||||
);
|
|
||||||
$decl.add_method(
|
|
||||||
sel!($getter_name),
|
|
||||||
$getter_name as extern "C" fn(&Object, Sel) -> $t,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// requires main thread
|
|
||||||
unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
|
|
||||||
static mut CLASSES: Option<HashMap<*const Class, &'static Class>> = None;
|
|
||||||
static mut ID: usize = 0;
|
|
||||||
|
|
||||||
if CLASSES.is_none() {
|
|
||||||
CLASSES = Some(HashMap::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
let classes = CLASSES.as_mut().unwrap();
|
|
||||||
|
|
||||||
classes.entry(root_view_class).or_insert_with(move || {
|
|
||||||
let uiview_class = class!(UIView);
|
|
||||||
let is_uiview: BOOL = msg_send![root_view_class, isSubclassOfClass: uiview_class];
|
|
||||||
assert_eq!(
|
|
||||||
is_uiview, YES,
|
|
||||||
"`root_view_class` must inherit from `UIView`"
|
|
||||||
);
|
|
||||||
|
|
||||||
extern "C" fn draw_rect(object: &Object, _: Sel, rect: CGRect) {
|
|
||||||
unsafe {
|
|
||||||
let window: id = msg_send![object, window];
|
|
||||||
assert!(!window.is_null());
|
|
||||||
app_state::handle_nonuser_events(
|
|
||||||
std::iter::once(EventWrapper::StaticEvent(Event::RedrawRequested(
|
|
||||||
RootWindowId(window.into()),
|
|
||||||
)))
|
|
||||||
.chain(std::iter::once(EventWrapper::StaticEvent(
|
|
||||||
Event::RedrawEventsCleared,
|
|
||||||
))),
|
|
||||||
);
|
|
||||||
let superclass: &'static Class = msg_send![object, superclass];
|
|
||||||
let _: () = msg_send![super(object, superclass), drawRect: rect];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn layout_subviews(object: &Object, _: Sel) {
|
|
||||||
unsafe {
|
|
||||||
let superclass: &'static Class = msg_send![object, superclass];
|
|
||||||
let _: () = msg_send![super(object, superclass), layoutSubviews];
|
|
||||||
|
|
||||||
let window: id = msg_send![object, window];
|
|
||||||
assert!(!window.is_null());
|
|
||||||
let window_bounds: CGRect = msg_send![window, bounds];
|
|
||||||
let screen: id = msg_send![window, screen];
|
|
||||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
|
||||||
let screen_frame: CGRect =
|
|
||||||
msg_send![object, convertRect:window_bounds toCoordinateSpace:screen_space];
|
|
||||||
let scale_factor: CGFloat = msg_send![screen, scale];
|
|
||||||
let size = crate::dpi::LogicalSize {
|
|
||||||
width: screen_frame.size.width as f64,
|
|
||||||
height: screen_frame.size.height as f64,
|
|
||||||
}
|
|
||||||
.to_physical(scale_factor as f64);
|
|
||||||
|
|
||||||
// If the app is started in landscape, the view frame and window bounds can be mismatched.
|
|
||||||
// The view frame will be in portrait and the window bounds in landscape. So apply the
|
|
||||||
// window bounds to the view frame to make it consistent.
|
|
||||||
let view_frame: CGRect = msg_send![object, frame];
|
|
||||||
if view_frame != window_bounds {
|
|
||||||
let _: () = msg_send![object, setFrame: window_bounds];
|
|
||||||
}
|
|
||||||
|
|
||||||
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
|
|
||||||
window_id: RootWindowId(window.into()),
|
|
||||||
event: WindowEvent::Resized(size),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn set_content_scale_factor(
|
|
||||||
object: &mut Object,
|
|
||||||
_: Sel,
|
|
||||||
untrusted_scale_factor: CGFloat,
|
|
||||||
) {
|
|
||||||
unsafe {
|
|
||||||
let superclass: &'static Class = msg_send![object, superclass];
|
|
||||||
let _: () = msg_send![
|
|
||||||
super(object, superclass),
|
|
||||||
setContentScaleFactor: untrusted_scale_factor
|
|
||||||
];
|
|
||||||
|
|
||||||
let window: id = msg_send![object, window];
|
|
||||||
// `window` is null when `setContentScaleFactor` is invoked prior to `[UIWindow
|
|
||||||
// makeKeyAndVisible]` at window creation time (either manually or internally by
|
|
||||||
// UIKit when the `UIView` is first created), in which case we send no events here
|
|
||||||
if window.is_null() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// `setContentScaleFactor` may be called with a value of 0, which means "reset the
|
|
||||||
// content scale factor to a device-specific default value", so we can't use the
|
|
||||||
// parameter here. We can query the actual factor using the getter
|
|
||||||
let scale_factor: CGFloat = msg_send![object, contentScaleFactor];
|
|
||||||
assert!(
|
|
||||||
!scale_factor.is_nan()
|
|
||||||
&& scale_factor.is_finite()
|
|
||||||
&& scale_factor.is_sign_positive()
|
|
||||||
&& scale_factor > 0.0,
|
|
||||||
"invalid scale_factor set on UIView",
|
|
||||||
);
|
|
||||||
let scale_factor = scale_factor as f64;
|
|
||||||
let bounds: CGRect = msg_send![object, bounds];
|
|
||||||
let screen: id = msg_send![window, screen];
|
|
||||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
|
||||||
let screen_frame: CGRect =
|
|
||||||
msg_send![object, convertRect:bounds toCoordinateSpace:screen_space];
|
|
||||||
let size = crate::dpi::LogicalSize {
|
|
||||||
width: screen_frame.size.width as _,
|
|
||||||
height: screen_frame.size.height as _,
|
|
||||||
};
|
|
||||||
app_state::handle_nonuser_events(
|
|
||||||
std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
|
|
||||||
window_id: window,
|
|
||||||
scale_factor,
|
|
||||||
suggested_size: size,
|
|
||||||
}))
|
|
||||||
.chain(std::iter::once(EventWrapper::StaticEvent(
|
|
||||||
Event::WindowEvent {
|
|
||||||
window_id: RootWindowId(window.into()),
|
|
||||||
event: WindowEvent::Resized(size.to_physical(scale_factor)),
|
|
||||||
},
|
|
||||||
))),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn handle_touches(object: &Object, _: Sel, touches: id, _: id) {
|
|
||||||
unsafe {
|
|
||||||
let window: id = msg_send![object, window];
|
|
||||||
assert!(!window.is_null());
|
|
||||||
let uiscreen: id = msg_send![window, screen];
|
|
||||||
let touches_enum: id = msg_send![touches, objectEnumerator];
|
|
||||||
let mut touch_events = Vec::new();
|
|
||||||
let os_supports_force = app_state::os_capabilities().force_touch;
|
|
||||||
loop {
|
|
||||||
let touch: id = msg_send![touches_enum, nextObject];
|
|
||||||
if touch == nil {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let logical_location: CGPoint = msg_send![touch, locationInView: nil];
|
|
||||||
let touch_type: UITouchType = msg_send![touch, type];
|
|
||||||
let force = if os_supports_force {
|
|
||||||
let trait_collection: id = msg_send![object, traitCollection];
|
|
||||||
let touch_capability: UIForceTouchCapability =
|
|
||||||
msg_send![trait_collection, forceTouchCapability];
|
|
||||||
// Both the OS _and_ the device need to be checked for force touch support.
|
|
||||||
if touch_capability == UIForceTouchCapability::Available {
|
|
||||||
let force: CGFloat = msg_send![touch, force];
|
|
||||||
let max_possible_force: CGFloat =
|
|
||||||
msg_send![touch, maximumPossibleForce];
|
|
||||||
let altitude_angle: Option<f64> = if touch_type == UITouchType::Pencil {
|
|
||||||
let angle: CGFloat = msg_send![touch, altitudeAngle];
|
|
||||||
Some(angle as _)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
Some(Force::Calibrated {
|
|
||||||
force: force as _,
|
|
||||||
max_possible_force: max_possible_force as _,
|
|
||||||
altitude_angle,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
let touch_id = touch as u64;
|
|
||||||
let phase: UITouchPhase = msg_send![touch, phase];
|
|
||||||
let phase = match phase {
|
|
||||||
UITouchPhase::Began => TouchPhase::Started,
|
|
||||||
UITouchPhase::Moved => TouchPhase::Moved,
|
|
||||||
// 2 is UITouchPhase::Stationary and is not expected here
|
|
||||||
UITouchPhase::Ended => TouchPhase::Ended,
|
|
||||||
UITouchPhase::Cancelled => TouchPhase::Cancelled,
|
|
||||||
_ => panic!("unexpected touch phase: {:?}", phase as i32),
|
|
||||||
};
|
|
||||||
|
|
||||||
let physical_location = {
|
|
||||||
let scale_factor: CGFloat = msg_send![object, contentScaleFactor];
|
|
||||||
PhysicalPosition::from_logical::<(f64, f64), f64>(
|
|
||||||
(logical_location.x as _, logical_location.y as _),
|
|
||||||
scale_factor,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
|
|
||||||
window_id: RootWindowId(window.into()),
|
|
||||||
event: WindowEvent::Touch(Touch {
|
|
||||||
device_id: RootDeviceId(DeviceId { uiscreen }),
|
|
||||||
id: touch_id,
|
|
||||||
location: physical_location,
|
|
||||||
force,
|
|
||||||
phase,
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
app_state::handle_nonuser_events(touch_events);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut decl = ClassDecl::new(&format!("WinitUIView{}", ID), root_view_class)
|
|
||||||
.expect("Failed to declare class `WinitUIView`");
|
|
||||||
ID += 1;
|
|
||||||
decl.add_method(
|
|
||||||
sel!(drawRect:),
|
|
||||||
draw_rect as extern "C" fn(&Object, Sel, CGRect),
|
|
||||||
);
|
|
||||||
decl.add_method(
|
|
||||||
sel!(layoutSubviews),
|
|
||||||
layout_subviews as extern "C" fn(&Object, Sel),
|
|
||||||
);
|
|
||||||
decl.add_method(
|
|
||||||
sel!(setContentScaleFactor:),
|
|
||||||
set_content_scale_factor as extern "C" fn(&mut Object, Sel, CGFloat),
|
|
||||||
);
|
|
||||||
|
|
||||||
decl.add_method(
|
|
||||||
sel!(touchesBegan:withEvent:),
|
|
||||||
handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id),
|
|
||||||
);
|
|
||||||
decl.add_method(
|
|
||||||
sel!(touchesMoved:withEvent:),
|
|
||||||
handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id),
|
|
||||||
);
|
|
||||||
decl.add_method(
|
|
||||||
sel!(touchesEnded:withEvent:),
|
|
||||||
handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id),
|
|
||||||
);
|
|
||||||
decl.add_method(
|
|
||||||
sel!(touchesCancelled:withEvent:),
|
|
||||||
handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id),
|
|
||||||
);
|
|
||||||
|
|
||||||
decl.register()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// requires main thread
|
|
||||||
unsafe fn get_view_controller_class() -> &'static Class {
|
|
||||||
static mut CLASS: Option<&'static Class> = None;
|
|
||||||
if CLASS.is_none() {
|
|
||||||
let os_capabilities = app_state::os_capabilities();
|
|
||||||
|
|
||||||
let uiviewcontroller_class = class!(UIViewController);
|
|
||||||
|
|
||||||
extern "C" fn should_autorotate(_: &Object, _: Sel) -> BOOL {
|
|
||||||
YES
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut decl = ClassDecl::new("WinitUIViewController", uiviewcontroller_class)
|
|
||||||
.expect("Failed to declare class `WinitUIViewController`");
|
|
||||||
decl.add_method(
|
|
||||||
sel!(shouldAutorotate),
|
|
||||||
should_autorotate as extern "C" fn(&Object, Sel) -> BOOL,
|
|
||||||
);
|
|
||||||
add_property! {
|
|
||||||
decl,
|
|
||||||
prefers_status_bar_hidden: BOOL,
|
|
||||||
setPrefersStatusBarHidden: |object| {
|
|
||||||
unsafe {
|
|
||||||
let _: () = msg_send![object, setNeedsStatusBarAppearanceUpdate];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
prefersStatusBarHidden,
|
|
||||||
}
|
|
||||||
add_property! {
|
|
||||||
decl,
|
|
||||||
prefers_home_indicator_auto_hidden: BOOL,
|
|
||||||
setPrefersHomeIndicatorAutoHidden:
|
|
||||||
os_capabilities.home_indicator_hidden,
|
|
||||||
OSCapabilities::home_indicator_hidden_err_msg;
|
|
||||||
|object| {
|
|
||||||
unsafe {
|
|
||||||
let _: () = msg_send![object, setNeedsUpdateOfHomeIndicatorAutoHidden];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
prefersHomeIndicatorAutoHidden,
|
|
||||||
}
|
|
||||||
add_property! {
|
|
||||||
decl,
|
|
||||||
supported_orientations: UIInterfaceOrientationMask,
|
|
||||||
setSupportedInterfaceOrientations: |object| {
|
|
||||||
unsafe {
|
|
||||||
let _: () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
supportedInterfaceOrientations,
|
|
||||||
}
|
|
||||||
add_property! {
|
|
||||||
decl,
|
|
||||||
preferred_screen_edges_deferring_system_gestures: UIRectEdge,
|
|
||||||
setPreferredScreenEdgesDeferringSystemGestures:
|
|
||||||
os_capabilities.defer_system_gestures,
|
|
||||||
OSCapabilities::defer_system_gestures_err_msg;
|
|
||||||
|object| {
|
|
||||||
unsafe {
|
|
||||||
let _: () = msg_send![object, setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
preferredScreenEdgesDeferringSystemGestures,
|
|
||||||
}
|
|
||||||
CLASS = Some(decl.register());
|
|
||||||
}
|
|
||||||
CLASS.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
// requires main thread
|
|
||||||
unsafe fn get_window_class() -> &'static Class {
|
|
||||||
static mut CLASS: Option<&'static Class> = None;
|
|
||||||
if CLASS.is_none() {
|
|
||||||
let uiwindow_class = class!(UIWindow);
|
|
||||||
|
|
||||||
extern "C" fn become_key_window(object: &Object, _: Sel) {
|
|
||||||
unsafe {
|
|
||||||
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
|
|
||||||
window_id: RootWindowId(object.into()),
|
|
||||||
event: WindowEvent::Focused(true),
|
|
||||||
}));
|
|
||||||
let _: () = msg_send![super(object, class!(UIWindow)), becomeKeyWindow];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn resign_key_window(object: &Object, _: Sel) {
|
|
||||||
unsafe {
|
|
||||||
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
|
|
||||||
window_id: RootWindowId(object.into()),
|
|
||||||
event: WindowEvent::Focused(false),
|
|
||||||
}));
|
|
||||||
let _: () = msg_send![super(object, class!(UIWindow)), resignKeyWindow];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut decl = ClassDecl::new("WinitUIWindow", uiwindow_class)
|
|
||||||
.expect("Failed to declare class `WinitUIWindow`");
|
|
||||||
decl.add_method(
|
|
||||||
sel!(becomeKeyWindow),
|
|
||||||
become_key_window as extern "C" fn(&Object, Sel),
|
|
||||||
);
|
|
||||||
decl.add_method(
|
|
||||||
sel!(resignKeyWindow),
|
|
||||||
resign_key_window as extern "C" fn(&Object, Sel),
|
|
||||||
);
|
|
||||||
|
|
||||||
CLASS = Some(decl.register());
|
|
||||||
}
|
|
||||||
CLASS.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
// requires main thread
|
|
||||||
pub(crate) unsafe fn create_view(
|
|
||||||
_window_attributes: &WindowAttributes,
|
|
||||||
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
|
||||||
frame: CGRect,
|
|
||||||
) -> id {
|
|
||||||
let class = get_view_class(platform_attributes.root_view_class);
|
|
||||||
|
|
||||||
let view: id = msg_send![class, alloc];
|
|
||||||
assert!(!view.is_null(), "Failed to create `UIView` instance");
|
|
||||||
let view: id = msg_send![view, initWithFrame: frame];
|
|
||||||
assert!(!view.is_null(), "Failed to initialize `UIView` instance");
|
|
||||||
let _: () = msg_send![view, setMultipleTouchEnabled: YES];
|
|
||||||
if let Some(scale_factor) = platform_attributes.scale_factor {
|
|
||||||
let _: () = msg_send![view, setContentScaleFactor: scale_factor as CGFloat];
|
|
||||||
}
|
|
||||||
|
|
||||||
view
|
|
||||||
}
|
|
||||||
|
|
||||||
// requires main thread
|
|
||||||
pub(crate) unsafe fn create_view_controller(
|
|
||||||
_window_attributes: &WindowAttributes,
|
|
||||||
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
|
||||||
view: id,
|
|
||||||
) -> id {
|
|
||||||
let class = get_view_controller_class();
|
|
||||||
|
|
||||||
let view_controller: id = msg_send![class, alloc];
|
|
||||||
assert!(
|
|
||||||
!view_controller.is_null(),
|
|
||||||
"Failed to create `UIViewController` instance"
|
|
||||||
);
|
|
||||||
let view_controller: id = msg_send![view_controller, init];
|
|
||||||
assert!(
|
|
||||||
!view_controller.is_null(),
|
|
||||||
"Failed to initialize `UIViewController` instance"
|
|
||||||
);
|
|
||||||
let status_bar_hidden = if platform_attributes.prefers_status_bar_hidden {
|
|
||||||
YES
|
|
||||||
} else {
|
|
||||||
NO
|
|
||||||
};
|
|
||||||
let idiom = event_loop::get_idiom();
|
|
||||||
let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom(
|
|
||||||
platform_attributes.valid_orientations,
|
|
||||||
idiom,
|
|
||||||
);
|
|
||||||
let prefers_home_indicator_hidden = if platform_attributes.prefers_home_indicator_hidden {
|
|
||||||
YES
|
|
||||||
} else {
|
|
||||||
NO
|
|
||||||
};
|
|
||||||
let edges: UIRectEdge = platform_attributes
|
|
||||||
.preferred_screen_edges_deferring_system_gestures
|
|
||||||
.into();
|
|
||||||
let _: () = msg_send![
|
|
||||||
view_controller,
|
|
||||||
setPrefersStatusBarHidden: status_bar_hidden
|
|
||||||
];
|
|
||||||
let _: () = msg_send![
|
|
||||||
view_controller,
|
|
||||||
setSupportedInterfaceOrientations: supported_orientations
|
|
||||||
];
|
|
||||||
let _: () = msg_send![
|
|
||||||
view_controller,
|
|
||||||
setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden
|
|
||||||
];
|
|
||||||
let _: () = msg_send![
|
|
||||||
view_controller,
|
|
||||||
setPreferredScreenEdgesDeferringSystemGestures: edges
|
|
||||||
];
|
|
||||||
let _: () = msg_send![view_controller, setView: view];
|
|
||||||
view_controller
|
|
||||||
}
|
|
||||||
|
|
||||||
// requires main thread
|
|
||||||
pub(crate) unsafe fn create_window(
|
|
||||||
window_attributes: &WindowAttributes,
|
|
||||||
_platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
|
||||||
frame: CGRect,
|
|
||||||
view_controller: id,
|
|
||||||
) -> id {
|
|
||||||
let class = get_window_class();
|
|
||||||
|
|
||||||
let window: id = msg_send![class, alloc];
|
|
||||||
assert!(!window.is_null(), "Failed to create `UIWindow` instance");
|
|
||||||
let window: id = msg_send![window, initWithFrame: frame];
|
|
||||||
assert!(
|
|
||||||
!window.is_null(),
|
|
||||||
"Failed to initialize `UIWindow` instance"
|
|
||||||
);
|
|
||||||
let _: () = msg_send![window, setRootViewController: view_controller];
|
|
||||||
match window_attributes.fullscreen {
|
|
||||||
Some(Fullscreen::Exclusive(ref video_mode)) => {
|
|
||||||
let uiscreen = video_mode.monitor().ui_screen() as id;
|
|
||||||
let _: () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0];
|
|
||||||
msg_send![window, setScreen:video_mode.monitor().ui_screen()]
|
|
||||||
}
|
|
||||||
Some(Fullscreen::Borderless(ref monitor)) => {
|
|
||||||
let uiscreen: id = match &monitor {
|
|
||||||
Some(monitor) => monitor.ui_screen() as id,
|
|
||||||
None => {
|
|
||||||
let uiscreen: id = msg_send![window, screen];
|
|
||||||
uiscreen
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
msg_send![window, setScreen: uiscreen]
|
|
||||||
}
|
|
||||||
None => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
window
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_delegate_class() {
|
|
||||||
extern "C" fn did_finish_launching(_: &mut Object, _: Sel, _: id, _: id) -> BOOL {
|
|
||||||
unsafe {
|
|
||||||
app_state::did_finish_launching();
|
|
||||||
}
|
|
||||||
YES
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn did_become_active(_: &Object, _: Sel, _: id) {
|
|
||||||
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn will_resign_active(_: &Object, _: Sel, _: id) {
|
|
||||||
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Suspended)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn will_enter_foreground(_: &Object, _: Sel, _: id) {}
|
|
||||||
extern "C" fn did_enter_background(_: &Object, _: Sel, _: id) {}
|
|
||||||
|
|
||||||
extern "C" fn will_terminate(_: &Object, _: Sel, _: id) {
|
|
||||||
unsafe {
|
|
||||||
let app: id = msg_send![class!(UIApplication), sharedApplication];
|
|
||||||
let windows: id = msg_send![app, windows];
|
|
||||||
let windows_enum: id = msg_send![windows, objectEnumerator];
|
|
||||||
let mut events = Vec::new();
|
|
||||||
loop {
|
|
||||||
let window: id = msg_send![windows_enum, nextObject];
|
|
||||||
if window == nil {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let is_winit_window: BOOL = msg_send![window, isKindOfClass: class!(WinitUIWindow)];
|
|
||||||
if is_winit_window == YES {
|
|
||||||
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
|
|
||||||
window_id: RootWindowId(window.into()),
|
|
||||||
event: WindowEvent::Destroyed,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
app_state::handle_nonuser_events(events);
|
|
||||||
app_state::terminated();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let ui_responder = class!(UIResponder);
|
|
||||||
let mut decl =
|
|
||||||
ClassDecl::new("AppDelegate", ui_responder).expect("Failed to declare class `AppDelegate`");
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
decl.add_method(
|
|
||||||
sel!(application:didFinishLaunchingWithOptions:),
|
|
||||||
did_finish_launching as extern "C" fn(&mut Object, Sel, id, id) -> BOOL,
|
|
||||||
);
|
|
||||||
|
|
||||||
decl.add_method(
|
|
||||||
sel!(applicationDidBecomeActive:),
|
|
||||||
did_become_active as extern "C" fn(&Object, Sel, id),
|
|
||||||
);
|
|
||||||
decl.add_method(
|
|
||||||
sel!(applicationWillResignActive:),
|
|
||||||
will_resign_active as extern "C" fn(&Object, Sel, id),
|
|
||||||
);
|
|
||||||
decl.add_method(
|
|
||||||
sel!(applicationWillEnterForeground:),
|
|
||||||
will_enter_foreground as extern "C" fn(&Object, Sel, id),
|
|
||||||
);
|
|
||||||
decl.add_method(
|
|
||||||
sel!(applicationDidEnterBackground:),
|
|
||||||
did_enter_background as extern "C" fn(&Object, Sel, id),
|
|
||||||
);
|
|
||||||
|
|
||||||
decl.add_method(
|
|
||||||
sel!(applicationWillTerminate:),
|
|
||||||
will_terminate as extern "C" fn(&Object, Sel, id),
|
|
||||||
);
|
|
||||||
|
|
||||||
decl.register();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,708 +0,0 @@
|
|||||||
use std::{
|
|
||||||
collections::VecDeque,
|
|
||||||
ops::{Deref, DerefMut},
|
|
||||||
};
|
|
||||||
|
|
||||||
use objc::runtime::{Class, Object, BOOL, NO, YES};
|
|
||||||
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, UiKitDisplayHandle, UiKitWindowHandle};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
|
|
||||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
|
||||||
event::{Event, WindowEvent},
|
|
||||||
icon::Icon,
|
|
||||||
monitor::MonitorHandle as RootMonitorHandle,
|
|
||||||
platform::ios::{MonitorHandleExtIOS, ScreenEdge, ValidOrientations},
|
|
||||||
platform_impl::platform::{
|
|
||||||
app_state,
|
|
||||||
event_loop::{self, EventProxy, EventWrapper},
|
|
||||||
ffi::{
|
|
||||||
id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask,
|
|
||||||
UIRectEdge, UIScreenOverscanCompensation,
|
|
||||||
},
|
|
||||||
monitor, view, EventLoopWindowTarget, MonitorHandle,
|
|
||||||
},
|
|
||||||
window::{
|
|
||||||
CursorGrabMode, CursorIcon, Fullscreen, UserAttentionType, WindowAttributes,
|
|
||||||
WindowId as RootWindowId,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Inner {
|
|
||||||
pub window: id,
|
|
||||||
pub view_controller: id,
|
|
||||||
pub view: id,
|
|
||||||
gl_or_metal_backed: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Inner {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
let _: () = msg_send![self.view, release];
|
|
||||||
let _: () = msg_send![self.view_controller, release];
|
|
||||||
let _: () = msg_send![self.window, release];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Inner {
|
|
||||||
pub fn set_title(&self, _title: &str) {
|
|
||||||
debug!("`Window::set_title` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_visible(&self, visible: bool) {
|
|
||||||
match visible {
|
|
||||||
true => unsafe {
|
|
||||||
let _: () = msg_send![self.window, setHidden: NO];
|
|
||||||
},
|
|
||||||
false => unsafe {
|
|
||||||
let _: () = msg_send![self.window, setHidden: YES];
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_visible(&self) -> Option<bool> {
|
|
||||||
warn!("`Window::is_visible` is ignored on iOS");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn request_redraw(&self) {
|
|
||||||
unsafe {
|
|
||||||
if self.gl_or_metal_backed {
|
|
||||||
// `setNeedsDisplay` does nothing on UIViews which are directly backed by CAEAGLLayer or CAMetalLayer.
|
|
||||||
// Ordinarily the OS sets up a bunch of UIKit state before calling drawRect: on a UIView, but when using
|
|
||||||
// raw or gl/metal for drawing this work is completely avoided.
|
|
||||||
//
|
|
||||||
// The docs for `setNeedsDisplay` don't mention `CAMetalLayer`; however, this has been confirmed via
|
|
||||||
// testing.
|
|
||||||
//
|
|
||||||
// https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc
|
|
||||||
app_state::queue_gl_or_metal_redraw(self.window);
|
|
||||||
} else {
|
|
||||||
let _: () = msg_send![self.view, setNeedsDisplay];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
|
||||||
unsafe {
|
|
||||||
let safe_area = self.safe_area_screen_space();
|
|
||||||
let position = LogicalPosition {
|
|
||||||
x: safe_area.origin.x as f64,
|
|
||||||
y: safe_area.origin.y as f64,
|
|
||||||
};
|
|
||||||
let scale_factor = self.scale_factor();
|
|
||||||
Ok(position.to_physical(scale_factor))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
|
||||||
unsafe {
|
|
||||||
let screen_frame = self.screen_frame();
|
|
||||||
let position = LogicalPosition {
|
|
||||||
x: screen_frame.origin.x as f64,
|
|
||||||
y: screen_frame.origin.y as f64,
|
|
||||||
};
|
|
||||||
let scale_factor = self.scale_factor();
|
|
||||||
Ok(position.to_physical(scale_factor))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_outer_position(&self, physical_position: Position) {
|
|
||||||
unsafe {
|
|
||||||
let scale_factor = self.scale_factor();
|
|
||||||
let position = physical_position.to_logical::<f64>(scale_factor);
|
|
||||||
let screen_frame = self.screen_frame();
|
|
||||||
let new_screen_frame = CGRect {
|
|
||||||
origin: CGPoint {
|
|
||||||
x: position.x as _,
|
|
||||||
y: position.y as _,
|
|
||||||
},
|
|
||||||
size: screen_frame.size,
|
|
||||||
};
|
|
||||||
let bounds = self.rect_from_screen_space(new_screen_frame);
|
|
||||||
let _: () = msg_send![self.window, setBounds: bounds];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
|
||||||
unsafe {
|
|
||||||
let scale_factor = self.scale_factor();
|
|
||||||
let safe_area = self.safe_area_screen_space();
|
|
||||||
let size = LogicalSize {
|
|
||||||
width: safe_area.size.width as f64,
|
|
||||||
height: safe_area.size.height as f64,
|
|
||||||
};
|
|
||||||
size.to_physical(scale_factor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
|
||||||
unsafe {
|
|
||||||
let scale_factor = self.scale_factor();
|
|
||||||
let screen_frame = self.screen_frame();
|
|
||||||
let size = LogicalSize {
|
|
||||||
width: screen_frame.size.width as f64,
|
|
||||||
height: screen_frame.size.height as f64,
|
|
||||||
};
|
|
||||||
size.to_physical(scale_factor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_inner_size(&self, _size: Size) {
|
|
||||||
warn!("not clear what `Window::set_inner_size` means on iOS");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_min_inner_size(&self, _dimensions: Option<Size>) {
|
|
||||||
warn!("`Window::set_min_inner_size` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_max_inner_size(&self, _dimensions: Option<Size>) {
|
|
||||||
warn!("`Window::set_max_inner_size` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_resizable(&self, _resizable: bool) {
|
|
||||||
warn!("`Window::set_resizable` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_resizable(&self) -> bool {
|
|
||||||
warn!("`Window::is_resizable` is ignored on iOS");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scale_factor(&self) -> f64 {
|
|
||||||
unsafe {
|
|
||||||
let hidpi: CGFloat = msg_send![self.view, contentScaleFactor];
|
|
||||||
hidpi as _
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_cursor_icon(&self, _cursor: CursorIcon) {
|
|
||||||
debug!("`Window::set_cursor_icon` ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_cursor_position(&self, _position: Position) -> Result<(), ExternalError> {
|
|
||||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), ExternalError> {
|
|
||||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_cursor_visible(&self, _visible: bool) {
|
|
||||||
debug!("`Window::set_cursor_visible` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn drag_window(&self) -> Result<(), ExternalError> {
|
|
||||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_cursor_hittest(&self, _hittest: bool) -> Result<(), ExternalError> {
|
|
||||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_minimized(&self, _minimized: bool) {
|
|
||||||
warn!("`Window::set_minimized` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_maximized(&self, _maximized: bool) {
|
|
||||||
warn!("`Window::set_maximized` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_maximized(&self) -> bool {
|
|
||||||
warn!("`Window::is_maximized` is ignored on iOS");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
|
|
||||||
unsafe {
|
|
||||||
let uiscreen = match monitor {
|
|
||||||
Some(Fullscreen::Exclusive(video_mode)) => {
|
|
||||||
let uiscreen = video_mode.video_mode.monitor.ui_screen() as id;
|
|
||||||
let _: () =
|
|
||||||
msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode.0];
|
|
||||||
uiscreen
|
|
||||||
}
|
|
||||||
Some(Fullscreen::Borderless(monitor)) => monitor
|
|
||||||
.unwrap_or_else(|| self.current_monitor_inner())
|
|
||||||
.ui_screen() as id,
|
|
||||||
None => {
|
|
||||||
warn!("`Window::set_fullscreen(None)` ignored on iOS");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// this is pretty slow on iOS, so avoid doing it if we can
|
|
||||||
let current: id = msg_send![self.window, screen];
|
|
||||||
if uiscreen != current {
|
|
||||||
let _: () = msg_send![self.window, setScreen: uiscreen];
|
|
||||||
}
|
|
||||||
|
|
||||||
let bounds: CGRect = msg_send![uiscreen, bounds];
|
|
||||||
let _: () = msg_send![self.window, setFrame: bounds];
|
|
||||||
|
|
||||||
// For external displays, we must disable overscan compensation or
|
|
||||||
// the displayed image will have giant black bars surrounding it on
|
|
||||||
// each side
|
|
||||||
let _: () = msg_send![
|
|
||||||
uiscreen,
|
|
||||||
setOverscanCompensation: UIScreenOverscanCompensation::None
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
|
||||||
unsafe {
|
|
||||||
let monitor = self.current_monitor_inner();
|
|
||||||
let uiscreen = monitor.inner.ui_screen();
|
|
||||||
let screen_space_bounds = self.screen_frame();
|
|
||||||
let screen_bounds: CGRect = msg_send![uiscreen, bounds];
|
|
||||||
|
|
||||||
// TODO: track fullscreen instead of relying on brittle float comparisons
|
|
||||||
if screen_space_bounds.origin.x == screen_bounds.origin.x
|
|
||||||
&& screen_space_bounds.origin.y == screen_bounds.origin.y
|
|
||||||
&& screen_space_bounds.size.width == screen_bounds.size.width
|
|
||||||
&& screen_space_bounds.size.height == screen_bounds.size.height
|
|
||||||
{
|
|
||||||
Some(Fullscreen::Borderless(Some(monitor)))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_decorations(&self, _decorations: bool) {
|
|
||||||
warn!("`Window::set_decorations` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_decorated(&self) -> bool {
|
|
||||||
warn!("`Window::is_decorated` is ignored on iOS");
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_always_on_top(&self, _always_on_top: bool) {
|
|
||||||
warn!("`Window::set_always_on_top` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_window_icon(&self, _icon: Option<Icon>) {
|
|
||||||
warn!("`Window::set_window_icon` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_ime_position(&self, _position: Position) {
|
|
||||||
warn!("`Window::set_ime_position` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_ime_allowed(&self, _allowed: bool) {
|
|
||||||
warn!("`Window::set_ime_allowed` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn focus_window(&self) {
|
|
||||||
warn!("`Window::set_focus` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn request_user_attention(&self, _request_type: Option<UserAttentionType>) {
|
|
||||||
warn!("`Window::request_user_attention` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow directly accessing the current monitor internally without unwrapping.
|
|
||||||
fn current_monitor_inner(&self) -> RootMonitorHandle {
|
|
||||||
unsafe {
|
|
||||||
let uiscreen: id = msg_send![self.window, screen];
|
|
||||||
RootMonitorHandle {
|
|
||||||
inner: MonitorHandle::retained_new(uiscreen),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn current_monitor(&self) -> Option<RootMonitorHandle> {
|
|
||||||
Some(self.current_monitor_inner())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
|
||||||
unsafe { monitor::uiscreens() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
|
|
||||||
let monitor = unsafe { monitor::main_uiscreen() };
|
|
||||||
Some(RootMonitorHandle { inner: monitor })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn id(&self) -> WindowId {
|
|
||||||
self.window.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
|
||||||
let mut window_handle = UiKitWindowHandle::empty();
|
|
||||||
window_handle.ui_window = self.window as _;
|
|
||||||
window_handle.ui_view = self.view as _;
|
|
||||||
window_handle.ui_view_controller = self.view_controller as _;
|
|
||||||
RawWindowHandle::UiKit(window_handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
|
||||||
RawDisplayHandle::UiKit(UiKitDisplayHandle::empty())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Window {
|
|
||||||
pub inner: Inner,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Window {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
assert_main_thread!("`Window::drop` can only be run on the main thread on iOS");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for Window {}
|
|
||||||
unsafe impl Sync for Window {}
|
|
||||||
|
|
||||||
impl Deref for Window {
|
|
||||||
type Target = Inner;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Inner {
|
|
||||||
unsafe {
|
|
||||||
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
|
|
||||||
}
|
|
||||||
&self.inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for Window {
|
|
||||||
fn deref_mut(&mut self) -> &mut Inner {
|
|
||||||
unsafe {
|
|
||||||
assert_main_thread!("`Window` methods can only be run on the main thread on iOS");
|
|
||||||
}
|
|
||||||
&mut self.inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Window {
|
|
||||||
pub(crate) fn new<T>(
|
|
||||||
_event_loop: &EventLoopWindowTarget<T>,
|
|
||||||
window_attributes: WindowAttributes,
|
|
||||||
platform_attributes: PlatformSpecificWindowBuilderAttributes,
|
|
||||||
) -> Result<Window, RootOsError> {
|
|
||||||
if window_attributes.min_inner_size.is_some() {
|
|
||||||
warn!("`WindowAttributes::min_inner_size` is ignored on iOS");
|
|
||||||
}
|
|
||||||
if window_attributes.max_inner_size.is_some() {
|
|
||||||
warn!("`WindowAttributes::max_inner_size` is ignored on iOS");
|
|
||||||
}
|
|
||||||
if window_attributes.always_on_top {
|
|
||||||
warn!("`WindowAttributes::always_on_top` is unsupported on iOS");
|
|
||||||
}
|
|
||||||
// TODO: transparency, visible
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let screen = match window_attributes.fullscreen {
|
|
||||||
Some(Fullscreen::Exclusive(ref video_mode)) => {
|
|
||||||
video_mode.video_mode.monitor.ui_screen() as id
|
|
||||||
}
|
|
||||||
Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.inner.ui_screen(),
|
|
||||||
Some(Fullscreen::Borderless(None)) | None => {
|
|
||||||
monitor::main_uiscreen().ui_screen() as id
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let screen_bounds: CGRect = msg_send![screen, bounds];
|
|
||||||
|
|
||||||
let frame = match window_attributes.inner_size {
|
|
||||||
Some(dim) => {
|
|
||||||
let scale_factor = msg_send![screen, scale];
|
|
||||||
let size = dim.to_logical::<f64>(scale_factor);
|
|
||||||
CGRect {
|
|
||||||
origin: screen_bounds.origin,
|
|
||||||
size: CGSize {
|
|
||||||
width: size.width as _,
|
|
||||||
height: size.height as _,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => screen_bounds,
|
|
||||||
};
|
|
||||||
|
|
||||||
let view = view::create_view(&window_attributes, &platform_attributes, frame);
|
|
||||||
|
|
||||||
let gl_or_metal_backed = {
|
|
||||||
let view_class: id = msg_send![view, class];
|
|
||||||
let layer_class: id = msg_send![view_class, layerClass];
|
|
||||||
let is_metal: BOOL =
|
|
||||||
msg_send![layer_class, isSubclassOfClass: class!(CAMetalLayer)];
|
|
||||||
let is_gl: BOOL = msg_send![layer_class, isSubclassOfClass: class!(CAEAGLLayer)];
|
|
||||||
is_metal == YES || is_gl == YES
|
|
||||||
};
|
|
||||||
|
|
||||||
let view_controller =
|
|
||||||
view::create_view_controller(&window_attributes, &platform_attributes, view);
|
|
||||||
let window = view::create_window(
|
|
||||||
&window_attributes,
|
|
||||||
&platform_attributes,
|
|
||||||
frame,
|
|
||||||
view_controller,
|
|
||||||
);
|
|
||||||
|
|
||||||
let result = Window {
|
|
||||||
inner: Inner {
|
|
||||||
window,
|
|
||||||
view_controller,
|
|
||||||
view,
|
|
||||||
gl_or_metal_backed,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
app_state::set_key_window(window);
|
|
||||||
|
|
||||||
// Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized`
|
|
||||||
// event on window creation if the DPI factor != 1.0
|
|
||||||
let scale_factor: CGFloat = msg_send![view, contentScaleFactor];
|
|
||||||
let scale_factor = scale_factor as f64;
|
|
||||||
if scale_factor != 1.0 {
|
|
||||||
let bounds: CGRect = msg_send![view, bounds];
|
|
||||||
let screen: id = msg_send![window, screen];
|
|
||||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
|
||||||
let screen_frame: CGRect =
|
|
||||||
msg_send![view, convertRect:bounds toCoordinateSpace:screen_space];
|
|
||||||
let size = crate::dpi::LogicalSize {
|
|
||||||
width: screen_frame.size.width as _,
|
|
||||||
height: screen_frame.size.height as _,
|
|
||||||
};
|
|
||||||
app_state::handle_nonuser_events(
|
|
||||||
std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
|
|
||||||
window_id: window,
|
|
||||||
scale_factor,
|
|
||||||
suggested_size: size,
|
|
||||||
}))
|
|
||||||
.chain(std::iter::once(EventWrapper::StaticEvent(
|
|
||||||
Event::WindowEvent {
|
|
||||||
window_id: RootWindowId(window.into()),
|
|
||||||
event: WindowEvent::Resized(size.to_physical(scale_factor)),
|
|
||||||
},
|
|
||||||
))),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WindowExtIOS
|
|
||||||
impl Inner {
|
|
||||||
pub fn ui_window(&self) -> id {
|
|
||||||
self.window
|
|
||||||
}
|
|
||||||
pub fn ui_view_controller(&self) -> id {
|
|
||||||
self.view_controller
|
|
||||||
}
|
|
||||||
pub fn ui_view(&self) -> id {
|
|
||||||
self.view
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_scale_factor(&self, scale_factor: f64) {
|
|
||||||
unsafe {
|
|
||||||
assert!(
|
|
||||||
dpi::validate_scale_factor(scale_factor),
|
|
||||||
"`WindowExtIOS::set_scale_factor` received an invalid hidpi factor"
|
|
||||||
);
|
|
||||||
let scale_factor = scale_factor as CGFloat;
|
|
||||||
let _: () = msg_send![self.view, setContentScaleFactor: scale_factor];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
|
|
||||||
unsafe {
|
|
||||||
let idiom = event_loop::get_idiom();
|
|
||||||
let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom(
|
|
||||||
valid_orientations,
|
|
||||||
idiom,
|
|
||||||
);
|
|
||||||
msg_send![
|
|
||||||
self.view_controller,
|
|
||||||
setSupportedInterfaceOrientations: supported_orientations
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
|
|
||||||
unsafe {
|
|
||||||
let prefers_home_indicator_hidden = if hidden { YES } else { NO };
|
|
||||||
let _: () = msg_send![
|
|
||||||
self.view_controller,
|
|
||||||
setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
|
|
||||||
let edges: UIRectEdge = edges.into();
|
|
||||||
unsafe {
|
|
||||||
let _: () = msg_send![
|
|
||||||
self.view_controller,
|
|
||||||
setPreferredScreenEdgesDeferringSystemGestures: edges
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_prefers_status_bar_hidden(&self, hidden: bool) {
|
|
||||||
unsafe {
|
|
||||||
let status_bar_hidden = if hidden { YES } else { NO };
|
|
||||||
let _: () = msg_send![
|
|
||||||
self.view_controller,
|
|
||||||
setPrefersStatusBarHidden: status_bar_hidden
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Inner {
|
|
||||||
// requires main thread
|
|
||||||
unsafe fn screen_frame(&self) -> CGRect {
|
|
||||||
self.rect_to_screen_space(msg_send![self.window, bounds])
|
|
||||||
}
|
|
||||||
|
|
||||||
// requires main thread
|
|
||||||
unsafe fn rect_to_screen_space(&self, rect: CGRect) -> CGRect {
|
|
||||||
let screen: id = msg_send![self.window, screen];
|
|
||||||
if !screen.is_null() {
|
|
||||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
|
||||||
msg_send![self.window, convertRect:rect toCoordinateSpace:screen_space]
|
|
||||||
} else {
|
|
||||||
rect
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// requires main thread
|
|
||||||
unsafe fn rect_from_screen_space(&self, rect: CGRect) -> CGRect {
|
|
||||||
let screen: id = msg_send![self.window, screen];
|
|
||||||
if !screen.is_null() {
|
|
||||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
|
||||||
msg_send![self.window, convertRect:rect fromCoordinateSpace:screen_space]
|
|
||||||
} else {
|
|
||||||
rect
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// requires main thread
|
|
||||||
unsafe fn safe_area_screen_space(&self) -> CGRect {
|
|
||||||
let bounds: CGRect = msg_send![self.window, bounds];
|
|
||||||
if app_state::os_capabilities().safe_area {
|
|
||||||
let safe_area: UIEdgeInsets = msg_send![self.window, safeAreaInsets];
|
|
||||||
let safe_bounds = CGRect {
|
|
||||||
origin: CGPoint {
|
|
||||||
x: bounds.origin.x + safe_area.left,
|
|
||||||
y: bounds.origin.y + safe_area.top,
|
|
||||||
},
|
|
||||||
size: CGSize {
|
|
||||||
width: bounds.size.width - safe_area.left - safe_area.right,
|
|
||||||
height: bounds.size.height - safe_area.top - safe_area.bottom,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
self.rect_to_screen_space(safe_bounds)
|
|
||||||
} else {
|
|
||||||
let screen_frame = self.rect_to_screen_space(bounds);
|
|
||||||
let status_bar_frame: CGRect = {
|
|
||||||
let app: id = msg_send![class!(UIApplication), sharedApplication];
|
|
||||||
assert!(
|
|
||||||
!app.is_null(),
|
|
||||||
"`Window::get_inner_position` cannot be called before `EventLoop::run` on iOS"
|
|
||||||
);
|
|
||||||
msg_send![app, statusBarFrame]
|
|
||||||
};
|
|
||||||
let (y, height) = if screen_frame.origin.y > status_bar_frame.size.height {
|
|
||||||
(screen_frame.origin.y, screen_frame.size.height)
|
|
||||||
} else {
|
|
||||||
let y = status_bar_frame.size.height;
|
|
||||||
let height = screen_frame.size.height
|
|
||||||
- (status_bar_frame.size.height - screen_frame.origin.y);
|
|
||||||
(y, height)
|
|
||||||
};
|
|
||||||
CGRect {
|
|
||||||
origin: CGPoint {
|
|
||||||
x: screen_frame.origin.x,
|
|
||||||
y,
|
|
||||||
},
|
|
||||||
size: CGSize {
|
|
||||||
width: screen_frame.size.width,
|
|
||||||
height,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct WindowId {
|
|
||||||
window: id,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowId {
|
|
||||||
pub const unsafe fn dummy() -> Self {
|
|
||||||
WindowId {
|
|
||||||
window: std::ptr::null_mut(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<WindowId> for u64 {
|
|
||||||
fn from(window_id: WindowId) -> Self {
|
|
||||||
window_id.window as u64
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u64> for WindowId {
|
|
||||||
fn from(raw_id: u64) -> Self {
|
|
||||||
Self {
|
|
||||||
window: raw_id as _,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for WindowId {}
|
|
||||||
unsafe impl Sync for WindowId {}
|
|
||||||
|
|
||||||
impl From<&Object> for WindowId {
|
|
||||||
fn from(window: &Object) -> WindowId {
|
|
||||||
WindowId {
|
|
||||||
window: window as *const _ as _,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&mut Object> for WindowId {
|
|
||||||
fn from(window: &mut Object) -> WindowId {
|
|
||||||
WindowId {
|
|
||||||
window: window as _,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<id> for WindowId {
|
|
||||||
fn from(window: id) -> WindowId {
|
|
||||||
WindowId { window }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
|
||||||
pub root_view_class: &'static Class,
|
|
||||||
pub scale_factor: Option<f64>,
|
|
||||||
pub valid_orientations: ValidOrientations,
|
|
||||||
pub prefers_home_indicator_hidden: bool,
|
|
||||||
pub prefers_status_bar_hidden: bool,
|
|
||||||
pub preferred_screen_edges_deferring_system_gestures: ScreenEdge,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for PlatformSpecificWindowBuilderAttributes {
|
|
||||||
fn default() -> PlatformSpecificWindowBuilderAttributes {
|
|
||||||
PlatformSpecificWindowBuilderAttributes {
|
|
||||||
root_view_class: class!(UIView),
|
|
||||||
scale_factor: None,
|
|
||||||
valid_orientations: Default::default(),
|
|
||||||
prefers_home_indicator_hidden: false,
|
|
||||||
prefers_status_bar_hidden: false,
|
|
||||||
preferred_screen_edges_deferring_system_gestures: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,869 +0,0 @@
|
|||||||
#![cfg(any(
|
|
||||||
target_os = "linux",
|
|
||||||
target_os = "dragonfly",
|
|
||||||
target_os = "freebsd",
|
|
||||||
target_os = "netbsd",
|
|
||||||
target_os = "openbsd"
|
|
||||||
))]
|
|
||||||
|
|
||||||
#[cfg(all(not(feature = "x11"), not(feature = "wayland")))]
|
|
||||||
compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
|
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
use std::{collections::VecDeque, env, fmt};
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc};
|
|
||||||
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
|
|
||||||
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
pub use self::x11::XNotSupported;
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
use self::x11::{ffi::XVisualInfo, util::WindowType as XWindowType, XConnection, XError};
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
use crate::platform::unix::XlibErrorHook;
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
use crate::window::Theme;
|
|
||||||
use crate::{
|
|
||||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
|
||||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
|
||||||
event::Event,
|
|
||||||
event_loop::{
|
|
||||||
ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW,
|
|
||||||
},
|
|
||||||
icon::Icon,
|
|
||||||
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
|
||||||
window::{CursorGrabMode, CursorIcon, Fullscreen, UserAttentionType, WindowAttributes},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
|
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
pub mod wayland;
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
pub mod x11;
|
|
||||||
|
|
||||||
/// Environment variable specifying which backend should be used on unix platform.
|
|
||||||
///
|
|
||||||
/// Legal values are x11 and wayland. If this variable is set only the named backend
|
|
||||||
/// will be tried by winit. If it is not set, winit will try to connect to a wayland connection,
|
|
||||||
/// and if it fails will fallback on x11.
|
|
||||||
///
|
|
||||||
/// If this variable is set with any other value, winit will panic.
|
|
||||||
const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND";
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) enum Backend {
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
X,
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
Wayland,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) struct PlatformSpecificEventLoopAttributes {
|
|
||||||
pub(crate) forced_backend: Option<Backend>,
|
|
||||||
pub(crate) any_thread: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct ApplicationName {
|
|
||||||
pub general: String,
|
|
||||||
pub instance: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ApplicationName {
|
|
||||||
pub fn new(general: String, instance: String) -> Self {
|
|
||||||
Self { general, instance }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
|
||||||
pub name: Option<ApplicationName>,
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
pub visual_infos: Option<XVisualInfo>,
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
pub screen_id: Option<i32>,
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
pub resize_increments: Option<Size>,
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
pub base_size: Option<Size>,
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
pub override_redirect: bool,
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
pub x11_window_types: Vec<XWindowType>,
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
pub gtk_theme_variant: Option<String>,
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
pub csd_theme: Option<Theme>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for PlatformSpecificWindowBuilderAttributes {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
name: None,
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
visual_infos: None,
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
screen_id: None,
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
resize_increments: None,
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
base_size: None,
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
override_redirect: false,
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
x11_window_types: vec![XWindowType::Normal],
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
gtk_theme_variant: None,
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
csd_theme: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
pub static X11_BACKEND: Lazy<Mutex<Result<Arc<XConnection>, XNotSupported>>> =
|
|
||||||
Lazy::new(|| Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)));
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum OsError {
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
XError(XError),
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
XMisc(&'static str),
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
WaylandMisc(&'static str),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for OsError {
|
|
||||||
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
|
||||||
match *self {
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
OsError::XError(ref e) => _f.pad(&e.description),
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
OsError::XMisc(e) => _f.pad(e),
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
OsError::WaylandMisc(e) => _f.pad(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Window {
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
X(x11::Window),
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
Wayland(wayland::Window),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct WindowId(u64);
|
|
||||||
|
|
||||||
impl From<WindowId> for u64 {
|
|
||||||
fn from(window_id: WindowId) -> Self {
|
|
||||||
window_id.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u64> for WindowId {
|
|
||||||
fn from(raw_id: u64) -> Self {
|
|
||||||
Self(raw_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowId {
|
|
||||||
pub const unsafe fn dummy() -> Self {
|
|
||||||
Self(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub enum DeviceId {
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
X(x11::DeviceId),
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
Wayland(wayland::DeviceId),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DeviceId {
|
|
||||||
pub const unsafe fn dummy() -> Self {
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
return DeviceId::Wayland(wayland::DeviceId::dummy());
|
|
||||||
#[cfg(all(not(feature = "wayland"), feature = "x11"))]
|
|
||||||
return DeviceId::X(x11::DeviceId::dummy());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub enum MonitorHandle {
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
X(x11::MonitorHandle),
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
Wayland(wayland::MonitorHandle),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `x11_or_wayland!(match expr; Enum(foo) => foo.something())`
|
|
||||||
/// expands to the equivalent of
|
|
||||||
/// ```ignore
|
|
||||||
/// match self {
|
|
||||||
/// Enum::X(foo) => foo.something(),
|
|
||||||
/// Enum::Wayland(foo) => foo.something(),
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
/// The result can be converted to another enum by adding `; as AnotherEnum`
|
|
||||||
macro_rules! x11_or_wayland {
|
|
||||||
(match $what:expr; $enum:ident ( $($c1:tt)* ) => $x:expr; as $enum2:ident ) => {
|
|
||||||
match $what {
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
$enum::X($($c1)*) => $enum2::X($x),
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
$enum::Wayland($($c1)*) => $enum2::Wayland($x),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
(match $what:expr; $enum:ident ( $($c1:tt)* ) => $x:expr) => {
|
|
||||||
match $what {
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
$enum::X($($c1)*) => $x,
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
$enum::Wayland($($c1)*) => $x,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MonitorHandle {
|
|
||||||
#[inline]
|
|
||||||
pub fn name(&self) -> Option<String> {
|
|
||||||
x11_or_wayland!(match self; MonitorHandle(m) => m.name())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn native_identifier(&self) -> u32 {
|
|
||||||
x11_or_wayland!(match self; MonitorHandle(m) => m.native_identifier())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn size(&self) -> PhysicalSize<u32> {
|
|
||||||
x11_or_wayland!(match self; MonitorHandle(m) => m.size())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn position(&self) -> PhysicalPosition<i32> {
|
|
||||||
x11_or_wayland!(match self; MonitorHandle(m) => m.position())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
|
||||||
x11_or_wayland!(match self; MonitorHandle(m) => m.refresh_rate_millihertz())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn scale_factor(&self) -> f64 {
|
|
||||||
x11_or_wayland!(match self; MonitorHandle(m) => m.scale_factor() as f64)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn video_modes(&self) -> Box<dyn Iterator<Item = RootVideoMode>> {
|
|
||||||
x11_or_wayland!(match self; MonitorHandle(m) => Box::new(m.video_modes()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub enum VideoMode {
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
X(x11::VideoMode),
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
Wayland(wayland::VideoMode),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VideoMode {
|
|
||||||
#[inline]
|
|
||||||
pub fn size(&self) -> PhysicalSize<u32> {
|
|
||||||
x11_or_wayland!(match self; VideoMode(m) => m.size())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn bit_depth(&self) -> u16 {
|
|
||||||
x11_or_wayland!(match self; VideoMode(m) => m.bit_depth())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn refresh_rate_millihertz(&self) -> u32 {
|
|
||||||
x11_or_wayland!(match self; VideoMode(m) => m.refresh_rate_millihertz())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn monitor(&self) -> RootMonitorHandle {
|
|
||||||
x11_or_wayland!(match self; VideoMode(m) => m.monitor())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Window {
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn new<T>(
|
|
||||||
window_target: &EventLoopWindowTarget<T>,
|
|
||||||
attribs: WindowAttributes,
|
|
||||||
pl_attribs: PlatformSpecificWindowBuilderAttributes,
|
|
||||||
) -> Result<Self, RootOsError> {
|
|
||||||
match *window_target {
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
EventLoopWindowTarget::Wayland(ref window_target) => {
|
|
||||||
wayland::Window::new(window_target, attribs, pl_attribs).map(Window::Wayland)
|
|
||||||
}
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
EventLoopWindowTarget::X(ref window_target) => {
|
|
||||||
x11::Window::new(window_target, attribs, pl_attribs).map(Window::X)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn id(&self) -> WindowId {
|
|
||||||
match self {
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
Self::Wayland(window) => window.id(),
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
Self::X(window) => window.id(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_title(&self, title: &str) {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.set_title(title));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_visible(&self, visible: bool) {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.set_visible(visible))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn is_visible(&self) -> Option<bool> {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.is_visible())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.outer_position())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.inner_position())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_outer_position(&self, position: Position) {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.set_outer_position(position))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.inner_size())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.outer_size())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_inner_size(&self, size: Size) {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.set_inner_size(size))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.set_min_inner_size(dimensions))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.set_max_inner_size(dimensions))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_resizable(&self, resizable: bool) {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.set_resizable(resizable))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn is_resizable(&self) -> bool {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.is_resizable())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.set_cursor_icon(cursor))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
|
|
||||||
x11_or_wayland!(match self; Window(window) => window.set_cursor_grab(mode))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_cursor_visible(&self, visible: bool) {
|
|
||||||
x11_or_wayland!(match self; Window(window) => window.set_cursor_visible(visible))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn drag_window(&self) -> Result<(), ExternalError> {
|
|
||||||
x11_or_wayland!(match self; Window(window) => window.drag_window())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.set_cursor_hittest(hittest))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn scale_factor(&self) -> f64 {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.scale_factor() as f64)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.set_cursor_position(position))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_maximized(&self, maximized: bool) {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.set_maximized(maximized))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn is_maximized(&self) -> bool {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.is_maximized())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_minimized(&self, minimized: bool) {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.set_minimized(minimized))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.fullscreen())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.set_fullscreen(monitor))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_decorations(&self, decorations: bool) {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.set_decorations(decorations))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn is_decorated(&self) -> bool {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.is_decorated())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_always_on_top(&self, _always_on_top: bool) {
|
|
||||||
match self {
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
Window::X(ref w) => w.set_always_on_top(_always_on_top),
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
Window::Wayland(_) => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_window_icon(&self, _window_icon: Option<Icon>) {
|
|
||||||
match self {
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
Window::X(ref w) => w.set_window_icon(_window_icon),
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
Window::Wayland(_) => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_ime_position(&self, position: Position) {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.set_ime_position(position))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_ime_allowed(&self, allowed: bool) {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.set_ime_allowed(allowed))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn focus_window(&self) {
|
|
||||||
match self {
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
Window::X(ref w) => w.focus_window(),
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
Window::Wayland(_) => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
|
|
||||||
match self {
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
Window::X(ref w) => w.request_user_attention(request_type),
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
Window::Wayland(ref w) => w.request_user_attention(request_type),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn request_redraw(&self) {
|
|
||||||
x11_or_wayland!(match self; Window(w) => w.request_redraw())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn current_monitor(&self) -> Option<RootMonitorHandle> {
|
|
||||||
match self {
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
Window::X(ref window) => {
|
|
||||||
let current_monitor = MonitorHandle::X(window.current_monitor());
|
|
||||||
Some(RootMonitorHandle {
|
|
||||||
inner: current_monitor,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
Window::Wayland(ref window) => {
|
|
||||||
let current_monitor = MonitorHandle::Wayland(window.current_monitor()?);
|
|
||||||
Some(RootMonitorHandle {
|
|
||||||
inner: current_monitor,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
|
||||||
match self {
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
Window::X(ref window) => window
|
|
||||||
.available_monitors()
|
|
||||||
.into_iter()
|
|
||||||
.map(MonitorHandle::X)
|
|
||||||
.collect(),
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
Window::Wayland(ref window) => window
|
|
||||||
.available_monitors()
|
|
||||||
.into_iter()
|
|
||||||
.map(MonitorHandle::Wayland)
|
|
||||||
.collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
|
|
||||||
match self {
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
Window::X(ref window) => {
|
|
||||||
let primary_monitor = MonitorHandle::X(window.primary_monitor());
|
|
||||||
Some(RootMonitorHandle {
|
|
||||||
inner: primary_monitor,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
Window::Wayland(ref window) => window.primary_monitor(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
|
||||||
x11_or_wayland!(match self; Window(window) => window.raw_window_handle())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
|
||||||
x11_or_wayland!(match self; Window(window) => window.raw_display_handle())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Hooks for X11 errors.
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
pub(crate) static mut XLIB_ERROR_HOOKS: Mutex<Vec<XlibErrorHook>> = Mutex::new(Vec::new());
|
|
||||||
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
unsafe extern "C" fn x_error_callback(
|
|
||||||
display: *mut x11::ffi::Display,
|
|
||||||
event: *mut x11::ffi::XErrorEvent,
|
|
||||||
) -> c_int {
|
|
||||||
let xconn_lock = X11_BACKEND.lock();
|
|
||||||
if let Ok(ref xconn) = *xconn_lock {
|
|
||||||
// Call all the hooks.
|
|
||||||
let mut error_handled = false;
|
|
||||||
for hook in XLIB_ERROR_HOOKS.lock().iter() {
|
|
||||||
error_handled |= hook(display as *mut _, event as *mut _);
|
|
||||||
}
|
|
||||||
|
|
||||||
// `assume_init` is safe here because the array consists of `MaybeUninit` values,
|
|
||||||
// which do not require initialization.
|
|
||||||
let mut buf: [MaybeUninit<c_char>; 1024] = MaybeUninit::uninit().assume_init();
|
|
||||||
(xconn.xlib.XGetErrorText)(
|
|
||||||
display,
|
|
||||||
(*event).error_code as c_int,
|
|
||||||
buf.as_mut_ptr() as *mut c_char,
|
|
||||||
buf.len() as c_int,
|
|
||||||
);
|
|
||||||
let description = CStr::from_ptr(buf.as_ptr() as *const c_char).to_string_lossy();
|
|
||||||
|
|
||||||
let error = XError {
|
|
||||||
description: description.into_owned(),
|
|
||||||
error_code: (*event).error_code,
|
|
||||||
request_code: (*event).request_code,
|
|
||||||
minor_code: (*event).minor_code,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Don't log error.
|
|
||||||
if !error_handled {
|
|
||||||
error!("X11 error: {:#?}", error);
|
|
||||||
}
|
|
||||||
|
|
||||||
*xconn.latest_error.lock() = Some(error);
|
|
||||||
}
|
|
||||||
// Fun fact: this return value is completely ignored.
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum EventLoop<T: 'static> {
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
Wayland(wayland::EventLoop<T>),
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
X(x11::EventLoop<T>),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum EventLoopProxy<T: 'static> {
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
X(x11::EventLoopProxy<T>),
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
Wayland(wayland::EventLoopProxy<T>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> Clone for EventLoopProxy<T> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.clone(); as EventLoopProxy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> EventLoop<T> {
|
|
||||||
pub(crate) fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Self {
|
|
||||||
if !attributes.any_thread && !is_main_thread() {
|
|
||||||
panic!(
|
|
||||||
"Initializing the event loop outside of the main thread is a significant \
|
|
||||||
cross-platform compatibility hazard. If you absolutely need to create an \
|
|
||||||
EventLoop on a different thread, you can use the \
|
|
||||||
`EventLoopBuilderExtUnix::any_thread` function."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
if attributes.forced_backend == Some(Backend::X) {
|
|
||||||
// TODO: Propagate
|
|
||||||
return EventLoop::new_x11_any_thread().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
if attributes.forced_backend == Some(Backend::Wayland) {
|
|
||||||
// TODO: Propagate
|
|
||||||
return EventLoop::new_wayland_any_thread().expect("failed to open Wayland connection");
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) {
|
|
||||||
match env_var.as_str() {
|
|
||||||
"x11" => {
|
|
||||||
// TODO: propagate
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
return EventLoop::new_x11_any_thread()
|
|
||||||
.expect("Failed to initialize X11 backend");
|
|
||||||
#[cfg(not(feature = "x11"))]
|
|
||||||
panic!("x11 feature is not enabled")
|
|
||||||
}
|
|
||||||
"wayland" => {
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
return EventLoop::new_wayland_any_thread()
|
|
||||||
.expect("Failed to initialize Wayland backend");
|
|
||||||
#[cfg(not(feature = "wayland"))]
|
|
||||||
panic!("wayland feature is not enabled");
|
|
||||||
}
|
|
||||||
_ => panic!(
|
|
||||||
"Unknown environment variable value for {}, try one of `x11`,`wayland`",
|
|
||||||
BACKEND_PREFERENCE_ENV_VAR,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
let wayland_err = match EventLoop::new_wayland_any_thread() {
|
|
||||||
Ok(event_loop) => return event_loop,
|
|
||||||
Err(err) => err,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
let x11_err = match EventLoop::new_x11_any_thread() {
|
|
||||||
Ok(event_loop) => return event_loop,
|
|
||||||
Err(err) => err,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(not(feature = "wayland"))]
|
|
||||||
let wayland_err = "backend disabled";
|
|
||||||
#[cfg(not(feature = "x11"))]
|
|
||||||
let x11_err = "backend disabled";
|
|
||||||
|
|
||||||
panic!(
|
|
||||||
"Failed to initialize any backend! Wayland status: {:?} X11 status: {:?}",
|
|
||||||
wayland_err, x11_err,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
fn new_wayland_any_thread() -> Result<EventLoop<T>, Box<dyn Error>> {
|
|
||||||
wayland::EventLoop::new().map(EventLoop::Wayland)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
fn new_x11_any_thread() -> Result<EventLoop<T>, XNotSupported> {
|
|
||||||
let xconn = match X11_BACKEND.lock().as_ref() {
|
|
||||||
Ok(xconn) => xconn.clone(),
|
|
||||||
Err(err) => return Err(err.clone()),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(EventLoop::X(x11::EventLoop::new(xconn)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
|
||||||
x11_or_wayland!(match self; EventLoop(evlp) => evlp.create_proxy(); as EventLoopProxy)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_return<F>(&mut self, callback: F) -> i32
|
|
||||||
where
|
|
||||||
F: FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
|
||||||
{
|
|
||||||
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run_return(callback))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run<F>(self, callback: F) -> !
|
|
||||||
where
|
|
||||||
F: 'static + FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
|
||||||
{
|
|
||||||
x11_or_wayland!(match self; EventLoop(evlp) => evlp.run(callback))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget<T> {
|
|
||||||
x11_or_wayland!(match self; EventLoop(evlp) => evlp.window_target())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> EventLoopProxy<T> {
|
|
||||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
|
||||||
x11_or_wayland!(match self; EventLoopProxy(proxy) => proxy.send_event(event))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum EventLoopWindowTarget<T> {
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
Wayland(wayland::EventLoopWindowTarget<T>),
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
X(x11::EventLoopWindowTarget<T>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopWindowTarget<T> {
|
|
||||||
#[inline]
|
|
||||||
pub fn is_wayland(&self) -> bool {
|
|
||||||
match *self {
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
EventLoopWindowTarget::Wayland(_) => true,
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
|
||||||
match *self {
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
EventLoopWindowTarget::Wayland(ref evlp) => evlp
|
|
||||||
.available_monitors()
|
|
||||||
.into_iter()
|
|
||||||
.map(MonitorHandle::Wayland)
|
|
||||||
.collect(),
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
EventLoopWindowTarget::X(ref evlp) => evlp
|
|
||||||
.x_connection()
|
|
||||||
.available_monitors()
|
|
||||||
.into_iter()
|
|
||||||
.map(MonitorHandle::X)
|
|
||||||
.collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
|
|
||||||
match *self {
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
EventLoopWindowTarget::Wayland(ref evlp) => evlp.primary_monitor(),
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
EventLoopWindowTarget::X(ref evlp) => {
|
|
||||||
let primary_monitor = MonitorHandle::X(evlp.x_connection().primary_monitor());
|
|
||||||
Some(RootMonitorHandle {
|
|
||||||
inner: primary_monitor,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_device_event_filter(&self, _filter: DeviceEventFilter) {
|
|
||||||
match *self {
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
EventLoopWindowTarget::Wayland(_) => (),
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
EventLoopWindowTarget::X(ref evlp) => evlp.set_device_event_filter(_filter),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle {
|
|
||||||
x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sticky_exit_callback<T, F>(
|
|
||||||
evt: Event<'_, T>,
|
|
||||||
target: &RootELW<T>,
|
|
||||||
control_flow: &mut ControlFlow,
|
|
||||||
callback: &mut F,
|
|
||||||
) where
|
|
||||||
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
|
||||||
{
|
|
||||||
// make ControlFlow::ExitWithCode sticky by providing a dummy
|
|
||||||
// control flow reference if it is already ExitWithCode.
|
|
||||||
if let ControlFlow::ExitWithCode(code) = *control_flow {
|
|
||||||
callback(evt, target, &mut ControlFlow::ExitWithCode(code))
|
|
||||||
} else {
|
|
||||||
callback(evt, target, control_flow)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
fn is_main_thread() -> bool {
|
|
||||||
use libc::{c_long, getpid, syscall, SYS_gettid};
|
|
||||||
|
|
||||||
unsafe { syscall(SYS_gettid) == getpid() as c_long }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
|
|
||||||
fn is_main_thread() -> bool {
|
|
||||||
use libc::pthread_main_np;
|
|
||||||
|
|
||||||
unsafe { pthread_main_np() == 1 }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "netbsd")]
|
|
||||||
fn is_main_thread() -> bool {
|
|
||||||
std::thread::current().name() == Some("main")
|
|
||||||
}
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
//! SCTK environment setup.
|
|
||||||
|
|
||||||
use sctk::reexports::client::protocol::wl_compositor::WlCompositor;
|
|
||||||
use sctk::reexports::client::protocol::wl_output::WlOutput;
|
|
||||||
use sctk::reexports::protocols::unstable::xdg_shell::v6::client::zxdg_shell_v6::ZxdgShellV6;
|
|
||||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
|
||||||
use sctk::reexports::protocols::unstable::xdg_decoration::v1::client::zxdg_decoration_manager_v1::ZxdgDecorationManagerV1;
|
|
||||||
use sctk::reexports::client::protocol::wl_shell::WlShell;
|
|
||||||
use sctk::reexports::client::protocol::wl_subcompositor::WlSubcompositor;
|
|
||||||
use sctk::reexports::client::{Attached, DispatchData};
|
|
||||||
use sctk::reexports::client::protocol::wl_shm::WlShm;
|
|
||||||
use sctk::reexports::protocols::xdg_shell::client::xdg_wm_base::XdgWmBase;
|
|
||||||
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1;
|
|
||||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1;
|
|
||||||
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
|
|
||||||
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_v1::XdgActivationV1;
|
|
||||||
|
|
||||||
use sctk::environment::{Environment, SimpleGlobal};
|
|
||||||
use sctk::output::{OutputHandler, OutputHandling, OutputInfo, OutputStatusListener};
|
|
||||||
use sctk::seat::{SeatData, SeatHandler, SeatHandling, SeatListener};
|
|
||||||
use sctk::shell::{Shell, ShellHandler, ShellHandling};
|
|
||||||
use sctk::shm::ShmHandler;
|
|
||||||
|
|
||||||
/// Set of extra features that are supported by the compositor.
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct WindowingFeatures {
|
|
||||||
pointer_constraints: bool,
|
|
||||||
xdg_activation: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowingFeatures {
|
|
||||||
/// Create `WindowingFeatures` based on the presented interfaces.
|
|
||||||
pub fn new(env: &Environment<WinitEnv>) -> Self {
|
|
||||||
let pointer_constraints = env.get_global::<ZwpPointerConstraintsV1>().is_some();
|
|
||||||
let xdg_activation = env.get_global::<XdgActivationV1>().is_some();
|
|
||||||
Self {
|
|
||||||
pointer_constraints,
|
|
||||||
xdg_activation,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pointer_constraints(&self) -> bool {
|
|
||||||
self.pointer_constraints
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn xdg_activation(&self) -> bool {
|
|
||||||
self.xdg_activation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sctk::environment!(WinitEnv,
|
|
||||||
singles = [
|
|
||||||
WlShm => shm,
|
|
||||||
WlCompositor => compositor,
|
|
||||||
WlSubcompositor => subcompositor,
|
|
||||||
WlShell => shell,
|
|
||||||
XdgWmBase => shell,
|
|
||||||
ZxdgShellV6 => shell,
|
|
||||||
ZxdgDecorationManagerV1 => decoration_manager,
|
|
||||||
ZwpRelativePointerManagerV1 => relative_pointer_manager,
|
|
||||||
ZwpPointerConstraintsV1 => pointer_constraints,
|
|
||||||
ZwpTextInputManagerV3 => text_input_manager,
|
|
||||||
XdgActivationV1 => xdg_activation,
|
|
||||||
],
|
|
||||||
multis = [
|
|
||||||
WlSeat => seats,
|
|
||||||
WlOutput => outputs,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
/// The environment that we utilize.
|
|
||||||
pub struct WinitEnv {
|
|
||||||
seats: SeatHandler,
|
|
||||||
|
|
||||||
outputs: OutputHandler,
|
|
||||||
|
|
||||||
shm: ShmHandler,
|
|
||||||
|
|
||||||
compositor: SimpleGlobal<WlCompositor>,
|
|
||||||
|
|
||||||
subcompositor: SimpleGlobal<WlSubcompositor>,
|
|
||||||
|
|
||||||
shell: ShellHandler,
|
|
||||||
|
|
||||||
relative_pointer_manager: SimpleGlobal<ZwpRelativePointerManagerV1>,
|
|
||||||
|
|
||||||
pointer_constraints: SimpleGlobal<ZwpPointerConstraintsV1>,
|
|
||||||
|
|
||||||
text_input_manager: SimpleGlobal<ZwpTextInputManagerV3>,
|
|
||||||
|
|
||||||
decoration_manager: SimpleGlobal<ZxdgDecorationManagerV1>,
|
|
||||||
|
|
||||||
xdg_activation: SimpleGlobal<XdgActivationV1>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WinitEnv {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
// Output tracking for available_monitors, etc.
|
|
||||||
let outputs = OutputHandler::new();
|
|
||||||
|
|
||||||
// Keyboard/Pointer/Touch input.
|
|
||||||
let seats = SeatHandler::new();
|
|
||||||
|
|
||||||
// Essential globals.
|
|
||||||
let shm = ShmHandler::new();
|
|
||||||
let compositor = SimpleGlobal::new();
|
|
||||||
let subcompositor = SimpleGlobal::new();
|
|
||||||
|
|
||||||
// Gracefully handle shell picking, since SCTK automatically supports multiple
|
|
||||||
// backends.
|
|
||||||
let shell = ShellHandler::new();
|
|
||||||
|
|
||||||
// Server side decorations.
|
|
||||||
let decoration_manager = SimpleGlobal::new();
|
|
||||||
|
|
||||||
// Device events for pointer.
|
|
||||||
let relative_pointer_manager = SimpleGlobal::new();
|
|
||||||
|
|
||||||
// Pointer grab functionality.
|
|
||||||
let pointer_constraints = SimpleGlobal::new();
|
|
||||||
|
|
||||||
// IME handling.
|
|
||||||
let text_input_manager = SimpleGlobal::new();
|
|
||||||
|
|
||||||
// Surface activation.
|
|
||||||
let xdg_activation = SimpleGlobal::new();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
seats,
|
|
||||||
outputs,
|
|
||||||
shm,
|
|
||||||
compositor,
|
|
||||||
subcompositor,
|
|
||||||
shell,
|
|
||||||
decoration_manager,
|
|
||||||
relative_pointer_manager,
|
|
||||||
pointer_constraints,
|
|
||||||
text_input_manager,
|
|
||||||
xdg_activation,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ShellHandling for WinitEnv {
|
|
||||||
fn get_shell(&self) -> Option<Shell> {
|
|
||||||
self.shell.get_shell()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SeatHandling for WinitEnv {
|
|
||||||
fn listen<F: FnMut(Attached<WlSeat>, &SeatData, DispatchData<'_>) + 'static>(
|
|
||||||
&mut self,
|
|
||||||
f: F,
|
|
||||||
) -> SeatListener {
|
|
||||||
self.seats.listen(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OutputHandling for WinitEnv {
|
|
||||||
fn listen<F: FnMut(WlOutput, &OutputInfo, DispatchData<'_>) + 'static>(
|
|
||||||
&mut self,
|
|
||||||
f: F,
|
|
||||||
) -> OutputStatusListener {
|
|
||||||
self.outputs.listen(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,551 +0,0 @@
|
|||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::error::Error;
|
|
||||||
use std::io::Result as IOResult;
|
|
||||||
use std::process;
|
|
||||||
use std::rc::Rc;
|
|
||||||
use std::time::{Duration, Instant};
|
|
||||||
|
|
||||||
use raw_window_handle::{RawDisplayHandle, WaylandDisplayHandle};
|
|
||||||
|
|
||||||
use sctk::reexports::client::protocol::wl_compositor::WlCompositor;
|
|
||||||
use sctk::reexports::client::protocol::wl_shm::WlShm;
|
|
||||||
use sctk::reexports::client::Display;
|
|
||||||
|
|
||||||
use sctk::reexports::calloop;
|
|
||||||
|
|
||||||
use sctk::environment::Environment;
|
|
||||||
use sctk::seat::pointer::{ThemeManager, ThemeSpec};
|
|
||||||
use sctk::WaylandSource;
|
|
||||||
|
|
||||||
use crate::event::{Event, StartCause, WindowEvent};
|
|
||||||
use crate::event_loop::{ControlFlow, EventLoopWindowTarget as RootEventLoopWindowTarget};
|
|
||||||
use crate::platform_impl::platform::sticky_exit_callback;
|
|
||||||
use crate::platform_impl::EventLoopWindowTarget as PlatformEventLoopWindowTarget;
|
|
||||||
|
|
||||||
use super::env::{WindowingFeatures, WinitEnv};
|
|
||||||
use super::output::OutputManager;
|
|
||||||
use super::seat::SeatManager;
|
|
||||||
use super::window::shim::{self, WindowUpdate};
|
|
||||||
use super::{DeviceId, WindowId};
|
|
||||||
|
|
||||||
mod proxy;
|
|
||||||
mod sink;
|
|
||||||
mod state;
|
|
||||||
|
|
||||||
pub use proxy::EventLoopProxy;
|
|
||||||
pub use sink::EventSink;
|
|
||||||
pub use state::WinitState;
|
|
||||||
|
|
||||||
type WinitDispatcher = calloop::Dispatcher<'static, WaylandSource, WinitState>;
|
|
||||||
|
|
||||||
pub struct EventLoopWindowTarget<T> {
|
|
||||||
/// Wayland display.
|
|
||||||
pub display: Display,
|
|
||||||
|
|
||||||
/// Environment to handle object creation, etc.
|
|
||||||
pub env: Environment<WinitEnv>,
|
|
||||||
|
|
||||||
/// Event loop handle.
|
|
||||||
pub event_loop_handle: calloop::LoopHandle<'static, WinitState>,
|
|
||||||
|
|
||||||
/// Output manager.
|
|
||||||
pub output_manager: OutputManager,
|
|
||||||
|
|
||||||
/// State that we share across callbacks.
|
|
||||||
pub state: RefCell<WinitState>,
|
|
||||||
|
|
||||||
/// Dispatcher of Wayland events.
|
|
||||||
pub wayland_dispatcher: WinitDispatcher,
|
|
||||||
|
|
||||||
/// A proxy to wake up event loop.
|
|
||||||
pub event_loop_awakener: calloop::ping::Ping,
|
|
||||||
|
|
||||||
/// The available windowing features.
|
|
||||||
pub windowing_features: WindowingFeatures,
|
|
||||||
|
|
||||||
/// Theme manager to manage cursors.
|
|
||||||
///
|
|
||||||
/// It's being shared between all windows to avoid loading
|
|
||||||
/// multiple similar themes.
|
|
||||||
pub theme_manager: ThemeManager,
|
|
||||||
|
|
||||||
_marker: std::marker::PhantomData<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopWindowTarget<T> {
|
|
||||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
|
||||||
let mut display_handle = WaylandDisplayHandle::empty();
|
|
||||||
display_handle.display = self.display.get_display_ptr() as *mut _;
|
|
||||||
RawDisplayHandle::Wayland(display_handle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventLoop<T: 'static> {
|
|
||||||
/// Event loop.
|
|
||||||
event_loop: calloop::EventLoop<'static, WinitState>,
|
|
||||||
|
|
||||||
/// Wayland display.
|
|
||||||
display: Display,
|
|
||||||
|
|
||||||
/// Pending user events.
|
|
||||||
pending_user_events: Rc<RefCell<Vec<T>>>,
|
|
||||||
|
|
||||||
/// Sender of user events.
|
|
||||||
user_events_sender: calloop::channel::Sender<T>,
|
|
||||||
|
|
||||||
/// Dispatcher of Wayland events.
|
|
||||||
pub wayland_dispatcher: WinitDispatcher,
|
|
||||||
|
|
||||||
/// Window target.
|
|
||||||
window_target: RootEventLoopWindowTarget<T>,
|
|
||||||
|
|
||||||
/// Output manager.
|
|
||||||
_seat_manager: SeatManager,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> EventLoop<T> {
|
|
||||||
pub fn new() -> Result<EventLoop<T>, Box<dyn Error>> {
|
|
||||||
// Connect to wayland server and setup event queue.
|
|
||||||
let display = Display::connect_to_env()?;
|
|
||||||
let mut event_queue = display.create_event_queue();
|
|
||||||
let display_proxy = display.attach(event_queue.token());
|
|
||||||
|
|
||||||
// Setup environment.
|
|
||||||
let env = Environment::new(&display_proxy, &mut event_queue, WinitEnv::new())?;
|
|
||||||
|
|
||||||
// Create event loop.
|
|
||||||
let event_loop = calloop::EventLoop::<'static, WinitState>::try_new()?;
|
|
||||||
// Build windowing features.
|
|
||||||
let windowing_features = WindowingFeatures::new(&env);
|
|
||||||
|
|
||||||
// Create a theme manager.
|
|
||||||
let compositor = env.require_global::<WlCompositor>();
|
|
||||||
let shm = env.require_global::<WlShm>();
|
|
||||||
let theme_manager = ThemeManager::init(ThemeSpec::System, compositor, shm);
|
|
||||||
|
|
||||||
// Setup theme seat and output managers.
|
|
||||||
let seat_manager = SeatManager::new(&env, event_loop.handle(), theme_manager.clone());
|
|
||||||
let output_manager = OutputManager::new(&env);
|
|
||||||
|
|
||||||
// A source of events that we plug into our event loop.
|
|
||||||
let wayland_source = WaylandSource::new(event_queue);
|
|
||||||
let wayland_dispatcher =
|
|
||||||
calloop::Dispatcher::new(wayland_source, |_, queue, winit_state| {
|
|
||||||
queue.dispatch_pending(winit_state, |event, object, _| {
|
|
||||||
panic!(
|
|
||||||
"[calloop] Encountered an orphan event: {}@{} : {}",
|
|
||||||
event.interface,
|
|
||||||
object.as_ref().id(),
|
|
||||||
event.name
|
|
||||||
);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
let _wayland_source_dispatcher = event_loop
|
|
||||||
.handle()
|
|
||||||
.register_dispatcher(wayland_dispatcher.clone())?;
|
|
||||||
|
|
||||||
// A source of user events.
|
|
||||||
let pending_user_events = Rc::new(RefCell::new(Vec::new()));
|
|
||||||
let pending_user_events_clone = pending_user_events.clone();
|
|
||||||
let (user_events_sender, user_events_channel) = calloop::channel::channel();
|
|
||||||
|
|
||||||
// User events channel.
|
|
||||||
event_loop
|
|
||||||
.handle()
|
|
||||||
.insert_source(user_events_channel, move |event, _, _| {
|
|
||||||
if let calloop::channel::Event::Msg(msg) = event {
|
|
||||||
pending_user_events_clone.borrow_mut().push(msg);
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// An event's loop awakener to wake up for window events from winit's windows.
|
|
||||||
let (event_loop_awakener, event_loop_awakener_source) = calloop::ping::make_ping()?;
|
|
||||||
|
|
||||||
// Handler of window requests.
|
|
||||||
event_loop.handle().insert_source(
|
|
||||||
event_loop_awakener_source,
|
|
||||||
move |_, _, winit_state| {
|
|
||||||
shim::handle_window_requests(winit_state);
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let event_loop_handle = event_loop.handle();
|
|
||||||
let window_map = HashMap::new();
|
|
||||||
let event_sink = EventSink::new();
|
|
||||||
let window_updates = HashMap::new();
|
|
||||||
|
|
||||||
// Create event loop window target.
|
|
||||||
let event_loop_window_target = EventLoopWindowTarget {
|
|
||||||
display: display.clone(),
|
|
||||||
env,
|
|
||||||
state: RefCell::new(WinitState {
|
|
||||||
window_map,
|
|
||||||
event_sink,
|
|
||||||
window_updates,
|
|
||||||
}),
|
|
||||||
event_loop_handle,
|
|
||||||
output_manager,
|
|
||||||
event_loop_awakener,
|
|
||||||
wayland_dispatcher: wayland_dispatcher.clone(),
|
|
||||||
windowing_features,
|
|
||||||
theme_manager,
|
|
||||||
_marker: std::marker::PhantomData,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create event loop itself.
|
|
||||||
let event_loop = Self {
|
|
||||||
event_loop,
|
|
||||||
display,
|
|
||||||
pending_user_events,
|
|
||||||
wayland_dispatcher,
|
|
||||||
_seat_manager: seat_manager,
|
|
||||||
user_events_sender,
|
|
||||||
window_target: RootEventLoopWindowTarget {
|
|
||||||
p: PlatformEventLoopWindowTarget::Wayland(event_loop_window_target),
|
|
||||||
_marker: std::marker::PhantomData,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(event_loop)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run<F>(mut self, callback: F) -> !
|
|
||||||
where
|
|
||||||
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow) + 'static,
|
|
||||||
{
|
|
||||||
let exit_code = self.run_return(callback);
|
|
||||||
process::exit(exit_code);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_return<F>(&mut self, mut callback: F) -> i32
|
|
||||||
where
|
|
||||||
F: FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
|
|
||||||
{
|
|
||||||
let mut control_flow = ControlFlow::Poll;
|
|
||||||
let pending_user_events = self.pending_user_events.clone();
|
|
||||||
|
|
||||||
callback(
|
|
||||||
Event::NewEvents(StartCause::Init),
|
|
||||||
&self.window_target,
|
|
||||||
&mut control_flow,
|
|
||||||
);
|
|
||||||
|
|
||||||
// NB: For consistency all platforms must emit a 'resumed' event even though Wayland
|
|
||||||
// applications don't themselves have a formal suspend/resume lifecycle.
|
|
||||||
callback(Event::Resumed, &self.window_target, &mut control_flow);
|
|
||||||
|
|
||||||
let mut window_updates: Vec<(WindowId, WindowUpdate)> = Vec::new();
|
|
||||||
let mut event_sink_back_buffer = Vec::new();
|
|
||||||
|
|
||||||
// NOTE We break on errors from dispatches, since if we've got protocol error
|
|
||||||
// libwayland-client/wayland-rs will inform us anyway, but crashing downstream is not
|
|
||||||
// really an option. Instead we inform that the event loop got destroyed. We may
|
|
||||||
// communicate an error that something was terminated, but winit doesn't provide us
|
|
||||||
// with an API to do that via some event.
|
|
||||||
// Still, we set the exit code to the error's OS error code, or to 1 if not possible.
|
|
||||||
let exit_code = loop {
|
|
||||||
// Send pending events to the server.
|
|
||||||
let _ = self.display.flush();
|
|
||||||
|
|
||||||
// During the run of the user callback, some other code monitoring and reading the
|
|
||||||
// Wayland socket may have been run (mesa for example does this with vsync), if that
|
|
||||||
// is the case, some events may have been enqueued in our event queue.
|
|
||||||
//
|
|
||||||
// If some messages are there, the event loop needs to behave as if it was instantly
|
|
||||||
// woken up by messages arriving from the Wayland socket, to avoid delaying the
|
|
||||||
// dispatch of these events until we're woken up again.
|
|
||||||
let instant_wakeup = {
|
|
||||||
let mut wayland_source = self.wayland_dispatcher.as_source_mut();
|
|
||||||
let queue = wayland_source.queue();
|
|
||||||
let state = match &mut self.window_target.p {
|
|
||||||
PlatformEventLoopWindowTarget::Wayland(window_target) => {
|
|
||||||
window_target.state.get_mut()
|
|
||||||
}
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
match queue.dispatch_pending(state, |_, _, _| unimplemented!()) {
|
|
||||||
Ok(dispatched) => dispatched > 0,
|
|
||||||
Err(error) => break error.raw_os_error().unwrap_or(1),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match control_flow {
|
|
||||||
ControlFlow::ExitWithCode(code) => break code,
|
|
||||||
ControlFlow::Poll => {
|
|
||||||
// Non-blocking dispatch.
|
|
||||||
let timeout = Duration::from_millis(0);
|
|
||||||
if let Err(error) = self.loop_dispatch(Some(timeout)) {
|
|
||||||
break error.raw_os_error().unwrap_or(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(
|
|
||||||
Event::NewEvents(StartCause::Poll),
|
|
||||||
&self.window_target,
|
|
||||||
&mut control_flow,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
ControlFlow::Wait => {
|
|
||||||
let timeout = if instant_wakeup {
|
|
||||||
Some(Duration::from_millis(0))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(error) = self.loop_dispatch(timeout) {
|
|
||||||
break error.raw_os_error().unwrap_or(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(
|
|
||||||
Event::NewEvents(StartCause::WaitCancelled {
|
|
||||||
start: Instant::now(),
|
|
||||||
requested_resume: None,
|
|
||||||
}),
|
|
||||||
&self.window_target,
|
|
||||||
&mut control_flow,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
ControlFlow::WaitUntil(deadline) => {
|
|
||||||
let start = Instant::now();
|
|
||||||
|
|
||||||
// Compute the amount of time we'll block for.
|
|
||||||
let duration = if deadline > start && !instant_wakeup {
|
|
||||||
deadline - start
|
|
||||||
} else {
|
|
||||||
Duration::from_millis(0)
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(error) = self.loop_dispatch(Some(duration)) {
|
|
||||||
break error.raw_os_error().unwrap_or(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let now = Instant::now();
|
|
||||||
|
|
||||||
if now < deadline {
|
|
||||||
callback(
|
|
||||||
Event::NewEvents(StartCause::WaitCancelled {
|
|
||||||
start,
|
|
||||||
requested_resume: Some(deadline),
|
|
||||||
}),
|
|
||||||
&self.window_target,
|
|
||||||
&mut control_flow,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
callback(
|
|
||||||
Event::NewEvents(StartCause::ResumeTimeReached {
|
|
||||||
start,
|
|
||||||
requested_resume: deadline,
|
|
||||||
}),
|
|
||||||
&self.window_target,
|
|
||||||
&mut control_flow,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle pending user events. We don't need back buffer, since we can't dispatch
|
|
||||||
// user events indirectly via callback to the user.
|
|
||||||
for user_event in pending_user_events.borrow_mut().drain(..) {
|
|
||||||
sticky_exit_callback(
|
|
||||||
Event::UserEvent(user_event),
|
|
||||||
&self.window_target,
|
|
||||||
&mut control_flow,
|
|
||||||
&mut callback,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process 'new' pending updates.
|
|
||||||
self.with_state(|state| {
|
|
||||||
window_updates.clear();
|
|
||||||
window_updates.extend(
|
|
||||||
state
|
|
||||||
.window_updates
|
|
||||||
.iter_mut()
|
|
||||||
.map(|(wid, window_update)| (*wid, window_update.take())),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
for (window_id, window_update) in window_updates.iter_mut() {
|
|
||||||
if let Some(scale_factor) = window_update.scale_factor.map(|f| f as f64) {
|
|
||||||
let mut physical_size = self.with_state(|state| {
|
|
||||||
let window_handle = state.window_map.get(window_id).unwrap();
|
|
||||||
let mut size = window_handle.size.lock().unwrap();
|
|
||||||
|
|
||||||
// Update the new logical size if it was changed.
|
|
||||||
let window_size = window_update.size.unwrap_or(*size);
|
|
||||||
*size = window_size;
|
|
||||||
|
|
||||||
window_size.to_physical(scale_factor)
|
|
||||||
});
|
|
||||||
|
|
||||||
sticky_exit_callback(
|
|
||||||
Event::WindowEvent {
|
|
||||||
window_id: crate::window::WindowId(*window_id),
|
|
||||||
event: WindowEvent::ScaleFactorChanged {
|
|
||||||
scale_factor,
|
|
||||||
new_inner_size: &mut physical_size,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
&self.window_target,
|
|
||||||
&mut control_flow,
|
|
||||||
&mut callback,
|
|
||||||
);
|
|
||||||
|
|
||||||
// We don't update size on a window handle since we'll do that later
|
|
||||||
// when handling size update.
|
|
||||||
let new_logical_size = physical_size.to_logical(scale_factor);
|
|
||||||
window_update.size = Some(new_logical_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(size) = window_update.size.take() {
|
|
||||||
let physical_size = self.with_state(|state| {
|
|
||||||
let window_handle = state.window_map.get_mut(window_id).unwrap();
|
|
||||||
let mut window_size = window_handle.size.lock().unwrap();
|
|
||||||
|
|
||||||
// Always issue resize event on scale factor change.
|
|
||||||
let physical_size =
|
|
||||||
if window_update.scale_factor.is_none() && *window_size == size {
|
|
||||||
// The size hasn't changed, don't inform downstream about that.
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
*window_size = size;
|
|
||||||
let scale_factor =
|
|
||||||
sctk::get_surface_scale_factor(window_handle.window.surface());
|
|
||||||
let physical_size = size.to_physical(scale_factor as f64);
|
|
||||||
Some(physical_size)
|
|
||||||
};
|
|
||||||
|
|
||||||
// We still perform all of those resize related logic even if the size
|
|
||||||
// hasn't changed, since GNOME relies on `set_geometry` calls after
|
|
||||||
// configures.
|
|
||||||
window_handle.window.resize(size.width, size.height);
|
|
||||||
window_handle.window.refresh();
|
|
||||||
|
|
||||||
// Mark that refresh isn't required, since we've done it right now.
|
|
||||||
window_update.refresh_frame = false;
|
|
||||||
|
|
||||||
physical_size
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(physical_size) = physical_size {
|
|
||||||
sticky_exit_callback(
|
|
||||||
Event::WindowEvent {
|
|
||||||
window_id: crate::window::WindowId(*window_id),
|
|
||||||
event: WindowEvent::Resized(physical_size),
|
|
||||||
},
|
|
||||||
&self.window_target,
|
|
||||||
&mut control_flow,
|
|
||||||
&mut callback,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if window_update.close_window {
|
|
||||||
sticky_exit_callback(
|
|
||||||
Event::WindowEvent {
|
|
||||||
window_id: crate::window::WindowId(*window_id),
|
|
||||||
event: WindowEvent::CloseRequested,
|
|
||||||
},
|
|
||||||
&self.window_target,
|
|
||||||
&mut control_flow,
|
|
||||||
&mut callback,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The purpose of the back buffer and that swap is to not hold borrow_mut when
|
|
||||||
// we're doing callback to the user, since we can double borrow if the user decides
|
|
||||||
// to create a window in one of those callbacks.
|
|
||||||
self.with_state(|state| {
|
|
||||||
std::mem::swap(
|
|
||||||
&mut event_sink_back_buffer,
|
|
||||||
&mut state.event_sink.window_events,
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle pending window events.
|
|
||||||
for event in event_sink_back_buffer.drain(..) {
|
|
||||||
let event = event.map_nonuser_event().unwrap();
|
|
||||||
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send events cleared.
|
|
||||||
sticky_exit_callback(
|
|
||||||
Event::MainEventsCleared,
|
|
||||||
&self.window_target,
|
|
||||||
&mut control_flow,
|
|
||||||
&mut callback,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handle RedrawRequested events.
|
|
||||||
for (window_id, window_update) in window_updates.iter() {
|
|
||||||
// Handle refresh of the frame.
|
|
||||||
if window_update.refresh_frame {
|
|
||||||
self.with_state(|state| {
|
|
||||||
let window_handle = state.window_map.get_mut(window_id).unwrap();
|
|
||||||
window_handle.window.refresh();
|
|
||||||
if !window_update.redraw_requested {
|
|
||||||
window_handle.window.surface().commit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle redraw request.
|
|
||||||
if window_update.redraw_requested {
|
|
||||||
sticky_exit_callback(
|
|
||||||
Event::RedrawRequested(crate::window::WindowId(*window_id)),
|
|
||||||
&self.window_target,
|
|
||||||
&mut control_flow,
|
|
||||||
&mut callback,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send RedrawEventCleared.
|
|
||||||
sticky_exit_callback(
|
|
||||||
Event::RedrawEventsCleared,
|
|
||||||
&self.window_target,
|
|
||||||
&mut control_flow,
|
|
||||||
&mut callback,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
callback(Event::LoopDestroyed, &self.window_target, &mut control_flow);
|
|
||||||
exit_code
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
|
||||||
EventLoopProxy::new(self.user_events_sender.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn window_target(&self) -> &RootEventLoopWindowTarget<T> {
|
|
||||||
&self.window_target
|
|
||||||
}
|
|
||||||
|
|
||||||
fn with_state<U, F: FnOnce(&mut WinitState) -> U>(&mut self, f: F) -> U {
|
|
||||||
let state = match &mut self.window_target.p {
|
|
||||||
PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(),
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
f(state)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn loop_dispatch<D: Into<Option<std::time::Duration>>>(&mut self, timeout: D) -> IOResult<()> {
|
|
||||||
let state = match &mut self.window_target.p {
|
|
||||||
PlatformEventLoopWindowTarget::Wayland(window_target) => window_target.state.get_mut(),
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.event_loop
|
|
||||||
.dispatch(timeout, state)
|
|
||||||
.map_err(|error| error.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
//! An event loop's sink to deliver events from the Wayland event callbacks.
|
|
||||||
|
|
||||||
use crate::event::{DeviceEvent, DeviceId as RootDeviceId, Event, WindowEvent};
|
|
||||||
use crate::platform_impl::platform::DeviceId as PlatformDeviceId;
|
|
||||||
use crate::window::WindowId as RootWindowId;
|
|
||||||
|
|
||||||
use super::{DeviceId, WindowId};
|
|
||||||
|
|
||||||
/// An event loop's sink to deliver events from the Wayland event callbacks
|
|
||||||
/// to the winit's user.
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct EventSink {
|
|
||||||
pub window_events: Vec<Event<'static, ()>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventSink {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add new device event to a queue.
|
|
||||||
pub fn push_device_event(&mut self, event: DeviceEvent, device_id: DeviceId) {
|
|
||||||
self.window_events.push(Event::DeviceEvent {
|
|
||||||
event,
|
|
||||||
device_id: RootDeviceId(PlatformDeviceId::Wayland(device_id)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add new window event to a queue.
|
|
||||||
pub fn push_window_event(&mut self, event: WindowEvent<'static>, window_id: WindowId) {
|
|
||||||
self.window_events.push(Event::WindowEvent {
|
|
||||||
event,
|
|
||||||
window_id: RootWindowId(window_id),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
//! A state that we pass around in a dispatch.
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use super::EventSink;
|
|
||||||
use crate::platform_impl::wayland::window::shim::{WindowHandle, WindowUpdate};
|
|
||||||
use crate::platform_impl::wayland::WindowId;
|
|
||||||
|
|
||||||
/// Wrapper to carry winit's state.
|
|
||||||
pub struct WinitState {
|
|
||||||
/// A sink for window and device events that is being filled during dispatching
|
|
||||||
/// event loop and forwarded downstream afterwards.
|
|
||||||
pub event_sink: EventSink,
|
|
||||||
|
|
||||||
/// Window updates, which are coming from SCTK or the compositor, which require
|
|
||||||
/// calling back to the winit's downstream. They are handled right in the event loop,
|
|
||||||
/// unlike the ones coming from buffers on the `WindowHandle`'s.
|
|
||||||
pub window_updates: HashMap<WindowId, WindowUpdate>,
|
|
||||||
|
|
||||||
/// Window map containing all SCTK windows. Since those windows aren't allowed
|
|
||||||
/// to be sent to other threads, they live on the event loop's thread
|
|
||||||
/// and requests from winit's windows are being forwarded to them either via
|
|
||||||
/// `WindowUpdate` or buffer on the associated with it `WindowHandle`.
|
|
||||||
pub window_map: HashMap<WindowId, WindowHandle>,
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
#![cfg(any(
|
|
||||||
target_os = "linux",
|
|
||||||
target_os = "dragonfly",
|
|
||||||
target_os = "freebsd",
|
|
||||||
target_os = "netbsd",
|
|
||||||
target_os = "openbsd"
|
|
||||||
))]
|
|
||||||
|
|
||||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
|
||||||
|
|
||||||
pub use crate::platform_impl::platform::WindowId;
|
|
||||||
pub use event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget};
|
|
||||||
pub use output::{MonitorHandle, VideoMode};
|
|
||||||
pub use window::Window;
|
|
||||||
|
|
||||||
mod env;
|
|
||||||
mod event_loop;
|
|
||||||
mod output;
|
|
||||||
mod seat;
|
|
||||||
mod window;
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct DeviceId;
|
|
||||||
|
|
||||||
impl DeviceId {
|
|
||||||
pub const unsafe fn dummy() -> Self {
|
|
||||||
DeviceId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn make_wid(surface: &WlSurface) -> WindowId {
|
|
||||||
WindowId(surface.as_ref().c_ptr() as u64)
|
|
||||||
}
|
|
||||||
@@ -1,250 +0,0 @@
|
|||||||
use std::collections::VecDeque;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use sctk::reexports::client::protocol::wl_output::WlOutput;
|
|
||||||
use sctk::reexports::client::Display;
|
|
||||||
|
|
||||||
use sctk::environment::Environment;
|
|
||||||
use sctk::output::OutputStatusListener;
|
|
||||||
|
|
||||||
use crate::dpi::{PhysicalPosition, PhysicalSize};
|
|
||||||
use crate::monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode};
|
|
||||||
use crate::platform_impl::platform::{
|
|
||||||
MonitorHandle as PlatformMonitorHandle, VideoMode as PlatformVideoMode,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::env::WinitEnv;
|
|
||||||
use super::event_loop::EventLoopWindowTarget;
|
|
||||||
|
|
||||||
/// Output manager.
|
|
||||||
pub struct OutputManager {
|
|
||||||
/// A handle that actually performs all operations on outputs.
|
|
||||||
handle: OutputManagerHandle,
|
|
||||||
|
|
||||||
_output_listener: OutputStatusListener,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OutputManager {
|
|
||||||
pub fn new(env: &Environment<WinitEnv>) -> Self {
|
|
||||||
let handle = OutputManagerHandle::new();
|
|
||||||
|
|
||||||
// Handle existing outputs.
|
|
||||||
for output in env.get_all_outputs() {
|
|
||||||
match sctk::output::with_output_info(&output, |info| info.obsolete) {
|
|
||||||
Some(false) => (),
|
|
||||||
// The output is obsolete or we've failed to access its data, skipping.
|
|
||||||
_ => continue,
|
|
||||||
}
|
|
||||||
|
|
||||||
// The output is present and unusable, add it to the output manager manager.
|
|
||||||
handle.add_output(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
let handle_for_listener = handle.clone();
|
|
||||||
|
|
||||||
let output_listener = env.listen_for_outputs(move |output, info, _| {
|
|
||||||
if info.obsolete {
|
|
||||||
handle_for_listener.remove_output(output)
|
|
||||||
} else {
|
|
||||||
handle_for_listener.add_output(output)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Self {
|
|
||||||
handle,
|
|
||||||
_output_listener: output_listener,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle(&self) -> OutputManagerHandle {
|
|
||||||
self.handle.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A handle to output manager.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct OutputManagerHandle {
|
|
||||||
outputs: Arc<Mutex<VecDeque<MonitorHandle>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OutputManagerHandle {
|
|
||||||
fn new() -> Self {
|
|
||||||
let outputs = Arc::new(Mutex::new(VecDeque::new()));
|
|
||||||
Self { outputs }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle addition of the output.
|
|
||||||
fn add_output(&self, output: WlOutput) {
|
|
||||||
let mut outputs = self.outputs.lock().unwrap();
|
|
||||||
let position = outputs.iter().position(|handle| handle.proxy == output);
|
|
||||||
if position.is_none() {
|
|
||||||
outputs.push_back(MonitorHandle::new(output));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle removal of the output.
|
|
||||||
fn remove_output(&self, output: WlOutput) {
|
|
||||||
let mut outputs = self.outputs.lock().unwrap();
|
|
||||||
let position = outputs.iter().position(|handle| handle.proxy == output);
|
|
||||||
if let Some(position) = position {
|
|
||||||
outputs.remove(position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get all observed outputs.
|
|
||||||
pub fn available_outputs(&self) -> VecDeque<MonitorHandle> {
|
|
||||||
self.outputs.lock().unwrap().clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct MonitorHandle {
|
|
||||||
pub(crate) proxy: WlOutput,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for MonitorHandle {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.native_identifier() == other.native_identifier()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for MonitorHandle {}
|
|
||||||
|
|
||||||
impl PartialOrd for MonitorHandle {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for MonitorHandle {
|
|
||||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
||||||
self.native_identifier().cmp(&other.native_identifier())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::hash::Hash for MonitorHandle {
|
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
self.native_identifier().hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MonitorHandle {
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn new(proxy: WlOutput) -> Self {
|
|
||||||
Self { proxy }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn name(&self) -> Option<String> {
|
|
||||||
sctk::output::with_output_info(&self.proxy, |info| {
|
|
||||||
format!("{} ({})", info.model, info.make)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn native_identifier(&self) -> u32 {
|
|
||||||
sctk::output::with_output_info(&self.proxy, |info| info.id).unwrap_or(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn size(&self) -> PhysicalSize<u32> {
|
|
||||||
match sctk::output::with_output_info(&self.proxy, |info| {
|
|
||||||
info.modes
|
|
||||||
.iter()
|
|
||||||
.find(|mode| mode.is_current)
|
|
||||||
.map(|mode| mode.dimensions)
|
|
||||||
}) {
|
|
||||||
Some(Some((w, h))) => (w as u32, h as u32),
|
|
||||||
_ => (0, 0),
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn position(&self) -> PhysicalPosition<i32> {
|
|
||||||
sctk::output::with_output_info(&self.proxy, |info| info.location)
|
|
||||||
.unwrap_or((0, 0))
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
|
||||||
sctk::output::with_output_info(&self.proxy, |info| {
|
|
||||||
info.modes
|
|
||||||
.iter()
|
|
||||||
.find_map(|mode| mode.is_current.then(|| mode.refresh_rate as u32))
|
|
||||||
})
|
|
||||||
.flatten()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn scale_factor(&self) -> i32 {
|
|
||||||
sctk::output::with_output_info(&self.proxy, |info| info.scale_factor).unwrap_or(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
|
|
||||||
let modes = sctk::output::with_output_info(&self.proxy, |info| info.modes.clone())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
let monitor = self.clone();
|
|
||||||
|
|
||||||
modes.into_iter().map(move |mode| RootVideoMode {
|
|
||||||
video_mode: PlatformVideoMode::Wayland(VideoMode {
|
|
||||||
size: (mode.dimensions.0 as u32, mode.dimensions.1 as u32).into(),
|
|
||||||
refresh_rate_millihertz: mode.refresh_rate as u32,
|
|
||||||
bit_depth: 32,
|
|
||||||
monitor: monitor.clone(),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub struct VideoMode {
|
|
||||||
pub(crate) size: PhysicalSize<u32>,
|
|
||||||
pub(crate) bit_depth: u16,
|
|
||||||
pub(crate) refresh_rate_millihertz: u32,
|
|
||||||
pub(crate) monitor: MonitorHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VideoMode {
|
|
||||||
#[inline]
|
|
||||||
pub fn size(&self) -> PhysicalSize<u32> {
|
|
||||||
self.size
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn bit_depth(&self) -> u16 {
|
|
||||||
self.bit_depth
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn refresh_rate_millihertz(&self) -> u32 {
|
|
||||||
self.refresh_rate_millihertz
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn monitor(&self) -> RootMonitorHandle {
|
|
||||||
RootMonitorHandle {
|
|
||||||
inner: PlatformMonitorHandle::Wayland(self.monitor.clone()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopWindowTarget<T> {
|
|
||||||
#[inline]
|
|
||||||
pub fn display(&self) -> &Display {
|
|
||||||
&self.display
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
|
||||||
self.output_manager.handle.available_outputs()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
|
|
||||||
// There's no primary monitor on Wayland.
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
//! Handling of various keyboard events.
|
|
||||||
|
|
||||||
use sctk::reexports::client::protocol::wl_keyboard::KeyState;
|
|
||||||
|
|
||||||
use sctk::seat::keyboard::Event as KeyboardEvent;
|
|
||||||
|
|
||||||
use crate::event::{ElementState, KeyboardInput, ModifiersState, WindowEvent};
|
|
||||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
|
||||||
use crate::platform_impl::wayland::{self, DeviceId};
|
|
||||||
|
|
||||||
use super::keymap;
|
|
||||||
use super::KeyboardInner;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(super) fn handle_keyboard(
|
|
||||||
event: KeyboardEvent<'_>,
|
|
||||||
inner: &mut KeyboardInner,
|
|
||||||
winit_state: &mut WinitState,
|
|
||||||
) {
|
|
||||||
let event_sink = &mut winit_state.event_sink;
|
|
||||||
match event {
|
|
||||||
KeyboardEvent::Enter { surface, .. } => {
|
|
||||||
let window_id = wayland::make_wid(&surface);
|
|
||||||
|
|
||||||
// Window gained focus.
|
|
||||||
event_sink.push_window_event(WindowEvent::Focused(true), window_id);
|
|
||||||
|
|
||||||
// Dispatch modifers changes that we've received before getting `Enter` event.
|
|
||||||
if let Some(modifiers) = inner.pending_modifers_state.take() {
|
|
||||||
*inner.modifiers_state.borrow_mut() = modifiers;
|
|
||||||
event_sink.push_window_event(WindowEvent::ModifiersChanged(modifiers), window_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
inner.target_window_id = Some(window_id);
|
|
||||||
}
|
|
||||||
KeyboardEvent::Leave { surface, .. } => {
|
|
||||||
let window_id = wayland::make_wid(&surface);
|
|
||||||
|
|
||||||
// Notify that no modifiers are being pressed.
|
|
||||||
if !inner.modifiers_state.borrow().is_empty() {
|
|
||||||
event_sink.push_window_event(
|
|
||||||
WindowEvent::ModifiersChanged(ModifiersState::empty()),
|
|
||||||
window_id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Window lost focus.
|
|
||||||
event_sink.push_window_event(WindowEvent::Focused(false), window_id);
|
|
||||||
|
|
||||||
// Reset the id.
|
|
||||||
inner.target_window_id = None;
|
|
||||||
}
|
|
||||||
KeyboardEvent::Key {
|
|
||||||
rawkey,
|
|
||||||
keysym,
|
|
||||||
state,
|
|
||||||
utf8,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let window_id = match inner.target_window_id {
|
|
||||||
Some(window_id) => window_id,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let state = match state {
|
|
||||||
KeyState::Pressed => ElementState::Pressed,
|
|
||||||
KeyState::Released => ElementState::Released,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let virtual_keycode = keymap::keysym_to_vkey(keysym);
|
|
||||||
|
|
||||||
event_sink.push_window_event(
|
|
||||||
#[allow(deprecated)]
|
|
||||||
WindowEvent::KeyboardInput {
|
|
||||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
|
||||||
DeviceId,
|
|
||||||
)),
|
|
||||||
input: KeyboardInput {
|
|
||||||
state,
|
|
||||||
scancode: rawkey,
|
|
||||||
virtual_keycode,
|
|
||||||
modifiers: *inner.modifiers_state.borrow(),
|
|
||||||
},
|
|
||||||
is_synthetic: false,
|
|
||||||
},
|
|
||||||
window_id,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Send ReceivedCharacter event only on ElementState::Pressed.
|
|
||||||
if ElementState::Released == state {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(txt) = utf8 {
|
|
||||||
for ch in txt.chars() {
|
|
||||||
event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyboardEvent::Repeat {
|
|
||||||
rawkey,
|
|
||||||
keysym,
|
|
||||||
utf8,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let window_id = match inner.target_window_id {
|
|
||||||
Some(window_id) => window_id,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let virtual_keycode = keymap::keysym_to_vkey(keysym);
|
|
||||||
|
|
||||||
event_sink.push_window_event(
|
|
||||||
#[allow(deprecated)]
|
|
||||||
WindowEvent::KeyboardInput {
|
|
||||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
|
||||||
DeviceId,
|
|
||||||
)),
|
|
||||||
input: KeyboardInput {
|
|
||||||
state: ElementState::Pressed,
|
|
||||||
scancode: rawkey,
|
|
||||||
virtual_keycode,
|
|
||||||
modifiers: *inner.modifiers_state.borrow(),
|
|
||||||
},
|
|
||||||
is_synthetic: false,
|
|
||||||
},
|
|
||||||
window_id,
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(txt) = utf8 {
|
|
||||||
for ch in txt.chars() {
|
|
||||||
event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyboardEvent::Modifiers { modifiers } => {
|
|
||||||
let modifiers = ModifiersState::from(modifiers);
|
|
||||||
if let Some(window_id) = inner.target_window_id {
|
|
||||||
*inner.modifiers_state.borrow_mut() = modifiers;
|
|
||||||
|
|
||||||
event_sink.push_window_event(WindowEvent::ModifiersChanged(modifiers), window_id);
|
|
||||||
} else {
|
|
||||||
// Compositor must send modifiers after wl_keyboard::enter, however certain
|
|
||||||
// compositors are still sending it before, so stash such events and send
|
|
||||||
// them on wl_keyboard::enter.
|
|
||||||
inner.pending_modifers_state = Some(modifiers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
//! Convert Wayland keys to winit keys.
|
|
||||||
|
|
||||||
use crate::event::VirtualKeyCode;
|
|
||||||
|
|
||||||
pub fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
|
|
||||||
use sctk::seat::keyboard::keysyms;
|
|
||||||
match keysym {
|
|
||||||
// Numbers.
|
|
||||||
keysyms::XKB_KEY_1 => Some(VirtualKeyCode::Key1),
|
|
||||||
keysyms::XKB_KEY_2 => Some(VirtualKeyCode::Key2),
|
|
||||||
keysyms::XKB_KEY_3 => Some(VirtualKeyCode::Key3),
|
|
||||||
keysyms::XKB_KEY_4 => Some(VirtualKeyCode::Key4),
|
|
||||||
keysyms::XKB_KEY_5 => Some(VirtualKeyCode::Key5),
|
|
||||||
keysyms::XKB_KEY_6 => Some(VirtualKeyCode::Key6),
|
|
||||||
keysyms::XKB_KEY_7 => Some(VirtualKeyCode::Key7),
|
|
||||||
keysyms::XKB_KEY_8 => Some(VirtualKeyCode::Key8),
|
|
||||||
keysyms::XKB_KEY_9 => Some(VirtualKeyCode::Key9),
|
|
||||||
keysyms::XKB_KEY_0 => Some(VirtualKeyCode::Key0),
|
|
||||||
// Letters.
|
|
||||||
keysyms::XKB_KEY_A | keysyms::XKB_KEY_a => Some(VirtualKeyCode::A),
|
|
||||||
keysyms::XKB_KEY_B | keysyms::XKB_KEY_b => Some(VirtualKeyCode::B),
|
|
||||||
keysyms::XKB_KEY_C | keysyms::XKB_KEY_c => Some(VirtualKeyCode::C),
|
|
||||||
keysyms::XKB_KEY_D | keysyms::XKB_KEY_d => Some(VirtualKeyCode::D),
|
|
||||||
keysyms::XKB_KEY_E | keysyms::XKB_KEY_e => Some(VirtualKeyCode::E),
|
|
||||||
keysyms::XKB_KEY_F | keysyms::XKB_KEY_f => Some(VirtualKeyCode::F),
|
|
||||||
keysyms::XKB_KEY_G | keysyms::XKB_KEY_g => Some(VirtualKeyCode::G),
|
|
||||||
keysyms::XKB_KEY_H | keysyms::XKB_KEY_h => Some(VirtualKeyCode::H),
|
|
||||||
keysyms::XKB_KEY_I | keysyms::XKB_KEY_i => Some(VirtualKeyCode::I),
|
|
||||||
keysyms::XKB_KEY_J | keysyms::XKB_KEY_j => Some(VirtualKeyCode::J),
|
|
||||||
keysyms::XKB_KEY_K | keysyms::XKB_KEY_k => Some(VirtualKeyCode::K),
|
|
||||||
keysyms::XKB_KEY_L | keysyms::XKB_KEY_l => Some(VirtualKeyCode::L),
|
|
||||||
keysyms::XKB_KEY_M | keysyms::XKB_KEY_m => Some(VirtualKeyCode::M),
|
|
||||||
keysyms::XKB_KEY_N | keysyms::XKB_KEY_n => Some(VirtualKeyCode::N),
|
|
||||||
keysyms::XKB_KEY_O | keysyms::XKB_KEY_o => Some(VirtualKeyCode::O),
|
|
||||||
keysyms::XKB_KEY_P | keysyms::XKB_KEY_p => Some(VirtualKeyCode::P),
|
|
||||||
keysyms::XKB_KEY_Q | keysyms::XKB_KEY_q => Some(VirtualKeyCode::Q),
|
|
||||||
keysyms::XKB_KEY_R | keysyms::XKB_KEY_r => Some(VirtualKeyCode::R),
|
|
||||||
keysyms::XKB_KEY_S | keysyms::XKB_KEY_s => Some(VirtualKeyCode::S),
|
|
||||||
keysyms::XKB_KEY_T | keysyms::XKB_KEY_t => Some(VirtualKeyCode::T),
|
|
||||||
keysyms::XKB_KEY_U | keysyms::XKB_KEY_u => Some(VirtualKeyCode::U),
|
|
||||||
keysyms::XKB_KEY_V | keysyms::XKB_KEY_v => Some(VirtualKeyCode::V),
|
|
||||||
keysyms::XKB_KEY_W | keysyms::XKB_KEY_w => Some(VirtualKeyCode::W),
|
|
||||||
keysyms::XKB_KEY_X | keysyms::XKB_KEY_x => Some(VirtualKeyCode::X),
|
|
||||||
keysyms::XKB_KEY_Y | keysyms::XKB_KEY_y => Some(VirtualKeyCode::Y),
|
|
||||||
keysyms::XKB_KEY_Z | keysyms::XKB_KEY_z => Some(VirtualKeyCode::Z),
|
|
||||||
// Escape.
|
|
||||||
keysyms::XKB_KEY_Escape => Some(VirtualKeyCode::Escape),
|
|
||||||
// Function keys.
|
|
||||||
keysyms::XKB_KEY_F1 => Some(VirtualKeyCode::F1),
|
|
||||||
keysyms::XKB_KEY_F2 => Some(VirtualKeyCode::F2),
|
|
||||||
keysyms::XKB_KEY_F3 => Some(VirtualKeyCode::F3),
|
|
||||||
keysyms::XKB_KEY_F4 => Some(VirtualKeyCode::F4),
|
|
||||||
keysyms::XKB_KEY_F5 => Some(VirtualKeyCode::F5),
|
|
||||||
keysyms::XKB_KEY_F6 => Some(VirtualKeyCode::F6),
|
|
||||||
keysyms::XKB_KEY_F7 => Some(VirtualKeyCode::F7),
|
|
||||||
keysyms::XKB_KEY_F8 => Some(VirtualKeyCode::F8),
|
|
||||||
keysyms::XKB_KEY_F9 => Some(VirtualKeyCode::F9),
|
|
||||||
keysyms::XKB_KEY_F10 => Some(VirtualKeyCode::F10),
|
|
||||||
keysyms::XKB_KEY_F11 => Some(VirtualKeyCode::F11),
|
|
||||||
keysyms::XKB_KEY_F12 => Some(VirtualKeyCode::F12),
|
|
||||||
keysyms::XKB_KEY_F13 => Some(VirtualKeyCode::F13),
|
|
||||||
keysyms::XKB_KEY_F14 => Some(VirtualKeyCode::F14),
|
|
||||||
keysyms::XKB_KEY_F15 => Some(VirtualKeyCode::F15),
|
|
||||||
keysyms::XKB_KEY_F16 => Some(VirtualKeyCode::F16),
|
|
||||||
keysyms::XKB_KEY_F17 => Some(VirtualKeyCode::F17),
|
|
||||||
keysyms::XKB_KEY_F18 => Some(VirtualKeyCode::F18),
|
|
||||||
keysyms::XKB_KEY_F19 => Some(VirtualKeyCode::F19),
|
|
||||||
keysyms::XKB_KEY_F20 => Some(VirtualKeyCode::F20),
|
|
||||||
keysyms::XKB_KEY_F21 => Some(VirtualKeyCode::F21),
|
|
||||||
keysyms::XKB_KEY_F22 => Some(VirtualKeyCode::F22),
|
|
||||||
keysyms::XKB_KEY_F23 => Some(VirtualKeyCode::F23),
|
|
||||||
keysyms::XKB_KEY_F24 => Some(VirtualKeyCode::F24),
|
|
||||||
// Flow control.
|
|
||||||
keysyms::XKB_KEY_Print => Some(VirtualKeyCode::Snapshot),
|
|
||||||
keysyms::XKB_KEY_Scroll_Lock => Some(VirtualKeyCode::Scroll),
|
|
||||||
keysyms::XKB_KEY_Pause => Some(VirtualKeyCode::Pause),
|
|
||||||
keysyms::XKB_KEY_Insert => Some(VirtualKeyCode::Insert),
|
|
||||||
keysyms::XKB_KEY_Home => Some(VirtualKeyCode::Home),
|
|
||||||
keysyms::XKB_KEY_Delete => Some(VirtualKeyCode::Delete),
|
|
||||||
keysyms::XKB_KEY_End => Some(VirtualKeyCode::End),
|
|
||||||
keysyms::XKB_KEY_Page_Down => Some(VirtualKeyCode::PageDown),
|
|
||||||
keysyms::XKB_KEY_Page_Up => Some(VirtualKeyCode::PageUp),
|
|
||||||
// Arrows.
|
|
||||||
keysyms::XKB_KEY_Left => Some(VirtualKeyCode::Left),
|
|
||||||
keysyms::XKB_KEY_Up => Some(VirtualKeyCode::Up),
|
|
||||||
keysyms::XKB_KEY_Right => Some(VirtualKeyCode::Right),
|
|
||||||
keysyms::XKB_KEY_Down => Some(VirtualKeyCode::Down),
|
|
||||||
|
|
||||||
keysyms::XKB_KEY_BackSpace => Some(VirtualKeyCode::Back),
|
|
||||||
keysyms::XKB_KEY_Return => Some(VirtualKeyCode::Return),
|
|
||||||
keysyms::XKB_KEY_space => Some(VirtualKeyCode::Space),
|
|
||||||
|
|
||||||
keysyms::XKB_KEY_Multi_key => Some(VirtualKeyCode::Compose),
|
|
||||||
keysyms::XKB_KEY_caret => Some(VirtualKeyCode::Caret),
|
|
||||||
|
|
||||||
// Keypad.
|
|
||||||
keysyms::XKB_KEY_Num_Lock => Some(VirtualKeyCode::Numlock),
|
|
||||||
keysyms::XKB_KEY_KP_0 => Some(VirtualKeyCode::Numpad0),
|
|
||||||
keysyms::XKB_KEY_KP_1 => Some(VirtualKeyCode::Numpad1),
|
|
||||||
keysyms::XKB_KEY_KP_2 => Some(VirtualKeyCode::Numpad2),
|
|
||||||
keysyms::XKB_KEY_KP_3 => Some(VirtualKeyCode::Numpad3),
|
|
||||||
keysyms::XKB_KEY_KP_4 => Some(VirtualKeyCode::Numpad4),
|
|
||||||
keysyms::XKB_KEY_KP_5 => Some(VirtualKeyCode::Numpad5),
|
|
||||||
keysyms::XKB_KEY_KP_6 => Some(VirtualKeyCode::Numpad6),
|
|
||||||
keysyms::XKB_KEY_KP_7 => Some(VirtualKeyCode::Numpad7),
|
|
||||||
keysyms::XKB_KEY_KP_8 => Some(VirtualKeyCode::Numpad8),
|
|
||||||
keysyms::XKB_KEY_KP_9 => Some(VirtualKeyCode::Numpad9),
|
|
||||||
// Misc.
|
|
||||||
// => Some(VirtualKeyCode::AbntC1),
|
|
||||||
// => Some(VirtualKeyCode::AbntC2),
|
|
||||||
keysyms::XKB_KEY_plus => Some(VirtualKeyCode::Plus),
|
|
||||||
keysyms::XKB_KEY_apostrophe => Some(VirtualKeyCode::Apostrophe),
|
|
||||||
// => Some(VirtualKeyCode::Apps),
|
|
||||||
keysyms::XKB_KEY_at => Some(VirtualKeyCode::At),
|
|
||||||
// => Some(VirtualKeyCode::Ax),
|
|
||||||
keysyms::XKB_KEY_backslash => Some(VirtualKeyCode::Backslash),
|
|
||||||
keysyms::XKB_KEY_XF86Calculator => Some(VirtualKeyCode::Calculator),
|
|
||||||
// => Some(VirtualKeyCode::Capital),
|
|
||||||
keysyms::XKB_KEY_colon => Some(VirtualKeyCode::Colon),
|
|
||||||
keysyms::XKB_KEY_comma => Some(VirtualKeyCode::Comma),
|
|
||||||
// => Some(VirtualKeyCode::Convert),
|
|
||||||
keysyms::XKB_KEY_equal => Some(VirtualKeyCode::Equals),
|
|
||||||
keysyms::XKB_KEY_grave => Some(VirtualKeyCode::Grave),
|
|
||||||
// => Some(VirtualKeyCode::Kana),
|
|
||||||
keysyms::XKB_KEY_Kanji => Some(VirtualKeyCode::Kanji),
|
|
||||||
keysyms::XKB_KEY_Alt_L => Some(VirtualKeyCode::LAlt),
|
|
||||||
keysyms::XKB_KEY_bracketleft => Some(VirtualKeyCode::LBracket),
|
|
||||||
keysyms::XKB_KEY_Control_L => Some(VirtualKeyCode::LControl),
|
|
||||||
keysyms::XKB_KEY_Shift_L => Some(VirtualKeyCode::LShift),
|
|
||||||
keysyms::XKB_KEY_Super_L => Some(VirtualKeyCode::LWin),
|
|
||||||
keysyms::XKB_KEY_XF86Mail => Some(VirtualKeyCode::Mail),
|
|
||||||
// => Some(VirtualKeyCode::MediaSelect),
|
|
||||||
// => Some(VirtualKeyCode::MediaStop),
|
|
||||||
keysyms::XKB_KEY_minus => Some(VirtualKeyCode::Minus),
|
|
||||||
keysyms::XKB_KEY_asterisk => Some(VirtualKeyCode::Asterisk),
|
|
||||||
keysyms::XKB_KEY_XF86AudioMute => Some(VirtualKeyCode::Mute),
|
|
||||||
// => Some(VirtualKeyCode::MyComputer),
|
|
||||||
keysyms::XKB_KEY_XF86AudioNext => Some(VirtualKeyCode::NextTrack),
|
|
||||||
// => Some(VirtualKeyCode::NoConvert),
|
|
||||||
keysyms::XKB_KEY_KP_Separator => Some(VirtualKeyCode::NumpadComma),
|
|
||||||
keysyms::XKB_KEY_KP_Enter => Some(VirtualKeyCode::NumpadEnter),
|
|
||||||
keysyms::XKB_KEY_KP_Equal => Some(VirtualKeyCode::NumpadEquals),
|
|
||||||
keysyms::XKB_KEY_KP_Add => Some(VirtualKeyCode::NumpadAdd),
|
|
||||||
keysyms::XKB_KEY_KP_Subtract => Some(VirtualKeyCode::NumpadSubtract),
|
|
||||||
keysyms::XKB_KEY_KP_Multiply => Some(VirtualKeyCode::NumpadMultiply),
|
|
||||||
keysyms::XKB_KEY_KP_Divide => Some(VirtualKeyCode::NumpadDivide),
|
|
||||||
keysyms::XKB_KEY_KP_Decimal => Some(VirtualKeyCode::NumpadDecimal),
|
|
||||||
keysyms::XKB_KEY_KP_Page_Up => Some(VirtualKeyCode::PageUp),
|
|
||||||
keysyms::XKB_KEY_KP_Page_Down => Some(VirtualKeyCode::PageDown),
|
|
||||||
keysyms::XKB_KEY_KP_Home => Some(VirtualKeyCode::Home),
|
|
||||||
keysyms::XKB_KEY_KP_End => Some(VirtualKeyCode::End),
|
|
||||||
keysyms::XKB_KEY_KP_Left => Some(VirtualKeyCode::Left),
|
|
||||||
keysyms::XKB_KEY_KP_Up => Some(VirtualKeyCode::Up),
|
|
||||||
keysyms::XKB_KEY_KP_Right => Some(VirtualKeyCode::Right),
|
|
||||||
keysyms::XKB_KEY_KP_Down => Some(VirtualKeyCode::Down),
|
|
||||||
// => Some(VirtualKeyCode::OEM102),
|
|
||||||
keysyms::XKB_KEY_period => Some(VirtualKeyCode::Period),
|
|
||||||
// => Some(VirtualKeyCode::Playpause),
|
|
||||||
keysyms::XKB_KEY_XF86PowerOff => Some(VirtualKeyCode::Power),
|
|
||||||
keysyms::XKB_KEY_XF86AudioPrev => Some(VirtualKeyCode::PrevTrack),
|
|
||||||
keysyms::XKB_KEY_Alt_R => Some(VirtualKeyCode::RAlt),
|
|
||||||
keysyms::XKB_KEY_bracketright => Some(VirtualKeyCode::RBracket),
|
|
||||||
keysyms::XKB_KEY_Control_R => Some(VirtualKeyCode::RControl),
|
|
||||||
keysyms::XKB_KEY_Shift_R => Some(VirtualKeyCode::RShift),
|
|
||||||
keysyms::XKB_KEY_Super_R => Some(VirtualKeyCode::RWin),
|
|
||||||
keysyms::XKB_KEY_semicolon => Some(VirtualKeyCode::Semicolon),
|
|
||||||
keysyms::XKB_KEY_slash => Some(VirtualKeyCode::Slash),
|
|
||||||
keysyms::XKB_KEY_XF86Sleep => Some(VirtualKeyCode::Sleep),
|
|
||||||
// => Some(VirtualKeyCode::Stop),
|
|
||||||
// => Some(VirtualKeyCode::Sysrq),
|
|
||||||
keysyms::XKB_KEY_Tab => Some(VirtualKeyCode::Tab),
|
|
||||||
keysyms::XKB_KEY_ISO_Left_Tab => Some(VirtualKeyCode::Tab),
|
|
||||||
keysyms::XKB_KEY_underscore => Some(VirtualKeyCode::Underline),
|
|
||||||
// => Some(VirtualKeyCode::Unlabeled),
|
|
||||||
keysyms::XKB_KEY_XF86AudioLowerVolume => Some(VirtualKeyCode::VolumeDown),
|
|
||||||
keysyms::XKB_KEY_XF86AudioRaiseVolume => Some(VirtualKeyCode::VolumeUp),
|
|
||||||
// => Some(VirtualKeyCode::Wake),
|
|
||||||
// => Some(VirtualKeyCode::Webback),
|
|
||||||
// => Some(VirtualKeyCode::WebFavorites),
|
|
||||||
// => Some(VirtualKeyCode::WebForward),
|
|
||||||
// => Some(VirtualKeyCode::WebHome),
|
|
||||||
// => Some(VirtualKeyCode::WebRefresh),
|
|
||||||
// => Some(VirtualKeyCode::WebSearch),
|
|
||||||
// => Some(VirtualKeyCode::WebStop),
|
|
||||||
keysyms::XKB_KEY_yen => Some(VirtualKeyCode::Yen),
|
|
||||||
keysyms::XKB_KEY_XF86Copy => Some(VirtualKeyCode::Copy),
|
|
||||||
keysyms::XKB_KEY_XF86Paste => Some(VirtualKeyCode::Paste),
|
|
||||||
keysyms::XKB_KEY_XF86Cut => Some(VirtualKeyCode::Cut),
|
|
||||||
// Fallback.
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
//! Wayland keyboard handling.
|
|
||||||
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use sctk::reexports::client::protocol::wl_keyboard::WlKeyboard;
|
|
||||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
|
||||||
use sctk::reexports::client::Attached;
|
|
||||||
|
|
||||||
use sctk::reexports::calloop::LoopHandle;
|
|
||||||
|
|
||||||
use sctk::seat::keyboard;
|
|
||||||
|
|
||||||
use crate::event::ModifiersState;
|
|
||||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
|
||||||
use crate::platform_impl::wayland::WindowId;
|
|
||||||
|
|
||||||
mod handlers;
|
|
||||||
mod keymap;
|
|
||||||
|
|
||||||
pub(crate) struct Keyboard {
|
|
||||||
pub keyboard: WlKeyboard,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Keyboard {
|
|
||||||
pub fn new(
|
|
||||||
seat: &Attached<WlSeat>,
|
|
||||||
loop_handle: LoopHandle<'static, WinitState>,
|
|
||||||
modifiers_state: Rc<RefCell<ModifiersState>>,
|
|
||||||
) -> Option<Self> {
|
|
||||||
let mut inner = KeyboardInner::new(modifiers_state);
|
|
||||||
let keyboard = keyboard::map_keyboard_repeat(
|
|
||||||
loop_handle.clone(),
|
|
||||||
seat,
|
|
||||||
None,
|
|
||||||
keyboard::RepeatKind::System,
|
|
||||||
move |event, _, mut dispatch_data| {
|
|
||||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
|
||||||
handlers::handle_keyboard(event, &mut inner, winit_state);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.ok()?;
|
|
||||||
|
|
||||||
Some(Self { keyboard })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Keyboard {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if self.keyboard.as_ref().version() >= 3 {
|
|
||||||
self.keyboard.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct KeyboardInner {
|
|
||||||
/// Currently focused surface.
|
|
||||||
target_window_id: Option<WindowId>,
|
|
||||||
|
|
||||||
/// A pending state of modifiers.
|
|
||||||
///
|
|
||||||
/// This state is getting set if we've got a modifiers update
|
|
||||||
/// before `Enter` event, which shouldn't happen in general, however
|
|
||||||
/// some compositors are still doing so.
|
|
||||||
pending_modifers_state: Option<ModifiersState>,
|
|
||||||
|
|
||||||
/// Current state of modifiers keys.
|
|
||||||
modifiers_state: Rc<RefCell<ModifiersState>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KeyboardInner {
|
|
||||||
fn new(modifiers_state: Rc<RefCell<ModifiersState>>) -> Self {
|
|
||||||
Self {
|
|
||||||
target_window_id: None,
|
|
||||||
pending_modifers_state: None,
|
|
||||||
modifiers_state,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<keyboard::ModifiersState> for ModifiersState {
|
|
||||||
fn from(mods: keyboard::ModifiersState) -> ModifiersState {
|
|
||||||
let mut wl_mods = ModifiersState::empty();
|
|
||||||
wl_mods.set(ModifiersState::SHIFT, mods.shift);
|
|
||||||
wl_mods.set(ModifiersState::CTRL, mods.ctrl);
|
|
||||||
wl_mods.set(ModifiersState::ALT, mods.alt);
|
|
||||||
wl_mods.set(ModifiersState::LOGO, mods.logo);
|
|
||||||
wl_mods
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
//! Seat handling and managing.
|
|
||||||
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1;
|
|
||||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1;
|
|
||||||
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
|
|
||||||
|
|
||||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
|
||||||
use sctk::reexports::client::Attached;
|
|
||||||
|
|
||||||
use sctk::environment::Environment;
|
|
||||||
use sctk::reexports::calloop::LoopHandle;
|
|
||||||
use sctk::seat::pointer::ThemeManager;
|
|
||||||
use sctk::seat::{SeatData, SeatListener};
|
|
||||||
|
|
||||||
use super::env::WinitEnv;
|
|
||||||
use super::event_loop::WinitState;
|
|
||||||
use crate::event::ModifiersState;
|
|
||||||
|
|
||||||
mod keyboard;
|
|
||||||
pub mod pointer;
|
|
||||||
pub mod text_input;
|
|
||||||
mod touch;
|
|
||||||
|
|
||||||
use keyboard::Keyboard;
|
|
||||||
use pointer::Pointers;
|
|
||||||
use text_input::TextInput;
|
|
||||||
use touch::Touch;
|
|
||||||
|
|
||||||
pub struct SeatManager {
|
|
||||||
/// Listener for seats.
|
|
||||||
_seat_listener: SeatListener,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SeatManager {
|
|
||||||
pub fn new(
|
|
||||||
env: &Environment<WinitEnv>,
|
|
||||||
loop_handle: LoopHandle<'static, WinitState>,
|
|
||||||
theme_manager: ThemeManager,
|
|
||||||
) -> Self {
|
|
||||||
let relative_pointer_manager = env.get_global::<ZwpRelativePointerManagerV1>();
|
|
||||||
let pointer_constraints = env.get_global::<ZwpPointerConstraintsV1>();
|
|
||||||
let text_input_manager = env.get_global::<ZwpTextInputManagerV3>();
|
|
||||||
|
|
||||||
let mut inner = SeatManagerInner::new(
|
|
||||||
theme_manager,
|
|
||||||
relative_pointer_manager,
|
|
||||||
pointer_constraints,
|
|
||||||
text_input_manager,
|
|
||||||
loop_handle,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handle existing seats.
|
|
||||||
for seat in env.get_all_seats() {
|
|
||||||
let seat_data = match sctk::seat::clone_seat_data(&seat) {
|
|
||||||
Some(seat_data) => seat_data,
|
|
||||||
None => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
inner.process_seat_update(&seat, &seat_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
let seat_listener = env.listen_for_seats(move |seat, seat_data, _| {
|
|
||||||
inner.process_seat_update(&seat, seat_data);
|
|
||||||
});
|
|
||||||
|
|
||||||
Self {
|
|
||||||
_seat_listener: seat_listener,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Inner state of the seat manager.
|
|
||||||
struct SeatManagerInner {
|
|
||||||
/// Currently observed seats.
|
|
||||||
seats: Vec<SeatInfo>,
|
|
||||||
|
|
||||||
/// Loop handle.
|
|
||||||
loop_handle: LoopHandle<'static, WinitState>,
|
|
||||||
|
|
||||||
/// Relative pointer manager.
|
|
||||||
relative_pointer_manager: Option<Attached<ZwpRelativePointerManagerV1>>,
|
|
||||||
|
|
||||||
/// Pointer constraints.
|
|
||||||
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
|
|
||||||
|
|
||||||
/// Text input manager.
|
|
||||||
text_input_manager: Option<Attached<ZwpTextInputManagerV3>>,
|
|
||||||
|
|
||||||
/// A theme manager.
|
|
||||||
theme_manager: ThemeManager,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SeatManagerInner {
|
|
||||||
fn new(
|
|
||||||
theme_manager: ThemeManager,
|
|
||||||
relative_pointer_manager: Option<Attached<ZwpRelativePointerManagerV1>>,
|
|
||||||
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
|
|
||||||
text_input_manager: Option<Attached<ZwpTextInputManagerV3>>,
|
|
||||||
loop_handle: LoopHandle<'static, WinitState>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
seats: Vec::new(),
|
|
||||||
loop_handle,
|
|
||||||
relative_pointer_manager,
|
|
||||||
pointer_constraints,
|
|
||||||
text_input_manager,
|
|
||||||
theme_manager,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle seats update from the `SeatListener`.
|
|
||||||
pub fn process_seat_update(&mut self, seat: &Attached<WlSeat>, seat_data: &SeatData) {
|
|
||||||
let detached_seat = seat.detach();
|
|
||||||
|
|
||||||
let position = self.seats.iter().position(|si| si.seat == detached_seat);
|
|
||||||
let index = position.unwrap_or_else(|| {
|
|
||||||
self.seats.push(SeatInfo::new(detached_seat));
|
|
||||||
self.seats.len() - 1
|
|
||||||
});
|
|
||||||
|
|
||||||
let seat_info = &mut self.seats[index];
|
|
||||||
|
|
||||||
// Pointer handling.
|
|
||||||
if seat_data.has_pointer && !seat_data.defunct {
|
|
||||||
if seat_info.pointer.is_none() {
|
|
||||||
seat_info.pointer = Some(Pointers::new(
|
|
||||||
seat,
|
|
||||||
&self.theme_manager,
|
|
||||||
&self.relative_pointer_manager,
|
|
||||||
&self.pointer_constraints,
|
|
||||||
seat_info.modifiers_state.clone(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
seat_info.pointer = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle keyboard.
|
|
||||||
if seat_data.has_keyboard && !seat_data.defunct {
|
|
||||||
if seat_info.keyboard.is_none() {
|
|
||||||
seat_info.keyboard = Keyboard::new(
|
|
||||||
seat,
|
|
||||||
self.loop_handle.clone(),
|
|
||||||
seat_info.modifiers_state.clone(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
seat_info.keyboard = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle touch.
|
|
||||||
if seat_data.has_touch && !seat_data.defunct {
|
|
||||||
if seat_info.touch.is_none() {
|
|
||||||
seat_info.touch = Some(Touch::new(seat));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
seat_info.touch = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle text input.
|
|
||||||
if let Some(text_input_manager) = self.text_input_manager.as_ref() {
|
|
||||||
if seat_data.defunct {
|
|
||||||
seat_info.text_input = None;
|
|
||||||
} else if seat_info.text_input.is_none() {
|
|
||||||
seat_info.text_input = Some(TextInput::new(seat, text_input_manager));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resources associtated with a given seat.
|
|
||||||
struct SeatInfo {
|
|
||||||
/// Seat to which this `SeatInfo` belongs.
|
|
||||||
seat: WlSeat,
|
|
||||||
|
|
||||||
/// A keyboard handle with its repeat rate handling.
|
|
||||||
keyboard: Option<Keyboard>,
|
|
||||||
|
|
||||||
/// All pointers we're using on a seat.
|
|
||||||
pointer: Option<Pointers>,
|
|
||||||
|
|
||||||
/// Touch handling.
|
|
||||||
touch: Option<Touch>,
|
|
||||||
|
|
||||||
/// Text input handling aka IME.
|
|
||||||
text_input: Option<TextInput>,
|
|
||||||
|
|
||||||
/// The current state of modifiers observed in keyboard handler.
|
|
||||||
///
|
|
||||||
/// We keep modifiers state on a seat, since it's being used by pointer events as well.
|
|
||||||
modifiers_state: Rc<RefCell<ModifiersState>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SeatInfo {
|
|
||||||
pub fn new(seat: WlSeat) -> Self {
|
|
||||||
Self {
|
|
||||||
seat,
|
|
||||||
keyboard: None,
|
|
||||||
pointer: None,
|
|
||||||
touch: None,
|
|
||||||
text_input: None,
|
|
||||||
modifiers_state: Rc::new(RefCell::new(ModifiersState::default())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
//! Data which is used in pointer callbacks.
|
|
||||||
|
|
||||||
use std::cell::{Cell, RefCell};
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
|
||||||
use sctk::reexports::client::Attached;
|
|
||||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1;
|
|
||||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
|
|
||||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1;
|
|
||||||
|
|
||||||
use crate::event::{ModifiersState, TouchPhase};
|
|
||||||
|
|
||||||
/// A data being used by pointer handlers.
|
|
||||||
pub(super) struct PointerData {
|
|
||||||
/// Winit's surface the pointer is currently over.
|
|
||||||
pub surface: Option<WlSurface>,
|
|
||||||
|
|
||||||
/// Current modifiers state.
|
|
||||||
///
|
|
||||||
/// This refers a state of modifiers from `WlKeyboard` on
|
|
||||||
/// the given seat.
|
|
||||||
pub modifiers_state: Rc<RefCell<ModifiersState>>,
|
|
||||||
|
|
||||||
/// Pointer constraints.
|
|
||||||
pub pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
|
|
||||||
|
|
||||||
pub confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
|
|
||||||
pub locked_pointer: Rc<RefCell<Option<ZwpLockedPointerV1>>>,
|
|
||||||
|
|
||||||
/// Latest observed serial in pointer events.
|
|
||||||
pub latest_serial: Rc<Cell<u32>>,
|
|
||||||
|
|
||||||
/// Latest observed serial in pointer enter events.
|
|
||||||
pub latest_enter_serial: Rc<Cell<u32>>,
|
|
||||||
|
|
||||||
/// The currently accumulated axis data on a pointer.
|
|
||||||
pub axis_data: AxisData,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PointerData {
|
|
||||||
pub fn new(
|
|
||||||
confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
|
|
||||||
locked_pointer: Rc<RefCell<Option<ZwpLockedPointerV1>>>,
|
|
||||||
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
|
|
||||||
modifiers_state: Rc<RefCell<ModifiersState>>,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
surface: None,
|
|
||||||
latest_serial: Rc::new(Cell::new(0)),
|
|
||||||
latest_enter_serial: Rc::new(Cell::new(0)),
|
|
||||||
confined_pointer,
|
|
||||||
locked_pointer,
|
|
||||||
modifiers_state,
|
|
||||||
pointer_constraints,
|
|
||||||
axis_data: AxisData::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Axis data.
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub(super) struct AxisData {
|
|
||||||
/// Current state of the axis.
|
|
||||||
pub axis_state: TouchPhase,
|
|
||||||
|
|
||||||
/// A buffer for `PixelDelta` event.
|
|
||||||
pub axis_buffer: Option<(f32, f32)>,
|
|
||||||
|
|
||||||
/// A buffer for `LineDelta` event.
|
|
||||||
pub axis_discrete_buffer: Option<(f32, f32)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AxisData {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
axis_state: TouchPhase::Ended,
|
|
||||||
axis_buffer: None,
|
|
||||||
axis_discrete_buffer: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,318 +0,0 @@
|
|||||||
//! Handlers for the pointers we're using.
|
|
||||||
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use sctk::reexports::client::protocol::wl_pointer::{self, Event as PointerEvent};
|
|
||||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
|
||||||
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::Event as RelativePointerEvent;
|
|
||||||
|
|
||||||
use sctk::seat::pointer::ThemedPointer;
|
|
||||||
|
|
||||||
use crate::dpi::LogicalPosition;
|
|
||||||
use crate::event::{
|
|
||||||
DeviceEvent, ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent,
|
|
||||||
};
|
|
||||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
|
||||||
use crate::platform_impl::wayland::{self, DeviceId};
|
|
||||||
|
|
||||||
use super::{PointerData, WinitPointer};
|
|
||||||
|
|
||||||
// These values are comming from <linux/input-event-codes.h>.
|
|
||||||
const BTN_LEFT: u32 = 0x110;
|
|
||||||
const BTN_RIGHT: u32 = 0x111;
|
|
||||||
const BTN_MIDDLE: u32 = 0x112;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(super) fn handle_pointer(
|
|
||||||
pointer: ThemedPointer,
|
|
||||||
event: PointerEvent,
|
|
||||||
pointer_data: &Rc<RefCell<PointerData>>,
|
|
||||||
winit_state: &mut WinitState,
|
|
||||||
seat: WlSeat,
|
|
||||||
) {
|
|
||||||
let event_sink = &mut winit_state.event_sink;
|
|
||||||
let mut pointer_data = pointer_data.borrow_mut();
|
|
||||||
match event {
|
|
||||||
PointerEvent::Enter {
|
|
||||||
surface,
|
|
||||||
surface_x,
|
|
||||||
surface_y,
|
|
||||||
serial,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
pointer_data.latest_serial.replace(serial);
|
|
||||||
pointer_data.latest_enter_serial.replace(serial);
|
|
||||||
|
|
||||||
let window_id = wayland::make_wid(&surface);
|
|
||||||
if !winit_state.window_map.contains_key(&window_id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let window_handle = match winit_state.window_map.get_mut(&window_id) {
|
|
||||||
Some(window_handle) => window_handle,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let scale_factor = sctk::get_surface_scale_factor(&surface) as f64;
|
|
||||||
pointer_data.surface = Some(surface);
|
|
||||||
|
|
||||||
// Notify window that pointer entered the surface.
|
|
||||||
let winit_pointer = WinitPointer {
|
|
||||||
pointer,
|
|
||||||
confined_pointer: Rc::downgrade(&pointer_data.confined_pointer),
|
|
||||||
locked_pointer: Rc::downgrade(&pointer_data.locked_pointer),
|
|
||||||
pointer_constraints: pointer_data.pointer_constraints.clone(),
|
|
||||||
latest_serial: pointer_data.latest_serial.clone(),
|
|
||||||
latest_enter_serial: pointer_data.latest_enter_serial.clone(),
|
|
||||||
seat,
|
|
||||||
};
|
|
||||||
window_handle.pointer_entered(winit_pointer);
|
|
||||||
|
|
||||||
event_sink.push_window_event(
|
|
||||||
WindowEvent::CursorEntered {
|
|
||||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
|
||||||
DeviceId,
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
window_id,
|
|
||||||
);
|
|
||||||
|
|
||||||
let position = LogicalPosition::new(surface_x, surface_y).to_physical(scale_factor);
|
|
||||||
|
|
||||||
event_sink.push_window_event(
|
|
||||||
WindowEvent::CursorMoved {
|
|
||||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
|
||||||
DeviceId,
|
|
||||||
)),
|
|
||||||
position,
|
|
||||||
modifiers: *pointer_data.modifiers_state.borrow(),
|
|
||||||
},
|
|
||||||
window_id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
PointerEvent::Leave { surface, serial } => {
|
|
||||||
pointer_data.surface = None;
|
|
||||||
pointer_data.latest_serial.replace(serial);
|
|
||||||
|
|
||||||
let window_id = wayland::make_wid(&surface);
|
|
||||||
|
|
||||||
let window_handle = match winit_state.window_map.get_mut(&window_id) {
|
|
||||||
Some(window_handle) => window_handle,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Notify a window that pointer is no longer observing it.
|
|
||||||
let winit_pointer = WinitPointer {
|
|
||||||
pointer,
|
|
||||||
confined_pointer: Rc::downgrade(&pointer_data.confined_pointer),
|
|
||||||
locked_pointer: Rc::downgrade(&pointer_data.locked_pointer),
|
|
||||||
pointer_constraints: pointer_data.pointer_constraints.clone(),
|
|
||||||
latest_serial: pointer_data.latest_serial.clone(),
|
|
||||||
latest_enter_serial: pointer_data.latest_enter_serial.clone(),
|
|
||||||
seat,
|
|
||||||
};
|
|
||||||
window_handle.pointer_left(winit_pointer);
|
|
||||||
|
|
||||||
event_sink.push_window_event(
|
|
||||||
WindowEvent::CursorLeft {
|
|
||||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
|
||||||
DeviceId,
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
window_id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
PointerEvent::Motion {
|
|
||||||
surface_x,
|
|
||||||
surface_y,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let surface = match pointer_data.surface.as_ref() {
|
|
||||||
Some(surface) => surface,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let window_id = wayland::make_wid(surface);
|
|
||||||
|
|
||||||
let scale_factor = sctk::get_surface_scale_factor(surface) as f64;
|
|
||||||
let position = LogicalPosition::new(surface_x, surface_y).to_physical(scale_factor);
|
|
||||||
|
|
||||||
event_sink.push_window_event(
|
|
||||||
WindowEvent::CursorMoved {
|
|
||||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
|
||||||
DeviceId,
|
|
||||||
)),
|
|
||||||
position,
|
|
||||||
modifiers: *pointer_data.modifiers_state.borrow(),
|
|
||||||
},
|
|
||||||
window_id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
PointerEvent::Button {
|
|
||||||
button,
|
|
||||||
state,
|
|
||||||
serial,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
pointer_data.latest_serial.replace(serial);
|
|
||||||
let window_id = match pointer_data.surface.as_ref().map(wayland::make_wid) {
|
|
||||||
Some(window_id) => window_id,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let state = match state {
|
|
||||||
wl_pointer::ButtonState::Pressed => ElementState::Pressed,
|
|
||||||
wl_pointer::ButtonState::Released => ElementState::Released,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let button = match button {
|
|
||||||
BTN_LEFT => MouseButton::Left,
|
|
||||||
BTN_RIGHT => MouseButton::Right,
|
|
||||||
BTN_MIDDLE => MouseButton::Middle,
|
|
||||||
button => MouseButton::Other(button as u16),
|
|
||||||
};
|
|
||||||
|
|
||||||
event_sink.push_window_event(
|
|
||||||
WindowEvent::MouseInput {
|
|
||||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
|
||||||
DeviceId,
|
|
||||||
)),
|
|
||||||
state,
|
|
||||||
button,
|
|
||||||
modifiers: *pointer_data.modifiers_state.borrow(),
|
|
||||||
},
|
|
||||||
window_id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
PointerEvent::Axis { axis, value, .. } => {
|
|
||||||
let surface = match pointer_data.surface.as_ref() {
|
|
||||||
Some(surface) => surface,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let window_id = wayland::make_wid(surface);
|
|
||||||
|
|
||||||
if pointer.as_ref().version() < 5 {
|
|
||||||
let (mut x, mut y) = (0.0, 0.0);
|
|
||||||
|
|
||||||
// Old seat compatibility.
|
|
||||||
match axis {
|
|
||||||
// Wayland sign convention is the inverse of winit.
|
|
||||||
wl_pointer::Axis::VerticalScroll => y -= value as f32,
|
|
||||||
wl_pointer::Axis::HorizontalScroll => x -= value as f32,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
let scale_factor = sctk::get_surface_scale_factor(surface) as f64;
|
|
||||||
let delta = LogicalPosition::new(x as f64, y as f64).to_physical(scale_factor);
|
|
||||||
|
|
||||||
event_sink.push_window_event(
|
|
||||||
WindowEvent::MouseWheel {
|
|
||||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
|
||||||
DeviceId,
|
|
||||||
)),
|
|
||||||
delta: MouseScrollDelta::PixelDelta(delta),
|
|
||||||
phase: TouchPhase::Moved,
|
|
||||||
modifiers: *pointer_data.modifiers_state.borrow(),
|
|
||||||
},
|
|
||||||
window_id,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let (mut x, mut y) = pointer_data.axis_data.axis_buffer.unwrap_or((0.0, 0.0));
|
|
||||||
match axis {
|
|
||||||
// Wayland sign convention is the inverse of winit.
|
|
||||||
wl_pointer::Axis::VerticalScroll => y -= value as f32,
|
|
||||||
wl_pointer::Axis::HorizontalScroll => x -= value as f32,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
pointer_data.axis_data.axis_buffer = Some((x, y));
|
|
||||||
|
|
||||||
pointer_data.axis_data.axis_state = match pointer_data.axis_data.axis_state {
|
|
||||||
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
|
|
||||||
_ => TouchPhase::Started,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PointerEvent::AxisDiscrete { axis, discrete } => {
|
|
||||||
let (mut x, mut y) = pointer_data
|
|
||||||
.axis_data
|
|
||||||
.axis_discrete_buffer
|
|
||||||
.unwrap_or((0., 0.));
|
|
||||||
|
|
||||||
match axis {
|
|
||||||
// Wayland sign convention is the inverse of winit.
|
|
||||||
wl_pointer::Axis::VerticalScroll => y -= discrete as f32,
|
|
||||||
wl_pointer::Axis::HorizontalScroll => x -= discrete as f32,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
pointer_data.axis_data.axis_discrete_buffer = Some((x, y));
|
|
||||||
|
|
||||||
pointer_data.axis_data.axis_state = match pointer_data.axis_data.axis_state {
|
|
||||||
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
|
|
||||||
_ => TouchPhase::Started,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PointerEvent::AxisSource { .. } => (),
|
|
||||||
PointerEvent::AxisStop { .. } => {
|
|
||||||
pointer_data.axis_data.axis_state = TouchPhase::Ended;
|
|
||||||
}
|
|
||||||
PointerEvent::Frame => {
|
|
||||||
let axis_buffer = pointer_data.axis_data.axis_buffer.take();
|
|
||||||
let axis_discrete_buffer = pointer_data.axis_data.axis_discrete_buffer.take();
|
|
||||||
|
|
||||||
let surface = match pointer_data.surface.as_ref() {
|
|
||||||
Some(surface) => surface,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
let window_id = wayland::make_wid(surface);
|
|
||||||
|
|
||||||
let window_event = if let Some((x, y)) = axis_discrete_buffer {
|
|
||||||
WindowEvent::MouseWheel {
|
|
||||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
|
||||||
DeviceId,
|
|
||||||
)),
|
|
||||||
delta: MouseScrollDelta::LineDelta(x, y),
|
|
||||||
phase: pointer_data.axis_data.axis_state,
|
|
||||||
modifiers: *pointer_data.modifiers_state.borrow(),
|
|
||||||
}
|
|
||||||
} else if let Some((x, y)) = axis_buffer {
|
|
||||||
let scale_factor = sctk::get_surface_scale_factor(surface) as f64;
|
|
||||||
let delta = LogicalPosition::new(x, y).to_physical(scale_factor);
|
|
||||||
|
|
||||||
WindowEvent::MouseWheel {
|
|
||||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
|
||||||
DeviceId,
|
|
||||||
)),
|
|
||||||
delta: MouseScrollDelta::PixelDelta(delta),
|
|
||||||
phase: pointer_data.axis_data.axis_state,
|
|
||||||
modifiers: *pointer_data.modifiers_state.borrow(),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
event_sink.push_window_event(window_event, window_id);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(super) fn handle_relative_pointer(event: RelativePointerEvent, winit_state: &mut WinitState) {
|
|
||||||
if let RelativePointerEvent::RelativeMotion {
|
|
||||||
dx_unaccel,
|
|
||||||
dy_unaccel,
|
|
||||||
..
|
|
||||||
} = event
|
|
||||||
{
|
|
||||||
winit_state.event_sink.push_device_event(
|
|
||||||
DeviceEvent::MouseMotion {
|
|
||||||
delta: (dx_unaccel, dy_unaccel),
|
|
||||||
},
|
|
||||||
DeviceId,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,343 +0,0 @@
|
|||||||
//! All pointer related handling.
|
|
||||||
|
|
||||||
use std::cell::{Cell, RefCell};
|
|
||||||
use std::rc::{Rc, Weak};
|
|
||||||
|
|
||||||
use sctk::reexports::client::protocol::wl_pointer::WlPointer;
|
|
||||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
|
||||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
|
||||||
use sctk::reexports::client::Attached;
|
|
||||||
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1;
|
|
||||||
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::ZwpRelativePointerV1;
|
|
||||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::{ZwpPointerConstraintsV1, Lifetime};
|
|
||||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
|
|
||||||
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1;
|
|
||||||
|
|
||||||
use sctk::seat::pointer::{ThemeManager, ThemedPointer};
|
|
||||||
use sctk::window::Window;
|
|
||||||
|
|
||||||
use crate::event::ModifiersState;
|
|
||||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
|
||||||
use crate::platform_impl::wayland::window::WinitFrame;
|
|
||||||
use crate::window::CursorIcon;
|
|
||||||
|
|
||||||
mod data;
|
|
||||||
mod handlers;
|
|
||||||
|
|
||||||
use data::PointerData;
|
|
||||||
|
|
||||||
/// A proxy to Wayland pointer, which serves requests from a `WindowHandle`.
|
|
||||||
pub struct WinitPointer {
|
|
||||||
pointer: ThemedPointer,
|
|
||||||
|
|
||||||
/// Create confined pointers.
|
|
||||||
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
|
|
||||||
|
|
||||||
/// Cursor to handle confine requests.
|
|
||||||
confined_pointer: Weak<RefCell<Option<ZwpConfinedPointerV1>>>,
|
|
||||||
|
|
||||||
/// Cursor to handle locked requests.
|
|
||||||
locked_pointer: Weak<RefCell<Option<ZwpLockedPointerV1>>>,
|
|
||||||
|
|
||||||
/// Latest observed serial in pointer events.
|
|
||||||
/// used by Window::start_interactive_move()
|
|
||||||
latest_serial: Rc<Cell<u32>>,
|
|
||||||
|
|
||||||
/// Latest observed serial in pointer enter events.
|
|
||||||
/// used by Window::set_cursor()
|
|
||||||
latest_enter_serial: Rc<Cell<u32>>,
|
|
||||||
|
|
||||||
/// Seat.
|
|
||||||
seat: WlSeat,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for WinitPointer {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
*self.pointer == *other.pointer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for WinitPointer {}
|
|
||||||
|
|
||||||
impl WinitPointer {
|
|
||||||
/// Set the cursor icon.
|
|
||||||
///
|
|
||||||
/// Providing `None` will hide the cursor.
|
|
||||||
pub fn set_cursor(&self, cursor_icon: Option<CursorIcon>) {
|
|
||||||
let cursor_icon = match cursor_icon {
|
|
||||||
Some(cursor_icon) => cursor_icon,
|
|
||||||
None => {
|
|
||||||
// Hide the cursor.
|
|
||||||
// WlPointer::set_cursor() expects the serial of the last *enter*
|
|
||||||
// event (compare to to start_interactive_move()).
|
|
||||||
(*self.pointer).set_cursor(self.latest_enter_serial.get(), None, 0, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let cursors: &[&str] = match cursor_icon {
|
|
||||||
CursorIcon::Alias => &["link"],
|
|
||||||
CursorIcon::Arrow => &["arrow"],
|
|
||||||
CursorIcon::Cell => &["plus"],
|
|
||||||
CursorIcon::Copy => &["copy"],
|
|
||||||
CursorIcon::Crosshair => &["crosshair"],
|
|
||||||
CursorIcon::Default => &["left_ptr"],
|
|
||||||
CursorIcon::Hand => &["hand2", "hand1"],
|
|
||||||
CursorIcon::Help => &["question_arrow"],
|
|
||||||
CursorIcon::Move => &["move"],
|
|
||||||
CursorIcon::Grab => &["openhand", "grab"],
|
|
||||||
CursorIcon::Grabbing => &["closedhand", "grabbing"],
|
|
||||||
CursorIcon::Progress => &["progress"],
|
|
||||||
CursorIcon::AllScroll => &["all-scroll"],
|
|
||||||
CursorIcon::ContextMenu => &["context-menu"],
|
|
||||||
|
|
||||||
CursorIcon::NoDrop => &["no-drop", "circle"],
|
|
||||||
CursorIcon::NotAllowed => &["crossed_circle"],
|
|
||||||
|
|
||||||
// Resize cursors
|
|
||||||
CursorIcon::EResize => &["right_side"],
|
|
||||||
CursorIcon::NResize => &["top_side"],
|
|
||||||
CursorIcon::NeResize => &["top_right_corner"],
|
|
||||||
CursorIcon::NwResize => &["top_left_corner"],
|
|
||||||
CursorIcon::SResize => &["bottom_side"],
|
|
||||||
CursorIcon::SeResize => &["bottom_right_corner"],
|
|
||||||
CursorIcon::SwResize => &["bottom_left_corner"],
|
|
||||||
CursorIcon::WResize => &["left_side"],
|
|
||||||
CursorIcon::EwResize => &["h_double_arrow"],
|
|
||||||
CursorIcon::NsResize => &["v_double_arrow"],
|
|
||||||
CursorIcon::NwseResize => &["bd_double_arrow", "size_fdiag"],
|
|
||||||
CursorIcon::NeswResize => &["fd_double_arrow", "size_bdiag"],
|
|
||||||
CursorIcon::ColResize => &["split_h", "h_double_arrow"],
|
|
||||||
CursorIcon::RowResize => &["split_v", "v_double_arrow"],
|
|
||||||
CursorIcon::Text => &["text", "xterm"],
|
|
||||||
CursorIcon::VerticalText => &["vertical-text"],
|
|
||||||
|
|
||||||
CursorIcon::Wait => &["watch"],
|
|
||||||
|
|
||||||
CursorIcon::ZoomIn => &["zoom-in"],
|
|
||||||
CursorIcon::ZoomOut => &["zoom-out"],
|
|
||||||
};
|
|
||||||
|
|
||||||
let serial = Some(self.latest_enter_serial.get());
|
|
||||||
for cursor in cursors {
|
|
||||||
if self.pointer.set_cursor(cursor, serial).is_ok() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
warn!("Failed to set cursor to {:?}", cursor_icon);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Confine the pointer to a surface.
|
|
||||||
pub fn confine(&self, surface: &WlSurface) {
|
|
||||||
let pointer_constraints = match &self.pointer_constraints {
|
|
||||||
Some(pointer_constraints) => pointer_constraints,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let confined_pointer = match self.confined_pointer.upgrade() {
|
|
||||||
Some(confined_pointer) => confined_pointer,
|
|
||||||
// A pointer is gone.
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
*confined_pointer.borrow_mut() = Some(init_confined_pointer(
|
|
||||||
pointer_constraints,
|
|
||||||
surface,
|
|
||||||
&*self.pointer,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tries to unconfine the pointer if the current pointer is confined.
|
|
||||||
pub fn unconfine(&self) {
|
|
||||||
let confined_pointer = match self.confined_pointer.upgrade() {
|
|
||||||
Some(confined_pointer) => confined_pointer,
|
|
||||||
// A pointer is gone.
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut confined_pointer = confined_pointer.borrow_mut();
|
|
||||||
|
|
||||||
if let Some(confined_pointer) = confined_pointer.take() {
|
|
||||||
confined_pointer.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lock(&self, surface: &WlSurface) {
|
|
||||||
let pointer_constraints = match &self.pointer_constraints {
|
|
||||||
Some(pointer_constraints) => pointer_constraints,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let locked_pointer = match self.locked_pointer.upgrade() {
|
|
||||||
Some(locked_pointer) => locked_pointer,
|
|
||||||
// A pointer is gone.
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
*locked_pointer.borrow_mut() = Some(init_locked_pointer(
|
|
||||||
pointer_constraints,
|
|
||||||
surface,
|
|
||||||
&*self.pointer,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unlock(&self) {
|
|
||||||
let locked_pointer = match self.locked_pointer.upgrade() {
|
|
||||||
Some(locked_pointer) => locked_pointer,
|
|
||||||
// A pointer is gone.
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut locked_pointer = locked_pointer.borrow_mut();
|
|
||||||
|
|
||||||
if let Some(locked_pointer) = locked_pointer.take() {
|
|
||||||
locked_pointer.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_cursor_position(&self, surface_x: u32, surface_y: u32) {
|
|
||||||
let locked_pointer = match self.locked_pointer.upgrade() {
|
|
||||||
Some(locked_pointer) => locked_pointer,
|
|
||||||
// A pointer is gone.
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let locked_pointer = locked_pointer.borrow_mut();
|
|
||||||
if let Some(locked_pointer) = locked_pointer.as_ref() {
|
|
||||||
locked_pointer.set_cursor_position_hint(surface_x.into(), surface_y.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn drag_window(&self, window: &Window<WinitFrame>) {
|
|
||||||
// WlPointer::setart_interactive_move() expects the last serial of *any*
|
|
||||||
// pointer event (compare to set_cursor()).
|
|
||||||
window.start_interactive_move(&self.seat, self.latest_serial.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A pointer wrapper for easy releasing and managing pointers.
|
|
||||||
pub(super) struct Pointers {
|
|
||||||
/// A pointer itself.
|
|
||||||
pointer: ThemedPointer,
|
|
||||||
|
|
||||||
/// A relative pointer handler.
|
|
||||||
relative_pointer: Option<ZwpRelativePointerV1>,
|
|
||||||
|
|
||||||
/// Confined pointer.
|
|
||||||
confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
|
|
||||||
|
|
||||||
/// Locked pointer.
|
|
||||||
locked_pointer: Rc<RefCell<Option<ZwpLockedPointerV1>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pointers {
|
|
||||||
pub(super) fn new(
|
|
||||||
seat: &Attached<WlSeat>,
|
|
||||||
theme_manager: &ThemeManager,
|
|
||||||
relative_pointer_manager: &Option<Attached<ZwpRelativePointerManagerV1>>,
|
|
||||||
pointer_constraints: &Option<Attached<ZwpPointerConstraintsV1>>,
|
|
||||||
modifiers_state: Rc<RefCell<ModifiersState>>,
|
|
||||||
) -> Self {
|
|
||||||
let confined_pointer = Rc::new(RefCell::new(None));
|
|
||||||
let locked_pointer = Rc::new(RefCell::new(None));
|
|
||||||
|
|
||||||
let pointer_data = Rc::new(RefCell::new(PointerData::new(
|
|
||||||
confined_pointer.clone(),
|
|
||||||
locked_pointer.clone(),
|
|
||||||
pointer_constraints.clone(),
|
|
||||||
modifiers_state,
|
|
||||||
)));
|
|
||||||
|
|
||||||
let pointer_seat = seat.detach();
|
|
||||||
let pointer = theme_manager.theme_pointer_with_impl(
|
|
||||||
seat,
|
|
||||||
move |event, pointer, mut dispatch_data| {
|
|
||||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
|
||||||
handlers::handle_pointer(
|
|
||||||
pointer,
|
|
||||||
event,
|
|
||||||
&pointer_data,
|
|
||||||
winit_state,
|
|
||||||
pointer_seat.clone(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Setup relative_pointer if it's available.
|
|
||||||
let relative_pointer = relative_pointer_manager
|
|
||||||
.as_ref()
|
|
||||||
.map(|relative_pointer_manager| {
|
|
||||||
init_relative_pointer(relative_pointer_manager, &*pointer)
|
|
||||||
});
|
|
||||||
|
|
||||||
Self {
|
|
||||||
pointer,
|
|
||||||
relative_pointer,
|
|
||||||
confined_pointer,
|
|
||||||
locked_pointer,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Pointers {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
// Drop relative pointer.
|
|
||||||
if let Some(relative_pointer) = self.relative_pointer.take() {
|
|
||||||
relative_pointer.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drop confined pointer.
|
|
||||||
if let Some(confined_pointer) = self.confined_pointer.borrow_mut().take() {
|
|
||||||
confined_pointer.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drop lock ponter.
|
|
||||||
if let Some(locked_pointer) = self.locked_pointer.borrow_mut().take() {
|
|
||||||
locked_pointer.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drop the pointer itself in case it's possible.
|
|
||||||
if self.pointer.as_ref().version() >= 3 {
|
|
||||||
self.pointer.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn init_relative_pointer(
|
|
||||||
relative_pointer_manager: &ZwpRelativePointerManagerV1,
|
|
||||||
pointer: &WlPointer,
|
|
||||||
) -> ZwpRelativePointerV1 {
|
|
||||||
let relative_pointer = relative_pointer_manager.get_relative_pointer(pointer);
|
|
||||||
relative_pointer.quick_assign(move |_, event, mut dispatch_data| {
|
|
||||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
|
||||||
handlers::handle_relative_pointer(event, winit_state);
|
|
||||||
});
|
|
||||||
|
|
||||||
relative_pointer.detach()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn init_confined_pointer(
|
|
||||||
pointer_constraints: &Attached<ZwpPointerConstraintsV1>,
|
|
||||||
surface: &WlSurface,
|
|
||||||
pointer: &WlPointer,
|
|
||||||
) -> ZwpConfinedPointerV1 {
|
|
||||||
let confined_pointer =
|
|
||||||
pointer_constraints.confine_pointer(surface, pointer, None, Lifetime::Persistent);
|
|
||||||
|
|
||||||
confined_pointer.quick_assign(move |_, _, _| {});
|
|
||||||
|
|
||||||
confined_pointer.detach()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn init_locked_pointer(
|
|
||||||
pointer_constraints: &Attached<ZwpPointerConstraintsV1>,
|
|
||||||
surface: &WlSurface,
|
|
||||||
pointer: &WlPointer,
|
|
||||||
) -> ZwpLockedPointerV1 {
|
|
||||||
let locked_pointer =
|
|
||||||
pointer_constraints.lock_pointer(surface, pointer, None, Lifetime::Persistent);
|
|
||||||
|
|
||||||
locked_pointer.quick_assign(move |_, _, _| {});
|
|
||||||
|
|
||||||
locked_pointer.detach()
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
//! Handling of IME events.
|
|
||||||
|
|
||||||
use sctk::reexports::client::Main;
|
|
||||||
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_v3::{
|
|
||||||
Event as TextInputEvent, ZwpTextInputV3,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::event::{Ime, WindowEvent};
|
|
||||||
use crate::platform_impl::wayland;
|
|
||||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
|
||||||
|
|
||||||
use super::{Preedit, TextInputHandler, TextInputInner};
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(super) fn handle_text_input(
|
|
||||||
text_input: Main<ZwpTextInputV3>,
|
|
||||||
inner: &mut TextInputInner,
|
|
||||||
event: TextInputEvent,
|
|
||||||
winit_state: &mut WinitState,
|
|
||||||
) {
|
|
||||||
let event_sink = &mut winit_state.event_sink;
|
|
||||||
match event {
|
|
||||||
TextInputEvent::Enter { surface } => {
|
|
||||||
let window_id = wayland::make_wid(&surface);
|
|
||||||
|
|
||||||
let window_handle = match winit_state.window_map.get_mut(&window_id) {
|
|
||||||
Some(window_handle) => window_handle,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
inner.target_window_id = Some(window_id);
|
|
||||||
|
|
||||||
// Enable text input on that surface.
|
|
||||||
if window_handle.ime_allowed.get() {
|
|
||||||
text_input.enable();
|
|
||||||
text_input.commit();
|
|
||||||
event_sink.push_window_event(WindowEvent::Ime(Ime::Enabled), window_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify a window we're currently over about text input handler.
|
|
||||||
let text_input_handler = TextInputHandler {
|
|
||||||
text_input: text_input.detach(),
|
|
||||||
};
|
|
||||||
window_handle.text_input_entered(text_input_handler);
|
|
||||||
}
|
|
||||||
TextInputEvent::Leave { surface } => {
|
|
||||||
// Always issue a disable.
|
|
||||||
text_input.disable();
|
|
||||||
text_input.commit();
|
|
||||||
|
|
||||||
let window_id = wayland::make_wid(&surface);
|
|
||||||
|
|
||||||
let window_handle = match winit_state.window_map.get_mut(&window_id) {
|
|
||||||
Some(window_handle) => window_handle,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
inner.target_window_id = None;
|
|
||||||
|
|
||||||
// Remove text input handler from the window we're leaving.
|
|
||||||
let text_input_handler = TextInputHandler {
|
|
||||||
text_input: text_input.detach(),
|
|
||||||
};
|
|
||||||
window_handle.text_input_left(text_input_handler);
|
|
||||||
event_sink.push_window_event(WindowEvent::Ime(Ime::Disabled), window_id);
|
|
||||||
}
|
|
||||||
TextInputEvent::PreeditString {
|
|
||||||
text,
|
|
||||||
cursor_begin,
|
|
||||||
cursor_end,
|
|
||||||
} => {
|
|
||||||
let cursor_begin = usize::try_from(cursor_begin).ok();
|
|
||||||
let cursor_end = usize::try_from(cursor_end).ok();
|
|
||||||
let text = text.unwrap_or_default();
|
|
||||||
inner.pending_preedit = Some(Preedit {
|
|
||||||
text,
|
|
||||||
cursor_begin,
|
|
||||||
cursor_end,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
TextInputEvent::CommitString { text } => {
|
|
||||||
// Update currenly commited string and reset previous preedit.
|
|
||||||
inner.pending_preedit = None;
|
|
||||||
inner.pending_commit = Some(text.unwrap_or_default());
|
|
||||||
}
|
|
||||||
TextInputEvent::Done { .. } => {
|
|
||||||
let window_id = match inner.target_window_id {
|
|
||||||
Some(window_id) => window_id,
|
|
||||||
_ => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(text) = inner.pending_commit.take() {
|
|
||||||
event_sink.push_window_event(WindowEvent::Ime(Ime::Commit(text)), window_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push preedit string we've got after latest commit.
|
|
||||||
if let Some(preedit) = inner.pending_preedit.take() {
|
|
||||||
let cursor_range = preedit
|
|
||||||
.cursor_begin
|
|
||||||
.map(|b| (b, preedit.cursor_end.unwrap_or(b)));
|
|
||||||
|
|
||||||
let event = Ime::Preedit(preedit.text, cursor_range);
|
|
||||||
event_sink.push_window_event(WindowEvent::Ime(event), window_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
|
||||||
use sctk::reexports::client::Attached;
|
|
||||||
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_manager_v3::ZwpTextInputManagerV3;
|
|
||||||
use sctk::reexports::protocols::unstable::text_input::v3::client::zwp_text_input_v3::ZwpTextInputV3;
|
|
||||||
|
|
||||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
|
||||||
use crate::platform_impl::wayland::WindowId;
|
|
||||||
|
|
||||||
mod handlers;
|
|
||||||
|
|
||||||
/// A handler for text input that we're advertising for `WindowHandle`.
|
|
||||||
#[derive(Eq, PartialEq)]
|
|
||||||
pub struct TextInputHandler {
|
|
||||||
text_input: ZwpTextInputV3,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextInputHandler {
|
|
||||||
#[inline]
|
|
||||||
pub fn set_ime_position(&self, x: i32, y: i32) {
|
|
||||||
self.text_input.set_cursor_rectangle(x, y, 0, 0);
|
|
||||||
self.text_input.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_input_allowed(&self, allowed: bool) {
|
|
||||||
if allowed {
|
|
||||||
self.text_input.enable();
|
|
||||||
} else {
|
|
||||||
self.text_input.disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.text_input.commit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A wrapper around text input to automatically destroy the object on `Drop`.
|
|
||||||
pub struct TextInput {
|
|
||||||
text_input: Attached<ZwpTextInputV3>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextInput {
|
|
||||||
pub fn new(seat: &Attached<WlSeat>, text_input_manager: &ZwpTextInputManagerV3) -> Self {
|
|
||||||
let text_input = text_input_manager.get_text_input(seat);
|
|
||||||
let mut text_input_inner = TextInputInner::new();
|
|
||||||
text_input.quick_assign(move |text_input, event, mut dispatch_data| {
|
|
||||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
|
||||||
handlers::handle_text_input(text_input, &mut text_input_inner, event, winit_state);
|
|
||||||
});
|
|
||||||
|
|
||||||
let text_input: Attached<ZwpTextInputV3> = text_input.into();
|
|
||||||
|
|
||||||
Self { text_input }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for TextInput {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.text_input.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TextInputInner {
|
|
||||||
/// Currently focused surface.
|
|
||||||
target_window_id: Option<WindowId>,
|
|
||||||
|
|
||||||
/// Pending commit event which will be dispatched on `text_input_v3::Done`.
|
|
||||||
pending_commit: Option<String>,
|
|
||||||
|
|
||||||
/// Pending preedit event which will be dispatched on `text_input_v3::Done`.
|
|
||||||
pending_preedit: Option<Preedit>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Preedit {
|
|
||||||
text: String,
|
|
||||||
cursor_begin: Option<usize>,
|
|
||||||
cursor_end: Option<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextInputInner {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
target_window_id: None,
|
|
||||||
pending_commit: None,
|
|
||||||
pending_preedit: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
//! Various handlers for touch events.
|
|
||||||
|
|
||||||
use sctk::reexports::client::protocol::wl_touch::Event as TouchEvent;
|
|
||||||
|
|
||||||
use crate::dpi::LogicalPosition;
|
|
||||||
use crate::event::{TouchPhase, WindowEvent};
|
|
||||||
|
|
||||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
|
||||||
use crate::platform_impl::wayland::{self, DeviceId};
|
|
||||||
|
|
||||||
use super::{TouchInner, TouchPoint};
|
|
||||||
|
|
||||||
/// Handle WlTouch events.
|
|
||||||
#[inline]
|
|
||||||
pub(super) fn handle_touch(
|
|
||||||
event: TouchEvent,
|
|
||||||
inner: &mut TouchInner,
|
|
||||||
winit_state: &mut WinitState,
|
|
||||||
) {
|
|
||||||
let event_sink = &mut winit_state.event_sink;
|
|
||||||
|
|
||||||
match event {
|
|
||||||
TouchEvent::Down {
|
|
||||||
surface, id, x, y, ..
|
|
||||||
} => {
|
|
||||||
let window_id = wayland::make_wid(&surface);
|
|
||||||
if !winit_state.window_map.contains_key(&window_id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let scale_factor = sctk::get_surface_scale_factor(&surface) as f64;
|
|
||||||
let position = LogicalPosition::new(x, y);
|
|
||||||
|
|
||||||
event_sink.push_window_event(
|
|
||||||
WindowEvent::Touch(crate::event::Touch {
|
|
||||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
|
||||||
DeviceId,
|
|
||||||
)),
|
|
||||||
phase: TouchPhase::Started,
|
|
||||||
location: position.to_physical(scale_factor),
|
|
||||||
force: None, // TODO
|
|
||||||
id: id as u64,
|
|
||||||
}),
|
|
||||||
window_id,
|
|
||||||
);
|
|
||||||
|
|
||||||
// For `TouchEvent::Up` we don't receive a position, so we're tracking active
|
|
||||||
// touch points. Update either a known touch id or register a new one.
|
|
||||||
if let Some(i) = inner.touch_points.iter().position(|p| p.id == id) {
|
|
||||||
inner.touch_points[i].position = position;
|
|
||||||
} else {
|
|
||||||
inner
|
|
||||||
.touch_points
|
|
||||||
.push(TouchPoint::new(surface, position, id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TouchEvent::Up { id, .. } => {
|
|
||||||
let touch_point = match inner.touch_points.iter().find(|p| p.id == id) {
|
|
||||||
Some(touch_point) => touch_point,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let scale_factor = sctk::get_surface_scale_factor(&touch_point.surface) as f64;
|
|
||||||
let location = touch_point.position.to_physical(scale_factor);
|
|
||||||
let window_id = wayland::make_wid(&touch_point.surface);
|
|
||||||
|
|
||||||
event_sink.push_window_event(
|
|
||||||
WindowEvent::Touch(crate::event::Touch {
|
|
||||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
|
||||||
DeviceId,
|
|
||||||
)),
|
|
||||||
phase: TouchPhase::Ended,
|
|
||||||
location,
|
|
||||||
force: None, // TODO
|
|
||||||
id: id as u64,
|
|
||||||
}),
|
|
||||||
window_id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
TouchEvent::Motion { id, x, y, .. } => {
|
|
||||||
let touch_point = match inner.touch_points.iter_mut().find(|p| p.id == id) {
|
|
||||||
Some(touch_point) => touch_point,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
touch_point.position = LogicalPosition::new(x, y);
|
|
||||||
|
|
||||||
let scale_factor = sctk::get_surface_scale_factor(&touch_point.surface) as f64;
|
|
||||||
let location = touch_point.position.to_physical(scale_factor);
|
|
||||||
let window_id = wayland::make_wid(&touch_point.surface);
|
|
||||||
|
|
||||||
event_sink.push_window_event(
|
|
||||||
WindowEvent::Touch(crate::event::Touch {
|
|
||||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
|
||||||
DeviceId,
|
|
||||||
)),
|
|
||||||
phase: TouchPhase::Moved,
|
|
||||||
location,
|
|
||||||
force: None, // TODO
|
|
||||||
id: id as u64,
|
|
||||||
}),
|
|
||||||
window_id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
TouchEvent::Frame => (),
|
|
||||||
TouchEvent::Cancel => {
|
|
||||||
for touch_point in inner.touch_points.drain(..) {
|
|
||||||
let scale_factor = sctk::get_surface_scale_factor(&touch_point.surface) as f64;
|
|
||||||
let location = touch_point.position.to_physical(scale_factor);
|
|
||||||
let window_id = wayland::make_wid(&touch_point.surface);
|
|
||||||
|
|
||||||
event_sink.push_window_event(
|
|
||||||
WindowEvent::Touch(crate::event::Touch {
|
|
||||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
|
||||||
DeviceId,
|
|
||||||
)),
|
|
||||||
phase: TouchPhase::Cancelled,
|
|
||||||
location,
|
|
||||||
force: None, // TODO
|
|
||||||
id: touch_point.id as u64,
|
|
||||||
}),
|
|
||||||
window_id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
//! Touch handling.
|
|
||||||
|
|
||||||
use sctk::reexports::client::protocol::wl_seat::WlSeat;
|
|
||||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
|
||||||
use sctk::reexports::client::protocol::wl_touch::WlTouch;
|
|
||||||
use sctk::reexports::client::Attached;
|
|
||||||
|
|
||||||
use crate::dpi::LogicalPosition;
|
|
||||||
|
|
||||||
use crate::platform_impl::wayland::event_loop::WinitState;
|
|
||||||
|
|
||||||
mod handlers;
|
|
||||||
|
|
||||||
/// Wrapper around touch to handle release.
|
|
||||||
pub struct Touch {
|
|
||||||
/// Proxy to touch.
|
|
||||||
touch: WlTouch,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Touch {
|
|
||||||
pub fn new(seat: &Attached<WlSeat>) -> Self {
|
|
||||||
let touch = seat.get_touch();
|
|
||||||
let mut inner = TouchInner::new();
|
|
||||||
|
|
||||||
touch.quick_assign(move |_, event, mut dispatch_data| {
|
|
||||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
|
||||||
handlers::handle_touch(event, &mut inner, winit_state);
|
|
||||||
});
|
|
||||||
|
|
||||||
Self {
|
|
||||||
touch: touch.detach(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Touch {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if self.touch.as_ref().version() >= 3 {
|
|
||||||
self.touch.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The data used by touch handlers.
|
|
||||||
pub(super) struct TouchInner {
|
|
||||||
/// Current touch points.
|
|
||||||
touch_points: Vec<TouchPoint>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TouchInner {
|
|
||||||
fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
touch_points: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Location of touch press.
|
|
||||||
pub(super) struct TouchPoint {
|
|
||||||
/// A surface where the touch point is located.
|
|
||||||
surface: WlSurface,
|
|
||||||
|
|
||||||
/// Location of the touch point.
|
|
||||||
position: LogicalPosition<f64>,
|
|
||||||
|
|
||||||
/// Id.
|
|
||||||
id: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TouchPoint {
|
|
||||||
pub fn new(surface: WlSurface, position: LogicalPosition<f64>, id: i32) -> Self {
|
|
||||||
Self {
|
|
||||||
surface,
|
|
||||||
position,
|
|
||||||
id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,618 +0,0 @@
|
|||||||
use std::collections::VecDeque;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use sctk::reexports::client::protocol::wl_surface::WlSurface;
|
|
||||||
use sctk::reexports::client::Display;
|
|
||||||
|
|
||||||
use sctk::reexports::calloop;
|
|
||||||
|
|
||||||
use raw_window_handle::{
|
|
||||||
RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle,
|
|
||||||
};
|
|
||||||
use sctk::window::Decorations;
|
|
||||||
|
|
||||||
use crate::dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size};
|
|
||||||
use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError};
|
|
||||||
use crate::monitor::MonitorHandle as RootMonitorHandle;
|
|
||||||
use crate::platform_impl::{
|
|
||||||
MonitorHandle as PlatformMonitorHandle, OsError,
|
|
||||||
PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
|
|
||||||
};
|
|
||||||
use crate::window::{
|
|
||||||
CursorGrabMode, CursorIcon, Fullscreen, Theme, UserAttentionType, WindowAttributes,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::env::WindowingFeatures;
|
|
||||||
use super::event_loop::WinitState;
|
|
||||||
use super::output::{MonitorHandle, OutputManagerHandle};
|
|
||||||
use super::{EventLoopWindowTarget, WindowId};
|
|
||||||
|
|
||||||
pub mod shim;
|
|
||||||
|
|
||||||
use shim::{WindowHandle, WindowRequest, WindowUpdate};
|
|
||||||
|
|
||||||
#[cfg(feature = "sctk-adwaita")]
|
|
||||||
pub type WinitFrame = sctk_adwaita::AdwaitaFrame;
|
|
||||||
#[cfg(not(feature = "sctk-adwaita"))]
|
|
||||||
pub type WinitFrame = sctk::window::FallbackFrame;
|
|
||||||
|
|
||||||
#[cfg(feature = "sctk-adwaita")]
|
|
||||||
const WAYLAND_CSD_THEME_ENV_VAR: &str = "WINIT_WAYLAND_CSD_THEME";
|
|
||||||
|
|
||||||
pub struct Window {
|
|
||||||
/// Window id.
|
|
||||||
window_id: WindowId,
|
|
||||||
|
|
||||||
/// The Wayland display.
|
|
||||||
display: Display,
|
|
||||||
|
|
||||||
/// The underlying wl_surface.
|
|
||||||
surface: WlSurface,
|
|
||||||
|
|
||||||
/// The current window size.
|
|
||||||
size: Arc<Mutex<LogicalSize<u32>>>,
|
|
||||||
|
|
||||||
/// A handle to output manager.
|
|
||||||
output_manager_handle: OutputManagerHandle,
|
|
||||||
|
|
||||||
/// Event loop proxy to wake it up.
|
|
||||||
event_loop_awakener: calloop::ping::Ping,
|
|
||||||
|
|
||||||
/// Fullscreen state.
|
|
||||||
fullscreen: Arc<AtomicBool>,
|
|
||||||
|
|
||||||
/// Maximized state.
|
|
||||||
maximized: Arc<AtomicBool>,
|
|
||||||
|
|
||||||
/// Available windowing features.
|
|
||||||
windowing_features: WindowingFeatures,
|
|
||||||
|
|
||||||
/// Requests that SCTK window should perform.
|
|
||||||
window_requests: Arc<Mutex<Vec<WindowRequest>>>,
|
|
||||||
|
|
||||||
/// Whether the window is resizeable.
|
|
||||||
resizeable: AtomicBool,
|
|
||||||
|
|
||||||
/// Whether the window is decorated.
|
|
||||||
decorated: AtomicBool,
|
|
||||||
|
|
||||||
/// Grabbing mode.
|
|
||||||
cursor_grab_mode: Mutex<CursorGrabMode>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Window {
|
|
||||||
pub(crate) fn new<T>(
|
|
||||||
event_loop_window_target: &EventLoopWindowTarget<T>,
|
|
||||||
attributes: WindowAttributes,
|
|
||||||
platform_attributes: PlatformAttributes,
|
|
||||||
) -> Result<Self, RootOsError> {
|
|
||||||
let surface = event_loop_window_target
|
|
||||||
.env
|
|
||||||
.create_surface_with_scale_callback(move |scale, surface, mut dispatch_data| {
|
|
||||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
|
||||||
|
|
||||||
// Get the window that received the event.
|
|
||||||
let window_id = super::make_wid(&surface);
|
|
||||||
let mut window_update = winit_state.window_updates.get_mut(&window_id).unwrap();
|
|
||||||
|
|
||||||
// Set pending scale factor.
|
|
||||||
window_update.scale_factor = Some(scale);
|
|
||||||
window_update.redraw_requested = true;
|
|
||||||
|
|
||||||
surface.set_buffer_scale(scale);
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
|
|
||||||
let scale_factor = sctk::get_surface_scale_factor(&surface);
|
|
||||||
|
|
||||||
let window_id = super::make_wid(&surface);
|
|
||||||
let maximized = Arc::new(AtomicBool::new(false));
|
|
||||||
let maximized_clone = maximized.clone();
|
|
||||||
let fullscreen = Arc::new(AtomicBool::new(false));
|
|
||||||
let fullscreen_clone = fullscreen.clone();
|
|
||||||
|
|
||||||
let (width, height) = attributes
|
|
||||||
.inner_size
|
|
||||||
.map(|size| size.to_logical::<f64>(scale_factor as f64).into())
|
|
||||||
.unwrap_or((800, 600));
|
|
||||||
|
|
||||||
let theme_manager = event_loop_window_target.theme_manager.clone();
|
|
||||||
let mut window = event_loop_window_target
|
|
||||||
.env
|
|
||||||
.create_window::<WinitFrame, _>(
|
|
||||||
surface.clone(),
|
|
||||||
Some(theme_manager),
|
|
||||||
(width, height),
|
|
||||||
move |event, mut dispatch_data| {
|
|
||||||
use sctk::window::{Event, State};
|
|
||||||
|
|
||||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
|
||||||
let mut window_update = winit_state.window_updates.get_mut(&window_id).unwrap();
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::Refresh => {
|
|
||||||
window_update.refresh_frame = true;
|
|
||||||
}
|
|
||||||
Event::Configure { new_size, states } => {
|
|
||||||
let is_maximized = states.contains(&State::Maximized);
|
|
||||||
maximized_clone.store(is_maximized, Ordering::Relaxed);
|
|
||||||
let is_fullscreen = states.contains(&State::Fullscreen);
|
|
||||||
fullscreen_clone.store(is_fullscreen, Ordering::Relaxed);
|
|
||||||
|
|
||||||
window_update.refresh_frame = true;
|
|
||||||
window_update.redraw_requested = true;
|
|
||||||
if let Some((w, h)) = new_size {
|
|
||||||
window_update.size = Some(LogicalSize::new(w, h));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::Close => {
|
|
||||||
window_update.close_window = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.map_err(|_| os_error!(OsError::WaylandMisc("failed to create window.")))?;
|
|
||||||
|
|
||||||
// Set CSD frame config
|
|
||||||
#[cfg(feature = "sctk-adwaita")]
|
|
||||||
{
|
|
||||||
let theme = platform_attributes.csd_theme.unwrap_or_else(|| {
|
|
||||||
let env = std::env::var(WAYLAND_CSD_THEME_ENV_VAR).unwrap_or_default();
|
|
||||||
match env.to_lowercase().as_str() {
|
|
||||||
"dark" => Theme::Dark,
|
|
||||||
_ => Theme::Light,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
window.set_frame_config(theme.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set decorations.
|
|
||||||
if attributes.decorations {
|
|
||||||
window.set_decorate(Decorations::FollowServer);
|
|
||||||
} else {
|
|
||||||
window.set_decorate(Decorations::None);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Min dimensions.
|
|
||||||
let min_size = attributes
|
|
||||||
.min_inner_size
|
|
||||||
.map(|size| size.to_logical::<f64>(scale_factor as f64).into());
|
|
||||||
window.set_min_size(min_size);
|
|
||||||
|
|
||||||
// Max dimensions.
|
|
||||||
let max_size = attributes
|
|
||||||
.max_inner_size
|
|
||||||
.map(|size| size.to_logical::<f64>(scale_factor as f64).into());
|
|
||||||
window.set_max_size(max_size);
|
|
||||||
|
|
||||||
// Set Wayland specific window attributes.
|
|
||||||
if let Some(name) = platform_attributes.name {
|
|
||||||
window.set_app_id(name.general);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set common window attributes.
|
|
||||||
//
|
|
||||||
// We set resizable after other attributes, since it touches min and max size under
|
|
||||||
// the hood.
|
|
||||||
window.set_resizable(attributes.resizable);
|
|
||||||
window.set_title(attributes.title);
|
|
||||||
|
|
||||||
// Set fullscreen/maximized if so was requested.
|
|
||||||
match attributes.fullscreen {
|
|
||||||
Some(Fullscreen::Exclusive(_)) => {
|
|
||||||
warn!("`Fullscreen::Exclusive` is ignored on Wayland")
|
|
||||||
}
|
|
||||||
Some(Fullscreen::Borderless(monitor)) => {
|
|
||||||
let monitor =
|
|
||||||
monitor.and_then(|RootMonitorHandle { inner: monitor }| match monitor {
|
|
||||||
PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy),
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
PlatformMonitorHandle::X(_) => None,
|
|
||||||
});
|
|
||||||
|
|
||||||
window.set_fullscreen(monitor.as_ref());
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
if attributes.maximized {
|
|
||||||
window.set_maximized();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Without this commit here at least on kwin 5.23.3 the initial configure
|
|
||||||
// will have a size (1,1), the second configure including the decoration
|
|
||||||
// mode will have the min_size as its size. With this commit the initial
|
|
||||||
// configure will have no size, the application will draw it's content
|
|
||||||
// with the initial size and everything works as expected afterwards.
|
|
||||||
//
|
|
||||||
// The window commit must be after setting on top level properties, but right before any
|
|
||||||
// buffer attachments commits.
|
|
||||||
window.surface().commit();
|
|
||||||
|
|
||||||
let size = Arc::new(Mutex::new(LogicalSize::new(width, height)));
|
|
||||||
|
|
||||||
// We should trigger redraw and commit the surface for the newly created window.
|
|
||||||
let mut window_update = WindowUpdate::new();
|
|
||||||
window_update.refresh_frame = true;
|
|
||||||
window_update.redraw_requested = true;
|
|
||||||
|
|
||||||
let window_id = super::make_wid(&surface);
|
|
||||||
let window_requests = Arc::new(Mutex::new(Vec::with_capacity(64)));
|
|
||||||
|
|
||||||
// Create a handle that performs all the requests on underlying sctk a window.
|
|
||||||
let window_handle = WindowHandle::new(
|
|
||||||
&event_loop_window_target.env,
|
|
||||||
window,
|
|
||||||
size.clone(),
|
|
||||||
window_requests.clone(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Set resizable state, so we can determine how to handle `Window::set_inner_size`.
|
|
||||||
window_handle.is_resizable.set(attributes.resizable);
|
|
||||||
|
|
||||||
let mut winit_state = event_loop_window_target.state.borrow_mut();
|
|
||||||
|
|
||||||
winit_state.window_map.insert(window_id, window_handle);
|
|
||||||
|
|
||||||
// On Wayland window doesn't have Focus by default and it'll get it later on. So be
|
|
||||||
// explicit here.
|
|
||||||
winit_state
|
|
||||||
.event_sink
|
|
||||||
.push_window_event(crate::event::WindowEvent::Focused(false), window_id);
|
|
||||||
|
|
||||||
winit_state
|
|
||||||
.window_updates
|
|
||||||
.insert(window_id, WindowUpdate::new());
|
|
||||||
|
|
||||||
let windowing_features = event_loop_window_target.windowing_features;
|
|
||||||
|
|
||||||
// To make our window usable for drawing right away we must `ack` a `configure`
|
|
||||||
// from the server, the acking part here is done by SCTK window frame, so we just
|
|
||||||
// need to sync with server so it'll be done automatically for us.
|
|
||||||
{
|
|
||||||
let mut wayland_source = event_loop_window_target.wayland_dispatcher.as_source_mut();
|
|
||||||
let event_queue = wayland_source.queue();
|
|
||||||
let _ = event_queue.sync_roundtrip(&mut *winit_state, |_, _, _| unreachable!());
|
|
||||||
}
|
|
||||||
|
|
||||||
// We all praise GNOME for these 3 lines of pure magic. If we don't do that,
|
|
||||||
// GNOME will shrink our window a bit for the size of the decorations. I guess it
|
|
||||||
// happens because we haven't committed them with buffers to the server.
|
|
||||||
let window_handle = winit_state.window_map.get_mut(&window_id).unwrap();
|
|
||||||
window_handle.window.refresh();
|
|
||||||
|
|
||||||
let output_manager_handle = event_loop_window_target.output_manager.handle();
|
|
||||||
|
|
||||||
let window = Self {
|
|
||||||
window_id,
|
|
||||||
surface,
|
|
||||||
display: event_loop_window_target.display.clone(),
|
|
||||||
output_manager_handle,
|
|
||||||
size,
|
|
||||||
window_requests,
|
|
||||||
event_loop_awakener: event_loop_window_target.event_loop_awakener.clone(),
|
|
||||||
fullscreen,
|
|
||||||
maximized,
|
|
||||||
windowing_features,
|
|
||||||
resizeable: AtomicBool::new(attributes.resizable),
|
|
||||||
decorated: AtomicBool::new(attributes.decorations),
|
|
||||||
cursor_grab_mode: Mutex::new(CursorGrabMode::None),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(window)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Window {
|
|
||||||
#[inline]
|
|
||||||
pub fn id(&self) -> WindowId {
|
|
||||||
self.window_id
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_title(&self, title: &str) {
|
|
||||||
self.send_request(WindowRequest::Title(title.to_owned()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_visible(&self, _visible: bool) {
|
|
||||||
// Not possible on Wayland.
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn is_visible(&self) -> Option<bool> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
|
||||||
Err(NotSupportedError::new())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
|
||||||
Err(NotSupportedError::new())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_outer_position(&self, _: Position) {
|
|
||||||
// Not possible on Wayland.
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
|
||||||
self.size
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.to_physical(self.scale_factor() as f64)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn request_redraw(&self) {
|
|
||||||
self.send_request(WindowRequest::Redraw);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
|
||||||
self.size
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.to_physical(self.scale_factor() as f64)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_inner_size(&self, size: Size) {
|
|
||||||
let scale_factor = self.scale_factor() as f64;
|
|
||||||
|
|
||||||
let size = size.to_logical::<u32>(scale_factor);
|
|
||||||
*self.size.lock().unwrap() = size;
|
|
||||||
|
|
||||||
self.send_request(WindowRequest::FrameSize(size));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
|
|
||||||
let scale_factor = self.scale_factor() as f64;
|
|
||||||
let size = dimensions.map(|size| size.to_logical::<u32>(scale_factor));
|
|
||||||
|
|
||||||
self.send_request(WindowRequest::MinSize(size));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
|
|
||||||
let scale_factor = self.scale_factor() as f64;
|
|
||||||
let size = dimensions.map(|size| size.to_logical::<u32>(scale_factor));
|
|
||||||
|
|
||||||
self.send_request(WindowRequest::MaxSize(size));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_resizable(&self, resizable: bool) {
|
|
||||||
self.resizeable.store(resizable, Ordering::Relaxed);
|
|
||||||
self.send_request(WindowRequest::Resizeable(resizable));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn is_resizable(&self) -> bool {
|
|
||||||
self.resizeable.load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn scale_factor(&self) -> u32 {
|
|
||||||
// The scale factor from `get_surface_scale_factor` is always greater than zero, so
|
|
||||||
// u32 conversion is safe.
|
|
||||||
sctk::get_surface_scale_factor(&self.surface) as u32
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_decorations(&self, decorate: bool) {
|
|
||||||
self.decorated.store(decorate, Ordering::Relaxed);
|
|
||||||
self.send_request(WindowRequest::Decorate(decorate));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn is_decorated(&self) -> bool {
|
|
||||||
self.decorated.load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_csd_theme(&self, theme: Theme) {
|
|
||||||
self.send_request(WindowRequest::CsdThemeVariant(theme));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_minimized(&self, minimized: bool) {
|
|
||||||
// You can't unminimize the window on Wayland.
|
|
||||||
if !minimized {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.send_request(WindowRequest::Minimize);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn is_maximized(&self) -> bool {
|
|
||||||
self.maximized.load(Ordering::Relaxed)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_maximized(&self, maximized: bool) {
|
|
||||||
self.send_request(WindowRequest::Maximize(maximized));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
|
||||||
if self.fullscreen.load(Ordering::Relaxed) {
|
|
||||||
let current_monitor = self.current_monitor().map(|monitor| RootMonitorHandle {
|
|
||||||
inner: PlatformMonitorHandle::Wayland(monitor),
|
|
||||||
});
|
|
||||||
|
|
||||||
Some(Fullscreen::Borderless(current_monitor))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
|
||||||
let fullscreen_request = match fullscreen {
|
|
||||||
Some(Fullscreen::Exclusive(_)) => {
|
|
||||||
warn!("`Fullscreen::Exclusive` is ignored on Wayland");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Some(Fullscreen::Borderless(monitor)) => {
|
|
||||||
let monitor =
|
|
||||||
monitor.and_then(|RootMonitorHandle { inner: monitor }| match monitor {
|
|
||||||
PlatformMonitorHandle::Wayland(monitor) => Some(monitor.proxy),
|
|
||||||
#[cfg(feature = "x11")]
|
|
||||||
PlatformMonitorHandle::X(_) => None,
|
|
||||||
});
|
|
||||||
|
|
||||||
WindowRequest::Fullscreen(monitor)
|
|
||||||
}
|
|
||||||
None => WindowRequest::UnsetFullscreen,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.send_request(fullscreen_request);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
|
||||||
self.send_request(WindowRequest::NewCursorIcon(cursor));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_cursor_visible(&self, visible: bool) {
|
|
||||||
self.send_request(WindowRequest::ShowCursor(visible));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
|
|
||||||
if !self.windowing_features.pointer_constraints() {
|
|
||||||
if mode == CursorGrabMode::None {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
return Err(ExternalError::NotSupported(NotSupportedError::new()));
|
|
||||||
}
|
|
||||||
|
|
||||||
*self.cursor_grab_mode.lock().unwrap() = mode;
|
|
||||||
self.send_request(WindowRequest::SetCursorGrabMode(mode));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
|
|
||||||
if !self.windowing_features.xdg_activation() {
|
|
||||||
warn!("`request_user_attention` isn't supported");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.send_request(WindowRequest::Attention(request_type));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> {
|
|
||||||
// Positon can be set only for locked cursor.
|
|
||||||
if *self.cursor_grab_mode.lock().unwrap() != CursorGrabMode::Locked {
|
|
||||||
return Err(ExternalError::Os(os_error!(OsError::WaylandMisc(
|
|
||||||
"cursor position can be set only for locked cursor."
|
|
||||||
))));
|
|
||||||
}
|
|
||||||
|
|
||||||
let scale_factor = self.scale_factor() as f64;
|
|
||||||
let position = position.to_logical(scale_factor);
|
|
||||||
self.send_request(WindowRequest::SetLockedCursorPosition(position));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn drag_window(&self) -> Result<(), ExternalError> {
|
|
||||||
self.send_request(WindowRequest::DragWindow);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> {
|
|
||||||
self.send_request(WindowRequest::PassthroughMouseInput(!hittest));
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_ime_position(&self, position: Position) {
|
|
||||||
let scale_factor = self.scale_factor() as f64;
|
|
||||||
let position = position.to_logical(scale_factor);
|
|
||||||
self.send_request(WindowRequest::ImePosition(position));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_ime_allowed(&self, allowed: bool) {
|
|
||||||
self.send_request(WindowRequest::AllowIme(allowed));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn display(&self) -> &Display {
|
|
||||||
&self.display
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn surface(&self) -> &WlSurface {
|
|
||||||
&self.surface
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn current_monitor(&self) -> Option<MonitorHandle> {
|
|
||||||
let output = sctk::get_surface_outputs(&self.surface).last()?.clone();
|
|
||||||
Some(MonitorHandle::new(output))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
|
||||||
self.output_manager_handle.available_outputs()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
|
||||||
let mut window_handle = WaylandWindowHandle::empty();
|
|
||||||
window_handle.surface = self.surface.as_ref().c_ptr() as *mut _;
|
|
||||||
RawWindowHandle::Wayland(window_handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
|
||||||
let mut display_handle = WaylandDisplayHandle::empty();
|
|
||||||
display_handle.display = self.display.get_display_ptr() as *mut _;
|
|
||||||
RawDisplayHandle::Wayland(display_handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn send_request(&self, request: WindowRequest) {
|
|
||||||
self.window_requests.lock().unwrap().push(request);
|
|
||||||
self.event_loop_awakener.ping();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Window {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.send_request(WindowRequest::Close);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "sctk-adwaita")]
|
|
||||||
impl From<Theme> for sctk_adwaita::FrameConfig {
|
|
||||||
fn from(theme: Theme) -> Self {
|
|
||||||
match theme {
|
|
||||||
Theme::Light => sctk_adwaita::FrameConfig::light(),
|
|
||||||
Theme::Dark => sctk_adwaita::FrameConfig::dark(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,577 +0,0 @@
|
|||||||
use std::cell::Cell;
|
|
||||||
use std::mem::ManuallyDrop;
|
|
||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use sctk::reexports::client::protocol::wl_compositor::WlCompositor;
|
|
||||||
use sctk::reexports::client::protocol::wl_output::WlOutput;
|
|
||||||
use sctk::reexports::client::Attached;
|
|
||||||
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_token_v1;
|
|
||||||
use sctk::reexports::protocols::staging::xdg_activation::v1::client::xdg_activation_v1::XdgActivationV1;
|
|
||||||
|
|
||||||
use sctk::environment::Environment;
|
|
||||||
use sctk::window::{Decorations, Window};
|
|
||||||
|
|
||||||
use crate::dpi::{LogicalPosition, LogicalSize};
|
|
||||||
|
|
||||||
use crate::event::{Ime, WindowEvent};
|
|
||||||
use crate::platform_impl::wayland;
|
|
||||||
use crate::platform_impl::wayland::env::WinitEnv;
|
|
||||||
use crate::platform_impl::wayland::event_loop::{EventSink, WinitState};
|
|
||||||
use crate::platform_impl::wayland::seat::pointer::WinitPointer;
|
|
||||||
use crate::platform_impl::wayland::seat::text_input::TextInputHandler;
|
|
||||||
use crate::platform_impl::wayland::WindowId;
|
|
||||||
use crate::window::{CursorGrabMode, CursorIcon, Theme, UserAttentionType};
|
|
||||||
|
|
||||||
use super::WinitFrame;
|
|
||||||
|
|
||||||
/// A request to SCTK window from Winit window.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum WindowRequest {
|
|
||||||
/// Set fullscreen.
|
|
||||||
///
|
|
||||||
/// Passing `None` will set it on the current monitor.
|
|
||||||
Fullscreen(Option<WlOutput>),
|
|
||||||
|
|
||||||
/// Unset fullscreen.
|
|
||||||
UnsetFullscreen,
|
|
||||||
|
|
||||||
/// Show cursor for the certain window or not.
|
|
||||||
ShowCursor(bool),
|
|
||||||
|
|
||||||
/// Change the cursor icon.
|
|
||||||
NewCursorIcon(CursorIcon),
|
|
||||||
|
|
||||||
/// Change cursor grabbing mode.
|
|
||||||
SetCursorGrabMode(CursorGrabMode),
|
|
||||||
|
|
||||||
/// Set cursor position.
|
|
||||||
SetLockedCursorPosition(LogicalPosition<u32>),
|
|
||||||
|
|
||||||
/// Drag window.
|
|
||||||
DragWindow,
|
|
||||||
|
|
||||||
/// Maximize the window.
|
|
||||||
Maximize(bool),
|
|
||||||
|
|
||||||
/// Minimize the window.
|
|
||||||
Minimize,
|
|
||||||
|
|
||||||
/// Request decorations change.
|
|
||||||
Decorate(bool),
|
|
||||||
|
|
||||||
/// Request decorations change.
|
|
||||||
CsdThemeVariant(Theme),
|
|
||||||
|
|
||||||
/// Make the window resizeable.
|
|
||||||
Resizeable(bool),
|
|
||||||
|
|
||||||
/// Set the title for window.
|
|
||||||
Title(String),
|
|
||||||
|
|
||||||
/// Min size.
|
|
||||||
MinSize(Option<LogicalSize<u32>>),
|
|
||||||
|
|
||||||
/// Max size.
|
|
||||||
MaxSize(Option<LogicalSize<u32>>),
|
|
||||||
|
|
||||||
/// New frame size.
|
|
||||||
FrameSize(LogicalSize<u32>),
|
|
||||||
|
|
||||||
/// Set IME window position.
|
|
||||||
ImePosition(LogicalPosition<u32>),
|
|
||||||
|
|
||||||
/// Enable IME on the given window.
|
|
||||||
AllowIme(bool),
|
|
||||||
|
|
||||||
/// Request Attention.
|
|
||||||
///
|
|
||||||
/// `None` unsets the attention request.
|
|
||||||
Attention(Option<UserAttentionType>),
|
|
||||||
|
|
||||||
/// Passthrough mouse input to underlying windows.
|
|
||||||
PassthroughMouseInput(bool),
|
|
||||||
|
|
||||||
/// Redraw was requested.
|
|
||||||
Redraw,
|
|
||||||
|
|
||||||
/// Window should be closed.
|
|
||||||
Close,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pending update to a window from SCTK window.
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct WindowUpdate {
|
|
||||||
/// New window size.
|
|
||||||
pub size: Option<LogicalSize<u32>>,
|
|
||||||
|
|
||||||
/// New scale factor.
|
|
||||||
pub scale_factor: Option<i32>,
|
|
||||||
|
|
||||||
/// Whether `redraw` was requested.
|
|
||||||
pub redraw_requested: bool,
|
|
||||||
|
|
||||||
/// Wether the frame should be refreshed.
|
|
||||||
pub refresh_frame: bool,
|
|
||||||
|
|
||||||
/// Close the window.
|
|
||||||
pub close_window: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowUpdate {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
size: None,
|
|
||||||
scale_factor: None,
|
|
||||||
redraw_requested: false,
|
|
||||||
refresh_frame: false,
|
|
||||||
close_window: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn take(&mut self) -> Self {
|
|
||||||
let size = self.size.take();
|
|
||||||
let scale_factor = self.scale_factor.take();
|
|
||||||
|
|
||||||
let redraw_requested = self.redraw_requested;
|
|
||||||
self.redraw_requested = false;
|
|
||||||
|
|
||||||
let refresh_frame = self.refresh_frame;
|
|
||||||
self.refresh_frame = false;
|
|
||||||
|
|
||||||
let close_window = self.close_window;
|
|
||||||
self.close_window = false;
|
|
||||||
|
|
||||||
Self {
|
|
||||||
size,
|
|
||||||
scale_factor,
|
|
||||||
redraw_requested,
|
|
||||||
refresh_frame,
|
|
||||||
close_window,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A handle to perform operations on SCTK window
|
|
||||||
/// and react to events.
|
|
||||||
pub struct WindowHandle {
|
|
||||||
/// An actual window.
|
|
||||||
pub window: ManuallyDrop<Window<WinitFrame>>,
|
|
||||||
|
|
||||||
/// The current size of the window.
|
|
||||||
pub size: Arc<Mutex<LogicalSize<u32>>>,
|
|
||||||
|
|
||||||
/// A pending requests to SCTK window.
|
|
||||||
pub pending_window_requests: Arc<Mutex<Vec<WindowRequest>>>,
|
|
||||||
|
|
||||||
/// Current cursor icon.
|
|
||||||
pub cursor_icon: Cell<CursorIcon>,
|
|
||||||
|
|
||||||
/// Whether the window is resizable.
|
|
||||||
pub is_resizable: Cell<bool>,
|
|
||||||
|
|
||||||
/// Allow IME events for that window.
|
|
||||||
pub ime_allowed: Cell<bool>,
|
|
||||||
|
|
||||||
/// Visible cursor or not.
|
|
||||||
cursor_visible: Cell<bool>,
|
|
||||||
|
|
||||||
/// Cursor confined to the surface.
|
|
||||||
cursor_grab_mode: Cell<CursorGrabMode>,
|
|
||||||
|
|
||||||
/// Pointers over the current surface.
|
|
||||||
pointers: Vec<WinitPointer>,
|
|
||||||
|
|
||||||
/// Text inputs on the current surface.
|
|
||||||
text_inputs: Vec<TextInputHandler>,
|
|
||||||
|
|
||||||
/// XdgActivation object.
|
|
||||||
xdg_activation: Option<Attached<XdgActivationV1>>,
|
|
||||||
|
|
||||||
/// Indicator whether user attention is requested.
|
|
||||||
attention_requested: Cell<bool>,
|
|
||||||
|
|
||||||
/// Compositor
|
|
||||||
compositor: Attached<WlCompositor>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowHandle {
|
|
||||||
pub fn new(
|
|
||||||
env: &Environment<WinitEnv>,
|
|
||||||
window: Window<WinitFrame>,
|
|
||||||
size: Arc<Mutex<LogicalSize<u32>>>,
|
|
||||||
pending_window_requests: Arc<Mutex<Vec<WindowRequest>>>,
|
|
||||||
) -> Self {
|
|
||||||
let xdg_activation = env.get_global::<XdgActivationV1>();
|
|
||||||
// Unwrap is safe, since we can't create window without compositor anyway and won't be
|
|
||||||
// here.
|
|
||||||
let compositor = env.get_global::<WlCompositor>().unwrap();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
window: ManuallyDrop::new(window),
|
|
||||||
size,
|
|
||||||
pending_window_requests,
|
|
||||||
cursor_icon: Cell::new(CursorIcon::Default),
|
|
||||||
is_resizable: Cell::new(true),
|
|
||||||
cursor_grab_mode: Cell::new(CursorGrabMode::None),
|
|
||||||
cursor_visible: Cell::new(true),
|
|
||||||
pointers: Vec::new(),
|
|
||||||
text_inputs: Vec::new(),
|
|
||||||
xdg_activation,
|
|
||||||
attention_requested: Cell::new(false),
|
|
||||||
compositor,
|
|
||||||
ime_allowed: Cell::new(false),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_cursor_grab(&self, mode: CursorGrabMode) {
|
|
||||||
// The new requested state matches the current confine status, return.
|
|
||||||
let old_mode = self.cursor_grab_mode.replace(mode);
|
|
||||||
if old_mode == mode {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear old pointer data.
|
|
||||||
match old_mode {
|
|
||||||
CursorGrabMode::None => (),
|
|
||||||
CursorGrabMode::Confined => self.pointers.iter().for_each(|p| p.unconfine()),
|
|
||||||
CursorGrabMode::Locked => self.pointers.iter().for_each(|p| p.unlock()),
|
|
||||||
}
|
|
||||||
|
|
||||||
let surface = self.window.surface();
|
|
||||||
match mode {
|
|
||||||
CursorGrabMode::Locked => self.pointers.iter().for_each(|p| p.lock(surface)),
|
|
||||||
CursorGrabMode::Confined => self.pointers.iter().for_each(|p| p.confine(surface)),
|
|
||||||
CursorGrabMode::None => {
|
|
||||||
// Current lock/confine was already removed.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_locked_cursor_position(&self, position: LogicalPosition<u32>) {
|
|
||||||
// XXX the cursor locking is ensured inside `Window`.
|
|
||||||
self.pointers
|
|
||||||
.iter()
|
|
||||||
.for_each(|p| p.set_cursor_position(position.x, position.y));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_user_attention(&self, request_type: Option<UserAttentionType>) {
|
|
||||||
let xdg_activation = match self.xdg_activation.as_ref() {
|
|
||||||
None => return,
|
|
||||||
Some(xdg_activation) => xdg_activation,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Urgency is only removed by the compositor and there's no need to raise urgency when it
|
|
||||||
// was already raised.
|
|
||||||
if request_type.is_none() || self.attention_requested.get() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let xdg_activation_token = xdg_activation.get_activation_token();
|
|
||||||
let surface = self.window.surface();
|
|
||||||
let window_id = wayland::make_wid(surface);
|
|
||||||
let xdg_activation = xdg_activation.clone();
|
|
||||||
|
|
||||||
xdg_activation_token.quick_assign(move |xdg_token, event, mut dispatch_data| {
|
|
||||||
let token = match event {
|
|
||||||
xdg_activation_token_v1::Event::Done { token } => token,
|
|
||||||
_ => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let winit_state = dispatch_data.get::<WinitState>().unwrap();
|
|
||||||
let window_handle = match winit_state.window_map.get_mut(&window_id) {
|
|
||||||
Some(window_handle) => window_handle,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let surface = window_handle.window.surface();
|
|
||||||
xdg_activation.activate(token, surface);
|
|
||||||
|
|
||||||
// Mark that attention request was done and drop the token.
|
|
||||||
window_handle.attention_requested.replace(false);
|
|
||||||
xdg_token.destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
xdg_activation_token.set_surface(surface);
|
|
||||||
xdg_activation_token.commit();
|
|
||||||
self.attention_requested.replace(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pointer appeared over the window.
|
|
||||||
pub fn pointer_entered(&mut self, pointer: WinitPointer) {
|
|
||||||
let position = self.pointers.iter().position(|p| *p == pointer);
|
|
||||||
|
|
||||||
if position.is_none() {
|
|
||||||
let surface = self.window.surface();
|
|
||||||
match self.cursor_grab_mode.get() {
|
|
||||||
CursorGrabMode::None => (),
|
|
||||||
CursorGrabMode::Locked => pointer.lock(surface),
|
|
||||||
CursorGrabMode::Confined => pointer.confine(surface),
|
|
||||||
}
|
|
||||||
|
|
||||||
self.pointers.push(pointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply the current cursor style.
|
|
||||||
self.set_cursor_visible(self.cursor_visible.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pointer left the window.
|
|
||||||
pub fn pointer_left(&mut self, pointer: WinitPointer) {
|
|
||||||
let position = self.pointers.iter().position(|p| *p == pointer);
|
|
||||||
|
|
||||||
if let Some(position) = position {
|
|
||||||
let pointer = self.pointers.remove(position);
|
|
||||||
|
|
||||||
// Drop the grabbing mode.
|
|
||||||
match self.cursor_grab_mode.get() {
|
|
||||||
CursorGrabMode::None => (),
|
|
||||||
CursorGrabMode::Locked => pointer.unlock(),
|
|
||||||
CursorGrabMode::Confined => pointer.unconfine(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text_input_entered(&mut self, text_input: TextInputHandler) {
|
|
||||||
if !self.text_inputs.iter().any(|t| *t == text_input) {
|
|
||||||
self.text_inputs.push(text_input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text_input_left(&mut self, text_input: TextInputHandler) {
|
|
||||||
if let Some(position) = self.text_inputs.iter().position(|t| *t == text_input) {
|
|
||||||
self.text_inputs.remove(position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_ime_position(&self, position: LogicalPosition<u32>) {
|
|
||||||
// XXX This won't fly unless user will have a way to request IME window per seat, since
|
|
||||||
// the ime windows will be overlapping, but winit doesn't expose API to specify for
|
|
||||||
// which seat we're setting IME position.
|
|
||||||
let (x, y) = (position.x as i32, position.y as i32);
|
|
||||||
for text_input in self.text_inputs.iter() {
|
|
||||||
text_input.set_ime_position(x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn passthrough_mouse_input(&self, passthrough_mouse_input: bool) {
|
|
||||||
if passthrough_mouse_input {
|
|
||||||
let region = self.compositor.create_region();
|
|
||||||
region.add(0, 0, 0, 0);
|
|
||||||
self.window
|
|
||||||
.surface()
|
|
||||||
.set_input_region(Some(®ion.detach()));
|
|
||||||
region.destroy();
|
|
||||||
} else {
|
|
||||||
// Using `None` results in the entire window being clickable.
|
|
||||||
self.window.surface().set_input_region(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_ime_allowed(&self, allowed: bool, event_sink: &mut EventSink) {
|
|
||||||
if self.ime_allowed.get() == allowed {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.ime_allowed.replace(allowed);
|
|
||||||
let window_id = wayland::make_wid(self.window.surface());
|
|
||||||
|
|
||||||
for text_input in self.text_inputs.iter() {
|
|
||||||
text_input.set_input_allowed(allowed);
|
|
||||||
}
|
|
||||||
|
|
||||||
let event = if allowed {
|
|
||||||
WindowEvent::Ime(Ime::Enabled)
|
|
||||||
} else {
|
|
||||||
WindowEvent::Ime(Ime::Disabled)
|
|
||||||
};
|
|
||||||
|
|
||||||
event_sink.push_window_event(event, window_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_cursor_visible(&self, visible: bool) {
|
|
||||||
self.cursor_visible.replace(visible);
|
|
||||||
let cursor_icon = match visible {
|
|
||||||
true => Some(self.cursor_icon.get()),
|
|
||||||
false => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
for pointer in self.pointers.iter() {
|
|
||||||
pointer.set_cursor(cursor_icon)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_cursor_icon(&self, cursor_icon: CursorIcon) {
|
|
||||||
self.cursor_icon.replace(cursor_icon);
|
|
||||||
|
|
||||||
if !self.cursor_visible.get() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for pointer in self.pointers.iter() {
|
|
||||||
pointer.set_cursor(Some(cursor_icon));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn drag_window(&self) {
|
|
||||||
for pointer in self.pointers.iter() {
|
|
||||||
pointer.drag_window(&self.window);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn handle_window_requests(winit_state: &mut WinitState) {
|
|
||||||
let window_map = &mut winit_state.window_map;
|
|
||||||
let window_updates = &mut winit_state.window_updates;
|
|
||||||
let mut windows_to_close: Vec<WindowId> = Vec::new();
|
|
||||||
|
|
||||||
// Process the rest of the events.
|
|
||||||
for (window_id, window_handle) in window_map.iter_mut() {
|
|
||||||
let mut requests = window_handle.pending_window_requests.lock().unwrap();
|
|
||||||
let requests = requests.drain(..);
|
|
||||||
for request in requests {
|
|
||||||
match request {
|
|
||||||
WindowRequest::Fullscreen(fullscreen) => {
|
|
||||||
window_handle.window.set_fullscreen(fullscreen.as_ref());
|
|
||||||
}
|
|
||||||
WindowRequest::UnsetFullscreen => {
|
|
||||||
window_handle.window.unset_fullscreen();
|
|
||||||
}
|
|
||||||
WindowRequest::ShowCursor(show_cursor) => {
|
|
||||||
window_handle.set_cursor_visible(show_cursor);
|
|
||||||
}
|
|
||||||
WindowRequest::NewCursorIcon(cursor_icon) => {
|
|
||||||
window_handle.set_cursor_icon(cursor_icon);
|
|
||||||
}
|
|
||||||
WindowRequest::ImePosition(position) => {
|
|
||||||
window_handle.set_ime_position(position);
|
|
||||||
}
|
|
||||||
WindowRequest::AllowIme(allow) => {
|
|
||||||
let event_sink = &mut winit_state.event_sink;
|
|
||||||
window_handle.set_ime_allowed(allow, event_sink);
|
|
||||||
}
|
|
||||||
WindowRequest::SetCursorGrabMode(mode) => {
|
|
||||||
window_handle.set_cursor_grab(mode);
|
|
||||||
}
|
|
||||||
WindowRequest::SetLockedCursorPosition(position) => {
|
|
||||||
window_handle.set_locked_cursor_position(position);
|
|
||||||
}
|
|
||||||
WindowRequest::DragWindow => {
|
|
||||||
window_handle.drag_window();
|
|
||||||
}
|
|
||||||
WindowRequest::Maximize(maximize) => {
|
|
||||||
if maximize {
|
|
||||||
window_handle.window.set_maximized();
|
|
||||||
} else {
|
|
||||||
window_handle.window.unset_maximized();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WindowRequest::Minimize => {
|
|
||||||
window_handle.window.set_minimized();
|
|
||||||
}
|
|
||||||
WindowRequest::Decorate(decorate) => {
|
|
||||||
let decorations = match decorate {
|
|
||||||
true => Decorations::FollowServer,
|
|
||||||
false => Decorations::None,
|
|
||||||
};
|
|
||||||
|
|
||||||
window_handle.window.set_decorate(decorations);
|
|
||||||
|
|
||||||
// We should refresh the frame to apply decorations change.
|
|
||||||
let window_update = window_updates.get_mut(window_id).unwrap();
|
|
||||||
window_update.refresh_frame = true;
|
|
||||||
}
|
|
||||||
#[cfg(feature = "sctk-adwaita")]
|
|
||||||
WindowRequest::CsdThemeVariant(theme) => {
|
|
||||||
window_handle.window.set_frame_config(theme.into());
|
|
||||||
|
|
||||||
let window_update = window_updates.get_mut(window_id).unwrap();
|
|
||||||
window_update.refresh_frame = true;
|
|
||||||
}
|
|
||||||
#[cfg(not(feature = "sctk-adwaita"))]
|
|
||||||
WindowRequest::CsdThemeVariant(_) => {}
|
|
||||||
WindowRequest::Resizeable(resizeable) => {
|
|
||||||
window_handle.window.set_resizable(resizeable);
|
|
||||||
|
|
||||||
// We should refresh the frame to update button state.
|
|
||||||
let window_update = window_updates.get_mut(window_id).unwrap();
|
|
||||||
window_update.refresh_frame = true;
|
|
||||||
}
|
|
||||||
WindowRequest::Title(title) => {
|
|
||||||
window_handle.window.set_title(title);
|
|
||||||
|
|
||||||
// We should refresh the frame to draw new title.
|
|
||||||
let window_update = window_updates.get_mut(window_id).unwrap();
|
|
||||||
window_update.refresh_frame = true;
|
|
||||||
}
|
|
||||||
WindowRequest::MinSize(size) => {
|
|
||||||
let size = size.map(|size| (size.width, size.height));
|
|
||||||
window_handle.window.set_min_size(size);
|
|
||||||
|
|
||||||
let window_update = window_updates.get_mut(window_id).unwrap();
|
|
||||||
window_update.redraw_requested = true;
|
|
||||||
}
|
|
||||||
WindowRequest::MaxSize(size) => {
|
|
||||||
let size = size.map(|size| (size.width, size.height));
|
|
||||||
window_handle.window.set_max_size(size);
|
|
||||||
|
|
||||||
let window_update = window_updates.get_mut(window_id).unwrap();
|
|
||||||
window_update.redraw_requested = true;
|
|
||||||
}
|
|
||||||
WindowRequest::FrameSize(size) => {
|
|
||||||
if !window_handle.is_resizable.get() {
|
|
||||||
// On Wayland non-resizable window is achieved by setting both min and max
|
|
||||||
// size of the window to the same value.
|
|
||||||
let size = Some((size.width, size.height));
|
|
||||||
window_handle.window.set_max_size(size);
|
|
||||||
window_handle.window.set_min_size(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
window_handle.window.resize(size.width, size.height);
|
|
||||||
|
|
||||||
// We should refresh the frame after resize.
|
|
||||||
let window_update = window_updates.get_mut(window_id).unwrap();
|
|
||||||
window_update.refresh_frame = true;
|
|
||||||
}
|
|
||||||
WindowRequest::PassthroughMouseInput(passthrough) => {
|
|
||||||
window_handle.passthrough_mouse_input(passthrough);
|
|
||||||
|
|
||||||
let window_update = window_updates.get_mut(window_id).unwrap();
|
|
||||||
window_update.refresh_frame = true;
|
|
||||||
}
|
|
||||||
WindowRequest::Attention(request_type) => {
|
|
||||||
window_handle.set_user_attention(request_type);
|
|
||||||
}
|
|
||||||
WindowRequest::Redraw => {
|
|
||||||
let window_update = window_updates.get_mut(window_id).unwrap();
|
|
||||||
window_update.redraw_requested = true;
|
|
||||||
}
|
|
||||||
WindowRequest::Close => {
|
|
||||||
// The window was requested to be closed.
|
|
||||||
windows_to_close.push(*window_id);
|
|
||||||
|
|
||||||
// Send event that the window was destroyed.
|
|
||||||
let event_sink = &mut winit_state.event_sink;
|
|
||||||
event_sink.push_window_event(WindowEvent::Destroyed, *window_id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the windows.
|
|
||||||
for window in windows_to_close {
|
|
||||||
let _ = window_map.remove(&window);
|
|
||||||
let _ = window_updates.remove(&window);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for WindowHandle {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
let surface = self.window.surface().clone();
|
|
||||||
// The window must be destroyed before wl_surface.
|
|
||||||
ManuallyDrop::drop(&mut self.window);
|
|
||||||
surface.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +0,0 @@
|
|||||||
use x11_dl::xmd::CARD32;
|
|
||||||
pub use x11_dl::{
|
|
||||||
error::OpenError, keysym::*, xcursor::*, xinput::*, xinput2::*, xlib::*, xlib_xcb::*,
|
|
||||||
xrandr::*, xrender::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Isn't defined by x11_dl
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
pub const IconicState: CARD32 = 3;
|
|
||||||
@@ -1,816 +0,0 @@
|
|||||||
#![cfg(any(
|
|
||||||
target_os = "linux",
|
|
||||||
target_os = "dragonfly",
|
|
||||||
target_os = "freebsd",
|
|
||||||
target_os = "netbsd",
|
|
||||||
target_os = "openbsd"
|
|
||||||
))]
|
|
||||||
|
|
||||||
mod dnd;
|
|
||||||
mod event_processor;
|
|
||||||
mod events;
|
|
||||||
pub mod ffi;
|
|
||||||
mod ime;
|
|
||||||
mod monitor;
|
|
||||||
pub mod util;
|
|
||||||
mod window;
|
|
||||||
mod xdisplay;
|
|
||||||
|
|
||||||
pub use self::{
|
|
||||||
monitor::{MonitorHandle, VideoMode},
|
|
||||||
window::UnownedWindow,
|
|
||||||
xdisplay::{XConnection, XError, XNotSupported},
|
|
||||||
};
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
cell::{Cell, RefCell},
|
|
||||||
collections::{HashMap, HashSet},
|
|
||||||
ffi::CStr,
|
|
||||||
mem::{self, MaybeUninit},
|
|
||||||
ops::Deref,
|
|
||||||
os::raw::*,
|
|
||||||
ptr,
|
|
||||||
rc::Rc,
|
|
||||||
slice,
|
|
||||||
sync::mpsc::{Receiver, Sender, TryRecvError},
|
|
||||||
sync::{mpsc, Arc, Weak},
|
|
||||||
time::{Duration, Instant},
|
|
||||||
};
|
|
||||||
|
|
||||||
use libc::{self, setlocale, LC_CTYPE};
|
|
||||||
|
|
||||||
use mio::{unix::SourceFd, Events, Interest, Poll, Token, Waker};
|
|
||||||
use raw_window_handle::{RawDisplayHandle, XlibDisplayHandle};
|
|
||||||
|
|
||||||
use self::{
|
|
||||||
dnd::{Dnd, DndState},
|
|
||||||
event_processor::EventProcessor,
|
|
||||||
ime::{Ime, ImeCreationError, ImeReceiver, ImeRequest, ImeSender},
|
|
||||||
util::modifiers::ModifierKeymap,
|
|
||||||
};
|
|
||||||
use crate::{
|
|
||||||
error::OsError as RootOsError,
|
|
||||||
event::{Event, StartCause},
|
|
||||||
event_loop::{
|
|
||||||
ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW,
|
|
||||||
},
|
|
||||||
platform_impl::{
|
|
||||||
platform::{sticky_exit_callback, WindowId},
|
|
||||||
PlatformSpecificWindowBuilderAttributes,
|
|
||||||
},
|
|
||||||
window::WindowAttributes,
|
|
||||||
};
|
|
||||||
|
|
||||||
const X_TOKEN: Token = Token(0);
|
|
||||||
const USER_REDRAW_TOKEN: Token = Token(1);
|
|
||||||
|
|
||||||
struct WakeSender<T> {
|
|
||||||
sender: Sender<T>,
|
|
||||||
waker: Arc<Waker>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PeekableReceiver<T> {
|
|
||||||
recv: Receiver<T>,
|
|
||||||
first: Option<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> PeekableReceiver<T> {
|
|
||||||
pub fn from_recv(recv: Receiver<T>) -> Self {
|
|
||||||
Self { recv, first: None }
|
|
||||||
}
|
|
||||||
pub fn has_incoming(&mut self) -> bool {
|
|
||||||
if self.first.is_some() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.recv.try_recv() {
|
|
||||||
Ok(v) => {
|
|
||||||
self.first = Some(v);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
Err(TryRecvError::Empty) => false,
|
|
||||||
Err(TryRecvError::Disconnected) => {
|
|
||||||
warn!("Channel was disconnected when checking incoming");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn try_recv(&mut self) -> Result<T, TryRecvError> {
|
|
||||||
if let Some(first) = self.first.take() {
|
|
||||||
return Ok(first);
|
|
||||||
}
|
|
||||||
self.recv.try_recv()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventLoopWindowTarget<T> {
|
|
||||||
xconn: Arc<XConnection>,
|
|
||||||
wm_delete_window: ffi::Atom,
|
|
||||||
net_wm_ping: ffi::Atom,
|
|
||||||
ime_sender: ImeSender,
|
|
||||||
root: ffi::Window,
|
|
||||||
ime: RefCell<Ime>,
|
|
||||||
windows: RefCell<HashMap<WindowId, Weak<UnownedWindow>>>,
|
|
||||||
redraw_sender: WakeSender<WindowId>,
|
|
||||||
device_event_filter: Cell<DeviceEventFilter>,
|
|
||||||
_marker: ::std::marker::PhantomData<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventLoop<T: 'static> {
|
|
||||||
poll: Poll,
|
|
||||||
waker: Arc<Waker>,
|
|
||||||
event_processor: EventProcessor<T>,
|
|
||||||
redraw_receiver: PeekableReceiver<WindowId>,
|
|
||||||
user_receiver: PeekableReceiver<T>, //waker.wake needs to be called whenever something gets sent
|
|
||||||
user_sender: Sender<T>,
|
|
||||||
target: Rc<RootELW<T>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventLoopProxy<T: 'static> {
|
|
||||||
user_sender: Sender<T>,
|
|
||||||
waker: Arc<Waker>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> Clone for EventLoopProxy<T> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
EventLoopProxy {
|
|
||||||
user_sender: self.user_sender.clone(),
|
|
||||||
waker: self.waker.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> EventLoop<T> {
|
|
||||||
pub fn new(xconn: Arc<XConnection>) -> EventLoop<T> {
|
|
||||||
let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) };
|
|
||||||
|
|
||||||
let wm_delete_window = unsafe { xconn.get_atom_unchecked(b"WM_DELETE_WINDOW\0") };
|
|
||||||
|
|
||||||
let net_wm_ping = unsafe { xconn.get_atom_unchecked(b"_NET_WM_PING\0") };
|
|
||||||
|
|
||||||
let dnd = Dnd::new(Arc::clone(&xconn))
|
|
||||||
.expect("Failed to call XInternAtoms when initializing drag and drop");
|
|
||||||
|
|
||||||
let (ime_sender, ime_receiver) = mpsc::channel();
|
|
||||||
let (ime_event_sender, ime_event_receiver) = mpsc::channel();
|
|
||||||
// Input methods will open successfully without setting the locale, but it won't be
|
|
||||||
// possible to actually commit pre-edit sequences.
|
|
||||||
unsafe {
|
|
||||||
// Remember default locale to restore it if target locale is unsupported
|
|
||||||
// by Xlib
|
|
||||||
let default_locale = setlocale(LC_CTYPE, ptr::null());
|
|
||||||
setlocale(LC_CTYPE, b"\0".as_ptr() as *const _);
|
|
||||||
|
|
||||||
// Check if set locale is supported by Xlib.
|
|
||||||
// If not, calls to some Xlib functions like `XSetLocaleModifiers`
|
|
||||||
// will fail.
|
|
||||||
let locale_supported = (xconn.xlib.XSupportsLocale)() == 1;
|
|
||||||
if !locale_supported {
|
|
||||||
let unsupported_locale = setlocale(LC_CTYPE, ptr::null());
|
|
||||||
warn!(
|
|
||||||
"Unsupported locale \"{}\". Restoring default locale \"{}\".",
|
|
||||||
CStr::from_ptr(unsupported_locale).to_string_lossy(),
|
|
||||||
CStr::from_ptr(default_locale).to_string_lossy()
|
|
||||||
);
|
|
||||||
// Restore default locale
|
|
||||||
setlocale(LC_CTYPE, default_locale);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let ime = RefCell::new({
|
|
||||||
let result = Ime::new(Arc::clone(&xconn), ime_event_sender);
|
|
||||||
if let Err(ImeCreationError::OpenFailure(ref state)) = result {
|
|
||||||
panic!("Failed to open input method: {:#?}", state);
|
|
||||||
}
|
|
||||||
result.expect("Failed to set input method destruction callback")
|
|
||||||
});
|
|
||||||
|
|
||||||
let randr_event_offset = xconn
|
|
||||||
.select_xrandr_input(root)
|
|
||||||
.expect("Failed to query XRandR extension");
|
|
||||||
|
|
||||||
let xi2ext = unsafe {
|
|
||||||
let mut ext = XExtension::default();
|
|
||||||
|
|
||||||
let res = (xconn.xlib.XQueryExtension)(
|
|
||||||
xconn.display,
|
|
||||||
b"XInputExtension\0".as_ptr() as *const c_char,
|
|
||||||
&mut ext.opcode,
|
|
||||||
&mut ext.first_event_id,
|
|
||||||
&mut ext.first_error_id,
|
|
||||||
);
|
|
||||||
|
|
||||||
if res == ffi::False {
|
|
||||||
panic!("X server missing XInput extension");
|
|
||||||
}
|
|
||||||
|
|
||||||
ext
|
|
||||||
};
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let mut xinput_major_ver = ffi::XI_2_Major;
|
|
||||||
let mut xinput_minor_ver = ffi::XI_2_Minor;
|
|
||||||
if (xconn.xinput2.XIQueryVersion)(
|
|
||||||
xconn.display,
|
|
||||||
&mut xinput_major_ver,
|
|
||||||
&mut xinput_minor_ver,
|
|
||||||
) != ffi::Success as libc::c_int
|
|
||||||
{
|
|
||||||
panic!(
|
|
||||||
"X server has XInput extension {}.{} but does not support XInput2",
|
|
||||||
xinput_major_ver, xinput_minor_ver,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
xconn.update_cached_wm_info(root);
|
|
||||||
|
|
||||||
let mut mod_keymap = ModifierKeymap::new();
|
|
||||||
mod_keymap.reset_from_x_connection(&xconn);
|
|
||||||
|
|
||||||
let poll = Poll::new().unwrap();
|
|
||||||
let waker = Arc::new(Waker::new(poll.registry(), USER_REDRAW_TOKEN).unwrap());
|
|
||||||
|
|
||||||
poll.registry()
|
|
||||||
.register(&mut SourceFd(&xconn.x11_fd), X_TOKEN, Interest::READABLE)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let (user_sender, user_channel) = std::sync::mpsc::channel();
|
|
||||||
let (redraw_sender, redraw_channel) = std::sync::mpsc::channel();
|
|
||||||
|
|
||||||
let window_target = EventLoopWindowTarget {
|
|
||||||
ime,
|
|
||||||
root,
|
|
||||||
windows: Default::default(),
|
|
||||||
_marker: ::std::marker::PhantomData,
|
|
||||||
ime_sender,
|
|
||||||
xconn,
|
|
||||||
wm_delete_window,
|
|
||||||
net_wm_ping,
|
|
||||||
redraw_sender: WakeSender {
|
|
||||||
sender: redraw_sender, // not used again so no clone
|
|
||||||
waker: waker.clone(),
|
|
||||||
},
|
|
||||||
device_event_filter: Default::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set initial device event filter.
|
|
||||||
window_target.update_device_event_filter(true);
|
|
||||||
|
|
||||||
let target = Rc::new(RootELW {
|
|
||||||
p: super::EventLoopWindowTarget::X(window_target),
|
|
||||||
_marker: ::std::marker::PhantomData,
|
|
||||||
});
|
|
||||||
|
|
||||||
let event_processor = EventProcessor {
|
|
||||||
target: target.clone(),
|
|
||||||
dnd,
|
|
||||||
devices: Default::default(),
|
|
||||||
randr_event_offset,
|
|
||||||
ime_receiver,
|
|
||||||
ime_event_receiver,
|
|
||||||
xi2ext,
|
|
||||||
mod_keymap,
|
|
||||||
device_mod_state: Default::default(),
|
|
||||||
num_touch: 0,
|
|
||||||
first_touch: None,
|
|
||||||
active_window: None,
|
|
||||||
is_composing: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Register for device hotplug events
|
|
||||||
// (The request buffer is flushed during `init_device`)
|
|
||||||
get_xtarget(&target)
|
|
||||||
.xconn
|
|
||||||
.select_xinput_events(root, ffi::XIAllDevices, ffi::XI_HierarchyChangedMask)
|
|
||||||
.queue();
|
|
||||||
|
|
||||||
event_processor.init_device(ffi::XIAllDevices);
|
|
||||||
|
|
||||||
EventLoop {
|
|
||||||
poll,
|
|
||||||
waker,
|
|
||||||
event_processor,
|
|
||||||
redraw_receiver: PeekableReceiver::from_recv(redraw_channel),
|
|
||||||
user_receiver: PeekableReceiver::from_recv(user_channel),
|
|
||||||
user_sender,
|
|
||||||
target,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
|
||||||
EventLoopProxy {
|
|
||||||
user_sender: self.user_sender.clone(),
|
|
||||||
waker: self.waker.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn window_target(&self) -> &RootELW<T> {
|
|
||||||
&self.target
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_return<F>(&mut self, mut callback: F) -> i32
|
|
||||||
where
|
|
||||||
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
|
||||||
{
|
|
||||||
struct IterationResult {
|
|
||||||
deadline: Option<Instant>,
|
|
||||||
timeout: Option<Duration>,
|
|
||||||
wait_start: Instant,
|
|
||||||
}
|
|
||||||
fn single_iteration<T, F>(
|
|
||||||
this: &mut EventLoop<T>,
|
|
||||||
control_flow: &mut ControlFlow,
|
|
||||||
cause: &mut StartCause,
|
|
||||||
callback: &mut F,
|
|
||||||
) -> IterationResult
|
|
||||||
where
|
|
||||||
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
|
||||||
{
|
|
||||||
sticky_exit_callback(
|
|
||||||
crate::event::Event::NewEvents(*cause),
|
|
||||||
&this.target,
|
|
||||||
control_flow,
|
|
||||||
callback,
|
|
||||||
);
|
|
||||||
|
|
||||||
// NB: For consistency all platforms must emit a 'resumed' event even though X11
|
|
||||||
// applications don't themselves have a formal suspend/resume lifecycle.
|
|
||||||
if *cause == StartCause::Init {
|
|
||||||
sticky_exit_callback(
|
|
||||||
crate::event::Event::Resumed,
|
|
||||||
&this.target,
|
|
||||||
control_flow,
|
|
||||||
callback,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process all pending events
|
|
||||||
this.drain_events(callback, control_flow);
|
|
||||||
|
|
||||||
// Empty the user event buffer
|
|
||||||
{
|
|
||||||
while let Ok(event) = this.user_receiver.try_recv() {
|
|
||||||
sticky_exit_callback(
|
|
||||||
crate::event::Event::UserEvent(event),
|
|
||||||
&this.target,
|
|
||||||
control_flow,
|
|
||||||
callback,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// send MainEventsCleared
|
|
||||||
{
|
|
||||||
sticky_exit_callback(
|
|
||||||
crate::event::Event::MainEventsCleared,
|
|
||||||
&this.target,
|
|
||||||
control_flow,
|
|
||||||
callback,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Empty the redraw requests
|
|
||||||
{
|
|
||||||
let mut windows = HashSet::new();
|
|
||||||
|
|
||||||
while let Ok(window_id) = this.redraw_receiver.try_recv() {
|
|
||||||
windows.insert(window_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
for window_id in windows {
|
|
||||||
let window_id = crate::window::WindowId(window_id);
|
|
||||||
sticky_exit_callback(
|
|
||||||
Event::RedrawRequested(window_id),
|
|
||||||
&this.target,
|
|
||||||
control_flow,
|
|
||||||
callback,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// send RedrawEventsCleared
|
|
||||||
{
|
|
||||||
sticky_exit_callback(
|
|
||||||
crate::event::Event::RedrawEventsCleared,
|
|
||||||
&this.target,
|
|
||||||
control_flow,
|
|
||||||
callback,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let start = Instant::now();
|
|
||||||
let (deadline, timeout);
|
|
||||||
|
|
||||||
match control_flow {
|
|
||||||
ControlFlow::ExitWithCode(_) => {
|
|
||||||
return IterationResult {
|
|
||||||
wait_start: start,
|
|
||||||
deadline: None,
|
|
||||||
timeout: None,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
ControlFlow::Poll => {
|
|
||||||
*cause = StartCause::Poll;
|
|
||||||
deadline = None;
|
|
||||||
timeout = Some(Duration::from_millis(0));
|
|
||||||
}
|
|
||||||
ControlFlow::Wait => {
|
|
||||||
*cause = StartCause::WaitCancelled {
|
|
||||||
start,
|
|
||||||
requested_resume: None,
|
|
||||||
};
|
|
||||||
deadline = None;
|
|
||||||
timeout = None;
|
|
||||||
}
|
|
||||||
ControlFlow::WaitUntil(wait_deadline) => {
|
|
||||||
*cause = StartCause::ResumeTimeReached {
|
|
||||||
start,
|
|
||||||
requested_resume: *wait_deadline,
|
|
||||||
};
|
|
||||||
timeout = if *wait_deadline > start {
|
|
||||||
Some(*wait_deadline - start)
|
|
||||||
} else {
|
|
||||||
Some(Duration::from_millis(0))
|
|
||||||
};
|
|
||||||
deadline = Some(*wait_deadline);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IterationResult {
|
|
||||||
wait_start: start,
|
|
||||||
deadline,
|
|
||||||
timeout,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut control_flow = ControlFlow::default();
|
|
||||||
let mut events = Events::with_capacity(8);
|
|
||||||
let mut cause = StartCause::Init;
|
|
||||||
|
|
||||||
// run the initial loop iteration
|
|
||||||
let mut iter_result = single_iteration(self, &mut control_flow, &mut cause, &mut callback);
|
|
||||||
|
|
||||||
let exit_code = loop {
|
|
||||||
if let ControlFlow::ExitWithCode(code) = control_flow {
|
|
||||||
break code;
|
|
||||||
}
|
|
||||||
let has_pending = self.event_processor.poll()
|
|
||||||
|| self.user_receiver.has_incoming()
|
|
||||||
|| self.redraw_receiver.has_incoming();
|
|
||||||
if !has_pending {
|
|
||||||
// Wait until
|
|
||||||
if let Err(e) = self.poll.poll(&mut events, iter_result.timeout) {
|
|
||||||
if e.raw_os_error() != Some(libc::EINTR) {
|
|
||||||
panic!("epoll returned an error: {:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
events.clear();
|
|
||||||
|
|
||||||
if control_flow == ControlFlow::Wait {
|
|
||||||
// We don't go straight into executing the event loop iteration, we instead go
|
|
||||||
// to the start of this loop and check again if there's any pending event. We
|
|
||||||
// must do this because during the execution of the iteration we sometimes wake
|
|
||||||
// the mio waker, and if the waker is already awaken before we call poll(),
|
|
||||||
// then poll doesn't block, but it returns immediately. This caused the event
|
|
||||||
// loop to run continuously even if the control_flow was `Wait`
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let wait_cancelled = iter_result
|
|
||||||
.deadline
|
|
||||||
.map_or(false, |deadline| Instant::now() < deadline);
|
|
||||||
|
|
||||||
if wait_cancelled {
|
|
||||||
cause = StartCause::WaitCancelled {
|
|
||||||
start: iter_result.wait_start,
|
|
||||||
requested_resume: iter_result.deadline,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
iter_result = single_iteration(self, &mut control_flow, &mut cause, &mut callback);
|
|
||||||
};
|
|
||||||
|
|
||||||
callback(
|
|
||||||
crate::event::Event::LoopDestroyed,
|
|
||||||
&self.target,
|
|
||||||
&mut control_flow,
|
|
||||||
);
|
|
||||||
exit_code
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run<F>(mut self, callback: F) -> !
|
|
||||||
where
|
|
||||||
F: 'static + FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
|
||||||
{
|
|
||||||
let exit_code = self.run_return(callback);
|
|
||||||
::std::process::exit(exit_code);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn drain_events<F>(&mut self, callback: &mut F, control_flow: &mut ControlFlow)
|
|
||||||
where
|
|
||||||
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
|
||||||
{
|
|
||||||
let target = &self.target;
|
|
||||||
let mut xev = MaybeUninit::uninit();
|
|
||||||
let wt = get_xtarget(&self.target);
|
|
||||||
|
|
||||||
while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } {
|
|
||||||
let mut xev = unsafe { xev.assume_init() };
|
|
||||||
self.event_processor.process_event(&mut xev, |event| {
|
|
||||||
sticky_exit_callback(
|
|
||||||
event,
|
|
||||||
target,
|
|
||||||
control_flow,
|
|
||||||
&mut |event, window_target, control_flow| {
|
|
||||||
if let Event::RedrawRequested(crate::window::WindowId(wid)) = event {
|
|
||||||
wt.redraw_sender.sender.send(wid).unwrap();
|
|
||||||
wt.redraw_sender.waker.wake().unwrap();
|
|
||||||
} else {
|
|
||||||
callback(event, window_target, control_flow);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn get_xtarget<T>(target: &RootELW<T>) -> &EventLoopWindowTarget<T> {
|
|
||||||
match target.p {
|
|
||||||
super::EventLoopWindowTarget::X(ref target) => target,
|
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopWindowTarget<T> {
|
|
||||||
/// Returns the `XConnection` of this events loop.
|
|
||||||
#[inline]
|
|
||||||
pub fn x_connection(&self) -> &Arc<XConnection> {
|
|
||||||
&self.xconn
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_device_event_filter(&self, filter: DeviceEventFilter) {
|
|
||||||
self.device_event_filter.set(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update the device event filter based on window focus.
|
|
||||||
pub fn update_device_event_filter(&self, focus: bool) {
|
|
||||||
let filter_events = self.device_event_filter.get() == DeviceEventFilter::Never
|
|
||||||
|| (self.device_event_filter.get() == DeviceEventFilter::Unfocused && !focus);
|
|
||||||
|
|
||||||
let mut mask = 0;
|
|
||||||
if !filter_events {
|
|
||||||
mask = ffi::XI_RawMotionMask
|
|
||||||
| ffi::XI_RawButtonPressMask
|
|
||||||
| ffi::XI_RawButtonReleaseMask
|
|
||||||
| ffi::XI_RawKeyPressMask
|
|
||||||
| ffi::XI_RawKeyReleaseMask;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.xconn
|
|
||||||
.select_xinput_events(self.root, ffi::XIAllMasterDevices, mask)
|
|
||||||
.queue();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn raw_display_handle(&self) -> raw_window_handle::RawDisplayHandle {
|
|
||||||
let mut display_handle = XlibDisplayHandle::empty();
|
|
||||||
display_handle.display = self.xconn.display as *mut _;
|
|
||||||
display_handle.screen =
|
|
||||||
unsafe { (self.xconn.xlib.XDefaultScreen)(self.xconn.display as *mut _) };
|
|
||||||
RawDisplayHandle::Xlib(display_handle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> EventLoopProxy<T> {
|
|
||||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
|
||||||
self.user_sender
|
|
||||||
.send(event)
|
|
||||||
.map_err(|e| EventLoopClosed(e.0))
|
|
||||||
.map(|_| self.waker.wake().unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DeviceInfo<'a> {
|
|
||||||
xconn: &'a XConnection,
|
|
||||||
info: *const ffi::XIDeviceInfo,
|
|
||||||
count: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> DeviceInfo<'a> {
|
|
||||||
fn get(xconn: &'a XConnection, device: c_int) -> Option<Self> {
|
|
||||||
unsafe {
|
|
||||||
let mut count = 0;
|
|
||||||
let info = (xconn.xinput2.XIQueryDevice)(xconn.display, device, &mut count);
|
|
||||||
xconn.check_errors().ok()?;
|
|
||||||
|
|
||||||
if info.is_null() || count == 0 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(DeviceInfo {
|
|
||||||
xconn,
|
|
||||||
info,
|
|
||||||
count: count as usize,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Drop for DeviceInfo<'a> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
assert!(!self.info.is_null());
|
|
||||||
unsafe { (self.xconn.xinput2.XIFreeDeviceInfo)(self.info as *mut _) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Deref for DeviceInfo<'a> {
|
|
||||||
type Target = [ffi::XIDeviceInfo];
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
unsafe { slice::from_raw_parts(self.info, self.count) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct DeviceId(c_int);
|
|
||||||
|
|
||||||
impl DeviceId {
|
|
||||||
pub const unsafe fn dummy() -> Self {
|
|
||||||
DeviceId(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Window(Arc<UnownedWindow>);
|
|
||||||
|
|
||||||
impl Deref for Window {
|
|
||||||
type Target = UnownedWindow;
|
|
||||||
#[inline]
|
|
||||||
fn deref(&self) -> &UnownedWindow {
|
|
||||||
&*self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Window {
|
|
||||||
pub(crate) fn new<T>(
|
|
||||||
event_loop: &EventLoopWindowTarget<T>,
|
|
||||||
attribs: WindowAttributes,
|
|
||||||
pl_attribs: PlatformSpecificWindowBuilderAttributes,
|
|
||||||
) -> Result<Self, RootOsError> {
|
|
||||||
let window = Arc::new(UnownedWindow::new(event_loop, attribs, pl_attribs)?);
|
|
||||||
event_loop
|
|
||||||
.windows
|
|
||||||
.borrow_mut()
|
|
||||||
.insert(window.id(), Arc::downgrade(&window));
|
|
||||||
Ok(Window(window))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Window {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let window = self.deref();
|
|
||||||
let xconn = &window.xconn;
|
|
||||||
unsafe {
|
|
||||||
(xconn.xlib.XDestroyWindow)(xconn.display, window.id().0 as ffi::Window);
|
|
||||||
// If the window was somehow already destroyed, we'll get a `BadWindow` error, which we don't care about.
|
|
||||||
let _ = xconn.check_errors();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// XEvents of type GenericEvent store their actual data in an XGenericEventCookie data structure. This is a wrapper to
|
|
||||||
/// extract the cookie from a GenericEvent XEvent and release the cookie data once it has been processed
|
|
||||||
struct GenericEventCookie<'a> {
|
|
||||||
xconn: &'a XConnection,
|
|
||||||
cookie: ffi::XGenericEventCookie,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> GenericEventCookie<'a> {
|
|
||||||
fn from_event(xconn: &XConnection, event: ffi::XEvent) -> Option<GenericEventCookie<'_>> {
|
|
||||||
unsafe {
|
|
||||||
let mut cookie: ffi::XGenericEventCookie = From::from(event);
|
|
||||||
if (xconn.xlib.XGetEventData)(xconn.display, &mut cookie) == ffi::True {
|
|
||||||
Some(GenericEventCookie { xconn, cookie })
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Drop for GenericEventCookie<'a> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
(self.xconn.xlib.XFreeEventData)(self.xconn.display, &mut self.cookie);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
|
||||||
struct XExtension {
|
|
||||||
opcode: c_int,
|
|
||||||
first_event_id: c_int,
|
|
||||||
first_error_id: c_int,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn mkwid(w: ffi::Window) -> crate::window::WindowId {
|
|
||||||
crate::window::WindowId(crate::platform_impl::platform::WindowId(w as u64))
|
|
||||||
}
|
|
||||||
fn mkdid(w: c_int) -> crate::event::DeviceId {
|
|
||||||
crate::event::DeviceId(crate::platform_impl::DeviceId::X(DeviceId(w)))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Device {
|
|
||||||
_name: String,
|
|
||||||
scroll_axes: Vec<(i32, ScrollAxis)>,
|
|
||||||
// For master devices, this is the paired device (pointer <-> keyboard).
|
|
||||||
// For slave devices, this is the master.
|
|
||||||
attachment: c_int,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
struct ScrollAxis {
|
|
||||||
increment: f64,
|
|
||||||
orientation: ScrollOrientation,
|
|
||||||
position: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
|
||||||
enum ScrollOrientation {
|
|
||||||
Vertical,
|
|
||||||
Horizontal,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Device {
|
|
||||||
fn new(info: &ffi::XIDeviceInfo) -> Self {
|
|
||||||
let name = unsafe { CStr::from_ptr(info.name).to_string_lossy() };
|
|
||||||
let mut scroll_axes = Vec::new();
|
|
||||||
|
|
||||||
if Device::physical_device(info) {
|
|
||||||
// Identify scroll axes
|
|
||||||
for class_ptr in Device::classes(info) {
|
|
||||||
let class = unsafe { &**class_ptr };
|
|
||||||
if class._type == ffi::XIScrollClass {
|
|
||||||
let info = unsafe {
|
|
||||||
mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIScrollClassInfo>(class)
|
|
||||||
};
|
|
||||||
scroll_axes.push((
|
|
||||||
info.number,
|
|
||||||
ScrollAxis {
|
|
||||||
increment: info.increment,
|
|
||||||
orientation: match info.scroll_type {
|
|
||||||
ffi::XIScrollTypeHorizontal => ScrollOrientation::Horizontal,
|
|
||||||
ffi::XIScrollTypeVertical => ScrollOrientation::Vertical,
|
|
||||||
_ => unreachable!(),
|
|
||||||
},
|
|
||||||
position: 0.0,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut device = Device {
|
|
||||||
_name: name.into_owned(),
|
|
||||||
scroll_axes,
|
|
||||||
attachment: info.attachment,
|
|
||||||
};
|
|
||||||
device.reset_scroll_position(info);
|
|
||||||
device
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reset_scroll_position(&mut self, info: &ffi::XIDeviceInfo) {
|
|
||||||
if Device::physical_device(info) {
|
|
||||||
for class_ptr in Device::classes(info) {
|
|
||||||
let class = unsafe { &**class_ptr };
|
|
||||||
if class._type == ffi::XIValuatorClass {
|
|
||||||
let info = unsafe {
|
|
||||||
mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIValuatorClassInfo>(class)
|
|
||||||
};
|
|
||||||
if let Some(&mut (_, ref mut axis)) = self
|
|
||||||
.scroll_axes
|
|
||||||
.iter_mut()
|
|
||||||
.find(|&&mut (axis, _)| axis == info.number)
|
|
||||||
{
|
|
||||||
axis.position = info.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn physical_device(info: &ffi::XIDeviceInfo) -> bool {
|
|
||||||
info._use == ffi::XISlaveKeyboard
|
|
||||||
|| info._use == ffi::XISlavePointer
|
|
||||||
|| info._use == ffi::XIFloatingSlave
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn classes(info: &ffi::XIDeviceInfo) -> &[*const ffi::XIAnyClassInfo] {
|
|
||||||
unsafe {
|
|
||||||
slice::from_raw_parts(
|
|
||||||
info.classes as *const *const ffi::XIAnyClassInfo,
|
|
||||||
info.num_classes as usize,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,345 +0,0 @@
|
|||||||
use std::os::raw::*;
|
|
||||||
use std::slice;
|
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
ffi::{
|
|
||||||
RRCrtc, RRCrtcChangeNotifyMask, RRMode, RROutputPropertyNotifyMask,
|
|
||||||
RRScreenChangeNotifyMask, True, Window, XRRCrtcInfo, XRRModeInfo, XRRScreenResources,
|
|
||||||
},
|
|
||||||
util, XConnection, XError,
|
|
||||||
};
|
|
||||||
use crate::{
|
|
||||||
dpi::{PhysicalPosition, PhysicalSize},
|
|
||||||
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
|
||||||
platform_impl::{MonitorHandle as PlatformMonitorHandle, VideoMode as PlatformVideoMode},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Used for testing. This should always be committed as false.
|
|
||||||
const DISABLE_MONITOR_LIST_CACHING: bool = false;
|
|
||||||
|
|
||||||
static MONITORS: Lazy<Mutex<Option<Vec<MonitorHandle>>>> = Lazy::new(Mutex::default);
|
|
||||||
|
|
||||||
pub fn invalidate_cached_monitor_list() -> Option<Vec<MonitorHandle>> {
|
|
||||||
// We update this lazily.
|
|
||||||
(*MONITORS.lock()).take()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub struct VideoMode {
|
|
||||||
pub(crate) size: (u32, u32),
|
|
||||||
pub(crate) bit_depth: u16,
|
|
||||||
pub(crate) refresh_rate_millihertz: u32,
|
|
||||||
pub(crate) native_mode: RRMode,
|
|
||||||
pub(crate) monitor: Option<MonitorHandle>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VideoMode {
|
|
||||||
#[inline]
|
|
||||||
pub fn size(&self) -> PhysicalSize<u32> {
|
|
||||||
self.size.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn bit_depth(&self) -> u16 {
|
|
||||||
self.bit_depth
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn refresh_rate_millihertz(&self) -> u32 {
|
|
||||||
self.refresh_rate_millihertz
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn monitor(&self) -> RootMonitorHandle {
|
|
||||||
RootMonitorHandle {
|
|
||||||
inner: PlatformMonitorHandle::X(self.monitor.clone().unwrap()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct MonitorHandle {
|
|
||||||
/// The actual id
|
|
||||||
pub(crate) id: RRCrtc,
|
|
||||||
/// The name of the monitor
|
|
||||||
pub(crate) name: String,
|
|
||||||
/// The size of the monitor
|
|
||||||
dimensions: (u32, u32),
|
|
||||||
/// The position of the monitor in the X screen
|
|
||||||
position: (i32, i32),
|
|
||||||
/// If the monitor is the primary one
|
|
||||||
primary: bool,
|
|
||||||
/// The refresh rate used by monitor.
|
|
||||||
refresh_rate_millihertz: Option<u32>,
|
|
||||||
/// The DPI scale factor
|
|
||||||
pub(crate) scale_factor: f64,
|
|
||||||
/// Used to determine which windows are on this monitor
|
|
||||||
pub(crate) rect: util::AaRect,
|
|
||||||
/// Supported video modes on this monitor
|
|
||||||
video_modes: Vec<VideoMode>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for MonitorHandle {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.id == other.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for MonitorHandle {}
|
|
||||||
|
|
||||||
impl PartialOrd for MonitorHandle {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for MonitorHandle {
|
|
||||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
||||||
self.id.cmp(&other.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::hash::Hash for MonitorHandle {
|
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
self.id.hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn mode_refresh_rate_millihertz(mode: &XRRModeInfo) -> Option<u32> {
|
|
||||||
if mode.dotClock > 0 && mode.hTotal > 0 && mode.vTotal > 0 {
|
|
||||||
Some((mode.dotClock as u64 * 1000 / (mode.hTotal as u64 * mode.vTotal as u64)) as u32)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MonitorHandle {
|
|
||||||
fn new(
|
|
||||||
xconn: &XConnection,
|
|
||||||
resources: *mut XRRScreenResources,
|
|
||||||
id: RRCrtc,
|
|
||||||
crtc: *mut XRRCrtcInfo,
|
|
||||||
primary: bool,
|
|
||||||
) -> Option<Self> {
|
|
||||||
let (name, scale_factor, video_modes) = unsafe { xconn.get_output_info(resources, crtc)? };
|
|
||||||
let dimensions = unsafe { ((*crtc).width as u32, (*crtc).height as u32) };
|
|
||||||
let position = unsafe { ((*crtc).x as i32, (*crtc).y as i32) };
|
|
||||||
|
|
||||||
// Get the refresh rate of the current video mode.
|
|
||||||
let current_mode = unsafe { (*crtc).mode };
|
|
||||||
let screen_modes =
|
|
||||||
unsafe { slice::from_raw_parts((*resources).modes, (*resources).nmode as usize) };
|
|
||||||
let refresh_rate_millihertz = screen_modes
|
|
||||||
.iter()
|
|
||||||
.find(|mode| mode.id == current_mode)
|
|
||||||
.and_then(mode_refresh_rate_millihertz);
|
|
||||||
|
|
||||||
let rect = util::AaRect::new(position, dimensions);
|
|
||||||
|
|
||||||
Some(MonitorHandle {
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
refresh_rate_millihertz,
|
|
||||||
scale_factor,
|
|
||||||
dimensions,
|
|
||||||
position,
|
|
||||||
primary,
|
|
||||||
rect,
|
|
||||||
video_modes,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dummy() -> Self {
|
|
||||||
MonitorHandle {
|
|
||||||
id: 0,
|
|
||||||
name: "<dummy monitor>".into(),
|
|
||||||
scale_factor: 1.0,
|
|
||||||
dimensions: (1, 1),
|
|
||||||
position: (0, 0),
|
|
||||||
refresh_rate_millihertz: None,
|
|
||||||
primary: true,
|
|
||||||
rect: util::AaRect::new((0, 0), (1, 1)),
|
|
||||||
video_modes: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn is_dummy(&self) -> bool {
|
|
||||||
// Zero is an invalid XID value; no real monitor will have it
|
|
||||||
self.id == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn name(&self) -> Option<String> {
|
|
||||||
Some(self.name.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn native_identifier(&self) -> u32 {
|
|
||||||
self.id as u32
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn size(&self) -> PhysicalSize<u32> {
|
|
||||||
self.dimensions.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn position(&self) -> PhysicalPosition<i32> {
|
|
||||||
self.position.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn refresh_rate_millihertz(&self) -> Option<u32> {
|
|
||||||
self.refresh_rate_millihertz
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn scale_factor(&self) -> f64 {
|
|
||||||
self.scale_factor
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
|
|
||||||
let monitor = self.clone();
|
|
||||||
self.video_modes.clone().into_iter().map(move |mut x| {
|
|
||||||
x.monitor = Some(monitor.clone());
|
|
||||||
RootVideoMode {
|
|
||||||
video_mode: PlatformVideoMode::X(x),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl XConnection {
|
|
||||||
pub fn get_monitor_for_window(&self, window_rect: Option<util::AaRect>) -> MonitorHandle {
|
|
||||||
let monitors = self.available_monitors();
|
|
||||||
|
|
||||||
if monitors.is_empty() {
|
|
||||||
// Return a dummy monitor to avoid panicking
|
|
||||||
return MonitorHandle::dummy();
|
|
||||||
}
|
|
||||||
|
|
||||||
let default = monitors.get(0).unwrap();
|
|
||||||
|
|
||||||
let window_rect = match window_rect {
|
|
||||||
Some(rect) => rect,
|
|
||||||
None => return default.to_owned(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut largest_overlap = 0;
|
|
||||||
let mut matched_monitor = default;
|
|
||||||
for monitor in &monitors {
|
|
||||||
let overlapping_area = window_rect.get_overlapping_area(&monitor.rect);
|
|
||||||
if overlapping_area > largest_overlap {
|
|
||||||
largest_overlap = overlapping_area;
|
|
||||||
matched_monitor = monitor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
matched_monitor.to_owned()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn query_monitor_list(&self) -> Vec<MonitorHandle> {
|
|
||||||
unsafe {
|
|
||||||
let mut major = 0;
|
|
||||||
let mut minor = 0;
|
|
||||||
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
|
|
||||||
|
|
||||||
let root = (self.xlib.XDefaultRootWindow)(self.display);
|
|
||||||
let resources = if (major == 1 && minor >= 3) || major > 1 {
|
|
||||||
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
|
|
||||||
} else {
|
|
||||||
// WARNING: this function is supposedly very slow, on the order of hundreds of ms.
|
|
||||||
// Upon failure, `resources` will be null.
|
|
||||||
(self.xrandr.XRRGetScreenResources)(self.display, root)
|
|
||||||
};
|
|
||||||
|
|
||||||
if resources.is_null() {
|
|
||||||
panic!("[winit] `XRRGetScreenResources` returned NULL. That should only happen if the root window doesn't exist.");
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut has_primary = false;
|
|
||||||
|
|
||||||
let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root);
|
|
||||||
let mut available = Vec::with_capacity((*resources).ncrtc as usize);
|
|
||||||
|
|
||||||
for crtc_index in 0..(*resources).ncrtc {
|
|
||||||
let crtc_id = *((*resources).crtcs.offset(crtc_index as isize));
|
|
||||||
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
|
|
||||||
let is_active = (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0;
|
|
||||||
if is_active {
|
|
||||||
let is_primary = *(*crtc).outputs.offset(0) == primary;
|
|
||||||
has_primary |= is_primary;
|
|
||||||
if let Some(monitor_id) =
|
|
||||||
MonitorHandle::new(self, resources, crtc_id, crtc, is_primary)
|
|
||||||
{
|
|
||||||
available.push(monitor_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(self.xrandr.XRRFreeCrtcInfo)(crtc);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no monitors were detected as being primary, we just pick one ourselves!
|
|
||||||
if !has_primary {
|
|
||||||
if let Some(ref mut fallback) = available.first_mut() {
|
|
||||||
// Setting this here will come in handy if we ever add an `is_primary` method.
|
|
||||||
fallback.primary = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(self.xrandr.XRRFreeScreenResources)(resources);
|
|
||||||
available
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn available_monitors(&self) -> Vec<MonitorHandle> {
|
|
||||||
let mut monitors_lock = MONITORS.lock();
|
|
||||||
(*monitors_lock)
|
|
||||||
.as_ref()
|
|
||||||
.cloned()
|
|
||||||
.or_else(|| {
|
|
||||||
let monitors = Some(self.query_monitor_list());
|
|
||||||
if !DISABLE_MONITOR_LIST_CACHING {
|
|
||||||
(*monitors_lock) = monitors.clone();
|
|
||||||
}
|
|
||||||
monitors
|
|
||||||
})
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
|
||||||
self.available_monitors()
|
|
||||||
.into_iter()
|
|
||||||
.find(|monitor| monitor.primary)
|
|
||||||
.unwrap_or_else(MonitorHandle::dummy)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn select_xrandr_input(&self, root: Window) -> Result<c_int, XError> {
|
|
||||||
let has_xrandr = unsafe {
|
|
||||||
let mut major = 0;
|
|
||||||
let mut minor = 0;
|
|
||||||
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor)
|
|
||||||
};
|
|
||||||
assert!(
|
|
||||||
has_xrandr == True,
|
|
||||||
"[winit] XRandR extension not available."
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut event_offset = 0;
|
|
||||||
let mut error_offset = 0;
|
|
||||||
let status = unsafe {
|
|
||||||
(self.xrandr.XRRQueryExtension)(self.display, &mut event_offset, &mut error_offset)
|
|
||||||
};
|
|
||||||
|
|
||||||
if status != True {
|
|
||||||
self.check_errors()?;
|
|
||||||
unreachable!("[winit] `XRRQueryExtension` failed but no error was received.");
|
|
||||||
}
|
|
||||||
|
|
||||||
let mask = RRCrtcChangeNotifyMask | RROutputPropertyNotifyMask | RRScreenChangeNotifyMask;
|
|
||||||
unsafe { (self.xrandr.XRRSelectInput)(self.display, root, mask) };
|
|
||||||
|
|
||||||
Ok(event_offset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
use std::{
|
|
||||||
collections::HashMap,
|
|
||||||
ffi::{CStr, CString},
|
|
||||||
fmt::Debug,
|
|
||||||
os::raw::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
type AtomCache = HashMap<CString, ffi::Atom>;
|
|
||||||
|
|
||||||
static ATOM_CACHE: Lazy<Mutex<AtomCache>> = Lazy::new(|| Mutex::new(HashMap::with_capacity(2048)));
|
|
||||||
|
|
||||||
impl XConnection {
|
|
||||||
pub fn get_atom<T: AsRef<CStr> + Debug>(&self, name: T) -> ffi::Atom {
|
|
||||||
let name = name.as_ref();
|
|
||||||
let mut atom_cache_lock = ATOM_CACHE.lock();
|
|
||||||
let cached_atom = (*atom_cache_lock).get(name).cloned();
|
|
||||||
if let Some(atom) = cached_atom {
|
|
||||||
atom
|
|
||||||
} else {
|
|
||||||
let atom = unsafe {
|
|
||||||
(self.xlib.XInternAtom)(self.display, name.as_ptr() as *const c_char, ffi::False)
|
|
||||||
};
|
|
||||||
if atom == 0 {
|
|
||||||
panic!(
|
|
||||||
"`XInternAtom` failed, which really shouldn't happen. Atom: {:?}, Error: {:#?}",
|
|
||||||
name,
|
|
||||||
self.check_errors(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/*println!(
|
|
||||||
"XInternAtom name:{:?} atom:{:?}",
|
|
||||||
name,
|
|
||||||
atom,
|
|
||||||
);*/
|
|
||||||
(*atom_cache_lock).insert(name.to_owned(), atom);
|
|
||||||
atom
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn get_atom_unchecked(&self, name: &[u8]) -> ffi::Atom {
|
|
||||||
debug_assert!(CStr::from_bytes_with_nul(name).is_ok());
|
|
||||||
let name = CStr::from_bytes_with_nul_unchecked(name);
|
|
||||||
self.get_atom(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: this doesn't use caching, for the sake of simplicity.
|
|
||||||
// If you're dealing with this many atoms, you'll usually want to cache them locally anyway.
|
|
||||||
pub unsafe fn get_atoms(&self, names: &[*mut c_char]) -> Result<Vec<ffi::Atom>, XError> {
|
|
||||||
let mut atoms = Vec::with_capacity(names.len());
|
|
||||||
(self.xlib.XInternAtoms)(
|
|
||||||
self.display,
|
|
||||||
names.as_ptr() as *mut _,
|
|
||||||
names.len() as c_int,
|
|
||||||
ffi::False,
|
|
||||||
atoms.as_mut_ptr(),
|
|
||||||
);
|
|
||||||
self.check_errors()?;
|
|
||||||
atoms.set_len(names.len());
|
|
||||||
/*println!(
|
|
||||||
"XInternAtoms atoms:{:?}",
|
|
||||||
atoms,
|
|
||||||
);*/
|
|
||||||
Ok(atoms)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
use super::*;
|
|
||||||
|
|
||||||
pub type ClientMsgPayload = [c_long; 5];
|
|
||||||
|
|
||||||
impl XConnection {
|
|
||||||
pub fn send_event<T: Into<ffi::XEvent>>(
|
|
||||||
&self,
|
|
||||||
target_window: c_ulong,
|
|
||||||
event_mask: Option<c_long>,
|
|
||||||
event: T,
|
|
||||||
) -> Flusher<'_> {
|
|
||||||
let event_mask = event_mask.unwrap_or(ffi::NoEventMask);
|
|
||||||
unsafe {
|
|
||||||
(self.xlib.XSendEvent)(
|
|
||||||
self.display,
|
|
||||||
target_window,
|
|
||||||
ffi::False,
|
|
||||||
event_mask,
|
|
||||||
&mut event.into(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Flusher::new(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send_client_msg(
|
|
||||||
&self,
|
|
||||||
window: c_ulong, // The window this is "about"; not necessarily this window
|
|
||||||
target_window: c_ulong, // The window we're sending to
|
|
||||||
message_type: ffi::Atom,
|
|
||||||
event_mask: Option<c_long>,
|
|
||||||
data: ClientMsgPayload,
|
|
||||||
) -> Flusher<'_> {
|
|
||||||
let event = ffi::XClientMessageEvent {
|
|
||||||
type_: ffi::ClientMessage,
|
|
||||||
display: self.display,
|
|
||||||
window,
|
|
||||||
message_type,
|
|
||||||
format: c_long::FORMAT as c_int,
|
|
||||||
data: unsafe { mem::transmute(data) },
|
|
||||||
// These fields are ignored by `XSendEvent`
|
|
||||||
serial: 0,
|
|
||||||
send_event: 0,
|
|
||||||
};
|
|
||||||
self.send_event(target_window, event_mask, event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
use crate::window::CursorIcon;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
impl XConnection {
|
|
||||||
pub fn set_cursor_icon(&self, window: ffi::Window, cursor: Option<CursorIcon>) {
|
|
||||||
let cursor = *self
|
|
||||||
.cursor_cache
|
|
||||||
.lock()
|
|
||||||
.entry(cursor)
|
|
||||||
.or_insert_with(|| self.get_cursor(cursor));
|
|
||||||
|
|
||||||
self.update_cursor(window, cursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_empty_cursor(&self) -> ffi::Cursor {
|
|
||||||
let data = 0;
|
|
||||||
let pixmap = unsafe {
|
|
||||||
let screen = (self.xlib.XDefaultScreen)(self.display);
|
|
||||||
let window = (self.xlib.XRootWindow)(self.display, screen);
|
|
||||||
(self.xlib.XCreateBitmapFromData)(self.display, window, &data, 1, 1)
|
|
||||||
};
|
|
||||||
|
|
||||||
if pixmap == 0 {
|
|
||||||
panic!("failed to allocate pixmap for cursor");
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
// We don't care about this color, since it only fills bytes
|
|
||||||
// in the pixmap which are not 0 in the mask.
|
|
||||||
let mut dummy_color = MaybeUninit::uninit();
|
|
||||||
let cursor = (self.xlib.XCreatePixmapCursor)(
|
|
||||||
self.display,
|
|
||||||
pixmap,
|
|
||||||
pixmap,
|
|
||||||
dummy_color.as_mut_ptr(),
|
|
||||||
dummy_color.as_mut_ptr(),
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
(self.xlib.XFreePixmap)(self.display, pixmap);
|
|
||||||
|
|
||||||
cursor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_cursor(&self, name: &[u8]) -> ffi::Cursor {
|
|
||||||
unsafe {
|
|
||||||
(self.xcursor.XcursorLibraryLoadCursor)(self.display, name.as_ptr() as *const c_char)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn load_first_existing_cursor(&self, names: &[&[u8]]) -> ffi::Cursor {
|
|
||||||
for name in names.iter() {
|
|
||||||
let xcursor = self.load_cursor(name);
|
|
||||||
if xcursor != 0 {
|
|
||||||
return xcursor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_cursor(&self, cursor: Option<CursorIcon>) -> ffi::Cursor {
|
|
||||||
let cursor = match cursor {
|
|
||||||
Some(cursor) => cursor,
|
|
||||||
None => return self.create_empty_cursor(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let load = |name: &[u8]| self.load_cursor(name);
|
|
||||||
|
|
||||||
let loadn = |names: &[&[u8]]| self.load_first_existing_cursor(names);
|
|
||||||
|
|
||||||
// Try multiple names in some cases where the name
|
|
||||||
// differs on the desktop environments or themes.
|
|
||||||
//
|
|
||||||
// Try the better looking (or more suiting) names first.
|
|
||||||
match cursor {
|
|
||||||
CursorIcon::Alias => load(b"link\0"),
|
|
||||||
CursorIcon::Arrow => load(b"arrow\0"),
|
|
||||||
CursorIcon::Cell => load(b"plus\0"),
|
|
||||||
CursorIcon::Copy => load(b"copy\0"),
|
|
||||||
CursorIcon::Crosshair => load(b"crosshair\0"),
|
|
||||||
CursorIcon::Default => load(b"left_ptr\0"),
|
|
||||||
CursorIcon::Hand => loadn(&[b"hand2\0", b"hand1\0"]),
|
|
||||||
CursorIcon::Help => load(b"question_arrow\0"),
|
|
||||||
CursorIcon::Move => load(b"move\0"),
|
|
||||||
CursorIcon::Grab => loadn(&[b"openhand\0", b"grab\0"]),
|
|
||||||
CursorIcon::Grabbing => loadn(&[b"closedhand\0", b"grabbing\0"]),
|
|
||||||
CursorIcon::Progress => load(b"left_ptr_watch\0"),
|
|
||||||
CursorIcon::AllScroll => load(b"all-scroll\0"),
|
|
||||||
CursorIcon::ContextMenu => load(b"context-menu\0"),
|
|
||||||
|
|
||||||
CursorIcon::NoDrop => loadn(&[b"no-drop\0", b"circle\0"]),
|
|
||||||
CursorIcon::NotAllowed => load(b"crossed_circle\0"),
|
|
||||||
|
|
||||||
// Resize cursors
|
|
||||||
CursorIcon::EResize => load(b"right_side\0"),
|
|
||||||
CursorIcon::NResize => load(b"top_side\0"),
|
|
||||||
CursorIcon::NeResize => load(b"top_right_corner\0"),
|
|
||||||
CursorIcon::NwResize => load(b"top_left_corner\0"),
|
|
||||||
CursorIcon::SResize => load(b"bottom_side\0"),
|
|
||||||
CursorIcon::SeResize => load(b"bottom_right_corner\0"),
|
|
||||||
CursorIcon::SwResize => load(b"bottom_left_corner\0"),
|
|
||||||
CursorIcon::WResize => load(b"left_side\0"),
|
|
||||||
CursorIcon::EwResize => load(b"h_double_arrow\0"),
|
|
||||||
CursorIcon::NsResize => load(b"v_double_arrow\0"),
|
|
||||||
CursorIcon::NwseResize => loadn(&[b"bd_double_arrow\0", b"size_fdiag\0"]),
|
|
||||||
CursorIcon::NeswResize => loadn(&[b"fd_double_arrow\0", b"size_bdiag\0"]),
|
|
||||||
CursorIcon::ColResize => loadn(&[b"split_h\0", b"h_double_arrow\0"]),
|
|
||||||
CursorIcon::RowResize => loadn(&[b"split_v\0", b"v_double_arrow\0"]),
|
|
||||||
|
|
||||||
CursorIcon::Text => loadn(&[b"text\0", b"xterm\0"]),
|
|
||||||
CursorIcon::VerticalText => load(b"vertical-text\0"),
|
|
||||||
|
|
||||||
CursorIcon::Wait => load(b"watch\0"),
|
|
||||||
|
|
||||||
CursorIcon::ZoomIn => load(b"zoom-in\0"),
|
|
||||||
CursorIcon::ZoomOut => load(b"zoom-out\0"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_cursor(&self, window: ffi::Window, cursor: ffi::Cursor) {
|
|
||||||
unsafe {
|
|
||||||
(self.xlib.XDefineCursor)(self.display, window, cursor);
|
|
||||||
|
|
||||||
self.flush_requests().expect("Failed to set the cursor");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
use std::{fmt::Debug, mem, os::raw::*};
|
|
||||||
|
|
||||||
// This isn't actually the number of the bits in the format.
|
|
||||||
// X11 does a match on this value to determine which type to call sizeof on.
|
|
||||||
// Thus, we use 32 for c_long, since 32 maps to c_long which maps to 64.
|
|
||||||
// ...if that sounds confusing, then you know why this enum is here.
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub enum Format {
|
|
||||||
Char = 8,
|
|
||||||
Short = 16,
|
|
||||||
Long = 32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Format {
|
|
||||||
pub fn from_format(format: usize) -> Option<Self> {
|
|
||||||
match format {
|
|
||||||
8 => Some(Format::Char),
|
|
||||||
16 => Some(Format::Short),
|
|
||||||
32 => Some(Format::Long),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_actual_size(&self) -> usize {
|
|
||||||
match self {
|
|
||||||
Format::Char => mem::size_of::<c_char>(),
|
|
||||||
Format::Short => mem::size_of::<c_short>(),
|
|
||||||
Format::Long => mem::size_of::<c_long>(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Formattable: Debug + Clone + Copy + PartialEq + PartialOrd {
|
|
||||||
const FORMAT: Format;
|
|
||||||
}
|
|
||||||
|
|
||||||
// You might be surprised by the absence of c_int, but not as surprised as X11 would be by the presence of it.
|
|
||||||
impl Formattable for c_schar {
|
|
||||||
const FORMAT: Format = Format::Char;
|
|
||||||
}
|
|
||||||
impl Formattable for c_uchar {
|
|
||||||
const FORMAT: Format = Format::Char;
|
|
||||||
}
|
|
||||||
impl Formattable for c_short {
|
|
||||||
const FORMAT: Format = Format::Short;
|
|
||||||
}
|
|
||||||
impl Formattable for c_ushort {
|
|
||||||
const FORMAT: Format = Format::Short;
|
|
||||||
}
|
|
||||||
impl Formattable for c_long {
|
|
||||||
const FORMAT: Format = Format::Long;
|
|
||||||
}
|
|
||||||
impl Formattable for c_ulong {
|
|
||||||
const FORMAT: Format = Format::Long;
|
|
||||||
}
|
|
||||||
@@ -1,347 +0,0 @@
|
|||||||
use std::slice;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub enum StateOperation {
|
|
||||||
Remove = 0, // _NET_WM_STATE_REMOVE
|
|
||||||
Add = 1, // _NET_WM_STATE_ADD
|
|
||||||
Toggle = 2, // _NET_WM_STATE_TOGGLE
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<bool> for StateOperation {
|
|
||||||
fn from(op: bool) -> Self {
|
|
||||||
if op {
|
|
||||||
StateOperation::Add
|
|
||||||
} else {
|
|
||||||
StateOperation::Remove
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// X window type. Maps directly to
|
|
||||||
/// [`_NET_WM_WINDOW_TYPE`](https://specifications.freedesktop.org/wm-spec/wm-spec-1.5.html).
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub enum WindowType {
|
|
||||||
/// A desktop feature. This can include a single window containing desktop icons with the same dimensions as the
|
|
||||||
/// screen, allowing the desktop environment to have full control of the desktop, without the need for proxying
|
|
||||||
/// root window clicks.
|
|
||||||
Desktop,
|
|
||||||
/// A dock or panel feature. Typically a Window Manager would keep such windows on top of all other windows.
|
|
||||||
Dock,
|
|
||||||
/// Toolbar windows. "Torn off" from the main application.
|
|
||||||
Toolbar,
|
|
||||||
/// Pinnable menu windows. "Torn off" from the main application.
|
|
||||||
Menu,
|
|
||||||
/// A small persistent utility window, such as a palette or toolbox.
|
|
||||||
Utility,
|
|
||||||
/// The window is a splash screen displayed as an application is starting up.
|
|
||||||
Splash,
|
|
||||||
/// This is a dialog window.
|
|
||||||
Dialog,
|
|
||||||
/// A dropdown menu that usually appears when the user clicks on an item in a menu bar.
|
|
||||||
/// This property is typically used on override-redirect windows.
|
|
||||||
DropdownMenu,
|
|
||||||
/// A popup menu that usually appears when the user right clicks on an object.
|
|
||||||
/// This property is typically used on override-redirect windows.
|
|
||||||
PopupMenu,
|
|
||||||
/// A tooltip window. Usually used to show additional information when hovering over an object with the cursor.
|
|
||||||
/// This property is typically used on override-redirect windows.
|
|
||||||
Tooltip,
|
|
||||||
/// The window is a notification.
|
|
||||||
/// This property is typically used on override-redirect windows.
|
|
||||||
Notification,
|
|
||||||
/// This should be used on the windows that are popped up by combo boxes.
|
|
||||||
/// This property is typically used on override-redirect windows.
|
|
||||||
Combo,
|
|
||||||
/// This indicates the the window is being dragged.
|
|
||||||
/// This property is typically used on override-redirect windows.
|
|
||||||
Dnd,
|
|
||||||
/// This is a normal, top-level window.
|
|
||||||
Normal,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for WindowType {
|
|
||||||
fn default() -> Self {
|
|
||||||
WindowType::Normal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowType {
|
|
||||||
pub(crate) fn as_atom(&self, xconn: &Arc<XConnection>) -> ffi::Atom {
|
|
||||||
use self::WindowType::*;
|
|
||||||
let atom_name: &[u8] = match *self {
|
|
||||||
Desktop => b"_NET_WM_WINDOW_TYPE_DESKTOP\0",
|
|
||||||
Dock => b"_NET_WM_WINDOW_TYPE_DOCK\0",
|
|
||||||
Toolbar => b"_NET_WM_WINDOW_TYPE_TOOLBAR\0",
|
|
||||||
Menu => b"_NET_WM_WINDOW_TYPE_MENU\0",
|
|
||||||
Utility => b"_NET_WM_WINDOW_TYPE_UTILITY\0",
|
|
||||||
Splash => b"_NET_WM_WINDOW_TYPE_SPLASH\0",
|
|
||||||
Dialog => b"_NET_WM_WINDOW_TYPE_DIALOG\0",
|
|
||||||
DropdownMenu => b"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU\0",
|
|
||||||
PopupMenu => b"_NET_WM_WINDOW_TYPE_POPUP_MENU\0",
|
|
||||||
Tooltip => b"_NET_WM_WINDOW_TYPE_TOOLTIP\0",
|
|
||||||
Notification => b"_NET_WM_WINDOW_TYPE_NOTIFICATION\0",
|
|
||||||
Combo => b"_NET_WM_WINDOW_TYPE_COMBO\0",
|
|
||||||
Dnd => b"_NET_WM_WINDOW_TYPE_DND\0",
|
|
||||||
Normal => b"_NET_WM_WINDOW_TYPE_NORMAL\0",
|
|
||||||
};
|
|
||||||
unsafe { xconn.get_atom_unchecked(atom_name) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct MotifHints {
|
|
||||||
hints: MwmHints,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
struct MwmHints {
|
|
||||||
flags: c_ulong,
|
|
||||||
functions: c_ulong,
|
|
||||||
decorations: c_ulong,
|
|
||||||
input_mode: c_long,
|
|
||||||
status: c_ulong,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
mod mwm {
|
|
||||||
use libc::c_ulong;
|
|
||||||
|
|
||||||
// Motif WM hints are obsolete, but still widely supported.
|
|
||||||
// https://stackoverflow.com/a/1909708
|
|
||||||
pub const MWM_HINTS_FUNCTIONS: c_ulong = 1 << 0;
|
|
||||||
pub const MWM_HINTS_DECORATIONS: c_ulong = 1 << 1;
|
|
||||||
|
|
||||||
pub const MWM_FUNC_ALL: c_ulong = 1 << 0;
|
|
||||||
pub const MWM_FUNC_RESIZE: c_ulong = 1 << 1;
|
|
||||||
pub const MWM_FUNC_MOVE: c_ulong = 1 << 2;
|
|
||||||
pub const MWM_FUNC_MINIMIZE: c_ulong = 1 << 3;
|
|
||||||
pub const MWM_FUNC_MAXIMIZE: c_ulong = 1 << 4;
|
|
||||||
pub const MWM_FUNC_CLOSE: c_ulong = 1 << 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MotifHints {
|
|
||||||
pub fn new() -> MotifHints {
|
|
||||||
MotifHints {
|
|
||||||
hints: MwmHints {
|
|
||||||
flags: 0,
|
|
||||||
functions: 0,
|
|
||||||
decorations: 0,
|
|
||||||
input_mode: 0,
|
|
||||||
status: 0,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_decorations(&mut self, decorations: bool) {
|
|
||||||
self.hints.flags |= mwm::MWM_HINTS_DECORATIONS;
|
|
||||||
self.hints.decorations = decorations as c_ulong;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_maximizable(&mut self, maximizable: bool) {
|
|
||||||
if maximizable {
|
|
||||||
self.add_func(mwm::MWM_FUNC_MAXIMIZE);
|
|
||||||
} else {
|
|
||||||
self.remove_func(mwm::MWM_FUNC_MAXIMIZE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_func(&mut self, func: c_ulong) {
|
|
||||||
if self.hints.flags & mwm::MWM_HINTS_FUNCTIONS != 0 {
|
|
||||||
if self.hints.functions & mwm::MWM_FUNC_ALL != 0 {
|
|
||||||
self.hints.functions &= !func;
|
|
||||||
} else {
|
|
||||||
self.hints.functions |= func;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_func(&mut self, func: c_ulong) {
|
|
||||||
if self.hints.flags & mwm::MWM_HINTS_FUNCTIONS == 0 {
|
|
||||||
self.hints.flags |= mwm::MWM_HINTS_FUNCTIONS;
|
|
||||||
self.hints.functions = mwm::MWM_FUNC_ALL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.hints.functions & mwm::MWM_FUNC_ALL != 0 {
|
|
||||||
self.hints.functions |= func;
|
|
||||||
} else {
|
|
||||||
self.hints.functions &= !func;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for MotifHints {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MwmHints {
|
|
||||||
fn as_slice(&self) -> &[c_ulong] {
|
|
||||||
unsafe { slice::from_raw_parts(self as *const _ as *const c_ulong, 5) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct NormalHints<'a> {
|
|
||||||
size_hints: XSmartPointer<'a, ffi::XSizeHints>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> NormalHints<'a> {
|
|
||||||
pub fn new(xconn: &'a XConnection) -> Self {
|
|
||||||
NormalHints {
|
|
||||||
size_hints: xconn.alloc_size_hints(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_position(&self) -> Option<(i32, i32)> {
|
|
||||||
if has_flag(self.size_hints.flags, ffi::PPosition) {
|
|
||||||
Some((self.size_hints.x as i32, self.size_hints.y as i32))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_position(&mut self, position: Option<(i32, i32)>) {
|
|
||||||
if let Some((x, y)) = position {
|
|
||||||
self.size_hints.flags |= ffi::PPosition;
|
|
||||||
self.size_hints.x = x as c_int;
|
|
||||||
self.size_hints.y = y as c_int;
|
|
||||||
} else {
|
|
||||||
self.size_hints.flags &= !ffi::PPosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WARNING: This hint is obsolete
|
|
||||||
pub fn set_size(&mut self, size: Option<(u32, u32)>) {
|
|
||||||
if let Some((width, height)) = size {
|
|
||||||
self.size_hints.flags |= ffi::PSize;
|
|
||||||
self.size_hints.width = width as c_int;
|
|
||||||
self.size_hints.height = height as c_int;
|
|
||||||
} else {
|
|
||||||
self.size_hints.flags &= !ffi::PSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_max_size(&mut self, max_size: Option<(u32, u32)>) {
|
|
||||||
if let Some((max_width, max_height)) = max_size {
|
|
||||||
self.size_hints.flags |= ffi::PMaxSize;
|
|
||||||
self.size_hints.max_width = max_width as c_int;
|
|
||||||
self.size_hints.max_height = max_height as c_int;
|
|
||||||
} else {
|
|
||||||
self.size_hints.flags &= !ffi::PMaxSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_min_size(&mut self, min_size: Option<(u32, u32)>) {
|
|
||||||
if let Some((min_width, min_height)) = min_size {
|
|
||||||
self.size_hints.flags |= ffi::PMinSize;
|
|
||||||
self.size_hints.min_width = min_width as c_int;
|
|
||||||
self.size_hints.min_height = min_height as c_int;
|
|
||||||
} else {
|
|
||||||
self.size_hints.flags &= !ffi::PMinSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_resize_increments(&mut self, resize_increments: Option<(u32, u32)>) {
|
|
||||||
if let Some((width_inc, height_inc)) = resize_increments {
|
|
||||||
self.size_hints.flags |= ffi::PResizeInc;
|
|
||||||
self.size_hints.width_inc = width_inc as c_int;
|
|
||||||
self.size_hints.height_inc = height_inc as c_int;
|
|
||||||
} else {
|
|
||||||
self.size_hints.flags &= !ffi::PResizeInc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_base_size(&mut self, base_size: Option<(u32, u32)>) {
|
|
||||||
if let Some((base_width, base_height)) = base_size {
|
|
||||||
self.size_hints.flags |= ffi::PBaseSize;
|
|
||||||
self.size_hints.base_width = base_width as c_int;
|
|
||||||
self.size_hints.base_height = base_height as c_int;
|
|
||||||
} else {
|
|
||||||
self.size_hints.flags &= !ffi::PBaseSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl XConnection {
|
|
||||||
pub fn get_wm_hints(
|
|
||||||
&self,
|
|
||||||
window: ffi::Window,
|
|
||||||
) -> Result<XSmartPointer<'_, ffi::XWMHints>, XError> {
|
|
||||||
let wm_hints = unsafe { (self.xlib.XGetWMHints)(self.display, window) };
|
|
||||||
self.check_errors()?;
|
|
||||||
let wm_hints = if wm_hints.is_null() {
|
|
||||||
self.alloc_wm_hints()
|
|
||||||
} else {
|
|
||||||
XSmartPointer::new(self, wm_hints).unwrap()
|
|
||||||
};
|
|
||||||
Ok(wm_hints)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_wm_hints(
|
|
||||||
&self,
|
|
||||||
window: ffi::Window,
|
|
||||||
wm_hints: XSmartPointer<'_, ffi::XWMHints>,
|
|
||||||
) -> Flusher<'_> {
|
|
||||||
unsafe {
|
|
||||||
(self.xlib.XSetWMHints)(self.display, window, wm_hints.ptr);
|
|
||||||
}
|
|
||||||
Flusher::new(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_normal_hints(&self, window: ffi::Window) -> Result<NormalHints<'_>, XError> {
|
|
||||||
let size_hints = self.alloc_size_hints();
|
|
||||||
let mut supplied_by_user = MaybeUninit::uninit();
|
|
||||||
unsafe {
|
|
||||||
(self.xlib.XGetWMNormalHints)(
|
|
||||||
self.display,
|
|
||||||
window,
|
|
||||||
size_hints.ptr,
|
|
||||||
supplied_by_user.as_mut_ptr(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
self.check_errors().map(|_| NormalHints { size_hints })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_normal_hints(
|
|
||||||
&self,
|
|
||||||
window: ffi::Window,
|
|
||||||
normal_hints: NormalHints<'_>,
|
|
||||||
) -> Flusher<'_> {
|
|
||||||
unsafe {
|
|
||||||
(self.xlib.XSetWMNormalHints)(self.display, window, normal_hints.size_hints.ptr);
|
|
||||||
}
|
|
||||||
Flusher::new(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_motif_hints(&self, window: ffi::Window) -> MotifHints {
|
|
||||||
let motif_hints = unsafe { self.get_atom_unchecked(b"_MOTIF_WM_HINTS\0") };
|
|
||||||
|
|
||||||
let mut hints = MotifHints::new();
|
|
||||||
|
|
||||||
if let Ok(props) = self.get_property::<c_ulong>(window, motif_hints, motif_hints) {
|
|
||||||
hints.hints.flags = props.first().cloned().unwrap_or(0);
|
|
||||||
hints.hints.functions = props.get(1).cloned().unwrap_or(0);
|
|
||||||
hints.hints.decorations = props.get(2).cloned().unwrap_or(0);
|
|
||||||
hints.hints.input_mode = props.get(3).cloned().unwrap_or(0) as c_long;
|
|
||||||
hints.hints.status = props.get(4).cloned().unwrap_or(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
hints
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_motif_hints(&self, window: ffi::Window, hints: &MotifHints) -> Flusher<'_> {
|
|
||||||
let motif_hints = unsafe { self.get_atom_unchecked(b"_MOTIF_WM_HINTS\0") };
|
|
||||||
|
|
||||||
self.change_property(
|
|
||||||
window,
|
|
||||||
motif_hints,
|
|
||||||
motif_hints,
|
|
||||||
PropMode::Replace,
|
|
||||||
hints.hints.as_slice(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,190 +0,0 @@
|
|||||||
use std::{slice, str};
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::event::ModifiersState;
|
|
||||||
|
|
||||||
pub const VIRTUAL_CORE_POINTER: c_int = 2;
|
|
||||||
pub const VIRTUAL_CORE_KEYBOARD: c_int = 3;
|
|
||||||
|
|
||||||
// A base buffer size of 1kB uses a negligible amount of RAM while preventing us from having to
|
|
||||||
// re-allocate (and make another round-trip) in the *vast* majority of cases.
|
|
||||||
// To test if `lookup_utf8` works correctly, set this to 1.
|
|
||||||
const TEXT_BUFFER_SIZE: usize = 1024;
|
|
||||||
|
|
||||||
impl ModifiersState {
|
|
||||||
pub(crate) fn from_x11(state: &ffi::XIModifierState) -> Self {
|
|
||||||
ModifiersState::from_x11_mask(state.effective as c_uint)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn from_x11_mask(mask: c_uint) -> Self {
|
|
||||||
let mut m = ModifiersState::empty();
|
|
||||||
m.set(ModifiersState::ALT, mask & ffi::Mod1Mask != 0);
|
|
||||||
m.set(ModifiersState::SHIFT, mask & ffi::ShiftMask != 0);
|
|
||||||
m.set(ModifiersState::CTRL, mask & ffi::ControlMask != 0);
|
|
||||||
m.set(ModifiersState::LOGO, mask & ffi::Mod4Mask != 0);
|
|
||||||
m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: Some of these fields are not used, but may be of use in the future.
|
|
||||||
pub struct PointerState<'a> {
|
|
||||||
xconn: &'a XConnection,
|
|
||||||
pub root: ffi::Window,
|
|
||||||
pub child: ffi::Window,
|
|
||||||
pub root_x: c_double,
|
|
||||||
pub root_y: c_double,
|
|
||||||
pub win_x: c_double,
|
|
||||||
pub win_y: c_double,
|
|
||||||
buttons: ffi::XIButtonState,
|
|
||||||
modifiers: ffi::XIModifierState,
|
|
||||||
pub group: ffi::XIGroupState,
|
|
||||||
pub relative_to_window: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> PointerState<'a> {
|
|
||||||
pub fn get_modifier_state(&self) -> ModifiersState {
|
|
||||||
ModifiersState::from_x11(&self.modifiers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Drop for PointerState<'a> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if !self.buttons.mask.is_null() {
|
|
||||||
unsafe {
|
|
||||||
// This is why you need to read the docs carefully...
|
|
||||||
(self.xconn.xlib.XFree)(self.buttons.mask as _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl XConnection {
|
|
||||||
pub fn select_xinput_events(
|
|
||||||
&self,
|
|
||||||
window: c_ulong,
|
|
||||||
device_id: c_int,
|
|
||||||
mask: i32,
|
|
||||||
) -> Flusher<'_> {
|
|
||||||
let mut event_mask = ffi::XIEventMask {
|
|
||||||
deviceid: device_id,
|
|
||||||
mask: &mask as *const _ as *mut c_uchar,
|
|
||||||
mask_len: mem::size_of_val(&mask) as c_int,
|
|
||||||
};
|
|
||||||
unsafe {
|
|
||||||
(self.xinput2.XISelectEvents)(
|
|
||||||
self.display,
|
|
||||||
window,
|
|
||||||
&mut event_mask as *mut ffi::XIEventMask,
|
|
||||||
1, // number of masks to read from pointer above
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Flusher::new(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn select_xkb_events(&self, device_id: c_uint, mask: c_ulong) -> Option<Flusher<'_>> {
|
|
||||||
let status = unsafe { (self.xlib.XkbSelectEvents)(self.display, device_id, mask, mask) };
|
|
||||||
if status == ffi::True {
|
|
||||||
Some(Flusher::new(self))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn query_pointer(
|
|
||||||
&self,
|
|
||||||
window: ffi::Window,
|
|
||||||
device_id: c_int,
|
|
||||||
) -> Result<PointerState<'_>, XError> {
|
|
||||||
unsafe {
|
|
||||||
let mut root = 0;
|
|
||||||
let mut child = 0;
|
|
||||||
let mut root_x = 0.0;
|
|
||||||
let mut root_y = 0.0;
|
|
||||||
let mut win_x = 0.0;
|
|
||||||
let mut win_y = 0.0;
|
|
||||||
let mut buttons = Default::default();
|
|
||||||
let mut modifiers = Default::default();
|
|
||||||
let mut group = Default::default();
|
|
||||||
|
|
||||||
let relative_to_window = (self.xinput2.XIQueryPointer)(
|
|
||||||
self.display,
|
|
||||||
device_id,
|
|
||||||
window,
|
|
||||||
&mut root,
|
|
||||||
&mut child,
|
|
||||||
&mut root_x,
|
|
||||||
&mut root_y,
|
|
||||||
&mut win_x,
|
|
||||||
&mut win_y,
|
|
||||||
&mut buttons,
|
|
||||||
&mut modifiers,
|
|
||||||
&mut group,
|
|
||||||
) == ffi::True;
|
|
||||||
|
|
||||||
self.check_errors()?;
|
|
||||||
|
|
||||||
Ok(PointerState {
|
|
||||||
xconn: self,
|
|
||||||
root,
|
|
||||||
child,
|
|
||||||
root_x,
|
|
||||||
root_y,
|
|
||||||
win_x,
|
|
||||||
win_y,
|
|
||||||
buttons,
|
|
||||||
modifiers,
|
|
||||||
group,
|
|
||||||
relative_to_window,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lookup_utf8_inner(
|
|
||||||
&self,
|
|
||||||
ic: ffi::XIC,
|
|
||||||
key_event: &mut ffi::XKeyEvent,
|
|
||||||
buffer: *mut u8,
|
|
||||||
size: usize,
|
|
||||||
) -> (ffi::KeySym, ffi::Status, c_int) {
|
|
||||||
let mut keysym: ffi::KeySym = 0;
|
|
||||||
let mut status: ffi::Status = 0;
|
|
||||||
let count = unsafe {
|
|
||||||
(self.xlib.Xutf8LookupString)(
|
|
||||||
ic,
|
|
||||||
key_event,
|
|
||||||
buffer as *mut c_char,
|
|
||||||
size as c_int,
|
|
||||||
&mut keysym,
|
|
||||||
&mut status,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
(keysym, status, count)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lookup_utf8(&self, ic: ffi::XIC, key_event: &mut ffi::XKeyEvent) -> String {
|
|
||||||
// `assume_init` is safe here because the array consists of `MaybeUninit` values,
|
|
||||||
// which do not require initialization.
|
|
||||||
let mut buffer: [MaybeUninit<u8>; TEXT_BUFFER_SIZE] =
|
|
||||||
unsafe { MaybeUninit::uninit().assume_init() };
|
|
||||||
// If the buffer overflows, we'll make a new one on the heap.
|
|
||||||
let mut vec;
|
|
||||||
|
|
||||||
let (_, status, count) =
|
|
||||||
self.lookup_utf8_inner(ic, key_event, buffer.as_mut_ptr() as *mut u8, buffer.len());
|
|
||||||
|
|
||||||
let bytes = if status == ffi::XBufferOverflow {
|
|
||||||
vec = Vec::with_capacity(count as usize);
|
|
||||||
let (_, _, new_count) =
|
|
||||||
self.lookup_utf8_inner(ic, key_event, vec.as_mut_ptr(), vec.capacity());
|
|
||||||
debug_assert_eq!(count, new_count);
|
|
||||||
|
|
||||||
unsafe { vec.set_len(count as usize) };
|
|
||||||
&vec[..count as usize]
|
|
||||||
} else {
|
|
||||||
unsafe { slice::from_raw_parts(buffer.as_ptr() as *const u8, count as usize) }
|
|
||||||
};
|
|
||||||
|
|
||||||
str::from_utf8(bytes).unwrap_or("").to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,223 +0,0 @@
|
|||||||
use std::{env, slice, str::FromStr};
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
ffi::{CurrentTime, RRCrtc, RRMode, Success, XRRCrtcInfo, XRRScreenResources},
|
|
||||||
*,
|
|
||||||
};
|
|
||||||
use crate::platform_impl::platform::x11::monitor;
|
|
||||||
use crate::{dpi::validate_scale_factor, platform_impl::platform::x11::VideoMode};
|
|
||||||
|
|
||||||
/// Represents values of `WINIT_HIDPI_FACTOR`.
|
|
||||||
pub enum EnvVarDPI {
|
|
||||||
Randr,
|
|
||||||
Scale(f64),
|
|
||||||
NotSet,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn calc_dpi_factor(
|
|
||||||
(width_px, height_px): (u32, u32),
|
|
||||||
(width_mm, height_mm): (u64, u64),
|
|
||||||
) -> f64 {
|
|
||||||
// See http://xpra.org/trac/ticket/728 for more information.
|
|
||||||
if width_mm == 0 || height_mm == 0 {
|
|
||||||
warn!("XRandR reported that the display's 0mm in size, which is certifiably insane");
|
|
||||||
return 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let ppmm = ((width_px as f64 * height_px as f64) / (width_mm as f64 * height_mm as f64)).sqrt();
|
|
||||||
// Quantize 1/12 step size
|
|
||||||
let dpi_factor = ((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0);
|
|
||||||
assert!(validate_scale_factor(dpi_factor));
|
|
||||||
if dpi_factor <= 20. {
|
|
||||||
dpi_factor
|
|
||||||
} else {
|
|
||||||
1.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl XConnection {
|
|
||||||
// Retrieve DPI from Xft.dpi property
|
|
||||||
pub unsafe fn get_xft_dpi(&self) -> Option<f64> {
|
|
||||||
(self.xlib.XrmInitialize)();
|
|
||||||
let resource_manager_str = (self.xlib.XResourceManagerString)(self.display);
|
|
||||||
if resource_manager_str.is_null() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if let Ok(res) = ::std::ffi::CStr::from_ptr(resource_manager_str).to_str() {
|
|
||||||
let name: &str = "Xft.dpi:\t";
|
|
||||||
for pair in res.split('\n') {
|
|
||||||
if pair.starts_with(&name) {
|
|
||||||
let res = &pair[name.len()..];
|
|
||||||
return f64::from_str(res).ok();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
pub unsafe fn get_output_info(
|
|
||||||
&self,
|
|
||||||
resources: *mut XRRScreenResources,
|
|
||||||
crtc: *mut XRRCrtcInfo,
|
|
||||||
) -> Option<(String, f64, Vec<VideoMode>)> {
|
|
||||||
let output_info =
|
|
||||||
(self.xrandr.XRRGetOutputInfo)(self.display, resources, *(*crtc).outputs.offset(0));
|
|
||||||
if output_info.is_null() {
|
|
||||||
// When calling `XRRGetOutputInfo` on a virtual monitor (versus a physical display)
|
|
||||||
// it's possible for it to return null.
|
|
||||||
// https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=816596
|
|
||||||
let _ = self.check_errors(); // discard `BadRROutput` error
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let screen = (self.xlib.XDefaultScreen)(self.display);
|
|
||||||
let bit_depth = (self.xlib.XDefaultDepth)(self.display, screen);
|
|
||||||
|
|
||||||
let output_modes =
|
|
||||||
slice::from_raw_parts((*output_info).modes, (*output_info).nmode as usize);
|
|
||||||
let resource_modes = slice::from_raw_parts((*resources).modes, (*resources).nmode as usize);
|
|
||||||
|
|
||||||
let modes = resource_modes
|
|
||||||
.iter()
|
|
||||||
// XRROutputInfo contains an array of mode ids that correspond to
|
|
||||||
// modes in the array in XRRScreenResources
|
|
||||||
.filter(|x| output_modes.iter().any(|id| x.id == *id))
|
|
||||||
.map(|mode| {
|
|
||||||
VideoMode {
|
|
||||||
size: (mode.width, mode.height),
|
|
||||||
refresh_rate_millihertz: monitor::mode_refresh_rate_millihertz(mode)
|
|
||||||
.unwrap_or(0),
|
|
||||||
bit_depth: bit_depth as u16,
|
|
||||||
native_mode: mode.id,
|
|
||||||
// This is populated in `MonitorHandle::video_modes` as the
|
|
||||||
// video mode is returned to the user
|
|
||||||
monitor: None,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let name_slice = slice::from_raw_parts(
|
|
||||||
(*output_info).name as *mut u8,
|
|
||||||
(*output_info).nameLen as usize,
|
|
||||||
);
|
|
||||||
let name = String::from_utf8_lossy(name_slice).into();
|
|
||||||
// Override DPI if `WINIT_X11_SCALE_FACTOR` variable is set
|
|
||||||
let deprecated_dpi_override = env::var("WINIT_HIDPI_FACTOR").ok();
|
|
||||||
if deprecated_dpi_override.is_some() {
|
|
||||||
warn!(
|
|
||||||
"The WINIT_HIDPI_FACTOR environment variable is deprecated; use WINIT_X11_SCALE_FACTOR"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
let dpi_env = env::var("WINIT_X11_SCALE_FACTOR").ok().map_or_else(
|
|
||||||
|| EnvVarDPI::NotSet,
|
|
||||||
|var| {
|
|
||||||
if var.to_lowercase() == "randr" {
|
|
||||||
EnvVarDPI::Randr
|
|
||||||
} else if let Ok(dpi) = f64::from_str(&var) {
|
|
||||||
EnvVarDPI::Scale(dpi)
|
|
||||||
} else if var.is_empty() {
|
|
||||||
EnvVarDPI::NotSet
|
|
||||||
} else {
|
|
||||||
panic!(
|
|
||||||
"`WINIT_X11_SCALE_FACTOR` invalid; DPI factors must be either normal floats greater than 0, or `randr`. Got `{}`",
|
|
||||||
var
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let scale_factor = match dpi_env {
|
|
||||||
EnvVarDPI::Randr => calc_dpi_factor(
|
|
||||||
((*crtc).width as u32, (*crtc).height as u32),
|
|
||||||
(
|
|
||||||
(*output_info).mm_width as u64,
|
|
||||||
(*output_info).mm_height as u64,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
EnvVarDPI::Scale(dpi_override) => {
|
|
||||||
if !validate_scale_factor(dpi_override) {
|
|
||||||
panic!(
|
|
||||||
"`WINIT_X11_SCALE_FACTOR` invalid; DPI factors must be either normal floats greater than 0, or `randr`. Got `{}`",
|
|
||||||
dpi_override,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
dpi_override
|
|
||||||
}
|
|
||||||
EnvVarDPI::NotSet => {
|
|
||||||
if let Some(dpi) = self.get_xft_dpi() {
|
|
||||||
dpi / 96.
|
|
||||||
} else {
|
|
||||||
calc_dpi_factor(
|
|
||||||
((*crtc).width as u32, (*crtc).height as u32),
|
|
||||||
(
|
|
||||||
(*output_info).mm_width as u64,
|
|
||||||
(*output_info).mm_height as u64,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
(self.xrandr.XRRFreeOutputInfo)(output_info);
|
|
||||||
Some((name, scale_factor, modes))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[must_use]
|
|
||||||
pub fn set_crtc_config(&self, crtc_id: RRCrtc, mode_id: RRMode) -> Option<()> {
|
|
||||||
unsafe {
|
|
||||||
let mut major = 0;
|
|
||||||
let mut minor = 0;
|
|
||||||
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
|
|
||||||
|
|
||||||
let root = (self.xlib.XDefaultRootWindow)(self.display);
|
|
||||||
let resources = if (major == 1 && minor >= 3) || major > 1 {
|
|
||||||
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
|
|
||||||
} else {
|
|
||||||
(self.xrandr.XRRGetScreenResources)(self.display, root)
|
|
||||||
};
|
|
||||||
|
|
||||||
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
|
|
||||||
let status = (self.xrandr.XRRSetCrtcConfig)(
|
|
||||||
self.display,
|
|
||||||
resources,
|
|
||||||
crtc_id,
|
|
||||||
CurrentTime,
|
|
||||||
(*crtc).x,
|
|
||||||
(*crtc).y,
|
|
||||||
mode_id,
|
|
||||||
(*crtc).rotation,
|
|
||||||
(*crtc).outputs.offset(0),
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
|
|
||||||
(self.xrandr.XRRFreeCrtcInfo)(crtc);
|
|
||||||
(self.xrandr.XRRFreeScreenResources)(resources);
|
|
||||||
|
|
||||||
if status == Success as i32 {
|
|
||||||
Some(())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_crtc_mode(&self, crtc_id: RRCrtc) -> RRMode {
|
|
||||||
unsafe {
|
|
||||||
let mut major = 0;
|
|
||||||
let mut minor = 0;
|
|
||||||
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
|
|
||||||
|
|
||||||
let root = (self.xlib.XDefaultRootWindow)(self.display);
|
|
||||||
let resources = if (major == 1 && minor >= 3) || major > 1 {
|
|
||||||
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
|
|
||||||
} else {
|
|
||||||
(self.xrandr.XRRGetScreenResources)(self.display, root)
|
|
||||||
};
|
|
||||||
|
|
||||||
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
|
|
||||||
let mode = (*crtc).mode;
|
|
||||||
(self.xrandr.XRRFreeCrtcInfo)(crtc);
|
|
||||||
(self.xrandr.XRRFreeScreenResources)(resources);
|
|
||||||
mode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
use super::*;
|
|
||||||
|
|
||||||
pub type Cardinal = c_long;
|
|
||||||
pub const CARDINAL_SIZE: usize = mem::size_of::<c_long>();
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum GetPropertyError {
|
|
||||||
XError(XError),
|
|
||||||
TypeMismatch(ffi::Atom),
|
|
||||||
FormatMismatch(c_int),
|
|
||||||
NothingAllocated,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GetPropertyError {
|
|
||||||
pub fn is_actual_property_type(&self, t: ffi::Atom) -> bool {
|
|
||||||
if let GetPropertyError::TypeMismatch(actual_type) = *self {
|
|
||||||
actual_type == t
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Number of 32-bit chunks to retrieve per iteration of get_property's inner loop.
|
|
||||||
// To test if `get_property` works correctly, set this to 1.
|
|
||||||
const PROPERTY_BUFFER_SIZE: c_long = 1024; // 4k of RAM ought to be enough for anyone!
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub enum PropMode {
|
|
||||||
Replace = ffi::PropModeReplace as isize,
|
|
||||||
Prepend = ffi::PropModePrepend as isize,
|
|
||||||
Append = ffi::PropModeAppend as isize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl XConnection {
|
|
||||||
pub fn get_property<T: Formattable>(
|
|
||||||
&self,
|
|
||||||
window: c_ulong,
|
|
||||||
property: ffi::Atom,
|
|
||||||
property_type: ffi::Atom,
|
|
||||||
) -> Result<Vec<T>, GetPropertyError> {
|
|
||||||
let mut data = Vec::new();
|
|
||||||
let mut offset = 0;
|
|
||||||
|
|
||||||
let mut done = false;
|
|
||||||
let mut actual_type = 0;
|
|
||||||
let mut actual_format = 0;
|
|
||||||
let mut quantity_returned = 0;
|
|
||||||
let mut bytes_after = 0;
|
|
||||||
let mut buf: *mut c_uchar = ptr::null_mut();
|
|
||||||
|
|
||||||
while !done {
|
|
||||||
unsafe {
|
|
||||||
(self.xlib.XGetWindowProperty)(
|
|
||||||
self.display,
|
|
||||||
window,
|
|
||||||
property,
|
|
||||||
// This offset is in terms of 32-bit chunks.
|
|
||||||
offset,
|
|
||||||
// This is the quantity of 32-bit chunks to receive at once.
|
|
||||||
PROPERTY_BUFFER_SIZE,
|
|
||||||
ffi::False,
|
|
||||||
property_type,
|
|
||||||
&mut actual_type,
|
|
||||||
&mut actual_format,
|
|
||||||
// This is the quantity of items we retrieved in our format, NOT of 32-bit chunks!
|
|
||||||
&mut quantity_returned,
|
|
||||||
// ...and this is a quantity of bytes. So, this function deals in 3 different units.
|
|
||||||
&mut bytes_after,
|
|
||||||
&mut buf,
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Err(e) = self.check_errors() {
|
|
||||||
return Err(GetPropertyError::XError(e));
|
|
||||||
}
|
|
||||||
|
|
||||||
if actual_type != property_type {
|
|
||||||
return Err(GetPropertyError::TypeMismatch(actual_type));
|
|
||||||
}
|
|
||||||
|
|
||||||
let format_mismatch = Format::from_format(actual_format as _) != Some(T::FORMAT);
|
|
||||||
if format_mismatch {
|
|
||||||
return Err(GetPropertyError::FormatMismatch(actual_format));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !buf.is_null() {
|
|
||||||
offset += PROPERTY_BUFFER_SIZE;
|
|
||||||
let new_data =
|
|
||||||
std::slice::from_raw_parts(buf as *mut T, quantity_returned as usize);
|
|
||||||
/*println!(
|
|
||||||
"XGetWindowProperty prop:{:?} fmt:{:02} len:{:02} off:{:02} out:{:02}, buf:{:?}",
|
|
||||||
property,
|
|
||||||
mem::size_of::<T>() * 8,
|
|
||||||
data.len(),
|
|
||||||
offset,
|
|
||||||
quantity_returned,
|
|
||||||
new_data,
|
|
||||||
);*/
|
|
||||||
data.extend_from_slice(new_data);
|
|
||||||
// Fun fact: XGetWindowProperty allocates one extra byte at the end.
|
|
||||||
(self.xlib.XFree)(buf as _); // Don't try to access new_data after this.
|
|
||||||
} else {
|
|
||||||
return Err(GetPropertyError::NothingAllocated);
|
|
||||||
}
|
|
||||||
|
|
||||||
done = bytes_after == 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn change_property<'a, T: Formattable>(
|
|
||||||
&'a self,
|
|
||||||
window: c_ulong,
|
|
||||||
property: ffi::Atom,
|
|
||||||
property_type: ffi::Atom,
|
|
||||||
mode: PropMode,
|
|
||||||
new_value: &[T],
|
|
||||||
) -> Flusher<'a> {
|
|
||||||
debug_assert_eq!(mem::size_of::<T>(), T::FORMAT.get_actual_size());
|
|
||||||
unsafe {
|
|
||||||
(self.xlib.XChangeProperty)(
|
|
||||||
self.display,
|
|
||||||
window,
|
|
||||||
property,
|
|
||||||
property_type,
|
|
||||||
T::FORMAT as c_int,
|
|
||||||
mode as c_int,
|
|
||||||
new_value.as_ptr() as *const c_uchar,
|
|
||||||
new_value.len() as c_int,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
/*println!(
|
|
||||||
"XChangeProperty prop:{:?} val:{:?}",
|
|
||||||
property,
|
|
||||||
new_value,
|
|
||||||
);*/
|
|
||||||
Flusher::new(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,165 +0,0 @@
|
|||||||
use std::{collections::HashMap, error::Error, fmt, os::raw::c_int, ptr};
|
|
||||||
|
|
||||||
use libc;
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
|
|
||||||
use crate::window::CursorIcon;
|
|
||||||
|
|
||||||
use super::ffi;
|
|
||||||
|
|
||||||
/// A connection to an X server.
|
|
||||||
pub struct XConnection {
|
|
||||||
pub xlib: ffi::Xlib,
|
|
||||||
/// Exposes XRandR functions from version < 1.5
|
|
||||||
pub xrandr: ffi::Xrandr_2_2_0,
|
|
||||||
/// Exposes XRandR functions from version = 1.5
|
|
||||||
pub xrandr_1_5: Option<ffi::Xrandr>,
|
|
||||||
pub xcursor: ffi::Xcursor,
|
|
||||||
pub xinput2: ffi::XInput2,
|
|
||||||
pub xlib_xcb: ffi::Xlib_xcb,
|
|
||||||
pub xrender: ffi::Xrender,
|
|
||||||
pub display: *mut ffi::Display,
|
|
||||||
pub x11_fd: c_int,
|
|
||||||
pub latest_error: Mutex<Option<XError>>,
|
|
||||||
pub cursor_cache: Mutex<HashMap<Option<CursorIcon>, ffi::Cursor>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for XConnection {}
|
|
||||||
unsafe impl Sync for XConnection {}
|
|
||||||
|
|
||||||
pub type XErrorHandler =
|
|
||||||
Option<unsafe extern "C" fn(*mut ffi::Display, *mut ffi::XErrorEvent) -> libc::c_int>;
|
|
||||||
|
|
||||||
impl XConnection {
|
|
||||||
pub fn new(error_handler: XErrorHandler) -> Result<XConnection, XNotSupported> {
|
|
||||||
// opening the libraries
|
|
||||||
let xlib = ffi::Xlib::open()?;
|
|
||||||
let xcursor = ffi::Xcursor::open()?;
|
|
||||||
let xrandr = ffi::Xrandr_2_2_0::open()?;
|
|
||||||
let xrandr_1_5 = ffi::Xrandr::open().ok();
|
|
||||||
let xinput2 = ffi::XInput2::open()?;
|
|
||||||
let xlib_xcb = ffi::Xlib_xcb::open()?;
|
|
||||||
let xrender = ffi::Xrender::open()?;
|
|
||||||
|
|
||||||
unsafe { (xlib.XInitThreads)() };
|
|
||||||
unsafe { (xlib.XSetErrorHandler)(error_handler) };
|
|
||||||
|
|
||||||
// calling XOpenDisplay
|
|
||||||
let display = unsafe {
|
|
||||||
let display = (xlib.XOpenDisplay)(ptr::null());
|
|
||||||
if display.is_null() {
|
|
||||||
return Err(XNotSupported::XOpenDisplayFailed);
|
|
||||||
}
|
|
||||||
display
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get X11 socket file descriptor
|
|
||||||
let fd = unsafe { (xlib.XConnectionNumber)(display) };
|
|
||||||
|
|
||||||
Ok(XConnection {
|
|
||||||
xlib,
|
|
||||||
xrandr,
|
|
||||||
xrandr_1_5,
|
|
||||||
xcursor,
|
|
||||||
xinput2,
|
|
||||||
xlib_xcb,
|
|
||||||
xrender,
|
|
||||||
display,
|
|
||||||
x11_fd: fd,
|
|
||||||
latest_error: Mutex::new(None),
|
|
||||||
cursor_cache: Default::default(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks whether an error has been triggered by the previous function calls.
|
|
||||||
#[inline]
|
|
||||||
pub fn check_errors(&self) -> Result<(), XError> {
|
|
||||||
let error = self.latest_error.lock().take();
|
|
||||||
if let Some(error) = error {
|
|
||||||
Err(error)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Ignores any previous error.
|
|
||||||
#[inline]
|
|
||||||
pub fn ignore_error(&self) {
|
|
||||||
*self.latest_error.lock() = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for XConnection {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
self.display.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for XConnection {
|
|
||||||
#[inline]
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe { (self.xlib.XCloseDisplay)(self.display) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Error triggered by xlib.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct XError {
|
|
||||||
pub description: String,
|
|
||||||
pub error_code: u8,
|
|
||||||
pub request_code: u8,
|
|
||||||
pub minor_code: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for XError {}
|
|
||||||
|
|
||||||
impl fmt::Display for XError {
|
|
||||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
|
||||||
write!(
|
|
||||||
formatter,
|
|
||||||
"X error: {} (code: {}, request code: {}, minor code: {})",
|
|
||||||
self.description, self.error_code, self.request_code, self.minor_code
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Error returned if this system doesn't have XLib or can't create an X connection.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub enum XNotSupported {
|
|
||||||
/// Failed to load one or several shared libraries.
|
|
||||||
LibraryOpenError(ffi::OpenError),
|
|
||||||
/// Connecting to the X server with `XOpenDisplay` failed.
|
|
||||||
XOpenDisplayFailed, // TODO: add better message
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ffi::OpenError> for XNotSupported {
|
|
||||||
#[inline]
|
|
||||||
fn from(err: ffi::OpenError) -> XNotSupported {
|
|
||||||
XNotSupported::LibraryOpenError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl XNotSupported {
|
|
||||||
fn description(&self) -> &'static str {
|
|
||||||
match self {
|
|
||||||
XNotSupported::LibraryOpenError(_) => "Failed to load one of xlib's shared libraries",
|
|
||||||
XNotSupported::XOpenDisplayFailed => "Failed to open connection to X server",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for XNotSupported {
|
|
||||||
#[inline]
|
|
||||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
|
||||||
match *self {
|
|
||||||
XNotSupported::LibraryOpenError(ref err) => Some(err),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for XNotSupported {
|
|
||||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
|
||||||
formatter.write_str(self.description())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
use std::collections::VecDeque;
|
|
||||||
|
|
||||||
use cocoa::{
|
|
||||||
appkit::{self, NSEvent},
|
|
||||||
base::id,
|
|
||||||
};
|
|
||||||
use objc::{
|
|
||||||
declare::ClassDecl,
|
|
||||||
runtime::{Class, Object, Sel},
|
|
||||||
};
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
|
|
||||||
use super::{app_state::AppState, event::EventWrapper, util, DEVICE_ID};
|
|
||||||
use crate::event::{DeviceEvent, ElementState, Event};
|
|
||||||
|
|
||||||
pub struct AppClass(pub *const Class);
|
|
||||||
unsafe impl Send for AppClass {}
|
|
||||||
unsafe impl Sync for AppClass {}
|
|
||||||
|
|
||||||
pub static APP_CLASS: Lazy<AppClass> = Lazy::new(|| unsafe {
|
|
||||||
let superclass = class!(NSApplication);
|
|
||||||
let mut decl = ClassDecl::new("WinitApp", superclass).unwrap();
|
|
||||||
|
|
||||||
decl.add_method(
|
|
||||||
sel!(sendEvent:),
|
|
||||||
send_event as extern "C" fn(&Object, Sel, id),
|
|
||||||
);
|
|
||||||
|
|
||||||
AppClass(decl.register())
|
|
||||||
});
|
|
||||||
|
|
||||||
// Normally, holding Cmd + any key never sends us a `keyUp` event for that key.
|
|
||||||
// Overriding `sendEvent:` like this fixes that. (https://stackoverflow.com/a/15294196)
|
|
||||||
// Fun fact: Firefox still has this bug! (https://bugzilla.mozilla.org/show_bug.cgi?id=1299553)
|
|
||||||
extern "C" fn send_event(this: &Object, _sel: Sel, event: id) {
|
|
||||||
unsafe {
|
|
||||||
// For posterity, there are some undocumented event types
|
|
||||||
// (https://github.com/servo/cocoa-rs/issues/155)
|
|
||||||
// but that doesn't really matter here.
|
|
||||||
let event_type = event.eventType();
|
|
||||||
let modifier_flags = event.modifierFlags();
|
|
||||||
if event_type == appkit::NSKeyUp
|
|
||||||
&& util::has_flag(
|
|
||||||
modifier_flags,
|
|
||||||
appkit::NSEventModifierFlags::NSCommandKeyMask,
|
|
||||||
)
|
|
||||||
{
|
|
||||||
let key_window: id = msg_send![this, keyWindow];
|
|
||||||
let _: () = msg_send![key_window, sendEvent: event];
|
|
||||||
} else {
|
|
||||||
maybe_dispatch_device_event(event);
|
|
||||||
let superclass = util::superclass(this);
|
|
||||||
let _: () = msg_send![super(this, superclass), sendEvent: event];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn maybe_dispatch_device_event(event: id) {
|
|
||||||
let event_type = event.eventType();
|
|
||||||
match event_type {
|
|
||||||
appkit::NSMouseMoved
|
|
||||||
| appkit::NSLeftMouseDragged
|
|
||||||
| appkit::NSOtherMouseDragged
|
|
||||||
| appkit::NSRightMouseDragged => {
|
|
||||||
let mut events = VecDeque::with_capacity(3);
|
|
||||||
|
|
||||||
let delta_x = event.deltaX() as f64;
|
|
||||||
let delta_y = event.deltaY() as f64;
|
|
||||||
|
|
||||||
if delta_x != 0.0 {
|
|
||||||
events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent {
|
|
||||||
device_id: DEVICE_ID,
|
|
||||||
event: DeviceEvent::Motion {
|
|
||||||
axis: 0,
|
|
||||||
value: delta_x,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if delta_y != 0.0 {
|
|
||||||
events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent {
|
|
||||||
device_id: DEVICE_ID,
|
|
||||||
event: DeviceEvent::Motion {
|
|
||||||
axis: 1,
|
|
||||||
value: delta_y,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if delta_x != 0.0 || delta_y != 0.0 {
|
|
||||||
events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent {
|
|
||||||
device_id: DEVICE_ID,
|
|
||||||
event: DeviceEvent::MouseMotion {
|
|
||||||
delta: (delta_x, delta_y),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
AppState::queue_events(events);
|
|
||||||
}
|
|
||||||
appkit::NSLeftMouseDown | appkit::NSRightMouseDown | appkit::NSOtherMouseDown => {
|
|
||||||
let mut events = VecDeque::with_capacity(1);
|
|
||||||
|
|
||||||
events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent {
|
|
||||||
device_id: DEVICE_ID,
|
|
||||||
event: DeviceEvent::Button {
|
|
||||||
button: event.buttonNumber() as u32,
|
|
||||||
state: ElementState::Pressed,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
AppState::queue_events(events);
|
|
||||||
}
|
|
||||||
appkit::NSLeftMouseUp | appkit::NSRightMouseUp | appkit::NSOtherMouseUp => {
|
|
||||||
let mut events = VecDeque::with_capacity(1);
|
|
||||||
|
|
||||||
events.push_back(EventWrapper::StaticEvent(Event::DeviceEvent {
|
|
||||||
device_id: DEVICE_ID,
|
|
||||||
event: DeviceEvent::Button {
|
|
||||||
button: event.buttonNumber() as u32,
|
|
||||||
state: ElementState::Released,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
AppState::queue_events(events);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
use std::{
|
|
||||||
cell::{RefCell, RefMut},
|
|
||||||
os::raw::c_void,
|
|
||||||
};
|
|
||||||
|
|
||||||
use cocoa::base::id;
|
|
||||||
use objc::{
|
|
||||||
declare::ClassDecl,
|
|
||||||
runtime::{Class, Object, Sel},
|
|
||||||
};
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
|
|
||||||
use crate::{platform::macos::ActivationPolicy, platform_impl::platform::app_state::AppState};
|
|
||||||
|
|
||||||
static AUX_DELEGATE_STATE_NAME: &str = "auxState";
|
|
||||||
|
|
||||||
pub struct AuxDelegateState {
|
|
||||||
pub activation_policy: ActivationPolicy,
|
|
||||||
pub default_menu: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct AppDelegateClass(pub *const Class);
|
|
||||||
unsafe impl Send for AppDelegateClass {}
|
|
||||||
unsafe impl Sync for AppDelegateClass {}
|
|
||||||
|
|
||||||
pub static APP_DELEGATE_CLASS: Lazy<AppDelegateClass> = Lazy::new(|| unsafe {
|
|
||||||
let superclass = class!(NSResponder);
|
|
||||||
let mut decl = ClassDecl::new("WinitAppDelegate", superclass).unwrap();
|
|
||||||
|
|
||||||
decl.add_class_method(sel!(new), new as extern "C" fn(&Class, Sel) -> id);
|
|
||||||
decl.add_method(sel!(dealloc), dealloc as extern "C" fn(&Object, Sel));
|
|
||||||
|
|
||||||
decl.add_method(
|
|
||||||
sel!(applicationDidFinishLaunching:),
|
|
||||||
did_finish_launching as extern "C" fn(&Object, Sel, id),
|
|
||||||
);
|
|
||||||
decl.add_method(
|
|
||||||
sel!(applicationWillTerminate:),
|
|
||||||
will_terminate as extern "C" fn(&Object, Sel, id),
|
|
||||||
);
|
|
||||||
|
|
||||||
decl.add_ivar::<*mut c_void>(AUX_DELEGATE_STATE_NAME);
|
|
||||||
|
|
||||||
AppDelegateClass(decl.register())
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Safety: Assumes that Object is an instance of APP_DELEGATE_CLASS
|
|
||||||
pub unsafe fn get_aux_state_mut(this: &Object) -> RefMut<'_, AuxDelegateState> {
|
|
||||||
let ptr: *mut c_void = *this.get_ivar(AUX_DELEGATE_STATE_NAME);
|
|
||||||
// Watch out that this needs to be the correct type
|
|
||||||
(*(ptr as *mut RefCell<AuxDelegateState>)).borrow_mut()
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn new(class: &Class, _: Sel) -> id {
|
|
||||||
unsafe {
|
|
||||||
let this: id = msg_send![class, alloc];
|
|
||||||
let this: id = msg_send![this, init];
|
|
||||||
// TODO: Remove the need for this initialization here
|
|
||||||
(*this).set_ivar(
|
|
||||||
AUX_DELEGATE_STATE_NAME,
|
|
||||||
Box::into_raw(Box::new(RefCell::new(AuxDelegateState {
|
|
||||||
activation_policy: ActivationPolicy::Regular,
|
|
||||||
default_menu: true,
|
|
||||||
}))) as *mut c_void,
|
|
||||||
);
|
|
||||||
this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn dealloc(this: &Object, _: Sel) {
|
|
||||||
unsafe {
|
|
||||||
let state_ptr: *mut c_void = *(this.get_ivar(AUX_DELEGATE_STATE_NAME));
|
|
||||||
// As soon as the box is constructed it is immediately dropped, releasing the underlying
|
|
||||||
// memory
|
|
||||||
drop(Box::from_raw(state_ptr as *mut RefCell<AuxDelegateState>));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn did_finish_launching(this: &Object, _: Sel, _: id) {
|
|
||||||
trace_scope!("applicationDidFinishLaunching:");
|
|
||||||
AppState::launched(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn will_terminate(_this: &Object, _: Sel, _: id) {
|
|
||||||
trace!("Triggered `applicationWillTerminate`");
|
|
||||||
// TODO: Notify every window that it will be destroyed, like done in iOS?
|
|
||||||
AppState::exit();
|
|
||||||
trace!("Completed `applicationWillTerminate`");
|
|
||||||
}
|
|
||||||
@@ -1,468 +0,0 @@
|
|||||||
use std::{
|
|
||||||
cell::{RefCell, RefMut},
|
|
||||||
collections::VecDeque,
|
|
||||||
fmt::{self, Debug},
|
|
||||||
hint::unreachable_unchecked,
|
|
||||||
mem,
|
|
||||||
rc::{Rc, Weak},
|
|
||||||
sync::{
|
|
||||||
atomic::{AtomicBool, Ordering},
|
|
||||||
Mutex, MutexGuard,
|
|
||||||
},
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
|
|
||||||
use cocoa::{
|
|
||||||
appkit::{NSApp, NSApplication, NSWindow},
|
|
||||||
base::{id, nil},
|
|
||||||
foundation::NSSize,
|
|
||||||
};
|
|
||||||
use objc::{
|
|
||||||
rc::autoreleasepool,
|
|
||||||
runtime::{Object, BOOL, NO, YES},
|
|
||||||
};
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
dpi::LogicalSize,
|
|
||||||
event::{Event, StartCause, WindowEvent},
|
|
||||||
event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget},
|
|
||||||
platform::macos::ActivationPolicy,
|
|
||||||
platform_impl::{
|
|
||||||
get_aux_state_mut,
|
|
||||||
platform::{
|
|
||||||
event::{EventProxy, EventWrapper},
|
|
||||||
event_loop::{post_dummy_event, PanicInfo},
|
|
||||||
menu,
|
|
||||||
observer::{CFRunLoopGetMain, CFRunLoopWakeUp, EventLoopWaker},
|
|
||||||
util::{IdRef, Never},
|
|
||||||
window::get_window_id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
window::WindowId,
|
|
||||||
};
|
|
||||||
|
|
||||||
static HANDLER: Lazy<Handler> = Lazy::new(Default::default);
|
|
||||||
|
|
||||||
impl<'a, Never> Event<'a, Never> {
|
|
||||||
fn userify<T: 'static>(self) -> Event<'a, T> {
|
|
||||||
self.map_nonuser_event()
|
|
||||||
// `Never` can't be constructed, so the `UserEvent` variant can't
|
|
||||||
// be present here.
|
|
||||||
.unwrap_or_else(|_| unsafe { unreachable_unchecked() })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait EventHandler: Debug {
|
|
||||||
// Not sure probably it should accept Event<'static, Never>
|
|
||||||
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow);
|
|
||||||
fn handle_user_events(&mut self, control_flow: &mut ControlFlow);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) type Callback<T> =
|
|
||||||
RefCell<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>;
|
|
||||||
|
|
||||||
struct EventLoopHandler<T: 'static> {
|
|
||||||
callback: Weak<Callback<T>>,
|
|
||||||
window_target: Rc<RootWindowTarget<T>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopHandler<T> {
|
|
||||||
fn with_callback<F>(&mut self, f: F)
|
|
||||||
where
|
|
||||||
F: FnOnce(
|
|
||||||
&mut EventLoopHandler<T>,
|
|
||||||
RefMut<'_, dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>,
|
|
||||||
),
|
|
||||||
{
|
|
||||||
if let Some(callback) = self.callback.upgrade() {
|
|
||||||
let callback = callback.borrow_mut();
|
|
||||||
(f)(self, callback);
|
|
||||||
} else {
|
|
||||||
panic!(
|
|
||||||
"Tried to dispatch an event, but the event loop that \
|
|
||||||
owned the event handler callback seems to be destroyed"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Debug for EventLoopHandler<T> {
|
|
||||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
formatter
|
|
||||||
.debug_struct("EventLoopHandler")
|
|
||||||
.field("window_target", &self.window_target)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventHandler for EventLoopHandler<T> {
|
|
||||||
fn handle_nonuser_event(&mut self, event: Event<'_, Never>, control_flow: &mut ControlFlow) {
|
|
||||||
self.with_callback(|this, mut callback| {
|
|
||||||
if let ControlFlow::ExitWithCode(code) = *control_flow {
|
|
||||||
let dummy = &mut ControlFlow::ExitWithCode(code);
|
|
||||||
(callback)(event.userify(), &this.window_target, dummy);
|
|
||||||
} else {
|
|
||||||
(callback)(event.userify(), &this.window_target, control_flow);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_user_events(&mut self, control_flow: &mut ControlFlow) {
|
|
||||||
self.with_callback(|this, mut callback| {
|
|
||||||
for event in this.window_target.p.receiver.try_iter() {
|
|
||||||
if let ControlFlow::ExitWithCode(code) = *control_flow {
|
|
||||||
let dummy = &mut ControlFlow::ExitWithCode(code);
|
|
||||||
(callback)(Event::UserEvent(event), &this.window_target, dummy);
|
|
||||||
} else {
|
|
||||||
(callback)(Event::UserEvent(event), &this.window_target, control_flow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct Handler {
|
|
||||||
ready: AtomicBool,
|
|
||||||
in_callback: AtomicBool,
|
|
||||||
control_flow: Mutex<ControlFlow>,
|
|
||||||
control_flow_prev: Mutex<ControlFlow>,
|
|
||||||
start_time: Mutex<Option<Instant>>,
|
|
||||||
callback: Mutex<Option<Box<dyn EventHandler>>>,
|
|
||||||
pending_events: Mutex<VecDeque<EventWrapper>>,
|
|
||||||
pending_redraw: Mutex<Vec<WindowId>>,
|
|
||||||
waker: Mutex<EventLoopWaker>,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for Handler {}
|
|
||||||
unsafe impl Sync for Handler {}
|
|
||||||
|
|
||||||
impl Handler {
|
|
||||||
fn events(&self) -> MutexGuard<'_, VecDeque<EventWrapper>> {
|
|
||||||
self.pending_events.lock().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn redraw(&self) -> MutexGuard<'_, Vec<WindowId>> {
|
|
||||||
self.pending_redraw.lock().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn waker(&self) -> MutexGuard<'_, EventLoopWaker> {
|
|
||||||
self.waker.lock().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_ready(&self) -> bool {
|
|
||||||
self.ready.load(Ordering::Acquire)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_ready(&self) {
|
|
||||||
self.ready.store(true, Ordering::Release);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn should_exit(&self) -> bool {
|
|
||||||
matches!(
|
|
||||||
*self.control_flow.lock().unwrap(),
|
|
||||||
ControlFlow::ExitWithCode(_)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_control_flow_and_update_prev(&self) -> ControlFlow {
|
|
||||||
let control_flow = self.control_flow.lock().unwrap();
|
|
||||||
*self.control_flow_prev.lock().unwrap() = *control_flow;
|
|
||||||
*control_flow
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_old_and_new_control_flow(&self) -> (ControlFlow, ControlFlow) {
|
|
||||||
let old = *self.control_flow_prev.lock().unwrap();
|
|
||||||
let new = *self.control_flow.lock().unwrap();
|
|
||||||
(old, new)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_start_time(&self) -> Option<Instant> {
|
|
||||||
*self.start_time.lock().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_start_time(&self) {
|
|
||||||
*self.start_time.lock().unwrap() = Some(Instant::now());
|
|
||||||
}
|
|
||||||
|
|
||||||
fn take_events(&self) -> VecDeque<EventWrapper> {
|
|
||||||
mem::take(&mut *self.events())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn should_redraw(&self) -> Vec<WindowId> {
|
|
||||||
mem::take(&mut *self.redraw())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_in_callback(&self) -> bool {
|
|
||||||
self.in_callback.load(Ordering::Acquire)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_in_callback(&self, in_callback: bool) {
|
|
||||||
self.in_callback.store(in_callback, Ordering::Release);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_nonuser_event(&self, wrapper: EventWrapper) {
|
|
||||||
if let Some(ref mut callback) = *self.callback.lock().unwrap() {
|
|
||||||
match wrapper {
|
|
||||||
EventWrapper::StaticEvent(event) => {
|
|
||||||
callback.handle_nonuser_event(event, &mut *self.control_flow.lock().unwrap())
|
|
||||||
}
|
|
||||||
EventWrapper::EventProxy(proxy) => self.handle_proxy(proxy, callback),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_user_events(&self) {
|
|
||||||
if let Some(ref mut callback) = *self.callback.lock().unwrap() {
|
|
||||||
callback.handle_user_events(&mut *self.control_flow.lock().unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_scale_factor_changed_event(
|
|
||||||
&self,
|
|
||||||
callback: &mut Box<dyn EventHandler + 'static>,
|
|
||||||
ns_window: IdRef,
|
|
||||||
suggested_size: LogicalSize<f64>,
|
|
||||||
scale_factor: f64,
|
|
||||||
) {
|
|
||||||
let mut size = suggested_size.to_physical(scale_factor);
|
|
||||||
let new_inner_size = &mut size;
|
|
||||||
let event = Event::WindowEvent {
|
|
||||||
window_id: WindowId(get_window_id(*ns_window)),
|
|
||||||
event: WindowEvent::ScaleFactorChanged {
|
|
||||||
scale_factor,
|
|
||||||
new_inner_size,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
callback.handle_nonuser_event(event, &mut *self.control_flow.lock().unwrap());
|
|
||||||
|
|
||||||
let physical_size = *new_inner_size;
|
|
||||||
let logical_size = physical_size.to_logical(scale_factor);
|
|
||||||
let size = NSSize::new(logical_size.width, logical_size.height);
|
|
||||||
unsafe { NSWindow::setContentSize_(*ns_window, size) };
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_proxy(&self, proxy: EventProxy, callback: &mut Box<dyn EventHandler + 'static>) {
|
|
||||||
match proxy {
|
|
||||||
EventProxy::DpiChangedProxy {
|
|
||||||
ns_window,
|
|
||||||
suggested_size,
|
|
||||||
scale_factor,
|
|
||||||
} => self.handle_scale_factor_changed_event(
|
|
||||||
callback,
|
|
||||||
ns_window,
|
|
||||||
suggested_size,
|
|
||||||
scale_factor,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum AppState {}
|
|
||||||
|
|
||||||
impl AppState {
|
|
||||||
pub fn set_callback<T>(callback: Weak<Callback<T>>, window_target: Rc<RootWindowTarget<T>>) {
|
|
||||||
*HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler {
|
|
||||||
callback,
|
|
||||||
window_target,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn exit() -> i32 {
|
|
||||||
HANDLER.set_in_callback(true);
|
|
||||||
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::LoopDestroyed));
|
|
||||||
HANDLER.set_in_callback(false);
|
|
||||||
HANDLER.callback.lock().unwrap().take();
|
|
||||||
if let ControlFlow::ExitWithCode(code) = HANDLER.get_old_and_new_control_flow().1 {
|
|
||||||
code
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn launched(app_delegate: &Object) {
|
|
||||||
apply_activation_policy(app_delegate);
|
|
||||||
unsafe {
|
|
||||||
let ns_app = NSApp();
|
|
||||||
window_activation_hack(ns_app);
|
|
||||||
// TODO: Consider allowing the user to specify they don't want their application activated
|
|
||||||
ns_app.activateIgnoringOtherApps_(YES);
|
|
||||||
};
|
|
||||||
HANDLER.set_ready();
|
|
||||||
HANDLER.waker().start();
|
|
||||||
let create_default_menu = unsafe { get_aux_state_mut(app_delegate).default_menu };
|
|
||||||
if create_default_menu {
|
|
||||||
// The menubar initialization should be before the `NewEvents` event, to allow
|
|
||||||
// overriding of the default menu even if it's created
|
|
||||||
menu::initialize();
|
|
||||||
}
|
|
||||||
HANDLER.set_in_callback(true);
|
|
||||||
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents(
|
|
||||||
StartCause::Init,
|
|
||||||
)));
|
|
||||||
// NB: For consistency all platforms must emit a 'resumed' event even though macOS
|
|
||||||
// applications don't themselves have a formal suspend/resume lifecycle.
|
|
||||||
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed));
|
|
||||||
HANDLER.set_in_callback(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wakeup(panic_info: Weak<PanicInfo>) {
|
|
||||||
let panic_info = panic_info
|
|
||||||
.upgrade()
|
|
||||||
.expect("The panic info must exist here. This failure indicates a developer error.");
|
|
||||||
|
|
||||||
// Return when in callback due to https://github.com/rust-windowing/winit/issues/1779
|
|
||||||
if panic_info.is_panicking() || !HANDLER.is_ready() || HANDLER.get_in_callback() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let start = HANDLER.get_start_time().unwrap();
|
|
||||||
let cause = match HANDLER.get_control_flow_and_update_prev() {
|
|
||||||
ControlFlow::Poll => StartCause::Poll,
|
|
||||||
ControlFlow::Wait => StartCause::WaitCancelled {
|
|
||||||
start,
|
|
||||||
requested_resume: None,
|
|
||||||
},
|
|
||||||
ControlFlow::WaitUntil(requested_resume) => {
|
|
||||||
if Instant::now() >= requested_resume {
|
|
||||||
StartCause::ResumeTimeReached {
|
|
||||||
start,
|
|
||||||
requested_resume,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
StartCause::WaitCancelled {
|
|
||||||
start,
|
|
||||||
requested_resume: Some(requested_resume),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ControlFlow::ExitWithCode(_) => StartCause::Poll, //panic!("unexpected `ControlFlow::Exit`"),
|
|
||||||
};
|
|
||||||
HANDLER.set_in_callback(true);
|
|
||||||
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents(cause)));
|
|
||||||
HANDLER.set_in_callback(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is called from multiple threads at present
|
|
||||||
pub fn queue_redraw(window_id: WindowId) {
|
|
||||||
let mut pending_redraw = HANDLER.redraw();
|
|
||||||
if !pending_redraw.contains(&window_id) {
|
|
||||||
pending_redraw.push(window_id);
|
|
||||||
}
|
|
||||||
unsafe {
|
|
||||||
let rl = CFRunLoopGetMain();
|
|
||||||
CFRunLoopWakeUp(rl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_redraw(window_id: WindowId) {
|
|
||||||
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawRequested(window_id)));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn queue_event(wrapper: EventWrapper) {
|
|
||||||
let is_main_thread: BOOL = unsafe { msg_send!(class!(NSThread), isMainThread) };
|
|
||||||
if is_main_thread == NO {
|
|
||||||
panic!("Event queued from different thread: {:#?}", wrapper);
|
|
||||||
}
|
|
||||||
HANDLER.events().push_back(wrapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn queue_events(mut wrappers: VecDeque<EventWrapper>) {
|
|
||||||
let is_main_thread: BOOL = unsafe { msg_send!(class!(NSThread), isMainThread) };
|
|
||||||
if is_main_thread == NO {
|
|
||||||
panic!("Events queued from different thread: {:#?}", wrappers);
|
|
||||||
}
|
|
||||||
HANDLER.events().append(&mut wrappers);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cleared(panic_info: Weak<PanicInfo>) {
|
|
||||||
let panic_info = panic_info
|
|
||||||
.upgrade()
|
|
||||||
.expect("The panic info must exist here. This failure indicates a developer error.");
|
|
||||||
|
|
||||||
// Return when in callback due to https://github.com/rust-windowing/winit/issues/1779
|
|
||||||
if panic_info.is_panicking() || !HANDLER.is_ready() || HANDLER.get_in_callback() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
HANDLER.set_in_callback(true);
|
|
||||||
HANDLER.handle_user_events();
|
|
||||||
for event in HANDLER.take_events() {
|
|
||||||
HANDLER.handle_nonuser_event(event);
|
|
||||||
}
|
|
||||||
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::MainEventsCleared));
|
|
||||||
for window_id in HANDLER.should_redraw() {
|
|
||||||
HANDLER
|
|
||||||
.handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawRequested(window_id)));
|
|
||||||
}
|
|
||||||
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawEventsCleared));
|
|
||||||
HANDLER.set_in_callback(false);
|
|
||||||
|
|
||||||
if HANDLER.should_exit() {
|
|
||||||
unsafe {
|
|
||||||
let app: id = NSApp();
|
|
||||||
|
|
||||||
autoreleasepool(|| {
|
|
||||||
let _: () = msg_send![app, stop: nil];
|
|
||||||
// To stop event loop immediately, we need to post some event here.
|
|
||||||
post_dummy_event(app);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
HANDLER.update_start_time();
|
|
||||||
match HANDLER.get_old_and_new_control_flow() {
|
|
||||||
(ControlFlow::ExitWithCode(_), _) | (_, ControlFlow::ExitWithCode(_)) => (),
|
|
||||||
(old, new) if old == new => (),
|
|
||||||
(_, ControlFlow::Wait) => HANDLER.waker().stop(),
|
|
||||||
(_, ControlFlow::WaitUntil(instant)) => HANDLER.waker().start_at(instant),
|
|
||||||
(_, ControlFlow::Poll) => HANDLER.waker().start(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A hack to make activation of multiple windows work when creating them before
|
|
||||||
/// `applicationDidFinishLaunching:` / `Event::Event::NewEvents(StartCause::Init)`.
|
|
||||||
///
|
|
||||||
/// Alternative to this would be the user calling `window.set_visible(true)` in
|
|
||||||
/// `StartCause::Init`.
|
|
||||||
///
|
|
||||||
/// If this becomes too bothersome to maintain, it can probably be removed
|
|
||||||
/// without too much damage.
|
|
||||||
unsafe fn window_activation_hack(ns_app: id) {
|
|
||||||
// Get the application's windows
|
|
||||||
// TODO: Proper ordering of the windows
|
|
||||||
let ns_windows: id = msg_send![ns_app, windows];
|
|
||||||
let ns_enumerator: id = msg_send![ns_windows, objectEnumerator];
|
|
||||||
loop {
|
|
||||||
// Enumerate over the windows
|
|
||||||
let ns_window: id = msg_send![ns_enumerator, nextObject];
|
|
||||||
if ns_window == nil {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// And call `makeKeyAndOrderFront` if it was called on the window in `UnownedWindow::new`
|
|
||||||
// This way we preserve the user's desired initial visiblity status
|
|
||||||
// TODO: Also filter on the type/"level" of the window, and maybe other things?
|
|
||||||
if ns_window.isVisible() == YES {
|
|
||||||
trace!("Activating visible window");
|
|
||||||
ns_window.makeKeyAndOrderFront_(nil);
|
|
||||||
} else {
|
|
||||||
trace!("Skipping activating invisible window");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn apply_activation_policy(app_delegate: &Object) {
|
|
||||||
unsafe {
|
|
||||||
use cocoa::appkit::NSApplicationActivationPolicy::*;
|
|
||||||
let ns_app = NSApp();
|
|
||||||
// We need to delay setting the activation policy and activating the app
|
|
||||||
// until `applicationDidFinishLaunching` has been called. Otherwise the
|
|
||||||
// menu bar is initially unresponsive on macOS 10.15.
|
|
||||||
let act_pol = get_aux_state_mut(app_delegate).activation_policy;
|
|
||||||
ns_app.setActivationPolicy_(match act_pol {
|
|
||||||
ActivationPolicy::Regular => NSApplicationActivationPolicyRegular,
|
|
||||||
ActivationPolicy::Accessory => NSApplicationActivationPolicyAccessory,
|
|
||||||
ActivationPolicy::Prohibited => NSApplicationActivationPolicyProhibited,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,304 +0,0 @@
|
|||||||
use std::os::raw::c_ushort;
|
|
||||||
|
|
||||||
use cocoa::{
|
|
||||||
appkit::{NSEvent, NSEventModifierFlags},
|
|
||||||
base::id,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
dpi::LogicalSize,
|
|
||||||
event::{ElementState, Event, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent},
|
|
||||||
platform_impl::platform::{
|
|
||||||
util::{IdRef, Never},
|
|
||||||
DEVICE_ID,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum EventWrapper {
|
|
||||||
StaticEvent(Event<'static, Never>),
|
|
||||||
EventProxy(EventProxy),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum EventProxy {
|
|
||||||
DpiChangedProxy {
|
|
||||||
ns_window: IdRef,
|
|
||||||
suggested_size: LogicalSize<f64>,
|
|
||||||
scale_factor: f64,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn char_to_keycode(c: char) -> Option<VirtualKeyCode> {
|
|
||||||
// We only translate keys that are affected by keyboard layout.
|
|
||||||
//
|
|
||||||
// Note that since keys are translated in a somewhat "dumb" way (reading character)
|
|
||||||
// there is a concern that some combination, i.e. Cmd+char, causes the wrong
|
|
||||||
// letter to be received, and so we receive the wrong key.
|
|
||||||
//
|
|
||||||
// Implementation reference: https://github.com/WebKit/webkit/blob/82bae82cf0f329dbe21059ef0986c4e92fea4ba6/Source/WebCore/platform/cocoa/KeyEventCocoa.mm#L626
|
|
||||||
Some(match c {
|
|
||||||
'a' | 'A' => VirtualKeyCode::A,
|
|
||||||
'b' | 'B' => VirtualKeyCode::B,
|
|
||||||
'c' | 'C' => VirtualKeyCode::C,
|
|
||||||
'd' | 'D' => VirtualKeyCode::D,
|
|
||||||
'e' | 'E' => VirtualKeyCode::E,
|
|
||||||
'f' | 'F' => VirtualKeyCode::F,
|
|
||||||
'g' | 'G' => VirtualKeyCode::G,
|
|
||||||
'h' | 'H' => VirtualKeyCode::H,
|
|
||||||
'i' | 'I' => VirtualKeyCode::I,
|
|
||||||
'j' | 'J' => VirtualKeyCode::J,
|
|
||||||
'k' | 'K' => VirtualKeyCode::K,
|
|
||||||
'l' | 'L' => VirtualKeyCode::L,
|
|
||||||
'm' | 'M' => VirtualKeyCode::M,
|
|
||||||
'n' | 'N' => VirtualKeyCode::N,
|
|
||||||
'o' | 'O' => VirtualKeyCode::O,
|
|
||||||
'p' | 'P' => VirtualKeyCode::P,
|
|
||||||
'q' | 'Q' => VirtualKeyCode::Q,
|
|
||||||
'r' | 'R' => VirtualKeyCode::R,
|
|
||||||
's' | 'S' => VirtualKeyCode::S,
|
|
||||||
't' | 'T' => VirtualKeyCode::T,
|
|
||||||
'u' | 'U' => VirtualKeyCode::U,
|
|
||||||
'v' | 'V' => VirtualKeyCode::V,
|
|
||||||
'w' | 'W' => VirtualKeyCode::W,
|
|
||||||
'x' | 'X' => VirtualKeyCode::X,
|
|
||||||
'y' | 'Y' => VirtualKeyCode::Y,
|
|
||||||
'z' | 'Z' => VirtualKeyCode::Z,
|
|
||||||
'1' | '!' => VirtualKeyCode::Key1,
|
|
||||||
'2' | '@' => VirtualKeyCode::Key2,
|
|
||||||
'3' | '#' => VirtualKeyCode::Key3,
|
|
||||||
'4' | '$' => VirtualKeyCode::Key4,
|
|
||||||
'5' | '%' => VirtualKeyCode::Key5,
|
|
||||||
'6' | '^' => VirtualKeyCode::Key6,
|
|
||||||
'7' | '&' => VirtualKeyCode::Key7,
|
|
||||||
'8' | '*' => VirtualKeyCode::Key8,
|
|
||||||
'9' | '(' => VirtualKeyCode::Key9,
|
|
||||||
'0' | ')' => VirtualKeyCode::Key0,
|
|
||||||
'=' | '+' => VirtualKeyCode::Equals,
|
|
||||||
'-' | '_' => VirtualKeyCode::Minus,
|
|
||||||
']' | '}' => VirtualKeyCode::RBracket,
|
|
||||||
'[' | '{' => VirtualKeyCode::LBracket,
|
|
||||||
'\'' | '"' => VirtualKeyCode::Apostrophe,
|
|
||||||
';' | ':' => VirtualKeyCode::Semicolon,
|
|
||||||
'\\' | '|' => VirtualKeyCode::Backslash,
|
|
||||||
',' | '<' => VirtualKeyCode::Comma,
|
|
||||||
'/' | '?' => VirtualKeyCode::Slash,
|
|
||||||
'.' | '>' => VirtualKeyCode::Period,
|
|
||||||
'`' | '~' => VirtualKeyCode::Grave,
|
|
||||||
_ => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scancode_to_keycode(scancode: c_ushort) -> Option<VirtualKeyCode> {
|
|
||||||
Some(match scancode {
|
|
||||||
0x00 => VirtualKeyCode::A,
|
|
||||||
0x01 => VirtualKeyCode::S,
|
|
||||||
0x02 => VirtualKeyCode::D,
|
|
||||||
0x03 => VirtualKeyCode::F,
|
|
||||||
0x04 => VirtualKeyCode::H,
|
|
||||||
0x05 => VirtualKeyCode::G,
|
|
||||||
0x06 => VirtualKeyCode::Z,
|
|
||||||
0x07 => VirtualKeyCode::X,
|
|
||||||
0x08 => VirtualKeyCode::C,
|
|
||||||
0x09 => VirtualKeyCode::V,
|
|
||||||
//0x0a => World 1,
|
|
||||||
0x0b => VirtualKeyCode::B,
|
|
||||||
0x0c => VirtualKeyCode::Q,
|
|
||||||
0x0d => VirtualKeyCode::W,
|
|
||||||
0x0e => VirtualKeyCode::E,
|
|
||||||
0x0f => VirtualKeyCode::R,
|
|
||||||
0x10 => VirtualKeyCode::Y,
|
|
||||||
0x11 => VirtualKeyCode::T,
|
|
||||||
0x12 => VirtualKeyCode::Key1,
|
|
||||||
0x13 => VirtualKeyCode::Key2,
|
|
||||||
0x14 => VirtualKeyCode::Key3,
|
|
||||||
0x15 => VirtualKeyCode::Key4,
|
|
||||||
0x16 => VirtualKeyCode::Key6,
|
|
||||||
0x17 => VirtualKeyCode::Key5,
|
|
||||||
0x18 => VirtualKeyCode::Equals,
|
|
||||||
0x19 => VirtualKeyCode::Key9,
|
|
||||||
0x1a => VirtualKeyCode::Key7,
|
|
||||||
0x1b => VirtualKeyCode::Minus,
|
|
||||||
0x1c => VirtualKeyCode::Key8,
|
|
||||||
0x1d => VirtualKeyCode::Key0,
|
|
||||||
0x1e => VirtualKeyCode::RBracket,
|
|
||||||
0x1f => VirtualKeyCode::O,
|
|
||||||
0x20 => VirtualKeyCode::U,
|
|
||||||
0x21 => VirtualKeyCode::LBracket,
|
|
||||||
0x22 => VirtualKeyCode::I,
|
|
||||||
0x23 => VirtualKeyCode::P,
|
|
||||||
0x24 => VirtualKeyCode::Return,
|
|
||||||
0x25 => VirtualKeyCode::L,
|
|
||||||
0x26 => VirtualKeyCode::J,
|
|
||||||
0x27 => VirtualKeyCode::Apostrophe,
|
|
||||||
0x28 => VirtualKeyCode::K,
|
|
||||||
0x29 => VirtualKeyCode::Semicolon,
|
|
||||||
0x2a => VirtualKeyCode::Backslash,
|
|
||||||
0x2b => VirtualKeyCode::Comma,
|
|
||||||
0x2c => VirtualKeyCode::Slash,
|
|
||||||
0x2d => VirtualKeyCode::N,
|
|
||||||
0x2e => VirtualKeyCode::M,
|
|
||||||
0x2f => VirtualKeyCode::Period,
|
|
||||||
0x30 => VirtualKeyCode::Tab,
|
|
||||||
0x31 => VirtualKeyCode::Space,
|
|
||||||
0x32 => VirtualKeyCode::Grave,
|
|
||||||
0x33 => VirtualKeyCode::Back,
|
|
||||||
//0x34 => unkown,
|
|
||||||
0x35 => VirtualKeyCode::Escape,
|
|
||||||
0x36 => VirtualKeyCode::RWin,
|
|
||||||
0x37 => VirtualKeyCode::LWin,
|
|
||||||
0x38 => VirtualKeyCode::LShift,
|
|
||||||
//0x39 => Caps lock,
|
|
||||||
0x3a => VirtualKeyCode::LAlt,
|
|
||||||
0x3b => VirtualKeyCode::LControl,
|
|
||||||
0x3c => VirtualKeyCode::RShift,
|
|
||||||
0x3d => VirtualKeyCode::RAlt,
|
|
||||||
0x3e => VirtualKeyCode::RControl,
|
|
||||||
//0x3f => Fn key,
|
|
||||||
0x40 => VirtualKeyCode::F17,
|
|
||||||
0x41 => VirtualKeyCode::NumpadDecimal,
|
|
||||||
//0x42 -> unkown,
|
|
||||||
0x43 => VirtualKeyCode::NumpadMultiply,
|
|
||||||
//0x44 => unkown,
|
|
||||||
0x45 => VirtualKeyCode::NumpadAdd,
|
|
||||||
//0x46 => unkown,
|
|
||||||
0x47 => VirtualKeyCode::Numlock,
|
|
||||||
//0x48 => KeypadClear,
|
|
||||||
0x49 => VirtualKeyCode::VolumeUp,
|
|
||||||
0x4a => VirtualKeyCode::VolumeDown,
|
|
||||||
0x4b => VirtualKeyCode::NumpadDivide,
|
|
||||||
0x4c => VirtualKeyCode::NumpadEnter,
|
|
||||||
//0x4d => unkown,
|
|
||||||
0x4e => VirtualKeyCode::NumpadSubtract,
|
|
||||||
0x4f => VirtualKeyCode::F18,
|
|
||||||
0x50 => VirtualKeyCode::F19,
|
|
||||||
0x51 => VirtualKeyCode::NumpadEquals,
|
|
||||||
0x52 => VirtualKeyCode::Numpad0,
|
|
||||||
0x53 => VirtualKeyCode::Numpad1,
|
|
||||||
0x54 => VirtualKeyCode::Numpad2,
|
|
||||||
0x55 => VirtualKeyCode::Numpad3,
|
|
||||||
0x56 => VirtualKeyCode::Numpad4,
|
|
||||||
0x57 => VirtualKeyCode::Numpad5,
|
|
||||||
0x58 => VirtualKeyCode::Numpad6,
|
|
||||||
0x59 => VirtualKeyCode::Numpad7,
|
|
||||||
0x5a => VirtualKeyCode::F20,
|
|
||||||
0x5b => VirtualKeyCode::Numpad8,
|
|
||||||
0x5c => VirtualKeyCode::Numpad9,
|
|
||||||
0x5d => VirtualKeyCode::Yen,
|
|
||||||
//0x5e => JIS Ro,
|
|
||||||
//0x5f => unkown,
|
|
||||||
0x60 => VirtualKeyCode::F5,
|
|
||||||
0x61 => VirtualKeyCode::F6,
|
|
||||||
0x62 => VirtualKeyCode::F7,
|
|
||||||
0x63 => VirtualKeyCode::F3,
|
|
||||||
0x64 => VirtualKeyCode::F8,
|
|
||||||
0x65 => VirtualKeyCode::F9,
|
|
||||||
//0x66 => JIS Eisuu (macOS),
|
|
||||||
0x67 => VirtualKeyCode::F11,
|
|
||||||
//0x68 => JIS Kanna (macOS),
|
|
||||||
0x69 => VirtualKeyCode::F13,
|
|
||||||
0x6a => VirtualKeyCode::F16,
|
|
||||||
0x6b => VirtualKeyCode::F14,
|
|
||||||
//0x6c => unkown,
|
|
||||||
0x6d => VirtualKeyCode::F10,
|
|
||||||
//0x6e => unkown,
|
|
||||||
0x6f => VirtualKeyCode::F12,
|
|
||||||
//0x70 => unkown,
|
|
||||||
0x71 => VirtualKeyCode::F15,
|
|
||||||
0x72 => VirtualKeyCode::Insert,
|
|
||||||
0x73 => VirtualKeyCode::Home,
|
|
||||||
0x74 => VirtualKeyCode::PageUp,
|
|
||||||
0x75 => VirtualKeyCode::Delete,
|
|
||||||
0x76 => VirtualKeyCode::F4,
|
|
||||||
0x77 => VirtualKeyCode::End,
|
|
||||||
0x78 => VirtualKeyCode::F2,
|
|
||||||
0x79 => VirtualKeyCode::PageDown,
|
|
||||||
0x7a => VirtualKeyCode::F1,
|
|
||||||
0x7b => VirtualKeyCode::Left,
|
|
||||||
0x7c => VirtualKeyCode::Right,
|
|
||||||
0x7d => VirtualKeyCode::Down,
|
|
||||||
0x7e => VirtualKeyCode::Up,
|
|
||||||
//0x7f => unkown,
|
|
||||||
0xa => VirtualKeyCode::Caret,
|
|
||||||
_ => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// While F1-F20 have scancodes we can match on, we have to check against UTF-16
|
|
||||||
// constants for the rest.
|
|
||||||
// https://developer.apple.com/documentation/appkit/1535851-function-key_unicodes?preferredLanguage=occ
|
|
||||||
pub fn check_function_keys(string: &str) -> Option<VirtualKeyCode> {
|
|
||||||
if let Some(ch) = string.encode_utf16().next() {
|
|
||||||
return Some(match ch {
|
|
||||||
0xf718 => VirtualKeyCode::F21,
|
|
||||||
0xf719 => VirtualKeyCode::F22,
|
|
||||||
0xf71a => VirtualKeyCode::F23,
|
|
||||||
0xf71b => VirtualKeyCode::F24,
|
|
||||||
_ => return None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn event_mods(event: id) -> ModifiersState {
|
|
||||||
let flags = unsafe { NSEvent::modifierFlags(event) };
|
|
||||||
let mut m = ModifiersState::empty();
|
|
||||||
m.set(
|
|
||||||
ModifiersState::SHIFT,
|
|
||||||
flags.contains(NSEventModifierFlags::NSShiftKeyMask),
|
|
||||||
);
|
|
||||||
m.set(
|
|
||||||
ModifiersState::CTRL,
|
|
||||||
flags.contains(NSEventModifierFlags::NSControlKeyMask),
|
|
||||||
);
|
|
||||||
m.set(
|
|
||||||
ModifiersState::ALT,
|
|
||||||
flags.contains(NSEventModifierFlags::NSAlternateKeyMask),
|
|
||||||
);
|
|
||||||
m.set(
|
|
||||||
ModifiersState::LOGO,
|
|
||||||
flags.contains(NSEventModifierFlags::NSCommandKeyMask),
|
|
||||||
);
|
|
||||||
m
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_scancode(event: cocoa::base::id) -> c_ushort {
|
|
||||||
// In AppKit, `keyCode` refers to the position (scancode) of a key rather than its character,
|
|
||||||
// and there is no easy way to navtively retrieve the layout-dependent character.
|
|
||||||
// In winit, we use keycode to refer to the key's character, and so this function aligns
|
|
||||||
// AppKit's terminology with ours.
|
|
||||||
unsafe { msg_send![event, keyCode] }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn modifier_event(
|
|
||||||
ns_event: id,
|
|
||||||
keymask: NSEventModifierFlags,
|
|
||||||
was_key_pressed: bool,
|
|
||||||
) -> Option<WindowEvent<'static>> {
|
|
||||||
if !was_key_pressed && NSEvent::modifierFlags(ns_event).contains(keymask)
|
|
||||||
|| was_key_pressed && !NSEvent::modifierFlags(ns_event).contains(keymask)
|
|
||||||
{
|
|
||||||
let state = if was_key_pressed {
|
|
||||||
ElementState::Released
|
|
||||||
} else {
|
|
||||||
ElementState::Pressed
|
|
||||||
};
|
|
||||||
|
|
||||||
let scancode = get_scancode(ns_event);
|
|
||||||
let virtual_keycode = scancode_to_keycode(scancode);
|
|
||||||
#[allow(deprecated)]
|
|
||||||
Some(WindowEvent::KeyboardInput {
|
|
||||||
device_id: DEVICE_ID,
|
|
||||||
input: KeyboardInput {
|
|
||||||
state,
|
|
||||||
scancode: scancode as _,
|
|
||||||
virtual_keycode,
|
|
||||||
modifiers: event_mods(ns_event),
|
|
||||||
},
|
|
||||||
is_synthetic: false,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,343 +0,0 @@
|
|||||||
use std::{
|
|
||||||
any::Any,
|
|
||||||
cell::{Cell, RefCell},
|
|
||||||
collections::VecDeque,
|
|
||||||
marker::PhantomData,
|
|
||||||
mem,
|
|
||||||
os::raw::c_void,
|
|
||||||
panic::{catch_unwind, resume_unwind, RefUnwindSafe, UnwindSafe},
|
|
||||||
process, ptr,
|
|
||||||
rc::{Rc, Weak},
|
|
||||||
sync::mpsc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use cocoa::{
|
|
||||||
appkit::{NSApp, NSEventModifierFlags, NSEventSubtype, NSEventType::NSApplicationDefined},
|
|
||||||
base::{id, nil, BOOL, NO, YES},
|
|
||||||
foundation::{NSInteger, NSPoint, NSTimeInterval},
|
|
||||||
};
|
|
||||||
use objc::rc::autoreleasepool;
|
|
||||||
use raw_window_handle::{AppKitDisplayHandle, RawDisplayHandle};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
event::Event,
|
|
||||||
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget},
|
|
||||||
monitor::MonitorHandle as RootMonitorHandle,
|
|
||||||
platform::macos::ActivationPolicy,
|
|
||||||
platform_impl::{
|
|
||||||
get_aux_state_mut,
|
|
||||||
platform::{
|
|
||||||
app::APP_CLASS,
|
|
||||||
app_delegate::APP_DELEGATE_CLASS,
|
|
||||||
app_state::{AppState, Callback},
|
|
||||||
monitor::{self, MonitorHandle},
|
|
||||||
observer::*,
|
|
||||||
util::IdRef,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct PanicInfo {
|
|
||||||
inner: Cell<Option<Box<dyn Any + Send + 'static>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// WARNING:
|
|
||||||
// As long as this struct is used through its `impl`, it is UnwindSafe.
|
|
||||||
// (If `get_mut` is called on `inner`, unwind safety may get broken.)
|
|
||||||
impl UnwindSafe for PanicInfo {}
|
|
||||||
impl RefUnwindSafe for PanicInfo {}
|
|
||||||
impl PanicInfo {
|
|
||||||
pub fn is_panicking(&self) -> bool {
|
|
||||||
let inner = self.inner.take();
|
|
||||||
let result = inner.is_some();
|
|
||||||
self.inner.set(inner);
|
|
||||||
result
|
|
||||||
}
|
|
||||||
/// Overwrites the curret state if the current state is not panicking
|
|
||||||
pub fn set_panic(&self, p: Box<dyn Any + Send + 'static>) {
|
|
||||||
if !self.is_panicking() {
|
|
||||||
self.inner.set(Some(p));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn take(&self) -> Option<Box<dyn Any + Send + 'static>> {
|
|
||||||
self.inner.take()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventLoopWindowTarget<T: 'static> {
|
|
||||||
pub sender: mpsc::Sender<T>, // this is only here to be cloned elsewhere
|
|
||||||
pub receiver: mpsc::Receiver<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Default for EventLoopWindowTarget<T> {
|
|
||||||
fn default() -> Self {
|
|
||||||
let (sender, receiver) = mpsc::channel();
|
|
||||||
EventLoopWindowTarget { sender, receiver }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> EventLoopWindowTarget<T> {
|
|
||||||
#[inline]
|
|
||||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
|
||||||
monitor::available_monitors()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn primary_monitor(&self) -> Option<RootMonitorHandle> {
|
|
||||||
let monitor = monitor::primary_monitor();
|
|
||||||
Some(RootMonitorHandle { inner: monitor })
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
|
||||||
RawDisplayHandle::AppKit(AppKitDisplayHandle::empty())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopWindowTarget<T> {
|
|
||||||
pub(crate) fn hide_application(&self) {
|
|
||||||
let cls = objc::runtime::Class::get("NSApplication").unwrap();
|
|
||||||
let app: cocoa::base::id = unsafe { msg_send![cls, sharedApplication] };
|
|
||||||
unsafe { msg_send![app, hide: 0] }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn hide_other_applications(&self) {
|
|
||||||
let cls = objc::runtime::Class::get("NSApplication").unwrap();
|
|
||||||
let app: cocoa::base::id = unsafe { msg_send![cls, sharedApplication] };
|
|
||||||
unsafe { msg_send![app, hideOtherApplications: 0] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventLoop<T: 'static> {
|
|
||||||
/// The delegate is only weakly referenced by NSApplication, so we keep
|
|
||||||
/// it around here as well.
|
|
||||||
_delegate: IdRef,
|
|
||||||
|
|
||||||
window_target: Rc<RootWindowTarget<T>>,
|
|
||||||
panic_info: Rc<PanicInfo>,
|
|
||||||
|
|
||||||
/// We make sure that the callback closure is dropped during a panic
|
|
||||||
/// by making the event loop own it.
|
|
||||||
///
|
|
||||||
/// Every other reference should be a Weak reference which is only upgraded
|
|
||||||
/// into a strong reference in order to call the callback but then the
|
|
||||||
/// strong reference should be dropped as soon as possible.
|
|
||||||
_callback: Option<Rc<Callback<T>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub(crate) struct PlatformSpecificEventLoopAttributes {
|
|
||||||
pub(crate) activation_policy: ActivationPolicy,
|
|
||||||
pub(crate) default_menu: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for PlatformSpecificEventLoopAttributes {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
activation_policy: Default::default(), // Regular
|
|
||||||
default_menu: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoop<T> {
|
|
||||||
pub(crate) fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Self {
|
|
||||||
let delegate = unsafe {
|
|
||||||
let is_main_thread: BOOL = msg_send!(class!(NSThread), isMainThread);
|
|
||||||
if is_main_thread == NO {
|
|
||||||
panic!("On macOS, `EventLoop` must be created on the main thread!");
|
|
||||||
}
|
|
||||||
|
|
||||||
// This must be done before `NSApp()` (equivalent to sending
|
|
||||||
// `sharedApplication`) is called anywhere else, or we'll end up
|
|
||||||
// with the wrong `NSApplication` class and the wrong thread could
|
|
||||||
// be marked as main.
|
|
||||||
let app: id = msg_send![APP_CLASS.0, sharedApplication];
|
|
||||||
|
|
||||||
let delegate = IdRef::new(msg_send![APP_DELEGATE_CLASS.0, new]);
|
|
||||||
|
|
||||||
let mut aux_state = get_aux_state_mut(&**delegate);
|
|
||||||
aux_state.activation_policy = attributes.activation_policy;
|
|
||||||
aux_state.default_menu = attributes.default_menu;
|
|
||||||
|
|
||||||
autoreleasepool(|| {
|
|
||||||
let _: () = msg_send![app, setDelegate:*delegate];
|
|
||||||
});
|
|
||||||
|
|
||||||
delegate
|
|
||||||
};
|
|
||||||
let panic_info: Rc<PanicInfo> = Default::default();
|
|
||||||
setup_control_flow_observers(Rc::downgrade(&panic_info));
|
|
||||||
EventLoop {
|
|
||||||
_delegate: delegate,
|
|
||||||
window_target: Rc::new(RootWindowTarget {
|
|
||||||
p: Default::default(),
|
|
||||||
_marker: PhantomData,
|
|
||||||
}),
|
|
||||||
panic_info,
|
|
||||||
_callback: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn window_target(&self) -> &RootWindowTarget<T> {
|
|
||||||
&self.window_target
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run<F>(mut self, callback: F) -> !
|
|
||||||
where
|
|
||||||
F: 'static + FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow),
|
|
||||||
{
|
|
||||||
let exit_code = self.run_return(callback);
|
|
||||||
process::exit(exit_code);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_return<F>(&mut self, callback: F) -> i32
|
|
||||||
where
|
|
||||||
F: FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow),
|
|
||||||
{
|
|
||||||
// This transmute is always safe, in case it was reached through `run`, since our
|
|
||||||
// lifetime will be already 'static. In other cases caller should ensure that all data
|
|
||||||
// they passed to callback will actually outlive it, some apps just can't move
|
|
||||||
// everything to event loop, so this is something that they should care about.
|
|
||||||
let callback = unsafe {
|
|
||||||
mem::transmute::<
|
|
||||||
Rc<RefCell<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>>,
|
|
||||||
Rc<RefCell<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>>,
|
|
||||||
>(Rc::new(RefCell::new(callback)))
|
|
||||||
};
|
|
||||||
|
|
||||||
self._callback = Some(Rc::clone(&callback));
|
|
||||||
|
|
||||||
let exit_code = autoreleasepool(|| unsafe {
|
|
||||||
let app = NSApp();
|
|
||||||
assert_ne!(app, nil);
|
|
||||||
|
|
||||||
// A bit of juggling with the callback references to make sure
|
|
||||||
// that `self.callback` is the only owner of the callback.
|
|
||||||
let weak_cb: Weak<_> = Rc::downgrade(&callback);
|
|
||||||
drop(callback);
|
|
||||||
|
|
||||||
AppState::set_callback(weak_cb, Rc::clone(&self.window_target));
|
|
||||||
let _: () = msg_send![app, run];
|
|
||||||
|
|
||||||
if let Some(panic) = self.panic_info.take() {
|
|
||||||
drop(self._callback.take());
|
|
||||||
resume_unwind(panic);
|
|
||||||
}
|
|
||||||
AppState::exit()
|
|
||||||
});
|
|
||||||
drop(self._callback.take());
|
|
||||||
|
|
||||||
exit_code
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
|
||||||
EventLoopProxy::new(self.window_target.p.sender.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub unsafe fn post_dummy_event(target: id) {
|
|
||||||
let event_class = class!(NSEvent);
|
|
||||||
let dummy_event: id = msg_send![
|
|
||||||
event_class,
|
|
||||||
otherEventWithType: NSApplicationDefined
|
|
||||||
location: NSPoint::new(0.0, 0.0)
|
|
||||||
modifierFlags: NSEventModifierFlags::empty()
|
|
||||||
timestamp: 0 as NSTimeInterval
|
|
||||||
windowNumber: 0 as NSInteger
|
|
||||||
context: nil
|
|
||||||
subtype: NSEventSubtype::NSWindowExposedEventType
|
|
||||||
data1: 0 as NSInteger
|
|
||||||
data2: 0 as NSInteger
|
|
||||||
];
|
|
||||||
let _: () = msg_send![target, postEvent: dummy_event atStart: YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Catches panics that happen inside `f` and when a panic
|
|
||||||
/// happens, stops the `sharedApplication`
|
|
||||||
#[inline]
|
|
||||||
pub fn stop_app_on_panic<F: FnOnce() -> R + UnwindSafe, R>(
|
|
||||||
panic_info: Weak<PanicInfo>,
|
|
||||||
f: F,
|
|
||||||
) -> Option<R> {
|
|
||||||
match catch_unwind(f) {
|
|
||||||
Ok(r) => Some(r),
|
|
||||||
Err(e) => {
|
|
||||||
// It's important that we set the panic before requesting a `stop`
|
|
||||||
// because some callback are still called during the `stop` message
|
|
||||||
// and we need to know in those callbacks if the application is currently
|
|
||||||
// panicking
|
|
||||||
{
|
|
||||||
let panic_info = panic_info.upgrade().unwrap();
|
|
||||||
panic_info.set_panic(e);
|
|
||||||
}
|
|
||||||
unsafe {
|
|
||||||
let app_class = class!(NSApplication);
|
|
||||||
let app: id = msg_send![app_class, sharedApplication];
|
|
||||||
let _: () = msg_send![app, stop: nil];
|
|
||||||
|
|
||||||
// Posting a dummy event to get `stop` to take effect immediately.
|
|
||||||
// See: https://stackoverflow.com/questions/48041279/stopping-the-nsapplication-main-event-loop/48064752#48064752
|
|
||||||
post_dummy_event(app);
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventLoopProxy<T> {
|
|
||||||
sender: mpsc::Sender<T>,
|
|
||||||
source: CFRunLoopSourceRef,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl<T: Send> Send for EventLoopProxy<T> {}
|
|
||||||
|
|
||||||
impl<T> Drop for EventLoopProxy<T> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
CFRelease(self.source as _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Clone for EventLoopProxy<T> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
EventLoopProxy::new(self.sender.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopProxy<T> {
|
|
||||||
fn new(sender: mpsc::Sender<T>) -> Self {
|
|
||||||
unsafe {
|
|
||||||
// just wake up the eventloop
|
|
||||||
extern "C" fn event_loop_proxy_handler(_: *mut c_void) {}
|
|
||||||
|
|
||||||
// adding a Source to the main CFRunLoop lets us wake it up and
|
|
||||||
// process user events through the normal OS EventLoop mechanisms.
|
|
||||||
let rl = CFRunLoopGetMain();
|
|
||||||
let mut context: CFRunLoopSourceContext = mem::zeroed();
|
|
||||||
context.perform = Some(event_loop_proxy_handler);
|
|
||||||
let source =
|
|
||||||
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context);
|
|
||||||
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
|
|
||||||
CFRunLoopWakeUp(rl);
|
|
||||||
|
|
||||||
EventLoopProxy { sender, source }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
|
||||||
self.sender
|
|
||||||
.send(event)
|
|
||||||
.map_err(|mpsc::SendError(x)| EventLoopClosed(x))?;
|
|
||||||
unsafe {
|
|
||||||
// let the main thread know there's a new event
|
|
||||||
CFRunLoopSourceSignal(self.source);
|
|
||||||
let rl = CFRunLoopGetMain();
|
|
||||||
CFRunLoopWakeUp(rl);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
use super::util::IdRef;
|
|
||||||
use cocoa::appkit::{NSApp, NSApplication, NSEventModifierFlags, NSMenu, NSMenuItem};
|
|
||||||
use cocoa::base::{nil, selector};
|
|
||||||
use cocoa::foundation::{NSProcessInfo, NSString};
|
|
||||||
use objc::{
|
|
||||||
rc::autoreleasepool,
|
|
||||||
runtime::{Object, Sel},
|
|
||||||
};
|
|
||||||
|
|
||||||
struct KeyEquivalent<'a> {
|
|
||||||
key: &'a str,
|
|
||||||
masks: Option<NSEventModifierFlags>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn initialize() {
|
|
||||||
autoreleasepool(|| unsafe {
|
|
||||||
let menubar = IdRef::new(NSMenu::new(nil));
|
|
||||||
let app_menu_item = IdRef::new(NSMenuItem::new(nil));
|
|
||||||
menubar.addItem_(*app_menu_item);
|
|
||||||
let app = NSApp();
|
|
||||||
app.setMainMenu_(*menubar);
|
|
||||||
|
|
||||||
let app_menu = NSMenu::new(nil);
|
|
||||||
let process_name = NSProcessInfo::processInfo(nil).processName();
|
|
||||||
|
|
||||||
// About menu item
|
|
||||||
let about_item_prefix = NSString::alloc(nil).init_str("About ");
|
|
||||||
let about_item_title = about_item_prefix.stringByAppendingString_(process_name);
|
|
||||||
let about_item = menu_item(
|
|
||||||
about_item_title,
|
|
||||||
selector("orderFrontStandardAboutPanel:"),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Seperator menu item
|
|
||||||
let sep_first = NSMenuItem::separatorItem(nil);
|
|
||||||
|
|
||||||
// Hide application menu item
|
|
||||||
let hide_item_prefix = NSString::alloc(nil).init_str("Hide ");
|
|
||||||
let hide_item_title = hide_item_prefix.stringByAppendingString_(process_name);
|
|
||||||
let hide_item = menu_item(
|
|
||||||
hide_item_title,
|
|
||||||
selector("hide:"),
|
|
||||||
Some(KeyEquivalent {
|
|
||||||
key: "h",
|
|
||||||
masks: None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Hide other applications menu item
|
|
||||||
let hide_others_item_title = NSString::alloc(nil).init_str("Hide Others");
|
|
||||||
let hide_others_item = menu_item(
|
|
||||||
hide_others_item_title,
|
|
||||||
selector("hideOtherApplications:"),
|
|
||||||
Some(KeyEquivalent {
|
|
||||||
key: "h",
|
|
||||||
masks: Some(
|
|
||||||
NSEventModifierFlags::NSAlternateKeyMask
|
|
||||||
| NSEventModifierFlags::NSCommandKeyMask,
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Show applications menu item
|
|
||||||
let show_all_item_title = NSString::alloc(nil).init_str("Show All");
|
|
||||||
let show_all_item = menu_item(
|
|
||||||
show_all_item_title,
|
|
||||||
selector("unhideAllApplications:"),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Seperator menu item
|
|
||||||
let sep = NSMenuItem::separatorItem(nil);
|
|
||||||
|
|
||||||
// Quit application menu item
|
|
||||||
let quit_item_prefix = NSString::alloc(nil).init_str("Quit ");
|
|
||||||
let quit_item_title = quit_item_prefix.stringByAppendingString_(process_name);
|
|
||||||
let quit_item = menu_item(
|
|
||||||
quit_item_title,
|
|
||||||
selector("terminate:"),
|
|
||||||
Some(KeyEquivalent {
|
|
||||||
key: "q",
|
|
||||||
masks: None,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
app_menu.addItem_(about_item);
|
|
||||||
app_menu.addItem_(sep_first);
|
|
||||||
app_menu.addItem_(hide_item);
|
|
||||||
app_menu.addItem_(hide_others_item);
|
|
||||||
app_menu.addItem_(show_all_item);
|
|
||||||
app_menu.addItem_(sep);
|
|
||||||
app_menu.addItem_(quit_item);
|
|
||||||
app_menu_item.setSubmenu_(app_menu);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn menu_item(
|
|
||||||
title: *mut Object,
|
|
||||||
selector: Sel,
|
|
||||||
key_equivalent: Option<KeyEquivalent<'_>>,
|
|
||||||
) -> *mut Object {
|
|
||||||
unsafe {
|
|
||||||
let (key, masks) = match key_equivalent {
|
|
||||||
Some(ke) => (NSString::alloc(nil).init_str(ke.key), ke.masks),
|
|
||||||
None => (NSString::alloc(nil).init_str(""), None),
|
|
||||||
};
|
|
||||||
let item = NSMenuItem::alloc(nil).initWithTitle_action_keyEquivalent_(title, selector, key);
|
|
||||||
if let Some(masks) = masks {
|
|
||||||
item.setKeyEquivalentModifierMask_(masks)
|
|
||||||
}
|
|
||||||
|
|
||||||
item
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
#![cfg(target_os = "macos")]
|
|
||||||
#![allow(clippy::let_unit_value)]
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
mod util;
|
|
||||||
|
|
||||||
mod app;
|
|
||||||
mod app_delegate;
|
|
||||||
mod app_state;
|
|
||||||
mod event;
|
|
||||||
mod event_loop;
|
|
||||||
mod ffi;
|
|
||||||
mod menu;
|
|
||||||
mod monitor;
|
|
||||||
mod observer;
|
|
||||||
mod view;
|
|
||||||
mod window;
|
|
||||||
mod window_delegate;
|
|
||||||
|
|
||||||
use std::{fmt, ops::Deref, sync::Arc};
|
|
||||||
|
|
||||||
pub(crate) use self::{
|
|
||||||
app_delegate::get_aux_state_mut,
|
|
||||||
event_loop::{
|
|
||||||
EventLoop, EventLoopProxy, EventLoopWindowTarget, PlatformSpecificEventLoopAttributes,
|
|
||||||
},
|
|
||||||
monitor::{MonitorHandle, VideoMode},
|
|
||||||
window::{PlatformSpecificWindowBuilderAttributes, UnownedWindow, WindowId},
|
|
||||||
};
|
|
||||||
use crate::{
|
|
||||||
error::OsError as RootOsError, event::DeviceId as RootDeviceId, window::WindowAttributes,
|
|
||||||
};
|
|
||||||
use objc::rc::autoreleasepool;
|
|
||||||
|
|
||||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct DeviceId;
|
|
||||||
|
|
||||||
impl DeviceId {
|
|
||||||
pub const unsafe fn dummy() -> Self {
|
|
||||||
DeviceId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constant device ID; to be removed when if backend is updated to report real device IDs.
|
|
||||||
pub(crate) const DEVICE_ID: RootDeviceId = RootDeviceId(DeviceId);
|
|
||||||
|
|
||||||
pub struct Window {
|
|
||||||
window: Arc<UnownedWindow>,
|
|
||||||
// We keep this around so that it doesn't get dropped until the window does.
|
|
||||||
_delegate: util::IdRef,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum OsError {
|
|
||||||
CGError(core_graphics::base::CGError),
|
|
||||||
CreationError(&'static str),
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for Window {}
|
|
||||||
unsafe impl Sync for Window {}
|
|
||||||
|
|
||||||
impl Deref for Window {
|
|
||||||
type Target = UnownedWindow;
|
|
||||||
#[inline]
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&*self.window
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Window {
|
|
||||||
pub(crate) fn new<T: 'static>(
|
|
||||||
_window_target: &EventLoopWindowTarget<T>,
|
|
||||||
attributes: WindowAttributes,
|
|
||||||
pl_attribs: PlatformSpecificWindowBuilderAttributes,
|
|
||||||
) -> Result<Self, RootOsError> {
|
|
||||||
let (window, _delegate) = autoreleasepool(|| UnownedWindow::new(attributes, pl_attribs))?;
|
|
||||||
Ok(Window { window, _delegate })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for OsError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
OsError::CGError(e) => f.pad(&format!("CGError {}", e)),
|
|
||||||
OsError::CreationError(e) => f.pad(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,303 +0,0 @@
|
|||||||
use std::{
|
|
||||||
self,
|
|
||||||
os::raw::*,
|
|
||||||
panic::{AssertUnwindSafe, UnwindSafe},
|
|
||||||
ptr,
|
|
||||||
rc::Weak,
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::platform_impl::platform::{
|
|
||||||
app_state::AppState,
|
|
||||||
event_loop::{stop_app_on_panic, PanicInfo},
|
|
||||||
ffi,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[link(name = "CoreFoundation", kind = "framework")]
|
|
||||||
extern "C" {
|
|
||||||
pub static kCFRunLoopCommonModes: CFRunLoopMode;
|
|
||||||
|
|
||||||
pub fn CFRunLoopGetMain() -> CFRunLoopRef;
|
|
||||||
pub fn CFRunLoopWakeUp(rl: CFRunLoopRef);
|
|
||||||
|
|
||||||
pub fn CFRunLoopObserverCreate(
|
|
||||||
allocator: CFAllocatorRef,
|
|
||||||
activities: CFOptionFlags,
|
|
||||||
repeats: ffi::Boolean,
|
|
||||||
order: CFIndex,
|
|
||||||
callout: CFRunLoopObserverCallBack,
|
|
||||||
context: *mut CFRunLoopObserverContext,
|
|
||||||
) -> CFRunLoopObserverRef;
|
|
||||||
pub fn CFRunLoopAddObserver(
|
|
||||||
rl: CFRunLoopRef,
|
|
||||||
observer: CFRunLoopObserverRef,
|
|
||||||
mode: CFRunLoopMode,
|
|
||||||
);
|
|
||||||
|
|
||||||
pub fn CFRunLoopTimerCreate(
|
|
||||||
allocator: CFAllocatorRef,
|
|
||||||
fireDate: CFAbsoluteTime,
|
|
||||||
interval: CFTimeInterval,
|
|
||||||
flags: CFOptionFlags,
|
|
||||||
order: CFIndex,
|
|
||||||
callout: CFRunLoopTimerCallBack,
|
|
||||||
context: *mut CFRunLoopTimerContext,
|
|
||||||
) -> CFRunLoopTimerRef;
|
|
||||||
pub fn CFRunLoopAddTimer(rl: CFRunLoopRef, timer: CFRunLoopTimerRef, mode: CFRunLoopMode);
|
|
||||||
pub fn CFRunLoopTimerSetNextFireDate(timer: CFRunLoopTimerRef, fireDate: CFAbsoluteTime);
|
|
||||||
pub fn CFRunLoopTimerInvalidate(time: CFRunLoopTimerRef);
|
|
||||||
|
|
||||||
pub fn CFRunLoopSourceCreate(
|
|
||||||
allocator: CFAllocatorRef,
|
|
||||||
order: CFIndex,
|
|
||||||
context: *mut CFRunLoopSourceContext,
|
|
||||||
) -> CFRunLoopSourceRef;
|
|
||||||
pub fn CFRunLoopAddSource(rl: CFRunLoopRef, source: CFRunLoopSourceRef, mode: CFRunLoopMode);
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn CFRunLoopSourceInvalidate(source: CFRunLoopSourceRef);
|
|
||||||
pub fn CFRunLoopSourceSignal(source: CFRunLoopSourceRef);
|
|
||||||
|
|
||||||
pub fn CFAbsoluteTimeGetCurrent() -> CFAbsoluteTime;
|
|
||||||
pub fn CFRelease(cftype: *const c_void);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum CFAllocator {}
|
|
||||||
pub type CFAllocatorRef = *mut CFAllocator;
|
|
||||||
pub enum CFRunLoop {}
|
|
||||||
pub type CFRunLoopRef = *mut CFRunLoop;
|
|
||||||
pub type CFRunLoopMode = CFStringRef;
|
|
||||||
pub enum CFRunLoopObserver {}
|
|
||||||
pub type CFRunLoopObserverRef = *mut CFRunLoopObserver;
|
|
||||||
pub enum CFRunLoopTimer {}
|
|
||||||
pub type CFRunLoopTimerRef = *mut CFRunLoopTimer;
|
|
||||||
pub enum CFRunLoopSource {}
|
|
||||||
pub type CFRunLoopSourceRef = *mut CFRunLoopSource;
|
|
||||||
pub enum CFString {}
|
|
||||||
pub type CFStringRef = *const CFString;
|
|
||||||
|
|
||||||
pub type CFHashCode = c_ulong;
|
|
||||||
pub type CFIndex = c_long;
|
|
||||||
pub type CFOptionFlags = c_ulong;
|
|
||||||
pub type CFRunLoopActivity = CFOptionFlags;
|
|
||||||
|
|
||||||
pub type CFAbsoluteTime = CFTimeInterval;
|
|
||||||
pub type CFTimeInterval = f64;
|
|
||||||
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
pub const kCFRunLoopEntry: CFRunLoopActivity = 0;
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
pub const kCFRunLoopBeforeWaiting: CFRunLoopActivity = 1 << 5;
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
pub const kCFRunLoopAfterWaiting: CFRunLoopActivity = 1 << 6;
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
pub const kCFRunLoopExit: CFRunLoopActivity = 1 << 7;
|
|
||||||
|
|
||||||
pub type CFRunLoopObserverCallBack =
|
|
||||||
extern "C" fn(observer: CFRunLoopObserverRef, activity: CFRunLoopActivity, info: *mut c_void);
|
|
||||||
pub type CFRunLoopTimerCallBack = extern "C" fn(timer: CFRunLoopTimerRef, info: *mut c_void);
|
|
||||||
|
|
||||||
pub enum CFRunLoopTimerContext {}
|
|
||||||
|
|
||||||
/// This mirrors the struct with the same name from Core Foundation.
|
|
||||||
///
|
|
||||||
/// <https://developer.apple.com/documentation/corefoundation/cfrunloopobservercontext?language=objc>
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct CFRunLoopObserverContext {
|
|
||||||
pub version: CFIndex,
|
|
||||||
pub info: *mut c_void,
|
|
||||||
pub retain: Option<extern "C" fn(info: *const c_void) -> *const c_void>,
|
|
||||||
pub release: Option<extern "C" fn(info: *const c_void)>,
|
|
||||||
pub copyDescription: Option<extern "C" fn(info: *const c_void) -> CFStringRef>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct CFRunLoopSourceContext {
|
|
||||||
pub version: CFIndex,
|
|
||||||
pub info: *mut c_void,
|
|
||||||
pub retain: Option<extern "C" fn(*const c_void) -> *const c_void>,
|
|
||||||
pub release: Option<extern "C" fn(*const c_void)>,
|
|
||||||
pub copyDescription: Option<extern "C" fn(*const c_void) -> CFStringRef>,
|
|
||||||
pub equal: Option<extern "C" fn(*const c_void, *const c_void) -> ffi::Boolean>,
|
|
||||||
pub hash: Option<extern "C" fn(*const c_void) -> CFHashCode>,
|
|
||||||
pub schedule: Option<extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode)>,
|
|
||||||
pub cancel: Option<extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode)>,
|
|
||||||
pub perform: Option<extern "C" fn(*mut c_void)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn control_flow_handler<F>(panic_info: *mut c_void, f: F)
|
|
||||||
where
|
|
||||||
F: FnOnce(Weak<PanicInfo>) + UnwindSafe,
|
|
||||||
{
|
|
||||||
let info_from_raw = Weak::from_raw(panic_info as *mut PanicInfo);
|
|
||||||
// Asserting unwind safety on this type should be fine because `PanicInfo` is
|
|
||||||
// `RefUnwindSafe` and `Rc<T>` is `UnwindSafe` if `T` is `RefUnwindSafe`.
|
|
||||||
let panic_info = AssertUnwindSafe(Weak::clone(&info_from_raw));
|
|
||||||
// `from_raw` takes ownership of the data behind the pointer.
|
|
||||||
// But if this scope takes ownership of the weak pointer, then
|
|
||||||
// the weak pointer will get free'd at the end of the scope.
|
|
||||||
// However we want to keep that weak reference around after the function.
|
|
||||||
std::mem::forget(info_from_raw);
|
|
||||||
|
|
||||||
stop_app_on_panic(Weak::clone(&panic_info), move || {
|
|
||||||
let _ = &panic_info;
|
|
||||||
f(panic_info.0)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// begin is queued with the highest priority to ensure it is processed before other observers
|
|
||||||
extern "C" fn control_flow_begin_handler(
|
|
||||||
_: CFRunLoopObserverRef,
|
|
||||||
activity: CFRunLoopActivity,
|
|
||||||
panic_info: *mut c_void,
|
|
||||||
) {
|
|
||||||
unsafe {
|
|
||||||
control_flow_handler(panic_info, |panic_info| {
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
match activity {
|
|
||||||
kCFRunLoopAfterWaiting => {
|
|
||||||
//trace!("Triggered `CFRunLoopAfterWaiting`");
|
|
||||||
AppState::wakeup(panic_info);
|
|
||||||
//trace!("Completed `CFRunLoopAfterWaiting`");
|
|
||||||
}
|
|
||||||
kCFRunLoopEntry => unimplemented!(), // not expected to ever happen
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// end is queued with the lowest priority to ensure it is processed after other observers
|
|
||||||
// without that, LoopDestroyed would get sent after MainEventsCleared
|
|
||||||
extern "C" fn control_flow_end_handler(
|
|
||||||
_: CFRunLoopObserverRef,
|
|
||||||
activity: CFRunLoopActivity,
|
|
||||||
panic_info: *mut c_void,
|
|
||||||
) {
|
|
||||||
unsafe {
|
|
||||||
control_flow_handler(panic_info, |panic_info| {
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
match activity {
|
|
||||||
kCFRunLoopBeforeWaiting => {
|
|
||||||
//trace!("Triggered `CFRunLoopBeforeWaiting`");
|
|
||||||
AppState::cleared(panic_info);
|
|
||||||
//trace!("Completed `CFRunLoopBeforeWaiting`");
|
|
||||||
}
|
|
||||||
kCFRunLoopExit => (), //unimplemented!(), // not expected to ever happen
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RunLoop(CFRunLoopRef);
|
|
||||||
|
|
||||||
impl RunLoop {
|
|
||||||
unsafe fn get() -> Self {
|
|
||||||
RunLoop(CFRunLoopGetMain())
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn add_observer(
|
|
||||||
&self,
|
|
||||||
flags: CFOptionFlags,
|
|
||||||
priority: CFIndex,
|
|
||||||
handler: CFRunLoopObserverCallBack,
|
|
||||||
context: *mut CFRunLoopObserverContext,
|
|
||||||
) {
|
|
||||||
let observer = CFRunLoopObserverCreate(
|
|
||||||
ptr::null_mut(),
|
|
||||||
flags,
|
|
||||||
ffi::TRUE, // Indicates we want this to run repeatedly
|
|
||||||
priority, // The lower the value, the sooner this will run
|
|
||||||
handler,
|
|
||||||
context,
|
|
||||||
);
|
|
||||||
CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setup_control_flow_observers(panic_info: Weak<PanicInfo>) {
|
|
||||||
unsafe {
|
|
||||||
let mut context = CFRunLoopObserverContext {
|
|
||||||
info: Weak::into_raw(panic_info) as *mut _,
|
|
||||||
version: 0,
|
|
||||||
retain: None,
|
|
||||||
release: None,
|
|
||||||
copyDescription: None,
|
|
||||||
};
|
|
||||||
let run_loop = RunLoop::get();
|
|
||||||
run_loop.add_observer(
|
|
||||||
kCFRunLoopEntry | kCFRunLoopAfterWaiting,
|
|
||||||
CFIndex::min_value(),
|
|
||||||
control_flow_begin_handler,
|
|
||||||
&mut context as *mut _,
|
|
||||||
);
|
|
||||||
run_loop.add_observer(
|
|
||||||
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
|
|
||||||
CFIndex::max_value(),
|
|
||||||
control_flow_end_handler,
|
|
||||||
&mut context as *mut _,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventLoopWaker {
|
|
||||||
timer: CFRunLoopTimerRef,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for EventLoopWaker {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
CFRunLoopTimerInvalidate(self.timer);
|
|
||||||
CFRelease(self.timer as _);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for EventLoopWaker {
|
|
||||||
fn default() -> EventLoopWaker {
|
|
||||||
extern "C" fn wakeup_main_loop(_timer: CFRunLoopTimerRef, _info: *mut c_void) {}
|
|
||||||
unsafe {
|
|
||||||
// Create a timer with a 0.1µs interval (1ns does not work) to mimic polling.
|
|
||||||
// It is initially setup with a first fire time really far into the
|
|
||||||
// future, but that gets changed to fire immediately in did_finish_launching
|
|
||||||
let timer = CFRunLoopTimerCreate(
|
|
||||||
ptr::null_mut(),
|
|
||||||
std::f64::MAX,
|
|
||||||
0.000_000_1,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
wakeup_main_loop,
|
|
||||||
ptr::null_mut(),
|
|
||||||
);
|
|
||||||
CFRunLoopAddTimer(CFRunLoopGetMain(), timer, kCFRunLoopCommonModes);
|
|
||||||
EventLoopWaker { timer }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventLoopWaker {
|
|
||||||
pub fn stop(&mut self) {
|
|
||||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MAX) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start(&mut self) {
|
|
||||||
unsafe { CFRunLoopTimerSetNextFireDate(self.timer, std::f64::MIN) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start_at(&mut self, instant: Instant) {
|
|
||||||
let now = Instant::now();
|
|
||||||
if now >= instant {
|
|
||||||
self.start();
|
|
||||||
} else {
|
|
||||||
unsafe {
|
|
||||||
let current = CFAbsoluteTimeGetCurrent();
|
|
||||||
let duration = instant - now;
|
|
||||||
let fsecs =
|
|
||||||
duration.subsec_nanos() as f64 / 1_000_000_000.0 + duration.as_secs() as f64;
|
|
||||||
CFRunLoopTimerSetNextFireDate(self.timer, current + fsecs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,236 +0,0 @@
|
|||||||
use std::{
|
|
||||||
ops::Deref,
|
|
||||||
sync::{Mutex, Weak},
|
|
||||||
};
|
|
||||||
|
|
||||||
use cocoa::{
|
|
||||||
appkit::{CGFloat, NSScreen, NSWindow, NSWindowStyleMask},
|
|
||||||
base::{id, nil},
|
|
||||||
foundation::{NSPoint, NSSize, NSString},
|
|
||||||
};
|
|
||||||
use dispatch::Queue;
|
|
||||||
use objc::rc::autoreleasepool;
|
|
||||||
use objc::runtime::{BOOL, NO, YES};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
dpi::LogicalSize,
|
|
||||||
platform_impl::platform::{
|
|
||||||
ffi,
|
|
||||||
util::IdRef,
|
|
||||||
window::{SharedState, SharedStateMutexGuard},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Unsafe wrapper type that allows us to dispatch things that aren't Send.
|
|
||||||
// This should *only* be used to dispatch to the main queue.
|
|
||||||
// While it is indeed not guaranteed that these types can safely be sent to
|
|
||||||
// other threads, we know that they're safe to use on the main thread.
|
|
||||||
struct MainThreadSafe<T>(T);
|
|
||||||
|
|
||||||
unsafe impl<T> Send for MainThreadSafe<T> {}
|
|
||||||
|
|
||||||
impl<T> Deref for MainThreadSafe<T> {
|
|
||||||
type Target = T;
|
|
||||||
fn deref(&self) -> &T {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn set_style_mask(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
|
|
||||||
ns_window.setStyleMask_(mask);
|
|
||||||
// If we don't do this, key handling will break
|
|
||||||
// (at least until the window is clicked again/etc.)
|
|
||||||
ns_window.makeFirstResponder_(ns_view);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always use this function instead of trying to modify `styleMask` directly!
|
|
||||||
// `setStyleMask:` isn't thread-safe, so we have to use Grand Central Dispatch.
|
|
||||||
// Otherwise, this would vomit out errors about not being on the main thread
|
|
||||||
// and fail to do anything.
|
|
||||||
pub unsafe fn set_style_mask_async(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
|
|
||||||
let ns_window = MainThreadSafe(ns_window);
|
|
||||||
let ns_view = MainThreadSafe(ns_view);
|
|
||||||
Queue::main().exec_async(move || {
|
|
||||||
set_style_mask(*ns_window, *ns_view, mask);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
pub unsafe fn set_style_mask_sync(ns_window: id, ns_view: id, mask: NSWindowStyleMask) {
|
|
||||||
let is_main_thread: BOOL = msg_send!(class!(NSThread), isMainThread);
|
|
||||||
if is_main_thread != NO {
|
|
||||||
set_style_mask(ns_window, ns_view, mask);
|
|
||||||
} else {
|
|
||||||
let ns_window = MainThreadSafe(ns_window);
|
|
||||||
let ns_view = MainThreadSafe(ns_view);
|
|
||||||
Queue::main().exec_sync(move || {
|
|
||||||
set_style_mask(*ns_window, *ns_view, mask);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// `setContentSize:` isn't thread-safe either, though it doesn't log any errors
|
|
||||||
// and just fails silently. Anyway, GCD to the rescue!
|
|
||||||
pub unsafe fn set_content_size_async(ns_window: id, size: LogicalSize<f64>) {
|
|
||||||
let ns_window = MainThreadSafe(ns_window);
|
|
||||||
Queue::main().exec_async(move || {
|
|
||||||
ns_window.setContentSize_(NSSize::new(size.width as CGFloat, size.height as CGFloat));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// `setFrameTopLeftPoint:` isn't thread-safe, but fortunately has the courtesy
|
|
||||||
// to log errors.
|
|
||||||
pub unsafe fn set_frame_top_left_point_async(ns_window: id, point: NSPoint) {
|
|
||||||
let ns_window = MainThreadSafe(ns_window);
|
|
||||||
Queue::main().exec_async(move || {
|
|
||||||
ns_window.setFrameTopLeftPoint_(point);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// `setFrameTopLeftPoint:` isn't thread-safe, and fails silently.
|
|
||||||
pub unsafe fn set_level_async(ns_window: id, level: ffi::NSWindowLevel) {
|
|
||||||
let ns_window = MainThreadSafe(ns_window);
|
|
||||||
Queue::main().exec_async(move || {
|
|
||||||
ns_window.setLevel_(level as _);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// `setIgnoresMouseEvents_:` isn't thread-safe, and fails silently.
|
|
||||||
pub unsafe fn set_ignore_mouse_events(ns_window: id, ignore: bool) {
|
|
||||||
let ns_window = MainThreadSafe(ns_window);
|
|
||||||
Queue::main().exec_async(move || {
|
|
||||||
ns_window.setIgnoresMouseEvents_(if ignore { YES } else { NO });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// `toggleFullScreen` is thread-safe, but our additional logic to account for
|
|
||||||
// window styles isn't.
|
|
||||||
pub unsafe fn toggle_full_screen_async(
|
|
||||||
ns_window: id,
|
|
||||||
ns_view: id,
|
|
||||||
not_fullscreen: bool,
|
|
||||||
shared_state: Weak<Mutex<SharedState>>,
|
|
||||||
) {
|
|
||||||
let ns_window = MainThreadSafe(ns_window);
|
|
||||||
let ns_view = MainThreadSafe(ns_view);
|
|
||||||
let shared_state = MainThreadSafe(shared_state);
|
|
||||||
Queue::main().exec_async(move || {
|
|
||||||
// `toggleFullScreen` doesn't work if the `StyleMask` is none, so we
|
|
||||||
// set a normal style temporarily. The previous state will be
|
|
||||||
// restored in `WindowDelegate::window_did_exit_fullscreen`.
|
|
||||||
if not_fullscreen {
|
|
||||||
let curr_mask = ns_window.styleMask();
|
|
||||||
let required =
|
|
||||||
NSWindowStyleMask::NSTitledWindowMask | NSWindowStyleMask::NSResizableWindowMask;
|
|
||||||
if !curr_mask.contains(required) {
|
|
||||||
set_style_mask(*ns_window, *ns_view, required);
|
|
||||||
if let Some(shared_state) = shared_state.upgrade() {
|
|
||||||
let mut shared_state_lock = SharedStateMutexGuard::new(
|
|
||||||
shared_state.lock().unwrap(),
|
|
||||||
"toggle_full_screen_callback",
|
|
||||||
);
|
|
||||||
(*shared_state_lock).saved_style = Some(curr_mask);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Window level must be restored from `CGShieldingWindowLevel()
|
|
||||||
// + 1` back to normal in order for `toggleFullScreen` to do
|
|
||||||
// anything
|
|
||||||
ns_window.setLevel_(0);
|
|
||||||
ns_window.toggleFullScreen_(nil);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn restore_display_mode_async(ns_screen: u32) {
|
|
||||||
Queue::main().exec_async(move || {
|
|
||||||
ffi::CGRestorePermanentDisplayConfiguration();
|
|
||||||
assert_eq!(ffi::CGDisplayRelease(ns_screen), ffi::kCGErrorSuccess);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// `setMaximized` is not thread-safe
|
|
||||||
pub unsafe fn set_maximized_async(
|
|
||||||
ns_window: id,
|
|
||||||
is_zoomed: bool,
|
|
||||||
maximized: bool,
|
|
||||||
shared_state: Weak<Mutex<SharedState>>,
|
|
||||||
) {
|
|
||||||
let ns_window = MainThreadSafe(ns_window);
|
|
||||||
let shared_state = MainThreadSafe(shared_state);
|
|
||||||
Queue::main().exec_async(move || {
|
|
||||||
if let Some(shared_state) = shared_state.upgrade() {
|
|
||||||
let mut shared_state_lock =
|
|
||||||
SharedStateMutexGuard::new(shared_state.lock().unwrap(), "set_maximized");
|
|
||||||
|
|
||||||
// Save the standard frame sized if it is not zoomed
|
|
||||||
if !is_zoomed {
|
|
||||||
shared_state_lock.standard_frame = Some(NSWindow::frame(*ns_window));
|
|
||||||
}
|
|
||||||
|
|
||||||
shared_state_lock.maximized = maximized;
|
|
||||||
|
|
||||||
if shared_state_lock.fullscreen.is_some() {
|
|
||||||
// Handle it in window_did_exit_fullscreen
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ns_window
|
|
||||||
.styleMask()
|
|
||||||
.contains(NSWindowStyleMask::NSResizableWindowMask)
|
|
||||||
{
|
|
||||||
// Just use the native zoom if resizable
|
|
||||||
ns_window.zoom_(nil);
|
|
||||||
} else {
|
|
||||||
// if it's not resizable, we set the frame directly
|
|
||||||
let new_rect = if maximized {
|
|
||||||
let screen = NSScreen::mainScreen(nil);
|
|
||||||
NSScreen::visibleFrame(screen)
|
|
||||||
} else {
|
|
||||||
shared_state_lock.saved_standard_frame()
|
|
||||||
};
|
|
||||||
ns_window.setFrame_display_(new_rect, NO);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// `orderOut:` isn't thread-safe. Calling it from another thread actually works,
|
|
||||||
// but with an odd delay.
|
|
||||||
pub unsafe fn order_out_async(ns_window: id) {
|
|
||||||
let ns_window = MainThreadSafe(ns_window);
|
|
||||||
Queue::main().exec_async(move || {
|
|
||||||
ns_window.orderOut_(nil);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// `makeKeyAndOrderFront:` isn't thread-safe. Calling it from another thread
|
|
||||||
// actually works, but with an odd delay.
|
|
||||||
pub unsafe fn make_key_and_order_front_async(ns_window: id) {
|
|
||||||
let ns_window = MainThreadSafe(ns_window);
|
|
||||||
Queue::main().exec_async(move || {
|
|
||||||
ns_window.makeKeyAndOrderFront_(nil);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// `setTitle:` isn't thread-safe. Calling it from another thread invalidates the
|
|
||||||
// window drag regions, which throws an exception when not done in the main
|
|
||||||
// thread
|
|
||||||
pub unsafe fn set_title_async(ns_window: id, title: String) {
|
|
||||||
let ns_window = MainThreadSafe(ns_window);
|
|
||||||
Queue::main().exec_async(move || {
|
|
||||||
let title = IdRef::new(NSString::alloc(nil).init_str(&title));
|
|
||||||
ns_window.setTitle_(*title);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// `close:` is thread-safe, but we want the event to be triggered from the main
|
|
||||||
// thread. Though, it's a good idea to look into that more...
|
|
||||||
//
|
|
||||||
// ArturKovacs: It's important that this operation keeps the underlying window alive
|
|
||||||
// through the `IdRef` because otherwise it would dereference free'd memory
|
|
||||||
pub unsafe fn close_async(ns_window: IdRef) {
|
|
||||||
let ns_window = MainThreadSafe(ns_window);
|
|
||||||
Queue::main().exec_async(move || {
|
|
||||||
autoreleasepool(move || {
|
|
||||||
ns_window.close();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
use cocoa::{
|
|
||||||
appkit::NSImage,
|
|
||||||
base::{id, nil},
|
|
||||||
foundation::{NSDictionary, NSPoint, NSString},
|
|
||||||
};
|
|
||||||
use objc::{runtime::Sel, runtime::NO};
|
|
||||||
use std::cell::RefCell;
|
|
||||||
|
|
||||||
use crate::window::CursorIcon;
|
|
||||||
|
|
||||||
pub enum Cursor {
|
|
||||||
Native(&'static str),
|
|
||||||
Undocumented(&'static str),
|
|
||||||
WebKit(&'static str),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<CursorIcon> for Cursor {
|
|
||||||
fn from(cursor: CursorIcon) -> Self {
|
|
||||||
// See native cursors at https://developer.apple.com/documentation/appkit/nscursor?language=objc.
|
|
||||||
match cursor {
|
|
||||||
CursorIcon::Arrow | CursorIcon::Default => Cursor::Native("arrowCursor"),
|
|
||||||
CursorIcon::Hand => Cursor::Native("pointingHandCursor"),
|
|
||||||
CursorIcon::Grab => Cursor::Native("openHandCursor"),
|
|
||||||
CursorIcon::Grabbing => Cursor::Native("closedHandCursor"),
|
|
||||||
CursorIcon::Text => Cursor::Native("IBeamCursor"),
|
|
||||||
CursorIcon::VerticalText => Cursor::Native("IBeamCursorForVerticalLayout"),
|
|
||||||
CursorIcon::Copy => Cursor::Native("dragCopyCursor"),
|
|
||||||
CursorIcon::Alias => Cursor::Native("dragLinkCursor"),
|
|
||||||
CursorIcon::NotAllowed | CursorIcon::NoDrop => {
|
|
||||||
Cursor::Native("operationNotAllowedCursor")
|
|
||||||
}
|
|
||||||
CursorIcon::ContextMenu => Cursor::Native("contextualMenuCursor"),
|
|
||||||
CursorIcon::Crosshair => Cursor::Native("crosshairCursor"),
|
|
||||||
CursorIcon::EResize => Cursor::Native("resizeRightCursor"),
|
|
||||||
CursorIcon::NResize => Cursor::Native("resizeUpCursor"),
|
|
||||||
CursorIcon::WResize => Cursor::Native("resizeLeftCursor"),
|
|
||||||
CursorIcon::SResize => Cursor::Native("resizeDownCursor"),
|
|
||||||
CursorIcon::EwResize | CursorIcon::ColResize => Cursor::Native("resizeLeftRightCursor"),
|
|
||||||
CursorIcon::NsResize | CursorIcon::RowResize => Cursor::Native("resizeUpDownCursor"),
|
|
||||||
|
|
||||||
// Undocumented cursors: https://stackoverflow.com/a/46635398/5435443
|
|
||||||
CursorIcon::Help => Cursor::Undocumented("_helpCursor"),
|
|
||||||
CursorIcon::ZoomIn => Cursor::Undocumented("_zoomInCursor"),
|
|
||||||
CursorIcon::ZoomOut => Cursor::Undocumented("_zoomOutCursor"),
|
|
||||||
CursorIcon::NeResize => Cursor::Undocumented("_windowResizeNorthEastCursor"),
|
|
||||||
CursorIcon::NwResize => Cursor::Undocumented("_windowResizeNorthWestCursor"),
|
|
||||||
CursorIcon::SeResize => Cursor::Undocumented("_windowResizeSouthEastCursor"),
|
|
||||||
CursorIcon::SwResize => Cursor::Undocumented("_windowResizeSouthWestCursor"),
|
|
||||||
CursorIcon::NeswResize => Cursor::Undocumented("_windowResizeNorthEastSouthWestCursor"),
|
|
||||||
CursorIcon::NwseResize => Cursor::Undocumented("_windowResizeNorthWestSouthEastCursor"),
|
|
||||||
|
|
||||||
// While these are available, the former just loads a white arrow,
|
|
||||||
// and the latter loads an ugly deflated beachball!
|
|
||||||
// CursorIcon::Move => Cursor::Undocumented("_moveCursor"),
|
|
||||||
// CursorIcon::Wait => Cursor::Undocumented("_waitCursor"),
|
|
||||||
|
|
||||||
// An even more undocumented cursor...
|
|
||||||
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=522349
|
|
||||||
// This is the wrong semantics for `Wait`, but it's the same as
|
|
||||||
// what's used in Safari and Chrome.
|
|
||||||
CursorIcon::Wait | CursorIcon::Progress => {
|
|
||||||
Cursor::Undocumented("busyButClickableCursor")
|
|
||||||
}
|
|
||||||
|
|
||||||
// For the rest, we can just snatch the cursors from WebKit...
|
|
||||||
// They fit the style of the native cursors, and will seem
|
|
||||||
// completely standard to macOS users.
|
|
||||||
// https://stackoverflow.com/a/21786835/5435443
|
|
||||||
CursorIcon::Move | CursorIcon::AllScroll => Cursor::WebKit("move"),
|
|
||||||
CursorIcon::Cell => Cursor::WebKit("cell"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Cursor {
|
|
||||||
fn default() -> Self {
|
|
||||||
Cursor::Native("arrowCursor")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Cursor {
|
|
||||||
pub unsafe fn load(&self) -> id {
|
|
||||||
match self {
|
|
||||||
Cursor::Native(cursor_name) => {
|
|
||||||
let sel = Sel::register(cursor_name);
|
|
||||||
msg_send![class!(NSCursor), performSelector: sel]
|
|
||||||
}
|
|
||||||
Cursor::Undocumented(cursor_name) => {
|
|
||||||
let class = class!(NSCursor);
|
|
||||||
let sel = Sel::register(cursor_name);
|
|
||||||
let sel = if msg_send![class, respondsToSelector: sel] {
|
|
||||||
sel
|
|
||||||
} else {
|
|
||||||
warn!("Cursor `{}` appears to be invalid", cursor_name);
|
|
||||||
sel!(arrowCursor)
|
|
||||||
};
|
|
||||||
msg_send![class, performSelector: sel]
|
|
||||||
}
|
|
||||||
Cursor::WebKit(cursor_name) => load_webkit_cursor(cursor_name),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note that loading `busybutclickable` with this code won't animate the frames;
|
|
||||||
// instead you'll just get them all in a column.
|
|
||||||
pub unsafe fn load_webkit_cursor(cursor_name: &str) -> id {
|
|
||||||
static CURSOR_ROOT: &str = "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/Resources/cursors";
|
|
||||||
let cursor_root = NSString::alloc(nil).init_str(CURSOR_ROOT);
|
|
||||||
let cursor_name = NSString::alloc(nil).init_str(cursor_name);
|
|
||||||
let cursor_pdf = NSString::alloc(nil).init_str("cursor.pdf");
|
|
||||||
let cursor_plist = NSString::alloc(nil).init_str("info.plist");
|
|
||||||
let key_x = NSString::alloc(nil).init_str("hotx");
|
|
||||||
let key_y = NSString::alloc(nil).init_str("hoty");
|
|
||||||
|
|
||||||
let cursor_path: id = msg_send![cursor_root, stringByAppendingPathComponent: cursor_name];
|
|
||||||
let pdf_path: id = msg_send![cursor_path, stringByAppendingPathComponent: cursor_pdf];
|
|
||||||
let info_path: id = msg_send![cursor_path, stringByAppendingPathComponent: cursor_plist];
|
|
||||||
|
|
||||||
let image = NSImage::alloc(nil).initByReferencingFile_(pdf_path);
|
|
||||||
let info = NSDictionary::dictionaryWithContentsOfFile_(nil, info_path);
|
|
||||||
let x = info.valueForKey_(key_x);
|
|
||||||
let y = info.valueForKey_(key_y);
|
|
||||||
let point = NSPoint::new(msg_send![x, doubleValue], msg_send![y, doubleValue]);
|
|
||||||
let cursor: id = msg_send![class!(NSCursor), alloc];
|
|
||||||
msg_send![cursor,
|
|
||||||
initWithImage:image
|
|
||||||
hotSpot:point
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn invisible_cursor() -> id {
|
|
||||||
// 16x16 GIF data for invisible cursor
|
|
||||||
// You can reproduce this via ImageMagick.
|
|
||||||
// $ convert -size 16x16 xc:none cursor.gif
|
|
||||||
static CURSOR_BYTES: &[u8] = &[
|
|
||||||
0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x10, 0x00, 0x10, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x0E, 0x84, 0x8F, 0xA9, 0xCB, 0xED, 0x0F,
|
|
||||||
0xA3, 0x9C, 0xB4, 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B,
|
|
||||||
];
|
|
||||||
|
|
||||||
thread_local! {
|
|
||||||
// We can't initialize this at startup.
|
|
||||||
static CURSOR_OBJECT: RefCell<id> = RefCell::new(nil);
|
|
||||||
}
|
|
||||||
|
|
||||||
CURSOR_OBJECT.with(|cursor_obj| {
|
|
||||||
if *cursor_obj.borrow() == nil {
|
|
||||||
// Create a cursor from `CURSOR_BYTES`
|
|
||||||
let cursor_data: id = msg_send![class!(NSData),
|
|
||||||
dataWithBytesNoCopy:CURSOR_BYTES as *const [u8]
|
|
||||||
length:CURSOR_BYTES.len()
|
|
||||||
freeWhenDone:NO
|
|
||||||
];
|
|
||||||
|
|
||||||
let ns_image: id = msg_send![class!(NSImage), alloc];
|
|
||||||
let _: id = msg_send![ns_image, initWithData: cursor_data];
|
|
||||||
let cursor: id = msg_send![class!(NSCursor), alloc];
|
|
||||||
*cursor_obj.borrow_mut() =
|
|
||||||
msg_send![cursor, initWithImage:ns_image hotSpot: NSPoint::new(0.0, 0.0)];
|
|
||||||
}
|
|
||||||
*cursor_obj.borrow()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
mod r#async;
|
|
||||||
mod cursor;
|
|
||||||
|
|
||||||
pub use self::{cursor::*, r#async::*};
|
|
||||||
|
|
||||||
use std::ops::{BitAnd, Deref};
|
|
||||||
use std::os::raw::c_uchar;
|
|
||||||
|
|
||||||
use cocoa::{
|
|
||||||
appkit::{CGFloat, NSApp, NSWindowStyleMask},
|
|
||||||
base::{id, nil},
|
|
||||||
foundation::{NSPoint, NSRect, NSString, NSUInteger},
|
|
||||||
};
|
|
||||||
use core_graphics::display::CGDisplay;
|
|
||||||
use objc::runtime::{Class, Object, BOOL, NO};
|
|
||||||
|
|
||||||
use crate::dpi::LogicalPosition;
|
|
||||||
use crate::platform_impl::platform::ffi;
|
|
||||||
|
|
||||||
// Replace with `!` once stable
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Never {}
|
|
||||||
|
|
||||||
pub fn has_flag<T>(bitset: T, flag: T) -> bool
|
|
||||||
where
|
|
||||||
T: Copy + PartialEq + BitAnd<T, Output = T>,
|
|
||||||
{
|
|
||||||
bitset & flag == flag
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const EMPTY_RANGE: ffi::NSRange = ffi::NSRange {
|
|
||||||
location: ffi::NSNotFound as NSUInteger,
|
|
||||||
length: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
|
||||||
pub struct IdRef(id);
|
|
||||||
|
|
||||||
impl IdRef {
|
|
||||||
pub fn new(inner: id) -> IdRef {
|
|
||||||
IdRef(inner)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn retain(inner: id) -> IdRef {
|
|
||||||
if inner != nil {
|
|
||||||
let _: id = unsafe { msg_send![inner, retain] };
|
|
||||||
}
|
|
||||||
IdRef(inner)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn non_nil(self) -> Option<IdRef> {
|
|
||||||
if self.0 == nil {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for IdRef {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if self.0 != nil {
|
|
||||||
unsafe {
|
|
||||||
let _: () = msg_send![self.0, release];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for IdRef {
|
|
||||||
type Target = id;
|
|
||||||
fn deref(&self) -> &id {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for IdRef {
|
|
||||||
fn clone(&self) -> IdRef {
|
|
||||||
IdRef::retain(self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! trace_scope {
|
|
||||||
($s:literal) => {
|
|
||||||
let _crate = $crate::platform_impl::platform::util::TraceGuard::new(module_path!(), $s);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct TraceGuard {
|
|
||||||
module_path: &'static str,
|
|
||||||
called_from_fn: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TraceGuard {
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn new(module_path: &'static str, called_from_fn: &'static str) -> Self {
|
|
||||||
trace!(target: module_path, "Triggered `{}`", called_from_fn);
|
|
||||||
Self {
|
|
||||||
module_path,
|
|
||||||
called_from_fn,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for TraceGuard {
|
|
||||||
#[inline]
|
|
||||||
fn drop(&mut self) {
|
|
||||||
trace!(target: self.module_path, "Completed `{}`", self.called_from_fn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For consistency with other platforms, this will...
|
|
||||||
// 1. translate the bottom-left window corner into the top-left window corner
|
|
||||||
// 2. translate the coordinate from a bottom-left origin coordinate system to a top-left one
|
|
||||||
pub fn bottom_left_to_top_left(rect: NSRect) -> f64 {
|
|
||||||
CGDisplay::main().pixels_high() as f64 - (rect.origin.y + rect.size.height) as f64
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts from winit screen-coordinates to macOS screen-coordinates.
|
|
||||||
/// Winit: top-left is (0, 0) and y increasing downwards
|
|
||||||
/// macOS: bottom-left is (0, 0) and y increasing upwards
|
|
||||||
pub fn window_position(position: LogicalPosition<f64>) -> NSPoint {
|
|
||||||
NSPoint::new(
|
|
||||||
position.x as CGFloat,
|
|
||||||
CGDisplay::main().pixels_high() as CGFloat - position.y as CGFloat,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn ns_string_id_ref(s: &str) -> IdRef {
|
|
||||||
IdRef::new(NSString::alloc(nil).init_str(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)] // In case we want to use this function in the future
|
|
||||||
pub unsafe fn app_name() -> Option<id> {
|
|
||||||
let bundle: id = msg_send![class!(NSBundle), mainBundle];
|
|
||||||
let dict: id = msg_send![bundle, infoDictionary];
|
|
||||||
let key = ns_string_id_ref("CFBundleName");
|
|
||||||
let app_name: id = msg_send![dict, objectForKey:*key];
|
|
||||||
if app_name != nil {
|
|
||||||
Some(app_name)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn superclass(this: &Object) -> &Class {
|
|
||||||
let superclass: *const Class = msg_send![this, superclass];
|
|
||||||
&*superclass
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub unsafe fn open_emoji_picker() {
|
|
||||||
let _: () = msg_send![NSApp(), orderFrontCharacterPalette: nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn toggle_style_mask(window: id, view: id, mask: NSWindowStyleMask, on: bool) {
|
|
||||||
use cocoa::appkit::NSWindow;
|
|
||||||
|
|
||||||
let current_style_mask = window.styleMask();
|
|
||||||
if on {
|
|
||||||
window.setStyleMask_(current_style_mask | mask);
|
|
||||||
} else {
|
|
||||||
window.setStyleMask_(current_style_mask & (!mask));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we don't do this, key handling will break. Therefore, never call `setStyleMask` directly!
|
|
||||||
window.makeFirstResponder_(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// For invalid utf8 sequences potentially returned by `UTF8String`,
|
|
||||||
/// it behaves identically to `String::from_utf8_lossy`
|
|
||||||
///
|
|
||||||
/// Safety: Assumes that `string` is an instance of `NSAttributedString` or `NSString`
|
|
||||||
pub unsafe fn id_to_string_lossy(string: id) -> String {
|
|
||||||
let has_attr: BOOL = msg_send![string, isKindOfClass: class!(NSAttributedString)];
|
|
||||||
let characters = if has_attr != NO {
|
|
||||||
// This is a *mut NSAttributedString
|
|
||||||
msg_send![string, string]
|
|
||||||
} else {
|
|
||||||
// This is already a *mut NSString
|
|
||||||
string
|
|
||||||
};
|
|
||||||
let utf8_sequence =
|
|
||||||
std::slice::from_raw_parts(characters.UTF8String() as *const c_uchar, characters.len());
|
|
||||||
String::from_utf8_lossy(utf8_sequence).into_owned()
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user