mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-27 15:13:13 -04:00
Compare commits
903 Commits
v0.21.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 | ||
|
|
50035643f7 | ||
|
|
64c22f9075 | ||
|
|
4895a29e92 | ||
|
|
6cdb3179c8 | ||
|
|
4fd52af682 | ||
|
|
5a0bad130d | ||
|
|
08d025968e | ||
|
|
1cd0e94c26 | ||
|
|
1ec976f95e | ||
|
|
f10ef5f331 | ||
|
|
653bc59813 | ||
|
|
3e991e13dc | ||
|
|
5397b53e04 | ||
|
|
f09259f6de | ||
|
|
430a49ebc2 | ||
|
|
1091a8ba1a | ||
|
|
9116b6c8cd | ||
|
|
990e34a129 | ||
|
|
472d7b9376 | ||
|
|
50dd7881b1 | ||
|
|
aa8f8db305 | ||
|
|
cdd9b1e1eb | ||
|
|
2d2ce70edc | ||
|
|
78f1d1df38 | ||
|
|
a06bb3f992 | ||
|
|
e289f30e5d | ||
|
|
4b10993970 | ||
|
|
d78a870e66 | ||
|
|
cb41c58f21 | ||
|
|
c55d97183d | ||
|
|
8646cbc9f5 | ||
|
|
64c1d4c5bb | ||
|
|
76b949c196 | ||
|
|
c93ef47b9b | ||
|
|
2b414cd825 | ||
|
|
ac42447459 | ||
|
|
401d20fa1f | ||
|
|
6b5b570b45 | ||
|
|
9e6f666616 | ||
|
|
8ef9fe44c7 | ||
|
|
3e0a544eb8 | ||
|
|
6474891f1e | ||
|
|
40abb526cc | ||
|
|
c532d910c0 | ||
|
|
44288f6280 | ||
|
|
eec84ade86 | ||
|
|
10419ff441 | ||
|
|
57981b533d | ||
|
|
c5eaa0ab69 | ||
|
|
2c01e9e747 | ||
|
|
4c39b3188c | ||
|
|
224872ce03 | ||
|
|
f10a984ba3 | ||
|
|
c7f7181388 | ||
|
|
92530299eb | ||
|
|
58cd23d1ac | ||
|
|
5d85c10a2c | ||
|
|
f11270dac0 | ||
|
|
bcd76d4718 | ||
|
|
4dd2b66aaa | ||
|
|
829a140d9b | ||
|
|
f04fa5d54f | ||
|
|
b4175c1454 | ||
|
|
0728105b2b | ||
|
|
ea09d1d10e | ||
|
|
e7f88588bf | ||
|
|
ce890c3455 | ||
|
|
cbba00d360 | ||
|
|
bf366cb99d | ||
|
|
142d55ff24 | ||
|
|
a58400a82c | ||
|
|
aac28d24ac | ||
|
|
d624a3e648 | ||
|
|
c57294b41a | ||
|
|
485e82dcb1 | ||
|
|
e8d910ffd3 | ||
|
|
ab1f636960 | ||
|
|
52c4670237 | ||
|
|
2ae12fb0a0 | ||
|
|
6c1d3c4fd8 | ||
|
|
08de2b3fc4 | ||
|
|
945a9e3122 | ||
|
|
6e28ba8927 | ||
|
|
7369551c02 | ||
|
|
e22c76b3ac | ||
|
|
a438091266 | ||
|
|
85baf79d17 | ||
|
|
1c68be0631 | ||
|
|
b222dde835 | ||
|
|
78e5a395da | ||
|
|
7846e6a31e | ||
|
|
b7e7755edd | ||
|
|
40f48cbeb4 | ||
|
|
fb8313aa97 | ||
|
|
f9643917d3 | ||
|
|
ac1c9b1218 | ||
|
|
daf0d6b9a7 | ||
|
|
fa14863284 | ||
|
|
cd9ec0afc7 | ||
|
|
f3f6f1008a | ||
|
|
0e52672f4a | ||
|
|
bc1dc1fd63 | ||
|
|
f93f2c158b | ||
|
|
9229e2d88b | ||
|
|
51bb6b751e | ||
|
|
2cc87cab65 | ||
|
|
7cd273ae58 | ||
|
|
001fb7ef60 | ||
|
|
cf4660841a | ||
|
|
a52f755ce8 | ||
|
|
2a2abc4843 | ||
|
|
8af222c1e3 | ||
|
|
d3e6949007 | ||
|
|
a033b25ecb | ||
|
|
39dd30c239 | ||
|
|
c5c99d2357 | ||
|
|
25ff30ee8c | ||
|
|
6b250a74f8 | ||
|
|
5331397c6c | ||
|
|
0b39024133 | ||
|
|
438d286fd5 | ||
|
|
18a61f1058 | ||
|
|
20d012ae3f | ||
|
|
efc54ab8ba | ||
|
|
ea1c031b54 | ||
|
|
11a44081df | ||
|
|
5eb9c9504b | ||
|
|
29a078f65c | ||
|
|
be61ca13fe | ||
|
|
e9d5b2007a | ||
|
|
f2de8475fc | ||
|
|
3ecbea3c39 | ||
|
|
c4df7ad7a5 | ||
|
|
387567a917 | ||
|
|
cfbe8462cc | ||
|
|
5f4df54895 | ||
|
|
ed698f2462 | ||
|
|
b4774861db | ||
|
|
9768f73bb7 | ||
|
|
805249e27e | ||
|
|
5f24c40d05 | ||
|
|
1b3b82a3c1 | ||
|
|
9e72396709 | ||
|
|
125ee0b446 | ||
|
|
3bfb580d7a | ||
|
|
b54d47796d | ||
|
|
b5d0d6ff3e | ||
|
|
c9520deef8 | ||
|
|
ceab0f8c40 | ||
|
|
b87757c552 | ||
|
|
1972eb952d | ||
|
|
f92803d80e | ||
|
|
8afeb910bd | ||
|
|
f16ed98af4 | ||
|
|
2a9916103b | ||
|
|
63ad47a7bf | ||
|
|
27e6548343 | ||
|
|
8c91986dd3 | ||
|
|
5a65347c4e | ||
|
|
635180c8be | ||
|
|
019ce9862f | ||
|
|
c7f46876a7 | ||
|
|
c916eb6137 | ||
|
|
67cca71524 | ||
|
|
1eff7ae004 | ||
|
|
982ad46c83 | ||
|
|
657b4fd59e | ||
|
|
b371b406d5 | ||
|
|
91591c4e94 | ||
|
|
078b9719cc | ||
|
|
41d9826ee9 | ||
|
|
0152508a39 | ||
|
|
cdeb1c3828 | ||
|
|
0986fae066 | ||
|
|
277515636d | ||
|
|
45aacd8407 | ||
|
|
e8cdf8b092 | ||
|
|
1c4d6e7613 | ||
|
|
04b4e48265 | ||
|
|
dabcb1834d | ||
|
|
629cd86c7c | ||
|
|
ba704c4eb4 | ||
|
|
0487876826 | ||
|
|
ca9c05368e | ||
|
|
0d634a0061 | ||
|
|
86748fbc68 | ||
|
|
599477d754 | ||
|
|
889258f538 | ||
|
|
ffe2143d14 | ||
|
|
98470393d1 | ||
|
|
4192d04a53 | ||
|
|
3571dcd68c | ||
|
|
952edcb804 | ||
|
|
10a94c0794 | ||
|
|
dd32ace9ab | ||
|
|
7e0c6ee097 | ||
|
|
b1be34c6a0 | ||
|
|
b9307a9967 | ||
|
|
b1d353180b | ||
|
|
bd99eb1347 | ||
|
|
f79c01b0cf | ||
|
|
3f1e09ec0e | ||
|
|
05125029c6 | ||
|
|
05fe983757 | ||
|
|
d1a7749df5 | ||
|
|
9d63fc7ca0 | ||
|
|
38fccebe1f | ||
|
|
c05952b813 | ||
|
|
932cbe40bf | ||
|
|
39573d65d0 | ||
|
|
6db308f1e9 | ||
|
|
6f70fd90b9 | ||
|
|
db038d943c | ||
|
|
c5620efc9c | ||
|
|
8fb7aa5cef | ||
|
|
6ddee9a8ac | ||
|
|
5700359a61 | ||
|
|
0861a353d6 | ||
|
|
f79efec7ef | ||
|
|
77d5d20391 | ||
|
|
165e51d850 | ||
|
|
1c38f113b3 | ||
|
|
66859607a3 | ||
|
|
edf396b1a4 | ||
|
|
cbeb51b436 | ||
|
|
45e4fd6ec1 | ||
|
|
3a077ff211 | ||
|
|
be850e483a | ||
|
|
33fb62bb25 | ||
|
|
66c117e599 | ||
|
|
8aa1be8336 | ||
|
|
037d4121a1 | ||
|
|
fbd3918d3a | ||
|
|
7c543a43a9 | ||
|
|
ee3996cac6 | ||
|
|
96809ac659 | ||
|
|
6343059bc0 | ||
|
|
5a78fe33e8 | ||
|
|
676fb947f2 | ||
|
|
d18afb4a50 | ||
|
|
fc336a76bf | ||
|
|
b9f3d333e4 | ||
|
|
3d85af04be | ||
|
|
471b1e003a | ||
|
|
be2e17d605 | ||
|
|
9d6b9797c0 | ||
|
|
3cd6a18048 | ||
|
|
c9558c5f0e | ||
|
|
71e3d25422 | ||
|
|
644dc13e00 | ||
|
|
47e7aa4209 | ||
|
|
1c97a310b1 | ||
|
|
d612a1b5a1 | ||
|
|
386ead15a3 | ||
|
|
83c95e774d | ||
|
|
e4754999b7 | ||
|
|
c66489dbb1 | ||
|
|
21f9aefc7e | ||
|
|
d103dc2631 | ||
|
|
cac627ed05 | ||
|
|
e2cf2a5754 | ||
|
|
658a9a4ea8 | ||
|
|
a2db4c0a32 | ||
|
|
02a34a167a | ||
|
|
bea60930b6 | ||
|
|
0f7c82d38f | ||
|
|
6ba583d198 | ||
|
|
89d4c06dec | ||
|
|
9c72cc2a98 | ||
|
|
412bd94ea4 | ||
|
|
514ab043f2 | ||
|
|
68100102be | ||
|
|
05fdcb5b27 | ||
|
|
7a49c88200 | ||
|
|
40232d48ba | ||
|
|
55dff53a98 | ||
|
|
6919c2fb2d | ||
|
|
3d5d05eac7 | ||
|
|
dd866a74a6 | ||
|
|
b1e22aa559 | ||
|
|
2191e9ecd5 | ||
|
|
bf62103417 | ||
|
|
4b1b314ce2 | ||
|
|
c1ea0dde92 | ||
|
|
5a6cfc314e | ||
|
|
a4121a2c2e | ||
|
|
03335cef85 | ||
|
|
ff66bdda7c | ||
|
|
6cfddfea21 | ||
|
|
49bcec1d27 | ||
|
|
878c179761 | ||
|
|
bc19c04339 | ||
|
|
c7a33f926b | ||
|
|
3c38afdb47 | ||
|
|
b8828105cf | ||
|
|
007b195a5e | ||
|
|
b4c6cdf9a3 | ||
|
|
26775fa0b6 | ||
|
|
114fe9d502 | ||
|
|
54bc41f68b | ||
|
|
47ff8d61d1 | ||
|
|
849b8f5dce | ||
|
|
aabe42d252 | ||
|
|
78a62ec547 | ||
|
|
6dae994bb4 | ||
|
|
4c4d0916fd | ||
|
|
d5609729cc | ||
|
|
1f24a09570 | ||
|
|
a8e777a5df | ||
|
|
0bc58f695b | ||
|
|
28023d9f5b | ||
|
|
c2aed1979d | ||
|
|
7e04273719 | ||
|
|
0683bdcd42 | ||
|
|
29ab0bb629 | ||
|
|
7a9c17a520 | ||
|
|
b208daa271 | ||
|
|
e85a80dd65 | ||
|
|
b1d8ce24e9 | ||
|
|
098fd5d602 | ||
|
|
2f27f64cdb | ||
|
|
cbb60d29a2 | ||
|
|
e707052f66 | ||
|
|
71bd6e73ca | ||
|
|
b8326f6452 | ||
|
|
ece2e70a53 | ||
|
|
2b14ec23d5 | ||
|
|
9999f53329 | ||
|
|
522a6e3298 | ||
|
|
76d0dd7ec3 | ||
|
|
d1073dcecb | ||
|
|
e88e8bc194 | ||
|
|
bc29931434 | ||
|
|
505f312d5f | ||
|
|
f0093d3c54 | ||
|
|
83b60beba6 | ||
|
|
5f52d7c9d0 | ||
|
|
a1b65f7080 | ||
|
|
96df858961 | ||
|
|
4eddd1e5bc |
2
.cargo/config.toml
Normal file
2
.cargo/config.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[alias]
|
||||||
|
run-wasm = ["run", "--release", "--package", "run-wasm", "--"]
|
||||||
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -20,5 +20,3 @@
|
|||||||
*.PDF diff=astextplain
|
*.PDF diff=astextplain
|
||||||
*.rtf diff=astextplain
|
*.rtf diff=astextplain
|
||||||
*.RTF diff=astextplain
|
*.RTF diff=astextplain
|
||||||
|
|
||||||
/CHANGELOG.md merge=union
|
|
||||||
|
|||||||
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
|
||||||
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
3
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,7 +1,4 @@
|
|||||||
- [ ] Tested on all platforms changed
|
- [ ] Tested on all platforms changed
|
||||||
- [ ] Compilation warnings were addressed
|
|
||||||
- [ ] `cargo fmt` has been run on this branch
|
|
||||||
- [ ] `cargo doc` builds successfully
|
|
||||||
- [ ] Added an entry to `CHANGELOG.md` if knowledge of this change could be valuable to users
|
- [ ] Added an entry to `CHANGELOG.md` if knowledge of this change could be valuable to users
|
||||||
- [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
|
- [ ] Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
|
||||||
- [ ] Created or updated an example program if it would help users understand this functionality
|
- [ ] Created or updated an example program if it would help users understand this functionality
|
||||||
|
|||||||
231
.github/workflows/ci.yml
vendored
231
.github/workflows/ci.yml
vendored
@@ -2,105 +2,202 @@ name: CI
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
|
||||||
- '**.rs'
|
|
||||||
- '**.toml'
|
|
||||||
- '.github/workflows/ci.yml'
|
|
||||||
push:
|
push:
|
||||||
branches: [master]
|
branches: [master]
|
||||||
paths:
|
|
||||||
- '**.rs'
|
|
||||||
- '**.toml'
|
|
||||||
- '.github/workflows/ci.yml'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
Check_Formatting:
|
fmt:
|
||||||
|
name: Check formatting
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- 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:
|
||||||
|
name: Test ${{ matrix.toolchain }} ${{ matrix.platform.name }}
|
||||||
|
runs-on: ${{ matrix.platform.os }}
|
||||||
|
|
||||||
Tests:
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
rust_version: [stable, nightly]
|
toolchain: [stable, nightly, '1.70.0']
|
||||||
platform:
|
platform:
|
||||||
- { target: x86_64-pc-windows-msvc, os: windows-latest, }
|
# Note: Make sure that we test all the `docs.rs` targets defined in Cargo.toml!
|
||||||
- { target: i686-pc-windows-msvc, os: windows-latest, }
|
- { name: 'Windows 64bit MSVC', target: x86_64-pc-windows-msvc, os: windows-latest, }
|
||||||
- { target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu }
|
- { name: 'Windows 32bit MSVC', target: i686-pc-windows-msvc, os: windows-latest, }
|
||||||
- { target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu }
|
- { name: 'Windows 64bit GNU', target: x86_64-pc-windows-gnu, os: windows-latest, host: -x86_64-pc-windows-gnu }
|
||||||
- { target: i686-unknown-linux-gnu, os: ubuntu-latest, }
|
- { name: 'Windows 32bit GNU', target: i686-pc-windows-gnu, os: windows-latest, host: -i686-pc-windows-gnu }
|
||||||
- { target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
|
- { name: 'Linux 32bit', target: i686-unknown-linux-gnu, os: ubuntu-latest, }
|
||||||
- { target: x86_64-apple-darwin, os: macos-latest, }
|
- { name: 'Linux 64bit', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, }
|
||||||
- { target: x86_64-apple-ios, os: macos-latest, }
|
- { name: 'X11', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=x11' }
|
||||||
- { target: aarch64-apple-ios, os: macos-latest, }
|
- { name: 'Wayland', target: x86_64-unknown-linux-gnu, os: ubuntu-latest, options: '--no-default-features --features=wayland,wayland-dlopen' }
|
||||||
# We're using Windows rather than Ubuntu to run the wasm tests because caching cargo-web
|
- { name: 'Android', target: aarch64-linux-android, os: ubuntu-latest, options: '--features=android-native-activity', cmd: 'apk --' }
|
||||||
# doesn't currently work on Linux.
|
- { name: 'Redox OS', target: x86_64-unknown-redox, os: ubuntu-latest, }
|
||||||
- { target: wasm32-unknown-unknown, os: windows-latest, features: stdweb, web: web }
|
- { name: 'macOS', target: x86_64-apple-darwin, os: macos-latest, }
|
||||||
- { target: wasm32-unknown-unknown, os: windows-latest, features: web-sys, web: web }
|
- { name: 'iOS x86_64', target: x86_64-apple-ios, os: macos-latest, }
|
||||||
|
- { name: 'iOS Aarch64', target: aarch64-apple-ios, os: macos-latest, }
|
||||||
|
- { name: 'web', target: wasm32-unknown-unknown, os: ubuntu-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
|
|
||||||
RUSTFLAGS: "-C debuginfo=0"
|
|
||||||
FEATURES: ${{ format(',{0}', matrix.platform.features ) }}
|
|
||||||
WEB: ${{ matrix.platform.web }}
|
|
||||||
|
|
||||||
runs-on: ${{ matrix.platform.os }}
|
# Faster compilation and error on warnings
|
||||||
|
RUSTFLAGS: '--codegen=debuginfo=0 --deny=warnings ${{ matrix.platform.rustflags }}'
|
||||||
|
|
||||||
|
OPTIONS: --target=${{ matrix.platform.target }} ${{ matrix.platform.options }}
|
||||||
|
CMD: ${{ matrix.platform.cmd }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- 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: |
|
||||||
|
~/.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: 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 apt-get update && sudo apt-get install gcc-multilib
|
run: sudo apt-get update && sudo apt-get install gcc-multilib
|
||||||
- name: Install cargo-web
|
|
||||||
continue-on-error: true
|
- name: Cache cargo-apk
|
||||||
if: contains(matrix.platform.target, 'wasm32')
|
if: contains(matrix.platform.target, 'android')
|
||||||
run: cargo install cargo-web
|
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
|
||||||
if: matrix.platform.target != 'wasm32-unknown-unknown'
|
env:
|
||||||
run: cargo doc --no-deps --target ${{ matrix.platform.target }} --features $FEATURES
|
RUSTDOCFLAGS: '--deny=warnings ${{ matrix.platform.rustflags }}'
|
||||||
|
|
||||||
- name: Build
|
- name: Build crate
|
||||||
shell: bash
|
run: cargo $CMD build -p winit $OPTIONS
|
||||||
run: cargo $WEB build --verbose --target ${{ matrix.platform.target }} --features $FEATURES
|
|
||||||
|
|
||||||
- name: Build tests
|
- name: Build tests
|
||||||
shell: bash
|
if: >
|
||||||
run: cargo $WEB test --no-run --verbose --target ${{ matrix.platform.target }} --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, 'ios') && !contains(matrix.platform.target, 'wasm32'))
|
!contains(matrix.platform.target, 'android') &&
|
||||||
run: cargo $WEB test --verbose --target ${{ matrix.platform.target }} --features $FEATURES
|
!contains(matrix.platform.target, 'ios') &&
|
||||||
|
!contains(matrix.platform.target, 'wasm32') &&
|
||||||
|
!contains(matrix.platform.target, 'redox') &&
|
||||||
|
matrix.toolchain != '1.70.0'
|
||||||
|
run: cargo $CMD test $OPTIONS
|
||||||
|
|
||||||
|
- name: Lint with clippy
|
||||||
- name: Build with serde enabled
|
if: (matrix.toolchain == 'stable') && !contains(matrix.platform.options, '--no-default-features')
|
||||||
shell: bash
|
run: cargo clippy --all-targets $OPTIONS -- -Dwarnings
|
||||||
run: cargo $WEB build --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES
|
|
||||||
|
|
||||||
- name: Build tests with serde enabled
|
- name: Build tests with serde enabled
|
||||||
shell: bash
|
if: >
|
||||||
run: cargo $WEB test --no-run --verbose --target ${{ matrix.platform.target }} --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, 'ios') && !contains(matrix.platform.target, 'wasm32'))
|
!contains(matrix.platform.target, 'android') &&
|
||||||
run: cargo $WEB test --verbose --target ${{ matrix.platform.target }} --features serde,$FEATURES
|
!contains(matrix.platform.target, 'ios') &&
|
||||||
|
!contains(matrix.platform.target, 'wasm32') &&
|
||||||
|
!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:
|
|
||||||
branches: [master]
|
|
||||||
paths: "Cargo.toml"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
Publish:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v1
|
|
||||||
- uses: hecrj/setup-rust-action@v1
|
|
||||||
with:
|
|
||||||
rust-version: stable
|
|
||||||
components: rustfmt
|
|
||||||
- name: Publish to crates.io
|
|
||||||
run: cargo publish --token ${{ secrets.cratesio_token }}
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ rls/
|
|||||||
*.ts
|
*.ts
|
||||||
*.js
|
*.js
|
||||||
#*#
|
#*#
|
||||||
|
.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
|
|
||||||
723
CHANGELOG.md
723
CHANGELOG.md
@@ -1,5 +1,709 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
- On Windows, fix hiding a maximized window.
|
||||||
|
- On Android, `ndk-glue`'s `NativeWindow` lock is now held between `Event::Resumed` and `Event::Suspended`.
|
||||||
|
- On Web, added `EventLoopExtWebSys` with a `spawn` method to start the event loop without throwing an exception.
|
||||||
|
- Added `WindowEvent::Occluded(bool)`, currently implemented on macOS and X11.
|
||||||
|
- On X11, fix events for caps lock key not being sent
|
||||||
|
- Build docs on `docs.rs` for iOS and Android as well.
|
||||||
|
- **Breaking:** Removed the `WindowAttributes` struct, since all its functionality is accessible from `WindowBuilder`.
|
||||||
|
- Added `WindowBuilder::transparent` getter to check if the user set `transparent` attribute.
|
||||||
|
- On macOS, Fix emitting `Event::LoopDestroyed` on CMD+Q.
|
||||||
|
- On macOS, fixed an issue where having multiple windows would prevent run_return from ever returning.
|
||||||
|
- On Wayland, fix bug where the cursor wouldn't hide in GNOME.
|
||||||
|
- On macOS, Windows, and Wayland, add `set_cursor_hittest` to let the window ignore mouse events.
|
||||||
|
- On Windows, added `WindowExtWindows::set_skip_taskbar` and `WindowBuilderExtWindows::with_skip_taskbar`.
|
||||||
|
- On Windows, added `EventLoopBuilderExtWindows::with_msg_hook`.
|
||||||
|
- On Windows, remove internally unique DC per window.
|
||||||
|
- On macOS, remove the need to call `set_ime_position` after moving the window.
|
||||||
|
- Added `Window::is_visible`.
|
||||||
|
- Added `Window::is_resizable`.
|
||||||
|
- Added `Window::is_decorated`.
|
||||||
|
- On X11, fix for repeated event loop iteration when `ControlFlow` was `Wait`
|
||||||
|
- On X11, fix scale factor calculation when the only monitor is reconnected
|
||||||
|
- On Wayland, report unaccelerated mouse deltas in `DeviceEvent::MouseMotion`.
|
||||||
|
- On Web, a focused event is manually generated when a click occurs to emulate behaviour of other backends.
|
||||||
|
- **Breaking:** Bump `ndk` version to 0.6, ndk-sys to `v0.3`, `ndk-glue` to `0.6`.
|
||||||
|
- Remove no longer needed `WINIT_LINK_COLORSYNC` environment variable.
|
||||||
|
- **Breaking:** Rename the `Exit` variant of `ControlFlow` to `ExitWithCode`, which holds a value to control the exit code after running. Add an `Exit` constant which aliases to `ExitWithCode(0)` instead to avoid major breakage. This shouldn't affect most existing programs.
|
||||||
|
- Add `EventLoopBuilder`, which allows you to create and tweak the settings of an event loop before creating it.
|
||||||
|
- Deprecated `EventLoop::with_user_event`; use `EventLoopBuilder::with_user_event` instead.
|
||||||
|
- **Breaking:** Replaced `EventLoopExtMacOS` with `EventLoopBuilderExtMacOS` (which also has renamed methods).
|
||||||
|
- **Breaking:** Replaced `EventLoopExtWindows` with `EventLoopBuilderExtWindows` (which also has renamed methods).
|
||||||
|
- **Breaking:** Replaced `EventLoopExtUnix` with `EventLoopBuilderExtUnix` (which also has renamed methods).
|
||||||
|
- **Breaking:** The platform specific extensions for Windows `winit::platform::windows` have changed. All `HANDLE`-like types e.g. `HWND` and `HMENU` were converted from winapi types or `*mut c_void` to `isize`. This was done to be consistent with the type definitions in windows-sys and to not expose internal dependencies.
|
||||||
|
- The internal bindings to the [Windows API](https://docs.microsoft.com/en-us/windows/) were changed from the unofficial [winapi](https://github.com/retep998/winapi-rs) bindings to the official Microsoft [windows-sys](https://github.com/microsoft/windows-rs) bindings.
|
||||||
|
- On Wayland, fix polling during consecutive `EventLoop::run_return` invocations.
|
||||||
|
- On Windows, fix race issue creating fullscreen windows with `WindowBuilder::with_fullscreen`
|
||||||
|
- On Android, `virtual_keycode` for `KeyboardInput` events is now filled in where a suitable match is found.
|
||||||
|
- Added helper methods on `ControlFlow` to set its value.
|
||||||
|
- On Wayland, fix `TouchPhase::Ended` always reporting the location of the first touch down, unless the compositor
|
||||||
|
sent a cancel or frame event.
|
||||||
|
- On iOS, send `RedrawEventsCleared` even if there are no redraw events, consistent with other platforms.
|
||||||
|
- **Breaking:** Replaced `Window::with_app_id` and `Window::with_class` with `Window::with_name` on `WindowBuilderExtUnix`.
|
||||||
|
- On Wayland, fallback CSD was replaced with proper one:
|
||||||
|
- `WindowBuilderExtUnix::with_wayland_csd_theme` to set color theme in builder.
|
||||||
|
- `WindowExtUnix::wayland_set_csd_theme` to set color theme when creating a window.
|
||||||
|
- `WINIT_WAYLAND_CSD_THEME` env variable was added, it can be used to set "dark"/"light" theme in apps that don't expose theme setting.
|
||||||
|
- `wayland-csd-adwaita` feature that enables proper CSD with title rendering using FreeType system library.
|
||||||
|
- `wayland-csd-adwaita-notitle` feature that enables CSD but without title rendering.
|
||||||
|
- On Wayland and X11, fix window not resizing with `Window::set_inner_size` after calling `Window:set_resizable(false)`.
|
||||||
|
- On Windows, fix wrong fullscreen monitors being recognized when handling WM_WINDOWPOSCHANGING messages
|
||||||
|
- **Breaking:** Added new `WindowEvent::Ime` supported on desktop platforms.
|
||||||
|
- Added `Window::set_ime_allowed` supported on desktop platforms.
|
||||||
|
- **Breaking:** IME input on desktop platforms won't be received unless it's explicitly allowed via `Window::set_ime_allowed` and new `WindowEvent::Ime` events are handled.
|
||||||
|
- On macOS, `WindowEvent::Resized` is now emitted in `frameDidChange` instead of `windowDidResize`.
|
||||||
|
- **Breaking:** On X11, device events are now ignored for unfocused windows by default, use `EventLoopWindowTarget::set_device_event_filter` to set the filter level.
|
||||||
|
- Implemented `Default` on `EventLoop<()>`.
|
||||||
|
- Implemented `Eq` for `Fullscreen`, `Theme`, and `UserAttentionType`.
|
||||||
|
- **Breaking:** `Window::set_cursor_grab` now accepts `CursorGrabMode` to control grabbing behavior.
|
||||||
|
- On Wayland, add support for `Window::set_cursor_position`.
|
||||||
|
- Fix on macOS `WindowBuilder::with_disallow_hidpi`, setting true or false by the user no matter the SO default value.
|
||||||
|
- `EventLoopBuilder::build` will now panic when the `EventLoop` is being created more than once.
|
||||||
|
- Added `From<u64>` for `WindowId` and `From<WindowId>` for `u64`.
|
||||||
|
- Added `MonitorHandle::refresh_rate_millihertz` to get monitor's refresh rate.
|
||||||
|
- **Breaking**, Replaced `VideoMode::refresh_rate` with `VideoMode::refresh_rate_millihertz` providing better precision.
|
||||||
|
- On Web, add `with_prevent_default` and `with_focusable` to `WindowBuilderExtWebSys` to control whether events should be propagated.
|
||||||
|
- On Windows, fix focus events being sent to inactive windows.
|
||||||
|
- **Breaking**, update `raw-window-handle` to `v0.5` and implement `HasRawDisplayHandle` for `Window` and `EventLoopWindowTarget`.
|
||||||
|
- On X11, add function `register_xlib_error_hook` into `winit::platform::unix` to subscribe for errors comming from Xlib.
|
||||||
|
- 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.
|
||||||
|
- **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)
|
||||||
|
|
||||||
|
- Fix linking to the `ColorSync` framework on macOS 10.7, and in newer Rust versions.
|
||||||
|
- On Web, implement cursor grabbing through the pointer lock API.
|
||||||
|
- On X11, add mappings for numpad comma, numpad enter, numlock and pause.
|
||||||
|
- On macOS, fix Pinyin IME input by reverting a change that intended to improve IME.
|
||||||
|
- On Windows, fix a crash with transparent windows on Windows 11.
|
||||||
|
|
||||||
|
# 0.26.0 (2021-12-01)
|
||||||
|
|
||||||
|
- Update `raw-window-handle` to `v0.4`. This is _not_ a breaking change, we still implement `HasRawWindowHandle` from `v0.3`, see [rust-windowing/raw-window-handle#74](https://github.com/rust-windowing/raw-window-handle/pull/74). Note that you might have to run `cargo update -p raw-window-handle` after upgrading.
|
||||||
|
- On X11, bump `mio` to 0.8.
|
||||||
|
- On Android, fixed `WindowExtAndroid::config` initially returning an empty `Configuration`.
|
||||||
|
- On Android, fixed `Window::scale_factor` and `MonitorHandle::scale_factor` initially always returning 1.0.
|
||||||
|
- On X11, select an appropriate visual for transparency if is requested
|
||||||
|
- On Wayland and X11, fix diagonal window resize cursor orientation.
|
||||||
|
- On macOS, drop the event callback before exiting.
|
||||||
|
- On Android, implement `Window::request_redraw`
|
||||||
|
- **Breaking:** On Web, remove the `stdweb` backend.
|
||||||
|
- Added `Window::focus_window`to bring the window to the front and set input focus.
|
||||||
|
- On Wayland and X11, implement `is_maximized` method on `Window`.
|
||||||
|
- On Windows, prevent ghost window from showing up in the taskbar after either several hours of use or restarting `explorer.exe`.
|
||||||
|
- On macOS, fix issue where `ReceivedCharacter` was not being emitted during some key repeat events.
|
||||||
|
- On Wayland, load cursor icons `hand2` and `hand1` for `CursorIcon::Hand`.
|
||||||
|
- **Breaking:** On Wayland, Theme trait and its support types are dropped.
|
||||||
|
- On Wayland, bump `smithay-client-toolkit` to 0.15.1.
|
||||||
|
- On Wayland, implement `request_user_attention` with `xdg_activation_v1`.
|
||||||
|
- On X11, emit missing `WindowEvent::ScaleFactorChanged` when the only monitor gets reconnected.
|
||||||
|
- On X11, if RANDR based scale factor is higher than 20 reset it to 1
|
||||||
|
- On Wayland, add an enabled-by-default feature called `wayland-dlopen` so users can opt out of using `dlopen` to load system libraries.
|
||||||
|
- **Breaking:** On Android, bump `ndk` and `ndk-glue` to 0.5.
|
||||||
|
- On Windows, increase wait timer resolution for more accurate timing when using `WaitUntil`.
|
||||||
|
- On macOS, fix native file dialogs hanging the event loop.
|
||||||
|
- On Wayland, implement a workaround for wrong configure size when using `xdg_decoration` in `kwin_wayland`
|
||||||
|
- On macOS, fix an issue that prevented the menu bar from showing in borderless fullscreen mode.
|
||||||
|
- On X11, EINTR while polling for events no longer causes a panic. Instead it will be treated as a spurious wakeup.
|
||||||
|
|
||||||
|
# 0.25.0 (2021-05-15)
|
||||||
|
|
||||||
|
- **Breaking:** On macOS, replace `WindowBuilderExtMacOS::with_activation_policy` with `EventLoopExtMacOS::set_activation_policy`
|
||||||
|
- On macOS, wait with activating the application until the application has initialized.
|
||||||
|
- On macOS, fix creating new windows when the application has a main menu.
|
||||||
|
- On Windows, fix fractional deltas for mouse wheel device events.
|
||||||
|
- On macOS, fix segmentation fault after dropping the main window.
|
||||||
|
- On Android, `InputEvent::KeyEvent` is partially implemented providing the key scancode.
|
||||||
|
- Added `is_maximized` method to `Window`.
|
||||||
|
- On Windows, fix bug where clicking the decoration bar would make the cursor blink.
|
||||||
|
- On Windows, fix bug causing newly created windows to erroneously display the "wait" (spinning) cursor.
|
||||||
|
- On macOS, wake up the event loop immediately when a redraw is requested.
|
||||||
|
- On Windows, change the default window size (1024x768) to match the default on other desktop platforms (800x600).
|
||||||
|
- On Windows, fix bug causing mouse capture to not be released.
|
||||||
|
- On Windows, fix fullscreen not preserving minimized/maximized state.
|
||||||
|
- On Android, unimplemented events are marked as unhandled on the native event loop.
|
||||||
|
- On Windows, added `WindowBuilderExtWindows::with_menu` to set a custom menu at window creation time.
|
||||||
|
- On Android, bump `ndk` and `ndk-glue` to 0.3: use predefined constants for event `ident`.
|
||||||
|
- On macOS, fix objects captured by the event loop closure not being dropped on panic.
|
||||||
|
- On Windows, fixed `WindowEvent::ThemeChanged` not properly firing and fixed `Window::theme` returning the wrong theme.
|
||||||
|
- On Web, added support for `DeviceEvent::MouseMotion` to listen for relative mouse movements.
|
||||||
|
- Added `WindowBuilder::with_position` to allow setting the position of a `Window` on creation. Supported on Windows, macOS and X11.
|
||||||
|
- Added `Window::drag_window`. Implemented on Windows, macOS, X11 and Wayland.
|
||||||
|
- On X11, bump `mio` to 0.7.
|
||||||
|
- On Windows, added `WindowBuilderExtWindows::with_owner_window` to allow creating popup windows.
|
||||||
|
- On Windows, added `WindowExtWindows::set_enable` to allow creating modal popup windows.
|
||||||
|
- On macOS, emit `RedrawRequested` events immediately while the window is being resized.
|
||||||
|
- Implement `Default`, `Hash`, and `Eq` for `LogicalPosition`, `PhysicalPosition`, `LogicalSize`, and `PhysicalSize`.
|
||||||
|
- On macOS, initialize the Menu Bar with minimal defaults. (Can be prevented using `enable_default_menu_creation`)
|
||||||
|
- On macOS, change the default behavior for first click when the window was unfocused. Now the window becomes focused and then emits a `MouseInput` event on a "first mouse click".
|
||||||
|
- Implement mint (math interoperability standard types) conversions (under feature flag `mint`).
|
||||||
|
|
||||||
|
# 0.24.0 (2020-12-09)
|
||||||
|
|
||||||
|
- On Windows, fix applications not exiting gracefully due to thread_event_target_callback accessing corrupted memory.
|
||||||
|
- On Windows, implement `Window::set_ime_position`.
|
||||||
|
- **Breaking:** On Windows, Renamed `WindowBuilderExtWindows`'s `is_dark_mode` to `theme`.
|
||||||
|
- **Breaking:** On Windows, renamed `WindowBuilderExtWindows::is_dark_mode` to `theme`.
|
||||||
|
- On Windows, add `WindowBuilderExtWindows::with_theme` to set a preferred theme.
|
||||||
|
- On Windows, fix bug causing message boxes to appear delayed.
|
||||||
|
- On Android, calling `WindowEvent::Focused` now works properly instead of always returning false.
|
||||||
|
- On Windows, fix Alt-Tab behaviour by removing borderless fullscreen "always on top" flag.
|
||||||
|
- On Windows, fix bug preventing windows with transparency enabled from having fully-opaque regions.
|
||||||
|
- **Breaking:** On Windows, include prefix byte in scancodes.
|
||||||
|
- On Wayland, fix window not being resizeable when using `WindowBuilder::with_min_inner_size`.
|
||||||
|
- On Unix, fix cross-compiling to wasm32 without enabling X11 or Wayland.
|
||||||
|
- On Windows, fix use-after-free crash during window destruction.
|
||||||
|
- On Web, fix `WindowEvent::ReceivedCharacter` never being sent on key input.
|
||||||
|
- On macOS, fix compilation when targeting aarch64.
|
||||||
|
- On X11, fix `Window::request_redraw` not waking the event loop.
|
||||||
|
- On Wayland, the keypad arrow keys are now recognized.
|
||||||
|
- **Breaking** Rename `desktop::EventLoopExtDesktop` to `run_return::EventLoopExtRunReturn`.
|
||||||
|
- Added `request_user_attention` method to `Window`.
|
||||||
|
- **Breaking:** On macOS, removed `WindowExt::request_user_attention`, use `Window::request_user_attention`.
|
||||||
|
- **Breaking:** On X11, removed `WindowExt::set_urgent`, use `Window::request_user_attention`.
|
||||||
|
- On Wayland, default font size in CSD increased from 11 to 17.
|
||||||
|
- On Windows, fix bug causing message boxes to appear delayed.
|
||||||
|
- On Android, support multi-touch.
|
||||||
|
- On Wayland, extra mouse buttons are not dropped anymore.
|
||||||
|
- **Breaking**: `MouseButton::Other` now uses `u16`.
|
||||||
|
|
||||||
|
# 0.23.0 (2020-10-02)
|
||||||
|
|
||||||
|
- 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 Unix, X11 and Wayland are now optional features (enabled by default)
|
||||||
|
- On X11, fix deadlock when calling `set_fullscreen_inner`.
|
||||||
|
- On Web, prevent the webpage from scrolling when the user is focused on a winit canvas
|
||||||
|
- On Web, calling `window.set_cursor_icon` no longer breaks HiDPI scaling
|
||||||
|
- On Windows, drag and drop is now optional (enabled by default) and can be disabled with `WindowBuilderExtWindows::with_drag_and_drop(false)`.
|
||||||
|
- On Wayland, fix deadlock when calling to `set_inner_size` from a callback.
|
||||||
|
- On macOS, add `hide__other_applications` to `EventLoopWindowTarget` via existing `EventLoopWindowTargetExtMacOS` trait. `hide_other_applications` will hide other applications by calling `-[NSApplication hideOtherApplications: nil]`.
|
||||||
|
- On android added support for `run_return`.
|
||||||
|
- On MacOS, Fixed fullscreen and dialog support for `run_return`.
|
||||||
|
- On Windows, fix bug where we'd try to emit `MainEventsCleared` events during nested win32 event loops.
|
||||||
|
- On Web, use mouse events if pointer events aren't supported. This affects Safari.
|
||||||
|
- On Windows, `set_ime_position` is now a no-op instead of a runtime crash.
|
||||||
|
- On Android, `set_fullscreen` is now a no-op instead of a runtime crash.
|
||||||
|
- On iOS and Android, `set_inner_size` is now a no-op instead of a runtime crash.
|
||||||
|
- On Android, fix `ControlFlow::Poll` not polling the Android event queue.
|
||||||
|
- On macOS, add `NSWindow.hasShadow` support.
|
||||||
|
- On Web, fix vertical mouse wheel scrolling being inverted.
|
||||||
|
- On Web, implement mouse capturing for click-dragging out of the canvas.
|
||||||
|
- On Web, fix `ControlFlow::Exit` not properly handled.
|
||||||
|
- On Web (web-sys only), send `WindowEvent::ScaleFactorChanged` event when `window.devicePixelRatio` is changed.
|
||||||
|
- **Breaking:** On Web, `set_cursor_position` and `set_cursor_grab` will now always return an error.
|
||||||
|
- **Breaking:** `PixelDelta` scroll events now return a `PhysicalPosition`.
|
||||||
|
- On NetBSD, fixed crash due to incorrect detection of the main thread.
|
||||||
|
- **Breaking:** On X11, `-` key is mapped to the `Minus` virtual key code, instead of `Subtract`.
|
||||||
|
- On macOS, fix inverted horizontal scroll.
|
||||||
|
- **Breaking:** `current_monitor` now returns `Option<MonitorHandle>`.
|
||||||
|
- **Breaking:** `primary_monitor` now returns `Option<MonitorHandle>`.
|
||||||
|
- On macOS, updated core-* dependencies and cocoa.
|
||||||
|
- Bump `parking_lot` to 0.11
|
||||||
|
- On Android, bump `ndk`, `ndk-sys` and `ndk-glue` to 0.2. Checkout the new ndk-glue main proc attribute.
|
||||||
|
- On iOS, fixed starting the app in landscape where the view still had portrait dimensions.
|
||||||
|
- Deprecate the stdweb backend, to be removed in a future release
|
||||||
|
- **Breaking:** Prefixed virtual key codes `Add`, `Multiply`, `Divide`, `Decimal`, and `Subtract` with `Numpad`.
|
||||||
|
- Added `Asterisk` and `Plus` virtual key codes.
|
||||||
|
- On Web (web-sys only), the `Event::LoopDestroyed` event is correctly emitted when leaving the page.
|
||||||
|
- On Web, the `WindowEvent::Destroyed` event now gets emitted when a `Window` is dropped.
|
||||||
|
- On Web (web-sys only), the event listeners are now removed when a `Window` is dropped or when the event loop is destroyed.
|
||||||
|
- On Web, the event handler closure passed to `EventLoop::run` now gets dropped after the event loop is destroyed.
|
||||||
|
- **Breaking:** On Web, the canvas element associated to a `Window` is no longer removed from the DOM when the `Window` is dropped.
|
||||||
|
- On Web, `WindowEvent::Resized` is now emitted when `Window::set_inner_size` is called.
|
||||||
|
- **Breaking:** `Fullscreen` enum now uses `Borderless(Option<MonitorHandle>)` instead of `Borderless(MonitorHandle)` to allow picking the current monitor.
|
||||||
|
- On MacOS, fix `WindowEvent::Moved` ignoring the scale factor.
|
||||||
|
- On Wayland, add missing virtual keycodes.
|
||||||
|
- On Wayland, implement proper `set_cursor_grab`.
|
||||||
|
- On Wayland, the cursor will use similar icons if the requested one isn't available.
|
||||||
|
- On Wayland, right clicking on client side decorations will request application menu.
|
||||||
|
- On Wayland, fix tracking of window size after state changes.
|
||||||
|
- On Wayland, fix client side decorations not being hidden properly in fullscreen.
|
||||||
|
- On Wayland, fix incorrect size event when entering fullscreen with client side decorations.
|
||||||
|
- On Wayland, fix `resizable` attribute not being applied properly on startup.
|
||||||
|
- On Wayland, fix disabled repeat rate not being handled.
|
||||||
|
- On Wayland, fix decoration buttons not working after tty switch.
|
||||||
|
- On Wayland, fix scaling not being applied on output re-enable.
|
||||||
|
- On Wayland, fix crash when `XCURSOR_SIZE` is `0`.
|
||||||
|
- On Wayland, fix pointer getting created in some cases without pointer capability.
|
||||||
|
- On Wayland, on kwin, fix space between window and decorations on startup.
|
||||||
|
- **Breaking:** On Wayland, `Theme` trait was reworked.
|
||||||
|
- On Wayland, disable maximize button for non-resizable window.
|
||||||
|
- On Wayland, added support for `set_ime_position`.
|
||||||
|
- On Wayland, fix crash on startup since GNOME 3.37.90.
|
||||||
|
- On X11, fix incorrect modifiers state on startup.
|
||||||
|
|
||||||
|
# 0.22.2 (2020-05-16)
|
||||||
|
|
||||||
|
- Added Clone implementation for 'static events.
|
||||||
|
- On Windows, fix window intermittently hanging when `ControlFlow` was set to `Poll`.
|
||||||
|
- On Windows, fix `WindowBuilder::with_maximized` being ignored.
|
||||||
|
- On Android, minimal platform support.
|
||||||
|
- On iOS, touch positions are now properly converted to physical pixels.
|
||||||
|
- On macOS, updated core-* dependencies and cocoa
|
||||||
|
|
||||||
|
# 0.22.1 (2020-04-16)
|
||||||
|
|
||||||
|
- On X11, fix `ResumeTimeReached` being fired too early.
|
||||||
|
- On Web, replaced zero timeout for `ControlFlow::Poll` with `requestAnimationFrame`
|
||||||
|
- On Web, fix a possible panic during event handling
|
||||||
|
- On macOS, fix `EventLoopProxy` leaking memory for every instance.
|
||||||
|
|
||||||
|
# 0.22.0 (2020-03-09)
|
||||||
|
|
||||||
|
- On Windows, fix minor timing issue in wait_until_time_or_msg
|
||||||
|
- On Windows, rework handling of request_redraw() to address panics.
|
||||||
|
- On macOS, fix `set_simple_screen` to remember frame excluding title bar.
|
||||||
|
- On Wayland, fix coordinates in touch events when scale factor isn't 1.
|
||||||
|
- On Wayland, fix color from `close_button_icon_color` not applying.
|
||||||
|
- Ignore locale if unsupported by X11 backend
|
||||||
|
- On Wayland, Add HiDPI cursor support
|
||||||
|
- On Web, add the ability to query "Light" or "Dark" system theme send `ThemeChanged` on change.
|
||||||
|
- Fix `Event::to_static` returning `None` for user events.
|
||||||
|
- On Wayland, Hide CSD for fullscreen windows.
|
||||||
|
- On Windows, ignore spurious mouse move messages.
|
||||||
|
- **Breaking:** Move `ModifiersChanged` variant from `DeviceEvent` to `WindowEvent`.
|
||||||
|
- On Windows, add `IconExtWindows` trait which exposes creating an `Icon` from an external file or embedded resource
|
||||||
|
- Add `BadIcon::OsError` variant for when OS icon functionality fails
|
||||||
|
- On Windows, fix crash at startup on systems that do not properly support Windows' Dark Mode
|
||||||
|
- Revert On macOS, fix not sending ReceivedCharacter event for specific keys combinations.
|
||||||
|
- on macOS, fix incorrect ReceivedCharacter events for some key combinations.
|
||||||
|
- **Breaking:** Use `i32` instead of `u32` for position type in `WindowEvent::Moved`.
|
||||||
|
- On macOS, a mouse motion event is now generated before every mouse click.
|
||||||
|
|
||||||
# 0.21.0 (2020-02-04)
|
# 0.21.0 (2020-02-04)
|
||||||
|
|
||||||
- On Windows, fixed "error: linking with `link.exe` failed: exit code: 1120" error on older versions of windows.
|
- On Windows, fixed "error: linking with `link.exe` failed: exit code: 1120" error on older versions of windows.
|
||||||
@@ -20,15 +724,14 @@
|
|||||||
# 0.20.0 (2020-01-05)
|
# 0.20.0 (2020-01-05)
|
||||||
|
|
||||||
- On X11, fix `ModifiersChanged` emitting incorrect modifier change events
|
- On X11, fix `ModifiersChanged` emitting incorrect modifier change events
|
||||||
|
|
||||||
- **Breaking**: Overhaul how Winit handles DPI:
|
- **Breaking**: Overhaul how Winit handles DPI:
|
||||||
+ Window functions and events now return `PhysicalSize` instead of `LogicalSize`.
|
- Window functions and events now return `PhysicalSize` instead of `LogicalSize`.
|
||||||
+ Functions that take `Size` or `Position` types can now take either `Logical` or `Physical` types.
|
- Functions that take `Size` or `Position` types can now take either `Logical` or `Physical` types.
|
||||||
+ `hidpi_factor` has been renamed to `scale_factor`.
|
- `hidpi_factor` has been renamed to `scale_factor`.
|
||||||
+ `HiDpiFactorChanged` has been renamed to `ScaleFactorChanged`, and lets you control how the OS
|
- `HiDpiFactorChanged` has been renamed to `ScaleFactorChanged`, and lets you control how the OS
|
||||||
resizes the window in response to the change.
|
resizes the window in response to the change.
|
||||||
+ On X11, deprecate `WINIT_HIDPI_FACTOR` environment variable in favor of `WINIT_X11_SCALE_FACTOR`.
|
- On X11, deprecate `WINIT_HIDPI_FACTOR` environment variable in favor of `WINIT_X11_SCALE_FACTOR`.
|
||||||
+ `Size` and `Position` types are now generic over their exact pixel type.
|
- `Size` and `Position` types are now generic over their exact pixel type.
|
||||||
|
|
||||||
# 0.20.0 Alpha 6 (2020-01-03)
|
# 0.20.0 Alpha 6 (2020-01-03)
|
||||||
|
|
||||||
@@ -133,7 +836,7 @@
|
|||||||
- `Window::set_fullscreen` now takes `Option<Fullscreen>` where `Fullscreen`
|
- `Window::set_fullscreen` now takes `Option<Fullscreen>` where `Fullscreen`
|
||||||
consists of `Fullscreen::Exclusive(VideoMode)` and
|
consists of `Fullscreen::Exclusive(VideoMode)` and
|
||||||
`Fullscreen::Borderless(MonitorHandle)` variants.
|
`Fullscreen::Borderless(MonitorHandle)` variants.
|
||||||
- Adds support for exclusive fullscreen mode.
|
- Adds support for exclusive fullscreen mode.
|
||||||
- On iOS, add support for hiding the home indicator.
|
- On iOS, add support for hiding the home indicator.
|
||||||
- On iOS, add support for deferring system gestures.
|
- On iOS, add support for deferring system gestures.
|
||||||
- On iOS, fix a crash that occurred while acquiring a monitor's name.
|
- On iOS, fix a crash that occurred while acquiring a monitor's name.
|
||||||
@@ -332,7 +1035,7 @@ and `WindowEvent::HoveredFile`.
|
|||||||
# Version 0.16.1 (2018-07-02)
|
# Version 0.16.1 (2018-07-02)
|
||||||
|
|
||||||
- Added logging through `log`. Logging will become more extensive over time.
|
- Added logging through `log`. Logging will become more extensive over time.
|
||||||
- On X11 and Windows, the window's DPI factor is guessed before creating the window. This *greatly* cuts back on unsightly auto-resizing that would occur immediately after window creation.
|
- On X11 and Windows, the window's DPI factor is guessed before creating the window. This _greatly_ cuts back on unsightly auto-resizing that would occur immediately after window creation.
|
||||||
- Fixed X11 backend compilation for environments where `c_char` is unsigned.
|
- Fixed X11 backend compilation for environments where `c_char` is unsigned.
|
||||||
|
|
||||||
# Version 0.16.0 (2018-06-25)
|
# Version 0.16.0 (2018-06-25)
|
||||||
@@ -482,7 +1185,7 @@ and `WindowEvent::HoveredFile`.
|
|||||||
|
|
||||||
# Version 0.10.1 (2018-02-05)
|
# Version 0.10.1 (2018-02-05)
|
||||||
|
|
||||||
*Yanked*
|
_Yanked_
|
||||||
|
|
||||||
# Version 0.10.0 (2017-12-27)
|
# Version 0.10.0 (2017-12-27)
|
||||||
|
|
||||||
|
|||||||
@@ -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,23 +20,52 @@ 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
|
||||||
maintainers and future contributors don't have to try to guess what your code is supposed to do
|
maintainers and future contributors don't have to try to guess what your code is supposed to do
|
||||||
- your PR adds an entry to the changelog file if the introduced change is relevant to winit users
|
- your PR adds an entry to the changelog file if the introduced change is relevant to winit users.
|
||||||
|
|
||||||
|
You needn't worry about the added entry causing conflicts, the maintainer that merges the PR will
|
||||||
|
handle those for you when merging (see below).
|
||||||
- if your PR affects the platform compatibility of one or more features or adds another feature, the
|
- if your PR affects the platform compatibility of one or more features or adds another feature, the
|
||||||
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).
|
||||||
|
|
||||||
|
Once your PR is deemed ready, the merging maintainer will take care of resolving conflicts in
|
||||||
|
`CHANGELOG.md` (but you must resolve other conflicts yourself). Doing this requires that you check the
|
||||||
|
"give contributors write access to the branch" checkbox when creating the PR.
|
||||||
|
|
||||||
## 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!
|
||||||
|
|
||||||
|
## Release process
|
||||||
|
|
||||||
|
Given that winit is a widely used library, we should be able to make a patch
|
||||||
|
releases at any time we want without blocking the development of new features.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
The exact steps for an exemplary `0.2.0` release might look like this:
|
||||||
|
1. Initially, the version on the latest master is `0.1.0`
|
||||||
|
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
|
||||||
|
|||||||
134
Cargo.toml
134
Cargo.toml
@@ -1,122 +1,16 @@
|
|||||||
[package]
|
[workspace]
|
||||||
name = "winit"
|
members = [
|
||||||
version = "0.21.0"
|
"run-wasm",
|
||||||
authors = ["The winit contributors", "Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
"winit",
|
||||||
description = "Cross-platform window creation library."
|
"winit-core"
|
||||||
edition = "2018"
|
|
||||||
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"]
|
|
||||||
|
|
||||||
[features]
|
|
||||||
web-sys = ["web_sys", "wasm-bindgen", "instant/wasm-bindgen"]
|
|
||||||
stdweb = ["std_web", "instant/stdweb"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
instant = "0.1"
|
|
||||||
lazy_static = "1"
|
|
||||||
libc = "0.2.64"
|
|
||||||
log = "0.4"
|
|
||||||
serde = { version = "1", optional = true, features = ["serde_derive"] }
|
|
||||||
raw-window-handle = "0.3"
|
|
||||||
bitflags = "1"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
image = "0.21"
|
|
||||||
simple_logger = "1"
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "android")'.dependencies.android_glue]
|
|
||||||
version = "0.2"
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "ios")'.dependencies]
|
|
||||||
objc = "0.2.3"
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
|
||||||
cocoa = "0.19.1"
|
|
||||||
core-foundation = "0.6"
|
|
||||||
core-graphics = "0.17.3"
|
|
||||||
dispatch = "0.2.0"
|
|
||||||
objc = "0.2.6"
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies.core-video-sys]
|
|
||||||
version = "0.1.3"
|
|
||||||
default_features = false
|
|
||||||
features = ["display_link"]
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "windows")'.dependencies.winapi]
|
|
||||||
version = "0.3.6"
|
|
||||||
features = [
|
|
||||||
"combaseapi",
|
|
||||||
"commctrl",
|
|
||||||
"dwmapi",
|
|
||||||
"errhandlingapi",
|
|
||||||
"hidusage",
|
|
||||||
"libloaderapi",
|
|
||||||
"objbase",
|
|
||||||
"ole2",
|
|
||||||
"processthreadsapi",
|
|
||||||
"shellapi",
|
|
||||||
"shellscalingapi",
|
|
||||||
"shobjidl_core",
|
|
||||||
"unknwnbase",
|
|
||||||
"winbase",
|
|
||||||
"windowsx",
|
|
||||||
"winerror",
|
|
||||||
"wingdi",
|
|
||||||
"winnt",
|
|
||||||
"winuser",
|
|
||||||
]
|
]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd"))'.dependencies]
|
[workspace.dependencies]
|
||||||
wayland-client = { version = "0.23.0", features = [ "dlopen", "egl", "cursor", "eventloop"] }
|
bitflags = "2"
|
||||||
mio = "0.6"
|
cfg_aliases = "0.2.0"
|
||||||
mio-extras = "2.0"
|
cursor-icon = "1.1.0"
|
||||||
smithay-client-toolkit = "0.6"
|
serde = { version = "1", features = ["serde_derive"] }
|
||||||
x11-dl = "2.18.3"
|
smol_str = "0.2.0"
|
||||||
percent-encoding = "2.0"
|
web-time = "1"
|
||||||
|
winit-core = { path = "./winit-core", default-features = false, features = ["std"] }
|
||||||
[target.'cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "windows"))'.dependencies.parking_lot]
|
|
||||||
version = "0.10"
|
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies.web_sys]
|
|
||||||
package = "web-sys"
|
|
||||||
version = "0.3.22"
|
|
||||||
optional = true
|
|
||||||
features = [
|
|
||||||
'console',
|
|
||||||
'CssStyleDeclaration',
|
|
||||||
'BeforeUnloadEvent',
|
|
||||||
'Document',
|
|
||||||
'DomRect',
|
|
||||||
'Element',
|
|
||||||
'Event',
|
|
||||||
'EventTarget',
|
|
||||||
'FocusEvent',
|
|
||||||
'HtmlCanvasElement',
|
|
||||||
'HtmlElement',
|
|
||||||
'KeyboardEvent',
|
|
||||||
'MouseEvent',
|
|
||||||
'Node',
|
|
||||||
'PointerEvent',
|
|
||||||
'Window',
|
|
||||||
'WheelEvent'
|
|
||||||
]
|
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies.wasm-bindgen]
|
|
||||||
version = "0.2.45"
|
|
||||||
optional = true
|
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies.std_web]
|
|
||||||
package = "stdweb"
|
|
||||||
version = "=0.4.20"
|
|
||||||
optional = true
|
|
||||||
features = ["experimental_features_which_may_break_on_minor_version_bumps"]
|
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
|
||||||
console_log = "0.1"
|
|
||||||
|
|||||||
149
FEATURES.md
149
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
|
||||||
@@ -100,8 +103,11 @@ If your PR makes notable changes to Winit's features, please update this section
|
|||||||
### Input Handling
|
### Input Handling
|
||||||
- **Mouse events**: Generating mouse events associated with pointer motion, click, and scrolling events.
|
- **Mouse events**: Generating mouse events associated with pointer motion, click, and scrolling events.
|
||||||
- **Mouse set location**: Forcibly changing the location of the pointer.
|
- **Mouse set location**: Forcibly changing the location of the pointer.
|
||||||
- **Cursor grab**: Locking the cursor so it cannot exit the client area of a window.
|
- **Cursor locking**: Locking the cursor inside the window so it cannot move.
|
||||||
- **Cursor icon**: Changing the cursor icon, or hiding the cursor.
|
- **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 image**: Changing the cursor to your own image.
|
||||||
|
- **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.
|
||||||
- **Multitouch**: Multi-touch events, including cancellation of a gesture.
|
- **Multitouch**: Multi-touch events, including cancellation of a gesture.
|
||||||
@@ -109,15 +115,22 @@ If your PR makes notable changes to Winit's features, please update this section
|
|||||||
translating keypresses into UTF-8 characters, handling dead keys and IMEs.
|
translating keypresses into UTF-8 characters, handling dead keys and IMEs.
|
||||||
- **Drag & Drop**: Dragging content into winit, detecting when content enters, drops, or if the drop is cancelled.
|
- **Drag & Drop**: Dragging content into winit, detecting when content enters, drops, or if the drop is cancelled.
|
||||||
- **Raw Device Events**: Capturing input from input devices without any OS filtering.
|
- **Raw Device Events**: Capturing input from input devices without any OS filtering.
|
||||||
- **Gamepad/Joystick events**: Capturing input from gampads and joysticks.
|
- **Gamepad/Joystick events**: Capturing input from gamepads and joysticks.
|
||||||
- **Device movement events:**: Capturing input from the device gyroscope and accelerometer.
|
- **Device movement events**: Capturing input from the device gyroscope and accelerometer.
|
||||||
|
|
||||||
## 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
|
||||||
* `WS_EX_NOREDIRECTIONBITMAP` support
|
* `WS_EX_NOREDIRECTIONBITMAP` support
|
||||||
* Theme the title bar according to Windows 10 Dark Mode setting
|
* 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
|
||||||
@@ -126,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
|
||||||
@@ -133,22 +148,22 @@ 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
|
||||||
|
* 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)
|
||||||
|
|
||||||
@@ -157,65 +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 |✔️ |✔️ |✔️ |▢[#306] |**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]|✔️ |**N/A**|
|
|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 |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|**N/A**|
|
|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|**N/A** |
|
||||||
|Cursor grab |✔️ |▢[#165] |▢[#242] |✔️ |**N/A**|**N/A**|❓ |
|
|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |❌ |
|
||||||
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|
|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|
||||||
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |
|
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|
||||||
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |
|
|Cursor image |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |**N/A** |
|
||||||
|Multitouch |✔️ |❌ |✔️ |✔️ |❓ |✔️ |✔️ |
|
|Cursor hittest |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |❌ |
|
||||||
|Keyboard events |✔️ |✔️ |✔️ |✔️ |❓ |❌ |✔️ |
|
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |✔️ |**N/A** |
|
||||||
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |❌[#306] |**N/A**|**N/A**|❓ |
|
|Touch pressure |✔️ |❌ |❌ |❌ |❌ |✔️ |✔️ |**N/A** |
|
||||||
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |
|
|Multitouch |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |**N/A** |
|
||||||
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |
|
|Keyboard events |✔️ |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |✔️ |
|
||||||
|Device movement events |❓ |❓ |❓ |❓ |❌ |❌ |❓ |
|
|Drag & Drop |▢[#720] |▢[#720] |▢[#720] |▢[#720] |**N/A**|**N/A**|❓ |**N/A** |
|
||||||
|
|Raw Device Events |▢[#750] |▢[#750] |▢[#750] |❌ |❌ |❌ |❓ |**N/A** |
|
||||||
|
|Gamepad/Joystick events |❌[#804] |❌ |❌ |❌ |❌ |❌ |❓ |**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
|
||||||
@@ -230,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
|
|
||||||
|
|||||||
@@ -1,14 +1,25 @@
|
|||||||
# Hall of Champions
|
# Hall of Champions
|
||||||
|
|
||||||
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
|
||||||
|
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
|
||||||
|
vastly more sustainable era of winit.
|
||||||
|
* [@goddessfreya]: For selflessly taking over maintainership of glutin and her
|
||||||
|
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
|
||||||
[@francesca64]: https://github.com/francesca64
|
[@francesca64]: https://github.com/francesca64
|
||||||
|
[@Osspial]: https://github.com/Osspial
|
||||||
|
[@goddessfreya]: https://github.com/goddessfreya
|
||||||
|
[@ArturKovacs]: https://github.com/ArturKovacs
|
||||||
|
|||||||
195
README.md
195
README.md
@@ -2,78 +2,187 @@
|
|||||||
|
|
||||||
[](https://crates.io/crates/winit)
|
[](https://crates.io/crates/winit)
|
||||||
[](https://docs.rs/winit)
|
[](https://docs.rs/winit)
|
||||||
[](https://travis-ci.org/rust-windowing/winit)
|
[](https://ci.appveyor.com/project/Osspial/winit/branch/master)
|
)](https://rust-windowing.github.io/winit/winit/index.html)
|
||||||
|
[](https://github.com/rust-windowing/winit/actions)
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
winit = "0.21.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).
|
||||||
|
|
||||||
[](http://webchat.freenode.net?channels=%23glutin&uio=MTY9dHJ1ZSYyPXRydWUmND10cnVlJjExPTE4NSYxMj10cnVlJjE1PXRydWU7a)
|
The maintainers have a meeting every friday at UTC 14. The meeting notes can be found [here](https://hackmd.io/@winit-meetings).
|
||||||
[](https://matrix.to/#/#Glutin:matrix.org)
|
|
||||||
[](https://gitter.im/tomaka/glutin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
|
||||||
|
|
||||||
## 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:
|
||||||
* `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde).
|
* `serde`: Enables serialization/deserialization of certain types with [Serde](https://crates.io/crates/serde).
|
||||||
|
* `x11` (enabled by default): On Unix platform, compiles with the X11 backend
|
||||||
|
* `wayland` (enabled by default): On Unix platform, compiles with the Wayland backend
|
||||||
|
* `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
|
||||||
|
|
||||||
#### WebAssembly
|
#### Wayland
|
||||||
|
|
||||||
Building a binary will yield a `.js` file. In order to use it in an HTML file, you need to:
|
Note that windows don't appear on Wayland until you draw/present to them.
|
||||||
|
|
||||||
- Put a `<canvas id="my_id"></canvas>` element somewhere. A canvas corresponds to a winit "window".
|
#### Web
|
||||||
- Write a Javascript code that creates a global variable named `Module`. Set `Module.canvas` to
|
|
||||||
the element of the `<canvas>` element (in the example you would retrieve it via `document.getElementById("my_id")`).
|
To run the web example: `cargo run-wasm --example web`
|
||||||
More information [here](https://kripken.github.io/emscripten-site/docs/api_reference/module.html).
|
|
||||||
- Make sure that you insert the `.js` file generated by Rust after the `Module` variable is created.
|
Winit supports compiling to the `wasm32-unknown-unknown` target with `web-sys`.
|
||||||
|
|
||||||
|
On the web platform, a Winit window is backed by a `<canvas>` element. You can
|
||||||
|
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
|
||||||
|
insert it into the DOM yourself.
|
||||||
|
|
||||||
|
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
|
||||||
|
book].
|
||||||
|
|
||||||
|
[web with_canvas]: https://docs.rs/winit/latest/wasm32-unknown-unknown/winit/platform/web/trait.WindowBuilderExtWebSys.html#tymethod.with_canvas
|
||||||
|
[web canvas getter]: https://docs.rs/winit/latest/wasm32-unknown-unknown/winit/platform/web/trait.WindowExtWebSys.html#tymethod.canvas
|
||||||
|
[web example]: ./examples/web.rs
|
||||||
|
[Rust and WebAssembly book]: https://rustwasm.github.io/book/
|
||||||
|
|
||||||
|
#### Android
|
||||||
|
|
||||||
|
The Android backend builds on (and exposes types from) the [`ndk`](https://docs.rs/ndk/latest/ndk/) crate.
|
||||||
|
|
||||||
|
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 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)).
|
||||||
|
|
||||||
|
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
|
||||||
|
application's main entry point. If Cargo resolves multiple versions, they will
|
||||||
|
clash.
|
||||||
|
|
||||||
|
`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
|
||||||
|
[lib]
|
||||||
|
name = "main"
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
```
|
||||||
|
|
||||||
|
All Android applications are based on an `Activity` subclass, and the
|
||||||
|
`android-activity` crate is designed to support different choices for this base
|
||||||
|
class. Your application _must_ specify the base class it needs via a feature flag:
|
||||||
|
|
||||||
|
| 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
|
||||||
|
|
||||||
|
A lot of functionality expects the application to be ready before you start
|
||||||
|
doing anything; this includes creating windows, fetching monitors, drawing,
|
||||||
|
and so on, see issues [#2238], [#2051] and [#2087].
|
||||||
|
|
||||||
|
If you encounter problems, you should try doing your initialization inside
|
||||||
|
`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
|
||||||
|
[#2051]: https://github.com/rust-windowing/winit/issues/2051
|
||||||
|
[#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,88 +0,0 @@
|
|||||||
use winit::{
|
|
||||||
event::{ElementState, Event, KeyboardInput, WindowEvent},
|
|
||||||
event_loop::{ControlFlow, EventLoop},
|
|
||||||
window::{CursorIcon, WindowBuilder},
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
simple_logger::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 = ControlFlow::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 = ControlFlow::Exit;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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,55 +0,0 @@
|
|||||||
use winit::{
|
|
||||||
event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent},
|
|
||||||
event_loop::{ControlFlow, EventLoop},
|
|
||||||
window::WindowBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
simple_logger::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 = ControlFlow::Wait;
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent { event, .. } => match event {
|
|
||||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
|
||||||
WindowEvent::KeyboardInput {
|
|
||||||
input:
|
|
||||||
KeyboardInput {
|
|
||||||
state: ElementState::Released,
|
|
||||||
virtual_keycode: Some(key),
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
use winit::event::VirtualKeyCode::*;
|
|
||||||
match key {
|
|
||||||
Escape => *control_flow = ControlFlow::Exit,
|
|
||||||
G => window.set_cursor_grab(!modifiers.shift()).unwrap(),
|
|
||||||
H => window.set_cursor_visible(modifiers.shift()),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
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),
|
|
||||||
},
|
|
||||||
DeviceEvent::ModifiersChanged(m) => modifiers = m,
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
use std::io::{stdin, stdout, Write};
|
|
||||||
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
|
|
||||||
use winit::event_loop::{ControlFlow, EventLoop};
|
|
||||||
use winit::monitor::{MonitorHandle, VideoMode};
|
|
||||||
use winit::window::{Fullscreen, WindowBuilder};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
simple_logger::init().unwrap();
|
|
||||||
let event_loop = EventLoop::new();
|
|
||||||
|
|
||||||
print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless: ");
|
|
||||||
stdout().flush().unwrap();
|
|
||||||
|
|
||||||
let mut num = String::new();
|
|
||||||
stdin().read_line(&mut num).unwrap();
|
|
||||||
let num = num.trim().parse().ok().expect("Please enter a number");
|
|
||||||
|
|
||||||
let fullscreen = Some(match num {
|
|
||||||
1 => Fullscreen::Exclusive(prompt_for_video_mode(&prompt_for_monitor(&event_loop))),
|
|
||||||
2 => Fullscreen::Borderless(prompt_for_monitor(&event_loop)),
|
|
||||||
_ => panic!("Please enter a valid number"),
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut is_maximized = false;
|
|
||||||
let mut decorations = true;
|
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
|
||||||
.with_title("Hello world!")
|
|
||||||
.with_fullscreen(fullscreen.clone())
|
|
||||||
.build(&event_loop)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
|
||||||
*control_flow = ControlFlow::Wait;
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent { event, .. } => match event {
|
|
||||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
|
||||||
WindowEvent::KeyboardInput {
|
|
||||||
input:
|
|
||||||
KeyboardInput {
|
|
||||||
virtual_keycode: Some(virtual_code),
|
|
||||||
state,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
|
||||||
} => match (virtual_code, state) {
|
|
||||||
(VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit,
|
|
||||||
(VirtualKeyCode::F, ElementState::Pressed) => {
|
|
||||||
if window.fullscreen().is_some() {
|
|
||||||
window.set_fullscreen(None);
|
|
||||||
} else {
|
|
||||||
window.set_fullscreen(fullscreen.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(VirtualKeyCode::S, ElementState::Pressed) => {
|
|
||||||
println!("window.fullscreen {:?}", window.fullscreen());
|
|
||||||
}
|
|
||||||
(VirtualKeyCode::M, ElementState::Pressed) => {
|
|
||||||
is_maximized = !is_maximized;
|
|
||||||
window.set_maximized(is_maximized);
|
|
||||||
}
|
|
||||||
(VirtualKeyCode::D, ElementState::Pressed) => {
|
|
||||||
decorations = !decorations;
|
|
||||||
window.set_decorations(decorations);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enumerate monitors and prompt user to choose one
|
|
||||||
fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle {
|
|
||||||
for (num, monitor) in event_loop.available_monitors().enumerate() {
|
|
||||||
println!("Monitor #{}: {:?}", num, monitor.name());
|
|
||||||
}
|
|
||||||
|
|
||||||
print!("Please write the number of the monitor to use: ");
|
|
||||||
stdout().flush().unwrap();
|
|
||||||
|
|
||||||
let mut num = String::new();
|
|
||||||
stdin().read_line(&mut num).unwrap();
|
|
||||||
let num = num.trim().parse().ok().expect("Please enter a number");
|
|
||||||
let monitor = event_loop
|
|
||||||
.available_monitors()
|
|
||||||
.nth(num)
|
|
||||||
.expect("Please enter a valid ID");
|
|
||||||
|
|
||||||
println!("Using {:?}", monitor.name());
|
|
||||||
|
|
||||||
monitor
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prompt_for_video_mode(monitor: &MonitorHandle) -> VideoMode {
|
|
||||||
for (i, video_mode) in monitor.video_modes().enumerate() {
|
|
||||||
println!("Video mode #{}: {}", i, video_mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
print!("Please write the number of the video mode to use: ");
|
|
||||||
stdout().flush().unwrap();
|
|
||||||
|
|
||||||
let mut num = String::new();
|
|
||||||
stdin().read_line(&mut num).unwrap();
|
|
||||||
let num = num.trim().parse().ok().expect("Please enter a number");
|
|
||||||
let video_mode = monitor
|
|
||||||
.video_modes()
|
|
||||||
.nth(num)
|
|
||||||
.expect("Please enter a valid ID");
|
|
||||||
|
|
||||||
println!("Using {}", video_mode);
|
|
||||||
|
|
||||||
video_mode
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
use winit::{
|
|
||||||
event::{Event, KeyboardInput, WindowEvent},
|
|
||||||
event_loop::{ControlFlow, EventLoop},
|
|
||||||
window::WindowBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
simple_logger::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 = ControlFlow::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 = ControlFlow::Exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
N => {
|
|
||||||
if close_requested {
|
|
||||||
println!("Your window will continue to stay by your side.");
|
|
||||||
close_requested = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
use winit::{
|
|
||||||
dpi::LogicalSize,
|
|
||||||
event::{Event, WindowEvent},
|
|
||||||
event_loop::{ControlFlow, EventLoop},
|
|
||||||
window::WindowBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
simple_logger::init().unwrap();
|
|
||||||
let event_loop = EventLoop::new();
|
|
||||||
|
|
||||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
|
||||||
|
|
||||||
window.set_min_inner_size(Some(LogicalSize::new(400.0, 200.0)));
|
|
||||||
window.set_max_inner_size(Some(LogicalSize::new(800.0, 400.0)));
|
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
|
||||||
*control_flow = ControlFlow::Wait;
|
|
||||||
println!("{:?}", event);
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::CloseRequested,
|
|
||||||
..
|
|
||||||
} => *control_flow = ControlFlow::Exit,
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
extern crate winit;
|
|
||||||
|
|
||||||
use winit::event::{Event, VirtualKeyCode, WindowEvent};
|
|
||||||
use winit::event_loop::{ControlFlow, EventLoop};
|
|
||||||
use winit::window::WindowBuilder;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
simple_logger::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| {
|
|
||||||
*control_flow = ControlFlow::Wait;
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::CloseRequested,
|
|
||||||
..
|
|
||||||
} => *control_flow = ControlFlow::Exit,
|
|
||||||
|
|
||||||
// Keyboard input event to handle minimize via a hotkey
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::KeyboardInput { input, .. },
|
|
||||||
window_id,
|
|
||||||
} => {
|
|
||||||
if window_id == window.id() {
|
|
||||||
// Pressing the 'M' key will minimize the window
|
|
||||||
if input.virtual_keycode == Some(VirtualKeyCode::M) {
|
|
||||||
window.set_minimized(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
use winit::{event_loop::EventLoop, window::WindowBuilder};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
simple_logger::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,185 +0,0 @@
|
|||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
fn main() {
|
|
||||||
use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
|
|
||||||
|
|
||||||
use winit::{
|
|
||||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
|
||||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
|
||||||
event_loop::{ControlFlow, EventLoop},
|
|
||||||
window::{CursorIcon, Fullscreen, WindowBuilder},
|
|
||||||
};
|
|
||||||
|
|
||||||
const WINDOW_COUNT: usize = 3;
|
|
||||||
const WINDOW_SIZE: PhysicalSize<u32> = PhysicalSize::new(600, 400);
|
|
||||||
|
|
||||||
simple_logger::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().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.iter().cloned().nth(video_mode_id);
|
|
||||||
video_modes = window.current_monitor().video_modes().collect();
|
|
||||||
video_mode_id = video_mode_id.min(video_modes.len());
|
|
||||||
let video_mode = video_modes.iter().nth(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.iter().nth(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.iter().nth(video_mode_id).unwrap()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
F => window.set_fullscreen(match (state, modifiers.alt()) {
|
|
||||||
(true, false) => {
|
|
||||||
Some(Fullscreen::Borderless(window.current_monitor()))
|
|
||||||
}
|
|
||||||
(true, true) => Some(Fullscreen::Exclusive(
|
|
||||||
video_modes.iter().nth(video_mode_id).unwrap().clone(),
|
|
||||||
)),
|
|
||||||
(false, _) => None,
|
|
||||||
}),
|
|
||||||
G => window.set_cursor_grab(state).unwrap(),
|
|
||||||
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| {
|
|
||||||
*control_flow = match !window_senders.is_empty() {
|
|
||||||
true => ControlFlow::Wait,
|
|
||||||
false => ControlFlow::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,51 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use winit::{
|
|
||||||
event::{ElementState, Event, KeyboardInput, WindowEvent},
|
|
||||||
event_loop::{ControlFlow, EventLoop},
|
|
||||||
window::Window,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
simple_logger::init().unwrap();
|
|
||||||
let event_loop = EventLoop::new();
|
|
||||||
|
|
||||||
let mut windows = HashMap::new();
|
|
||||||
for _ in 0..3 {
|
|
||||||
let window = Window::new(&event_loop).unwrap();
|
|
||||||
windows.insert(window.id(), window);
|
|
||||||
}
|
|
||||||
|
|
||||||
event_loop.run(move |event, event_loop, control_flow| {
|
|
||||||
*control_flow = ControlFlow::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 = ControlFlow::Exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WindowEvent::KeyboardInput {
|
|
||||||
input:
|
|
||||||
KeyboardInput {
|
|
||||||
state: ElementState::Pressed,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let window = Window::new(&event_loop).unwrap();
|
|
||||||
windows.insert(window.id(), window);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
use winit::{
|
|
||||||
event::{ElementState, Event, WindowEvent},
|
|
||||||
event_loop::{ControlFlow, EventLoop},
|
|
||||||
window::WindowBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
simple_logger::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 = ControlFlow::Wait;
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent { event, .. } => match event {
|
|
||||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
|
||||||
WindowEvent::MouseInput {
|
|
||||||
state: ElementState::Released,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
Event::RedrawRequested(_) => {
|
|
||||||
println!("\nredrawing!\n");
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
use winit::{
|
|
||||||
dpi::LogicalSize,
|
|
||||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
|
||||||
event_loop::{ControlFlow, EventLoop},
|
|
||||||
window::WindowBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
simple_logger::init().unwrap();
|
|
||||||
let event_loop = EventLoop::new();
|
|
||||||
|
|
||||||
let mut resizable = false;
|
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
|
||||||
.with_title("Hit space to toggle resizability.")
|
|
||||||
.with_inner_size(LogicalSize::new(400.0, 200.0))
|
|
||||||
.with_resizable(resizable)
|
|
||||||
.build(&event_loop)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
|
||||||
*control_flow = ControlFlow::Wait;
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent { event, .. } => match event {
|
|
||||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
|
||||||
WindowEvent::KeyboardInput {
|
|
||||||
input:
|
|
||||||
KeyboardInput {
|
|
||||||
virtual_keycode: Some(VirtualKeyCode::Space),
|
|
||||||
state: ElementState::Released,
|
|
||||||
..
|
|
||||||
},
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
resizable = !resizable;
|
|
||||||
println!("Resizable: {}", resizable);
|
|
||||||
window.set_resizable(resizable);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
_ => (),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
use instant::Instant;
|
|
||||||
use std::time::Duration;
|
|
||||||
use winit::{
|
|
||||||
event::{Event, StartCause, WindowEvent},
|
|
||||||
event_loop::{ControlFlow, EventLoop},
|
|
||||||
window::WindowBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
simple_logger::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 = ControlFlow::WaitUntil(Instant::now() + timer_length)
|
|
||||||
}
|
|
||||||
Event::NewEvents(StartCause::ResumeTimeReached { .. }) => {
|
|
||||||
*control_flow = ControlFlow::WaitUntil(Instant::now() + timer_length);
|
|
||||||
println!("\nTimer\n");
|
|
||||||
}
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::CloseRequested,
|
|
||||||
..
|
|
||||||
} => *control_flow = ControlFlow::Exit,
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
use winit::{
|
|
||||||
event::{Event, WindowEvent},
|
|
||||||
event_loop::{ControlFlow, EventLoop},
|
|
||||||
window::WindowBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
simple_logger::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 = ControlFlow::Wait;
|
|
||||||
println!("{:?}", event);
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::CloseRequested,
|
|
||||||
..
|
|
||||||
} => *control_flow = ControlFlow::Exit,
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
use winit::event_loop::EventLoop;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
simple_logger::init().unwrap();
|
|
||||||
let event_loop = EventLoop::new();
|
|
||||||
let monitor = event_loop.primary_monitor();
|
|
||||||
|
|
||||||
println!("Listing available video modes:");
|
|
||||||
|
|
||||||
for mode in monitor.video_modes() {
|
|
||||||
println!("{}", mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
use winit::{
|
|
||||||
event::{Event, WindowEvent},
|
|
||||||
event_loop::{ControlFlow, 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(feature = "web-sys")]
|
|
||||||
{
|
|
||||||
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();
|
|
||||||
|
|
||||||
body.append_child(&canvas)
|
|
||||||
.expect("Append canvas to HTML body");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "stdweb")]
|
|
||||||
{
|
|
||||||
use std_web::web::INode;
|
|
||||||
use winit::platform::web::WindowExtStdweb;
|
|
||||||
|
|
||||||
let canvas = window.canvas();
|
|
||||||
|
|
||||||
let document = std_web::web::document();
|
|
||||||
let body: std_web::web::Node = document.body().expect("Get HTML body").into();
|
|
||||||
|
|
||||||
body.append_child(&canvas);
|
|
||||||
}
|
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
|
||||||
*control_flow = ControlFlow::Wait;
|
|
||||||
|
|
||||||
#[cfg(feature = "web-sys")]
|
|
||||||
log::debug!("{:?}", event);
|
|
||||||
|
|
||||||
#[cfg(feature = "stdweb")]
|
|
||||||
std_web::console!(log, "%s", format!("{:?}", event));
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::CloseRequested,
|
|
||||||
window_id,
|
|
||||||
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
|
|
||||||
Event::MainEventsCleared => {
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "web-sys")]
|
|
||||||
mod wasm {
|
|
||||||
use wasm_bindgen::prelude::*;
|
|
||||||
|
|
||||||
#[wasm_bindgen(start)]
|
|
||||||
pub fn run() {
|
|
||||||
console_log::init_with_level(log::Level::Debug);
|
|
||||||
|
|
||||||
super::main();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
use winit::{
|
|
||||||
event::{Event, WindowEvent},
|
|
||||||
event_loop::{ControlFlow, EventLoop},
|
|
||||||
window::WindowBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
simple_logger::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 = ControlFlow::Wait;
|
|
||||||
println!("{:?}", event);
|
|
||||||
|
|
||||||
match event {
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::CloseRequested,
|
|
||||||
window_id,
|
|
||||||
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
|
|
||||||
Event::MainEventsCleared => {
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
// This example is used by developers to test various window functions.
|
|
||||||
|
|
||||||
use winit::{
|
|
||||||
dpi::{LogicalSize, PhysicalSize},
|
|
||||||
event::{DeviceEvent, ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
|
||||||
event_loop::{ControlFlow, EventLoop},
|
|
||||||
window::{Fullscreen, WindowBuilder},
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
simple_logger::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!(" (M) Toggle minimized");
|
|
||||||
eprintln!(" (Q) Quit event loop");
|
|
||||||
eprintln!(" (V) Toggle visibility");
|
|
||||||
eprintln!(" (X) Toggle maximized");
|
|
||||||
|
|
||||||
let mut minimized = false;
|
|
||||||
let mut maximized = false;
|
|
||||||
let mut visible = true;
|
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
|
||||||
*control_flow = ControlFlow::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, .. },
|
|
||||||
..
|
|
||||||
} => match 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();
|
|
||||||
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::M => {
|
|
||||||
minimized = !minimized;
|
|
||||||
window.set_minimized(minimized);
|
|
||||||
}
|
|
||||||
VirtualKeyCode::Q => {
|
|
||||||
*control_flow = ControlFlow::Exit;
|
|
||||||
}
|
|
||||||
VirtualKeyCode::V => {
|
|
||||||
visible = !visible;
|
|
||||||
window.set_visible(visible);
|
|
||||||
}
|
|
||||||
VirtualKeyCode::X => {
|
|
||||||
maximized = !maximized;
|
|
||||||
window.set_maximized(maximized);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
Event::WindowEvent {
|
|
||||||
event: WindowEvent::CloseRequested,
|
|
||||||
window_id,
|
|
||||||
} if window_id == window.id() => *control_flow = ControlFlow::Exit,
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
// 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"
|
|
||||||
))]
|
|
||||||
fn main() {
|
|
||||||
use std::{thread::sleep, time::Duration};
|
|
||||||
use winit::{
|
|
||||||
event::{Event, WindowEvent},
|
|
||||||
event_loop::{ControlFlow, EventLoop},
|
|
||||||
platform::desktop::EventLoopExtDesktop,
|
|
||||||
window::WindowBuilder,
|
|
||||||
};
|
|
||||||
let mut event_loop = EventLoop::new();
|
|
||||||
|
|
||||||
simple_logger::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 = ControlFlow::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 = ControlFlow::Exit;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sleep for 1/60 second to simulate rendering
|
|
||||||
println!("rendering");
|
|
||||||
sleep(Duration::from_millis(16));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(target_os = "ios", target_os = "android", target_arch = "wasm32"))]
|
|
||||||
fn main() {
|
|
||||||
println!("This platform doesn't support run_return.");
|
|
||||||
}
|
|
||||||
8
run-wasm/Cargo.toml
Normal file
8
run-wasm/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "run-wasm"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cargo-run-wasm = "0.2.0"
|
||||||
3
run-wasm/src/main.rs
Normal file
3
run-wasm/src/main.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
cargo_run_wasm::run_wasm_with_css("body { margin: 0px; }");
|
||||||
|
}
|
||||||
498
src/dpi.rs
498
src/dpi.rs
@@ -1,498 +0,0 @@
|
|||||||
//! UI scaling is important, so read the docs for this module if you don't want to be confused.
|
|
||||||
//!
|
|
||||||
//! ## Why should I care about UI scaling?
|
|
||||||
//!
|
|
||||||
//! Modern computer screens don't have a consistent relationship between resolution and size.
|
|
||||||
//! 1920x1080 is a common resolution for both desktop and mobile screens, despite mobile screens
|
|
||||||
//! normally being less than a quarter the size of their desktop counterparts. What's more, neither
|
|
||||||
//! desktop nor mobile screens are consistent resolutions within their own size classes - common
|
|
||||||
//! mobile screens range from below 720p to above 1440p, and desktop screens range from 720p to 5K
|
|
||||||
//! and beyond.
|
|
||||||
//!
|
|
||||||
//! Given that, it's a mistake to assume that 2D content will only be displayed on screens with
|
|
||||||
//! a consistent pixel density. If you were to render a 96-pixel-square image on a 1080p screen,
|
|
||||||
//! then render the same image on a similarly-sized 4K screen, the 4K rendition would only take up
|
|
||||||
//! about a quarter of the physical space as it did on the 1080p screen. That issue is especially
|
|
||||||
//! problematic with text rendering, where quarter-sized text becomes a significant legibility
|
|
||||||
//! problem.
|
|
||||||
//!
|
|
||||||
//! Failure to account for the scale factor can create a significantly degraded user experience.
|
|
||||||
//! Most notably, it can make users feel like they have bad eyesight, which will potentially cause
|
|
||||||
//! them to think about growing elderly, resulting in them having an existential crisis. Once users
|
|
||||||
//! enter that state, they will no longer be focused on your application.
|
|
||||||
//!
|
|
||||||
//! ## How should I handle it?
|
|
||||||
//!
|
|
||||||
//! The solution to this problem is to account for the device's *scale factor*. The scale factor is
|
|
||||||
//! the factor UI elements should be scaled by to be consistent with the rest of the user's system -
|
|
||||||
//! for example, a button that's normally 50 pixels across would be 100 pixels across on a device
|
|
||||||
//! with a scale factor of `2.0`, or 75 pixels across with a scale factor of `1.5`.
|
|
||||||
//!
|
|
||||||
//! Many UI systems, such as CSS, expose DPI-dependent units like [points] or [picas]. That's
|
|
||||||
//! usually a mistake, since there's no consistent mapping between the scale factor and the screen's
|
|
||||||
//! actual DPI. Unless you're printing to a physical medium, you should work in scaled pixels rather
|
|
||||||
//! than any DPI-dependent units.
|
|
||||||
//!
|
|
||||||
//! ### Position and Size types
|
|
||||||
//!
|
|
||||||
//! Winit's `Physical(Position|Size)` types correspond with the actual pixels on the device, and the
|
|
||||||
//! `Logical(Position|Size)` types correspond to the physical pixels divided by the scale factor.
|
|
||||||
//! All of Winit's functions return physical types, but can take either logical or physical
|
|
||||||
//! coordinates as input, allowing you to use the most convenient coordinate system for your
|
|
||||||
//! particular application.
|
|
||||||
//!
|
|
||||||
//! Winit's position and size types types are generic over their exact pixel type, `P`, to allow the
|
|
||||||
//! API to have integer precision where appropriate (e.g. most window manipulation functions) and
|
|
||||||
//! floating precision when necessary (e.g. logical sizes for fractional scale factors and touch
|
|
||||||
//! input). If `P` is a floating-point type, please do not cast the values with `as {int}`. Doing so
|
|
||||||
//! will truncate the fractional part of the float, rather than properly round to the nearest
|
|
||||||
//! integer. Use the provided `cast` function or `From`/`Into` conversions, which handle the
|
|
||||||
//! rounding properly. Note that precision loss will still occur when rounding from a float to an
|
|
||||||
//! int, although rounding lessens the problem.
|
|
||||||
//!
|
|
||||||
//! ### Events
|
|
||||||
//!
|
|
||||||
//! Winit will dispatch a [`ScaleFactorChanged`](crate::event::WindowEvent::ScaleFactorChanged)
|
|
||||||
//! event whenever a window's scale factor has changed. This can happen if the user drags their
|
|
||||||
//! window from a standard-resolution monitor to a high-DPI monitor, or if the user changes their
|
|
||||||
//! DPI settings. This gives you a chance to rescale your application's UI elements and adjust how
|
|
||||||
//! the platform changes the window's size to reflect the new scale factor. If a window hasn't
|
|
||||||
//! received a [`ScaleFactorChanged`](crate::event::WindowEvent::ScaleFactorChanged) event,
|
|
||||||
//! then its scale factor is `1.0`.
|
|
||||||
//!
|
|
||||||
//! ## How is the scale factor calculated?
|
|
||||||
//!
|
|
||||||
//! Scale factor is calculated differently on different platforms:
|
|
||||||
//!
|
|
||||||
//! - **Windows:** On Windows 8 and 10, per-monitor scaling is readily configured by users from the
|
|
||||||
//! display settings. While users are free to select any option they want, they're only given a
|
|
||||||
//! selection of "nice" scale factors, i.e. 1.0, 1.25, 1.5... on Windows 7, the scale factor is
|
|
||||||
//! global and changing it requires logging out. See [this article][windows_1] for technical
|
|
||||||
//! details.
|
|
||||||
//! - **macOS:** "retina displays" have a scale factor of 2.0. Otherwise, the scale factor is 1.0.
|
|
||||||
//! Intermediate scale factors are never used. It's possible for any display to use that 2.0 scale
|
|
||||||
//! factor, given the use of the command line.
|
|
||||||
//! - **X11:** Many man-hours have been spent trying to figure out how to handle DPI in X11. Winit
|
|
||||||
//! currently uses a three-pronged approach:
|
|
||||||
//! + Use the value in the `WINIT_X11_SCALE_FACTOR` environment variable, if present.
|
|
||||||
//! + If not present, use the value set in `Xft.dpi` in Xresources.
|
|
||||||
//! + Otherwise, calcuate the scale factor based on the millimeter monitor dimensions provided by XRandR.
|
|
||||||
//!
|
|
||||||
//! If `WINIT_X11_SCALE_FACTOR` is set to `randr`, it'll ignore the `Xft.dpi` field and use the
|
|
||||||
//! XRandR scaling method. Generally speaking, you should try to configure the standard system
|
|
||||||
//! variables to do what you want before resorting to `WINIT_X11_SCALE_FACTOR`.
|
|
||||||
//! - **Wayland:** On Wayland, scale factors are set per-screen by the server, and are always
|
|
||||||
//! integers (most often 1 or 2).
|
|
||||||
//! - **iOS:** Scale factors are set by Apple to the value that best suits the device, and range
|
|
||||||
//! from `1.0` to `3.0`. See [this article][apple_1] and [this article][apple_2] for more
|
|
||||||
//! information.
|
|
||||||
//! - **Android:** Scale factors are set by the manufacturer to the value that best suits the
|
|
||||||
//! device, and range from `1.0` to `4.0`. See [this article][android_1] for more information.
|
|
||||||
//! - **Web:** The scale factor is the ratio between CSS pixels and the physical device pixels.
|
|
||||||
//!
|
|
||||||
//! [points]: https://en.wikipedia.org/wiki/Point_(typography)
|
|
||||||
//! [picas]: https://en.wikipedia.org/wiki/Pica_(typography)
|
|
||||||
//! [windows_1]: https://docs.microsoft.com/en-us/windows/win32/hidpi/high-dpi-desktop-application-development-on-windows
|
|
||||||
//! [apple_1]: https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html
|
|
||||||
//! [apple_2]: https://developer.apple.com/design/human-interface-guidelines/macos/icons-and-images/image-size-and-resolution/
|
|
||||||
//! [android_1]: https://developer.android.com/training/multiscreen/screendensities
|
|
||||||
|
|
||||||
pub trait Pixel: Copy + Into<f64> {
|
|
||||||
fn from_f64(f: f64) -> Self;
|
|
||||||
fn cast<P: Pixel>(self) -> P {
|
|
||||||
P::from_f64(self.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pixel for u8 {
|
|
||||||
fn from_f64(f: f64) -> Self {
|
|
||||||
f.round() as u8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Pixel for u16 {
|
|
||||||
fn from_f64(f: f64) -> Self {
|
|
||||||
f.round() as u16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Pixel for u32 {
|
|
||||||
fn from_f64(f: f64) -> Self {
|
|
||||||
f.round() as u32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Pixel for i8 {
|
|
||||||
fn from_f64(f: f64) -> Self {
|
|
||||||
f.round() as i8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Pixel for i16 {
|
|
||||||
fn from_f64(f: f64) -> Self {
|
|
||||||
f.round() as i16
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Pixel for i32 {
|
|
||||||
fn from_f64(f: f64) -> Self {
|
|
||||||
f.round() as i32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Pixel for f32 {
|
|
||||||
fn from_f64(f: f64) -> Self {
|
|
||||||
f as f32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Pixel for f64 {
|
|
||||||
fn from_f64(f: f64) -> Self {
|
|
||||||
f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks that the scale factor is a normal positive `f64`.
|
|
||||||
///
|
|
||||||
/// All functions that take a scale factor assert that this will return `true`. If you're sourcing scale factors from
|
|
||||||
/// anywhere other than winit, it's recommended to validate them using this function before passing them to winit;
|
|
||||||
/// otherwise, you risk panics.
|
|
||||||
#[inline]
|
|
||||||
pub fn validate_scale_factor(dpi_factor: f64) -> bool {
|
|
||||||
dpi_factor.is_sign_positive() && dpi_factor.is_normal()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A position represented in logical pixels.
|
|
||||||
///
|
|
||||||
/// The position is stored as floats, so please be careful. Casting floats to integers truncates the
|
|
||||||
/// fractional part, which can cause noticable issues. To help with that, an `Into<(i32, i32)>`
|
|
||||||
/// implementation is provided which does the rounding for you.
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub struct LogicalPosition<P> {
|
|
||||||
pub x: P,
|
|
||||||
pub y: P,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P> LogicalPosition<P> {
|
|
||||||
#[inline]
|
|
||||||
pub const fn new(x: P, y: P) -> Self {
|
|
||||||
LogicalPosition { x, y }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel> LogicalPosition<P> {
|
|
||||||
#[inline]
|
|
||||||
pub fn from_physical<T: Into<PhysicalPosition<X>>, X: Pixel>(
|
|
||||||
physical: T,
|
|
||||||
dpi_factor: f64,
|
|
||||||
) -> Self {
|
|
||||||
physical.into().to_logical(dpi_factor)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn to_physical<X: Pixel>(&self, dpi_factor: f64) -> PhysicalPosition<X> {
|
|
||||||
assert!(validate_scale_factor(dpi_factor));
|
|
||||||
let x = self.x.into() * dpi_factor;
|
|
||||||
let y = self.y.into() * dpi_factor;
|
|
||||||
PhysicalPosition::new(x, y).cast()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn cast<X: Pixel>(&self) -> LogicalPosition<X> {
|
|
||||||
LogicalPosition {
|
|
||||||
x: self.x.cast(),
|
|
||||||
y: self.y.cast(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalPosition<P> {
|
|
||||||
fn from((x, y): (X, X)) -> LogicalPosition<P> {
|
|
||||||
LogicalPosition::new(x.cast(), y.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> Into<(X, X)> for LogicalPosition<P> {
|
|
||||||
fn into(self: Self) -> (X, X) {
|
|
||||||
(self.x.cast(), self.y.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalPosition<P> {
|
|
||||||
fn from([x, y]: [X; 2]) -> LogicalPosition<P> {
|
|
||||||
LogicalPosition::new(x.cast(), y.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> Into<[X; 2]> for LogicalPosition<P> {
|
|
||||||
fn into(self: Self) -> [X; 2] {
|
|
||||||
[self.x.cast(), self.y.cast()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A position represented in physical pixels.
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub struct PhysicalPosition<P> {
|
|
||||||
pub x: P,
|
|
||||||
pub y: P,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P> PhysicalPosition<P> {
|
|
||||||
#[inline]
|
|
||||||
pub const fn new(x: P, y: P) -> Self {
|
|
||||||
PhysicalPosition { x, y }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel> PhysicalPosition<P> {
|
|
||||||
#[inline]
|
|
||||||
pub fn from_logical<T: Into<LogicalPosition<X>>, X: Pixel>(
|
|
||||||
logical: T,
|
|
||||||
dpi_factor: f64,
|
|
||||||
) -> Self {
|
|
||||||
logical.into().to_physical(dpi_factor)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn to_logical<X: Pixel>(&self, dpi_factor: f64) -> LogicalPosition<X> {
|
|
||||||
assert!(validate_scale_factor(dpi_factor));
|
|
||||||
let x = self.x.into() / dpi_factor;
|
|
||||||
let y = self.y.into() / dpi_factor;
|
|
||||||
LogicalPosition::new(x, y).cast()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn cast<X: Pixel>(&self) -> PhysicalPosition<X> {
|
|
||||||
PhysicalPosition {
|
|
||||||
x: self.x.cast(),
|
|
||||||
y: self.y.cast(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalPosition<P> {
|
|
||||||
fn from((x, y): (X, X)) -> PhysicalPosition<P> {
|
|
||||||
PhysicalPosition::new(x.cast(), y.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> Into<(X, X)> for PhysicalPosition<P> {
|
|
||||||
fn into(self: Self) -> (X, X) {
|
|
||||||
(self.x.cast(), self.y.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalPosition<P> {
|
|
||||||
fn from([x, y]: [X; 2]) -> PhysicalPosition<P> {
|
|
||||||
PhysicalPosition::new(x.cast(), y.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> Into<[X; 2]> for PhysicalPosition<P> {
|
|
||||||
fn into(self: Self) -> [X; 2] {
|
|
||||||
[self.x.cast(), self.y.cast()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A size represented in logical pixels.
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub struct LogicalSize<P> {
|
|
||||||
pub width: P,
|
|
||||||
pub height: P,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P> LogicalSize<P> {
|
|
||||||
#[inline]
|
|
||||||
pub const fn new(width: P, height: P) -> Self {
|
|
||||||
LogicalSize { width, height }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel> LogicalSize<P> {
|
|
||||||
#[inline]
|
|
||||||
pub fn from_physical<T: Into<PhysicalSize<X>>, X: Pixel>(physical: T, dpi_factor: f64) -> Self {
|
|
||||||
physical.into().to_logical(dpi_factor)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn to_physical<X: Pixel>(&self, dpi_factor: f64) -> PhysicalSize<X> {
|
|
||||||
assert!(validate_scale_factor(dpi_factor));
|
|
||||||
let width = self.width.into() * dpi_factor;
|
|
||||||
let height = self.height.into() * dpi_factor;
|
|
||||||
PhysicalSize::new(width, height).cast()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn cast<X: Pixel>(&self) -> LogicalSize<X> {
|
|
||||||
LogicalSize {
|
|
||||||
width: self.width.cast(),
|
|
||||||
height: self.height.cast(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> From<(X, X)> for LogicalSize<P> {
|
|
||||||
fn from((x, y): (X, X)) -> LogicalSize<P> {
|
|
||||||
LogicalSize::new(x.cast(), y.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> Into<(X, X)> for LogicalSize<P> {
|
|
||||||
fn into(self: LogicalSize<P>) -> (X, X) {
|
|
||||||
(self.width.cast(), self.height.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> From<[X; 2]> for LogicalSize<P> {
|
|
||||||
fn from([x, y]: [X; 2]) -> LogicalSize<P> {
|
|
||||||
LogicalSize::new(x.cast(), y.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> Into<[X; 2]> for LogicalSize<P> {
|
|
||||||
fn into(self: Self) -> [X; 2] {
|
|
||||||
[self.width.cast(), self.height.cast()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A size represented in physical pixels.
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub struct PhysicalSize<P> {
|
|
||||||
pub width: P,
|
|
||||||
pub height: P,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P> PhysicalSize<P> {
|
|
||||||
#[inline]
|
|
||||||
pub const fn new(width: P, height: P) -> Self {
|
|
||||||
PhysicalSize { width, height }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel> PhysicalSize<P> {
|
|
||||||
#[inline]
|
|
||||||
pub fn from_logical<T: Into<LogicalSize<X>>, X: Pixel>(logical: T, dpi_factor: f64) -> Self {
|
|
||||||
logical.into().to_physical(dpi_factor)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn to_logical<X: Pixel>(&self, dpi_factor: f64) -> LogicalSize<X> {
|
|
||||||
assert!(validate_scale_factor(dpi_factor));
|
|
||||||
let width = self.width.into() / dpi_factor;
|
|
||||||
let height = self.height.into() / dpi_factor;
|
|
||||||
LogicalSize::new(width, height).cast()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn cast<X: Pixel>(&self) -> PhysicalSize<X> {
|
|
||||||
PhysicalSize {
|
|
||||||
width: self.width.cast(),
|
|
||||||
height: self.height.cast(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> From<(X, X)> for PhysicalSize<P> {
|
|
||||||
fn from((x, y): (X, X)) -> PhysicalSize<P> {
|
|
||||||
PhysicalSize::new(x.cast(), y.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> Into<(X, X)> for PhysicalSize<P> {
|
|
||||||
fn into(self: Self) -> (X, X) {
|
|
||||||
(self.width.cast(), self.height.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> From<[X; 2]> for PhysicalSize<P> {
|
|
||||||
fn from([x, y]: [X; 2]) -> PhysicalSize<P> {
|
|
||||||
PhysicalSize::new(x.cast(), y.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel, X: Pixel> Into<[X; 2]> for PhysicalSize<P> {
|
|
||||||
fn into(self: Self) -> [X; 2] {
|
|
||||||
[self.width.cast(), self.height.cast()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A size that's either physical or logical.
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub enum Size {
|
|
||||||
Physical(PhysicalSize<u32>),
|
|
||||||
Logical(LogicalSize<f64>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Size {
|
|
||||||
pub fn new<S: Into<Size>>(size: S) -> Size {
|
|
||||||
size.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_logical<P: Pixel>(&self, dpi_factor: f64) -> LogicalSize<P> {
|
|
||||||
match *self {
|
|
||||||
Size::Physical(size) => size.to_logical(dpi_factor),
|
|
||||||
Size::Logical(size) => size.cast(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_physical<P: Pixel>(&self, dpi_factor: f64) -> PhysicalSize<P> {
|
|
||||||
match *self {
|
|
||||||
Size::Physical(size) => size.cast(),
|
|
||||||
Size::Logical(size) => size.to_physical(dpi_factor),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel> From<PhysicalSize<P>> for Size {
|
|
||||||
#[inline]
|
|
||||||
fn from(size: PhysicalSize<P>) -> Size {
|
|
||||||
Size::Physical(size.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel> From<LogicalSize<P>> for Size {
|
|
||||||
#[inline]
|
|
||||||
fn from(size: LogicalSize<P>) -> Size {
|
|
||||||
Size::Logical(size.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A position that's either physical or logical.
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub enum Position {
|
|
||||||
Physical(PhysicalPosition<i32>),
|
|
||||||
Logical(LogicalPosition<f64>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Position {
|
|
||||||
pub fn new<S: Into<Position>>(position: S) -> Position {
|
|
||||||
position.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_logical<P: Pixel>(&self, dpi_factor: f64) -> LogicalPosition<P> {
|
|
||||||
match *self {
|
|
||||||
Position::Physical(position) => position.to_logical(dpi_factor),
|
|
||||||
Position::Logical(position) => position.cast(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_physical<P: Pixel>(&self, dpi_factor: f64) -> PhysicalPosition<P> {
|
|
||||||
match *self {
|
|
||||||
Position::Physical(position) => position.cast(),
|
|
||||||
Position::Logical(position) => position.to_physical(dpi_factor),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel> From<PhysicalPosition<P>> for Position {
|
|
||||||
#[inline]
|
|
||||||
fn from(position: PhysicalPosition<P>) -> Position {
|
|
||||||
Position::Physical(position.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Pixel> From<LogicalPosition<P>> for Position {
|
|
||||||
#[inline]
|
|
||||||
fn from(position: LogicalPosition<P>) -> Position {
|
|
||||||
Position::Logical(position.cast())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 {}
|
|
||||||
947
src/event.rs
947
src/event.rs
@@ -1,947 +0,0 @@
|
|||||||
//! The `Event` enum and assorted supporting types.
|
|
||||||
//!
|
|
||||||
//! These are sent to the closure given to [`EventLoop::run(...)`][event_loop_run], where they get
|
|
||||||
//! processed and used to modify the program state. For more details, see the root-level documentation.
|
|
||||||
//!
|
|
||||||
//! Some of these events represent different "parts" of a traditional event-handling loop. You could
|
|
||||||
//! approximate the basic ordering loop of [`EventLoop::run(...)`][event_loop_run] like this:
|
|
||||||
//!
|
|
||||||
//! ```rust,ignore
|
|
||||||
//! let mut control_flow = ControlFlow::Poll;
|
|
||||||
//! let mut start_cause = StartCause::Init;
|
|
||||||
//!
|
|
||||||
//! while control_flow != ControlFlow::Exit {
|
|
||||||
//! event_handler(NewEvents(start_cause), ..., &mut control_flow);
|
|
||||||
//!
|
|
||||||
//! for e in (window events, user events, device events) {
|
|
||||||
//! event_handler(e, ..., &mut control_flow);
|
|
||||||
//! }
|
|
||||||
//! event_handler(MainEventsCleared, ..., &mut control_flow);
|
|
||||||
//!
|
|
||||||
//! for w in (redraw windows) {
|
|
||||||
//! event_handler(RedrawRequested(w), ..., &mut control_flow);
|
|
||||||
//! }
|
|
||||||
//! event_handler(RedrawEventsCleared, ..., &mut control_flow);
|
|
||||||
//!
|
|
||||||
//! start_cause = wait_if_necessary(control_flow);
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! event_handler(LoopDestroyed, ..., &mut control_flow);
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! This leaves out timing details like `ControlFlow::WaitUntil` but hopefully
|
|
||||||
//! describes what happens in what order.
|
|
||||||
//!
|
|
||||||
//! [event_loop_run]: crate::event_loop::EventLoop::run
|
|
||||||
use instant::Instant;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
dpi::{LogicalPosition, PhysicalPosition, PhysicalSize},
|
|
||||||
platform_impl,
|
|
||||||
window::{Theme, WindowId},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Describes a generic event.
|
|
||||||
///
|
|
||||||
/// See the module-level docs for more information on the event loop manages each event.
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum Event<'a, T: 'static> {
|
|
||||||
/// Emitted when new events arrive from the OS to be processed.
|
|
||||||
///
|
|
||||||
/// This event type is useful as a place to put code that should be done before you start
|
|
||||||
/// processing events, such as updating frame timing information for benchmarking or checking
|
|
||||||
/// the [`StartCause`][crate::event::StartCause] to see if a timer set by
|
|
||||||
/// [`ControlFlow::WaitUntil`](crate::event_loop::ControlFlow::WaitUntil) has elapsed.
|
|
||||||
NewEvents(StartCause),
|
|
||||||
|
|
||||||
/// Emitted when the OS sends an event to a winit window.
|
|
||||||
WindowEvent {
|
|
||||||
window_id: WindowId,
|
|
||||||
event: WindowEvent<'a>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Emitted when the OS sends an event to a device.
|
|
||||||
DeviceEvent {
|
|
||||||
device_id: DeviceId,
|
|
||||||
event: DeviceEvent,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Emitted when an event is sent from [`EventLoopProxy::send_event`](crate::event_loop::EventLoopProxy::send_event)
|
|
||||||
UserEvent(T),
|
|
||||||
|
|
||||||
/// Emitted when the application has been suspended.
|
|
||||||
Suspended,
|
|
||||||
|
|
||||||
/// Emitted when the application has been resumed.
|
|
||||||
Resumed,
|
|
||||||
|
|
||||||
/// Emitted when all of the event loop's input events have been processed and redraw processing
|
|
||||||
/// is about to begin.
|
|
||||||
///
|
|
||||||
/// This event is useful as a place to put your code that should be run after all
|
|
||||||
/// state-changing events have been handled and you want to do stuff (updating state, performing
|
|
||||||
/// calculations, etc) that happens as the "main body" of your event loop. If your program draws
|
|
||||||
/// graphics, it's usually better to do it in response to
|
|
||||||
/// [`Event::RedrawRequested`](crate::event::Event::RedrawRequested), which gets emitted
|
|
||||||
/// immediately after this event.
|
|
||||||
MainEventsCleared,
|
|
||||||
|
|
||||||
/// Emitted after `MainEventsCleared` when a window should be redrawn.
|
|
||||||
///
|
|
||||||
/// This gets triggered in two scenarios:
|
|
||||||
/// - The OS has performed an operation that's invalidated the window's contents (such as
|
|
||||||
/// resizing the window).
|
|
||||||
/// - The application has explicitly requested a redraw via
|
|
||||||
/// [`Window::request_redraw`](crate::window::Window::request_redraw).
|
|
||||||
///
|
|
||||||
/// During each iteration of the event loop, Winit will aggregate duplicate redraw requests
|
|
||||||
/// into a single event, to help avoid duplicating rendering work.
|
|
||||||
RedrawRequested(WindowId),
|
|
||||||
|
|
||||||
/// Emitted after all `RedrawRequested` events have been processed and control flow is about to
|
|
||||||
/// be taken away from the program. If there are no `RedrawRequested` events, it is emitted
|
|
||||||
/// immediately after `MainEventsCleared`.
|
|
||||||
///
|
|
||||||
/// This event is useful for doing any cleanup or bookkeeping work after all the rendering
|
|
||||||
/// tasks have been completed.
|
|
||||||
RedrawEventsCleared,
|
|
||||||
|
|
||||||
/// Emitted when the event loop is being shut down.
|
|
||||||
///
|
|
||||||
/// This is irreversable - if this event is emitted, it is guaranteed to be the last event that
|
|
||||||
/// gets emitted. You generally want to treat this as an "do on quit" event.
|
|
||||||
LoopDestroyed,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T> Event<'a, T> {
|
|
||||||
pub fn map_nonuser_event<U>(self) -> Result<Event<'a, U>, Event<'a, T>> {
|
|
||||||
use self::Event::*;
|
|
||||||
match self {
|
|
||||||
UserEvent(_) => Err(self),
|
|
||||||
WindowEvent { window_id, event } => Ok(WindowEvent { window_id, event }),
|
|
||||||
DeviceEvent { device_id, event } => Ok(DeviceEvent { device_id, event }),
|
|
||||||
NewEvents(cause) => Ok(NewEvents(cause)),
|
|
||||||
MainEventsCleared => Ok(MainEventsCleared),
|
|
||||||
RedrawRequested(wid) => Ok(RedrawRequested(wid)),
|
|
||||||
RedrawEventsCleared => Ok(RedrawEventsCleared),
|
|
||||||
LoopDestroyed => Ok(LoopDestroyed),
|
|
||||||
Suspended => Ok(Suspended),
|
|
||||||
Resumed => Ok(Resumed),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If the event doesn't contain a reference, turn it into an event with a `'static` lifetime.
|
|
||||||
/// Otherwise, return `None`.
|
|
||||||
pub fn to_static(self) -> Option<Event<'static, T>> {
|
|
||||||
use self::Event::*;
|
|
||||||
match self {
|
|
||||||
WindowEvent { window_id, event } => event
|
|
||||||
.to_static()
|
|
||||||
.map(|event| WindowEvent { window_id, event }),
|
|
||||||
UserEvent(_) => None,
|
|
||||||
DeviceEvent { device_id, event } => Some(DeviceEvent { device_id, event }),
|
|
||||||
NewEvents(cause) => Some(NewEvents(cause)),
|
|
||||||
MainEventsCleared => Some(MainEventsCleared),
|
|
||||||
RedrawRequested(wid) => Some(RedrawRequested(wid)),
|
|
||||||
RedrawEventsCleared => Some(RedrawEventsCleared),
|
|
||||||
LoopDestroyed => Some(LoopDestroyed),
|
|
||||||
Suspended => Some(Suspended),
|
|
||||||
Resumed => Some(Resumed),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes the reason the event loop is resuming.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum StartCause {
|
|
||||||
/// Sent if the time specified by `ControlFlow::WaitUntil` has been reached. Contains the
|
|
||||||
/// moment the timeout was requested and the requested resume time. The actual resume time is
|
|
||||||
/// guaranteed to be equal to or after the requested resume time.
|
|
||||||
ResumeTimeReached {
|
|
||||||
start: Instant,
|
|
||||||
requested_resume: Instant,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Sent if the OS has new events to send to the window, after a wait was requested. Contains
|
|
||||||
/// the moment the wait was requested and the resume time, if requested.
|
|
||||||
WaitCancelled {
|
|
||||||
start: Instant,
|
|
||||||
requested_resume: Option<Instant>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Sent if the event loop is being resumed after the loop's control flow was set to
|
|
||||||
/// `ControlFlow::Poll`.
|
|
||||||
Poll,
|
|
||||||
|
|
||||||
/// Sent once, immediately after `run` is called. Indicates that the loop was just initialized.
|
|
||||||
Init,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes an event from a `Window`.
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum WindowEvent<'a> {
|
|
||||||
/// The size of the window has changed. Contains the client area's new dimensions.
|
|
||||||
Resized(PhysicalSize<u32>),
|
|
||||||
|
|
||||||
/// The position of the window has changed. Contains the window's new position.
|
|
||||||
Moved(PhysicalPosition<u32>),
|
|
||||||
|
|
||||||
/// The window has been requested to close.
|
|
||||||
CloseRequested,
|
|
||||||
|
|
||||||
/// The window has been destroyed.
|
|
||||||
Destroyed,
|
|
||||||
|
|
||||||
/// A file has been dropped into the window.
|
|
||||||
///
|
|
||||||
/// When the user drops multiple files at once, this event will be emitted for each file
|
|
||||||
/// separately.
|
|
||||||
DroppedFile(PathBuf),
|
|
||||||
|
|
||||||
/// A file is being hovered over the window.
|
|
||||||
///
|
|
||||||
/// When the user hovers multiple files at once, this event will be emitted for each file
|
|
||||||
/// separately.
|
|
||||||
HoveredFile(PathBuf),
|
|
||||||
|
|
||||||
/// A file was hovered, but has exited the window.
|
|
||||||
///
|
|
||||||
/// There will be a single `HoveredFileCancelled` event triggered even if multiple files were
|
|
||||||
/// hovered.
|
|
||||||
HoveredFileCancelled,
|
|
||||||
|
|
||||||
/// The window received a unicode character.
|
|
||||||
ReceivedCharacter(char),
|
|
||||||
|
|
||||||
/// The window gained or lost focus.
|
|
||||||
///
|
|
||||||
/// The parameter is true if the window has gained focus, and false if it has lost focus.
|
|
||||||
Focused(bool),
|
|
||||||
|
|
||||||
/// An event from the keyboard has been received.
|
|
||||||
KeyboardInput {
|
|
||||||
device_id: DeviceId,
|
|
||||||
input: KeyboardInput,
|
|
||||||
/// If `true`, the event was generated synthetically by winit
|
|
||||||
/// in one of the following circumstances:
|
|
||||||
///
|
|
||||||
/// * Synthetic key press events are generated for all keys pressed
|
|
||||||
/// when a window gains focus. Likewise, synthetic key release events
|
|
||||||
/// are generated for all keys pressed when a window goes out of focus.
|
|
||||||
/// ***Currently, this is only functional on X11 and Windows***
|
|
||||||
///
|
|
||||||
/// Otherwise, this value is always `false`.
|
|
||||||
is_synthetic: bool,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// The cursor has moved on the window.
|
|
||||||
CursorMoved {
|
|
||||||
device_id: DeviceId,
|
|
||||||
|
|
||||||
/// (x,y) coords in pixels relative to the top-left corner of the window. Because the range of this data is
|
|
||||||
/// limited by the display area and it may have been transformed by the OS to implement effects such as cursor
|
|
||||||
/// acceleration, it should not be used to implement non-cursor-like interactions such as 3D camera control.
|
|
||||||
position: PhysicalPosition<f64>,
|
|
||||||
#[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"]
|
|
||||||
modifiers: ModifiersState,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// The cursor has entered the window.
|
|
||||||
CursorEntered { device_id: DeviceId },
|
|
||||||
|
|
||||||
/// The cursor has left the window.
|
|
||||||
CursorLeft { device_id: DeviceId },
|
|
||||||
|
|
||||||
/// A mouse wheel movement or touchpad scroll occurred.
|
|
||||||
MouseWheel {
|
|
||||||
device_id: DeviceId,
|
|
||||||
delta: MouseScrollDelta,
|
|
||||||
phase: TouchPhase,
|
|
||||||
#[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"]
|
|
||||||
modifiers: ModifiersState,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// An mouse button press has been received.
|
|
||||||
MouseInput {
|
|
||||||
device_id: DeviceId,
|
|
||||||
state: ElementState,
|
|
||||||
button: MouseButton,
|
|
||||||
#[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"]
|
|
||||||
modifiers: ModifiersState,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Touchpad pressure event.
|
|
||||||
///
|
|
||||||
/// At the moment, only supported on Apple forcetouch-capable macbooks.
|
|
||||||
/// The parameters are: pressure level (value between 0 and 1 representing how hard the touchpad
|
|
||||||
/// is being pressed) and stage (integer representing the click level).
|
|
||||||
TouchpadPressure {
|
|
||||||
device_id: DeviceId,
|
|
||||||
pressure: f32,
|
|
||||||
stage: i64,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Motion on some analog axis. May report data redundant to other, more specific events.
|
|
||||||
AxisMotion {
|
|
||||||
device_id: DeviceId,
|
|
||||||
axis: AxisId,
|
|
||||||
value: f64,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Touch event has been received
|
|
||||||
Touch(Touch),
|
|
||||||
|
|
||||||
/// The window's scale factor has changed.
|
|
||||||
///
|
|
||||||
/// The following user actions can cause DPI changes:
|
|
||||||
///
|
|
||||||
/// * Changing the display's resolution.
|
|
||||||
/// * Changing the display's scale factor (e.g. in Control Panel on Windows).
|
|
||||||
/// * Moving the window to a display with a different scale factor.
|
|
||||||
///
|
|
||||||
/// After this event callback has been processed, the window will be resized to whatever value
|
|
||||||
/// is pointed to by the `new_inner_size` reference. By default, this will contain the size suggested
|
|
||||||
/// by the OS, but it can be changed to any value.
|
|
||||||
///
|
|
||||||
/// For more information about DPI in general, see the [`dpi`](crate::dpi) module.
|
|
||||||
ScaleFactorChanged {
|
|
||||||
scale_factor: f64,
|
|
||||||
new_inner_size: &'a mut PhysicalSize<u32>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// The system window theme has changed.
|
|
||||||
///
|
|
||||||
/// Applications might wish to react to this to change the theme of the content of the window
|
|
||||||
/// when the system changes the window theme.
|
|
||||||
///
|
|
||||||
/// At the moment this is only supported on Windows.
|
|
||||||
ThemeChanged(Theme),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> WindowEvent<'a> {
|
|
||||||
pub fn to_static(self) -> Option<WindowEvent<'static>> {
|
|
||||||
use self::WindowEvent::*;
|
|
||||||
match self {
|
|
||||||
Resized(size) => Some(Resized(size)),
|
|
||||||
Moved(position) => Some(Moved(position)),
|
|
||||||
CloseRequested => Some(CloseRequested),
|
|
||||||
Destroyed => Some(Destroyed),
|
|
||||||
DroppedFile(file) => Some(DroppedFile(file)),
|
|
||||||
HoveredFile(file) => Some(HoveredFile(file)),
|
|
||||||
HoveredFileCancelled => Some(HoveredFileCancelled),
|
|
||||||
ReceivedCharacter(c) => Some(ReceivedCharacter(c)),
|
|
||||||
Focused(focused) => Some(Focused(focused)),
|
|
||||||
KeyboardInput {
|
|
||||||
device_id,
|
|
||||||
input,
|
|
||||||
is_synthetic,
|
|
||||||
} => Some(KeyboardInput {
|
|
||||||
device_id,
|
|
||||||
input,
|
|
||||||
is_synthetic,
|
|
||||||
}),
|
|
||||||
#[allow(deprecated)]
|
|
||||||
CursorMoved {
|
|
||||||
device_id,
|
|
||||||
position,
|
|
||||||
modifiers,
|
|
||||||
} => Some(CursorMoved {
|
|
||||||
device_id,
|
|
||||||
position,
|
|
||||||
modifiers,
|
|
||||||
}),
|
|
||||||
CursorEntered { device_id } => Some(CursorEntered { device_id }),
|
|
||||||
CursorLeft { device_id } => Some(CursorLeft { device_id }),
|
|
||||||
#[allow(deprecated)]
|
|
||||||
MouseWheel {
|
|
||||||
device_id,
|
|
||||||
delta,
|
|
||||||
phase,
|
|
||||||
modifiers,
|
|
||||||
} => Some(MouseWheel {
|
|
||||||
device_id,
|
|
||||||
delta,
|
|
||||||
phase,
|
|
||||||
modifiers,
|
|
||||||
}),
|
|
||||||
#[allow(deprecated)]
|
|
||||||
MouseInput {
|
|
||||||
device_id,
|
|
||||||
state,
|
|
||||||
button,
|
|
||||||
modifiers,
|
|
||||||
} => Some(MouseInput {
|
|
||||||
device_id,
|
|
||||||
state,
|
|
||||||
button,
|
|
||||||
modifiers,
|
|
||||||
}),
|
|
||||||
TouchpadPressure {
|
|
||||||
device_id,
|
|
||||||
pressure,
|
|
||||||
stage,
|
|
||||||
} => Some(TouchpadPressure {
|
|
||||||
device_id,
|
|
||||||
pressure,
|
|
||||||
stage,
|
|
||||||
}),
|
|
||||||
AxisMotion {
|
|
||||||
device_id,
|
|
||||||
axis,
|
|
||||||
value,
|
|
||||||
} => Some(AxisMotion {
|
|
||||||
device_id,
|
|
||||||
axis,
|
|
||||||
value,
|
|
||||||
}),
|
|
||||||
Touch(touch) => Some(Touch(touch)),
|
|
||||||
ThemeChanged(theme) => Some(ThemeChanged(theme)),
|
|
||||||
ScaleFactorChanged { .. } => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Identifier of an input device.
|
|
||||||
///
|
|
||||||
/// Whenever you receive an event arising from a particular input device, this event contains a `DeviceId` which
|
|
||||||
/// identifies its origin. Note that devices may be virtual (representing an on-screen cursor and keyboard focus) or
|
|
||||||
/// physical. Virtual devices typically aggregate inputs from multiple physical devices.
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct DeviceId(pub(crate) platform_impl::DeviceId);
|
|
||||||
|
|
||||||
impl DeviceId {
|
|
||||||
/// Returns a dummy `DeviceId`, useful for unit testing. The only guarantee made about the return
|
|
||||||
/// value of this function is that it will always be equal to itself and to future values returned
|
|
||||||
/// by this function. No other guarantees are made. This may be equal to a real `DeviceId`.
|
|
||||||
///
|
|
||||||
/// **Passing this into a winit function will result in undefined behavior.**
|
|
||||||
pub unsafe fn dummy() -> Self {
|
|
||||||
DeviceId(platform_impl::DeviceId::dummy())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents raw hardware events that are not associated with any particular window.
|
|
||||||
///
|
|
||||||
/// Useful for interactions that diverge significantly from a conventional 2D GUI, such as 3D camera or first-person
|
|
||||||
/// game controls. Many physical actions, such as mouse movement, can produce both device and window events. Because
|
|
||||||
/// window events typically arise from virtual devices (corresponding to GUI cursors and keyboard focus) the device IDs
|
|
||||||
/// may not match.
|
|
||||||
///
|
|
||||||
/// Note that these events are delivered regardless of input focus.
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
|
||||||
pub enum DeviceEvent {
|
|
||||||
Added,
|
|
||||||
Removed,
|
|
||||||
|
|
||||||
/// Change in physical position of a pointing device.
|
|
||||||
///
|
|
||||||
/// This represents raw, unfiltered physical motion. Not to be confused with `WindowEvent::CursorMoved`.
|
|
||||||
MouseMotion {
|
|
||||||
/// (x, y) change in position in unspecified units.
|
|
||||||
///
|
|
||||||
/// Different devices may use different units.
|
|
||||||
delta: (f64, f64),
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Physical scroll event
|
|
||||||
MouseWheel {
|
|
||||||
delta: MouseScrollDelta,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Motion on some analog axis. This event will be reported for all arbitrary input devices
|
|
||||||
/// that winit supports on this platform, including mouse devices. If the device is a mouse
|
|
||||||
/// device then this will be reported alongside the MouseMotion event.
|
|
||||||
Motion {
|
|
||||||
axis: AxisId,
|
|
||||||
value: f64,
|
|
||||||
},
|
|
||||||
|
|
||||||
Button {
|
|
||||||
button: ButtonId,
|
|
||||||
state: ElementState,
|
|
||||||
},
|
|
||||||
|
|
||||||
Key(KeyboardInput),
|
|
||||||
|
|
||||||
/// The keyboard modifiers have changed.
|
|
||||||
///
|
|
||||||
/// This is tracked internally to avoid tracking errors arising from modifier key state changes when events from
|
|
||||||
/// this device are not being delivered to the application, e.g. due to keyboard focus being elsewhere.
|
|
||||||
///
|
|
||||||
/// Platform-specific behavior:
|
|
||||||
/// - **Web**: This API is currently unimplemented on the web. This isn't by design - it's an
|
|
||||||
/// issue, and it should get fixed - but it's the current state of the API.
|
|
||||||
ModifiersChanged(ModifiersState),
|
|
||||||
|
|
||||||
Text {
|
|
||||||
codepoint: char,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes a keyboard input event.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub struct KeyboardInput {
|
|
||||||
/// Identifies the physical key pressed
|
|
||||||
///
|
|
||||||
/// This should not change if the user adjusts the host's keyboard map. Use when the physical location of the
|
|
||||||
/// key is more important than the key's host GUI semantics, such as for movement controls in a first-person
|
|
||||||
/// game.
|
|
||||||
pub scancode: ScanCode,
|
|
||||||
|
|
||||||
pub state: ElementState,
|
|
||||||
|
|
||||||
/// Identifies the semantic meaning of the key
|
|
||||||
///
|
|
||||||
/// Use when the semantics of the key are more important than the physical location of the key, such as when
|
|
||||||
/// implementing appropriate behavior for "page up."
|
|
||||||
pub virtual_keycode: Option<VirtualKeyCode>,
|
|
||||||
|
|
||||||
/// Modifier keys active at the time of this input.
|
|
||||||
///
|
|
||||||
/// This is tracked internally to avoid tracking errors arising from modifier key state changes when events from
|
|
||||||
/// this device are not being delivered to the application, e.g. due to keyboard focus being elsewhere.
|
|
||||||
#[deprecated = "Deprecated in favor of DeviceEvent::ModifiersChanged"]
|
|
||||||
pub modifiers: ModifiersState,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes touch-screen input state.
|
|
||||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub enum TouchPhase {
|
|
||||||
Started,
|
|
||||||
Moved,
|
|
||||||
Ended,
|
|
||||||
Cancelled,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a touch event
|
|
||||||
///
|
|
||||||
/// Every time the user touches the screen, a new `Start` event with an unique
|
|
||||||
/// identifier for the finger is generated. When the finger is lifted, an `End`
|
|
||||||
/// event is generated with the same finger id.
|
|
||||||
///
|
|
||||||
/// After a `Start` event has been emitted, there may be zero or more `Move`
|
|
||||||
/// events when the finger is moved or the touch pressure changes.
|
|
||||||
///
|
|
||||||
/// The finger id may be reused by the system after an `End` event. The user
|
|
||||||
/// should assume that a new `Start` event received with the same id has nothing
|
|
||||||
/// to do with the old finger and is a new finger.
|
|
||||||
///
|
|
||||||
/// A `Cancelled` event is emitted when the system has canceled tracking this
|
|
||||||
/// touch, such as when the window loses focus, or on iOS if the user moves the
|
|
||||||
/// device against their face.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
pub struct Touch {
|
|
||||||
pub device_id: DeviceId,
|
|
||||||
pub phase: TouchPhase,
|
|
||||||
pub location: PhysicalPosition<f64>,
|
|
||||||
/// Describes how hard the screen was pressed. May be `None` if the platform
|
|
||||||
/// does not support pressure sensitivity.
|
|
||||||
///
|
|
||||||
/// ## Platform-specific
|
|
||||||
///
|
|
||||||
/// - Only available on **iOS** 9.0+ and **Windows** 8+.
|
|
||||||
pub force: Option<Force>,
|
|
||||||
/// Unique identifier of a finger.
|
|
||||||
pub id: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes the force of a touch event
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
pub enum Force {
|
|
||||||
/// On iOS, the force is calibrated so that the same number corresponds to
|
|
||||||
/// roughly the same amount of pressure on the screen regardless of the
|
|
||||||
/// device.
|
|
||||||
Calibrated {
|
|
||||||
/// The force of the touch, where a value of 1.0 represents the force of
|
|
||||||
/// an average touch (predetermined by the system, not user-specific).
|
|
||||||
///
|
|
||||||
/// The force reported by Apple Pencil is measured along the axis of the
|
|
||||||
/// pencil. If you want a force perpendicular to the device, you need to
|
|
||||||
/// calculate this value using the `altitude_angle` value.
|
|
||||||
force: f64,
|
|
||||||
/// The maximum possible force for a touch.
|
|
||||||
///
|
|
||||||
/// The value of this field is sufficiently high to provide a wide
|
|
||||||
/// dynamic range for values of the `force` field.
|
|
||||||
max_possible_force: f64,
|
|
||||||
/// The altitude (in radians) of the stylus.
|
|
||||||
///
|
|
||||||
/// A value of 0 radians indicates that the stylus is parallel to the
|
|
||||||
/// surface. The value of this property is Pi/2 when the stylus is
|
|
||||||
/// perpendicular to the surface.
|
|
||||||
altitude_angle: Option<f64>,
|
|
||||||
},
|
|
||||||
/// If the platform reports the force as normalized, we have no way of
|
|
||||||
/// knowing how much pressure 1.0 corresponds to – we know it's the maximum
|
|
||||||
/// amount of force, but as to how much force, you might either have to
|
|
||||||
/// press really really hard, or not hard at all, depending on the device.
|
|
||||||
Normalized(f64),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Force {
|
|
||||||
/// Returns the force normalized to the range between 0.0 and 1.0 inclusive.
|
|
||||||
/// Instead of normalizing the force, you should prefer to handle
|
|
||||||
/// `Force::Calibrated` so that the amount of force the user has to apply is
|
|
||||||
/// consistent across devices.
|
|
||||||
pub fn normalized(&self) -> f64 {
|
|
||||||
match self {
|
|
||||||
Force::Calibrated {
|
|
||||||
force,
|
|
||||||
max_possible_force,
|
|
||||||
altitude_angle,
|
|
||||||
} => {
|
|
||||||
let force = match altitude_angle {
|
|
||||||
Some(altitude_angle) => force / altitude_angle.sin(),
|
|
||||||
None => *force,
|
|
||||||
};
|
|
||||||
force / max_possible_force
|
|
||||||
}
|
|
||||||
Force::Normalized(force) => *force,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Hardware-dependent keyboard scan code.
|
|
||||||
pub type ScanCode = u32;
|
|
||||||
|
|
||||||
/// Identifier for a specific analog axis on some device.
|
|
||||||
pub type AxisId = u32;
|
|
||||||
|
|
||||||
/// Identifier for a specific button on some device.
|
|
||||||
pub type ButtonId = u32;
|
|
||||||
|
|
||||||
/// Describes the input state of a key.
|
|
||||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub enum ElementState {
|
|
||||||
Pressed,
|
|
||||||
Released,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes a button of a mouse controller.
|
|
||||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub enum MouseButton {
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
Middle,
|
|
||||||
Other(u8),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Describes a difference in the mouse scroll wheel state.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub enum MouseScrollDelta {
|
|
||||||
/// Amount in lines or rows to scroll in the horizontal
|
|
||||||
/// and vertical directions.
|
|
||||||
///
|
|
||||||
/// Positive values indicate movement forward
|
|
||||||
/// (away from the user) or rightwards.
|
|
||||||
LineDelta(f32, f32),
|
|
||||||
/// Amount in pixels to scroll in the horizontal and
|
|
||||||
/// vertical direction.
|
|
||||||
///
|
|
||||||
/// Scroll events are expressed as a PixelDelta if
|
|
||||||
/// supported by the device (eg. a touchpad) and
|
|
||||||
/// platform.
|
|
||||||
PixelDelta(LogicalPosition<f64>),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Symbolic name for a keyboard key.
|
|
||||||
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
|
|
||||||
#[repr(u32)]
|
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
||||||
pub enum VirtualKeyCode {
|
|
||||||
/// The '1' key over the letters.
|
|
||||||
Key1,
|
|
||||||
/// The '2' key over the letters.
|
|
||||||
Key2,
|
|
||||||
/// The '3' key over the letters.
|
|
||||||
Key3,
|
|
||||||
/// The '4' key over the letters.
|
|
||||||
Key4,
|
|
||||||
/// The '5' key over the letters.
|
|
||||||
Key5,
|
|
||||||
/// The '6' key over the letters.
|
|
||||||
Key6,
|
|
||||||
/// The '7' key over the letters.
|
|
||||||
Key7,
|
|
||||||
/// The '8' key over the letters.
|
|
||||||
Key8,
|
|
||||||
/// The '9' key over the letters.
|
|
||||||
Key9,
|
|
||||||
/// The '0' key over the 'O' and 'P' keys.
|
|
||||||
Key0,
|
|
||||||
|
|
||||||
A,
|
|
||||||
B,
|
|
||||||
C,
|
|
||||||
D,
|
|
||||||
E,
|
|
||||||
F,
|
|
||||||
G,
|
|
||||||
H,
|
|
||||||
I,
|
|
||||||
J,
|
|
||||||
K,
|
|
||||||
L,
|
|
||||||
M,
|
|
||||||
N,
|
|
||||||
O,
|
|
||||||
P,
|
|
||||||
Q,
|
|
||||||
R,
|
|
||||||
S,
|
|
||||||
T,
|
|
||||||
U,
|
|
||||||
V,
|
|
||||||
W,
|
|
||||||
X,
|
|
||||||
Y,
|
|
||||||
Z,
|
|
||||||
|
|
||||||
/// The Escape key, next to F1.
|
|
||||||
Escape,
|
|
||||||
|
|
||||||
F1,
|
|
||||||
F2,
|
|
||||||
F3,
|
|
||||||
F4,
|
|
||||||
F5,
|
|
||||||
F6,
|
|
||||||
F7,
|
|
||||||
F8,
|
|
||||||
F9,
|
|
||||||
F10,
|
|
||||||
F11,
|
|
||||||
F12,
|
|
||||||
F13,
|
|
||||||
F14,
|
|
||||||
F15,
|
|
||||||
F16,
|
|
||||||
F17,
|
|
||||||
F18,
|
|
||||||
F19,
|
|
||||||
F20,
|
|
||||||
F21,
|
|
||||||
F22,
|
|
||||||
F23,
|
|
||||||
F24,
|
|
||||||
|
|
||||||
/// Print Screen/SysRq.
|
|
||||||
Snapshot,
|
|
||||||
/// Scroll Lock.
|
|
||||||
Scroll,
|
|
||||||
/// Pause/Break key, next to Scroll lock.
|
|
||||||
Pause,
|
|
||||||
|
|
||||||
/// `Insert`, next to Backspace.
|
|
||||||
Insert,
|
|
||||||
Home,
|
|
||||||
Delete,
|
|
||||||
End,
|
|
||||||
PageDown,
|
|
||||||
PageUp,
|
|
||||||
|
|
||||||
Left,
|
|
||||||
Up,
|
|
||||||
Right,
|
|
||||||
Down,
|
|
||||||
|
|
||||||
/// The Backspace key, right over Enter.
|
|
||||||
// TODO: rename
|
|
||||||
Back,
|
|
||||||
/// The Enter key.
|
|
||||||
Return,
|
|
||||||
/// The space bar.
|
|
||||||
Space,
|
|
||||||
|
|
||||||
/// The "Compose" key on Linux.
|
|
||||||
Compose,
|
|
||||||
|
|
||||||
Caret,
|
|
||||||
|
|
||||||
Numlock,
|
|
||||||
Numpad0,
|
|
||||||
Numpad1,
|
|
||||||
Numpad2,
|
|
||||||
Numpad3,
|
|
||||||
Numpad4,
|
|
||||||
Numpad5,
|
|
||||||
Numpad6,
|
|
||||||
Numpad7,
|
|
||||||
Numpad8,
|
|
||||||
Numpad9,
|
|
||||||
|
|
||||||
AbntC1,
|
|
||||||
AbntC2,
|
|
||||||
Add,
|
|
||||||
Apostrophe,
|
|
||||||
Apps,
|
|
||||||
At,
|
|
||||||
Ax,
|
|
||||||
Backslash,
|
|
||||||
Calculator,
|
|
||||||
Capital,
|
|
||||||
Colon,
|
|
||||||
Comma,
|
|
||||||
Convert,
|
|
||||||
Decimal,
|
|
||||||
Divide,
|
|
||||||
Equals,
|
|
||||||
Grave,
|
|
||||||
Kana,
|
|
||||||
Kanji,
|
|
||||||
LAlt,
|
|
||||||
LBracket,
|
|
||||||
LControl,
|
|
||||||
LShift,
|
|
||||||
LWin,
|
|
||||||
Mail,
|
|
||||||
MediaSelect,
|
|
||||||
MediaStop,
|
|
||||||
Minus,
|
|
||||||
Multiply,
|
|
||||||
Mute,
|
|
||||||
MyComputer,
|
|
||||||
NavigateForward, // also called "Prior"
|
|
||||||
NavigateBackward, // also called "Next"
|
|
||||||
NextTrack,
|
|
||||||
NoConvert,
|
|
||||||
NumpadComma,
|
|
||||||
NumpadEnter,
|
|
||||||
NumpadEquals,
|
|
||||||
OEM102,
|
|
||||||
Period,
|
|
||||||
PlayPause,
|
|
||||||
Power,
|
|
||||||
PrevTrack,
|
|
||||||
RAlt,
|
|
||||||
RBracket,
|
|
||||||
RControl,
|
|
||||||
RShift,
|
|
||||||
RWin,
|
|
||||||
Semicolon,
|
|
||||||
Slash,
|
|
||||||
Sleep,
|
|
||||||
Stop,
|
|
||||||
Subtract,
|
|
||||||
Sysrq,
|
|
||||||
Tab,
|
|
||||||
Underline,
|
|
||||||
Unlabeled,
|
|
||||||
VolumeDown,
|
|
||||||
VolumeUp,
|
|
||||||
Wake,
|
|
||||||
WebBack,
|
|
||||||
WebFavorites,
|
|
||||||
WebForward,
|
|
||||||
WebHome,
|
|
||||||
WebRefresh,
|
|
||||||
WebSearch,
|
|
||||||
WebStop,
|
|
||||||
Yen,
|
|
||||||
Copy,
|
|
||||||
Paste,
|
|
||||||
Cut,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ModifiersState {
|
|
||||||
/// Returns `true` if the shift key is pressed.
|
|
||||||
pub fn shift(&self) -> bool {
|
|
||||||
self.intersects(Self::SHIFT)
|
|
||||||
}
|
|
||||||
/// Returns `true` if the control key is pressed.
|
|
||||||
pub fn ctrl(&self) -> bool {
|
|
||||||
self.intersects(Self::CTRL)
|
|
||||||
}
|
|
||||||
/// Returns `true` if the alt key is pressed.
|
|
||||||
pub fn alt(&self) -> bool {
|
|
||||||
self.intersects(Self::ALT)
|
|
||||||
}
|
|
||||||
/// Returns `true` if the logo key is pressed.
|
|
||||||
pub fn logo(&self) -> bool {
|
|
||||||
self.intersects(Self::LOGO)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitflags! {
|
|
||||||
/// Represents the current state of the keyboard modifiers
|
|
||||||
///
|
|
||||||
/// Each flag represents a modifier and is set if this modifier is active.
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct ModifiersState: u32 {
|
|
||||||
// left and right modifiers are currently commented out, but we should be able to support
|
|
||||||
// them in a future release
|
|
||||||
/// The "shift" key.
|
|
||||||
const SHIFT = 0b100 << 0;
|
|
||||||
// const LSHIFT = 0b010 << 0;
|
|
||||||
// const RSHIFT = 0b001 << 0;
|
|
||||||
/// The "control" key.
|
|
||||||
const CTRL = 0b100 << 3;
|
|
||||||
// const LCTRL = 0b010 << 3;
|
|
||||||
// const RCTRL = 0b001 << 3;
|
|
||||||
/// The "alt" key.
|
|
||||||
const ALT = 0b100 << 6;
|
|
||||||
// const LALT = 0b010 << 6;
|
|
||||||
// const RALT = 0b001 << 6;
|
|
||||||
/// This is the "windows" key on PC and "command" key on Mac.
|
|
||||||
const LOGO = 0b100 << 9;
|
|
||||||
// const LLOGO = 0b010 << 9;
|
|
||||||
// const RLOGO = 0b001 << 9;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
mod modifiers_serde {
|
|
||||||
use super::ModifiersState;
|
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize)]
|
|
||||||
#[serde(default)]
|
|
||||||
#[serde(rename = "ModifiersState")]
|
|
||||||
pub struct ModifiersStateSerialize {
|
|
||||||
pub shift: bool,
|
|
||||||
pub ctrl: bool,
|
|
||||||
pub alt: bool,
|
|
||||||
pub logo: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serialize for ModifiersState {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
let s = ModifiersStateSerialize {
|
|
||||||
shift: self.shift(),
|
|
||||||
ctrl: self.ctrl(),
|
|
||||||
alt: self.alt(),
|
|
||||||
logo: self.logo(),
|
|
||||||
};
|
|
||||||
s.serialize(serializer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for ModifiersState {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
let ModifiersStateSerialize {
|
|
||||||
shift,
|
|
||||||
ctrl,
|
|
||||||
alt,
|
|
||||||
logo,
|
|
||||||
} = ModifiersStateSerialize::deserialize(deserializer)?;
|
|
||||||
let mut m = ModifiersState::empty();
|
|
||||||
m.set(ModifiersState::SHIFT, shift);
|
|
||||||
m.set(ModifiersState::CTRL, ctrl);
|
|
||||||
m.set(ModifiersState::ALT, alt);
|
|
||||||
m.set(ModifiersState::LOGO, logo);
|
|
||||||
Ok(m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,224 +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()`][create_proxy]
|
|
||||||
//! to acquire an [`EventLoopProxy`][event_loop_proxy] and call its [`send_event`][send_event] method.
|
|
||||||
//!
|
|
||||||
//! See the root-level documentation for information on how to create and use an event loop to
|
|
||||||
//! handle events.
|
|
||||||
//!
|
|
||||||
//! [create_proxy]: crate::event_loop::EventLoop::create_proxy
|
|
||||||
//! [event_loop_proxy]: crate::event_loop::EventLoopProxy
|
|
||||||
//! [send_event]: crate::event_loop::EventLoopProxy::send_event
|
|
||||||
use instant::Instant;
|
|
||||||
use std::ops::Deref;
|
|
||||||
use std::{error, fmt};
|
|
||||||
|
|
||||||
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 the `EventLoop` 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 `EventLoop` _can_ be sent to an other thread, and the
|
|
||||||
/// `EventLoopProxy` allows you to wake up an `EventLoop` from another thread.
|
|
||||||
///
|
|
||||||
pub struct EventLoop<T: 'static> {
|
|
||||||
pub(crate) event_loop: platform_impl::EventLoop<T>,
|
|
||||||
pub(crate) _marker: ::std::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: ::std::marker::PhantomData<*mut ()>, // Not Send nor Sync
|
|
||||||
}
|
|
||||||
|
|
||||||
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`][events_cleared]
|
|
||||||
/// 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 `Exit` 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`.
|
|
||||||
///
|
|
||||||
/// [events_cleared]: crate::event::Event::RedrawEventsCleared
|
|
||||||
#[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.
|
|
||||||
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.
|
|
||||||
WaitUntil(Instant),
|
|
||||||
/// Send a `LoopDestroyed` event and stop the event loop. This variant is *sticky* - once set,
|
|
||||||
/// `control_flow` cannot be changed from `Exit`, and any future attempts to do so will result
|
|
||||||
/// in the `control_flow` parameter being reset to `Exit`.
|
|
||||||
Exit,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ControlFlow {
|
|
||||||
#[inline(always)]
|
|
||||||
fn default() -> ControlFlow {
|
|
||||||
ControlFlow::Poll
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventLoop<()> {
|
|
||||||
/// Builds a new event loop with a `()` as the user event type.
|
|
||||||
///
|
|
||||||
/// ***For cross-platform compatibility, the `EventLoop` must be created on the main thread.***
|
|
||||||
/// Attempting to create the event loop on a different thread 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. `EventLoopExt::new_any_thread` functions are exposed
|
|
||||||
/// in the relevant `platform` module if the target platform supports creating an event loop on
|
|
||||||
/// any thread.
|
|
||||||
///
|
|
||||||
/// Usage will result in display backend initialisation, this can be controlled on linux
|
|
||||||
/// 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 it fails will
|
|
||||||
/// fallback on x11. If this variable is set with any other value, winit will panic.
|
|
||||||
///
|
|
||||||
/// ## Platform-specific
|
|
||||||
///
|
|
||||||
/// - **iOS:** Can only be called on the main thread.
|
|
||||||
pub fn new() -> EventLoop<()> {
|
|
||||||
EventLoop::<()>::with_user_event()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoop<T> {
|
|
||||||
/// Builds a new event loop.
|
|
||||||
///
|
|
||||||
/// All caveats documented in [`EventLoop::new`] apply to this function.
|
|
||||||
///
|
|
||||||
/// ## Platform-specific
|
|
||||||
///
|
|
||||||
/// - **iOS:** Can only be called on the main thread.
|
|
||||||
pub fn with_user_event() -> EventLoop<T> {
|
|
||||||
EventLoop {
|
|
||||||
event_loop: platform_impl::EventLoop::new(),
|
|
||||||
_marker: ::std::marker::PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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.
|
|
||||||
///
|
|
||||||
/// [`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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the list of all the monitors available on the system.
|
|
||||||
#[inline]
|
|
||||||
pub fn available_monitors(&self) -> impl Iterator<Item = MonitorHandle> {
|
|
||||||
self.event_loop
|
|
||||||
.available_monitors()
|
|
||||||
.into_iter()
|
|
||||||
.map(|inner| MonitorHandle { inner })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the primary monitor of the system.
|
|
||||||
#[inline]
|
|
||||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
|
||||||
MonitorHandle {
|
|
||||||
inner: self.event_loop.primary_monitor(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Deref for EventLoop<T> {
|
|
||||||
type Target = EventLoopWindowTarget<T>;
|
|
||||||
fn deref(&self) -> &EventLoopWindowTarget<T> {
|
|
||||||
self.event_loop.window_target()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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.
|
|
||||||
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 `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> {}
|
|
||||||
91
src/icon.rs
91
src/icon.rs
@@ -1,91 +0,0 @@
|
|||||||
use std::{error::Error, fmt, mem};
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) struct Pixel {
|
|
||||||
pub(crate) r: u8,
|
|
||||||
pub(crate) g: u8,
|
|
||||||
pub(crate) b: u8,
|
|
||||||
pub(crate) a: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) const PIXEL_SIZE: usize = mem::size_of::<Pixel>();
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
/// An error produced when using `Icon::from_rgba` with invalid arguments.
|
|
||||||
pub enum BadIcon {
|
|
||||||
/// Produced when the length of the `rgba` argument isn't divisible by 4, thus `rgba` can't be
|
|
||||||
/// safely interpreted as 32bpp RGBA pixels.
|
|
||||||
ByteCountNotDivisibleBy4 { byte_count: usize },
|
|
||||||
/// Produced when the number of pixels (`rgba.len() / 4`) isn't equal to `width * height`.
|
|
||||||
/// At least one of your arguments is incorrect.
|
|
||||||
DimensionsVsPixelCount {
|
|
||||||
width: u32,
|
|
||||||
height: u32,
|
|
||||||
width_x_height: usize,
|
|
||||||
pixel_count: usize,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for BadIcon {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
BadIcon::ByteCountNotDivisibleBy4 { byte_count } => write!(f,
|
|
||||||
"The length of the `rgba` argument ({:?}) isn't divisible by 4, making it impossible to interpret as 32bpp RGBA pixels.",
|
|
||||||
byte_count,
|
|
||||||
),
|
|
||||||
BadIcon::DimensionsVsPixelCount {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
width_x_height,
|
|
||||||
pixel_count,
|
|
||||||
} => write!(f,
|
|
||||||
"The specified dimensions ({:?}x{:?}) don't match the number of pixels supplied by the `rgba` argument ({:?}). For those dimensions, the expected pixel count is {:?}.",
|
|
||||||
width, height, pixel_count, width_x_height,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Error for BadIcon {
|
|
||||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
|
||||||
Some(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
/// An icon used for the window titlebar, taskbar, etc.
|
|
||||||
pub struct Icon {
|
|
||||||
pub(crate) rgba: Vec<u8>,
|
|
||||||
pub(crate) width: u32,
|
|
||||||
pub(crate) height: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Icon {
|
|
||||||
/// Creates an `Icon` from 32bpp RGBA data.
|
|
||||||
///
|
|
||||||
/// The length of `rgba` must be divisible by 4, and `width * height` must equal
|
|
||||||
/// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error.
|
|
||||||
pub fn from_rgba(rgba: Vec<u8>, width: u32, height: u32) -> Result<Self, BadIcon> {
|
|
||||||
if rgba.len() % PIXEL_SIZE != 0 {
|
|
||||||
return Err(BadIcon::ByteCountNotDivisibleBy4 {
|
|
||||||
byte_count: rgba.len(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let pixel_count = rgba.len() / PIXEL_SIZE;
|
|
||||||
if pixel_count != (width * height) as usize {
|
|
||||||
Err(BadIcon::DimensionsVsPixelCount {
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
width_x_height: (width * height) as usize,
|
|
||||||
pixel_count,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Ok(Icon {
|
|
||||||
rgba,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
150
src/lib.rs
150
src/lib.rs
@@ -1,150 +0,0 @@
|
|||||||
//! Winit is a cross-platform window creation and event loop management library.
|
|
||||||
//!
|
|
||||||
//! # Building windows
|
|
||||||
//!
|
|
||||||
//! Before you can build a [`Window`], you first need to build an [`EventLoop`]. This is done with the
|
|
||||||
//! [`EventLoop::new()`] function.
|
|
||||||
//!
|
|
||||||
//! ```no_run
|
|
||||||
//! use winit::event_loop::EventLoop;
|
|
||||||
//! let event_loop = EventLoop::new();
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! Once this is done there are two ways to create a [`Window`]:
|
|
||||||
//!
|
|
||||||
//! - Calling [`Window::new(&event_loop)`][window_new].
|
|
||||||
//! - Calling [`let builder = WindowBuilder::new()`][window_builder_new] then [`builder.build(&event_loop)`][window_builder_build].
|
|
||||||
//!
|
|
||||||
//! The first method is the simplest, and will give you default values for everything. The second
|
|
||||||
//! method allows you to customize the way your [`Window`] will look and behave by modifying the
|
|
||||||
//! fields of the [`WindowBuilder`] object before you create the [`Window`].
|
|
||||||
//!
|
|
||||||
//! # Event handling
|
|
||||||
//!
|
|
||||||
//! Once a [`Window`] has been created, it will generate different *events*. A [`Window`] object can
|
|
||||||
//! generate [`WindowEvent`]s when certain input events occur, such as a cursor moving over the
|
|
||||||
//! window or a key getting pressed while the window is focused. Devices can generate
|
|
||||||
//! [`DeviceEvent`]s, which contain unfiltered event data that isn't specific to a certain window.
|
|
||||||
//! Some user activity, like mouse movement, can generate both a [`WindowEvent`] *and* a
|
|
||||||
//! [`DeviceEvent`]. You can also create and handle your own custom [`UserEvent`]s, if desired.
|
|
||||||
//!
|
|
||||||
//! You can retreive events by calling [`EventLoop::run`][event_loop_run]. This function will
|
|
||||||
//! dispatch events for every [`Window`] that was created with that particular [`EventLoop`], and
|
|
||||||
//! will run until the `control_flow` argument given to the closure is set to
|
|
||||||
//! [`ControlFlow`]`::`[`Exit`], at which point [`Event`]`::`[`LoopDestroyed`] is emitted and the
|
|
||||||
//! entire program terminates.
|
|
||||||
//!
|
|
||||||
//! Winit no longer uses a `EventLoop::poll_events() -> impl Iterator<Event>`-based event loop
|
|
||||||
//! model, since that can't be implemented properly on web and mobile platforms and works poorly on
|
|
||||||
//! most desktop platforms. However, this model can be re-implemented to an extent on desktops with
|
|
||||||
//! [`EventLoopExtDesktop::run_return`]. See that method's documentation for more reasons about why
|
|
||||||
//! it's discouraged, beyond mobile/web compatibility reasons.
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//! ```no_run
|
|
||||||
//! use winit::{
|
|
||||||
//! event::{Event, WindowEvent},
|
|
||||||
//! event_loop::{ControlFlow, EventLoop},
|
|
||||||
//! window::WindowBuilder,
|
|
||||||
//! };
|
|
||||||
//!
|
|
||||||
//! let event_loop = EventLoop::new();
|
|
||||||
//! let window = WindowBuilder::new().build(&event_loop).unwrap();
|
|
||||||
//!
|
|
||||||
//! event_loop.run(move |event, _, control_flow| {
|
|
||||||
//! // ControlFlow::Poll continuously runs the event loop, even if the OS hasn't
|
|
||||||
//! // dispatched any events. This is ideal for games and similar applications.
|
|
||||||
//! *control_flow = ControlFlow::Poll;
|
|
||||||
//!
|
|
||||||
//! // ControlFlow::Wait pauses the event loop if no events are available to process.
|
|
||||||
//! // This is ideal for non-game applications that only update in response to user
|
|
||||||
//! // input, and uses significantly less power/CPU time than ControlFlow::Poll.
|
|
||||||
//! *control_flow = ControlFlow::Wait;
|
|
||||||
//!
|
|
||||||
//! match event {
|
|
||||||
//! Event::WindowEvent {
|
|
||||||
//! event: WindowEvent::CloseRequested,
|
|
||||||
//! ..
|
|
||||||
//! } => {
|
|
||||||
//! println!("The close button was pressed; stopping");
|
|
||||||
//! *control_flow = ControlFlow::Exit
|
|
||||||
//! },
|
|
||||||
//! Event::MainEventsCleared => {
|
|
||||||
//! // Application update code.
|
|
||||||
//!
|
|
||||||
//! // Queue a RedrawRequested event.
|
|
||||||
//! window.request_redraw();
|
|
||||||
//! },
|
|
||||||
//! Event::RedrawRequested(_) => {
|
|
||||||
//! // Redraw the application.
|
|
||||||
//! //
|
|
||||||
//! // It's preferrable to render in this event rather than in MainEventsCleared, since
|
|
||||||
//! // rendering in here allows the program to gracefully handle redraws requested
|
|
||||||
//! // by the OS.
|
|
||||||
//! },
|
|
||||||
//! _ => ()
|
|
||||||
//! }
|
|
||||||
//! });
|
|
||||||
//! ```
|
|
||||||
//!
|
|
||||||
//! [`Event`]`::`[`WindowEvent`] has a [`WindowId`] member. In multi-window environments, it should be
|
|
||||||
//! compared to the value returned by [`Window::id()`][window_id_fn] to determine which [`Window`]
|
|
||||||
//! dispatched the event.
|
|
||||||
//!
|
|
||||||
//! # Drawing on the window
|
|
||||||
//!
|
|
||||||
//! Winit doesn't directly provide any methods for drawing on a [`Window`]. However it allows you to
|
|
||||||
//! retrieve the raw handle of the window (see the [`platform`] module), which in turn allows you
|
|
||||||
//! to create an OpenGL/Vulkan/DirectX/Metal/etc. context that can be used to render graphics.
|
|
||||||
//!
|
|
||||||
//! [`EventLoop`]: event_loop::EventLoop
|
|
||||||
//! [`EventLoopExtDesktop::run_return`]: ./platform/desktop/trait.EventLoopExtDesktop.html#tymethod.run_return
|
|
||||||
//! [`EventLoop::new()`]: event_loop::EventLoop::new
|
|
||||||
//! [event_loop_run]: event_loop::EventLoop::run
|
|
||||||
//! [`ControlFlow`]: event_loop::ControlFlow
|
|
||||||
//! [`Exit`]: event_loop::ControlFlow::Exit
|
|
||||||
//! [`Window`]: window::Window
|
|
||||||
//! [`WindowId`]: window::WindowId
|
|
||||||
//! [`WindowBuilder`]: window::WindowBuilder
|
|
||||||
//! [window_new]: window::Window::new
|
|
||||||
//! [window_builder_new]: window::WindowBuilder::new
|
|
||||||
//! [window_builder_build]: window::WindowBuilder::build
|
|
||||||
//! [window_id_fn]: window::Window::id
|
|
||||||
//! [`Event`]: event::Event
|
|
||||||
//! [`WindowEvent`]: event::WindowEvent
|
|
||||||
//! [`DeviceEvent`]: event::DeviceEvent
|
|
||||||
//! [`UserEvent`]: event::Event::UserEvent
|
|
||||||
//! [`LoopDestroyed`]: event::Event::LoopDestroyed
|
|
||||||
//! [`platform`]: platform
|
|
||||||
|
|
||||||
#![deny(rust_2018_idioms)]
|
|
||||||
#![deny(intra_doc_link_resolution_failure)]
|
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
#[macro_use]
|
|
||||||
extern crate lazy_static;
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
#[cfg(feature = "serde")]
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate bitflags;
|
|
||||||
#[cfg(any(target_os = "macos", target_os = "ios"))]
|
|
||||||
#[macro_use]
|
|
||||||
extern crate objc;
|
|
||||||
#[cfg(all(target_arch = "wasm32", feature = "std_web"))]
|
|
||||||
extern crate std_web as stdweb;
|
|
||||||
|
|
||||||
pub mod dpi;
|
|
||||||
#[macro_use]
|
|
||||||
pub mod error;
|
|
||||||
pub mod event;
|
|
||||||
pub mod event_loop;
|
|
||||||
mod icon;
|
|
||||||
pub mod monitor;
|
|
||||||
mod platform_impl;
|
|
||||||
pub mod window;
|
|
||||||
|
|
||||||
pub mod platform;
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
#![cfg(any(target_os = "android"))]
|
|
||||||
|
|
||||||
use crate::{EventLoop, Window, WindowBuilder};
|
|
||||||
use std::os::raw::c_void;
|
|
||||||
|
|
||||||
/// Additional methods on `EventLoop` that are specific to Android.
|
|
||||||
pub trait EventLoopExtAndroid {
|
|
||||||
/// Makes it possible for glutin to register a callback when a suspend event happens on Android
|
|
||||||
fn set_suspend_callback(&self, cb: Option<Box<dyn Fn(bool) -> ()>>);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventLoopExtAndroid for EventLoop {
|
|
||||||
fn set_suspend_callback(&self, cb: Option<Box<dyn Fn(bool) -> ()>>) {
|
|
||||||
self.event_loop.set_suspend_callback(cb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on `Window` that are specific to Android.
|
|
||||||
pub trait WindowExtAndroid {
|
|
||||||
fn native_window(&self) -> *const c_void;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowExtAndroid for Window {
|
|
||||||
#[inline]
|
|
||||||
fn native_window(&self) -> *const c_void {
|
|
||||||
self.window.native_window()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on `WindowBuilder` that are specific to Android.
|
|
||||||
pub trait WindowBuilderExtAndroid {}
|
|
||||||
|
|
||||||
impl WindowBuilderExtAndroid for WindowBuilder {}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
#![cfg(any(
|
|
||||||
target_os = "windows",
|
|
||||||
target_os = "macos",
|
|
||||||
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` that are specific to desktop platforms.
|
|
||||||
pub trait EventLoopExtDesktop {
|
|
||||||
/// A type provided by the user that can be passed through `Event::UserEvent`.
|
|
||||||
type UserEvent;
|
|
||||||
|
|
||||||
/// Initializes the `winit` event loop.
|
|
||||||
///
|
|
||||||
/// Unlike `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 apperance 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
|
|
||||||
/// underyling OS APIs, which cannot be hidden by Winit without severe stability reprecussions.
|
|
||||||
///
|
|
||||||
/// You are strongly encouraged to use `run`, unless the use of this is absolutely necessary.
|
|
||||||
fn run_return<F>(&mut self, event_handler: F)
|
|
||||||
where
|
|
||||||
F: FnMut(
|
|
||||||
Event<'_, Self::UserEvent>,
|
|
||||||
&EventLoopWindowTarget<Self::UserEvent>,
|
|
||||||
&mut ControlFlow,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopExtDesktop for EventLoop<T> {
|
|
||||||
type UserEvent = T;
|
|
||||||
|
|
||||||
fn run_return<F>(&mut self, event_handler: F)
|
|
||||||
where
|
|
||||||
F: FnMut(
|
|
||||||
Event<'_, Self::UserEvent>,
|
|
||||||
&EventLoopWindowTarget<Self::UserEvent>,
|
|
||||||
&mut ControlFlow,
|
|
||||||
),
|
|
||||||
{
|
|
||||||
self.event_loop.run_return(event_handler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,226 +0,0 @@
|
|||||||
#![cfg(target_os = "macos")]
|
|
||||||
|
|
||||||
use std::os::raw::c_void;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
dpi::LogicalSize,
|
|
||||||
event_loop::EventLoopWindowTarget,
|
|
||||||
monitor::MonitorHandle,
|
|
||||||
window::{Window, WindowBuilder},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Corresponds to `NSRequestUserAttentionType`.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
pub enum RequestUserAttentionType {
|
|
||||||
/// Corresponds to `NSCriticalRequest`.
|
|
||||||
///
|
|
||||||
/// Dock icon will bounce until the application is focused.
|
|
||||||
Critical,
|
|
||||||
|
|
||||||
/// Corresponds to `NSInformationalRequest`.
|
|
||||||
///
|
|
||||||
/// Dock icon will bounce once.
|
|
||||||
Informational,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for RequestUserAttentionType {
|
|
||||||
fn default() -> Self {
|
|
||||||
RequestUserAttentionType::Critical
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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;
|
|
||||||
|
|
||||||
/// Request user attention, causing the application's dock icon to bounce.
|
|
||||||
/// Note that this has no effect if the application is already focused.
|
|
||||||
fn request_user_attention(&self, request_type: RequestUserAttentionType);
|
|
||||||
|
|
||||||
/// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 request_user_attention(&self, request_type: RequestUserAttentionType) {
|
|
||||||
self.window.request_user_attention(request_type)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Corresponds to `NSApplicationActivationPolicy`.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
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 `with_decorations` method
|
|
||||||
/// on the base `WindowBuilder`:
|
|
||||||
///
|
|
||||||
/// - `with_titlebar_transparent`
|
|
||||||
/// - `with_title_hidden`
|
|
||||||
/// - `with_titlebar_hidden`
|
|
||||||
/// - `with_titlebar_buttons_hidden`
|
|
||||||
/// - `with_fullsize_content_view`
|
|
||||||
pub trait WindowBuilderExtMacOS {
|
|
||||||
/// Sets the activation policy for the window being built.
|
|
||||||
fn with_activation_policy(self, activation_policy: ActivationPolicy) -> WindowBuilder;
|
|
||||||
/// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowBuilderExtMacOS for WindowBuilder {
|
|
||||||
#[inline]
|
|
||||||
fn with_activation_policy(mut self, activation_policy: ActivationPolicy) -> WindowBuilder {
|
|
||||||
self.platform_specific.activation_policy = activation_policy;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> WindowBuilder {
|
|
||||||
self.platform_specific.disallow_hidpi = disallow_hidpi;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
|
|
||||||
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] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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:
|
|
||||||
//!
|
|
||||||
//! - `desktop` (available on `windows`, `unix`, and `macos`)
|
|
||||||
//!
|
|
||||||
//! 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 windows;
|
|
||||||
|
|
||||||
pub mod desktop;
|
|
||||||
pub mod web;
|
|
||||||
@@ -1,488 +0,0 @@
|
|||||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
|
||||||
|
|
||||||
use std::{os::raw, ptr, sync::Arc};
|
|
||||||
|
|
||||||
use smithay_client_toolkit::window::{ButtonState as SCTKButtonState, Theme as SCTKTheme};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
dpi::Size,
|
|
||||||
event_loop::{EventLoop, EventLoopWindowTarget},
|
|
||||||
monitor::MonitorHandle,
|
|
||||||
window::{Window, WindowBuilder},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::platform_impl::{
|
|
||||||
x11::{ffi::XVisualInfo, XConnection},
|
|
||||||
EventLoop as LinuxEventLoop, EventLoopWindowTarget as LinuxEventLoopWindowTarget,
|
|
||||||
Window as LinuxWindow,
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: stupid hack so that glutin can do its work
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub use crate::platform_impl::x11;
|
|
||||||
|
|
||||||
pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported};
|
|
||||||
|
|
||||||
/// Additional methods on `EventLoopWindowTarget` that are specific to Unix.
|
|
||||||
pub trait EventLoopWindowTargetExtUnix {
|
|
||||||
/// True if the `EventLoopWindowTarget` uses Wayland.
|
|
||||||
fn is_wayland(&self) -> bool;
|
|
||||||
///
|
|
||||||
/// True if the `EventLoopWindowTarget` uses X11.
|
|
||||||
fn is_x11(&self) -> bool;
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
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.
|
|
||||||
fn wayland_display(&self) -> Option<*mut raw::c_void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopWindowTargetExtUnix for EventLoopWindowTarget<T> {
|
|
||||||
#[inline]
|
|
||||||
fn is_wayland(&self) -> bool {
|
|
||||||
self.p.is_wayland()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn is_x11(&self) -> bool {
|
|
||||||
!self.p.is_wayland()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[doc(hidden)]
|
|
||||||
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
|
|
||||||
match self.p {
|
|
||||||
LinuxEventLoopWindowTarget::X(ref e) => Some(e.x_connection().clone()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn wayland_display(&self) -> Option<*mut raw::c_void> {
|
|
||||||
match self.p {
|
|
||||||
LinuxEventLoopWindowTarget::Wayland(ref p) => {
|
|
||||||
Some(p.display().get_display_ptr() as *mut _)
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on `EventLoop` that are specific to Unix.
|
|
||||||
pub trait EventLoopExtUnix {
|
|
||||||
/// Builds a new `EventLoop` that is forced to use X11.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// If called outside the main thread. To initialize an X11 event loop outside
|
|
||||||
/// the main thread, use [`new_x11_any_thread`](#tymethod.new_x11_any_thread).
|
|
||||||
fn new_x11() -> Result<Self, XNotSupported>
|
|
||||||
where
|
|
||||||
Self: Sized;
|
|
||||||
|
|
||||||
/// Builds a new `EventLoop` that is forced to use Wayland.
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// If called outside the main thread. To initialize a Wayland event loop outside
|
|
||||||
/// the main thread, use [`new_wayland_any_thread`](#tymethod.new_wayland_any_thread).
|
|
||||||
fn new_wayland() -> Self
|
|
||||||
where
|
|
||||||
Self: Sized;
|
|
||||||
|
|
||||||
/// Builds a new `EventLoop` on any thread.
|
|
||||||
///
|
|
||||||
/// This method bypasses the cross-platform compatibility requirement
|
|
||||||
/// that `EventLoop` be created on the main thread.
|
|
||||||
fn new_any_thread() -> Self
|
|
||||||
where
|
|
||||||
Self: Sized;
|
|
||||||
|
|
||||||
/// Builds a new X11 `EventLoop` on any thread.
|
|
||||||
///
|
|
||||||
/// This method bypasses the cross-platform compatibility requirement
|
|
||||||
/// that `EventLoop` be created on the main thread.
|
|
||||||
fn new_x11_any_thread() -> Result<Self, XNotSupported>
|
|
||||||
where
|
|
||||||
Self: Sized;
|
|
||||||
|
|
||||||
/// Builds a new Wayland `EventLoop` on any thread.
|
|
||||||
///
|
|
||||||
/// This method bypasses the cross-platform compatibility requirement
|
|
||||||
/// that `EventLoop` be created on the main thread.
|
|
||||||
fn new_wayland_any_thread() -> Self
|
|
||||||
where
|
|
||||||
Self: Sized;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn wrap_ev<T>(event_loop: LinuxEventLoop<T>) -> EventLoop<T> {
|
|
||||||
EventLoop {
|
|
||||||
event_loop,
|
|
||||||
_marker: std::marker::PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopExtUnix for EventLoop<T> {
|
|
||||||
#[inline]
|
|
||||||
fn new_any_thread() -> Self {
|
|
||||||
wrap_ev(LinuxEventLoop::new_any_thread())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn new_x11_any_thread() -> Result<Self, XNotSupported> {
|
|
||||||
LinuxEventLoop::new_x11_any_thread().map(wrap_ev)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn new_wayland_any_thread() -> Self {
|
|
||||||
wrap_ev(
|
|
||||||
LinuxEventLoop::new_wayland_any_thread()
|
|
||||||
// TODO: propagate
|
|
||||||
.expect("failed to open Wayland connection"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn new_x11() -> Result<Self, XNotSupported> {
|
|
||||||
LinuxEventLoop::new_x11().map(wrap_ev)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn new_wayland() -> Self {
|
|
||||||
wrap_ev(
|
|
||||||
LinuxEventLoop::new_wayland()
|
|
||||||
// TODO: propagate
|
|
||||||
.expect("failed to open Wayland connection"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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).
|
|
||||||
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 glutin `Window` is destroyed.
|
|
||||||
fn xlib_display(&self) -> Option<*mut raw::c_void>;
|
|
||||||
|
|
||||||
fn xlib_screen_id(&self) -> Option<raw::c_int>;
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
fn xlib_xconnection(&self) -> Option<Arc<XConnection>>;
|
|
||||||
|
|
||||||
/// Set window urgency hint (`XUrgencyHint`). Only relevant on X.
|
|
||||||
fn set_urgent(&self, is_urgent: bool);
|
|
||||||
|
|
||||||
/// 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 glutin `Window` is destroyed.
|
|
||||||
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 glutin `Window` is destroyed.
|
|
||||||
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 glutin `Window` is destroyed.
|
|
||||||
fn wayland_display(&self) -> Option<*mut raw::c_void>;
|
|
||||||
|
|
||||||
/// Sets the color theme of the client side window decorations on wayland
|
|
||||||
fn set_wayland_theme<T: Theme>(&self, theme: T);
|
|
||||||
|
|
||||||
/// 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]
|
|
||||||
fn xlib_window(&self) -> Option<raw::c_ulong> {
|
|
||||||
match self.window {
|
|
||||||
LinuxWindow::X(ref w) => Some(w.xlib_window()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn xlib_display(&self) -> Option<*mut raw::c_void> {
|
|
||||||
match self.window {
|
|
||||||
LinuxWindow::X(ref w) => Some(w.xlib_display()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn xlib_screen_id(&self) -> Option<raw::c_int> {
|
|
||||||
match self.window {
|
|
||||||
LinuxWindow::X(ref w) => Some(w.xlib_screen_id()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[doc(hidden)]
|
|
||||||
fn xlib_xconnection(&self) -> Option<Arc<XConnection>> {
|
|
||||||
match self.window {
|
|
||||||
LinuxWindow::X(ref w) => Some(w.xlib_xconnection()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_urgent(&self, is_urgent: bool) {
|
|
||||||
if let LinuxWindow::X(ref w) = self.window {
|
|
||||||
w.set_urgent(is_urgent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn xcb_connection(&self) -> Option<*mut raw::c_void> {
|
|
||||||
match self.window {
|
|
||||||
LinuxWindow::X(ref w) => Some(w.xcb_connection()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
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 _),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn wayland_display(&self) -> Option<*mut raw::c_void> {
|
|
||||||
match self.window {
|
|
||||||
LinuxWindow::Wayland(ref w) => Some(w.display().as_ref().c_ptr() as *mut _),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_wayland_theme<T: Theme>(&self, theme: T) {
|
|
||||||
match self.window {
|
|
||||||
LinuxWindow::Wayland(ref w) => w.set_theme(WaylandTheme(theme)),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn is_ready(&self) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on `WindowBuilder` that are specific to Unix.
|
|
||||||
pub trait WindowBuilderExtUnix {
|
|
||||||
fn with_x11_visual<T>(self, visual_infos: *const T) -> Self;
|
|
||||||
fn with_x11_screen(self, screen_id: i32) -> Self;
|
|
||||||
|
|
||||||
/// Build window with `WM_CLASS` hint; defaults to the name of the binary. Only relevant on X11.
|
|
||||||
fn with_class(self, class: String, instance: String) -> Self;
|
|
||||||
/// Build window with override-redirect flag; defaults to false. Only relevant on 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.
|
|
||||||
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.
|
|
||||||
fn with_gtk_theme_variant(self, variant: String) -> Self;
|
|
||||||
/// Build window with resize increment hint. Only implemented on X11.
|
|
||||||
fn with_resize_increments<S: Into<Size>>(self, increments: S) -> Self;
|
|
||||||
/// Build window with base size hint. Only implemented on X11.
|
|
||||||
fn with_base_size<S: Into<Size>>(self, base_size: S) -> Self;
|
|
||||||
|
|
||||||
/// Build window with a given application ID. It should match the `.desktop` file distributed with
|
|
||||||
/// your program. Only relevant on Wayland.
|
|
||||||
///
|
|
||||||
/// 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_app_id(self, app_id: String) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowBuilderExtUnix for WindowBuilder {
|
|
||||||
#[inline]
|
|
||||||
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]
|
|
||||||
fn with_x11_screen(mut self, screen_id: i32) -> Self {
|
|
||||||
self.platform_specific.screen_id = Some(screen_id);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_class(mut self, instance: String, class: String) -> Self {
|
|
||||||
self.platform_specific.class = Some((instance, class));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_override_redirect(mut self, override_redirect: bool) -> Self {
|
|
||||||
self.platform_specific.override_redirect = override_redirect;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_x11_window_type(mut self, x11_window_types: Vec<XWindowType>) -> Self {
|
|
||||||
self.platform_specific.x11_window_types = x11_window_types;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_gtk_theme_variant(mut self, variant: String) -> Self {
|
|
||||||
self.platform_specific.gtk_theme_variant = Some(variant);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_resize_increments<S: Into<Size>>(mut self, increments: S) -> Self {
|
|
||||||
self.platform_specific.resize_increments = Some(increments.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_base_size<S: Into<Size>>(mut self, base_size: S) -> Self {
|
|
||||||
self.platform_specific.base_size = Some(base_size.into());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_app_id(mut self, app_id: String) -> Self {
|
|
||||||
self.platform_specific.app_id = Some(app_id);
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrapper for implementing SCTK's theme trait.
|
|
||||||
struct WaylandTheme<T: Theme>(T);
|
|
||||||
|
|
||||||
pub trait Theme: Send + 'static {
|
|
||||||
/// Primary color of the scheme.
|
|
||||||
fn primary_color(&self, window_active: bool) -> [u8; 4];
|
|
||||||
|
|
||||||
/// Secondary color of the scheme.
|
|
||||||
fn secondary_color(&self, window_active: bool) -> [u8; 4];
|
|
||||||
|
|
||||||
/// Color for the close button.
|
|
||||||
fn close_button_color(&self, status: ButtonState) -> [u8; 4];
|
|
||||||
|
|
||||||
/// Icon color for the close button, defaults to the secondary color.
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn close_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
|
|
||||||
self.secondary_color(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Background color for the maximize button.
|
|
||||||
fn maximize_button_color(&self, status: ButtonState) -> [u8; 4];
|
|
||||||
|
|
||||||
/// Icon color for the maximize button, defaults to the secondary color.
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn maximize_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
|
|
||||||
self.secondary_color(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Background color for the minimize button.
|
|
||||||
fn minimize_button_color(&self, status: ButtonState) -> [u8; 4];
|
|
||||||
|
|
||||||
/// Icon color for the minimize button, defaults to the secondary color.
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn minimize_button_icon_color(&self, status: ButtonState) -> [u8; 4] {
|
|
||||||
self.secondary_color(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Theme> SCTKTheme for WaylandTheme<T> {
|
|
||||||
fn get_primary_color(&self, active: bool) -> [u8; 4] {
|
|
||||||
self.0.primary_color(active)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_secondary_color(&self, active: bool) -> [u8; 4] {
|
|
||||||
self.0.secondary_color(active)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_close_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
|
||||||
self.0.close_button_color(ButtonState::from_sctk(status))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_close_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
|
||||||
self.0.close_button_color(ButtonState::from_sctk(status))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_maximize_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
|
||||||
self.0.maximize_button_color(ButtonState::from_sctk(status))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_maximize_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
|
||||||
self.0
|
|
||||||
.maximize_button_icon_color(ButtonState::from_sctk(status))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_minimize_button_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
|
||||||
self.0.minimize_button_color(ButtonState::from_sctk(status))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_minimize_button_icon_color(&self, status: SCTKButtonState) -> [u8; 4] {
|
|
||||||
self.0
|
|
||||||
.minimize_button_icon_color(ButtonState::from_sctk(status))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum ButtonState {
|
|
||||||
/// Button is being hovered over by pointer.
|
|
||||||
Hovered,
|
|
||||||
/// Button is not being hovered over by pointer.
|
|
||||||
Idle,
|
|
||||||
/// Button is disabled.
|
|
||||||
Disabled,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ButtonState {
|
|
||||||
fn from_sctk(button_state: SCTKButtonState) -> Self {
|
|
||||||
match button_state {
|
|
||||||
SCTKButtonState::Hovered => Self::Hovered,
|
|
||||||
SCTKButtonState::Idle => Self::Idle,
|
|
||||||
SCTKButtonState::Disabled => Self::Disabled,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +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 `WindowExtStdweb` or
|
|
||||||
//! `WindowExtWebSys` traits (depending on your web backend) to retrieve the canvas from the
|
|
||||||
//! Window. Alternatively, use the `WindowBuilderExtStdweb` or `WindowBuilderExtWebSys` to provide
|
|
||||||
//! your own canvas.
|
|
||||||
|
|
||||||
use crate::window::WindowBuilder;
|
|
||||||
|
|
||||||
#[cfg(feature = "stdweb")]
|
|
||||||
use stdweb::web::html_element::CanvasElement;
|
|
||||||
|
|
||||||
#[cfg(feature = "stdweb")]
|
|
||||||
pub trait WindowExtStdweb {
|
|
||||||
fn canvas(&self) -> CanvasElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "web-sys")]
|
|
||||||
use web_sys::HtmlCanvasElement;
|
|
||||||
|
|
||||||
#[cfg(feature = "web-sys")]
|
|
||||||
pub trait WindowExtWebSys {
|
|
||||||
fn canvas(&self) -> HtmlCanvasElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "stdweb")]
|
|
||||||
pub trait WindowBuilderExtStdweb {
|
|
||||||
fn with_canvas(self, canvas: Option<CanvasElement>) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "stdweb")]
|
|
||||||
impl WindowBuilderExtStdweb for WindowBuilder {
|
|
||||||
fn with_canvas(mut self, canvas: Option<CanvasElement>) -> Self {
|
|
||||||
self.platform_specific.canvas = canvas;
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "web-sys")]
|
|
||||||
pub trait WindowBuilderExtWebSys {
|
|
||||||
fn with_canvas(self, canvas: Option<HtmlCanvasElement>) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "web-sys")]
|
|
||||||
impl WindowBuilderExtWebSys for WindowBuilder {
|
|
||||||
fn with_canvas(mut self, canvas: Option<HtmlCanvasElement>) -> Self {
|
|
||||||
self.platform_specific.canvas = canvas;
|
|
||||||
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
#![cfg(target_os = "windows")]
|
|
||||||
|
|
||||||
use std::os::raw::c_void;
|
|
||||||
|
|
||||||
use libc;
|
|
||||||
use winapi::shared::windef::HWND;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
event::DeviceId,
|
|
||||||
event_loop::EventLoop,
|
|
||||||
monitor::MonitorHandle,
|
|
||||||
platform_impl::EventLoop as WindowsEventLoop,
|
|
||||||
window::{Icon, Window, WindowBuilder},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Additional methods on `EventLoop` that are specific to Windows.
|
|
||||||
pub trait EventLoopExtWindows {
|
|
||||||
/// Creates an event loop off of the main thread.
|
|
||||||
///
|
|
||||||
/// # `Window` caveats
|
|
||||||
///
|
|
||||||
/// Note that any `Window` created on the new thread will be destroyed when the thread
|
|
||||||
/// terminates. Attempting to use a `Window` after its parent thread terminates has
|
|
||||||
/// unspecified, although explicitly not undefined, behavior.
|
|
||||||
fn new_any_thread() -> Self
|
|
||||||
where
|
|
||||||
Self: Sized;
|
|
||||||
|
|
||||||
/// By default, winit on Windows will attempt to enable process-wide DPI awareness. If that's
|
|
||||||
/// undesirable, you can create an `EventLoop` using this function instead.
|
|
||||||
fn new_dpi_unaware() -> Self
|
|
||||||
where
|
|
||||||
Self: Sized;
|
|
||||||
|
|
||||||
/// Creates a DPI-unaware event loop off of the main thread.
|
|
||||||
///
|
|
||||||
/// The `Window` caveats in [`new_any_thread`](EventLoopExtWindows::new_any_thread) also apply here.
|
|
||||||
fn new_dpi_unaware_any_thread() -> Self
|
|
||||||
where
|
|
||||||
Self: Sized;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopExtWindows for EventLoop<T> {
|
|
||||||
#[inline]
|
|
||||||
fn new_any_thread() -> Self {
|
|
||||||
EventLoop {
|
|
||||||
event_loop: WindowsEventLoop::new_any_thread(),
|
|
||||||
_marker: ::std::marker::PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn new_dpi_unaware() -> Self {
|
|
||||||
EventLoop {
|
|
||||||
event_loop: WindowsEventLoop::new_dpi_unaware(),
|
|
||||||
_marker: ::std::marker::PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn new_dpi_unaware_any_thread() -> Self {
|
|
||||||
EventLoop {
|
|
||||||
event_loop: WindowsEventLoop::new_dpi_unaware_any_thread(),
|
|
||||||
_marker: ::std::marker::PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on `Window` that are specific to Windows.
|
|
||||||
pub trait WindowExtWindows {
|
|
||||||
/// Returns the HINSTANCE of the window
|
|
||||||
fn hinstance(&self) -> *mut libc::c_void;
|
|
||||||
/// Returns the native handle that is used by this window.
|
|
||||||
///
|
|
||||||
/// The pointer will become invalid when the native window was destroyed.
|
|
||||||
fn hwnd(&self) -> *mut libc::c_void;
|
|
||||||
|
|
||||||
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
|
|
||||||
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>);
|
|
||||||
|
|
||||||
/// Whether the system theme is currently Windows 10's "Dark Mode".
|
|
||||||
fn is_dark_mode(&self) -> bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowExtWindows for Window {
|
|
||||||
#[inline]
|
|
||||||
fn hinstance(&self) -> *mut libc::c_void {
|
|
||||||
self.window.hinstance() as *mut _
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn hwnd(&self) -> *mut libc::c_void {
|
|
||||||
self.window.hwnd() as *mut _
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>) {
|
|
||||||
self.window.set_taskbar_icon(taskbar_icon)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn is_dark_mode(&self) -> bool {
|
|
||||||
self.window.is_dark_mode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on `WindowBuilder` that are specific to Windows.
|
|
||||||
pub trait WindowBuilderExtWindows {
|
|
||||||
/// Sets a parent to the window to be created.
|
|
||||||
fn with_parent_window(self, parent: HWND) -> WindowBuilder;
|
|
||||||
|
|
||||||
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
|
|
||||||
fn with_taskbar_icon(self, taskbar_icon: Option<Icon>) -> WindowBuilder;
|
|
||||||
|
|
||||||
/// This sets `WS_EX_NOREDIRECTIONBITMAP`.
|
|
||||||
fn with_no_redirection_bitmap(self, flag: bool) -> WindowBuilder;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowBuilderExtWindows for WindowBuilder {
|
|
||||||
#[inline]
|
|
||||||
fn with_parent_window(mut self, parent: HWND) -> WindowBuilder {
|
|
||||||
self.platform_specific.parent = Some(parent);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_taskbar_icon(mut self, taskbar_icon: Option<Icon>) -> WindowBuilder {
|
|
||||||
self.platform_specific.taskbar_icon = taskbar_icon;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_no_redirection_bitmap(mut self, flag: bool) -> WindowBuilder {
|
|
||||||
self.platform_specific.no_redirection_bitmap = flag;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on `MonitorHandle` that are specific to Windows.
|
|
||||||
pub trait MonitorHandleExtWindows {
|
|
||||||
/// Returns the name of the monitor adapter specific to the Win32 API.
|
|
||||||
fn native_id(&self) -> String;
|
|
||||||
|
|
||||||
/// Returns the handle of the monitor - `HMONITOR`.
|
|
||||||
fn hmonitor(&self) -> *mut c_void;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MonitorHandleExtWindows for MonitorHandle {
|
|
||||||
#[inline]
|
|
||||||
fn native_id(&self) -> String {
|
|
||||||
self.inner.native_identifier()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn hmonitor(&self) -> *mut c_void {
|
|
||||||
self.inner.hmonitor() as *mut _
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Additional methods on `DeviceId` that are specific to Windows.
|
|
||||||
pub trait DeviceIdExtWindows {
|
|
||||||
/// Returns an identifier that persistently refers to this specific device.
|
|
||||||
///
|
|
||||||
/// Will return `None` if the device is no longer available.
|
|
||||||
fn persistent_identifier(&self) -> Option<String>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DeviceIdExtWindows for DeviceId {
|
|
||||||
#[inline]
|
|
||||||
fn persistent_identifier(&self) -> Option<String> {
|
|
||||||
self.0.persistent_identifier()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
#![allow(dead_code)]
|
|
||||||
#![allow(non_snake_case)]
|
|
||||||
#![allow(non_camel_case_types)]
|
|
||||||
#![allow(non_upper_case_globals)]
|
|
||||||
|
|
||||||
use libc;
|
|
||||||
use std::os::raw;
|
|
||||||
|
|
||||||
#[link(name = "android")]
|
|
||||||
#[link(name = "EGL")]
|
|
||||||
#[link(name = "GLESv2")]
|
|
||||||
extern "C" {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
** asset_manager.h
|
|
||||||
**/
|
|
||||||
pub type AAssetManager = raw::c_void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
** native_window.h
|
|
||||||
**/
|
|
||||||
pub type ANativeWindow = raw::c_void;
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
pub fn ANativeWindow_getHeight(window: *const ANativeWindow) -> libc::int32_t;
|
|
||||||
pub fn ANativeWindow_getWidth(window: *const ANativeWindow) -> libc::int32_t;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
** native_activity.h
|
|
||||||
**/
|
|
||||||
pub type JavaVM = ();
|
|
||||||
pub type JNIEnv = ();
|
|
||||||
pub type jobject = *const libc::c_void;
|
|
||||||
|
|
||||||
pub type AInputQueue = (); // FIXME: wrong
|
|
||||||
pub type ARect = (); // FIXME: wrong
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct ANativeActivity {
|
|
||||||
pub callbacks: *mut ANativeActivityCallbacks,
|
|
||||||
pub vm: *mut JavaVM,
|
|
||||||
pub env: *mut JNIEnv,
|
|
||||||
pub clazz: jobject,
|
|
||||||
pub internalDataPath: *const libc::c_char,
|
|
||||||
pub externalDataPath: *const libc::c_char,
|
|
||||||
pub sdkVersion: libc::int32_t,
|
|
||||||
pub instance: *mut libc::c_void,
|
|
||||||
pub assetManager: *mut AAssetManager,
|
|
||||||
pub obbPath: *const libc::c_char,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct ANativeActivityCallbacks {
|
|
||||||
pub onStart: extern "C" fn(*mut ANativeActivity),
|
|
||||||
pub onResume: extern "C" fn(*mut ANativeActivity),
|
|
||||||
pub onSaveInstanceState: extern "C" fn(*mut ANativeActivity, *mut libc::size_t),
|
|
||||||
pub onPause: extern "C" fn(*mut ANativeActivity),
|
|
||||||
pub onStop: extern "C" fn(*mut ANativeActivity),
|
|
||||||
pub onDestroy: extern "C" fn(*mut ANativeActivity),
|
|
||||||
pub onWindowFocusChanged: extern "C" fn(*mut ANativeActivity, libc::c_int),
|
|
||||||
pub onNativeWindowCreated: extern "C" fn(*mut ANativeActivity, *const ANativeWindow),
|
|
||||||
pub onNativeWindowResized: extern "C" fn(*mut ANativeActivity, *const ANativeWindow),
|
|
||||||
pub onNativeWindowRedrawNeeded: extern "C" fn(*mut ANativeActivity, *const ANativeWindow),
|
|
||||||
pub onNativeWindowDestroyed: extern "C" fn(*mut ANativeActivity, *const ANativeWindow),
|
|
||||||
pub onInputQueueCreated: extern "C" fn(*mut ANativeActivity, *mut AInputQueue),
|
|
||||||
pub onInputQueueDestroyed: extern "C" fn(*mut ANativeActivity, *mut AInputQueue),
|
|
||||||
pub onContentRectChanged: extern "C" fn(*mut ANativeActivity, *const ARect),
|
|
||||||
pub onConfigurationChanged: extern "C" fn(*mut ANativeActivity),
|
|
||||||
pub onLowMemory: extern "C" fn(*mut ANativeActivity),
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
** looper.h
|
|
||||||
**/
|
|
||||||
pub type ALooper = ();
|
|
||||||
|
|
||||||
#[link(name = "android")]
|
|
||||||
extern "C" {
|
|
||||||
pub fn ALooper_forThread() -> *const ALooper;
|
|
||||||
pub fn ALooper_acquire(looper: *const ALooper);
|
|
||||||
pub fn ALooper_release(looper: *const ALooper);
|
|
||||||
pub fn ALooper_prepare(opts: libc::c_int) -> *const ALooper;
|
|
||||||
pub fn ALooper_pollOnce(
|
|
||||||
timeoutMillis: libc::c_int,
|
|
||||||
outFd: *mut libc::c_int,
|
|
||||||
outEvents: *mut libc::c_int,
|
|
||||||
outData: *mut *mut libc::c_void,
|
|
||||||
) -> libc::c_int;
|
|
||||||
pub fn ALooper_pollAll(
|
|
||||||
timeoutMillis: libc::c_int,
|
|
||||||
outFd: *mut libc::c_int,
|
|
||||||
outEvents: *mut libc::c_int,
|
|
||||||
outData: *mut *mut libc::c_void,
|
|
||||||
) -> libc::c_int;
|
|
||||||
pub fn ALooper_wake(looper: *const ALooper);
|
|
||||||
pub fn ALooper_addFd(
|
|
||||||
looper: *const ALooper,
|
|
||||||
fd: libc::c_int,
|
|
||||||
ident: libc::c_int,
|
|
||||||
events: libc::c_int,
|
|
||||||
callback: ALooper_callbackFunc,
|
|
||||||
data: *mut libc::c_void,
|
|
||||||
) -> libc::c_int;
|
|
||||||
pub fn ALooper_removeFd(looper: *const ALooper, fd: libc::c_int) -> libc::c_int;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const ALOOPER_PREPARE_ALLOW_NON_CALLBACKS: libc::c_int = 1 << 0;
|
|
||||||
|
|
||||||
pub const ALOOPER_POLL_WAKE: libc::c_int = -1;
|
|
||||||
pub const ALOOPER_POLL_CALLBACK: libc::c_int = -2;
|
|
||||||
pub const ALOOPER_POLL_TIMEOUT: libc::c_int = -3;
|
|
||||||
pub const ALOOPER_POLL_ERROR: libc::c_int = -4;
|
|
||||||
|
|
||||||
pub const ALOOPER_EVENT_INPUT: libc::c_int = 1 << 0;
|
|
||||||
pub const ALOOPER_EVENT_OUTPUT: libc::c_int = 1 << 1;
|
|
||||||
pub const ALOOPER_EVENT_ERROR: libc::c_int = 1 << 2;
|
|
||||||
pub const ALOOPER_EVENT_HANGUP: libc::c_int = 1 << 3;
|
|
||||||
pub const ALOOPER_EVENT_INVALID: libc::c_int = 1 << 4;
|
|
||||||
|
|
||||||
pub type ALooper_callbackFunc =
|
|
||||||
extern "C" fn(libc::c_int, libc::c_int, *mut libc::c_void) -> libc::c_int;
|
|
||||||
@@ -1,448 +0,0 @@
|
|||||||
#![cfg(target_os = "android")]
|
|
||||||
|
|
||||||
extern crate android_glue;
|
|
||||||
|
|
||||||
mod ffi;
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
cell::RefCell,
|
|
||||||
collections::VecDeque,
|
|
||||||
fmt,
|
|
||||||
os::raw::c_void,
|
|
||||||
sync::mpsc::{channel, Receiver},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
error::{ExternalError, NotSupportedError},
|
|
||||||
events::{Touch, TouchPhase},
|
|
||||||
window::MonitorHandle as RootMonitorHandle,
|
|
||||||
CreationError, CursorIcon, Event, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize,
|
|
||||||
WindowAttributes, WindowEvent, WindowId as RootWindowId,
|
|
||||||
};
|
|
||||||
use raw_window_handle::{android::AndroidHandle, RawWindowHandle};
|
|
||||||
use CreationError::OsError;
|
|
||||||
|
|
||||||
pub type OsError = std::io::Error;
|
|
||||||
|
|
||||||
pub struct EventLoop {
|
|
||||||
event_rx: Receiver<android_glue::Event>,
|
|
||||||
suspend_callback: RefCell<Option<Box<dyn Fn(bool) -> ()>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct EventLoopProxy;
|
|
||||||
|
|
||||||
impl EventLoop {
|
|
||||||
pub fn new() -> EventLoop {
|
|
||||||
let (tx, rx) = channel();
|
|
||||||
android_glue::add_sender(tx);
|
|
||||||
EventLoop {
|
|
||||||
event_rx: rx,
|
|
||||||
suspend_callback: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
|
||||||
let mut rb = VecDeque::with_capacity(1);
|
|
||||||
rb.push_back(MonitorHandle);
|
|
||||||
rb
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
|
||||||
MonitorHandle
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn poll_events<F>(&mut self, mut callback: F)
|
|
||||||
where
|
|
||||||
F: FnMut(::Event),
|
|
||||||
{
|
|
||||||
while let Ok(event) = self.event_rx.try_recv() {
|
|
||||||
let e = match event {
|
|
||||||
android_glue::Event::EventMotion(motion) => {
|
|
||||||
let dpi_factor = MonitorHandle.scale_factor();
|
|
||||||
let location = LogicalPosition::from_physical(
|
|
||||||
(motion.x as f64, motion.y as f64),
|
|
||||||
dpi_factor,
|
|
||||||
);
|
|
||||||
Some(Event::WindowEvent {
|
|
||||||
window_id: RootWindowId(WindowId),
|
|
||||||
event: WindowEvent::Touch(Touch {
|
|
||||||
phase: match motion.action {
|
|
||||||
android_glue::MotionAction::Down => TouchPhase::Started,
|
|
||||||
android_glue::MotionAction::Move => TouchPhase::Moved,
|
|
||||||
android_glue::MotionAction::Up => TouchPhase::Ended,
|
|
||||||
android_glue::MotionAction::Cancel => TouchPhase::Cancelled,
|
|
||||||
},
|
|
||||||
location,
|
|
||||||
force: None, // TODO
|
|
||||||
id: motion.pointer_id as u64,
|
|
||||||
device_id: DEVICE_ID,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
android_glue::Event::InitWindow => {
|
|
||||||
// The activity went to foreground.
|
|
||||||
if let Some(cb) = self.suspend_callback.borrow().as_ref() {
|
|
||||||
(*cb)(false);
|
|
||||||
}
|
|
||||||
Some(Event::Resumed)
|
|
||||||
}
|
|
||||||
android_glue::Event::TermWindow => {
|
|
||||||
// The activity went to background.
|
|
||||||
if let Some(cb) = self.suspend_callback.borrow().as_ref() {
|
|
||||||
(*cb)(true);
|
|
||||||
}
|
|
||||||
Some(Event::Suspended)
|
|
||||||
}
|
|
||||||
android_glue::Event::WindowResized | android_glue::Event::ConfigChanged => {
|
|
||||||
// Activity Orientation changed or resized.
|
|
||||||
let native_window = unsafe { android_glue::native_window() };
|
|
||||||
if native_window.is_null() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let dpi_factor = MonitorHandle.scale_factor();
|
|
||||||
let physical_size = MonitorHandle.size();
|
|
||||||
let size = LogicalSize::from_physical(physical_size, dpi_factor);
|
|
||||||
Some(Event::WindowEvent {
|
|
||||||
window_id: RootWindowId(WindowId),
|
|
||||||
event: WindowEvent::Resized(size),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
android_glue::Event::WindowRedrawNeeded => {
|
|
||||||
// The activity needs to be redrawn.
|
|
||||||
Some(Event::WindowEvent {
|
|
||||||
window_id: RootWindowId(WindowId),
|
|
||||||
event: WindowEvent::Redraw,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
android_glue::Event::Wake => Some(Event::Awakened),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(event) = e {
|
|
||||||
callback(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_suspend_callback(&self, cb: Option<Box<dyn Fn(bool) -> ()>>) {
|
|
||||||
*self.suspend_callback.borrow_mut() = cb;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_forever<F>(&mut self, mut callback: F)
|
|
||||||
where
|
|
||||||
F: FnMut(::Event) -> ::ControlFlow,
|
|
||||||
{
|
|
||||||
// Yeah that's a very bad implementation.
|
|
||||||
loop {
|
|
||||||
let mut control_flow = ::ControlFlow::Continue;
|
|
||||||
self.poll_events(|e| {
|
|
||||||
if let ::ControlFlow::Break = callback(e) {
|
|
||||||
control_flow = ::ControlFlow::Break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if let ::ControlFlow::Break = control_flow {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
::std::thread::sleep(::std::time::Duration::from_millis(5));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_proxy(&self) -> EventLoopProxy {
|
|
||||||
EventLoopProxy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl EventLoopProxy {
|
|
||||||
pub fn wakeup(&self) -> Result<(), ::EventLoopClosed<()>> {
|
|
||||||
android_glue::wake_event_loop();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct WindowId;
|
|
||||||
|
|
||||||
impl WindowId {
|
|
||||||
pub unsafe fn dummy() -> Self {
|
|
||||||
WindowId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct DeviceId;
|
|
||||||
|
|
||||||
impl DeviceId {
|
|
||||||
pub unsafe fn dummy() -> Self {
|
|
||||||
DeviceId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Window {
|
|
||||||
native_window: *const c_void,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct MonitorHandle;
|
|
||||||
|
|
||||||
impl fmt::Debug for MonitorHandle {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct MonitorHandle {
|
|
||||||
name: Option<String>,
|
|
||||||
dimensions: PhysicalSize<u32>,
|
|
||||||
position: PhysicalPosition<i32>,
|
|
||||||
scale_factor: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
let monitor_id_proxy = MonitorHandle {
|
|
||||||
name: self.name(),
|
|
||||||
dimensions: self.size(),
|
|
||||||
position: self.outer_position(),
|
|
||||||
scale_factor: self.scale_factor(),
|
|
||||||
};
|
|
||||||
|
|
||||||
monitor_id_proxy.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MonitorHandle {
|
|
||||||
#[inline]
|
|
||||||
pub fn name(&self) -> Option<String> {
|
|
||||||
Some("Primary".to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn size(&self) -> PhysicalSize<u32> {
|
|
||||||
unsafe {
|
|
||||||
let window = android_glue::native_window();
|
|
||||||
(
|
|
||||||
ffi::ANativeWindow_getWidth(window) as f64,
|
|
||||||
ffi::ANativeWindow_getHeight(window) as f64,
|
|
||||||
)
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn outer_position(&self) -> PhysicalPosition<i32> {
|
|
||||||
// Android assumes single screen
|
|
||||||
(0, 0).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn scale_factor(&self) -> f64 {
|
|
||||||
1.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
|
||||||
pub struct PlatformSpecificWindowBuilderAttributes;
|
|
||||||
#[derive(Clone, Default)]
|
|
||||||
pub struct PlatformSpecificHeadlessBuilderAttributes;
|
|
||||||
|
|
||||||
impl Window {
|
|
||||||
pub fn new(
|
|
||||||
_: &EventLoop,
|
|
||||||
win_attribs: WindowAttributes,
|
|
||||||
_: PlatformSpecificWindowBuilderAttributes,
|
|
||||||
) -> Result<Window, CreationError> {
|
|
||||||
let native_window = unsafe { android_glue::native_window() };
|
|
||||||
if native_window.is_null() {
|
|
||||||
return Err(OsError(format!("Android's native window is null")));
|
|
||||||
}
|
|
||||||
|
|
||||||
android_glue::set_multitouch(true);
|
|
||||||
|
|
||||||
Ok(Window {
|
|
||||||
native_window: native_window as *const _,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn native_window(&self) -> *const c_void {
|
|
||||||
self.native_window
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_title(&self, _: &str) {
|
|
||||||
// N/A
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn show(&self) {
|
|
||||||
// N/A
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn hide(&self) {
|
|
||||||
// N/A
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn outer_position(&self) -> Option<LogicalPosition<f64>> {
|
|
||||||
// N/A
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn inner_position(&self) -> Option<LogicalPosition<f64>> {
|
|
||||||
// N/A
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_outer_position(&self, _position: LogicalPosition<f64>) {
|
|
||||||
// N/A
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_min_inner_size(&self, _dimensions: Option<LogicalSize<f64>>) {
|
|
||||||
// N/A
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_max_inner_size(&self, _dimensions: Option<LogicalSize<f64>>) {
|
|
||||||
// N/A
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_resizable(&self, _resizable: bool) {
|
|
||||||
// N/A
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn inner_size(&self) -> Option<LogicalSize<f64>> {
|
|
||||||
if self.native_window.is_null() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let dpi_factor = self.scale_factor();
|
|
||||||
let physical_size = self.current_monitor().size();
|
|
||||||
Some(LogicalSize::from_physical(physical_size, dpi_factor))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn outer_size(&self) -> Option<LogicalSize<f64>> {
|
|
||||||
self.inner_size()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_inner_size(&self, _size: LogicalSize<f64>) {
|
|
||||||
// N/A
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn scale_factor(&self) -> f64 {
|
|
||||||
self.current_monitor().scale_factor()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_cursor_icon(&self, _: CursorIcon) {
|
|
||||||
// N/A
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> {
|
|
||||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn hide_cursor(&self, _hide: bool) {
|
|
||||||
// N/A
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_cursor_position(
|
|
||||||
&self,
|
|
||||||
_position: LogicalPosition<f64>,
|
|
||||||
) -> Result<(), ExternalError> {
|
|
||||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_minimized(&self, _minimized: bool) {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_maximized(&self, _maximized: bool) {
|
|
||||||
// N/A
|
|
||||||
// Android has single screen maximized apps so nothing to do
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
|
|
||||||
// N/A
|
|
||||||
// Android has single screen maximized apps so nothing to do
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_fullscreen(&self, _monitor: Option<RootMonitorHandle>) {
|
|
||||||
// N/A
|
|
||||||
// Android has single screen maximized apps so nothing to do
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_decorations(&self, _decorations: bool) {
|
|
||||||
// N/A
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_always_on_top(&self, _always_on_top: bool) {
|
|
||||||
// N/A
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_window_icon(&self, _icon: Option<::Icon>) {
|
|
||||||
// N/A
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_ime_position(&self, _spot: LogicalPosition<f64>) {
|
|
||||||
// N/A
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn current_monitor(&self) -> RootMonitorHandle {
|
|
||||||
RootMonitorHandle {
|
|
||||||
inner: MonitorHandle,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
|
||||||
let mut rb = VecDeque::with_capacity(1);
|
|
||||||
rb.push_back(MonitorHandle);
|
|
||||||
rb
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
|
||||||
MonitorHandle
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn id(&self) -> WindowId {
|
|
||||||
WindowId
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
|
||||||
let handle = AndroidHandle {
|
|
||||||
a_native_window: self.native_window,
|
|
||||||
..WindowsHandle::empty()
|
|
||||||
};
|
|
||||||
RawWindowHandle::Android(handle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl Send for Window {}
|
|
||||||
unsafe impl Sync for Window {}
|
|
||||||
|
|
||||||
// Constant device ID, to be removed when this backend is updated to report real device IDs.
|
|
||||||
const DEVICE_ID: ::DeviceId = ::DeviceId(DeviceId);
|
|
||||||
@@ -1,338 +0,0 @@
|
|||||||
use std::{
|
|
||||||
collections::VecDeque,
|
|
||||||
ffi::c_void,
|
|
||||||
fmt::{self, Debug},
|
|
||||||
marker::PhantomData,
|
|
||||||
mem, ptr,
|
|
||||||
sync::mpsc::{self, Receiver, Sender},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
dpi::LogicalSize,
|
|
||||||
event::Event,
|
|
||||||
event_loop::{
|
|
||||||
ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootEventLoopWindowTarget,
|
|
||||||
},
|
|
||||||
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, NSString, 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>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventLoop<T: 'static> {
|
|
||||||
window_target: RootEventLoopWindowTarget<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> EventLoop<T> {
|
|
||||||
pub fn new() -> 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,
|
|
||||||
NSString::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 available_monitors(&self) -> VecDeque<MonitorHandle> {
|
|
||||||
// guaranteed to be on main thread
|
|
||||||
unsafe { monitor::uiscreens() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
|
||||||
// guaranteed to be on main thread
|
|
||||||
unsafe { monitor::main_uiscreen() }
|
|
||||||
}
|
|
||||||
|
|
||||||
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,390 +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)]
|
|
||||||
pub struct CGPoint {
|
|
||||||
pub x: CGFloat,
|
|
||||||
pub y: CGFloat,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
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)]
|
|
||||||
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)]
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[repr(isize)]
|
|
||||||
pub enum UIForceTouchCapability {
|
|
||||||
Unknown = 0,
|
|
||||||
Unavailable,
|
|
||||||
Available,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
#[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 Into<Idiom> for UIUserInterfaceIdiom {
|
|
||||||
fn into(self) -> Idiom {
|
|
||||||
match self {
|
|
||||||
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 Into<ScreenEdge> for UIRectEdge {
|
|
||||||
fn into(self) -> ScreenEdge {
|
|
||||||
let bits: u8 = self.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)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait NSString: 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 NSString 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,299 +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: u16,
|
|
||||||
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: self.refresh_rate,
|
|
||||||
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 os_capabilities = app_state::os_capabilities();
|
|
||||||
let refresh_rate: NSInteger = 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
|
|
||||||
};
|
|
||||||
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: refresh_rate as u16,
|
|
||||||
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(&self) -> u16 {
|
|
||||||
self.refresh_rate
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
#[derive(Debug)]
|
|
||||||
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 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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,599 +0,0 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use objc::{
|
|
||||||
declare::ClassDecl,
|
|
||||||
runtime::{Class, Object, Sel, BOOL, NO, YES},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
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 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:bounds toCoordinateSpace:screen_space];
|
|
||||||
let dpi_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(dpi_factor.into());
|
|
||||||
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 dpi_factor: CGFloat = msg_send![object, contentScaleFactor];
|
|
||||||
assert!(
|
|
||||||
!dpi_factor.is_nan()
|
|
||||||
&& dpi_factor.is_finite()
|
|
||||||
&& dpi_factor.is_sign_positive()
|
|
||||||
&& dpi_factor > 0.0,
|
|
||||||
"invalid scale_factor set on UIView",
|
|
||||||
);
|
|
||||||
let scale_factor: f64 = dpi_factor.into();
|
|
||||||
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 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),
|
|
||||||
};
|
|
||||||
|
|
||||||
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: (location.x as f64, location.y as f64).into(),
|
|
||||||
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 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 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 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)) => {
|
|
||||||
msg_send![window, setScreen:monitor.ui_screen()]
|
|
||||||
}
|
|
||||||
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,638 +0,0 @@
|
|||||||
use raw_window_handle::{ios::IOSHandle, RawWindowHandle};
|
|
||||||
use std::{
|
|
||||||
collections::VecDeque,
|
|
||||||
ops::{Deref, DerefMut},
|
|
||||||
};
|
|
||||||
|
|
||||||
use objc::runtime::{Class, Object, BOOL, NO, YES};
|
|
||||||
|
|
||||||
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::{CursorIcon, Fullscreen, 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 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 dpi_factor = self.scale_factor();
|
|
||||||
Ok(position.to_physical(dpi_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 dpi_factor = self.scale_factor();
|
|
||||||
Ok(position.to_physical(dpi_factor))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_outer_position(&self, physical_position: Position) {
|
|
||||||
unsafe {
|
|
||||||
let dpi_factor = self.scale_factor();
|
|
||||||
let position = physical_position.to_logical::<f64>(dpi_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.from_screen_space(new_screen_frame);
|
|
||||||
let () = msg_send![self.window, setBounds: bounds];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
|
||||||
unsafe {
|
|
||||||
let dpi_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(dpi_factor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
|
||||||
unsafe {
|
|
||||||
let dpi_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(dpi_factor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_inner_size(&self, _size: Size) {
|
|
||||||
unimplemented!("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 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, _grab: bool) -> 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 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 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];
|
|
||||||
uiscreen
|
|
||||||
}
|
|
||||||
Some(Fullscreen::Borderless(monitor)) => monitor.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();
|
|
||||||
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(monitor))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_decorations(&self, _decorations: bool) {
|
|
||||||
warn!("`Window::set_decorations` is ignored on iOS")
|
|
||||||
}
|
|
||||||
|
|
||||||
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 current_monitor(&self) -> RootMonitorHandle {
|
|
||||||
unsafe {
|
|
||||||
let uiscreen: id = msg_send![self.window, screen];
|
|
||||||
RootMonitorHandle {
|
|
||||||
inner: MonitorHandle::retained_new(uiscreen),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
|
||||||
unsafe { monitor::uiscreens() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
|
||||||
unsafe { monitor::main_uiscreen() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn id(&self) -> WindowId {
|
|
||||||
self.window.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
|
||||||
let handle = IOSHandle {
|
|
||||||
ui_window: self.window as _,
|
|
||||||
ui_view: self.view as _,
|
|
||||||
ui_view_controller: self.view_controller as _,
|
|
||||||
..IOSHandle::empty()
|
|
||||||
};
|
|
||||||
RawWindowHandle::IOS(handle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 fn new<T>(
|
|
||||||
_event_loop: &EventLoopWindowTarget<T>,
|
|
||||||
window_attributes: WindowAttributes,
|
|
||||||
platform_attributes: PlatformSpecificWindowBuilderAttributes,
|
|
||||||
) -> Result<Window, RootOsError> {
|
|
||||||
if let Some(_) = window_attributes.min_inner_size {
|
|
||||||
warn!("`WindowAttributes::min_inner_size` is ignored on iOS");
|
|
||||||
}
|
|
||||||
if let Some(_) = window_attributes.max_inner_size {
|
|
||||||
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(ref monitor)) => monitor.ui_screen() as id,
|
|
||||||
None => monitor::main_uiscreen().ui_screen(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let screen_bounds: CGRect = msg_send![screen, bounds];
|
|
||||||
|
|
||||||
let frame = match window_attributes.inner_size {
|
|
||||||
Some(dim) => {
|
|
||||||
let dpi_factor = msg_send![screen, scale];
|
|
||||||
let size = dim.to_logical::<f64>(dpi_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.clone());
|
|
||||||
|
|
||||||
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 dpi_factor: CGFloat = msg_send![view, contentScaleFactor];
|
|
||||||
let scale_factor: f64 = dpi_factor.into();
|
|
||||||
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.to_screen_space(msg_send![self.window, bounds])
|
|
||||||
}
|
|
||||||
|
|
||||||
// requires main thread
|
|
||||||
unsafe fn 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 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.to_screen_space(safe_bounds)
|
|
||||||
} else {
|
|
||||||
let screen_frame = self.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 unsafe fn dummy() -> Self {
|
|
||||||
WindowId {
|
|
||||||
window: std::ptr::null_mut(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,730 +0,0 @@
|
|||||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
|
|
||||||
|
|
||||||
use std::{collections::VecDeque, env, ffi::CStr, fmt, mem::MaybeUninit, os::raw::*, sync::Arc};
|
|
||||||
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
use raw_window_handle::RawWindowHandle;
|
|
||||||
use smithay_client_toolkit::reexports::client::ConnectError;
|
|
||||||
|
|
||||||
pub use self::x11::XNotSupported;
|
|
||||||
use self::x11::{ffi::XVisualInfo, util::WindowType as XWindowType, XConnection, XError};
|
|
||||||
use crate::{
|
|
||||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
|
||||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
|
||||||
event::Event,
|
|
||||||
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
|
||||||
icon::Icon,
|
|
||||||
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
|
||||||
window::{CursorIcon, Fullscreen, WindowAttributes},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod wayland;
|
|
||||||
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(Clone)]
|
|
||||||
pub struct PlatformSpecificWindowBuilderAttributes {
|
|
||||||
pub visual_infos: Option<XVisualInfo>,
|
|
||||||
pub screen_id: Option<i32>,
|
|
||||||
pub resize_increments: Option<Size>,
|
|
||||||
pub base_size: Option<Size>,
|
|
||||||
pub class: Option<(String, String)>,
|
|
||||||
pub override_redirect: bool,
|
|
||||||
pub x11_window_types: Vec<XWindowType>,
|
|
||||||
pub gtk_theme_variant: Option<String>,
|
|
||||||
pub app_id: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for PlatformSpecificWindowBuilderAttributes {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
visual_infos: None,
|
|
||||||
screen_id: None,
|
|
||||||
resize_increments: None,
|
|
||||||
base_size: None,
|
|
||||||
class: None,
|
|
||||||
override_redirect: false,
|
|
||||||
x11_window_types: vec![XWindowType::Normal],
|
|
||||||
gtk_theme_variant: None,
|
|
||||||
app_id: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref X11_BACKEND: Mutex<Result<Arc<XConnection>, XNotSupported>> =
|
|
||||||
{ Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)) };
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum OsError {
|
|
||||||
XError(XError),
|
|
||||||
XMisc(&'static str),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for OsError {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
|
||||||
match self {
|
|
||||||
OsError::XError(e) => f.pad(&e.description),
|
|
||||||
OsError::XMisc(e) => f.pad(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Window {
|
|
||||||
X(x11::Window),
|
|
||||||
Wayland(wayland::Window),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub enum WindowId {
|
|
||||||
X(x11::WindowId),
|
|
||||||
Wayland(wayland::WindowId),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowId {
|
|
||||||
pub unsafe fn dummy() -> Self {
|
|
||||||
WindowId::Wayland(wayland::WindowId::dummy())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub enum DeviceId {
|
|
||||||
X(x11::DeviceId),
|
|
||||||
Wayland(wayland::DeviceId),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DeviceId {
|
|
||||||
pub unsafe fn dummy() -> Self {
|
|
||||||
DeviceId::Wayland(wayland::DeviceId::dummy())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub enum MonitorHandle {
|
|
||||||
X(x11::MonitorHandle),
|
|
||||||
Wayland(wayland::MonitorHandle),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MonitorHandle {
|
|
||||||
#[inline]
|
|
||||||
pub fn name(&self) -> Option<String> {
|
|
||||||
match self {
|
|
||||||
&MonitorHandle::X(ref m) => m.name(),
|
|
||||||
&MonitorHandle::Wayland(ref m) => m.name(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn native_identifier(&self) -> u32 {
|
|
||||||
match self {
|
|
||||||
&MonitorHandle::X(ref m) => m.native_identifier(),
|
|
||||||
&MonitorHandle::Wayland(ref m) => m.native_identifier(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn size(&self) -> PhysicalSize<u32> {
|
|
||||||
match self {
|
|
||||||
&MonitorHandle::X(ref m) => m.size(),
|
|
||||||
&MonitorHandle::Wayland(ref m) => m.size(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn position(&self) -> PhysicalPosition<i32> {
|
|
||||||
match self {
|
|
||||||
&MonitorHandle::X(ref m) => m.position(),
|
|
||||||
&MonitorHandle::Wayland(ref m) => m.position(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn scale_factor(&self) -> f64 {
|
|
||||||
match self {
|
|
||||||
&MonitorHandle::X(ref m) => m.scale_factor(),
|
|
||||||
&MonitorHandle::Wayland(ref m) => m.scale_factor() as f64,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn video_modes(&self) -> Box<dyn Iterator<Item = RootVideoMode>> {
|
|
||||||
match self {
|
|
||||||
MonitorHandle::X(m) => Box::new(m.video_modes()),
|
|
||||||
MonitorHandle::Wayland(m) => Box::new(m.video_modes()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub enum VideoMode {
|
|
||||||
X(x11::VideoMode),
|
|
||||||
Wayland(wayland::VideoMode),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VideoMode {
|
|
||||||
#[inline]
|
|
||||||
pub fn size(&self) -> PhysicalSize<u32> {
|
|
||||||
match self {
|
|
||||||
&VideoMode::X(ref m) => m.size(),
|
|
||||||
&VideoMode::Wayland(ref m) => m.size(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn bit_depth(&self) -> u16 {
|
|
||||||
match self {
|
|
||||||
&VideoMode::X(ref m) => m.bit_depth(),
|
|
||||||
&VideoMode::Wayland(ref m) => m.bit_depth(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn refresh_rate(&self) -> u16 {
|
|
||||||
match self {
|
|
||||||
&VideoMode::X(ref m) => m.refresh_rate(),
|
|
||||||
&VideoMode::Wayland(ref m) => m.refresh_rate(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn monitor(&self) -> RootMonitorHandle {
|
|
||||||
match self {
|
|
||||||
&VideoMode::X(ref m) => m.monitor(),
|
|
||||||
&VideoMode::Wayland(ref m) => m.monitor(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Window {
|
|
||||||
#[inline]
|
|
||||||
pub fn new<T>(
|
|
||||||
window_target: &EventLoopWindowTarget<T>,
|
|
||||||
attribs: WindowAttributes,
|
|
||||||
pl_attribs: PlatformSpecificWindowBuilderAttributes,
|
|
||||||
) -> Result<Self, RootOsError> {
|
|
||||||
match *window_target {
|
|
||||||
EventLoopWindowTarget::Wayland(ref window_target) => {
|
|
||||||
wayland::Window::new(window_target, attribs, pl_attribs).map(Window::Wayland)
|
|
||||||
}
|
|
||||||
EventLoopWindowTarget::X(ref window_target) => {
|
|
||||||
x11::Window::new(window_target, attribs, pl_attribs).map(Window::X)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn id(&self) -> WindowId {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref w) => WindowId::X(w.id()),
|
|
||||||
&Window::Wayland(ref w) => WindowId::Wayland(w.id()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_title(&self, title: &str) {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref w) => w.set_title(title),
|
|
||||||
&Window::Wayland(ref w) => w.set_title(title),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_visible(&self, visible: bool) {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref w) => w.set_visible(visible),
|
|
||||||
&Window::Wayland(ref w) => w.set_visible(visible),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref w) => w.outer_position(),
|
|
||||||
&Window::Wayland(ref w) => w.outer_position(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref m) => m.inner_position(),
|
|
||||||
&Window::Wayland(ref m) => m.inner_position(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_outer_position(&self, position: Position) {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref w) => w.set_outer_position(position),
|
|
||||||
&Window::Wayland(ref w) => w.set_outer_position(position),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref w) => w.inner_size(),
|
|
||||||
&Window::Wayland(ref w) => w.inner_size(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref w) => w.outer_size(),
|
|
||||||
&Window::Wayland(ref w) => w.outer_size(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_inner_size(&self, size: Size) {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref w) => w.set_inner_size(size),
|
|
||||||
&Window::Wayland(ref w) => w.set_inner_size(size),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref w) => w.set_min_inner_size(dimensions),
|
|
||||||
&Window::Wayland(ref w) => w.set_min_inner_size(dimensions),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref w) => w.set_max_inner_size(dimensions),
|
|
||||||
&Window::Wayland(ref w) => w.set_max_inner_size(dimensions),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_resizable(&self, resizable: bool) {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref w) => w.set_resizable(resizable),
|
|
||||||
&Window::Wayland(ref w) => w.set_resizable(resizable),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref w) => w.set_cursor_icon(cursor),
|
|
||||||
&Window::Wayland(ref w) => w.set_cursor_icon(cursor),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref window) => window.set_cursor_grab(grab),
|
|
||||||
&Window::Wayland(ref window) => window.set_cursor_grab(grab),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_cursor_visible(&self, visible: bool) {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref window) => window.set_cursor_visible(visible),
|
|
||||||
&Window::Wayland(ref window) => window.set_cursor_visible(visible),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn scale_factor(&self) -> f64 {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref w) => w.scale_factor(),
|
|
||||||
&Window::Wayland(ref w) => w.scale_factor() as f64,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref w) => w.set_cursor_position(position),
|
|
||||||
&Window::Wayland(ref w) => w.set_cursor_position(position),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_maximized(&self, maximized: bool) {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref w) => w.set_maximized(maximized),
|
|
||||||
&Window::Wayland(ref w) => w.set_maximized(maximized),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_minimized(&self, minimized: bool) {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref w) => w.set_minimized(minimized),
|
|
||||||
&Window::Wayland(ref w) => w.set_minimized(minimized),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref w) => w.fullscreen(),
|
|
||||||
&Window::Wayland(ref w) => w.fullscreen(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref w) => w.set_fullscreen(monitor),
|
|
||||||
&Window::Wayland(ref w) => w.set_fullscreen(monitor),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_decorations(&self, decorations: bool) {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref w) => w.set_decorations(decorations),
|
|
||||||
&Window::Wayland(ref w) => w.set_decorations(decorations),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_always_on_top(&self, always_on_top: bool) {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref w) => w.set_always_on_top(always_on_top),
|
|
||||||
&Window::Wayland(_) => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_window_icon(&self, window_icon: Option<Icon>) {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref w) => w.set_window_icon(window_icon),
|
|
||||||
&Window::Wayland(_) => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_ime_position(&self, position: Position) {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref w) => w.set_ime_position(position),
|
|
||||||
&Window::Wayland(_) => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn request_redraw(&self) {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref w) => w.request_redraw(),
|
|
||||||
&Window::Wayland(ref w) => w.request_redraw(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn current_monitor(&self) -> RootMonitorHandle {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref window) => RootMonitorHandle {
|
|
||||||
inner: MonitorHandle::X(window.current_monitor()),
|
|
||||||
},
|
|
||||||
&Window::Wayland(ref window) => RootMonitorHandle {
|
|
||||||
inner: MonitorHandle::Wayland(window.current_monitor()),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref window) => window
|
|
||||||
.available_monitors()
|
|
||||||
.into_iter()
|
|
||||||
.map(MonitorHandle::X)
|
|
||||||
.collect(),
|
|
||||||
&Window::Wayland(ref window) => window
|
|
||||||
.available_monitors()
|
|
||||||
.into_iter()
|
|
||||||
.map(MonitorHandle::Wayland)
|
|
||||||
.collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref window) => MonitorHandle::X(window.primary_monitor()),
|
|
||||||
&Window::Wayland(ref window) => MonitorHandle::Wayland(window.primary_monitor()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
|
||||||
match self {
|
|
||||||
&Window::X(ref window) => RawWindowHandle::Xlib(window.raw_window_handle()),
|
|
||||||
&Window::Wayland(ref window) => RawWindowHandle::Wayland(window.raw_window_handle()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
// `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,
|
|
||||||
};
|
|
||||||
|
|
||||||
error!("X11 error: {:#?}", error);
|
|
||||||
|
|
||||||
*xconn.latest_error.lock() = Some(error);
|
|
||||||
}
|
|
||||||
// Fun fact: this return value is completely ignored.
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum EventLoop<T: 'static> {
|
|
||||||
Wayland(wayland::EventLoop<T>),
|
|
||||||
X(x11::EventLoop<T>),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum EventLoopProxy<T: 'static> {
|
|
||||||
X(x11::EventLoopProxy<T>),
|
|
||||||
Wayland(wayland::EventLoopProxy<T>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> Clone for EventLoopProxy<T> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
match self {
|
|
||||||
EventLoopProxy::X(proxy) => EventLoopProxy::X(proxy.clone()),
|
|
||||||
EventLoopProxy::Wayland(proxy) => EventLoopProxy::Wayland(proxy.clone()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> EventLoop<T> {
|
|
||||||
pub fn new() -> EventLoop<T> {
|
|
||||||
assert_is_main_thread("new_any_thread");
|
|
||||||
|
|
||||||
EventLoop::new_any_thread()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_any_thread() -> EventLoop<T> {
|
|
||||||
if let Ok(env_var) = env::var(BACKEND_PREFERENCE_ENV_VAR) {
|
|
||||||
match env_var.as_str() {
|
|
||||||
"x11" => {
|
|
||||||
// TODO: propagate
|
|
||||||
return EventLoop::new_x11_any_thread()
|
|
||||||
.expect("Failed to initialize X11 backend");
|
|
||||||
}
|
|
||||||
"wayland" => {
|
|
||||||
return EventLoop::new_wayland_any_thread()
|
|
||||||
.expect("Failed to initialize Wayland backend");
|
|
||||||
}
|
|
||||||
_ => panic!(
|
|
||||||
"Unknown environment variable value for {}, try one of `x11`,`wayland`",
|
|
||||||
BACKEND_PREFERENCE_ENV_VAR,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let wayland_err = match EventLoop::new_wayland_any_thread() {
|
|
||||||
Ok(event_loop) => return event_loop,
|
|
||||||
Err(err) => err,
|
|
||||||
};
|
|
||||||
|
|
||||||
let x11_err = match EventLoop::new_x11_any_thread() {
|
|
||||||
Ok(event_loop) => return event_loop,
|
|
||||||
Err(err) => err,
|
|
||||||
};
|
|
||||||
|
|
||||||
let err_string = format!(
|
|
||||||
"Failed to initialize any backend! Wayland status: {:?} X11 status: {:?}",
|
|
||||||
wayland_err, x11_err,
|
|
||||||
);
|
|
||||||
panic!(err_string);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_wayland() -> Result<EventLoop<T>, ConnectError> {
|
|
||||||
assert_is_main_thread("new_wayland_any_thread");
|
|
||||||
|
|
||||||
EventLoop::new_wayland_any_thread()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_wayland_any_thread() -> Result<EventLoop<T>, ConnectError> {
|
|
||||||
wayland::EventLoop::new().map(EventLoop::Wayland)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_x11() -> Result<EventLoop<T>, XNotSupported> {
|
|
||||||
assert_is_main_thread("new_x11_any_thread");
|
|
||||||
|
|
||||||
EventLoop::new_x11_any_thread()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_x11_any_thread() -> Result<EventLoop<T>, XNotSupported> {
|
|
||||||
X11_BACKEND
|
|
||||||
.lock()
|
|
||||||
.as_ref()
|
|
||||||
.map(Arc::clone)
|
|
||||||
.map(x11::EventLoop::new)
|
|
||||||
.map(EventLoop::X)
|
|
||||||
.map_err(|err| err.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
|
||||||
match *self {
|
|
||||||
EventLoop::Wayland(ref evlp) => evlp
|
|
||||||
.available_monitors()
|
|
||||||
.into_iter()
|
|
||||||
.map(MonitorHandle::Wayland)
|
|
||||||
.collect(),
|
|
||||||
EventLoop::X(ref evlp) => evlp
|
|
||||||
.x_connection()
|
|
||||||
.available_monitors()
|
|
||||||
.into_iter()
|
|
||||||
.map(MonitorHandle::X)
|
|
||||||
.collect(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
|
||||||
match *self {
|
|
||||||
EventLoop::Wayland(ref evlp) => MonitorHandle::Wayland(evlp.primary_monitor()),
|
|
||||||
EventLoop::X(ref evlp) => MonitorHandle::X(evlp.x_connection().primary_monitor()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
|
||||||
match *self {
|
|
||||||
EventLoop::Wayland(ref evlp) => EventLoopProxy::Wayland(evlp.create_proxy()),
|
|
||||||
EventLoop::X(ref evlp) => EventLoopProxy::X(evlp.create_proxy()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_return<F>(&mut self, callback: F)
|
|
||||||
where
|
|
||||||
F: FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
|
||||||
{
|
|
||||||
match *self {
|
|
||||||
EventLoop::Wayland(ref mut evlp) => evlp.run_return(callback),
|
|
||||||
EventLoop::X(ref mut evlp) => evlp.run_return(callback),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run<F>(self, callback: F) -> !
|
|
||||||
where
|
|
||||||
F: 'static + FnMut(crate::event::Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
|
||||||
{
|
|
||||||
match self {
|
|
||||||
EventLoop::Wayland(evlp) => evlp.run(callback),
|
|
||||||
EventLoop::X(evlp) => evlp.run(callback),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn window_target(&self) -> &crate::event_loop::EventLoopWindowTarget<T> {
|
|
||||||
match *self {
|
|
||||||
EventLoop::Wayland(ref evl) => evl.window_target(),
|
|
||||||
EventLoop::X(ref evl) => evl.window_target(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> EventLoopProxy<T> {
|
|
||||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
|
||||||
match *self {
|
|
||||||
EventLoopProxy::Wayland(ref proxy) => proxy.send_event(event),
|
|
||||||
EventLoopProxy::X(ref proxy) => proxy.send_event(event),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum EventLoopWindowTarget<T> {
|
|
||||||
Wayland(wayland::EventLoopWindowTarget<T>),
|
|
||||||
X(x11::EventLoopWindowTarget<T>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopWindowTarget<T> {
|
|
||||||
#[inline]
|
|
||||||
pub fn is_wayland(&self) -> bool {
|
|
||||||
match *self {
|
|
||||||
EventLoopWindowTarget::Wayland(_) => true,
|
|
||||||
EventLoopWindowTarget::X(_) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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::Exit sticky by providing a dummy
|
|
||||||
// control flow reference if it is already Exit.
|
|
||||||
let mut dummy = ControlFlow::Exit;
|
|
||||||
let cf = if *control_flow == ControlFlow::Exit {
|
|
||||||
&mut dummy
|
|
||||||
} else {
|
|
||||||
control_flow
|
|
||||||
};
|
|
||||||
// user callback
|
|
||||||
callback(evt, target, cf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_is_main_thread(suggested_method: &str) {
|
|
||||||
if !is_main_thread() {
|
|
||||||
panic!(
|
|
||||||
"Initializing the event loop outside of the main thread is a significant \
|
|
||||||
cross-platform compatibility hazard. If you really, absolutely need to create an \
|
|
||||||
EventLoop on a different thread, please use the `EventLoopExtUnix::{}` function.",
|
|
||||||
suggested_method
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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 {
|
|
||||||
use libc::_lwp_self;
|
|
||||||
|
|
||||||
unsafe { _lwp_self() == 1 }
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,382 +0,0 @@
|
|||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use super::{event_loop::EventsSink, make_wid, DeviceId};
|
|
||||||
use smithay_client_toolkit::{
|
|
||||||
keyboard::{
|
|
||||||
self, map_keyboard_auto_with_repeat, Event as KbEvent, KeyRepeatEvent, KeyRepeatKind,
|
|
||||||
},
|
|
||||||
reexports::client::protocol::{wl_keyboard, wl_seat},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::event::{
|
|
||||||
DeviceEvent, ElementState, KeyboardInput, ModifiersState, VirtualKeyCode, WindowEvent,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn init_keyboard(
|
|
||||||
seat: &wl_seat::WlSeat,
|
|
||||||
sink: EventsSink,
|
|
||||||
modifiers_tracker: Arc<Mutex<ModifiersState>>,
|
|
||||||
) -> wl_keyboard::WlKeyboard {
|
|
||||||
// { variables to be captured by the closures
|
|
||||||
let target = Arc::new(Mutex::new(None));
|
|
||||||
let my_sink = sink.clone();
|
|
||||||
let repeat_sink = sink.clone();
|
|
||||||
let repeat_target = target.clone();
|
|
||||||
let my_modifiers = modifiers_tracker.clone();
|
|
||||||
// }
|
|
||||||
let ret = map_keyboard_auto_with_repeat(
|
|
||||||
seat,
|
|
||||||
KeyRepeatKind::System,
|
|
||||||
move |evt: KbEvent<'_>, _| {
|
|
||||||
match evt {
|
|
||||||
KbEvent::Enter { surface, .. } => {
|
|
||||||
let wid = make_wid(&surface);
|
|
||||||
my_sink.send_window_event(WindowEvent::Focused(true), wid);
|
|
||||||
*target.lock().unwrap() = Some(wid);
|
|
||||||
}
|
|
||||||
KbEvent::Leave { surface, .. } => {
|
|
||||||
let wid = make_wid(&surface);
|
|
||||||
my_sink.send_window_event(WindowEvent::Focused(false), wid);
|
|
||||||
*target.lock().unwrap() = None;
|
|
||||||
}
|
|
||||||
KbEvent::Key {
|
|
||||||
rawkey,
|
|
||||||
keysym,
|
|
||||||
state,
|
|
||||||
utf8,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
if let Some(wid) = *target.lock().unwrap() {
|
|
||||||
let state = match state {
|
|
||||||
wl_keyboard::KeyState::Pressed => ElementState::Pressed,
|
|
||||||
wl_keyboard::KeyState::Released => ElementState::Released,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
let vkcode = key_to_vkey(rawkey, keysym);
|
|
||||||
my_sink.send_window_event(
|
|
||||||
#[allow(deprecated)]
|
|
||||||
WindowEvent::KeyboardInput {
|
|
||||||
device_id: crate::event::DeviceId(
|
|
||||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
|
||||||
),
|
|
||||||
input: KeyboardInput {
|
|
||||||
state,
|
|
||||||
scancode: rawkey,
|
|
||||||
virtual_keycode: vkcode,
|
|
||||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
|
||||||
},
|
|
||||||
is_synthetic: false,
|
|
||||||
},
|
|
||||||
wid,
|
|
||||||
);
|
|
||||||
// send char event only on key press, not release
|
|
||||||
if let ElementState::Released = state {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if let Some(txt) = utf8 {
|
|
||||||
for chr in txt.chars() {
|
|
||||||
my_sink.send_window_event(WindowEvent::ReceivedCharacter(chr), wid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KbEvent::RepeatInfo { .. } => { /* Handled by smithay client toolkit */ }
|
|
||||||
KbEvent::Modifiers {
|
|
||||||
modifiers: event_modifiers,
|
|
||||||
} => {
|
|
||||||
let modifiers = ModifiersState::from_wayland(event_modifiers);
|
|
||||||
|
|
||||||
*modifiers_tracker.lock().unwrap() = modifiers;
|
|
||||||
|
|
||||||
my_sink.send_device_event(DeviceEvent::ModifiersChanged(modifiers), DeviceId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
move |repeat_event: KeyRepeatEvent, _| {
|
|
||||||
if let Some(wid) = *repeat_target.lock().unwrap() {
|
|
||||||
let state = ElementState::Pressed;
|
|
||||||
let vkcode = key_to_vkey(repeat_event.rawkey, repeat_event.keysym);
|
|
||||||
repeat_sink.send_window_event(
|
|
||||||
#[allow(deprecated)]
|
|
||||||
WindowEvent::KeyboardInput {
|
|
||||||
device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland(
|
|
||||||
DeviceId,
|
|
||||||
)),
|
|
||||||
input: KeyboardInput {
|
|
||||||
state,
|
|
||||||
scancode: repeat_event.rawkey,
|
|
||||||
virtual_keycode: vkcode,
|
|
||||||
modifiers: my_modifiers.lock().unwrap().clone(),
|
|
||||||
},
|
|
||||||
is_synthetic: false,
|
|
||||||
},
|
|
||||||
wid,
|
|
||||||
);
|
|
||||||
if let Some(txt) = repeat_event.utf8 {
|
|
||||||
for chr in txt.chars() {
|
|
||||||
repeat_sink.send_window_event(WindowEvent::ReceivedCharacter(chr), wid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
match ret {
|
|
||||||
Ok(keyboard) => keyboard,
|
|
||||||
Err(_) => {
|
|
||||||
// This is a fallback impl if libxkbcommon was not available
|
|
||||||
// This case should probably never happen, as most wayland
|
|
||||||
// compositors _need_ libxkbcommon anyway...
|
|
||||||
//
|
|
||||||
// In this case, we don't have the keymap information (it is
|
|
||||||
// supposed to be serialized by the compositor using libxkbcommon)
|
|
||||||
|
|
||||||
seat.get_keyboard(|keyboard| {
|
|
||||||
// { variables to be captured by the closure
|
|
||||||
let mut target = None;
|
|
||||||
let my_sink = sink;
|
|
||||||
// }
|
|
||||||
|
|
||||||
keyboard.implement_closure(
|
|
||||||
move |evt, _| match evt {
|
|
||||||
wl_keyboard::Event::Enter { surface, .. } => {
|
|
||||||
let wid = make_wid(&surface);
|
|
||||||
my_sink.send_window_event(WindowEvent::Focused(true), wid);
|
|
||||||
target = Some(wid);
|
|
||||||
}
|
|
||||||
wl_keyboard::Event::Leave { surface, .. } => {
|
|
||||||
let wid = make_wid(&surface);
|
|
||||||
my_sink.send_window_event(WindowEvent::Focused(false), wid);
|
|
||||||
target = None;
|
|
||||||
}
|
|
||||||
wl_keyboard::Event::Key { key, state, .. } => {
|
|
||||||
if let Some(wid) = target {
|
|
||||||
let state = match state {
|
|
||||||
wl_keyboard::KeyState::Pressed => ElementState::Pressed,
|
|
||||||
wl_keyboard::KeyState::Released => ElementState::Released,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
my_sink.send_window_event(
|
|
||||||
#[allow(deprecated)]
|
|
||||||
WindowEvent::KeyboardInput {
|
|
||||||
device_id: crate::event::DeviceId(
|
|
||||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
|
||||||
),
|
|
||||||
input: KeyboardInput {
|
|
||||||
state,
|
|
||||||
scancode: key,
|
|
||||||
virtual_keycode: None,
|
|
||||||
modifiers: ModifiersState::default(),
|
|
||||||
},
|
|
||||||
is_synthetic: false,
|
|
||||||
},
|
|
||||||
wid,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
},
|
|
||||||
(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn key_to_vkey(rawkey: u32, keysym: u32) -> Option<VirtualKeyCode> {
|
|
||||||
match rawkey {
|
|
||||||
1 => Some(VirtualKeyCode::Escape),
|
|
||||||
2 => Some(VirtualKeyCode::Key1),
|
|
||||||
3 => Some(VirtualKeyCode::Key2),
|
|
||||||
4 => Some(VirtualKeyCode::Key3),
|
|
||||||
5 => Some(VirtualKeyCode::Key4),
|
|
||||||
6 => Some(VirtualKeyCode::Key5),
|
|
||||||
7 => Some(VirtualKeyCode::Key6),
|
|
||||||
8 => Some(VirtualKeyCode::Key7),
|
|
||||||
9 => Some(VirtualKeyCode::Key8),
|
|
||||||
10 => Some(VirtualKeyCode::Key9),
|
|
||||||
11 => Some(VirtualKeyCode::Key0),
|
|
||||||
_ => keysym_to_vkey(keysym),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn keysym_to_vkey(keysym: u32) -> Option<VirtualKeyCode> {
|
|
||||||
use smithay_client_toolkit::keyboard::keysyms;
|
|
||||||
match keysym {
|
|
||||||
// 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),
|
|
||||||
// F--
|
|
||||||
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),
|
|
||||||
// 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::Add),
|
|
||||||
keysyms::XKB_KEY_apostrophe => Some(VirtualKeyCode::Apostrophe),
|
|
||||||
// => Some(VirtualKeyCode::Apps),
|
|
||||||
// => Some(VirtualKeyCode::At),
|
|
||||||
// => Some(VirtualKeyCode::Ax),
|
|
||||||
keysyms::XKB_KEY_backslash => Some(VirtualKeyCode::Backslash),
|
|
||||||
// => Some(VirtualKeyCode::Calculator),
|
|
||||||
// => Some(VirtualKeyCode::Capital),
|
|
||||||
keysyms::XKB_KEY_colon => Some(VirtualKeyCode::Colon),
|
|
||||||
keysyms::XKB_KEY_comma => Some(VirtualKeyCode::Comma),
|
|
||||||
// => Some(VirtualKeyCode::Convert),
|
|
||||||
// => Some(VirtualKeyCode::Decimal),
|
|
||||||
// => Some(VirtualKeyCode::Divide),
|
|
||||||
keysyms::XKB_KEY_equal => Some(VirtualKeyCode::Equals),
|
|
||||||
// => Some(VirtualKeyCode::Grave),
|
|
||||||
// => Some(VirtualKeyCode::Kana),
|
|
||||||
// => Some(VirtualKeyCode::Kanji),
|
|
||||||
keysyms::XKB_KEY_Alt_L => Some(VirtualKeyCode::LAlt),
|
|
||||||
// => Some(VirtualKeyCode::LBracket),
|
|
||||||
keysyms::XKB_KEY_Control_L => Some(VirtualKeyCode::LControl),
|
|
||||||
keysyms::XKB_KEY_Shift_L => Some(VirtualKeyCode::LShift),
|
|
||||||
// => Some(VirtualKeyCode::LWin),
|
|
||||||
// => Some(VirtualKeyCode::Mail),
|
|
||||||
// => Some(VirtualKeyCode::MediaSelect),
|
|
||||||
// => Some(VirtualKeyCode::MediaStop),
|
|
||||||
keysyms::XKB_KEY_minus => Some(VirtualKeyCode::Minus),
|
|
||||||
keysyms::XKB_KEY_asterisk => Some(VirtualKeyCode::Multiply),
|
|
||||||
// => Some(VirtualKeyCode::Mute),
|
|
||||||
// => Some(VirtualKeyCode::MyComputer),
|
|
||||||
// => 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::Add),
|
|
||||||
keysyms::XKB_KEY_KP_Subtract => Some(VirtualKeyCode::Subtract),
|
|
||||||
keysyms::XKB_KEY_KP_Divide => Some(VirtualKeyCode::Divide),
|
|
||||||
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),
|
|
||||||
// => Some(VirtualKeyCode::OEM102),
|
|
||||||
// => Some(VirtualKeyCode::Period),
|
|
||||||
// => Some(VirtualKeyCode::Playpause),
|
|
||||||
// => Some(VirtualKeyCode::Power),
|
|
||||||
// => Some(VirtualKeyCode::Prevtrack),
|
|
||||||
keysyms::XKB_KEY_Alt_R => Some(VirtualKeyCode::RAlt),
|
|
||||||
// => Some(VirtualKeyCode::RBracket),
|
|
||||||
keysyms::XKB_KEY_Control_R => Some(VirtualKeyCode::RControl),
|
|
||||||
keysyms::XKB_KEY_Shift_R => Some(VirtualKeyCode::RShift),
|
|
||||||
// => Some(VirtualKeyCode::RWin),
|
|
||||||
keysyms::XKB_KEY_semicolon => Some(VirtualKeyCode::Semicolon),
|
|
||||||
keysyms::XKB_KEY_slash => Some(VirtualKeyCode::Slash),
|
|
||||||
// => Some(VirtualKeyCode::Sleep),
|
|
||||||
// => Some(VirtualKeyCode::Stop),
|
|
||||||
// => Some(VirtualKeyCode::Subtract),
|
|
||||||
// => Some(VirtualKeyCode::Sysrq),
|
|
||||||
keysyms::XKB_KEY_Tab => Some(VirtualKeyCode::Tab),
|
|
||||||
keysyms::XKB_KEY_ISO_Left_Tab => Some(VirtualKeyCode::Tab),
|
|
||||||
// => 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),
|
|
||||||
// => 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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ModifiersState {
|
|
||||||
pub(crate) fn from_wayland(mods: keyboard::ModifiersState) -> ModifiersState {
|
|
||||||
let mut m = ModifiersState::empty();
|
|
||||||
m.set(ModifiersState::SHIFT, mods.shift);
|
|
||||||
m.set(ModifiersState::CTRL, mods.ctrl);
|
|
||||||
m.set(ModifiersState::ALT, mods.alt);
|
|
||||||
m.set(ModifiersState::LOGO, mods.logo);
|
|
||||||
m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd",
|
|
||||||
target_os = "netbsd", target_os = "openbsd"))]
|
|
||||||
|
|
||||||
pub use self::{
|
|
||||||
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, VideoMode},
|
|
||||||
window::Window,
|
|
||||||
};
|
|
||||||
|
|
||||||
use smithay_client_toolkit::reexports::client::protocol::wl_surface;
|
|
||||||
|
|
||||||
mod event_loop;
|
|
||||||
mod keyboard;
|
|
||||||
mod pointer;
|
|
||||||
mod touch;
|
|
||||||
mod window;
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct DeviceId;
|
|
||||||
|
|
||||||
impl DeviceId {
|
|
||||||
pub unsafe fn dummy() -> Self {
|
|
||||||
DeviceId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct WindowId(usize);
|
|
||||||
|
|
||||||
impl WindowId {
|
|
||||||
pub unsafe fn dummy() -> Self {
|
|
||||||
WindowId(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn make_wid(s: &wl_surface::WlSurface) -> WindowId {
|
|
||||||
WindowId(s.as_ref().c_ptr() as usize)
|
|
||||||
}
|
|
||||||
@@ -1,282 +0,0 @@
|
|||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use crate::event::{
|
|
||||||
DeviceEvent, ElementState, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase,
|
|
||||||
WindowEvent,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
event_loop::{CursorManager, EventsSink},
|
|
||||||
make_wid,
|
|
||||||
window::WindowStore,
|
|
||||||
DeviceId,
|
|
||||||
};
|
|
||||||
|
|
||||||
use smithay_client_toolkit::surface;
|
|
||||||
|
|
||||||
use smithay_client_toolkit::reexports::client::protocol::{
|
|
||||||
wl_pointer::{self, Event as PtrEvent, WlPointer},
|
|
||||||
wl_seat,
|
|
||||||
};
|
|
||||||
|
|
||||||
use smithay_client_toolkit::reexports::protocols::unstable::relative_pointer::v1::client::{
|
|
||||||
zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1, zwp_relative_pointer_v1::Event,
|
|
||||||
zwp_relative_pointer_v1::ZwpRelativePointerV1,
|
|
||||||
};
|
|
||||||
|
|
||||||
use smithay_client_toolkit::reexports::protocols::unstable::pointer_constraints::v1::client::{
|
|
||||||
zwp_locked_pointer_v1::ZwpLockedPointerV1, zwp_pointer_constraints_v1::Lifetime,
|
|
||||||
zwp_pointer_constraints_v1::ZwpPointerConstraintsV1,
|
|
||||||
};
|
|
||||||
|
|
||||||
use smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface;
|
|
||||||
|
|
||||||
pub fn implement_pointer(
|
|
||||||
seat: &wl_seat::WlSeat,
|
|
||||||
sink: EventsSink,
|
|
||||||
store: Arc<Mutex<WindowStore>>,
|
|
||||||
modifiers_tracker: Arc<Mutex<ModifiersState>>,
|
|
||||||
cursor_manager: Arc<Mutex<CursorManager>>,
|
|
||||||
) -> WlPointer {
|
|
||||||
seat.get_pointer(|pointer| {
|
|
||||||
// Currently focused winit surface
|
|
||||||
let mut mouse_focus = None;
|
|
||||||
let mut axis_buffer = None;
|
|
||||||
let mut axis_discrete_buffer = None;
|
|
||||||
let mut axis_state = TouchPhase::Ended;
|
|
||||||
|
|
||||||
pointer.implement_closure(
|
|
||||||
move |evt, pointer| {
|
|
||||||
let store = store.lock().unwrap();
|
|
||||||
let mut cursor_manager = cursor_manager.lock().unwrap();
|
|
||||||
match evt {
|
|
||||||
PtrEvent::Enter {
|
|
||||||
surface,
|
|
||||||
surface_x,
|
|
||||||
surface_y,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let wid = store.find_wid(&surface);
|
|
||||||
|
|
||||||
if let Some(wid) = wid {
|
|
||||||
let scale_factor = surface::get_dpi_factor(&surface) as f64;
|
|
||||||
mouse_focus = Some(surface);
|
|
||||||
|
|
||||||
// Reload cursor style only when we enter winit's surface. Calling
|
|
||||||
// this function every time on `PtrEvent::Enter` could interfere with
|
|
||||||
// SCTK CSD handling, since it changes cursor icons when you hover
|
|
||||||
// cursor over the window borders.
|
|
||||||
cursor_manager.reload_cursor_style();
|
|
||||||
|
|
||||||
sink.send_window_event(
|
|
||||||
WindowEvent::CursorEntered {
|
|
||||||
device_id: crate::event::DeviceId(
|
|
||||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
wid,
|
|
||||||
);
|
|
||||||
sink.send_window_event(
|
|
||||||
WindowEvent::CursorMoved {
|
|
||||||
device_id: crate::event::DeviceId(
|
|
||||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
|
||||||
),
|
|
||||||
position: (surface_x * scale_factor, surface_y * scale_factor)
|
|
||||||
.into(),
|
|
||||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
|
||||||
},
|
|
||||||
wid,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PtrEvent::Leave { surface, .. } => {
|
|
||||||
mouse_focus = None;
|
|
||||||
let wid = store.find_wid(&surface);
|
|
||||||
if let Some(wid) = wid {
|
|
||||||
sink.send_window_event(
|
|
||||||
WindowEvent::CursorLeft {
|
|
||||||
device_id: crate::event::DeviceId(
|
|
||||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
wid,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PtrEvent::Motion {
|
|
||||||
surface_x,
|
|
||||||
surface_y,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
if let Some(surface) = mouse_focus.as_ref() {
|
|
||||||
let scale_factor = surface::get_dpi_factor(&surface) as f64;
|
|
||||||
let wid = make_wid(surface);
|
|
||||||
sink.send_window_event(
|
|
||||||
WindowEvent::CursorMoved {
|
|
||||||
device_id: crate::event::DeviceId(
|
|
||||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
|
||||||
),
|
|
||||||
position: (surface_x * scale_factor, surface_y * scale_factor)
|
|
||||||
.into(),
|
|
||||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
|
||||||
},
|
|
||||||
wid,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PtrEvent::Button { button, state, .. } => {
|
|
||||||
if let Some(surface) = mouse_focus.as_ref() {
|
|
||||||
let state = match state {
|
|
||||||
wl_pointer::ButtonState::Pressed => ElementState::Pressed,
|
|
||||||
wl_pointer::ButtonState::Released => ElementState::Released,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
let button = match button {
|
|
||||||
0x110 => MouseButton::Left,
|
|
||||||
0x111 => MouseButton::Right,
|
|
||||||
0x112 => MouseButton::Middle,
|
|
||||||
// TODO figure out the translation ?
|
|
||||||
_ => return,
|
|
||||||
};
|
|
||||||
sink.send_window_event(
|
|
||||||
WindowEvent::MouseInput {
|
|
||||||
device_id: crate::event::DeviceId(
|
|
||||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
|
||||||
),
|
|
||||||
state,
|
|
||||||
button,
|
|
||||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
|
||||||
},
|
|
||||||
make_wid(surface),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PtrEvent::Axis { axis, value, .. } => {
|
|
||||||
if let Some(surface) = mouse_focus.as_ref() {
|
|
||||||
let wid = make_wid(surface);
|
|
||||||
if pointer.as_ref().version() < 5 {
|
|
||||||
let (mut x, mut y) = (0.0, 0.0);
|
|
||||||
// old seat compatibility
|
|
||||||
match axis {
|
|
||||||
// wayland vertical sign convention is the inverse of winit
|
|
||||||
wl_pointer::Axis::VerticalScroll => y -= value as f32,
|
|
||||||
wl_pointer::Axis::HorizontalScroll => x += value as f32,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
sink.send_window_event(
|
|
||||||
WindowEvent::MouseWheel {
|
|
||||||
device_id: crate::event::DeviceId(
|
|
||||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
|
||||||
),
|
|
||||||
delta: MouseScrollDelta::PixelDelta(
|
|
||||||
(x as f64, y as f64).into(),
|
|
||||||
),
|
|
||||||
phase: TouchPhase::Moved,
|
|
||||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
|
||||||
},
|
|
||||||
wid,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let (mut x, mut y) = axis_buffer.unwrap_or((0.0, 0.0));
|
|
||||||
match axis {
|
|
||||||
// wayland vertical sign convention is the inverse of winit
|
|
||||||
wl_pointer::Axis::VerticalScroll => y -= value as f32,
|
|
||||||
wl_pointer::Axis::HorizontalScroll => x += value as f32,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
axis_buffer = Some((x, y));
|
|
||||||
axis_state = match axis_state {
|
|
||||||
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
|
|
||||||
_ => TouchPhase::Started,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PtrEvent::Frame => {
|
|
||||||
let axis_buffer = axis_buffer.take();
|
|
||||||
let axis_discrete_buffer = axis_discrete_buffer.take();
|
|
||||||
if let Some(surface) = mouse_focus.as_ref() {
|
|
||||||
let wid = make_wid(surface);
|
|
||||||
if let Some((x, y)) = axis_discrete_buffer {
|
|
||||||
sink.send_window_event(
|
|
||||||
WindowEvent::MouseWheel {
|
|
||||||
device_id: crate::event::DeviceId(
|
|
||||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
|
||||||
),
|
|
||||||
delta: MouseScrollDelta::LineDelta(x as f32, y as f32),
|
|
||||||
phase: axis_state,
|
|
||||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
|
||||||
},
|
|
||||||
wid,
|
|
||||||
);
|
|
||||||
} else if let Some((x, y)) = axis_buffer {
|
|
||||||
sink.send_window_event(
|
|
||||||
WindowEvent::MouseWheel {
|
|
||||||
device_id: crate::event::DeviceId(
|
|
||||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
|
||||||
),
|
|
||||||
delta: MouseScrollDelta::PixelDelta(
|
|
||||||
(x as f64, y as f64).into(),
|
|
||||||
),
|
|
||||||
phase: axis_state,
|
|
||||||
modifiers: modifiers_tracker.lock().unwrap().clone(),
|
|
||||||
},
|
|
||||||
wid,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PtrEvent::AxisSource { .. } => (),
|
|
||||||
PtrEvent::AxisStop { .. } => {
|
|
||||||
axis_state = TouchPhase::Ended;
|
|
||||||
}
|
|
||||||
PtrEvent::AxisDiscrete { axis, discrete } => {
|
|
||||||
let (mut x, mut y) = axis_discrete_buffer.unwrap_or((0, 0));
|
|
||||||
match axis {
|
|
||||||
// wayland vertical sign convention is the inverse of winit
|
|
||||||
wl_pointer::Axis::VerticalScroll => y -= discrete,
|
|
||||||
wl_pointer::Axis::HorizontalScroll => x += discrete,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
axis_discrete_buffer = Some((x, y));
|
|
||||||
axis_state = match axis_state {
|
|
||||||
TouchPhase::Started | TouchPhase::Moved => TouchPhase::Moved,
|
|
||||||
_ => TouchPhase::Started,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn implement_relative_pointer(
|
|
||||||
sink: EventsSink,
|
|
||||||
pointer: &WlPointer,
|
|
||||||
manager: &ZwpRelativePointerManagerV1,
|
|
||||||
) -> Result<ZwpRelativePointerV1, ()> {
|
|
||||||
manager.get_relative_pointer(pointer, |rel_pointer| {
|
|
||||||
rel_pointer.implement_closure(
|
|
||||||
move |evt, _rel_pointer| match evt {
|
|
||||||
Event::RelativeMotion { dx, dy, .. } => {
|
|
||||||
sink.send_device_event(DeviceEvent::MouseMotion { delta: (dx, dy) }, DeviceId)
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
},
|
|
||||||
(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn implement_locked_pointer(
|
|
||||||
surface: &WlSurface,
|
|
||||||
pointer: &WlPointer,
|
|
||||||
constraints: &ZwpPointerConstraintsV1,
|
|
||||||
) -> Result<ZwpLockedPointerV1, ()> {
|
|
||||||
constraints.lock_pointer(surface, pointer, None, Lifetime::Persistent.to_raw(), |c| {
|
|
||||||
c.implement_closure(|_, _| (), ())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
use std::sync::{Arc, Mutex};
|
|
||||||
|
|
||||||
use crate::event::{TouchPhase, WindowEvent};
|
|
||||||
|
|
||||||
use super::{event_loop::EventsSink, window::WindowStore, DeviceId, WindowId};
|
|
||||||
|
|
||||||
use smithay_client_toolkit::reexports::client::protocol::{
|
|
||||||
wl_seat,
|
|
||||||
wl_touch::{Event as TouchEvent, WlTouch},
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TouchPoint {
|
|
||||||
wid: WindowId,
|
|
||||||
location: (f64, f64),
|
|
||||||
id: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn implement_touch(
|
|
||||||
seat: &wl_seat::WlSeat,
|
|
||||||
sink: EventsSink,
|
|
||||||
store: Arc<Mutex<WindowStore>>,
|
|
||||||
) -> WlTouch {
|
|
||||||
let mut pending_ids = Vec::new();
|
|
||||||
seat.get_touch(|touch| {
|
|
||||||
touch.implement_closure(
|
|
||||||
move |evt, _| {
|
|
||||||
let store = store.lock().unwrap();
|
|
||||||
match evt {
|
|
||||||
TouchEvent::Down {
|
|
||||||
surface, id, x, y, ..
|
|
||||||
} => {
|
|
||||||
let wid = store.find_wid(&surface);
|
|
||||||
if let Some(wid) = wid {
|
|
||||||
sink.send_window_event(
|
|
||||||
WindowEvent::Touch(crate::event::Touch {
|
|
||||||
device_id: crate::event::DeviceId(
|
|
||||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
|
||||||
),
|
|
||||||
phase: TouchPhase::Started,
|
|
||||||
location: (x, y).into(),
|
|
||||||
force: None, // TODO
|
|
||||||
id: id as u64,
|
|
||||||
}),
|
|
||||||
wid,
|
|
||||||
);
|
|
||||||
pending_ids.push(TouchPoint {
|
|
||||||
wid,
|
|
||||||
location: (x, y),
|
|
||||||
id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TouchEvent::Up { id, .. } => {
|
|
||||||
let idx = pending_ids.iter().position(|p| p.id == id);
|
|
||||||
if let Some(idx) = idx {
|
|
||||||
let pt = pending_ids.remove(idx);
|
|
||||||
sink.send_window_event(
|
|
||||||
WindowEvent::Touch(crate::event::Touch {
|
|
||||||
device_id: crate::event::DeviceId(
|
|
||||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
|
||||||
),
|
|
||||||
phase: TouchPhase::Ended,
|
|
||||||
location: pt.location.into(),
|
|
||||||
force: None, // TODO
|
|
||||||
id: id as u64,
|
|
||||||
}),
|
|
||||||
pt.wid,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TouchEvent::Motion { id, x, y, .. } => {
|
|
||||||
let pt = pending_ids.iter_mut().find(|p| p.id == id);
|
|
||||||
if let Some(pt) = pt {
|
|
||||||
pt.location = (x, y);
|
|
||||||
sink.send_window_event(
|
|
||||||
WindowEvent::Touch(crate::event::Touch {
|
|
||||||
device_id: crate::event::DeviceId(
|
|
||||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
|
||||||
),
|
|
||||||
phase: TouchPhase::Moved,
|
|
||||||
location: (x, y).into(),
|
|
||||||
force: None, // TODO
|
|
||||||
id: id as u64,
|
|
||||||
}),
|
|
||||||
pt.wid,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TouchEvent::Frame => (),
|
|
||||||
TouchEvent::Cancel => {
|
|
||||||
for pt in pending_ids.drain(..) {
|
|
||||||
sink.send_window_event(
|
|
||||||
WindowEvent::Touch(crate::event::Touch {
|
|
||||||
device_id: crate::event::DeviceId(
|
|
||||||
crate::platform_impl::DeviceId::Wayland(DeviceId),
|
|
||||||
),
|
|
||||||
phase: TouchPhase::Cancelled,
|
|
||||||
location: pt.location.into(),
|
|
||||||
force: None, // TODO
|
|
||||||
id: pt.id as u64,
|
|
||||||
}),
|
|
||||||
pt.wid,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
@@ -1,520 +0,0 @@
|
|||||||
use raw_window_handle::unix::WaylandHandle;
|
|
||||||
use std::{
|
|
||||||
collections::VecDeque,
|
|
||||||
mem::replace,
|
|
||||||
sync::{Arc, Mutex, Weak},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
|
|
||||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
|
||||||
monitor::MonitorHandle as RootMonitorHandle,
|
|
||||||
platform_impl::{
|
|
||||||
platform::wayland::event_loop::{available_monitors, primary_monitor},
|
|
||||||
MonitorHandle as PlatformMonitorHandle,
|
|
||||||
PlatformSpecificWindowBuilderAttributes as PlAttributes,
|
|
||||||
},
|
|
||||||
window::{CursorIcon, Fullscreen, WindowAttributes},
|
|
||||||
};
|
|
||||||
|
|
||||||
use smithay_client_toolkit::{
|
|
||||||
output::OutputMgr,
|
|
||||||
reexports::client::{
|
|
||||||
protocol::{wl_seat, wl_surface},
|
|
||||||
Display,
|
|
||||||
},
|
|
||||||
surface::{get_dpi_factor, get_outputs},
|
|
||||||
window::{ConceptFrame, Event as WEvent, State as WState, Theme, Window as SWindow},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{event_loop::CursorManager, make_wid, EventLoopWindowTarget, MonitorHandle, WindowId};
|
|
||||||
|
|
||||||
pub struct Window {
|
|
||||||
surface: wl_surface::WlSurface,
|
|
||||||
frame: Arc<Mutex<SWindow<ConceptFrame>>>,
|
|
||||||
cursor_manager: Arc<Mutex<CursorManager>>,
|
|
||||||
outputs: OutputMgr, // Access to info for all monitors
|
|
||||||
size: Arc<Mutex<(u32, u32)>>,
|
|
||||||
kill_switch: (Arc<Mutex<bool>>, Arc<Mutex<bool>>),
|
|
||||||
display: Arc<Display>,
|
|
||||||
need_frame_refresh: Arc<Mutex<bool>>,
|
|
||||||
need_refresh: Arc<Mutex<bool>>,
|
|
||||||
fullscreen: Arc<Mutex<bool>>,
|
|
||||||
cursor_grab_changed: Arc<Mutex<Option<bool>>>, // Update grab state
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Window {
|
|
||||||
pub fn new<T>(
|
|
||||||
evlp: &EventLoopWindowTarget<T>,
|
|
||||||
attributes: WindowAttributes,
|
|
||||||
pl_attribs: PlAttributes,
|
|
||||||
) -> Result<Window, RootOsError> {
|
|
||||||
// Create the surface first to get initial DPI
|
|
||||||
let window_store = evlp.store.clone();
|
|
||||||
let cursor_manager = evlp.cursor_manager.clone();
|
|
||||||
let surface = evlp.env.create_surface(move |dpi, surface| {
|
|
||||||
window_store.lock().unwrap().dpi_change(&surface, dpi);
|
|
||||||
surface.set_buffer_scale(dpi);
|
|
||||||
});
|
|
||||||
|
|
||||||
let dpi = get_dpi_factor(&surface) as f64;
|
|
||||||
let (width, height) = attributes
|
|
||||||
.inner_size
|
|
||||||
.map(|size| size.to_logical::<f64>(dpi).into())
|
|
||||||
.unwrap_or((800, 600));
|
|
||||||
|
|
||||||
// Create the window
|
|
||||||
let size = Arc::new(Mutex::new((width, height)));
|
|
||||||
let fullscreen = Arc::new(Mutex::new(false));
|
|
||||||
|
|
||||||
let window_store = evlp.store.clone();
|
|
||||||
|
|
||||||
let my_surface = surface.clone();
|
|
||||||
let mut frame = SWindow::<ConceptFrame>::init_from_env(
|
|
||||||
&evlp.env,
|
|
||||||
surface.clone(),
|
|
||||||
(width, height),
|
|
||||||
move |event| match event {
|
|
||||||
WEvent::Configure { new_size, states } => {
|
|
||||||
let mut store = window_store.lock().unwrap();
|
|
||||||
let is_fullscreen = states.contains(&WState::Fullscreen);
|
|
||||||
|
|
||||||
for window in &mut store.windows {
|
|
||||||
if window.surface.as_ref().equals(&my_surface.as_ref()) {
|
|
||||||
window.newsize = new_size;
|
|
||||||
*(window.need_refresh.lock().unwrap()) = true;
|
|
||||||
*(window.fullscreen.lock().unwrap()) = is_fullscreen;
|
|
||||||
*(window.need_frame_refresh.lock().unwrap()) = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WEvent::Refresh => {
|
|
||||||
let store = window_store.lock().unwrap();
|
|
||||||
for window in &store.windows {
|
|
||||||
if window.surface.as_ref().equals(&my_surface.as_ref()) {
|
|
||||||
*(window.need_frame_refresh.lock().unwrap()) = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
WEvent::Close => {
|
|
||||||
let mut store = window_store.lock().unwrap();
|
|
||||||
for window in &mut store.windows {
|
|
||||||
if window.surface.as_ref().equals(&my_surface.as_ref()) {
|
|
||||||
window.closed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
if let Some(app_id) = pl_attribs.app_id {
|
|
||||||
frame.set_app_id(app_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
for &(_, ref seat) in evlp.seats.lock().unwrap().iter() {
|
|
||||||
frame.new_seat(seat);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for fullscreen requirements
|
|
||||||
match attributes.fullscreen {
|
|
||||||
Some(Fullscreen::Exclusive(_)) => {
|
|
||||||
panic!("Wayland doesn't support exclusive fullscreen")
|
|
||||||
}
|
|
||||||
Some(Fullscreen::Borderless(RootMonitorHandle {
|
|
||||||
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
|
|
||||||
})) => frame.set_fullscreen(Some(&monitor_id.proxy)),
|
|
||||||
Some(Fullscreen::Borderless(_)) => unreachable!(),
|
|
||||||
None => {
|
|
||||||
if attributes.maximized {
|
|
||||||
frame.set_maximized();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
frame.set_resizable(attributes.resizable);
|
|
||||||
|
|
||||||
// set decorations
|
|
||||||
frame.set_decorate(attributes.decorations);
|
|
||||||
|
|
||||||
// set title
|
|
||||||
frame.set_title(attributes.title);
|
|
||||||
|
|
||||||
// min-max dimensions
|
|
||||||
frame.set_min_size(
|
|
||||||
attributes
|
|
||||||
.min_inner_size
|
|
||||||
.map(|size| size.to_logical::<f64>(dpi).into()),
|
|
||||||
);
|
|
||||||
frame.set_max_size(
|
|
||||||
attributes
|
|
||||||
.max_inner_size
|
|
||||||
.map(|size| size.to_logical::<f64>(dpi).into()),
|
|
||||||
);
|
|
||||||
|
|
||||||
let kill_switch = Arc::new(Mutex::new(false));
|
|
||||||
let need_frame_refresh = Arc::new(Mutex::new(true));
|
|
||||||
let frame = Arc::new(Mutex::new(frame));
|
|
||||||
let need_refresh = Arc::new(Mutex::new(true));
|
|
||||||
let cursor_grab_changed = Arc::new(Mutex::new(None));
|
|
||||||
|
|
||||||
evlp.store.lock().unwrap().windows.push(InternalWindow {
|
|
||||||
closed: false,
|
|
||||||
newsize: None,
|
|
||||||
size: size.clone(),
|
|
||||||
need_refresh: need_refresh.clone(),
|
|
||||||
fullscreen: fullscreen.clone(),
|
|
||||||
cursor_grab_changed: cursor_grab_changed.clone(),
|
|
||||||
need_frame_refresh: need_frame_refresh.clone(),
|
|
||||||
surface: surface.clone(),
|
|
||||||
kill_switch: kill_switch.clone(),
|
|
||||||
frame: Arc::downgrade(&frame),
|
|
||||||
current_dpi: 1,
|
|
||||||
new_dpi: None,
|
|
||||||
});
|
|
||||||
evlp.evq.borrow_mut().sync_roundtrip().unwrap();
|
|
||||||
|
|
||||||
Ok(Window {
|
|
||||||
display: evlp.display.clone(),
|
|
||||||
surface,
|
|
||||||
frame,
|
|
||||||
outputs: evlp.env.outputs.clone(),
|
|
||||||
size,
|
|
||||||
kill_switch: (kill_switch, evlp.cleanup_needed.clone()),
|
|
||||||
need_frame_refresh,
|
|
||||||
need_refresh,
|
|
||||||
cursor_manager,
|
|
||||||
fullscreen,
|
|
||||||
cursor_grab_changed,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn id(&self) -> WindowId {
|
|
||||||
make_wid(&self.surface)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_title(&self, title: &str) {
|
|
||||||
self.frame.lock().unwrap().set_title(title.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_visible(&self, _visible: bool) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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, _pos: Position) {
|
|
||||||
// Not possible with wayland
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn inner_size(&self) -> PhysicalSize<u32> {
|
|
||||||
let dpi = self.scale_factor() as f64;
|
|
||||||
let size = LogicalSize::<f64>::from(*self.size.lock().unwrap());
|
|
||||||
size.to_physical(dpi)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn request_redraw(&self) {
|
|
||||||
*self.need_refresh.lock().unwrap() = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn outer_size(&self) -> PhysicalSize<u32> {
|
|
||||||
let dpi = self.scale_factor() as f64;
|
|
||||||
let (w, h) = self.size.lock().unwrap().clone();
|
|
||||||
// let (w, h) = super::wayland_window::add_borders(w as i32, h as i32);
|
|
||||||
let size = LogicalSize::<f64>::from((w, h));
|
|
||||||
size.to_physical(dpi)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
// NOTE: This will only resize the borders, the contents must be updated by the user
|
|
||||||
pub fn set_inner_size(&self, size: Size) {
|
|
||||||
let dpi = self.scale_factor() as f64;
|
|
||||||
let (w, h) = size.to_logical::<u32>(dpi).into();
|
|
||||||
self.frame.lock().unwrap().resize(w, h);
|
|
||||||
*(self.size.lock().unwrap()) = (w, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_min_inner_size(&self, dimensions: Option<Size>) {
|
|
||||||
let dpi = self.scale_factor() as f64;
|
|
||||||
self.frame
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_min_size(dimensions.map(|dim| dim.to_logical::<f64>(dpi).into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_max_inner_size(&self, dimensions: Option<Size>) {
|
|
||||||
let dpi = self.scale_factor() as f64;
|
|
||||||
self.frame
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_max_size(dimensions.map(|dim| dim.to_logical::<f64>(dpi).into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_resizable(&self, resizable: bool) {
|
|
||||||
self.frame.lock().unwrap().set_resizable(resizable);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn scale_factor(&self) -> i32 {
|
|
||||||
get_dpi_factor(&self.surface)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_decorations(&self, decorate: bool) {
|
|
||||||
self.frame.lock().unwrap().set_decorate(decorate);
|
|
||||||
*(self.need_frame_refresh.lock().unwrap()) = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_minimized(&self, minimized: bool) {
|
|
||||||
// An app cannot un-minimize itself on Wayland
|
|
||||||
if minimized {
|
|
||||||
self.frame.lock().unwrap().set_minimized();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_maximized(&self, maximized: bool) {
|
|
||||||
if maximized {
|
|
||||||
self.frame.lock().unwrap().set_maximized();
|
|
||||||
} else {
|
|
||||||
self.frame.lock().unwrap().unset_maximized();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
|
||||||
if *(self.fullscreen.lock().unwrap()) {
|
|
||||||
Some(Fullscreen::Borderless(RootMonitorHandle {
|
|
||||||
inner: PlatformMonitorHandle::Wayland(self.current_monitor()),
|
|
||||||
}))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
|
||||||
match fullscreen {
|
|
||||||
Some(Fullscreen::Exclusive(_)) => {
|
|
||||||
panic!("Wayland doesn't support exclusive fullscreen")
|
|
||||||
}
|
|
||||||
Some(Fullscreen::Borderless(RootMonitorHandle {
|
|
||||||
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
|
|
||||||
})) => {
|
|
||||||
self.frame
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.set_fullscreen(Some(&monitor_id.proxy));
|
|
||||||
}
|
|
||||||
Some(Fullscreen::Borderless(_)) => unreachable!(),
|
|
||||||
None => self.frame.lock().unwrap().unset_fullscreen(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_theme<T: Theme>(&self, theme: T) {
|
|
||||||
self.frame.lock().unwrap().set_theme(theme)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_cursor_icon(&self, cursor: CursorIcon) {
|
|
||||||
let mut cursor_manager = self.cursor_manager.lock().unwrap();
|
|
||||||
cursor_manager.set_cursor_icon(cursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_cursor_visible(&self, visible: bool) {
|
|
||||||
let mut cursor_manager = self.cursor_manager.lock().unwrap();
|
|
||||||
cursor_manager.set_cursor_visible(visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
|
|
||||||
*self.cursor_grab_changed.lock().unwrap() = Some(grab);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn set_cursor_position(&self, _pos: Position) -> Result<(), ExternalError> {
|
|
||||||
Err(ExternalError::NotSupported(NotSupportedError::new()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn display(&self) -> &Display {
|
|
||||||
&*self.display
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn surface(&self) -> &wl_surface::WlSurface {
|
|
||||||
&self.surface
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn current_monitor(&self) -> MonitorHandle {
|
|
||||||
let output = get_outputs(&self.surface).last().unwrap().clone();
|
|
||||||
MonitorHandle {
|
|
||||||
proxy: output,
|
|
||||||
mgr: self.outputs.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
|
||||||
available_monitors(&self.outputs)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
|
||||||
primary_monitor(&self.outputs)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn raw_window_handle(&self) -> WaylandHandle {
|
|
||||||
WaylandHandle {
|
|
||||||
surface: self.surface().as_ref().c_ptr() as *mut _,
|
|
||||||
display: self.display().as_ref().c_ptr() as *mut _,
|
|
||||||
..WaylandHandle::empty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Window {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
*(self.kill_switch.0.lock().unwrap()) = true;
|
|
||||||
*(self.kill_switch.1.lock().unwrap()) = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Internal store for windows
|
|
||||||
*/
|
|
||||||
|
|
||||||
struct InternalWindow {
|
|
||||||
surface: wl_surface::WlSurface,
|
|
||||||
// TODO: CONVERT TO LogicalSize<u32>s
|
|
||||||
newsize: Option<(u32, u32)>,
|
|
||||||
size: Arc<Mutex<(u32, u32)>>,
|
|
||||||
need_refresh: Arc<Mutex<bool>>,
|
|
||||||
fullscreen: Arc<Mutex<bool>>,
|
|
||||||
need_frame_refresh: Arc<Mutex<bool>>,
|
|
||||||
cursor_grab_changed: Arc<Mutex<Option<bool>>>,
|
|
||||||
closed: bool,
|
|
||||||
kill_switch: Arc<Mutex<bool>>,
|
|
||||||
frame: Weak<Mutex<SWindow<ConceptFrame>>>,
|
|
||||||
current_dpi: i32,
|
|
||||||
new_dpi: Option<i32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct WindowStore {
|
|
||||||
windows: Vec<InternalWindow>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct WindowStoreForEach<'a> {
|
|
||||||
pub newsize: Option<(u32, u32)>,
|
|
||||||
pub size: &'a mut (u32, u32),
|
|
||||||
pub prev_dpi: i32,
|
|
||||||
pub new_dpi: Option<i32>,
|
|
||||||
pub closed: bool,
|
|
||||||
pub grab_cursor: Option<bool>,
|
|
||||||
pub surface: &'a wl_surface::WlSurface,
|
|
||||||
pub wid: WindowId,
|
|
||||||
pub frame: Option<&'a mut SWindow<ConceptFrame>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WindowStore {
|
|
||||||
pub fn new() -> WindowStore {
|
|
||||||
WindowStore {
|
|
||||||
windows: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_wid(&self, surface: &wl_surface::WlSurface) -> Option<WindowId> {
|
|
||||||
for window in &self.windows {
|
|
||||||
if surface.as_ref().equals(&window.surface.as_ref()) {
|
|
||||||
return Some(make_wid(surface));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cleanup(&mut self) -> Vec<WindowId> {
|
|
||||||
let mut pruned = Vec::new();
|
|
||||||
self.windows.retain(|w| {
|
|
||||||
if *w.kill_switch.lock().unwrap() {
|
|
||||||
// window is dead, cleanup
|
|
||||||
pruned.push(make_wid(&w.surface));
|
|
||||||
w.surface.destroy();
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
pruned
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_seat(&self, seat: &wl_seat::WlSeat) {
|
|
||||||
for window in &self.windows {
|
|
||||||
if let Some(w) = window.frame.upgrade() {
|
|
||||||
w.lock().unwrap().new_seat(seat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dpi_change(&mut self, surface: &wl_surface::WlSurface, new: i32) {
|
|
||||||
for window in &mut self.windows {
|
|
||||||
if surface.as_ref().equals(&window.surface.as_ref()) {
|
|
||||||
window.new_dpi = Some(new);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn for_each<F>(&mut self, mut f: F)
|
|
||||||
where
|
|
||||||
F: FnMut(WindowStoreForEach<'_>),
|
|
||||||
{
|
|
||||||
for window in &mut self.windows {
|
|
||||||
let opt_arc = window.frame.upgrade();
|
|
||||||
let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap());
|
|
||||||
let mut size = { *window.size.lock().unwrap() };
|
|
||||||
f(WindowStoreForEach {
|
|
||||||
newsize: window.newsize.take(),
|
|
||||||
size: &mut size,
|
|
||||||
prev_dpi: window.current_dpi,
|
|
||||||
new_dpi: window.new_dpi,
|
|
||||||
closed: window.closed,
|
|
||||||
grab_cursor: window.cursor_grab_changed.lock().unwrap().take(),
|
|
||||||
surface: &window.surface,
|
|
||||||
wid: make_wid(&window.surface),
|
|
||||||
frame: opt_mutex_lock.as_mut().map(|m| &mut **m),
|
|
||||||
});
|
|
||||||
*window.size.lock().unwrap() = size;
|
|
||||||
if let Some(dpi) = window.new_dpi.take() {
|
|
||||||
window.current_dpi = dpi;
|
|
||||||
}
|
|
||||||
// avoid re-spamming the event
|
|
||||||
window.closed = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn for_each_redraw_trigger<F>(&mut self, mut f: F)
|
|
||||||
where
|
|
||||||
F: FnMut(bool, bool, WindowId, Option<&mut SWindow<ConceptFrame>>),
|
|
||||||
{
|
|
||||||
for window in &mut self.windows {
|
|
||||||
let opt_arc = window.frame.upgrade();
|
|
||||||
let mut opt_mutex_lock = opt_arc.as_ref().map(|m| m.lock().unwrap());
|
|
||||||
f(
|
|
||||||
replace(&mut *window.need_refresh.lock().unwrap(), false),
|
|
||||||
replace(&mut *window.need_frame_refresh.lock().unwrap(), false),
|
|
||||||
make_wid(&window.surface),
|
|
||||||
opt_mutex_lock.as_mut().map(|m| &mut **m),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +0,0 @@
|
|||||||
pub use x11_dl::{
|
|
||||||
error::OpenError, keysym::*, xcursor::*, xinput::*, xinput2::*, xlib::*, xlib_xcb::*,
|
|
||||||
xrandr::*, xrender::*,
|
|
||||||
};
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
use std::{collections::HashMap, os::raw::c_char, ptr, sync::Arc};
|
|
||||||
|
|
||||||
use super::{ffi, XConnection, XError};
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
context::{ImeContext, ImeContextCreationError},
|
|
||||||
inner::{close_im, ImeInner},
|
|
||||||
input_method::PotentialInputMethods,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub unsafe fn xim_set_callback(
|
|
||||||
xconn: &Arc<XConnection>,
|
|
||||||
xim: ffi::XIM,
|
|
||||||
field: *const c_char,
|
|
||||||
callback: *mut ffi::XIMCallback,
|
|
||||||
) -> Result<(), XError> {
|
|
||||||
// It's advisable to wrap variadic FFI functions in our own functions, as we want to minimize
|
|
||||||
// access that isn't type-checked.
|
|
||||||
(xconn.xlib.XSetIMValues)(xim, field, callback, ptr::null_mut::<()>());
|
|
||||||
xconn.check_errors()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a callback for when an input method matching the current locale modifiers becomes
|
|
||||||
// available. Note that this has nothing to do with what input methods are open or able to be
|
|
||||||
// opened, and simply uses the modifiers that are set when the callback is set.
|
|
||||||
// * This is called per locale modifier, not per input method opened with that locale modifier.
|
|
||||||
// * Trying to set this for multiple locale modifiers causes problems, i.e. one of the rebuilt
|
|
||||||
// input contexts would always silently fail to use the input method.
|
|
||||||
pub unsafe fn set_instantiate_callback(
|
|
||||||
xconn: &Arc<XConnection>,
|
|
||||||
client_data: ffi::XPointer,
|
|
||||||
) -> Result<(), XError> {
|
|
||||||
(xconn.xlib.XRegisterIMInstantiateCallback)(
|
|
||||||
xconn.display,
|
|
||||||
ptr::null_mut(),
|
|
||||||
ptr::null_mut(),
|
|
||||||
ptr::null_mut(),
|
|
||||||
Some(xim_instantiate_callback),
|
|
||||||
client_data,
|
|
||||||
);
|
|
||||||
xconn.check_errors()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn unset_instantiate_callback(
|
|
||||||
xconn: &Arc<XConnection>,
|
|
||||||
client_data: ffi::XPointer,
|
|
||||||
) -> Result<(), XError> {
|
|
||||||
(xconn.xlib.XUnregisterIMInstantiateCallback)(
|
|
||||||
xconn.display,
|
|
||||||
ptr::null_mut(),
|
|
||||||
ptr::null_mut(),
|
|
||||||
ptr::null_mut(),
|
|
||||||
Some(xim_instantiate_callback),
|
|
||||||
client_data,
|
|
||||||
);
|
|
||||||
xconn.check_errors()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn set_destroy_callback(
|
|
||||||
xconn: &Arc<XConnection>,
|
|
||||||
im: ffi::XIM,
|
|
||||||
inner: &ImeInner,
|
|
||||||
) -> Result<(), XError> {
|
|
||||||
xim_set_callback(
|
|
||||||
&xconn,
|
|
||||||
im,
|
|
||||||
ffi::XNDestroyCallback_0.as_ptr() as *const _,
|
|
||||||
&inner.destroy_callback as *const _ as *mut _,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum ReplaceImError {
|
|
||||||
MethodOpenFailed(PotentialInputMethods),
|
|
||||||
ContextCreationFailed(ImeContextCreationError),
|
|
||||||
SetDestroyCallbackFailed(XError),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to replace current IM (which may or may not be presently valid) with a new one. This
|
|
||||||
// includes replacing all existing input contexts and free'ing resources as necessary. This only
|
|
||||||
// modifies existing state if all operations succeed.
|
|
||||||
unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> {
|
|
||||||
let xconn = &(*inner).xconn;
|
|
||||||
|
|
||||||
let (new_im, is_fallback) = {
|
|
||||||
let new_im = (*inner).potential_input_methods.open_im(xconn, None);
|
|
||||||
let is_fallback = new_im.is_fallback();
|
|
||||||
(
|
|
||||||
new_im.ok().ok_or_else(|| {
|
|
||||||
ReplaceImError::MethodOpenFailed((*inner).potential_input_methods.clone())
|
|
||||||
})?,
|
|
||||||
is_fallback,
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
// It's important to always set a destroy callback, since there's otherwise potential for us
|
|
||||||
// to try to use or free a resource that's already been destroyed on the server.
|
|
||||||
{
|
|
||||||
let result = set_destroy_callback(xconn, new_im.im, &*inner);
|
|
||||||
if result.is_err() {
|
|
||||||
let _ = close_im(xconn, new_im.im);
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
.map_err(ReplaceImError::SetDestroyCallbackFailed)?;
|
|
||||||
|
|
||||||
let mut new_contexts = HashMap::new();
|
|
||||||
for (window, old_context) in (*inner).contexts.iter() {
|
|
||||||
let spot = old_context.as_ref().map(|old_context| old_context.ic_spot);
|
|
||||||
let new_context = {
|
|
||||||
let result = ImeContext::new(xconn, new_im.im, *window, spot);
|
|
||||||
if result.is_err() {
|
|
||||||
let _ = close_im(xconn, new_im.im);
|
|
||||||
}
|
|
||||||
result.map_err(ReplaceImError::ContextCreationFailed)?
|
|
||||||
};
|
|
||||||
new_contexts.insert(*window, Some(new_context));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we've made it this far, everything succeeded.
|
|
||||||
let _ = (*inner).destroy_all_contexts_if_necessary();
|
|
||||||
let _ = (*inner).close_im_if_necessary();
|
|
||||||
(*inner).im = new_im.im;
|
|
||||||
(*inner).contexts = new_contexts;
|
|
||||||
(*inner).is_destroyed = false;
|
|
||||||
(*inner).is_fallback = is_fallback;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe extern "C" fn xim_instantiate_callback(
|
|
||||||
_display: *mut ffi::Display,
|
|
||||||
client_data: ffi::XPointer,
|
|
||||||
// This field is unsupplied.
|
|
||||||
_call_data: ffi::XPointer,
|
|
||||||
) {
|
|
||||||
let inner: *mut ImeInner = client_data as _;
|
|
||||||
if !inner.is_null() {
|
|
||||||
let xconn = &(*inner).xconn;
|
|
||||||
let result = replace_im(inner);
|
|
||||||
if result.is_ok() {
|
|
||||||
let _ = unset_instantiate_callback(xconn, client_data);
|
|
||||||
(*inner).is_fallback = false;
|
|
||||||
} else if result.is_err() && (*inner).is_destroyed {
|
|
||||||
// We have no usable input methods!
|
|
||||||
result.expect("Failed to reopen input method");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This callback is triggered when the input method is closed on the server end. When this
|
|
||||||
// happens, XCloseIM/XDestroyIC doesn't need to be called, as the resources have already been
|
|
||||||
// free'd (attempting to do so causes our connection to freeze).
|
|
||||||
pub unsafe extern "C" fn xim_destroy_callback(
|
|
||||||
_xim: ffi::XIM,
|
|
||||||
client_data: ffi::XPointer,
|
|
||||||
// This field is unsupplied.
|
|
||||||
_call_data: ffi::XPointer,
|
|
||||||
) {
|
|
||||||
let inner: *mut ImeInner = client_data as _;
|
|
||||||
if !inner.is_null() {
|
|
||||||
(*inner).is_destroyed = true;
|
|
||||||
let xconn = &(*inner).xconn;
|
|
||||||
if !(*inner).is_fallback {
|
|
||||||
let _ = set_instantiate_callback(xconn, client_data);
|
|
||||||
// Attempt to open fallback input method.
|
|
||||||
let result = replace_im(inner);
|
|
||||||
if result.is_ok() {
|
|
||||||
(*inner).is_fallback = true;
|
|
||||||
} else {
|
|
||||||
// We have no usable input methods!
|
|
||||||
result.expect("Failed to open fallback input method");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
use std::{
|
|
||||||
os::raw::{c_short, c_void},
|
|
||||||
ptr,
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{ffi, util, XConnection, XError};
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum ImeContextCreationError {
|
|
||||||
XError(XError),
|
|
||||||
Null,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn create_pre_edit_attr<'a>(
|
|
||||||
xconn: &'a Arc<XConnection>,
|
|
||||||
ic_spot: &'a ffi::XPoint,
|
|
||||||
) -> util::XSmartPointer<'a, c_void> {
|
|
||||||
util::XSmartPointer::new(
|
|
||||||
xconn,
|
|
||||||
(xconn.xlib.XVaCreateNestedList)(
|
|
||||||
0,
|
|
||||||
ffi::XNSpotLocation_0.as_ptr() as *const _,
|
|
||||||
ic_spot,
|
|
||||||
ptr::null_mut::<()>(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.expect("XVaCreateNestedList returned NULL")
|
|
||||||
}
|
|
||||||
|
|
||||||
// WARNING: this struct doesn't destroy its XIC resource when dropped.
|
|
||||||
// This is intentional, as it doesn't have enough information to know whether or not the context
|
|
||||||
// still exists on the server. Since `ImeInner` has that awareness, destruction must be handled
|
|
||||||
// through `ImeInner`.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ImeContext {
|
|
||||||
pub ic: ffi::XIC,
|
|
||||||
pub ic_spot: ffi::XPoint,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ImeContext {
|
|
||||||
pub unsafe fn new(
|
|
||||||
xconn: &Arc<XConnection>,
|
|
||||||
im: ffi::XIM,
|
|
||||||
window: ffi::Window,
|
|
||||||
ic_spot: Option<ffi::XPoint>,
|
|
||||||
) -> Result<Self, ImeContextCreationError> {
|
|
||||||
let ic = if let Some(ic_spot) = ic_spot {
|
|
||||||
ImeContext::create_ic_with_spot(xconn, im, window, ic_spot)
|
|
||||||
} else {
|
|
||||||
ImeContext::create_ic(xconn, im, window)
|
|
||||||
};
|
|
||||||
|
|
||||||
let ic = ic.ok_or(ImeContextCreationError::Null)?;
|
|
||||||
xconn
|
|
||||||
.check_errors()
|
|
||||||
.map_err(ImeContextCreationError::XError)?;
|
|
||||||
|
|
||||||
Ok(ImeContext {
|
|
||||||
ic,
|
|
||||||
ic_spot: ic_spot.unwrap_or_else(|| ffi::XPoint { x: 0, y: 0 }),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn create_ic(
|
|
||||||
xconn: &Arc<XConnection>,
|
|
||||||
im: ffi::XIM,
|
|
||||||
window: ffi::Window,
|
|
||||||
) -> Option<ffi::XIC> {
|
|
||||||
let ic = (xconn.xlib.XCreateIC)(
|
|
||||||
im,
|
|
||||||
ffi::XNInputStyle_0.as_ptr() as *const _,
|
|
||||||
ffi::XIMPreeditNothing | ffi::XIMStatusNothing,
|
|
||||||
ffi::XNClientWindow_0.as_ptr() as *const _,
|
|
||||||
window,
|
|
||||||
ptr::null_mut::<()>(),
|
|
||||||
);
|
|
||||||
if ic.is_null() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(ic)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn create_ic_with_spot(
|
|
||||||
xconn: &Arc<XConnection>,
|
|
||||||
im: ffi::XIM,
|
|
||||||
window: ffi::Window,
|
|
||||||
ic_spot: ffi::XPoint,
|
|
||||||
) -> Option<ffi::XIC> {
|
|
||||||
let pre_edit_attr = create_pre_edit_attr(xconn, &ic_spot);
|
|
||||||
let ic = (xconn.xlib.XCreateIC)(
|
|
||||||
im,
|
|
||||||
ffi::XNInputStyle_0.as_ptr() as *const _,
|
|
||||||
ffi::XIMPreeditNothing | ffi::XIMStatusNothing,
|
|
||||||
ffi::XNClientWindow_0.as_ptr() as *const _,
|
|
||||||
window,
|
|
||||||
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
|
|
||||||
pre_edit_attr.ptr,
|
|
||||||
ptr::null_mut::<()>(),
|
|
||||||
);
|
|
||||||
if ic.is_null() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(ic)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn focus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
|
|
||||||
unsafe {
|
|
||||||
(xconn.xlib.XSetICFocus)(self.ic);
|
|
||||||
}
|
|
||||||
xconn.check_errors()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unfocus(&self, xconn: &Arc<XConnection>) -> Result<(), XError> {
|
|
||||||
unsafe {
|
|
||||||
(xconn.xlib.XUnsetICFocus)(self.ic);
|
|
||||||
}
|
|
||||||
xconn.check_errors()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_spot(&mut self, xconn: &Arc<XConnection>, x: c_short, y: c_short) {
|
|
||||||
if self.ic_spot.x == x && self.ic_spot.y == y {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.ic_spot = ffi::XPoint { x, y };
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let pre_edit_attr = create_pre_edit_attr(xconn, &self.ic_spot);
|
|
||||||
(xconn.xlib.XSetICValues)(
|
|
||||||
self.ic,
|
|
||||||
ffi::XNPreeditAttributes_0.as_ptr() as *const _,
|
|
||||||
pre_edit_attr.ptr,
|
|
||||||
ptr::null_mut::<()>(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,692 +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::RefCell,
|
|
||||||
collections::{HashMap, HashSet},
|
|
||||||
ffi::CStr,
|
|
||||||
mem::{self, MaybeUninit},
|
|
||||||
ops::Deref,
|
|
||||||
os::raw::*,
|
|
||||||
rc::Rc,
|
|
||||||
slice,
|
|
||||||
sync::{mpsc, Arc, Mutex, Weak},
|
|
||||||
time::{Duration, Instant},
|
|
||||||
};
|
|
||||||
|
|
||||||
use libc::{self, setlocale, LC_CTYPE};
|
|
||||||
|
|
||||||
use mio::{unix::EventedFd, Events, Poll, PollOpt, Ready, Token};
|
|
||||||
|
|
||||||
use mio_extras::channel::{channel, Receiver, SendError, Sender};
|
|
||||||
|
|
||||||
use self::{
|
|
||||||
dnd::{Dnd, DndState},
|
|
||||||
event_processor::EventProcessor,
|
|
||||||
ime::{Ime, ImeCreationError, ImeReceiver, ImeSender},
|
|
||||||
util::modifiers::ModifierKeymap,
|
|
||||||
};
|
|
||||||
use crate::{
|
|
||||||
error::OsError as RootOsError,
|
|
||||||
event::{Event, StartCause},
|
|
||||||
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
|
||||||
platform_impl::{platform::sticky_exit_callback, PlatformSpecificWindowBuilderAttributes},
|
|
||||||
window::WindowAttributes,
|
|
||||||
};
|
|
||||||
|
|
||||||
const X_TOKEN: Token = Token(0);
|
|
||||||
const USER_TOKEN: Token = Token(1);
|
|
||||||
|
|
||||||
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>>>,
|
|
||||||
pending_redraws: Arc<Mutex<HashSet<WindowId>>>,
|
|
||||||
_marker: ::std::marker::PhantomData<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventLoop<T: 'static> {
|
|
||||||
poll: Poll,
|
|
||||||
event_processor: EventProcessor<T>,
|
|
||||||
user_channel: Receiver<T>,
|
|
||||||
user_sender: Sender<T>,
|
|
||||||
target: Rc<RootELW<T>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventLoopProxy<T: 'static> {
|
|
||||||
user_sender: Sender<T>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> Clone for EventLoopProxy<T> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
EventLoopProxy {
|
|
||||||
user_sender: self.user_sender.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();
|
|
||||||
// Input methods will open successfully without setting the locale, but it won't be
|
|
||||||
// possible to actually commit pre-edit sequences.
|
|
||||||
unsafe {
|
|
||||||
setlocale(LC_CTYPE, b"\0".as_ptr() as *const _);
|
|
||||||
}
|
|
||||||
let ime = RefCell::new({
|
|
||||||
let result = Ime::new(Arc::clone(&xconn));
|
|
||||||
if let Err(ImeCreationError::OpenFailure(ref state)) = result {
|
|
||||||
panic!(format!("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 pending_redraws: Arc<Mutex<HashSet<WindowId>>> = Default::default();
|
|
||||||
|
|
||||||
let mut mod_keymap = ModifierKeymap::new();
|
|
||||||
mod_keymap.reset_from_x_connection(&xconn);
|
|
||||||
|
|
||||||
let target = Rc::new(RootELW {
|
|
||||||
p: super::EventLoopWindowTarget::X(EventLoopWindowTarget {
|
|
||||||
ime,
|
|
||||||
root,
|
|
||||||
windows: Default::default(),
|
|
||||||
_marker: ::std::marker::PhantomData,
|
|
||||||
ime_sender,
|
|
||||||
xconn,
|
|
||||||
wm_delete_window,
|
|
||||||
net_wm_ping,
|
|
||||||
pending_redraws: pending_redraws.clone(),
|
|
||||||
}),
|
|
||||||
_marker: ::std::marker::PhantomData,
|
|
||||||
});
|
|
||||||
|
|
||||||
let poll = Poll::new().unwrap();
|
|
||||||
|
|
||||||
let (user_sender, user_channel) = channel();
|
|
||||||
|
|
||||||
poll.register(
|
|
||||||
&EventedFd(&get_xtarget(&target).xconn.x11_fd),
|
|
||||||
X_TOKEN,
|
|
||||||
Ready::readable(),
|
|
||||||
PollOpt::level(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
poll.register(
|
|
||||||
&user_channel,
|
|
||||||
USER_TOKEN,
|
|
||||||
Ready::readable(),
|
|
||||||
PollOpt::level(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let event_processor = EventProcessor {
|
|
||||||
target: target.clone(),
|
|
||||||
dnd,
|
|
||||||
devices: Default::default(),
|
|
||||||
randr_event_offset,
|
|
||||||
ime_receiver,
|
|
||||||
xi2ext,
|
|
||||||
mod_keymap,
|
|
||||||
device_mod_state: Default::default(),
|
|
||||||
num_touch: 0,
|
|
||||||
first_touch: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
let result = EventLoop {
|
|
||||||
poll,
|
|
||||||
user_channel,
|
|
||||||
user_sender,
|
|
||||||
event_processor,
|
|
||||||
target,
|
|
||||||
};
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
|
||||||
EventLoopProxy {
|
|
||||||
user_sender: self.user_sender.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn window_target(&self) -> &RootELW<T> {
|
|
||||||
&self.target
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn x_connection(&self) -> &Arc<XConnection> {
|
|
||||||
get_xtarget(&self.target).x_connection()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_return<F>(&mut self, mut callback: F)
|
|
||||||
where
|
|
||||||
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
|
||||||
{
|
|
||||||
let mut control_flow = ControlFlow::default();
|
|
||||||
let mut events = Events::with_capacity(8);
|
|
||||||
|
|
||||||
callback(
|
|
||||||
crate::event::Event::NewEvents(crate::event::StartCause::Init),
|
|
||||||
&self.target,
|
|
||||||
&mut control_flow,
|
|
||||||
);
|
|
||||||
|
|
||||||
loop {
|
|
||||||
// Process all pending events
|
|
||||||
self.drain_events(&mut callback, &mut control_flow);
|
|
||||||
|
|
||||||
let wt = get_xtarget(&self.target);
|
|
||||||
|
|
||||||
// Empty the user event buffer
|
|
||||||
{
|
|
||||||
while let Ok(event) = self.user_channel.try_recv() {
|
|
||||||
sticky_exit_callback(
|
|
||||||
crate::event::Event::UserEvent(event),
|
|
||||||
&self.target,
|
|
||||||
&mut control_flow,
|
|
||||||
&mut callback,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// send MainEventsCleared
|
|
||||||
{
|
|
||||||
sticky_exit_callback(
|
|
||||||
crate::event::Event::MainEventsCleared,
|
|
||||||
&self.target,
|
|
||||||
&mut control_flow,
|
|
||||||
&mut callback,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Empty the redraw requests
|
|
||||||
{
|
|
||||||
// Release the lock to prevent deadlock
|
|
||||||
let windows: Vec<_> = wt.pending_redraws.lock().unwrap().drain().collect();
|
|
||||||
|
|
||||||
for wid in windows {
|
|
||||||
sticky_exit_callback(
|
|
||||||
Event::RedrawRequested(crate::window::WindowId(super::WindowId::X(wid))),
|
|
||||||
&self.target,
|
|
||||||
&mut control_flow,
|
|
||||||
&mut callback,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// send RedrawEventsCleared
|
|
||||||
{
|
|
||||||
sticky_exit_callback(
|
|
||||||
crate::event::Event::RedrawEventsCleared,
|
|
||||||
&self.target,
|
|
||||||
&mut control_flow,
|
|
||||||
&mut callback,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let start = Instant::now();
|
|
||||||
let (mut cause, deadline, timeout);
|
|
||||||
|
|
||||||
match control_flow {
|
|
||||||
ControlFlow::Exit => break,
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.event_processor.poll() {
|
|
||||||
// If the XConnection already contains buffered events, we don't
|
|
||||||
// need to wait for data on the socket.
|
|
||||||
// However, we still need to check for user events.
|
|
||||||
self.poll
|
|
||||||
.poll(&mut events, Some(Duration::from_millis(0)))
|
|
||||||
.unwrap();
|
|
||||||
events.clear();
|
|
||||||
|
|
||||||
callback(
|
|
||||||
crate::event::Event::NewEvents(cause),
|
|
||||||
&self.target,
|
|
||||||
&mut control_flow,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
self.poll.poll(&mut events, timeout).unwrap();
|
|
||||||
events.clear();
|
|
||||||
|
|
||||||
let wait_cancelled = deadline.map_or(false, |deadline| Instant::now() < deadline);
|
|
||||||
|
|
||||||
if wait_cancelled {
|
|
||||||
cause = StartCause::WaitCancelled {
|
|
||||||
start,
|
|
||||||
requested_resume: deadline,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(
|
|
||||||
crate::event::Event::NewEvents(cause),
|
|
||||||
&self.target,
|
|
||||||
&mut control_flow,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(
|
|
||||||
crate::event::Event::LoopDestroyed,
|
|
||||||
&self.target,
|
|
||||||
&mut control_flow,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run<F>(mut self, callback: F) -> !
|
|
||||||
where
|
|
||||||
F: 'static + FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
|
||||||
{
|
|
||||||
self.run_return(callback);
|
|
||||||
::std::process::exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
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(
|
|
||||||
super::WindowId::X(wid),
|
|
||||||
)) = event
|
|
||||||
{
|
|
||||||
wt.pending_redraws.lock().unwrap().insert(wid);
|
|
||||||
} 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,
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoopWindowTarget<T> {
|
|
||||||
/// Returns the `XConnection` of this events loop.
|
|
||||||
#[inline]
|
|
||||||
pub fn x_connection(&self) -> &Arc<XConnection> {
|
|
||||||
&self.xconn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: 'static> EventLoopProxy<T> {
|
|
||||||
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
|
||||||
self.user_sender.send(event).map_err(|e| {
|
|
||||||
EventLoopClosed(if let SendError::Disconnected(x) = e {
|
|
||||||
x
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 WindowId(ffi::Window);
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct DeviceId(c_int);
|
|
||||||
|
|
||||||
pub struct Window(Arc<UnownedWindow>);
|
|
||||||
|
|
||||||
impl Deref for Window {
|
|
||||||
type Target = UnownedWindow;
|
|
||||||
#[inline]
|
|
||||||
fn deref(&self) -> &UnownedWindow {
|
|
||||||
&*self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Window {
|
|
||||||
pub 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);
|
|
||||||
// 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<'b>(
|
|
||||||
xconn: &'b XConnection,
|
|
||||||
event: ffi::XEvent,
|
|
||||||
) -> Option<GenericEventCookie<'b>> {
|
|
||||||
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::WindowId::X(WindowId(w)))
|
|
||||||
}
|
|
||||||
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<T: 'static>(el: &EventProcessor<T>, info: &ffi::XIDeviceInfo) -> Self {
|
|
||||||
let name = unsafe { CStr::from_ptr(info.name).to_string_lossy() };
|
|
||||||
let mut scroll_axes = Vec::new();
|
|
||||||
|
|
||||||
let wt = get_xtarget(&el.target);
|
|
||||||
|
|
||||||
if Device::physical_device(info) {
|
|
||||||
// Register for global raw events
|
|
||||||
let mask = ffi::XI_RawMotionMask
|
|
||||||
| ffi::XI_RawButtonPressMask
|
|
||||||
| ffi::XI_RawButtonReleaseMask
|
|
||||||
| ffi::XI_RawKeyPressMask
|
|
||||||
| ffi::XI_RawKeyReleaseMask;
|
|
||||||
// The request buffer is flushed when we poll for events
|
|
||||||
wt.xconn
|
|
||||||
.select_xinput_events(wt.root, info.deviceid, mask)
|
|
||||||
.queue();
|
|
||||||
|
|
||||||
// Identify scroll axes
|
|
||||||
for class_ptr in Device::classes(info) {
|
|
||||||
let class = unsafe { &**class_ptr };
|
|
||||||
match 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 };
|
|
||||||
match 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,314 +0,0 @@
|
|||||||
use std::os::raw::*;
|
|
||||||
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
ffi::{
|
|
||||||
RRCrtc, RRCrtcChangeNotifyMask, RRMode, RROutputPropertyNotifyMask,
|
|
||||||
RRScreenChangeNotifyMask, True, Window, XRRCrtcInfo, 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;
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref MONITORS: Mutex<Option<Vec<MonitorHandle>>> = 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: u16,
|
|
||||||
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(&self) -> u16 {
|
|
||||||
self.refresh_rate
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) };
|
|
||||||
let rect = util::AaRect::new(position, dimensions);
|
|
||||||
Some(MonitorHandle {
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
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),
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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 available;
|
|
||||||
let mut has_primary = false;
|
|
||||||
|
|
||||||
let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root);
|
|
||||||
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;
|
|
||||||
MonitorHandle::new(self, resources, crtc_id, crtc, is_primary)
|
|
||||||
.map(|monitor_id| 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,72 +0,0 @@
|
|||||||
use std::{
|
|
||||||
collections::HashMap,
|
|
||||||
ffi::{CStr, CString},
|
|
||||||
fmt::Debug,
|
|
||||||
os::raw::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
type AtomCache = HashMap<CString, ffi::Atom>;
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref ATOM_CACHE: Mutex<AtomCache> = 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 {
|
|
||||||
let msg = format!(
|
|
||||||
"`XInternAtom` failed, which really shouldn't happen. Atom: {:?}, Error: {:#?}",
|
|
||||||
name,
|
|
||||||
self.check_errors(),
|
|
||||||
);
|
|
||||||
panic!(msg);
|
|
||||||
}
|
|
||||||
/*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_bdiag\0"]),
|
|
||||||
CursorIcon::NeswResize => loadn(&[b"fd_double_arrow\0", b"size_fdiag\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,323 +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 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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.get(0).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,220 +0,0 @@
|
|||||||
use std::{env, slice, str::FromStr};
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
ffi::{CurrentTime, RRCrtc, RRMode, Success, XRRCrtcInfo, XRRScreenResources},
|
|
||||||
*,
|
|
||||||
};
|
|
||||||
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));
|
|
||||||
dpi_factor
|
|
||||||
}
|
|
||||||
|
|
||||||
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 == ptr::null_mut() {
|
|
||||||
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(|x| {
|
|
||||||
let refresh_rate = if x.dotClock > 0 && x.hTotal > 0 && x.vTotal > 0 {
|
|
||||||
x.dotClock as u64 * 1000 / (x.hTotal as u64 * x.vTotal as u64)
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
|
|
||||||
VideoMode {
|
|
||||||
size: (x.width, x.height),
|
|
||||||
refresh_rate: (refresh_rate as f32 / 1000.0).round() as u16,
|
|
||||||
bit_depth: bit_depth as u16,
|
|
||||||
native_mode: x.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))
|
|
||||||
}
|
|
||||||
pub fn set_crtc_config(&self, crtc_id: RRCrtc, mode_id: RRMode) -> Result<(), ()> {
|
|
||||||
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 {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 quanity 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,208 +0,0 @@
|
|||||||
// Normally when you run or distribute a macOS app, it's bundled: it's in one
|
|
||||||
// of those fun little folders that you have to right click "Show Package
|
|
||||||
// Contents" on, and usually contains myriad delights including, but not
|
|
||||||
// limited to, plists, icons, and of course, your beloved executable. However,
|
|
||||||
// when you use `cargo run`, your app is unbundled - it's just a lonely, bare
|
|
||||||
// executable.
|
|
||||||
//
|
|
||||||
// Apple isn't especially fond of unbundled apps, which is to say, they seem to
|
|
||||||
// barely be supported. If you move the mouse while opening a winit window from
|
|
||||||
// an unbundled app, the window will fail to activate and be in a grayed-out
|
|
||||||
// uninteractable state. Switching to another app and back is the only way to
|
|
||||||
// get the winit window into a normal state. None of this happens if the app is
|
|
||||||
// bundled, i.e. when running via Xcode.
|
|
||||||
//
|
|
||||||
// To workaround this, we just switch focus to the Dock and then switch back to
|
|
||||||
// our app. We only do this for unbundled apps, and only when they fail to
|
|
||||||
// become active on their own.
|
|
||||||
//
|
|
||||||
// This solution was derived from this Godot PR:
|
|
||||||
// https://github.com/godotengine/godot/pull/17187
|
|
||||||
// (which appears to be based on https://stackoverflow.com/a/7602677)
|
|
||||||
// The curious specialness of mouse motions is touched upon here:
|
|
||||||
// https://github.com/godotengine/godot/issues/8653#issuecomment-358130512
|
|
||||||
//
|
|
||||||
// We omit the 2nd step of the solution used in Godot, since it appears to have
|
|
||||||
// no effect - I speculate that it's just technical debt picked up from the SO
|
|
||||||
// answer; the API used is fairly exotic, and was historically used for very
|
|
||||||
// old versions of macOS that didn't support `activateIgnoringOtherApps`, i.e.
|
|
||||||
// in previous versions of SDL:
|
|
||||||
// https://hg.libsdl.org/SDL/file/c0bcc39a3491/src/video/cocoa/SDL_cocoaevents.m#l322
|
|
||||||
//
|
|
||||||
// The `performSelector` delays in the Godot solution are used for sequencing,
|
|
||||||
// since refocusing the app will fail if the call is made before it finishes
|
|
||||||
// unfocusing. The delays used there are much smaller than the ones in the
|
|
||||||
// original SO answer, presumably because they found the fastest delay that
|
|
||||||
// works reliably through trial and error. Instead of using delays, we just
|
|
||||||
// handle `applicationDidResignActive`; despite the app not activating reliably,
|
|
||||||
// that still triggers when we switch focus to the Dock.
|
|
||||||
//
|
|
||||||
// The Godot solution doesn't appear to skip the hack when an unbundled app
|
|
||||||
// activates normally. Checking for this is difficult, since if you call
|
|
||||||
// `isActive` too early, it will always be `NO`. Even though we receive
|
|
||||||
// `applicationDidResignActive` when switching focus to the Dock, we never
|
|
||||||
// receive a preceding `applicationDidBecomeActive` if the app fails to
|
|
||||||
// activate normally. I wasn't able to find a proper point in time to perform
|
|
||||||
// the `isActive` check, so we instead check for the cause of the quirk: if
|
|
||||||
// any mouse motion occurs prior to us receiving `applicationDidResignActive`,
|
|
||||||
// we assume the app failed to become active.
|
|
||||||
//
|
|
||||||
// Fun fact: this issue is still present in GLFW
|
|
||||||
// (https://github.com/glfw/glfw/issues/1515)
|
|
||||||
//
|
|
||||||
// A similar issue was found in SDL, but the resolution doesn't seem to work
|
|
||||||
// for us: https://bugzilla.libsdl.org/show_bug.cgi?id=3051
|
|
||||||
|
|
||||||
use super::util;
|
|
||||||
use cocoa::{
|
|
||||||
appkit::{NSApp, NSApplicationActivateIgnoringOtherApps},
|
|
||||||
base::id,
|
|
||||||
foundation::NSUInteger,
|
|
||||||
};
|
|
||||||
use objc::runtime::{Object, Sel, BOOL, NO, YES};
|
|
||||||
use std::{
|
|
||||||
os::raw::c_void,
|
|
||||||
sync::atomic::{AtomicBool, Ordering},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct State {
|
|
||||||
// Indicates that the hack has either completed or been skipped.
|
|
||||||
activated: AtomicBool,
|
|
||||||
// Indicates that the mouse has moved at some point in time.
|
|
||||||
mouse_moved: AtomicBool,
|
|
||||||
// Indicates that the hack is in progress, and that we should refocus when
|
|
||||||
// the app resigns active.
|
|
||||||
needs_refocus: AtomicBool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl State {
|
|
||||||
pub fn name() -> &'static str {
|
|
||||||
"activationHackState"
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new() -> *mut c_void {
|
|
||||||
let this = Box::new(Self::default());
|
|
||||||
Box::into_raw(this) as *mut c_void
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn free(this: *mut Self) {
|
|
||||||
Box::from_raw(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn get_ptr(obj: &Object) -> *mut Self {
|
|
||||||
let this: *mut c_void = *(*obj).get_ivar(Self::name());
|
|
||||||
assert!(!this.is_null(), "`activationHackState` pointer was null");
|
|
||||||
this as *mut Self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn set_activated(obj: &Object, value: bool) {
|
|
||||||
let this = Self::get_ptr(obj);
|
|
||||||
(*this).activated.store(value, Ordering::Release);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn get_activated(obj: &Object) -> bool {
|
|
||||||
let this = Self::get_ptr(obj);
|
|
||||||
(*this).activated.load(Ordering::Acquire)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn set_mouse_moved(obj: &Object, value: bool) {
|
|
||||||
let this = Self::get_ptr(obj);
|
|
||||||
(*this).mouse_moved.store(value, Ordering::Release);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn get_mouse_moved(obj: &Object) -> bool {
|
|
||||||
let this = Self::get_ptr(obj);
|
|
||||||
(*this).mouse_moved.load(Ordering::Acquire)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn set_needs_refocus(obj: &Object, value: bool) {
|
|
||||||
let this = Self::get_ptr(obj);
|
|
||||||
(*this).needs_refocus.store(value, Ordering::Release);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn get_needs_refocus(obj: &Object) -> bool {
|
|
||||||
let this = Self::get_ptr(obj);
|
|
||||||
(*this).needs_refocus.load(Ordering::Acquire)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the entry point for the hack - if the app is unbundled and a mouse
|
|
||||||
// movement occurs before the app activates, it will trigger the hack. Because
|
|
||||||
// mouse movements prior to activation are the cause of this quirk, they should
|
|
||||||
// be a reliable way to determine if the hack needs to be performed.
|
|
||||||
pub extern "C" fn mouse_moved(this: &Object, _: Sel, _: id) {
|
|
||||||
trace!("Triggered `activationHackMouseMoved`");
|
|
||||||
unsafe {
|
|
||||||
if !State::get_activated(this) {
|
|
||||||
// We check if `CFBundleName` is undefined to determine if the
|
|
||||||
// app is unbundled.
|
|
||||||
if let None = util::app_name() {
|
|
||||||
info!("App detected as unbundled");
|
|
||||||
unfocus(this);
|
|
||||||
} else {
|
|
||||||
info!("App detected as bundled");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
trace!("Completed `activationHackMouseMoved`");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch focus to the dock.
|
|
||||||
unsafe fn unfocus(this: &Object) {
|
|
||||||
// We only perform the hack if the app failed to activate, since otherwise,
|
|
||||||
// there'd be a gross (but fast) flicker as it unfocused and then refocused.
|
|
||||||
// However, we only enter this function if we detect mouse movement prior
|
|
||||||
// to activation, so this should always be `NO`.
|
|
||||||
//
|
|
||||||
// Note that this check isn't necessarily reliable in detecting a violation
|
|
||||||
// of the invariant above, since it's not guaranteed that activation will
|
|
||||||
// resolve before this point. In other words, it can spuriously return `NO`.
|
|
||||||
// This is also why the mouse motion approach was chosen, since it's not
|
|
||||||
// obvious how to sequence this check - if someone knows how to, then that
|
|
||||||
// would almost surely be a cleaner approach.
|
|
||||||
let active: BOOL = msg_send![NSApp(), isActive];
|
|
||||||
if active == YES {
|
|
||||||
error!("Unbundled app activation hack triggered on an app that's already active; this shouldn't happen!");
|
|
||||||
} else {
|
|
||||||
info!("Performing unbundled app activation hack");
|
|
||||||
let dock_bundle_id = util::ns_string_id_ref("com.apple.dock");
|
|
||||||
let dock_array: id = msg_send![
|
|
||||||
class!(NSRunningApplication),
|
|
||||||
runningApplicationsWithBundleIdentifier: *dock_bundle_id
|
|
||||||
];
|
|
||||||
let dock_array_len: NSUInteger = msg_send![dock_array, count];
|
|
||||||
if dock_array_len == 0 {
|
|
||||||
error!("The Dock doesn't seem to be running, so switching focus to it is impossible");
|
|
||||||
} else {
|
|
||||||
State::set_needs_refocus(this, true);
|
|
||||||
let dock: id = msg_send![dock_array, objectAtIndex: 0];
|
|
||||||
// This will trigger `applicationDidResignActive`, which will in
|
|
||||||
// turn call `refocus`.
|
|
||||||
let status: BOOL = msg_send![
|
|
||||||
dock,
|
|
||||||
activateWithOptions: NSApplicationActivateIgnoringOtherApps
|
|
||||||
];
|
|
||||||
if status == NO {
|
|
||||||
error!("Failed to switch focus to Dock");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Switch focus back to our app, causing the user to rejoice!
|
|
||||||
pub unsafe fn refocus(this: &Object) {
|
|
||||||
if State::get_needs_refocus(this) {
|
|
||||||
State::set_needs_refocus(this, false);
|
|
||||||
let app: id = msg_send![class!(NSRunningApplication), currentApplication];
|
|
||||||
// Simply calling `NSApp activateIgnoringOtherApps` doesn't work. The
|
|
||||||
// nuanced difference isn't clear to me, but hey, I tried.
|
|
||||||
let success: BOOL = msg_send![
|
|
||||||
app,
|
|
||||||
activateWithOptions: NSApplicationActivateIgnoringOtherApps
|
|
||||||
];
|
|
||||||
if success == NO {
|
|
||||||
error!("Failed to refocus app");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
use std::collections::VecDeque;
|
|
||||||
|
|
||||||
use cocoa::{
|
|
||||||
appkit::{self, NSEvent},
|
|
||||||
base::{id, nil},
|
|
||||||
};
|
|
||||||
use objc::{
|
|
||||||
declare::ClassDecl,
|
|
||||||
runtime::{Class, Object, Sel},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{activation_hack, 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 {}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref APP_CLASS: AppClass = 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(this, event);
|
|
||||||
let superclass = util::superclass(this);
|
|
||||||
let _: () = msg_send![super(this, superclass), sendEvent: event];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn maybe_dispatch_device_event(this: &Object, 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);
|
|
||||||
|
|
||||||
// Notify the delegate when the first mouse move occurs. This is
|
|
||||||
// used for the unbundled app activation hack, which needs to know
|
|
||||||
// if any mouse motions occurred prior to the app activating.
|
|
||||||
let delegate: id = msg_send![this, delegate];
|
|
||||||
assert_ne!(delegate, nil);
|
|
||||||
if !activation_hack::State::get_mouse_moved(&*delegate) {
|
|
||||||
activation_hack::State::set_mouse_moved(&*delegate, true);
|
|
||||||
let () = msg_send![
|
|
||||||
delegate,
|
|
||||||
performSelector: sel!(activationHackMouseMoved:)
|
|
||||||
withObject: nil
|
|
||||||
afterDelay: 0.0
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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,81 +0,0 @@
|
|||||||
use super::{activation_hack, app_state::AppState};
|
|
||||||
use cocoa::base::id;
|
|
||||||
use objc::{
|
|
||||||
declare::ClassDecl,
|
|
||||||
runtime::{Class, Object, Sel},
|
|
||||||
};
|
|
||||||
use std::os::raw::c_void;
|
|
||||||
|
|
||||||
pub struct AppDelegateClass(pub *const Class);
|
|
||||||
unsafe impl Send for AppDelegateClass {}
|
|
||||||
unsafe impl Sync for AppDelegateClass {}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref APP_DELEGATE_CLASS: AppDelegateClass = 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!(applicationDidBecomeActive:),
|
|
||||||
did_become_active as extern "C" fn(&Object, Sel, id),
|
|
||||||
);
|
|
||||||
decl.add_method(
|
|
||||||
sel!(applicationDidResignActive:),
|
|
||||||
did_resign_active as extern "C" fn(&Object, Sel, id),
|
|
||||||
);
|
|
||||||
|
|
||||||
decl.add_ivar::<*mut c_void>(activation_hack::State::name());
|
|
||||||
decl.add_method(
|
|
||||||
sel!(activationHackMouseMoved:),
|
|
||||||
activation_hack::mouse_moved as extern "C" fn(&Object, Sel, id),
|
|
||||||
);
|
|
||||||
|
|
||||||
AppDelegateClass(decl.register())
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn new(class: &Class, _: Sel) -> id {
|
|
||||||
unsafe {
|
|
||||||
let this: id = msg_send![class, alloc];
|
|
||||||
let this: id = msg_send![this, init];
|
|
||||||
(*this).set_ivar(
|
|
||||||
activation_hack::State::name(),
|
|
||||||
activation_hack::State::new(),
|
|
||||||
);
|
|
||||||
this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn dealloc(this: &Object, _: Sel) {
|
|
||||||
unsafe {
|
|
||||||
activation_hack::State::free(activation_hack::State::get_ptr(this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn did_finish_launching(_: &Object, _: Sel, _: id) {
|
|
||||||
trace!("Triggered `applicationDidFinishLaunching`");
|
|
||||||
AppState::launched();
|
|
||||||
trace!("Completed `applicationDidFinishLaunching`");
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn did_become_active(this: &Object, _: Sel, _: id) {
|
|
||||||
trace!("Triggered `applicationDidBecomeActive`");
|
|
||||||
unsafe {
|
|
||||||
activation_hack::State::set_activated(this, true);
|
|
||||||
}
|
|
||||||
trace!("Completed `applicationDidBecomeActive`");
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" fn did_resign_active(this: &Object, _: Sel, _: id) {
|
|
||||||
trace!("Triggered `applicationDidResignActive`");
|
|
||||||
unsafe {
|
|
||||||
activation_hack::refocus(this);
|
|
||||||
}
|
|
||||||
trace!("Completed `applicationDidResignActive`");
|
|
||||||
}
|
|
||||||
@@ -1,373 +0,0 @@
|
|||||||
use std::{
|
|
||||||
collections::VecDeque,
|
|
||||||
fmt::{self, Debug},
|
|
||||||
hint::unreachable_unchecked,
|
|
||||||
mem,
|
|
||||||
rc::Rc,
|
|
||||||
sync::{
|
|
||||||
atomic::{AtomicBool, Ordering},
|
|
||||||
Mutex, MutexGuard,
|
|
||||||
},
|
|
||||||
time::Instant,
|
|
||||||
};
|
|
||||||
|
|
||||||
use cocoa::{
|
|
||||||
appkit::{NSApp, NSEventType::NSApplicationDefined, NSWindow},
|
|
||||||
base::{id, nil},
|
|
||||||
foundation::{NSAutoreleasePool, NSPoint, NSSize},
|
|
||||||
};
|
|
||||||
|
|
||||||
use objc::runtime::YES;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
dpi::LogicalSize,
|
|
||||||
event::{Event, StartCause, WindowEvent},
|
|
||||||
event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget},
|
|
||||||
platform_impl::platform::{
|
|
||||||
event::{EventProxy, EventWrapper},
|
|
||||||
observer::EventLoopWaker,
|
|
||||||
util::{IdRef, Never},
|
|
||||||
window::get_window_id,
|
|
||||||
},
|
|
||||||
window::WindowId,
|
|
||||||
};
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref HANDLER: Handler = 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct EventLoopHandler<T: 'static> {
|
|
||||||
callback: Box<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>,
|
|
||||||
will_exit: bool,
|
|
||||||
window_target: Rc<RootWindowTarget<T>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
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.callback)(event.userify(), &self.window_target, control_flow);
|
|
||||||
self.will_exit |= *control_flow == ControlFlow::Exit;
|
|
||||||
if self.will_exit {
|
|
||||||
*control_flow = ControlFlow::Exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_user_events(&mut self, control_flow: &mut ControlFlow) {
|
|
||||||
let mut will_exit = self.will_exit;
|
|
||||||
for event in self.window_target.p.receiver.try_iter() {
|
|
||||||
(self.callback)(Event::UserEvent(event), &self.window_target, control_flow);
|
|
||||||
will_exit |= *control_flow == ControlFlow::Exit;
|
|
||||||
if will_exit {
|
|
||||||
*control_flow = ControlFlow::Exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.will_exit = will_exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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<'a>(&'a self) -> MutexGuard<'a, 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 {
|
|
||||||
*self.control_flow.lock().unwrap() == ControlFlow::Exit
|
|
||||||
}
|
|
||||||
|
|
||||||
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::replace(&mut *self.events(), Default::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn should_redraw(&self) -> Vec<WindowId> {
|
|
||||||
mem::replace(&mut *self.redraw(), Default::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
// This function extends lifetime of `callback` to 'static as its side effect
|
|
||||||
pub unsafe fn set_callback<F, T>(callback: F, window_target: Rc<RootWindowTarget<T>>)
|
|
||||||
where
|
|
||||||
F: FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow),
|
|
||||||
{
|
|
||||||
*HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler {
|
|
||||||
// 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.
|
|
||||||
callback: mem::transmute::<
|
|
||||||
Box<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>,
|
|
||||||
Box<dyn FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow)>,
|
|
||||||
>(Box::new(callback)),
|
|
||||||
will_exit: false,
|
|
||||||
window_target,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn exit() {
|
|
||||||
HANDLER.set_in_callback(true);
|
|
||||||
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::LoopDestroyed));
|
|
||||||
HANDLER.set_in_callback(false);
|
|
||||||
HANDLER.callback.lock().unwrap().take();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn launched() {
|
|
||||||
HANDLER.set_ready();
|
|
||||||
HANDLER.waker().start();
|
|
||||||
HANDLER.set_in_callback(true);
|
|
||||||
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents(
|
|
||||||
StartCause::Init,
|
|
||||||
)));
|
|
||||||
HANDLER.set_in_callback(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn wakeup() {
|
|
||||||
if !HANDLER.is_ready() {
|
|
||||||
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::Exit => 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn queue_event(wrapper: EventWrapper) {
|
|
||||||
if !unsafe { msg_send![class!(NSThread), isMainThread] } {
|
|
||||||
panic!("Event queued from different thread: {:#?}", wrapper);
|
|
||||||
}
|
|
||||||
HANDLER.events().push_back(wrapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn queue_events(mut wrappers: VecDeque<EventWrapper>) {
|
|
||||||
if !unsafe { msg_send![class!(NSThread), isMainThread] } {
|
|
||||||
panic!("Events queued from different thread: {:#?}", wrappers);
|
|
||||||
}
|
|
||||||
HANDLER.events().append(&mut wrappers);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cleared() {
|
|
||||||
if !HANDLER.is_ready() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if !HANDLER.get_in_callback() {
|
|
||||||
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 _: () = msg_send![NSApp(), stop: nil];
|
|
||||||
|
|
||||||
let pool = NSAutoreleasePool::new(nil);
|
|
||||||
|
|
||||||
let windows: id = msg_send![NSApp(), windows];
|
|
||||||
let window: id = msg_send![windows, objectAtIndex:0];
|
|
||||||
assert_ne!(window, nil);
|
|
||||||
|
|
||||||
let dummy_event: id = msg_send![class!(NSEvent),
|
|
||||||
otherEventWithType: NSApplicationDefined
|
|
||||||
location: NSPoint::new(0.0, 0.0)
|
|
||||||
modifierFlags: 0
|
|
||||||
timestamp: 0
|
|
||||||
windowNumber: 0
|
|
||||||
context: nil
|
|
||||||
subtype: 0
|
|
||||||
data1: 0
|
|
||||||
data2: 0
|
|
||||||
];
|
|
||||||
// To stop event loop immediately, we need to post some event here.
|
|
||||||
let _: () = msg_send![window, postEvent: dummy_event atStart: YES];
|
|
||||||
|
|
||||||
pool.drain();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
HANDLER.update_start_time();
|
|
||||||
match HANDLER.get_old_and_new_control_flow() {
|
|
||||||
(ControlFlow::Exit, _) | (_, ControlFlow::Exit) => (),
|
|
||||||
(old, new) if old == new => (),
|
|
||||||
(_, ControlFlow::Wait) => HANDLER.waker().stop(),
|
|
||||||
(_, ControlFlow::WaitUntil(instant)) => HANDLER.waker().start_at(instant),
|
|
||||||
(_, ControlFlow::Poll) => HANDLER.waker().start(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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::Decimal,
|
|
||||||
//0x42 -> unkown,
|
|
||||||
0x43 => VirtualKeyCode::Multiply,
|
|
||||||
//0x44 => unkown,
|
|
||||||
0x45 => VirtualKeyCode::Add,
|
|
||||||
//0x46 => unkown,
|
|
||||||
0x47 => VirtualKeyCode::Numlock,
|
|
||||||
//0x48 => KeypadClear,
|
|
||||||
0x49 => VirtualKeyCode::VolumeUp,
|
|
||||||
0x4a => VirtualKeyCode::VolumeDown,
|
|
||||||
0x4b => VirtualKeyCode::Divide,
|
|
||||||
0x4c => VirtualKeyCode::NumpadEnter,
|
|
||||||
//0x4d => unkown,
|
|
||||||
0x4e => VirtualKeyCode::Subtract,
|
|
||||||
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,158 +0,0 @@
|
|||||||
use std::{
|
|
||||||
collections::VecDeque, marker::PhantomData, mem, os::raw::c_void, process, ptr, rc::Rc,
|
|
||||||
sync::mpsc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use cocoa::{
|
|
||||||
appkit::NSApp,
|
|
||||||
base::{id, nil},
|
|
||||||
foundation::NSAutoreleasePool,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
event::Event,
|
|
||||||
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootWindowTarget},
|
|
||||||
platform_impl::platform::{
|
|
||||||
app::APP_CLASS,
|
|
||||||
app_delegate::APP_DELEGATE_CLASS,
|
|
||||||
app_state::AppState,
|
|
||||||
monitor::{self, MonitorHandle},
|
|
||||||
observer::*,
|
|
||||||
util::IdRef,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventLoop<T: 'static> {
|
|
||||||
window_target: Rc<RootWindowTarget<T>>,
|
|
||||||
_delegate: IdRef,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> EventLoop<T> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let delegate = unsafe {
|
|
||||||
if !msg_send![class!(NSThread), isMainThread] {
|
|
||||||
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 pool = NSAutoreleasePool::new(nil);
|
|
||||||
let _: () = msg_send![app, setDelegate:*delegate];
|
|
||||||
let _: () = msg_send![pool, drain];
|
|
||||||
delegate
|
|
||||||
};
|
|
||||||
setup_control_flow_observers();
|
|
||||||
EventLoop {
|
|
||||||
window_target: Rc::new(RootWindowTarget {
|
|
||||||
p: Default::default(),
|
|
||||||
_marker: PhantomData,
|
|
||||||
}),
|
|
||||||
_delegate: delegate,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
|
||||||
monitor::available_monitors()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn primary_monitor(&self) -> MonitorHandle {
|
|
||||||
monitor::primary_monitor()
|
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
|
||||||
{
|
|
||||||
self.run_return(callback);
|
|
||||||
process::exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_return<F>(&mut self, callback: F)
|
|
||||||
where
|
|
||||||
F: FnMut(Event<'_, T>, &RootWindowTarget<T>, &mut ControlFlow),
|
|
||||||
{
|
|
||||||
unsafe {
|
|
||||||
let pool = NSAutoreleasePool::new(nil);
|
|
||||||
let app = NSApp();
|
|
||||||
assert_ne!(app, nil);
|
|
||||||
AppState::set_callback(callback, Rc::clone(&self.window_target));
|
|
||||||
let _: () = msg_send![app, run];
|
|
||||||
AppState::exit();
|
|
||||||
pool.drain();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_proxy(&self) -> Proxy<T> {
|
|
||||||
Proxy::new(self.window_target.p.sender.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Proxy<T> {
|
|
||||||
sender: mpsc::Sender<T>,
|
|
||||||
source: CFRunLoopSourceRef,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl<T: Send> Send for Proxy<T> {}
|
|
||||||
|
|
||||||
impl<T> Clone for Proxy<T> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Proxy::new(self.sender.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Proxy<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);
|
|
||||||
|
|
||||||
Proxy { 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,209 +0,0 @@
|
|||||||
// TODO: Upstream these
|
|
||||||
|
|
||||||
#![allow(dead_code, non_snake_case, non_upper_case_globals)]
|
|
||||||
|
|
||||||
use cocoa::{
|
|
||||||
base::id,
|
|
||||||
foundation::{NSInteger, NSUInteger},
|
|
||||||
};
|
|
||||||
use core_foundation::{
|
|
||||||
array::CFArrayRef, dictionary::CFDictionaryRef, string::CFStringRef, uuid::CFUUIDRef,
|
|
||||||
};
|
|
||||||
use core_graphics::{
|
|
||||||
base::CGError,
|
|
||||||
display::{CGDirectDisplayID, CGDisplayConfigRef},
|
|
||||||
};
|
|
||||||
use objc;
|
|
||||||
|
|
||||||
pub const NSNotFound: NSInteger = NSInteger::max_value();
|
|
||||||
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct NSRange {
|
|
||||||
pub location: NSUInteger,
|
|
||||||
pub length: NSUInteger,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NSRange {
|
|
||||||
#[inline]
|
|
||||||
pub fn new(location: NSUInteger, length: NSUInteger) -> NSRange {
|
|
||||||
NSRange { location, length }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl objc::Encode for NSRange {
|
|
||||||
fn encode() -> objc::Encoding {
|
|
||||||
let encoding = format!(
|
|
||||||
// TODO: Verify that this is correct
|
|
||||||
"{{NSRange={}{}}}",
|
|
||||||
NSUInteger::encode().as_str(),
|
|
||||||
NSUInteger::encode().as_str(),
|
|
||||||
);
|
|
||||||
unsafe { objc::Encoding::from_str(&encoding) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait NSMutableAttributedString: Sized {
|
|
||||||
unsafe fn alloc(_: Self) -> id {
|
|
||||||
msg_send![class!(NSMutableAttributedString), alloc]
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn init(self) -> id; // *mut NSMutableAttributedString
|
|
||||||
unsafe fn initWithString(self, string: id) -> id;
|
|
||||||
unsafe fn initWithAttributedString(self, string: id) -> id;
|
|
||||||
|
|
||||||
unsafe fn string(self) -> id; // *mut NSString
|
|
||||||
unsafe fn mutableString(self) -> id; // *mut NSMutableString
|
|
||||||
unsafe fn length(self) -> NSUInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NSMutableAttributedString for id {
|
|
||||||
unsafe fn init(self) -> id {
|
|
||||||
msg_send![self, init]
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn initWithString(self, string: id) -> id {
|
|
||||||
msg_send![self, initWithString: string]
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn initWithAttributedString(self, string: id) -> id {
|
|
||||||
msg_send![self, initWithAttributedString: string]
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn string(self) -> id {
|
|
||||||
msg_send![self, string]
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn mutableString(self) -> id {
|
|
||||||
msg_send![self, mutableString]
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn length(self) -> NSUInteger {
|
|
||||||
msg_send![self, length]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const kCGBaseWindowLevelKey: NSInteger = 0;
|
|
||||||
pub const kCGMinimumWindowLevelKey: NSInteger = 1;
|
|
||||||
pub const kCGDesktopWindowLevelKey: NSInteger = 2;
|
|
||||||
pub const kCGBackstopMenuLevelKey: NSInteger = 3;
|
|
||||||
pub const kCGNormalWindowLevelKey: NSInteger = 4;
|
|
||||||
pub const kCGFloatingWindowLevelKey: NSInteger = 5;
|
|
||||||
pub const kCGTornOffMenuWindowLevelKey: NSInteger = 6;
|
|
||||||
pub const kCGDockWindowLevelKey: NSInteger = 7;
|
|
||||||
pub const kCGMainMenuWindowLevelKey: NSInteger = 8;
|
|
||||||
pub const kCGStatusWindowLevelKey: NSInteger = 9;
|
|
||||||
pub const kCGModalPanelWindowLevelKey: NSInteger = 10;
|
|
||||||
pub const kCGPopUpMenuWindowLevelKey: NSInteger = 11;
|
|
||||||
pub const kCGDraggingWindowLevelKey: NSInteger = 12;
|
|
||||||
pub const kCGScreenSaverWindowLevelKey: NSInteger = 13;
|
|
||||||
pub const kCGMaximumWindowLevelKey: NSInteger = 14;
|
|
||||||
pub const kCGOverlayWindowLevelKey: NSInteger = 15;
|
|
||||||
pub const kCGHelpWindowLevelKey: NSInteger = 16;
|
|
||||||
pub const kCGUtilityWindowLevelKey: NSInteger = 17;
|
|
||||||
pub const kCGDesktopIconWindowLevelKey: NSInteger = 18;
|
|
||||||
pub const kCGCursorWindowLevelKey: NSInteger = 19;
|
|
||||||
pub const kCGNumberOfWindowLevelKeys: NSInteger = 20;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub enum NSWindowLevel {
|
|
||||||
NSNormalWindowLevel = kCGBaseWindowLevelKey as _,
|
|
||||||
NSFloatingWindowLevel = kCGFloatingWindowLevelKey as _,
|
|
||||||
NSTornOffMenuWindowLevel = kCGTornOffMenuWindowLevelKey as _,
|
|
||||||
NSModalPanelWindowLevel = kCGModalPanelWindowLevelKey as _,
|
|
||||||
NSMainMenuWindowLevel = kCGMainMenuWindowLevelKey as _,
|
|
||||||
NSStatusWindowLevel = kCGStatusWindowLevelKey as _,
|
|
||||||
NSPopUpMenuWindowLevel = kCGPopUpMenuWindowLevelKey as _,
|
|
||||||
NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type CGDisplayFadeInterval = f32;
|
|
||||||
pub type CGDisplayReservationInterval = f32;
|
|
||||||
pub type CGDisplayBlendFraction = f32;
|
|
||||||
|
|
||||||
pub const kCGDisplayBlendNormal: f32 = 0.0;
|
|
||||||
pub const kCGDisplayBlendSolidColor: f32 = 1.0;
|
|
||||||
|
|
||||||
pub type CGDisplayFadeReservationToken = u32;
|
|
||||||
pub const kCGDisplayFadeReservationInvalidToken: CGDisplayFadeReservationToken = 0;
|
|
||||||
|
|
||||||
pub type Boolean = u8;
|
|
||||||
pub const FALSE: Boolean = 0;
|
|
||||||
pub const TRUE: Boolean = 1;
|
|
||||||
|
|
||||||
pub const kCGErrorSuccess: i32 = 0;
|
|
||||||
pub const kCGErrorFailure: i32 = 1000;
|
|
||||||
pub const kCGErrorIllegalArgument: i32 = 1001;
|
|
||||||
pub const kCGErrorInvalidConnection: i32 = 1002;
|
|
||||||
pub const kCGErrorInvalidContext: i32 = 1003;
|
|
||||||
pub const kCGErrorCannotComplete: i32 = 1004;
|
|
||||||
pub const kCGErrorNotImplemented: i32 = 1006;
|
|
||||||
pub const kCGErrorRangeCheck: i32 = 1007;
|
|
||||||
pub const kCGErrorTypeCheck: i32 = 1008;
|
|
||||||
pub const kCGErrorInvalidOperation: i32 = 1010;
|
|
||||||
pub const kCGErrorNoneAvailable: i32 = 1011;
|
|
||||||
|
|
||||||
pub const IO1BitIndexedPixels: &str = "P";
|
|
||||||
pub const IO2BitIndexedPixels: &str = "PP";
|
|
||||||
pub const IO4BitIndexedPixels: &str = "PPPP";
|
|
||||||
pub const IO8BitIndexedPixels: &str = "PPPPPPPP";
|
|
||||||
pub const IO16BitDirectPixels: &str = "-RRRRRGGGGGBBBBB";
|
|
||||||
pub const IO32BitDirectPixels: &str = "--------RRRRRRRRGGGGGGGGBBBBBBBB";
|
|
||||||
|
|
||||||
pub const kIO30BitDirectPixels: &str = "--RRRRRRRRRRGGGGGGGGGGBBBBBBBBBB";
|
|
||||||
pub const kIO64BitDirectPixels: &str = "-16R16G16B16";
|
|
||||||
|
|
||||||
pub const kIO16BitFloatPixels: &str = "-16FR16FG16FB16";
|
|
||||||
pub const kIO32BitFloatPixels: &str = "-32FR32FG32FB32";
|
|
||||||
|
|
||||||
pub const IOYUV422Pixels: &str = "Y4U2V2";
|
|
||||||
pub const IO8BitOverlayPixels: &str = "O8";
|
|
||||||
|
|
||||||
pub type CGWindowLevel = i32;
|
|
||||||
pub type CGDisplayModeRef = *mut libc::c_void;
|
|
||||||
|
|
||||||
#[link(name = "CoreGraphics", kind = "framework")]
|
|
||||||
extern "C" {
|
|
||||||
pub fn CGRestorePermanentDisplayConfiguration();
|
|
||||||
pub fn CGDisplayCapture(display: CGDirectDisplayID) -> CGError;
|
|
||||||
pub fn CGDisplayRelease(display: CGDirectDisplayID) -> CGError;
|
|
||||||
pub fn CGConfigureDisplayFadeEffect(
|
|
||||||
config: CGDisplayConfigRef,
|
|
||||||
fadeOutSeconds: CGDisplayFadeInterval,
|
|
||||||
fadeInSeconds: CGDisplayFadeInterval,
|
|
||||||
fadeRed: f32,
|
|
||||||
fadeGreen: f32,
|
|
||||||
fadeBlue: f32,
|
|
||||||
) -> CGError;
|
|
||||||
pub fn CGAcquireDisplayFadeReservation(
|
|
||||||
seconds: CGDisplayReservationInterval,
|
|
||||||
token: *mut CGDisplayFadeReservationToken,
|
|
||||||
) -> CGError;
|
|
||||||
pub fn CGDisplayFade(
|
|
||||||
token: CGDisplayFadeReservationToken,
|
|
||||||
duration: CGDisplayFadeInterval,
|
|
||||||
startBlend: CGDisplayBlendFraction,
|
|
||||||
endBlend: CGDisplayBlendFraction,
|
|
||||||
redBlend: f32,
|
|
||||||
greenBlend: f32,
|
|
||||||
blueBlend: f32,
|
|
||||||
synchronous: Boolean,
|
|
||||||
) -> CGError;
|
|
||||||
pub fn CGReleaseDisplayFadeReservation(token: CGDisplayFadeReservationToken) -> CGError;
|
|
||||||
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
|
|
||||||
pub fn CGShieldingWindowLevel() -> CGWindowLevel;
|
|
||||||
pub fn CGDisplaySetDisplayMode(
|
|
||||||
display: CGDirectDisplayID,
|
|
||||||
mode: CGDisplayModeRef,
|
|
||||||
options: CFDictionaryRef,
|
|
||||||
) -> CGError;
|
|
||||||
pub fn CGDisplayCopyAllDisplayModes(
|
|
||||||
display: CGDirectDisplayID,
|
|
||||||
options: CFDictionaryRef,
|
|
||||||
) -> CFArrayRef;
|
|
||||||
pub fn CGDisplayModeGetPixelWidth(mode: CGDisplayModeRef) -> usize;
|
|
||||||
pub fn CGDisplayModeGetPixelHeight(mode: CGDisplayModeRef) -> usize;
|
|
||||||
pub fn CGDisplayModeGetRefreshRate(mode: CGDisplayModeRef) -> f64;
|
|
||||||
pub fn CGDisplayModeCopyPixelEncoding(mode: CGDisplayModeRef) -> CFStringRef;
|
|
||||||
pub fn CGDisplayModeRetain(mode: CGDisplayModeRef);
|
|
||||||
pub fn CGDisplayModeRelease(mode: CGDisplayModeRef);
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
#![cfg(target_os = "macos")]
|
|
||||||
|
|
||||||
mod activation_hack;
|
|
||||||
mod app;
|
|
||||||
mod app_delegate;
|
|
||||||
mod app_state;
|
|
||||||
mod event;
|
|
||||||
mod event_loop;
|
|
||||||
mod ffi;
|
|
||||||
mod monitor;
|
|
||||||
mod observer;
|
|
||||||
mod util;
|
|
||||||
mod view;
|
|
||||||
mod window;
|
|
||||||
mod window_delegate;
|
|
||||||
|
|
||||||
use std::{fmt, ops::Deref, sync::Arc};
|
|
||||||
|
|
||||||
pub use self::{
|
|
||||||
event_loop::{EventLoop, EventLoopWindowTarget, Proxy as EventLoopProxy},
|
|
||||||
monitor::{MonitorHandle, VideoMode},
|
|
||||||
window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, UnownedWindow},
|
|
||||||
};
|
|
||||||
use crate::{
|
|
||||||
error::OsError as RootOsError, event::DeviceId as RootDeviceId, window::WindowAttributes,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
||||||
pub struct DeviceId;
|
|
||||||
|
|
||||||
impl DeviceId {
|
|
||||||
pub 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 fn new<T: 'static>(
|
|
||||||
_window_target: &EventLoopWindowTarget<T>,
|
|
||||||
attributes: WindowAttributes,
|
|
||||||
pl_attribs: PlatformSpecificWindowBuilderAttributes,
|
|
||||||
) -> Result<Self, RootOsError> {
|
|
||||||
let (window, _delegate) = 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,322 +0,0 @@
|
|||||||
use std::{collections::VecDeque, fmt};
|
|
||||||
|
|
||||||
use super::{ffi, util};
|
|
||||||
use crate::{
|
|
||||||
dpi::{PhysicalPosition, PhysicalSize},
|
|
||||||
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
|
||||||
};
|
|
||||||
use cocoa::{
|
|
||||||
appkit::NSScreen,
|
|
||||||
base::{id, nil},
|
|
||||||
foundation::NSUInteger,
|
|
||||||
};
|
|
||||||
use core_foundation::{
|
|
||||||
array::{CFArrayGetCount, CFArrayGetValueAtIndex},
|
|
||||||
base::{CFRelease, TCFType},
|
|
||||||
string::CFString,
|
|
||||||
};
|
|
||||||
use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds};
|
|
||||||
use core_video_sys::{
|
|
||||||
kCVReturnSuccess, kCVTimeIsIndefinite, CVDisplayLinkCreateWithCGDisplay,
|
|
||||||
CVDisplayLinkGetNominalOutputVideoRefreshPeriod, CVDisplayLinkRelease,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct VideoMode {
|
|
||||||
pub(crate) size: (u32, u32),
|
|
||||||
pub(crate) bit_depth: u16,
|
|
||||||
pub(crate) refresh_rate: u16,
|
|
||||||
pub(crate) monitor: MonitorHandle,
|
|
||||||
pub(crate) native_mode: NativeDisplayMode,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for VideoMode {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.size == other.size
|
|
||||||
&& self.bit_depth == other.bit_depth
|
|
||||||
&& self.refresh_rate == other.refresh_rate
|
|
||||||
&& self.monitor == other.monitor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for VideoMode {}
|
|
||||||
|
|
||||||
impl std::hash::Hash for VideoMode {
|
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
self.size.hash(state);
|
|
||||||
self.bit_depth.hash(state);
|
|
||||||
self.refresh_rate.hash(state);
|
|
||||||
self.monitor.hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Debug for VideoMode {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
f.debug_struct("VideoMode")
|
|
||||||
.field("size", &self.size)
|
|
||||||
.field("bit_depth", &self.bit_depth)
|
|
||||||
.field("refresh_rate", &self.refresh_rate)
|
|
||||||
.field("monitor", &self.monitor)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct NativeDisplayMode(pub ffi::CGDisplayModeRef);
|
|
||||||
|
|
||||||
unsafe impl Send for NativeDisplayMode {}
|
|
||||||
|
|
||||||
impl Drop for NativeDisplayMode {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
unsafe {
|
|
||||||
ffi::CGDisplayModeRelease(self.0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for NativeDisplayMode {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
unsafe {
|
|
||||||
ffi::CGDisplayModeRetain(self.0);
|
|
||||||
}
|
|
||||||
NativeDisplayMode(self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VideoMode {
|
|
||||||
pub fn size(&self) -> PhysicalSize<u32> {
|
|
||||||
self.size.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bit_depth(&self) -> u16 {
|
|
||||||
self.bit_depth
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn refresh_rate(&self) -> u16 {
|
|
||||||
self.refresh_rate
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn monitor(&self) -> RootMonitorHandle {
|
|
||||||
RootMonitorHandle {
|
|
||||||
inner: self.monitor.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct MonitorHandle(CGDirectDisplayID);
|
|
||||||
|
|
||||||
// `CGDirectDisplayID` changes on video mode change, so we cannot rely on that
|
|
||||||
// for comparisons, but we can use `CGDisplayCreateUUIDFromDisplayID` to get an
|
|
||||||
// unique identifier that persists even across system reboots
|
|
||||||
impl PartialEq for MonitorHandle {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
unsafe {
|
|
||||||
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
|
|
||||||
== ffi::CGDisplayCreateUUIDFromDisplayID(other.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
unsafe {
|
|
||||||
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
|
|
||||||
.cmp(&ffi::CGDisplayCreateUUIDFromDisplayID(other.0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::hash::Hash for MonitorHandle {
|
|
||||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
||||||
unsafe {
|
|
||||||
ffi::CGDisplayCreateUUIDFromDisplayID(self.0).hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn available_monitors() -> VecDeque<MonitorHandle> {
|
|
||||||
if let Ok(displays) = CGDisplay::active_displays() {
|
|
||||||
let mut monitors = VecDeque::with_capacity(displays.len());
|
|
||||||
for display in displays {
|
|
||||||
monitors.push_back(MonitorHandle(display));
|
|
||||||
}
|
|
||||||
monitors
|
|
||||||
} else {
|
|
||||||
VecDeque::with_capacity(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn primary_monitor() -> MonitorHandle {
|
|
||||||
MonitorHandle(CGDisplay::main().id)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for MonitorHandle {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
// TODO: Do this using the proper fmt API
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct MonitorHandle {
|
|
||||||
name: Option<String>,
|
|
||||||
native_identifier: u32,
|
|
||||||
size: PhysicalSize<u32>,
|
|
||||||
position: PhysicalPosition<i32>,
|
|
||||||
scale_factor: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
let monitor_id_proxy = MonitorHandle {
|
|
||||||
name: self.name(),
|
|
||||||
native_identifier: self.native_identifier(),
|
|
||||||
size: self.size(),
|
|
||||||
position: self.position(),
|
|
||||||
scale_factor: self.scale_factor(),
|
|
||||||
};
|
|
||||||
|
|
||||||
monitor_id_proxy.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MonitorHandle {
|
|
||||||
pub fn new(id: CGDirectDisplayID) -> Self {
|
|
||||||
MonitorHandle(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn name(&self) -> Option<String> {
|
|
||||||
let MonitorHandle(display_id) = *self;
|
|
||||||
let screen_num = CGDisplay::new(display_id).model_number();
|
|
||||||
Some(format!("Monitor #{}", screen_num))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn native_identifier(&self) -> u32 {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn size(&self) -> PhysicalSize<u32> {
|
|
||||||
let MonitorHandle(display_id) = *self;
|
|
||||||
let display = CGDisplay::new(display_id);
|
|
||||||
let height = display.pixels_high();
|
|
||||||
let width = display.pixels_wide();
|
|
||||||
PhysicalSize::from_logical::<_, f64>((width as f64, height as f64), self.scale_factor())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn position(&self) -> PhysicalPosition<i32> {
|
|
||||||
let bounds = unsafe { CGDisplayBounds(self.native_identifier()) };
|
|
||||||
PhysicalPosition::from_logical::<_, f64>(
|
|
||||||
(bounds.origin.x as f64, bounds.origin.y as f64),
|
|
||||||
self.scale_factor(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scale_factor(&self) -> f64 {
|
|
||||||
let screen = match self.ns_screen() {
|
|
||||||
Some(screen) => screen,
|
|
||||||
None => return 1.0, // default to 1.0 when we can't find the screen
|
|
||||||
};
|
|
||||||
unsafe { NSScreen::backingScaleFactor(screen) as f64 }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
|
|
||||||
let cv_refresh_rate = unsafe {
|
|
||||||
let mut display_link = std::ptr::null_mut();
|
|
||||||
assert_eq!(
|
|
||||||
CVDisplayLinkCreateWithCGDisplay(self.0, &mut display_link),
|
|
||||||
kCVReturnSuccess
|
|
||||||
);
|
|
||||||
let time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(display_link);
|
|
||||||
CVDisplayLinkRelease(display_link);
|
|
||||||
|
|
||||||
// This value is indefinite if an invalid display link was specified
|
|
||||||
assert!(time.flags & kCVTimeIsIndefinite == 0);
|
|
||||||
|
|
||||||
time.timeScale as i64 / time.timeValue
|
|
||||||
};
|
|
||||||
|
|
||||||
let monitor = self.clone();
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let modes = {
|
|
||||||
let array = ffi::CGDisplayCopyAllDisplayModes(self.0, std::ptr::null());
|
|
||||||
assert!(!array.is_null(), "failed to get list of display modes");
|
|
||||||
let array_count = CFArrayGetCount(array);
|
|
||||||
let modes: Vec<_> = (0..array_count)
|
|
||||||
.map(move |i| {
|
|
||||||
let mode = CFArrayGetValueAtIndex(array, i) as *mut _;
|
|
||||||
ffi::CGDisplayModeRetain(mode);
|
|
||||||
mode
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
CFRelease(array as *const _);
|
|
||||||
modes
|
|
||||||
};
|
|
||||||
|
|
||||||
modes.into_iter().map(move |mode| {
|
|
||||||
let cg_refresh_rate = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64;
|
|
||||||
|
|
||||||
// CGDisplayModeGetRefreshRate returns 0.0 for any display that
|
|
||||||
// isn't a CRT
|
|
||||||
let refresh_rate = if cg_refresh_rate > 0 {
|
|
||||||
cg_refresh_rate
|
|
||||||
} else {
|
|
||||||
cv_refresh_rate
|
|
||||||
};
|
|
||||||
|
|
||||||
let pixel_encoding =
|
|
||||||
CFString::wrap_under_create_rule(ffi::CGDisplayModeCopyPixelEncoding(mode))
|
|
||||||
.to_string();
|
|
||||||
let bit_depth = if pixel_encoding.eq_ignore_ascii_case(ffi::IO32BitDirectPixels) {
|
|
||||||
32
|
|
||||||
} else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) {
|
|
||||||
16
|
|
||||||
} else if pixel_encoding.eq_ignore_ascii_case(ffi::kIO30BitDirectPixels) {
|
|
||||||
30
|
|
||||||
} else {
|
|
||||||
unimplemented!()
|
|
||||||
};
|
|
||||||
|
|
||||||
let video_mode = VideoMode {
|
|
||||||
size: (
|
|
||||||
ffi::CGDisplayModeGetPixelWidth(mode) as u32,
|
|
||||||
ffi::CGDisplayModeGetPixelHeight(mode) as u32,
|
|
||||||
),
|
|
||||||
refresh_rate: refresh_rate as u16,
|
|
||||||
bit_depth,
|
|
||||||
monitor: monitor.clone(),
|
|
||||||
native_mode: NativeDisplayMode(mode),
|
|
||||||
};
|
|
||||||
|
|
||||||
RootVideoMode { video_mode }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn ns_screen(&self) -> Option<id> {
|
|
||||||
unsafe {
|
|
||||||
let uuid = ffi::CGDisplayCreateUUIDFromDisplayID(self.0);
|
|
||||||
let screens = NSScreen::screens(nil);
|
|
||||||
let count: NSUInteger = msg_send![screens, count];
|
|
||||||
let key = util::ns_string_id_ref("NSScreenNumber");
|
|
||||||
for i in 0..count {
|
|
||||||
let screen = msg_send![screens, objectAtIndex: i as NSUInteger];
|
|
||||||
let device_description = NSScreen::deviceDescription(screen);
|
|
||||||
let value: id = msg_send![device_description, objectForKey:*key];
|
|
||||||
if value != nil {
|
|
||||||
let other_native_id: NSUInteger = msg_send![value, unsignedIntegerValue];
|
|
||||||
let other_uuid =
|
|
||||||
ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID);
|
|
||||||
if uuid == other_uuid {
|
|
||||||
return Some(screen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,242 +0,0 @@
|
|||||||
use std::{self, os::raw::*, ptr, time::Instant};
|
|
||||||
|
|
||||||
use crate::platform_impl::platform::{app_state::AppState, 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 CFRunLoopObserverContext {}
|
|
||||||
pub enum CFRunLoopTimerContext {}
|
|
||||||
|
|
||||||
#[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)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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,
|
|
||||||
) {
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
match activity {
|
|
||||||
kCFRunLoopAfterWaiting => {
|
|
||||||
//trace!("Triggered `CFRunLoopAfterWaiting`");
|
|
||||||
AppState::wakeup();
|
|
||||||
//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,
|
|
||||||
_: *mut c_void,
|
|
||||||
) {
|
|
||||||
#[allow(non_upper_case_globals)]
|
|
||||||
match activity {
|
|
||||||
kCFRunLoopBeforeWaiting => {
|
|
||||||
//trace!("Triggered `CFRunLoopBeforeWaiting`");
|
|
||||||
AppState::cleared();
|
|
||||||
//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,
|
|
||||||
) {
|
|
||||||
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,
|
|
||||||
ptr::null_mut(),
|
|
||||||
);
|
|
||||||
CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setup_control_flow_observers() {
|
|
||||||
unsafe {
|
|
||||||
let run_loop = RunLoop::get();
|
|
||||||
run_loop.add_observer(
|
|
||||||
kCFRunLoopEntry | kCFRunLoopAfterWaiting,
|
|
||||||
CFIndex::min_value(),
|
|
||||||
control_flow_begin_handler,
|
|
||||||
);
|
|
||||||
run_loop.add_observer(
|
|
||||||
kCFRunLoopExit | kCFRunLoopBeforeWaiting,
|
|
||||||
CFIndex::max_value(),
|
|
||||||
control_flow_end_handler,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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,216 +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 crate::{
|
|
||||||
dpi::LogicalSize,
|
|
||||||
platform_impl::platform::{ffi, util::IdRef, window::SharedState},
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
if msg_send![class!(NSThread), isMainThread] {
|
|
||||||
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 _);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// `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() {
|
|
||||||
trace!("Locked shared state in `toggle_full_screen_callback`");
|
|
||||||
let mut shared_state_lock = shared_state.lock().unwrap();
|
|
||||||
(*shared_state_lock).saved_style = Some(curr_mask);
|
|
||||||
trace!("Unlocked shared state in `toggle_full_screen_callback`");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 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() {
|
|
||||||
trace!("Locked shared state in `set_maximized`");
|
|
||||||
let mut shared_state_lock = shared_state.lock().unwrap();
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
let curr_mask = ns_window.styleMask();
|
|
||||||
if shared_state_lock.fullscreen.is_some() {
|
|
||||||
// Handle it in window_did_exit_fullscreen
|
|
||||||
return;
|
|
||||||
} else if curr_mask.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, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
trace!("Unlocked shared state in `set_maximized`");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// `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...
|
|
||||||
pub unsafe fn close_async(ns_window: id) {
|
|
||||||
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: &'static 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,142 +0,0 @@
|
|||||||
mod r#async;
|
|
||||||
mod cursor;
|
|
||||||
|
|
||||||
pub use self::{cursor::*, r#async::*};
|
|
||||||
|
|
||||||
use std::ops::{BitAnd, Deref};
|
|
||||||
|
|
||||||
use cocoa::{
|
|
||||||
appkit::{NSApp, NSWindowStyleMask},
|
|
||||||
base::{id, nil},
|
|
||||||
foundation::{NSAutoreleasePool, NSRect, NSString, NSUInteger},
|
|
||||||
};
|
|
||||||
use core_graphics::display::CGDisplay;
|
|
||||||
use objc::runtime::{Class, Object, Sel, BOOL, YES};
|
|
||||||
|
|
||||||
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)]
|
|
||||||
pub struct IdRef(id);
|
|
||||||
|
|
||||||
impl IdRef {
|
|
||||||
pub fn new(inner: id) -> IdRef {
|
|
||||||
IdRef(inner)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn retain(inner: id) -> IdRef {
|
|
||||||
if inner != nil {
|
|
||||||
let () = 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 pool = NSAutoreleasePool::new(nil);
|
|
||||||
let () = msg_send![self.0, release];
|
|
||||||
pool.drain();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for IdRef {
|
|
||||||
type Target = id;
|
|
||||||
fn deref<'a>(&'a self) -> &'a id {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for IdRef {
|
|
||||||
fn clone(&self) -> IdRef {
|
|
||||||
if self.0 != nil {
|
|
||||||
let _: id = unsafe { msg_send![self.0, retain] };
|
|
||||||
}
|
|
||||||
IdRef(self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn ns_string_id_ref(s: &str) -> IdRef {
|
|
||||||
IdRef::new(NSString::alloc(nil).init_str(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
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<'a>(this: &'a Object) -> &'a Class {
|
|
||||||
let superclass: id = msg_send![this, superclass];
|
|
||||||
&*(superclass as *const _)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn create_input_context(view: id) -> IdRef {
|
|
||||||
let input_context: id = msg_send![class!(NSTextInputContext), alloc];
|
|
||||||
let input_context: id = msg_send![input_context, initWithClient: view];
|
|
||||||
IdRef::new(input_context)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub unsafe fn open_emoji_picker() {
|
|
||||||
let () = msg_send![NSApp(), orderFrontCharacterPalette: nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub extern "C" fn yes(_: &Object, _: Sel) -> BOOL {
|
|
||||||
YES
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
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