Compare commits
857 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
36a20f7b00 | ||
|
|
35cbf49b08 | ||
|
|
21836cedda | ||
|
|
0a892b72e6 | ||
|
|
2c6e5a6e73 | ||
|
|
2cb25cf5a8 | ||
|
|
e07916425e | ||
|
|
f62e2374e4 | ||
|
|
eb4ee2f0af | ||
|
|
be3c76da20 | ||
|
|
b15e58621c | ||
|
|
601a6c9e3d | ||
|
|
3ff184348e | ||
|
|
c8efcd2be1 | ||
|
|
9c116a2975 | ||
|
|
8818b5d8df | ||
|
|
e96dfadae7 | ||
|
|
deb564e5a6 | ||
|
|
ffc71c348c | ||
|
|
19df7b94ea | ||
|
|
a3ecc79f0d | ||
|
|
b140b15119 | ||
|
|
b7b2b6075a | ||
|
|
7525e152b8 | ||
|
|
bc9dfec615 | ||
|
|
7824739034 | ||
|
|
e7b60a3628 | ||
|
|
97c3061b8f | ||
|
|
1a042c873c | ||
|
|
064aa3a150 | ||
|
|
8a178ebe55 | ||
|
|
9a97f0bced | ||
|
|
5c7471910d | ||
|
|
27b6517594 | ||
|
|
cd4dacb406 | ||
|
|
5f6faa1bf0 | ||
|
|
cfc5a952f8 | ||
|
|
10b186957b | ||
|
|
f597481736 | ||
|
|
9fa97e6187 | ||
|
|
3844321f20 | ||
|
|
8efe08c45b | ||
|
|
c78563b452 | ||
|
|
122e172092 | ||
|
|
8f85c0d167 | ||
|
|
ac6d1ce174 | ||
|
|
03e0bf4c0e | ||
|
|
1cb183f5e1 | ||
|
|
d78a553910 | ||
|
|
1326532850 | ||
|
|
408508247d | ||
|
|
e2be09e028 | ||
|
|
c7c14f184c | ||
|
|
24ca919f33 | ||
|
|
85fe783652 | ||
|
|
04b6bf3b74 | ||
|
|
8b2fb105d0 | ||
|
|
558caa232d | ||
|
|
d8886edbeb | ||
|
|
f72ecf1791 | ||
|
|
45d1b978d8 | ||
|
|
0b780cc065 | ||
|
|
5263f6598c | ||
|
|
f9e60e7ef6 | ||
|
|
45c00d0b1f | ||
|
|
c70e2f7d3e | ||
|
|
1ec0bed1ce | ||
|
|
46b5e4513a | ||
|
|
ffb5196f3b | ||
|
|
107f1af3c4 | ||
|
|
03b1484c96 | ||
|
|
aaf64ad63e | ||
|
|
f548e795d5 | ||
|
|
767da68239 | ||
|
|
f44e0ff4bd | ||
|
|
7f226f2ea1 | ||
|
|
1c82a39a6e | ||
|
|
b70cf276b4 | ||
|
|
80b2df2ef4 | ||
|
|
ad27452275 | ||
|
|
196a0b6b7e | ||
|
|
f356f63994 | ||
|
|
4947fb6b57 | ||
|
|
622f12b84e | ||
|
|
c1f84ce603 | ||
|
|
14912461ad | ||
|
|
a9f265e8b7 | ||
|
|
199ee20adb | ||
|
|
7c3c87abf6 | ||
|
|
b0ad44bcf2 | ||
|
|
9ba808ec3c | ||
|
|
eacf849e3a | ||
|
|
42c2791182 | ||
|
|
390e883ecf | ||
|
|
9fe631ee3f | ||
|
|
82d8c4b230 | ||
|
|
9775bb0a9d | ||
|
|
139c9a2d6d | ||
|
|
542880f87b | ||
|
|
3846286bee | ||
|
|
72be402a2d | ||
|
|
c6b9cb759a | ||
|
|
13ccf269a6 | ||
|
|
fcc7a0255a | ||
|
|
e7447f38cf | ||
|
|
7ae35631a6 | ||
|
|
55ad228166 | ||
|
|
48fb051253 | ||
|
|
f97e8b7eeb | ||
|
|
84f914742b | ||
|
|
0d704f3715 | ||
|
|
d8896eb210 | ||
|
|
b9d74e2012 | ||
|
|
803fba9cfd | ||
|
|
581548ed67 | ||
|
|
9a8c0f8e7e | ||
|
|
328f569c16 | ||
|
|
7931654a7c | ||
|
|
6eb0c10339 | ||
|
|
269f294d19 | ||
|
|
d19e2f1350 | ||
|
|
3b745b82db | ||
|
|
02f30c2a03 | ||
|
|
54002b386a | ||
|
|
0e2ff077af | ||
|
|
012cf70321 | ||
|
|
9da0ba52db | ||
|
|
7bfb07e59e | ||
|
|
9cb2c4b1ff | ||
|
|
8c088fad0f | ||
|
|
392722aed0 | ||
|
|
fabfb30fc4 | ||
|
|
c7cb021e92 | ||
|
|
be05d9c3e9 | ||
|
|
e38690e8d9 | ||
|
|
7172115559 | ||
|
|
7314019832 | ||
|
|
b9684941c4 | ||
|
|
a27289352c | ||
|
|
dc77009e55 | ||
|
|
d12e10830d | ||
|
|
936f6c22f0 | ||
|
|
9712be903d | ||
|
|
7441cefdbb | ||
|
|
7bf83603c8 | ||
|
|
6768bc1705 | ||
|
|
707cb37212 | ||
|
|
1cf0a8b78e | ||
|
|
3d2cb37af0 | ||
|
|
09fbd6f5e0 | ||
|
|
474bef65cb | ||
|
|
e97454c4a5 | ||
|
|
936dc74a27 | ||
|
|
362e4266bb | ||
|
|
d08f2e10f1 | ||
|
|
f1e5ee74a6 | ||
|
|
7832d33348 | ||
|
|
de25f94fae | ||
|
|
98625e9c36 | ||
|
|
5768cc98b6 | ||
|
|
da1cc6c1c5 | ||
|
|
50384fac97 | ||
|
|
424bd8a6cd | ||
|
|
421fae2b57 | ||
|
|
8bb8615a5a | ||
|
|
80f8692e92 | ||
|
|
25623c758d | ||
|
|
e1a0a195a9 | ||
|
|
f8cf49a90c | ||
|
|
ac9f8595c9 | ||
|
|
97f22bbff6 | ||
|
|
d6ddfa9d18 | ||
|
|
5e48ae8bac | ||
|
|
cdd943ded4 | ||
|
|
39b2bf963c | ||
|
|
06827fd437 | ||
|
|
70ccc1cf6d | ||
|
|
f801c47d1e | ||
|
|
01c0e8da93 | ||
|
|
e844ad6db3 | ||
|
|
3f8c042bc3 | ||
|
|
707ad705a4 | ||
|
|
79a780004f | ||
|
|
81b28bc1bd | ||
|
|
e9c4d540b1 | ||
|
|
50d9718c68 | ||
|
|
e39da2b2bb | ||
|
|
f08ec1522a | ||
|
|
e61e93712c | ||
|
|
c9e198e02e | ||
|
|
0ce181b578 | ||
|
|
99950fe448 | ||
|
|
d8daf84a09 | ||
|
|
3d32202b3a | ||
|
|
4f4d562670 | ||
|
|
28fc9c69da | ||
|
|
6edf00e699 | ||
|
|
a0d6445dda | ||
|
|
e74797037c | ||
|
|
3fb125dba7 | ||
|
|
3c8640f9ad | ||
|
|
2e63a4c83f | ||
|
|
9da5bee178 | ||
|
|
2fc6f3b542 | ||
|
|
4754f42e51 | ||
|
|
941fe918fb | ||
|
|
f4e82e972e | ||
|
|
f66dbf111d | ||
|
|
1a2da5c49d | ||
|
|
8327dabb29 | ||
|
|
195ea6c140 | ||
|
|
fcc0707174 | ||
|
|
aa9c3cd634 | ||
|
|
b541341681 | ||
|
|
e5d50aca6d | ||
|
|
b738f4d4ff | ||
|
|
5c38aabf0e | ||
|
|
03e9ed6947 | ||
|
|
63493fad22 | ||
|
|
696b857f91 | ||
|
|
53ae2bbe0d | ||
|
|
01069e919d | ||
|
|
fc0095f505 | ||
|
|
421528e130 | ||
|
|
7b70356515 | ||
|
|
e803505aad | ||
|
|
ad1ff455a0 | ||
|
|
7692cb5c3d | ||
|
|
a94e208db3 | ||
|
|
ba9d5cf3ff | ||
|
|
0dff0d03f9 | ||
|
|
c92aca5d19 | ||
|
|
5762baeed7 | ||
|
|
601cf01222 | ||
|
|
0eef48b516 | ||
|
|
3c1954f329 | ||
|
|
cc29640604 | ||
|
|
d0329460b8 | ||
|
|
717c0c96de | ||
|
|
b3f161efce | ||
|
|
0286940163 | ||
|
|
d4a4ee386f | ||
|
|
33498f9133 | ||
|
|
da3d4d1bef | ||
|
|
c69b8870d2 | ||
|
|
85b55b31b6 | ||
|
|
948919e42b | ||
|
|
85c40090ed | ||
|
|
98b50ec145 | ||
|
|
1ab4f7d2d0 | ||
|
|
98801b6fa6 | ||
|
|
bdd68284b5 | ||
|
|
a61d70ea9a | ||
|
|
e271719dd2 | ||
|
|
a08efa1fc5 | ||
|
|
ffac3600e0 | ||
|
|
6499f8ac24 | ||
|
|
63307cb1d8 | ||
|
|
0a6fb362f6 | ||
|
|
457fcbfb90 | ||
|
|
3b6277b9e2 | ||
|
|
e6e61028b1 | ||
|
|
e9be66126b | ||
|
|
681fe7f2f2 | ||
|
|
0196920533 | ||
|
|
5f5bb3ace0 | ||
|
|
3c970e0122 | ||
|
|
43af6e3b8a | ||
|
|
ac8cfc12fb | ||
|
|
b19472e0f5 | ||
|
|
b4d08044a0 | ||
|
|
b2fe02e63f | ||
|
|
d1a5d30233 | ||
|
|
bba30138e1 | ||
|
|
082546a1e7 | ||
|
|
41ffd58e26 | ||
|
|
b4f24e4e1e | ||
|
|
75c8ffd715 | ||
|
|
688606ff7c | ||
|
|
2e3f9dffa7 | ||
|
|
859033415b | ||
|
|
1f1b52b580 | ||
|
|
4e7bf80447 | ||
|
|
e39df50884 | ||
|
|
8d6a9d45c2 | ||
|
|
0862e85577 | ||
|
|
0b9a37139e | ||
|
|
6357f7b811 | ||
|
|
24197dc422 | ||
|
|
9bc2ab3245 | ||
|
|
bf4e8c490c | ||
|
|
590a4d70ee | ||
|
|
40341d2ddd | ||
|
|
0cd2c41ee3 | ||
|
|
5d65cdb6a8 | ||
|
|
759a15ccbb | ||
|
|
5aead5eebe | ||
|
|
a325a5fdee | ||
|
|
931e91f1a7 | ||
|
|
6e13509720 | ||
|
|
5b4844e71e | ||
|
|
66cbbea9bb | ||
|
|
b65119b8a9 | ||
|
|
86703da917 | ||
|
|
610e9afdf0 | ||
|
|
e3af9f68d3 | ||
|
|
747ac85e7c | ||
|
|
8c23f9e526 | ||
|
|
e6e339fcd3 | ||
|
|
f04680ac47 | ||
|
|
90985cc846 | ||
|
|
63917944f2 | ||
|
|
793605f80f | ||
|
|
93a281c7e3 | ||
|
|
8f1c6c4d74 | ||
|
|
1610dba7ab | ||
|
|
d9d419991c | ||
|
|
f9e86b19aa | ||
|
|
fffb976e43 | ||
|
|
0da566b49f | ||
|
|
da55717d20 | ||
|
|
254836902c | ||
|
|
d01f8d1865 | ||
|
|
a46c3f7007 | ||
|
|
4e58062025 | ||
|
|
aa3be0535f | ||
|
|
8ebf5e007c | ||
|
|
c2957e7f12 | ||
|
|
bf7c26d357 | ||
|
|
0547102bdd | ||
|
|
a07369ce69 | ||
|
|
21c5610431 | ||
|
|
6dbdf72f40 | ||
|
|
76a6bba643 | ||
|
|
b0b8222a96 | ||
|
|
0d87b8fac5 | ||
|
|
5227b1f98b | ||
|
|
c4f7c5c1cd | ||
|
|
33a84a609c | ||
|
|
037111715a | ||
|
|
93271b93e6 | ||
|
|
04488ada06 | ||
|
|
21c7002c68 | ||
|
|
97cae0e355 | ||
|
|
85fc0a59b1 | ||
|
|
eddd45bbc3 | ||
|
|
e2339697b9 | ||
|
|
cfe9b408ce | ||
|
|
83e8018787 | ||
|
|
372d2008a6 | ||
|
|
ab9a36f8c1 | ||
|
|
2458c3a7c6 | ||
|
|
e827e6fa21 | ||
|
|
342f0981d8 | ||
|
|
9c19e88288 | ||
|
|
7329bd4910 | ||
|
|
b9ad62226e | ||
|
|
3bff3bcbe4 | ||
|
|
a06fdad034 | ||
|
|
a49bd98ec6 | ||
|
|
90f41108d3 | ||
|
|
382c31ddf2 | ||
|
|
871ca4dda6 | ||
|
|
fa003f8233 | ||
|
|
12f37bcfad | ||
|
|
e86c712424 | ||
|
|
e8a9935cb2 | ||
|
|
f353131b44 | ||
|
|
7c4a2ba329 | ||
|
|
dfb543bd94 | ||
|
|
c912eef040 | ||
|
|
5f06d3a427 | ||
|
|
a70fa9abc5 | ||
|
|
5956277863 | ||
|
|
d3f37eeba4 | ||
|
|
ea48332f46 | ||
|
|
eb12511646 | ||
|
|
aa11ddf32c | ||
|
|
a348f64833 | ||
|
|
626a96e20f | ||
|
|
60995ff04b | ||
|
|
ba4e711715 | ||
|
|
891b184195 | ||
|
|
5414dc398e | ||
|
|
917559f883 | ||
|
|
9f2fb76309 | ||
|
|
b6692849d6 | ||
|
|
b27f6d5957 | ||
|
|
cda1e28b32 | ||
|
|
8a3e71d3d0 | ||
|
|
149ae2866a | ||
|
|
21707171bb | ||
|
|
2ebdf738ca | ||
|
|
0e871a89aa | ||
|
|
2d4f194f0d | ||
|
|
ce94a9b2df | ||
|
|
bfbb3c7068 | ||
|
|
6d1fd20542 | ||
|
|
bb8cab3a02 | ||
|
|
10435c23c2 | ||
|
|
01142cdc8c | ||
|
|
fc0adfd965 | ||
|
|
8803eeb890 | ||
|
|
25be705bcf | ||
|
|
d8f214a267 | ||
|
|
f74fb846e5 | ||
|
|
8ae110691e | ||
|
|
abff2292c1 | ||
|
|
7d690bbad2 | ||
|
|
7298de646a | ||
|
|
3c2f3ecd8e | ||
|
|
ac2c21e046 | ||
|
|
c817ba9eae | ||
|
|
92827ae647 | ||
|
|
5bdb405635 | ||
|
|
9beae321d7 | ||
|
|
353db678a2 | ||
|
|
9b973ef99c | ||
|
|
308d171dae | ||
|
|
7057bc663e | ||
|
|
d84cff0baf | ||
|
|
645890ccc8 | ||
|
|
cc1336a616 | ||
|
|
9b32f18109 | ||
|
|
a0321376d5 | ||
|
|
0a6e264d09 | ||
|
|
afec07b124 | ||
|
|
8a34fede55 | ||
|
|
686b5f297e | ||
|
|
6609d219f4 | ||
|
|
804998ca69 | ||
|
|
de5254469c | ||
|
|
ef879bab9c | ||
|
|
f729985346 | ||
|
|
1e998b6a3f | ||
|
|
77e9460eea | ||
|
|
81a58186c6 | ||
|
|
ac149be2bb | ||
|
|
a76a163db5 | ||
|
|
4ae59d3f77 | ||
|
|
d83a450260 | ||
|
|
61acc0784e | ||
|
|
f05890719d | ||
|
|
0f4e7921ee | ||
|
|
ec8c96b8e6 | ||
|
|
f0b29e15c3 | ||
|
|
8ad62106e0 | ||
|
|
36eb52424f | ||
|
|
c9ce2f47dc | ||
|
|
5035ed61ee | ||
|
|
a4cc10c41b | ||
|
|
5730ce5f53 | ||
|
|
27524bfc3d | ||
|
|
8843ded1c9 | ||
|
|
64b497589f | ||
|
|
ee48fd7151 | ||
|
|
291cd5e23f | ||
|
|
be668cfd9a | ||
|
|
f889c1c75a | ||
|
|
55c5f3e9fd | ||
|
|
242d335095 | ||
|
|
c763db94ce | ||
|
|
a391732600 | ||
|
|
06b45fcace | ||
|
|
bdac5f7d5b | ||
|
|
74865efddb | ||
|
|
b1f4d16692 | ||
|
|
01b8e2c3f5 | ||
|
|
8c1ef94b26 | ||
|
|
507fbc7abf | ||
|
|
2f8002c567 | ||
|
|
551e28d8e0 | ||
|
|
e84163e838 | ||
|
|
3a64b835fb | ||
|
|
08a44ed4dd | ||
|
|
4b8cc23a86 | ||
|
|
c4b2de8939 | ||
|
|
e2a603dcda | ||
|
|
294d01def6 | ||
|
|
05f289f912 | ||
|
|
4343c4067e | ||
|
|
c167dcc4e9 | ||
|
|
9a67176719 | ||
|
|
42d1a4755a | ||
|
|
1690e6933f | ||
|
|
5bbc15583e | ||
|
|
e03aabd972 | ||
|
|
6f5968eeb0 | ||
|
|
71b6e8f12a | ||
|
|
9c486f6e69 | ||
|
|
2f24588c66 | ||
|
|
6b1ea326a0 | ||
|
|
4512d0dbac | ||
|
|
511c4da6a3 | ||
|
|
af8fdf1dbe | ||
|
|
b7d12de0a7 | ||
|
|
2d870fc84a | ||
|
|
6e87929603 | ||
|
|
2b441df9e9 | ||
|
|
3e672ac178 | ||
|
|
12a24d5c86 | ||
|
|
dec1a0886d | ||
|
|
a82e45ad71 | ||
|
|
82e5750d71 | ||
|
|
6a3eb3f3d7 | ||
|
|
533a739e2b | ||
|
|
b12a8331f1 | ||
|
|
8f4ec036e7 | ||
|
|
cba36f2240 | ||
|
|
b54ffbefe4 | ||
|
|
6d470e4da0 | ||
|
|
4e1ebec8c2 | ||
|
|
e9779258e9 | ||
|
|
5c83f45bc6 | ||
|
|
eb65aca1f4 | ||
|
|
5d7e393c93 | ||
|
|
d6d01102fe | ||
|
|
1ad802b194 | ||
|
|
4a191b65bb | ||
|
|
65cc9f4e1b | ||
|
|
9f4b13b523 | ||
|
|
97e1b62a1e | ||
|
|
3ac966a38b | ||
|
|
6f1fda0a48 | ||
|
|
c00b486864 | ||
|
|
7928fff645 | ||
|
|
0285f5a1ad | ||
|
|
56e5c8be32 | ||
|
|
ef569976cb | ||
|
|
f8ae048d60 | ||
|
|
c51f4df0ca | ||
|
|
84453bf15c | ||
|
|
917350f058 | ||
|
|
0c58662eb6 | ||
|
|
52ca75855e | ||
|
|
49a07651b9 | ||
|
|
90a8ba3789 | ||
|
|
734c3199f0 | ||
|
|
64567646d9 | ||
|
|
1a1638cc7f | ||
|
|
fdf055312c | ||
|
|
b0b8b06b56 | ||
|
|
9c1ffb1adf | ||
|
|
563bafea08 | ||
|
|
efe4c32ffd | ||
|
|
0f003a9a77 | ||
|
|
3eff929652 | ||
|
|
3010ebfef5 | ||
|
|
ff14f47781 | ||
|
|
0b99a5caae | ||
|
|
467b96f2f2 | ||
|
|
18751a05c0 | ||
|
|
9007623d0c | ||
|
|
c1abb7038d | ||
|
|
53f7eebc4d | ||
|
|
d54d29122d | ||
|
|
1d71791b3c | ||
|
|
0c6145d4f2 | ||
|
|
62e273c5bb | ||
|
|
d82f399be7 | ||
|
|
20a7050564 | ||
|
|
b235f411b0 | ||
|
|
99faeeacfc | ||
|
|
08ad4f389d | ||
|
|
edba63d0d9 | ||
|
|
e33f882abc | ||
|
|
53e059f4f7 | ||
|
|
830364e026 | ||
|
|
332714a925 | ||
|
|
1eee6deb22 | ||
|
|
547f138703 | ||
|
|
b6c345fc3c | ||
|
|
29c40a4957 | ||
|
|
72af821e1b | ||
|
|
5fe4d10f38 | ||
|
|
3b36470e84 | ||
|
|
f9e8a4050c | ||
|
|
ce308286df | ||
|
|
b670973226 | ||
|
|
01b11b8a53 | ||
|
|
75c47a8659 | ||
|
|
5b4e0ed483 | ||
|
|
7432a7f346 | ||
|
|
0830fc3b5a | ||
|
|
ab2c901e85 | ||
|
|
3774cc54c7 | ||
|
|
330ed8e4ae | ||
|
|
eddae44d6d | ||
|
|
226488717a | ||
|
|
2f9dabd344 | ||
|
|
cf1b7c0adb | ||
|
|
eb57d0d431 | ||
|
|
8116ee015a | ||
|
|
0fa3cec61f | ||
|
|
eb60464177 | ||
|
|
44b2651a0f | ||
|
|
f2e91b5ed2 | ||
|
|
e72b60c882 | ||
|
|
5263babc9a | ||
|
|
9034b50b3a | ||
|
|
fdd7fe1948 | ||
|
|
8e7b8b70dc | ||
|
|
9bf24bdff5 | ||
|
|
ee53edd96d | ||
|
|
1bc5d7038d | ||
|
|
8a6b8b415c | ||
|
|
86174c81c8 | ||
|
|
374500d7b0 | ||
|
|
319a141967 | ||
|
|
c0d0062ef8 | ||
|
|
aaaa34e119 | ||
|
|
fba9502a9d | ||
|
|
6670cc1534 | ||
|
|
970729dca0 | ||
|
|
6627586d6e | ||
|
|
9f364209af | ||
|
|
c31a39a052 | ||
|
|
36fda90814 | ||
|
|
7ccfbc16e9 | ||
|
|
ad3b527bc4 | ||
|
|
f7eef3f456 | ||
|
|
1870364888 | ||
|
|
d7dfc85cf7 | ||
|
|
c047e5f16a | ||
|
|
8d21f63ec2 | ||
|
|
85b6a91e5c | ||
|
|
c788e30b66 | ||
|
|
a99caed693 | ||
|
|
9589fb5275 | ||
|
|
1a6dc530a4 | ||
|
|
60835871eb | ||
|
|
644ee646a2 | ||
|
|
8f1ec053ef | ||
|
|
2e3037ce3a | ||
|
|
109a856b91 | ||
|
|
eaec854bfb | ||
|
|
2c6cc6f047 | ||
|
|
f32ae10339 | ||
|
|
c46317f96f | ||
|
|
a67e4e550e | ||
|
|
65a7b19f40 | ||
|
|
1b5daf930a | ||
|
|
748ca81d4b | ||
|
|
0ea7d24ba4 | ||
|
|
6c9861cb76 | ||
|
|
a1125b5cc8 | ||
|
|
b4e189d2bf | ||
|
|
dbc763fca0 | ||
|
|
4c03ffeae2 | ||
|
|
2719ac7d01 | ||
|
|
219b81b5ee | ||
|
|
8e1443360f | ||
|
|
d55242aa82 | ||
|
|
ba531969ec | ||
|
|
c12e7587cb | ||
|
|
3aac0860a2 | ||
|
|
e4fd12001c | ||
|
|
3581ffe04b | ||
|
|
f6901e4a3d | ||
|
|
5a2cc4578e | ||
|
|
6b5acc4d3d | ||
|
|
43326c9bd0 | ||
|
|
96f3253e6d | ||
|
|
f5090c951a | ||
|
|
edacdc5e25 | ||
|
|
c67661af61 | ||
|
|
5e60329adf | ||
|
|
6b4f8408de | ||
|
|
ea1ef709c6 | ||
|
|
bbf247c5c0 | ||
|
|
8243fd505f | ||
|
|
ed9f9db1b0 | ||
|
|
74bed658e1 | ||
|
|
2f7bdc274d | ||
|
|
1347ff27d9 | ||
|
|
5bf4d520f5 | ||
|
|
cf8d665eb7 | ||
|
|
b2eee6df5c | ||
|
|
e8121b39ec | ||
|
|
5359abab76 | ||
|
|
01644ac3f0 | ||
|
|
8fbf552494 | ||
|
|
9c3390c5e6 | ||
|
|
24d45b99e3 | ||
|
|
936f2186cc | ||
|
|
c68b302fa2 | ||
|
|
ca7cab1a2d | ||
|
|
083b294659 | ||
|
|
bc24716476 | ||
|
|
4acab65016 | ||
|
|
f6b03f106c | ||
|
|
8f9bbeabd7 | ||
|
|
ac72ac7af2 | ||
|
|
68e00bc8e1 | ||
|
|
922d6b3b32 | ||
|
|
4dde98305e | ||
|
|
3593189a75 | ||
|
|
4f135be86c | ||
|
|
605b32f78a | ||
|
|
476a8b1429 | ||
|
|
da9165aebf | ||
|
|
455a1e9532 | ||
|
|
f12031e284 | ||
|
|
df551bc992 | ||
|
|
35fb702833 | ||
|
|
072cd29e3c | ||
|
|
624dd78460 | ||
|
|
51c4737a29 | ||
|
|
d63ea11cac | ||
|
|
3d307794be | ||
|
|
d32a05925f | ||
|
|
04006ea81e | ||
|
|
1723616968 | ||
|
|
23bc2171c9 | ||
|
|
0b3602f764 | ||
|
|
0e448347cd | ||
|
|
e0c66ea5ba | ||
|
|
268de9a9d5 | ||
|
|
74eb523178 | ||
|
|
8c540d5a46 | ||
|
|
2045055ae3 | ||
|
|
d8ed135b46 | ||
|
|
3a4ba7e834 | ||
|
|
fab57fbdf1 | ||
|
|
76a93b6cd6 | ||
|
|
f44c3a4606 | ||
|
|
714bee7f45 | ||
|
|
91673642b3 | ||
|
|
fee08d746c | ||
|
|
527ff0238a | ||
|
|
789d649b5c | ||
|
|
8020657d8c | ||
|
|
a15d3ff12d | ||
|
|
fbce95aeda | ||
|
|
9bc1b5a2d9 | ||
|
|
6a8b7bf92f | ||
|
|
48728bdfdc | ||
|
|
b6416cf8a9 | ||
|
|
053de5763a | ||
|
|
fdb915d0c8 | ||
|
|
c2769c9aed | ||
|
|
57deb19d0b | ||
|
|
321771b150 | ||
|
|
61dd2011f5 | ||
|
|
8a5f189213 | ||
|
|
14560b008a | ||
|
|
db85e07d41 | ||
|
|
07c838fe24 | ||
|
|
1583b1b0c6 | ||
|
|
d250555cd0 | ||
|
|
dbd0dab154 | ||
|
|
b479ea410d | ||
|
|
1db613e95b | ||
|
|
70071fe1f6 | ||
|
|
58688dda38 | ||
|
|
756d65c41e | ||
|
|
b9a08eacb0 | ||
|
|
b653711446 | ||
|
|
e725c647ef | ||
|
|
003f77b257 | ||
|
|
10b578e6d2 | ||
|
|
151c6c2c70 | ||
|
|
53e6de9bd9 | ||
|
|
66a455a079 | ||
|
|
f8855410e0 | ||
|
|
93e8074325 | ||
|
|
296f512bfb | ||
|
|
c8f20d0edf | ||
|
|
286f3630d7 | ||
|
|
38423505b1 | ||
|
|
9a3c621eb6 | ||
|
|
bcf847fe98 | ||
|
|
ee7a5eef99 | ||
|
|
7bed66a458 | ||
|
|
b26229c44e | ||
|
|
c497c0f96a | ||
|
|
6df64613f2 | ||
|
|
46dd648380 | ||
|
|
4cd5de78c7 | ||
|
|
32fa46d6eb | ||
|
|
902c2f6c88 | ||
|
|
2a5196873a | ||
|
|
d2067141d3 | ||
|
|
b5229ec73c | ||
|
|
3a2a16f54c | ||
|
|
b2d428618c | ||
|
|
97af84a063 | ||
|
|
18faee6250 | ||
|
|
fd90a3ddc3 | ||
|
|
a08931d84d | ||
|
|
2f0b6e24c9 | ||
|
|
33c5fc0fb6 | ||
|
|
71adb2dcf8 | ||
|
|
1ae324691d | ||
|
|
689d059517 | ||
|
|
18f0ab9e02 | ||
|
|
088ab5f137 | ||
|
|
3dd83975ba | ||
|
|
a796a6cbef | ||
|
|
4766d7de82 | ||
|
|
4a3686c16c | ||
|
|
b49105495c | ||
|
|
ca2eda2c5b | ||
|
|
4fc7c4aafc | ||
|
|
0981828493 | ||
|
|
6bc4f18a59 | ||
|
|
79532a315c | ||
|
|
7472a992ed | ||
|
|
e40616a0db | ||
|
|
dd8b97a86c | ||
|
|
5eff3897de | ||
|
|
530cf2eb14 | ||
|
|
2baa34beb8 | ||
|
|
87e2f7f86d | ||
|
|
e142083d53 | ||
|
|
2e850a0d0d | ||
|
|
aec9c31bca | ||
|
|
598a147500 | ||
|
|
ba201c4c92 | ||
|
|
5bc7cfaa43 | ||
|
|
2804e1ff81 | ||
|
|
ae27d36902 | ||
|
|
08182fbe4d | ||
|
|
778474f436 | ||
|
|
c6698ce305 | ||
|
|
21bc215313 | ||
|
|
88a9cc42d2 | ||
|
|
28f33a67cf | ||
|
|
6742fabfba | ||
|
|
b3806f4533 | ||
|
|
6d5fb4ccc6 | ||
|
|
2487f18f24 | ||
|
|
f59afff1d1 | ||
|
|
b92f95b092 | ||
|
|
d68ae01448 | ||
|
|
87eda834d3 | ||
|
|
c28ff5259a | ||
|
|
4619259e26 | ||
|
|
7bf0afa621 | ||
|
|
e3e02c7271 | ||
|
|
24e17cb7d8 | ||
|
|
769998adca | ||
|
|
9b5d88e92d | ||
|
|
6d7bebee3a | ||
|
|
1c38654321 | ||
|
|
da7a78691f | ||
|
|
1eaf7083ef | ||
|
|
79a6fcdc5c | ||
|
|
90bcbbf426 | ||
|
|
41049e2a40 | ||
|
|
c9b8510e9c | ||
|
|
d854fc1cd0 | ||
|
|
5f7bcae072 | ||
|
|
652eec3033 | ||
|
|
a9928ec98a | ||
|
|
e1dfb608ca | ||
|
|
2cee3a8809 |
@@ -1,104 +0,0 @@
|
||||
version: 2
|
||||
|
||||
jobs:
|
||||
|
||||
lin-sys:
|
||||
docker:
|
||||
- image: kovidgoyal/kitty-test:latest
|
||||
steps:
|
||||
- checkout
|
||||
- run: if grep -Inr '\s$' kitty kitty_tests kittens docs *.py *.asciidoc *.rst .gitattributes .gitignore; then echo Trailing whitespace found, aborting.; exit 1; fi
|
||||
- run: python3 -m flake8 --count .
|
||||
- run: python3 setup.py build --debug --verbose
|
||||
- run: ./kitty/launcher/kitty +launch test.py
|
||||
- run: make FAIL_WARN=-W man
|
||||
- run: make FAIL_WARN=-W html
|
||||
|
||||
lin-package:
|
||||
docker:
|
||||
- image: kovidgoyal/kitty-test:latest
|
||||
steps:
|
||||
- checkout
|
||||
- run: python3 setup.py linux-package --update-check-interval=0
|
||||
|
||||
lin-35:
|
||||
docker:
|
||||
- image: kovidgoyal/kitty-test:latest
|
||||
environment:
|
||||
CC: gcc
|
||||
CFLAGS: -funsigned-char
|
||||
LD_LIBRARY_PATH: /opt/py3.5/lib
|
||||
ASAN_OPTIONS: leak_check_at_exit=0
|
||||
steps:
|
||||
- checkout
|
||||
- run: /opt/py3.5/bin/python3 setup.py build --debug --verbose --sanitize
|
||||
- run: ./kitty/launcher/kitty +launch test.py
|
||||
|
||||
lin-37:
|
||||
docker:
|
||||
- image: kovidgoyal/kitty-test:latest
|
||||
environment:
|
||||
CC: clang
|
||||
LD_LIBRARY_PATH: /opt/py3.7/lib
|
||||
ASAN_OPTIONS: leak_check_at_exit=0
|
||||
steps:
|
||||
- checkout
|
||||
- run: /opt/py3.7/bin/python3 setup.py build --debug --verbose --sanitize
|
||||
- run: ./kitty/launcher/kitty +launch test.py
|
||||
|
||||
lin-bundle:
|
||||
docker:
|
||||
- image: kovidgoyal/kitty-test:latest
|
||||
environment:
|
||||
ASAN_OPTIONS: leak_check_at_exit=0
|
||||
steps:
|
||||
- checkout
|
||||
- run: echo "export LD_LIBRARY_PATH=$SW/lib" >> $BASH_ENV
|
||||
- run: echo "export PKG_CONFIG_PATH=$SW/lib/pkgconfig" >> $BASH_ENV
|
||||
- run: echo "export PYTHONHOME=$SW" >> $BASH_ENV
|
||||
- run: $SW/bin/python3 setup.py build --debug --verbose
|
||||
- run: ./kitty/launcher/kitty +launch test.py
|
||||
|
||||
mac-bundle:
|
||||
macos:
|
||||
xcode: "10.1.0"
|
||||
environment:
|
||||
LC_ALL: en_US.UTF-8
|
||||
LANG: en_US.UTF-8
|
||||
SW: /Users/Shared/buildbot/sw/sw
|
||||
steps:
|
||||
- checkout
|
||||
- run: mkdir -p $SW
|
||||
- run: curl https://download.calibre-ebook.com/travis/kitty/osx.tar.xz | tar xJ -C $SW
|
||||
- run: echo "export PATH=$SW/bin:$PATH" >> $BASH_ENV
|
||||
- run: python3 setup.py build --debug --verbose
|
||||
- run: ./kitty/launcher/kitty +launch test.py
|
||||
|
||||
mac-brew:
|
||||
macos:
|
||||
xcode: "10.2.0"
|
||||
environment:
|
||||
LC_ALL: en_US.UTF-8
|
||||
LANG: en_US.UTF-8
|
||||
steps:
|
||||
- checkout
|
||||
- run: brew bundle
|
||||
- run: python3 setup.py build --debug --verbose
|
||||
- run: ./kitty/launcher/kitty +launch test.py
|
||||
- run: echo "export PATH=/usr/local/opt/sphinx-doc/bin:$PATH" >> $BASH_ENV
|
||||
- run: python3 setup.py linux-package --verbose
|
||||
- run: python3 setup.py kitty.app --verbose
|
||||
- run: kitty.app/Contents/MacOS/kitty +runpy "from kitty.constants import *; print(kitty_exe())"
|
||||
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
all_jobs:
|
||||
jobs:
|
||||
- mac-bundle
|
||||
- lin-sys
|
||||
- lin-35
|
||||
- lin-37
|
||||
- lin-package
|
||||
- lin-bundle
|
||||
- mac-brew
|
||||
@@ -1,55 +0,0 @@
|
||||
# When changing this file, go to
|
||||
# https://cloud.docker.com/repository/docker/kovidgoyal/kitty-test/builds and
|
||||
# click the "Trigger" button under Automated builds to rebuild
|
||||
FROM ubuntu:latest
|
||||
|
||||
# make Apt non-interactive
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN echo 'APT::Get::Assume-Yes "true";' > /etc/apt/apt.conf.d/90circleci \
|
||||
&& echo 'DPkg::Options "--force-confnew";' >> /etc/apt/apt.conf.d/90circleci \
|
||||
&& echo 'Acquire::Languages "none";' >> /etc/apt/apt.conf.d/90circleci
|
||||
|
||||
# System setup
|
||||
RUN apt-get update && mkdir -p /usr/share/man/man1 && apt-get install -y python3-flake8 python3-pip python3-pil clang git apt locales sudo openssh-client ca-certificates tar gzip parallel net-tools netcat unzip zip bzip2 gnupg curl wget build-essential apt-utils
|
||||
|
||||
# Setup circleci user
|
||||
RUN groupadd --gid 3434 circleci \
|
||||
&& useradd --uid 3434 --gid circleci --shell /bin/bash --create-home circleci \
|
||||
&& echo 'circleci ALL=NOPASSWD: ALL' >> /etc/sudoers.d/50-circleci \
|
||||
&& echo 'Defaults env_keep += "DEBIAN_FRONTEND"' >> /etc/sudoers.d/env_keep \
|
||||
&& echo 'Defaults env_keep += "SW"' >> /etc/sudoers.d/env_keep \
|
||||
&& echo 'Defaults env_keep += "LANG"' >> /etc/sudoers.d/env_keep
|
||||
|
||||
# Set timezone to UTC by default
|
||||
RUN ln -sf /usr/share/zoneinfo/Etc/UTC /etc/localtime
|
||||
|
||||
# Use unicode
|
||||
RUN locale-gen en_US.UTF-8 || true
|
||||
ENV LANG=en_US.UTF-8
|
||||
|
||||
# Deps needed for building python
|
||||
RUN apt-get install -y libreadline-dev zlib1g-dev libssl-dev libbz2-dev libsqlite3-dev libffi-dev
|
||||
|
||||
# kitty deps
|
||||
RUN apt-get install -y libgl1-mesa-dev libxi-dev libxrandr-dev libxinerama-dev libxcursor-dev libxcb-xkb-dev libdbus-1-dev libxkbcommon-dev libharfbuzz-dev libpng-dev libfontconfig-dev libpython3-dev libxkbcommon-x11-dev python3-pygments libcanberra-dev
|
||||
|
||||
# Needed to build kitty docs
|
||||
RUN pip3 install sphinx
|
||||
|
||||
# Install multiple pythons
|
||||
ADD install-python.py /tmp/install-python.py
|
||||
RUN python3 /tmp/install-python.py py3.5 https://www.python.org/ftp/python/3.5.6/Python-3.5.6.tar.xz
|
||||
RUN python3 /tmp/install-python.py py3.7 https://www.python.org/ftp/python/3.7.2/Python-3.7.2.tar.xz
|
||||
|
||||
# Install kitty bundle
|
||||
ENV SW=/home/circleci/sw
|
||||
RUN python3 /tmp/install-python.py bundle https://download.calibre-ebook.com/travis/kitty/linux-64.tar.xz
|
||||
RUN echo SW=$SW >> /etc/environment
|
||||
|
||||
# Clean unused files
|
||||
RUN apt-get clean -y
|
||||
|
||||
USER circleci
|
||||
|
||||
LABEL com.circleci.preserve-entrypoint=true
|
||||
ENTRYPOINT sleep 2h
|
||||
@@ -1,60 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2019, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import io
|
||||
import os
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
from urllib.request import urlopen
|
||||
|
||||
PY, URL = sys.argv[1], sys.argv[2]
|
||||
if PY == 'bundle':
|
||||
SW = os.environ['SW']
|
||||
os.mkdir(SW)
|
||||
os.chdir(SW)
|
||||
else:
|
||||
os.chdir('/usr/src')
|
||||
|
||||
|
||||
def run(cmd):
|
||||
cmd = shlex.split(cmd)
|
||||
p = subprocess.Popen(cmd)
|
||||
if p.wait() != 0:
|
||||
raise SystemExit(p.returncode)
|
||||
|
||||
|
||||
with urlopen(URL) as f:
|
||||
data = f.read()
|
||||
|
||||
with tarfile.open(fileobj=io.BytesIO(data), mode='r:xz') as tf:
|
||||
tf.extractall()
|
||||
|
||||
|
||||
def replace_in_file(path, src, dest):
|
||||
with open(path, 'r+') as f:
|
||||
n = f.read().replace(src, dest)
|
||||
f.seek(0), f.truncate()
|
||||
f.write(n)
|
||||
|
||||
|
||||
if PY == 'bundle':
|
||||
replaced = 0
|
||||
for dirpath, dirnames, filenames in os.walk(SW):
|
||||
for f in filenames:
|
||||
if f.endswith('.pc') or (f.endswith('.py') and f.startswith('_sysconfig')):
|
||||
replace_in_file(os.path.join(dirpath, f), '/sw/sw', SW)
|
||||
replaced += 1
|
||||
if replaced < 2:
|
||||
raise SystemExit('Failed to replace path to SW in bundle')
|
||||
else:
|
||||
src = os.path.abspath(tuple(os.listdir('.'))[0])
|
||||
os.chdir(src)
|
||||
run(f'./configure --prefix=/opt/{PY} --enable-shared --with-system-expat --without-ensurepip')
|
||||
run(f'make -j {os.cpu_count()}')
|
||||
run('make install')
|
||||
os.chdir('/')
|
||||
shutil.rmtree(src)
|
||||
21
.editorconfig
Normal file
21
.editorconfig
Normal file
@@ -0,0 +1,21 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = spaces
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[{Makefile,*.terminfo}]
|
||||
indent_style = tab
|
||||
|
||||
# Autogenerated files with tabs below this line.
|
||||
|
||||
[kitty/{unicode-data.c,emoji.h,wcwidth-std.h}]
|
||||
indent_style = tab
|
||||
|
||||
[kittens/unicode_input/names.h]
|
||||
indent_style = tab
|
||||
|
||||
[glfw/wayland-*-protocol.{c,h}]
|
||||
indent_style = tab
|
||||
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
34
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a bug report
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. XXX
|
||||
2. YYY
|
||||
3. ZZZ
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Enviroment details**
|
||||
OS: Name and version of operating system(s)
|
||||
|
||||
```
|
||||
Output of kitty --debug-config
|
||||
```
|
||||
|
||||
**Additional context**
|
||||
Try to reproduce the problem with `kitty --config NONE` if you cannot then post a minimal kitty.conf that reproduces the problem. If the problem involves interaction with some other terminal program post a minimal config for that program to reproduce the problem as well.
|
||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
125
.github/workflows/ci.py
vendored
Normal file
125
.github/workflows/ci.py
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import io
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
from urllib.request import urlopen
|
||||
|
||||
is_bundle = os.environ.get('KITTY_BUNDLE') == '1'
|
||||
is_macos = 'darwin' in sys.platform.lower()
|
||||
SW = None
|
||||
|
||||
|
||||
def run(*a):
|
||||
if len(a) == 1:
|
||||
a = shlex.split(a[0])
|
||||
print(' '.join(map(shlex.quote, a)))
|
||||
sys.stdout.flush()
|
||||
ret = subprocess.Popen(a).wait()
|
||||
if ret != 0:
|
||||
raise SystemExit(ret)
|
||||
|
||||
|
||||
def install_deps():
|
||||
print('Installing kitty dependencies...')
|
||||
sys.stdout.flush()
|
||||
if is_macos:
|
||||
items = (x.strip() for x in open('Brewfile').readlines() if not x.startswith('#'))
|
||||
run('brew', 'install', *items)
|
||||
else:
|
||||
run('sudo apt-get update')
|
||||
run('sudo apt-get install -y libgl1-mesa-dev libxi-dev libxrandr-dev libxinerama-dev'
|
||||
' libxcursor-dev libxcb-xkb-dev libdbus-1-dev libxkbcommon-dev libharfbuzz-dev'
|
||||
' libpng-dev libfontconfig-dev libxkbcommon-x11-dev libcanberra-dev')
|
||||
if is_bundle:
|
||||
install_bundle()
|
||||
else:
|
||||
run('pip install Pillow pygments')
|
||||
|
||||
|
||||
def build_kitty():
|
||||
python = 'python3' if is_bundle else sys.executable
|
||||
cmd = '{} setup.py build --verbose'.format(python)
|
||||
if os.environ.get('KITTY_SANITIZE') == '1':
|
||||
cmd += ' --debug --sanitize'
|
||||
run(cmd)
|
||||
|
||||
|
||||
def test_kitty():
|
||||
run('./kitty/launcher/kitty +launch test.py')
|
||||
|
||||
|
||||
def package_kitty():
|
||||
py = 'python3' if is_macos else 'python'
|
||||
run(py + ' setup.py linux-package --update-check-interval=0 --verbose')
|
||||
if is_macos:
|
||||
run('python3 setup.py kitty.app --update-check-interval=0 --verbose')
|
||||
run('kitty.app/Contents/MacOS/kitty +runpy "from kitty.constants import *; print(kitty_exe())"')
|
||||
|
||||
|
||||
def replace_in_file(path, src, dest):
|
||||
with open(path, 'r+') as f:
|
||||
n = f.read().replace(src, dest)
|
||||
f.seek(0), f.truncate()
|
||||
f.write(n)
|
||||
|
||||
|
||||
def setup_bundle_env():
|
||||
global SW
|
||||
os.environ['SW'] = SW = '/Users/Shared/buildbot/sw/sw' if is_macos else os.path.join(os.environ['GITHUB_WORKSPACE'], 'sw')
|
||||
os.environ['PKG_CONFIG_PATH'] = SW + '/lib/pkgconfig'
|
||||
if is_macos:
|
||||
os.environ['PATH'] = '{}:{}'.format('/usr/local/opt/sphinx-doc/bin', os.environ['PATH'])
|
||||
else:
|
||||
os.environ['LD_LIBRARY_PATH'] = SW + '/lib'
|
||||
os.environ['PYTHONHOME'] = SW
|
||||
os.environ['PATH'] = '{}:{}'.format(os.path.join(SW, 'bin'), os.environ['PATH'])
|
||||
|
||||
|
||||
def install_bundle():
|
||||
cwd = os.getcwd()
|
||||
os.makedirs(SW)
|
||||
os.chdir(SW)
|
||||
with urlopen('https://download.calibre-ebook.com/travis/kitty/{}.tar.xz'.format(
|
||||
'osx' if is_macos else 'linux-64')) as f:
|
||||
data = f.read()
|
||||
with tarfile.open(fileobj=io.BytesIO(data), mode='r:xz') as tf:
|
||||
tf.extractall()
|
||||
if not is_macos:
|
||||
replaced = 0
|
||||
for dirpath, dirnames, filenames in os.walk('.'):
|
||||
for f in filenames:
|
||||
if f.endswith('.pc') or (f.endswith('.py') and f.startswith('_sysconfig')):
|
||||
replace_in_file(os.path.join(dirpath, f), '/sw/sw', SW)
|
||||
replaced += 1
|
||||
if replaced < 2:
|
||||
raise SystemExit('Failed to replace path to SW in bundle')
|
||||
os.chdir(cwd)
|
||||
|
||||
|
||||
def main():
|
||||
if is_bundle:
|
||||
setup_bundle_env()
|
||||
else:
|
||||
if not is_macos:
|
||||
os.environ['LD_LIBRARY_PATH'] = '{}/lib'.format(os.environ['pythonLocation'])
|
||||
action = sys.argv[-1]
|
||||
if action in ('build', 'package'):
|
||||
install_deps()
|
||||
if action == 'build':
|
||||
build_kitty()
|
||||
elif action == 'package':
|
||||
package_kitty()
|
||||
elif action == 'test':
|
||||
test_kitty()
|
||||
else:
|
||||
raise SystemExit('Unknown action: ' + action)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
132
.github/workflows/ci.yml
vendored
Normal file
132
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
name: CI
|
||||
on: [push, pull_request]
|
||||
env:
|
||||
CI: 'true'
|
||||
ASAN_OPTIONS: leak_check_at_exit=0
|
||||
LC_ALL: en_US.UTF-8
|
||||
LANG: en_US.UTF-8
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
name: Linux (python=${{ matrix.pyver }} cc=${{ matrix.cc }} sanitize=${{ matrix.sanitize }})
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CC: ${{ matrix.cc }}
|
||||
KITTY_SANITIZE: ${{ matrix.sanitize }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
python: [a, b, c]
|
||||
cc: [gcc, clang]
|
||||
include:
|
||||
- python: a
|
||||
pyver: 3.6
|
||||
sanitize: 0
|
||||
|
||||
- python: b
|
||||
pyver: 3.7
|
||||
sanitize: 1
|
||||
|
||||
- python: c
|
||||
pyver: 3.8
|
||||
sanitize: 1
|
||||
|
||||
|
||||
exclude:
|
||||
- python: a
|
||||
cc: clang
|
||||
- python: b
|
||||
cc: clang
|
||||
- python: c
|
||||
cc: gcc
|
||||
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Set up Python ${{ matrix.pyver }}
|
||||
uses: actions/setup-python@master
|
||||
with:
|
||||
python-version: ${{ matrix.pyver }}
|
||||
|
||||
- name: Build kitty
|
||||
run: python .github/workflows/ci.py build
|
||||
|
||||
- name: Test kitty
|
||||
run: python .github/workflows/ci.py test
|
||||
|
||||
linux-package:
|
||||
name: Linux package
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CFLAGS: -funsigned-char
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: 10
|
||||
|
||||
- name: Test for trailing whitespace
|
||||
run: if grep -Inr '\s$' kitty kitty_tests kittens docs *.py *.asciidoc *.rst .gitattributes .gitignore; then echo Trailing whitespace found, aborting.; exit 1; fi
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@master
|
||||
with:
|
||||
python-version: 3.8
|
||||
|
||||
- name: Install build-only deps
|
||||
run: pip install flake8 mypy sphinx
|
||||
|
||||
- name: Run pyflakes
|
||||
run: python -m flake8 --count .
|
||||
|
||||
- name: Build kitty package
|
||||
run: python .github/workflows/ci.py package
|
||||
|
||||
- name: Run mypy
|
||||
run: python test.py mypy
|
||||
|
||||
- name: Build man page
|
||||
run: make FAIL_WARN=-W man
|
||||
|
||||
- name: Build HTML docs
|
||||
run: make FAIL_WARN=-W html
|
||||
|
||||
bundle:
|
||||
name: Bundle test (${{ matrix.os }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
env:
|
||||
KITTY_BUNDLE: 1
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: 10
|
||||
|
||||
- name: Build kitty
|
||||
run: which python3 && python3 .github/workflows/ci.py build
|
||||
|
||||
- name: Test kitty
|
||||
run: python3 .github/workflows/ci.py test
|
||||
|
||||
brew:
|
||||
name: macOS Brew
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: 10
|
||||
|
||||
- name: Build kitty
|
||||
run: python3 .github/workflows/ci.py build
|
||||
|
||||
- name: Test kitty
|
||||
run: python3 .github/workflows/ci.py test
|
||||
|
||||
- name: Build kitty package
|
||||
run: python3 .github/workflows/ci.py package
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,6 +1,8 @@
|
||||
*.so
|
||||
*.pyc
|
||||
*.pyo
|
||||
*_stub.pyi
|
||||
/.dmypy.json
|
||||
/tags
|
||||
/build/
|
||||
/linux-package/
|
||||
@@ -14,4 +16,6 @@ __pycache__/
|
||||
/glfw/wayland-*-client-protocol.[ch]
|
||||
/docs/_build/
|
||||
/docs/generated/
|
||||
/.mypy_cache
|
||||
.DS_Store
|
||||
bypy/b
|
||||
|
||||
10
Brewfile
10
Brewfile
@@ -1,5 +1,5 @@
|
||||
brew 'pkg-config'
|
||||
brew 'imagemagick'
|
||||
brew 'harfbuzz'
|
||||
brew 'python'
|
||||
brew 'sphinx-doc'
|
||||
pkg-config
|
||||
python
|
||||
imagemagick
|
||||
harfbuzz
|
||||
sphinx-doc
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
To build from source:
|
||||
To build from source: <https://sw.kovidgoyal.net/kitty/build.html>
|
||||
|
||||
https://sw.kovidgoyal.net/kitty/build.html
|
||||
|
||||
Pre-built binaries:
|
||||
|
||||
https://sw.kovidgoyal.net/kitty/binary.html
|
||||
Pre-built binaries: <https://sw.kovidgoyal.net/kitty/binary.html>
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
See https://sw.kovidgoyal.net/kitty/
|
||||
|
||||
image:https://circleci.com/gh/kovidgoyal/kitty.svg?style=svg["Build status", link="https://circleci.com/gh/kovidgoyal/kitty"]
|
||||
image:https://github.com/kovidgoyal/kitty/workflows/CI/badge.svg["Build status", link="https://github.com/kovidgoyal/kitty/actions?query=workflow%3ACI"]
|
||||
|
||||
56
__main__.py
56
__main__.py
@@ -4,54 +4,56 @@
|
||||
|
||||
import sys
|
||||
import os
|
||||
from typing import List
|
||||
|
||||
|
||||
def icat(args):
|
||||
from kittens.runner import run_kitten
|
||||
def icat(args: List[str]) -> None:
|
||||
from kittens.runner import run_kitten as rk
|
||||
sys.argv = args
|
||||
run_kitten('icat')
|
||||
rk('icat')
|
||||
|
||||
|
||||
def list_fonts(args):
|
||||
from kitty.fonts.list import main
|
||||
main(args)
|
||||
def list_fonts(args: List[str]) -> None:
|
||||
from kitty.fonts.list import main as list_main
|
||||
list_main(args)
|
||||
|
||||
|
||||
def remote_control(args):
|
||||
from kitty.remote_control import main
|
||||
main(args)
|
||||
def remote_control(args: List[str]) -> None:
|
||||
from kitty.remote_control import main as rc_main
|
||||
rc_main(args)
|
||||
|
||||
|
||||
def runpy(args):
|
||||
def runpy(args: List[str]) -> None:
|
||||
sys.argv = ['kitty'] + args[2:]
|
||||
exec(args[1])
|
||||
|
||||
|
||||
def hold(args):
|
||||
def hold(args: List[str]) -> None:
|
||||
import subprocess
|
||||
ret = subprocess.Popen(args[1:]).wait()
|
||||
sys.stdin.read()
|
||||
raise SystemExit(ret)
|
||||
|
||||
|
||||
def complete(args):
|
||||
from kitty.complete import main
|
||||
main(args[1:], entry_points, namespaced_entry_points)
|
||||
def complete(args: List[str]) -> None:
|
||||
from kitty.complete import main as complete_main
|
||||
complete_main(args[1:], entry_points, namespaced_entry_points)
|
||||
|
||||
|
||||
def launch(args):
|
||||
def launch(args: List[str]) -> None:
|
||||
import runpy
|
||||
sys.argv = args[1:]
|
||||
exe = args[1]
|
||||
if exe.startswith(':'):
|
||||
import shutil
|
||||
exe = shutil.which(exe[1:])
|
||||
if not exe:
|
||||
q = shutil.which(exe[1:])
|
||||
if not q:
|
||||
raise SystemExit('{} not found in PATH'.format(args[1][1:]))
|
||||
exe = q
|
||||
runpy.run_path(exe, run_name='__main__')
|
||||
|
||||
|
||||
def run_kitten(args):
|
||||
def run_kitten(args: List[str]) -> None:
|
||||
try:
|
||||
kitten = args[1]
|
||||
except IndexError:
|
||||
@@ -59,11 +61,11 @@ def run_kitten(args):
|
||||
list_kittens()
|
||||
raise SystemExit(1)
|
||||
sys.argv = args[1:]
|
||||
from kittens.runner import run_kitten
|
||||
run_kitten(kitten)
|
||||
from kittens.runner import run_kitten as rk
|
||||
rk(kitten)
|
||||
|
||||
|
||||
def namespaced(args):
|
||||
def namespaced(args: List[str]) -> None:
|
||||
func = namespaced_entry_points[args[1]]
|
||||
func(args[1:])
|
||||
|
||||
@@ -84,7 +86,7 @@ namespaced_entry_points['hold'] = hold
|
||||
namespaced_entry_points['complete'] = complete
|
||||
|
||||
|
||||
def setup_openssl_environment():
|
||||
def setup_openssl_environment() -> None:
|
||||
# Workaround for Linux distros that have still failed to get their heads
|
||||
# out of their asses and implement a common location for SSL certificates.
|
||||
# It's not that hard people, there exists a wonderful tool called the symlink
|
||||
@@ -92,13 +94,13 @@ def setup_openssl_environment():
|
||||
if 'SSL_CERT_FILE' not in os.environ and 'SSL_CERT_DIR' not in os.environ:
|
||||
if os.access('/etc/pki/tls/certs/ca-bundle.crt', os.R_OK):
|
||||
os.environ['SSL_CERT_FILE'] = '/etc/pki/tls/certs/ca-bundle.crt'
|
||||
sys.kitty_ssl_env_var = 'SSL_CERT_FILE'
|
||||
setattr(sys, 'kitty_ssl_env_var', 'SSL_CERT_FILE')
|
||||
elif os.path.isdir('/etc/ssl/certs'):
|
||||
os.environ['SSL_CERT_DIR'] = '/etc/ssl/certs'
|
||||
sys.kitty_ssl_env_var = 'SSL_CERT_DIR'
|
||||
setattr(sys, 'kitty_ssl_env_var', 'SSL_CERT_DIR')
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
if getattr(sys, 'frozen', False) and 'darwin' not in sys.platform.lower():
|
||||
setup_openssl_environment()
|
||||
first_arg = '' if len(sys.argv) < 2 else sys.argv[1]
|
||||
@@ -109,8 +111,8 @@ def main():
|
||||
elif first_arg.startswith('+'):
|
||||
namespaced(['+', first_arg[1:]] + sys.argv[2:])
|
||||
else:
|
||||
from kitty.main import main
|
||||
main()
|
||||
from kitty.main import main as kitty_main
|
||||
kitty_main()
|
||||
else:
|
||||
func(sys.argv[1:])
|
||||
|
||||
|
||||
75
bypy/init_env.py
Normal file
75
bypy/init_env.py
Normal file
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from bypy.constants import (
|
||||
LIBDIR, PREFIX, PYTHON, SRC as KITTY_DIR, ismacos, worker_env
|
||||
)
|
||||
from bypy.utils import run_shell
|
||||
|
||||
|
||||
def read_src_file(name):
|
||||
with open(os.path.join(KITTY_DIR, 'kitty', name), 'rb') as f:
|
||||
return f.read().decode('utf-8')
|
||||
|
||||
|
||||
def initialize_constants():
|
||||
kitty_constants = {}
|
||||
src = read_src_file('constants.py')
|
||||
nv = re.search(r'Version\((\d+), (\d+), (\d+)\)', src)
|
||||
kitty_constants['version'] = '%s.%s.%s' % (nv.group(1), nv.group(2), nv.group(3))
|
||||
kitty_constants['appname'] = re.search(
|
||||
r'appname: str\s+=\s+(u{0,1})[\'"]([^\'"]+)[\'"]', src
|
||||
).group(2)
|
||||
return kitty_constants
|
||||
|
||||
|
||||
def run(*args, **extra_env):
|
||||
env = os.environ.copy()
|
||||
env.update(worker_env)
|
||||
env.update(extra_env)
|
||||
env['SW'] = PREFIX
|
||||
env['LD_LIBRARY_PATH'] = LIBDIR
|
||||
if ismacos:
|
||||
env['PKGCONFIG_EXE'] = os.path.join(PREFIX, 'bin', 'pkg-config')
|
||||
cwd = env.pop('cwd', KITTY_DIR)
|
||||
return subprocess.call(list(args), env=env, cwd=cwd)
|
||||
|
||||
|
||||
def build_c_extensions(ext_dir, args):
|
||||
writeable_src_dir = os.path.join(ext_dir, 'src')
|
||||
shutil.copytree(
|
||||
KITTY_DIR, writeable_src_dir, symlinks=True,
|
||||
ignore=shutil.ignore_patterns('b', 'build', 'dist', '*_commands.json', '*.o'))
|
||||
cmd = [PYTHON, 'setup.py']
|
||||
bundle = 'macos-freeze' if ismacos else 'linux-freeze'
|
||||
cmd.append(bundle)
|
||||
dest = kitty_constants['appname'] + ('.app' if ismacos else '')
|
||||
dest = os.path.join(ext_dir, dest)
|
||||
cmd += ['--prefix', dest]
|
||||
if run(*cmd, cwd=writeable_src_dir) != 0:
|
||||
print('Building of kitty package failed', file=sys.stderr)
|
||||
os.chdir(KITTY_DIR)
|
||||
run_shell()
|
||||
raise SystemExit('Building of kitty package failed')
|
||||
return ext_dir
|
||||
|
||||
|
||||
def run_tests(path_to_kitty, cwd_on_failure):
|
||||
ret = run(PYTHON, 'test.py', cwd=cwd_on_failure)
|
||||
if ret != 0:
|
||||
os.chdir(cwd_on_failure)
|
||||
print(
|
||||
'running kitty tests failed with return code:', ret, file=sys.stderr)
|
||||
run_shell()
|
||||
raise SystemExit('running kitty tests failed')
|
||||
|
||||
|
||||
if __name__ == 'program':
|
||||
kitty_constants = initialize_constants()
|
||||
3
bypy/linux.conf
Normal file
3
bypy/linux.conf
Normal file
@@ -0,0 +1,3 @@
|
||||
image 'https://partner-images.canonical.com/core/xenial/current/ubuntu-xenial-core-cloudimg-{}-root.tar.gz'
|
||||
|
||||
deps 'bison flex libxcursor-dev libxrandr-dev libxi-dev libxinerama-dev libgl1-mesa-dev libxcb-xkb-dev libfontconfig1-dev libdbus-1-dev'
|
||||
209
bypy/linux/__main__.py
Normal file
209
bypy/linux/__main__.py
Normal file
@@ -0,0 +1,209 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import errno
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import tarfile
|
||||
import time
|
||||
|
||||
from bypy.constants import (
|
||||
OUTPUT_DIR, PREFIX, is64bit, python_major_minor_version
|
||||
)
|
||||
from bypy.utils import get_dll_path, py_compile, walk
|
||||
|
||||
j = os.path.join
|
||||
self_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
arch = 'x86_64' if is64bit else 'i686'
|
||||
|
||||
py_ver = '.'.join(map(str, python_major_minor_version()))
|
||||
iv = globals()['init_env']
|
||||
kitty_constants = iv['kitty_constants']
|
||||
|
||||
|
||||
def binary_includes():
|
||||
return tuple(map(get_dll_path, (
|
||||
'expat', 'sqlite3', 'ffi', 'z', 'lzma', 'png16',
|
||||
|
||||
# dont include freetype because fontconfig is closely coupled to it
|
||||
# and also distros often patch freetype
|
||||
# 'iconv.so.2', 'pcre.so.1', 'graphite2.so.3', 'glib-2.0.so.0', 'freetype.so.6',
|
||||
|
||||
'harfbuzz', 'xkbcommon', 'xkbcommon-x11',
|
||||
'ncursesw', 'readline',
|
||||
))) + (
|
||||
get_dll_path('bz2', 2), get_dll_path('ssl', 3), get_dll_path('crypto', 3),
|
||||
get_dll_path('python' + py_ver, 2),
|
||||
)
|
||||
|
||||
|
||||
class Env:
|
||||
|
||||
def __init__(self, package_dir):
|
||||
self.base = package_dir
|
||||
self.lib_dir = j(self.base, 'lib')
|
||||
self.py_dir = j(self.lib_dir, 'python' + py_ver)
|
||||
os.makedirs(self.py_dir)
|
||||
self.bin_dir = j(self.base, 'bin')
|
||||
|
||||
|
||||
def ignore_in_lib(base, items, ignored_dirs=None):
|
||||
ans = []
|
||||
if ignored_dirs is None:
|
||||
ignored_dirs = {'.svn', '.bzr', '.git', 'test', 'tests', 'testing'}
|
||||
for name in items:
|
||||
path = j(base, name)
|
||||
if os.path.isdir(path):
|
||||
if name in ignored_dirs or not os.path.exists(j(path, '__init__.py')):
|
||||
if name != 'plugins':
|
||||
ans.append(name)
|
||||
else:
|
||||
if name.rpartition('.')[-1] not in ('so', 'py'):
|
||||
ans.append(name)
|
||||
return ans
|
||||
|
||||
|
||||
def import_site_packages(srcdir, dest):
|
||||
if not os.path.exists(dest):
|
||||
os.mkdir(dest)
|
||||
for x in os.listdir(srcdir):
|
||||
ext = x.rpartition('.')[-1]
|
||||
f = j(srcdir, x)
|
||||
if ext in ('py', 'so'):
|
||||
shutil.copy2(f, dest)
|
||||
elif ext == 'pth' and x != 'setuptools.pth':
|
||||
for line in open(f, 'rb').read().splitlines():
|
||||
src = os.path.abspath(j(srcdir, line))
|
||||
if os.path.exists(src) and os.path.isdir(src):
|
||||
import_site_packages(src, dest)
|
||||
elif os.path.exists(j(f, '__init__.py')):
|
||||
shutil.copytree(f, j(dest, x), ignore=ignore_in_lib)
|
||||
|
||||
|
||||
def copy_libs(env):
|
||||
print('Copying libs...')
|
||||
|
||||
for x in binary_includes():
|
||||
dest = env.bin_dir if '/bin/' in x else env.lib_dir
|
||||
shutil.copy2(x, dest)
|
||||
|
||||
|
||||
def copy_python(env):
|
||||
print('Copying python...')
|
||||
srcdir = j(PREFIX, 'lib/python' + py_ver)
|
||||
|
||||
for x in os.listdir(srcdir):
|
||||
y = j(srcdir, x)
|
||||
ext = os.path.splitext(x)[1]
|
||||
if os.path.isdir(y) and x not in ('test', 'hotshot', 'distutils', 'tkinter', 'turtledemo',
|
||||
'site-packages', 'idlelib', 'lib2to3', 'dist-packages'):
|
||||
shutil.copytree(y, j(env.py_dir, x), ignore=ignore_in_lib)
|
||||
if os.path.isfile(y) and ext in ('.py', '.so'):
|
||||
shutil.copy2(y, env.py_dir)
|
||||
|
||||
srcdir = j(srcdir, 'site-packages')
|
||||
dest = j(env.py_dir, 'site-packages')
|
||||
import_site_packages(srcdir, dest)
|
||||
py_compile(env.py_dir)
|
||||
py_compile(os.path.join(env.base, 'lib', 'kitty'))
|
||||
|
||||
|
||||
def is_elf(path):
|
||||
with open(path, 'rb') as f:
|
||||
return f.read(4) == b'\x7fELF'
|
||||
|
||||
|
||||
def fix_permissions(files):
|
||||
for path in files:
|
||||
os.chmod(path, 0o755)
|
||||
|
||||
|
||||
STRIPCMD = ['strip']
|
||||
|
||||
|
||||
def find_binaries(env):
|
||||
files = {j(env.bin_dir, x) for x in os.listdir(env.bin_dir)} | {
|
||||
x for x in {
|
||||
j(os.path.dirname(env.bin_dir), x) for x in os.listdir(env.bin_dir)} if os.path.exists(x)}
|
||||
for x in walk(env.lib_dir):
|
||||
x = os.path.realpath(x)
|
||||
if x not in files and is_elf(x):
|
||||
files.add(x)
|
||||
return files
|
||||
|
||||
|
||||
def strip_files(files, argv_max=(256 * 1024)):
|
||||
""" Strip a list of files """
|
||||
while files:
|
||||
cmd = list(STRIPCMD)
|
||||
pathlen = sum(len(s) + 1 for s in cmd)
|
||||
while pathlen < argv_max and files:
|
||||
f = files.pop()
|
||||
cmd.append(f)
|
||||
pathlen += len(f) + 1
|
||||
if len(cmd) > len(STRIPCMD):
|
||||
all_files = cmd[len(STRIPCMD):]
|
||||
unwritable_files = tuple(filter(None, (None if os.access(x, os.W_OK) else (x, os.stat(x).st_mode) for x in all_files)))
|
||||
[os.chmod(x, stat.S_IWRITE | old_mode) for x, old_mode in unwritable_files]
|
||||
subprocess.check_call(cmd)
|
||||
[os.chmod(x, old_mode) for x, old_mode in unwritable_files]
|
||||
|
||||
|
||||
def strip_binaries(files):
|
||||
print('Stripping %d files...' % len(files))
|
||||
before = sum(os.path.getsize(x) for x in files)
|
||||
strip_files(files)
|
||||
after = sum(os.path.getsize(x) for x in files)
|
||||
print('Stripped %.1f MB' % ((before - after) / (1024 * 1024.)))
|
||||
|
||||
|
||||
def create_tarfile(env, compression_level='9'):
|
||||
print('Creating archive...')
|
||||
base = OUTPUT_DIR
|
||||
try:
|
||||
shutil.rmtree(base)
|
||||
except EnvironmentError as err:
|
||||
if err.errno != errno.ENOENT:
|
||||
raise
|
||||
os.mkdir(base)
|
||||
dist = os.path.join(base, '%s-%s-%s.tar' % (kitty_constants['appname'], kitty_constants['version'], arch))
|
||||
with tarfile.open(dist, mode='w', format=tarfile.PAX_FORMAT) as tf:
|
||||
cwd = os.getcwd()
|
||||
os.chdir(env.base)
|
||||
try:
|
||||
for x in os.listdir('.'):
|
||||
tf.add(x)
|
||||
finally:
|
||||
os.chdir(cwd)
|
||||
print('Compressing archive...')
|
||||
ans = dist.rpartition('.')[0] + '.txz'
|
||||
start_time = time.time()
|
||||
subprocess.check_call(['xz', '--threads=0', '-f', '-' + compression_level, dist])
|
||||
secs = time.time() - start_time
|
||||
print('Compressed in %d minutes %d seconds' % (secs // 60, secs % 60))
|
||||
os.rename(dist + '.xz', ans)
|
||||
print('Archive %s created: %.2f MB' % (
|
||||
os.path.basename(ans), os.stat(ans).st_size / (1024.**2)))
|
||||
|
||||
|
||||
def main():
|
||||
args = globals()['args']
|
||||
ext_dir = globals()['ext_dir']
|
||||
if not args.skip_tests:
|
||||
run_tests = iv['run_tests']
|
||||
run_tests(None, os.path.join(ext_dir, 'src'))
|
||||
env = Env(os.path.join(ext_dir, kitty_constants['appname']))
|
||||
copy_libs(env)
|
||||
copy_python(env)
|
||||
files = find_binaries(env)
|
||||
fix_permissions(files)
|
||||
if not args.dont_strip:
|
||||
strip_binaries(files)
|
||||
create_tarfile(env, args.compression_level)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
6
bypy/macos.conf
Normal file
6
bypy/macos.conf
Normal file
@@ -0,0 +1,6 @@
|
||||
# Requires installation of XCode 10.3 and Python 3 and
|
||||
# python3 -m pip install certifi
|
||||
|
||||
vm_name 'macos-kitty-build'
|
||||
root '/Users/Shared/kitty-build'
|
||||
python '/usr/local/bin/python3'
|
||||
459
bypy/macos/__main__.py
Normal file
459
bypy/macos/__main__.py
Normal file
@@ -0,0 +1,459 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import zipfile
|
||||
|
||||
from bypy.constants import PREFIX, PYTHON, SW, python_major_minor_version
|
||||
from bypy.macos_sign import (
|
||||
codesign, create_entitlements_file, make_certificate_useable, notarize_app,
|
||||
verify_signature
|
||||
)
|
||||
from bypy.utils import current_dir, py_compile, run_shell, timeit, walk
|
||||
|
||||
iv = globals()['init_env']
|
||||
kitty_constants = iv['kitty_constants']
|
||||
join = os.path.join
|
||||
basename = os.path.basename
|
||||
abspath = os.path.abspath
|
||||
APPNAME = kitty_constants['appname']
|
||||
VERSION = kitty_constants['version']
|
||||
py_ver = '.'.join(map(str, python_major_minor_version()))
|
||||
|
||||
|
||||
def flush(func):
|
||||
def ff(*args, **kwargs):
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
ret = func(*args, **kwargs)
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
return ret
|
||||
|
||||
return ff
|
||||
|
||||
|
||||
def flipwritable(fn, mode=None):
|
||||
"""
|
||||
Flip the writability of a file and return the old mode. Returns None
|
||||
if the file is already writable.
|
||||
"""
|
||||
if os.access(fn, os.W_OK):
|
||||
return None
|
||||
old_mode = os.stat(fn).st_mode
|
||||
os.chmod(fn, stat.S_IWRITE | old_mode)
|
||||
return old_mode
|
||||
|
||||
|
||||
STRIPCMD = ('/usr/bin/strip', '-x', '-S', '-')
|
||||
|
||||
|
||||
def strip_files(files, argv_max=(256 * 1024)):
|
||||
"""
|
||||
Strip a list of files
|
||||
"""
|
||||
tostrip = [(fn, flipwritable(fn)) for fn in files if os.path.exists(fn)]
|
||||
while tostrip:
|
||||
cmd = list(STRIPCMD)
|
||||
flips = []
|
||||
pathlen = sum(len(s) + 1 for s in cmd)
|
||||
while pathlen < argv_max:
|
||||
if not tostrip:
|
||||
break
|
||||
added, flip = tostrip.pop()
|
||||
pathlen += len(added) + 1
|
||||
cmd.append(added)
|
||||
flips.append((added, flip))
|
||||
else:
|
||||
cmd.pop()
|
||||
tostrip.append(flips.pop())
|
||||
os.spawnv(os.P_WAIT, cmd[0], cmd)
|
||||
for args in flips:
|
||||
flipwritable(*args)
|
||||
|
||||
|
||||
def files_in(folder):
|
||||
for record in os.walk(folder):
|
||||
for f in record[-1]:
|
||||
yield os.path.join(record[0], f)
|
||||
|
||||
|
||||
def expand_dirs(items, exclude=lambda x: x.endswith('.so')):
|
||||
items = set(items)
|
||||
dirs = set(x for x in items if os.path.isdir(x))
|
||||
items.difference_update(dirs)
|
||||
for x in dirs:
|
||||
items.update({y for y in files_in(x) if not exclude(y)})
|
||||
return items
|
||||
|
||||
|
||||
def do_sign(app_dir):
|
||||
with current_dir(os.path.join(app_dir, 'Contents')):
|
||||
# Sign all .so files
|
||||
so_files = {x for x in files_in('.') if x.endswith('.so')}
|
||||
codesign(so_files)
|
||||
# Sign everything else in Frameworks
|
||||
with current_dir('Frameworks'):
|
||||
fw = set(glob.glob('*.framework'))
|
||||
codesign(fw)
|
||||
items = set(os.listdir('.')) - fw
|
||||
codesign(expand_dirs(items))
|
||||
|
||||
# Now sign the main app
|
||||
codesign(app_dir)
|
||||
verify_signature(app_dir)
|
||||
|
||||
|
||||
def sign_app(app_dir, notarize):
|
||||
# Copied from iTerm2: https://github.com/gnachman/iTerm2/blob/master/iTerm2.entitlements
|
||||
create_entitlements_file({
|
||||
'com.apple.security.automation.apple-events': True,
|
||||
'com.apple.security.cs.allow-jit': True,
|
||||
'com.apple.security.device.audio-input': True,
|
||||
'com.apple.security.device.camera': True,
|
||||
'com.apple.security.personal-information.addressbook': True,
|
||||
'com.apple.security.personal-information.calendars': True,
|
||||
'com.apple.security.personal-information.location': True,
|
||||
'com.apple.security.personal-information.photos-library': True,
|
||||
})
|
||||
with make_certificate_useable():
|
||||
do_sign(app_dir)
|
||||
if notarize:
|
||||
notarize_app(app_dir)
|
||||
|
||||
|
||||
class Freeze(object):
|
||||
|
||||
FID = '@executable_path/../Frameworks'
|
||||
|
||||
def __init__(self, build_dir, dont_strip=False, sign_installers=False, notarize=False):
|
||||
self.build_dir = build_dir
|
||||
self.sign_installers = sign_installers
|
||||
self.notarize = notarize
|
||||
self.dont_strip = dont_strip
|
||||
self.contents_dir = join(self.build_dir, 'Contents')
|
||||
self.resources_dir = join(self.contents_dir, 'Resources')
|
||||
self.frameworks_dir = join(self.contents_dir, 'Frameworks')
|
||||
self.to_strip = []
|
||||
self.warnings = []
|
||||
self.py_ver = py_ver
|
||||
self.python_stdlib = join(self.resources_dir, 'Python', 'lib', 'python' + self.py_ver)
|
||||
self.site_packages = self.python_stdlib # hack to avoid needing to add site-packages to path
|
||||
|
||||
self.run()
|
||||
|
||||
def run_shell(self):
|
||||
with current_dir(self.contents_dir):
|
||||
run_shell()
|
||||
|
||||
def run(self):
|
||||
ret = 0
|
||||
self.add_python_framework()
|
||||
self.add_site_packages()
|
||||
self.add_stdlib()
|
||||
self.add_misc_libraries()
|
||||
self.compile_py_modules()
|
||||
self.fix_dependencies_in_kitty()
|
||||
if not self.dont_strip:
|
||||
self.strip_files()
|
||||
# self.run_shell()
|
||||
|
||||
ret = self.makedmg(self.build_dir, APPNAME + '-' + VERSION)
|
||||
|
||||
return ret
|
||||
|
||||
@flush
|
||||
def strip_files(self):
|
||||
print('\nStripping files...')
|
||||
strip_files(self.to_strip)
|
||||
|
||||
@flush
|
||||
def set_id(self, path_to_lib, new_id):
|
||||
old_mode = flipwritable(path_to_lib)
|
||||
subprocess.check_call(
|
||||
['install_name_tool', '-id', new_id, path_to_lib])
|
||||
if old_mode is not None:
|
||||
flipwritable(path_to_lib, old_mode)
|
||||
|
||||
@flush
|
||||
def get_dependencies(self, path_to_lib):
|
||||
install_name = subprocess.check_output(
|
||||
['otool', '-D', path_to_lib]).decode('utf-8').splitlines()[-1].strip()
|
||||
raw = subprocess.check_output(['otool', '-L', path_to_lib]).decode('utf-8')
|
||||
for line in raw.splitlines():
|
||||
if 'compatibility' not in line or line.strip().endswith(':'):
|
||||
continue
|
||||
idx = line.find('(')
|
||||
path = line[:idx].strip()
|
||||
yield path, path == install_name
|
||||
|
||||
@flush
|
||||
def get_local_dependencies(self, path_to_lib):
|
||||
for x, is_id in self.get_dependencies(path_to_lib):
|
||||
for y in (PREFIX + '/lib/', PREFIX + '/python/Python.framework/'):
|
||||
if x.startswith(y):
|
||||
if y == PREFIX + '/python/Python.framework/':
|
||||
y = PREFIX + '/python/'
|
||||
yield x, x[len(y):], is_id
|
||||
break
|
||||
|
||||
@flush
|
||||
def change_dep(self, old_dep, new_dep, is_id, path_to_lib):
|
||||
cmd = ['-id', new_dep] if is_id else ['-change', old_dep, new_dep]
|
||||
subprocess.check_call(['install_name_tool'] + cmd + [path_to_lib])
|
||||
|
||||
@flush
|
||||
def fix_dependencies_in_lib(self, path_to_lib):
|
||||
self.to_strip.append(path_to_lib)
|
||||
old_mode = flipwritable(path_to_lib)
|
||||
for dep, bname, is_id in self.get_local_dependencies(path_to_lib):
|
||||
ndep = self.FID + '/' + bname
|
||||
self.change_dep(dep, ndep, is_id, path_to_lib)
|
||||
ldeps = list(self.get_local_dependencies(path_to_lib))
|
||||
if ldeps:
|
||||
print('\nFailed to fix dependencies in', path_to_lib)
|
||||
print('Remaining local dependencies:', ldeps)
|
||||
raise SystemExit(1)
|
||||
if old_mode is not None:
|
||||
flipwritable(path_to_lib, old_mode)
|
||||
|
||||
@flush
|
||||
def add_python_framework(self):
|
||||
print('\nAdding Python framework')
|
||||
src = join(PREFIX + '/python', 'Python.framework')
|
||||
x = join(self.frameworks_dir, 'Python.framework')
|
||||
curr = os.path.realpath(join(src, 'Versions', 'Current'))
|
||||
currd = join(x, 'Versions', basename(curr))
|
||||
rd = join(currd, 'Resources')
|
||||
os.makedirs(rd)
|
||||
shutil.copy2(join(curr, 'Resources', 'Info.plist'), rd)
|
||||
shutil.copy2(join(curr, 'Python'), currd)
|
||||
self.set_id(
|
||||
join(currd, 'Python'),
|
||||
self.FID + '/Python.framework/Versions/%s/Python' % basename(curr))
|
||||
self.fix_dependencies_in_lib(join(self.contents_dir, 'MacOS', 'kitty'))
|
||||
# The following is needed for codesign
|
||||
with current_dir(x):
|
||||
os.symlink(basename(curr), 'Versions/Current')
|
||||
for y in ('Python', 'Resources'):
|
||||
os.symlink('Versions/Current/%s' % y, y)
|
||||
|
||||
@flush
|
||||
def install_dylib(self, path, set_id=True):
|
||||
shutil.copy2(path, self.frameworks_dir)
|
||||
if set_id:
|
||||
self.set_id(
|
||||
join(self.frameworks_dir, basename(path)),
|
||||
self.FID + '/' + basename(path))
|
||||
self.fix_dependencies_in_lib(join(self.frameworks_dir, basename(path)))
|
||||
|
||||
@flush
|
||||
def add_misc_libraries(self):
|
||||
for x in (
|
||||
'sqlite3.0',
|
||||
'z.1',
|
||||
'harfbuzz.0',
|
||||
'png16.16',
|
||||
'crypto.1.0.0',
|
||||
'ssl.1.0.0',
|
||||
):
|
||||
print('\nAdding', x)
|
||||
x = 'lib%s.dylib' % x
|
||||
src = join(PREFIX, 'lib', x)
|
||||
shutil.copy2(src, self.frameworks_dir)
|
||||
dest = join(self.frameworks_dir, x)
|
||||
self.set_id(dest, self.FID + '/' + x)
|
||||
self.fix_dependencies_in_lib(dest)
|
||||
base = join(self.frameworks_dir, 'kitty')
|
||||
for lib in walk(base):
|
||||
if lib.endswith('.so'):
|
||||
self.set_id(lib, self.FID + '/' + os.path.relpath(lib, self.frameworks_dir))
|
||||
self.fix_dependencies_in_lib(lib)
|
||||
|
||||
@flush
|
||||
def add_package_dir(self, x, dest=None):
|
||||
def ignore(root, files):
|
||||
ans = []
|
||||
for y in files:
|
||||
ext = os.path.splitext(y)[1]
|
||||
if ext not in ('', '.py', '.so') or \
|
||||
(not ext and not os.path.isdir(join(root, y))):
|
||||
ans.append(y)
|
||||
|
||||
return ans
|
||||
|
||||
if dest is None:
|
||||
dest = self.site_packages
|
||||
dest = join(dest, basename(x))
|
||||
shutil.copytree(x, dest, symlinks=True, ignore=ignore)
|
||||
self.postprocess_package(x, dest)
|
||||
for f in walk(dest):
|
||||
if f.endswith('.so'):
|
||||
self.fix_dependencies_in_lib(f)
|
||||
|
||||
@flush
|
||||
def fix_dependencies_in_kitty(self):
|
||||
for f in walk(join(self.resources_dir, 'kitty')):
|
||||
if f.endswith('.so'):
|
||||
self.fix_dependencies_in_lib(f)
|
||||
|
||||
@flush
|
||||
def postprocess_package(self, src_path, dest_path):
|
||||
pass
|
||||
|
||||
@flush
|
||||
def add_stdlib(self):
|
||||
print('\nAdding python stdlib')
|
||||
src = PREFIX + '/python/Python.framework/Versions/Current/lib/python' + self.py_ver
|
||||
dest = self.python_stdlib
|
||||
if not os.path.exists(dest):
|
||||
os.makedirs(dest)
|
||||
for x in os.listdir(src):
|
||||
if x in ('site-packages', 'config', 'test', 'lib2to3', 'lib-tk',
|
||||
'lib-old', 'idlelib', 'plat-mac', 'plat-darwin',
|
||||
'site.py', 'distutils', 'turtledemo', 'tkinter'):
|
||||
continue
|
||||
x = join(src, x)
|
||||
if os.path.isdir(x):
|
||||
self.add_package_dir(x, dest)
|
||||
elif os.path.splitext(x)[1] in ('.so', '.py'):
|
||||
shutil.copy2(x, dest)
|
||||
dest2 = join(dest, basename(x))
|
||||
if dest2.endswith('.so'):
|
||||
self.fix_dependencies_in_lib(dest2)
|
||||
|
||||
@flush
|
||||
def add_site_packages(self):
|
||||
print('\nAdding site-packages')
|
||||
os.makedirs(self.site_packages)
|
||||
sys_path = json.loads(subprocess.check_output([
|
||||
PYTHON, '-c', 'import sys, json; json.dump(sys.path, sys.stdout)']))
|
||||
paths = reversed(tuple(map(abspath, [x for x in sys_path if x.startswith('/') and not x.startswith('/Library/')])))
|
||||
upaths = []
|
||||
for x in paths:
|
||||
if x not in upaths and (x.endswith('.egg') or x.endswith('/site-packages')):
|
||||
upaths.append(x)
|
||||
for x in upaths:
|
||||
print('\t', x)
|
||||
tdir = None
|
||||
try:
|
||||
if not os.path.isdir(x):
|
||||
zf = zipfile.ZipFile(x)
|
||||
tdir = tempfile.mkdtemp()
|
||||
zf.extractall(tdir)
|
||||
x = tdir
|
||||
self.add_modules_from_dir(x)
|
||||
self.add_packages_from_dir(x)
|
||||
finally:
|
||||
if tdir is not None:
|
||||
shutil.rmtree(tdir)
|
||||
self.remove_bytecode(self.site_packages)
|
||||
|
||||
@flush
|
||||
def add_modules_from_dir(self, src):
|
||||
for x in glob.glob(join(src, '*.py')) + glob.glob(join(src, '*.so')):
|
||||
shutil.copy2(x, self.site_packages)
|
||||
if x.endswith('.so'):
|
||||
self.fix_dependencies_in_lib(x)
|
||||
|
||||
@flush
|
||||
def add_packages_from_dir(self, src):
|
||||
for x in os.listdir(src):
|
||||
x = join(src, x)
|
||||
if os.path.isdir(x) and os.path.exists(join(x, '__init__.py')):
|
||||
if self.filter_package(basename(x)):
|
||||
continue
|
||||
self.add_package_dir(x)
|
||||
|
||||
@flush
|
||||
def filter_package(self, name):
|
||||
return name in ('Cython', 'modulegraph', 'macholib', 'py2app',
|
||||
'bdist_mpkg', 'altgraph')
|
||||
|
||||
@flush
|
||||
def remove_bytecode(self, dest):
|
||||
for x in os.walk(dest):
|
||||
root = x[0]
|
||||
for f in x[-1]:
|
||||
if os.path.splitext(f) == '.pyc':
|
||||
os.remove(join(root, f))
|
||||
|
||||
@flush
|
||||
def compile_py_modules(self):
|
||||
print('\nCompiling Python modules')
|
||||
self.remove_bytecode(join(self.resources_dir, 'Python'))
|
||||
py_compile(join(self.resources_dir, 'Python'))
|
||||
self.remove_bytecode(join(self.resources_dir, 'kitty'))
|
||||
py_compile(join(self.resources_dir, 'kitty'))
|
||||
|
||||
@flush
|
||||
def makedmg(self, d, volname, internet_enable=True, format='ULFO'):
|
||||
''' Copy a directory d into a dmg named volname '''
|
||||
print('\nMaking dmg...')
|
||||
sys.stdout.flush()
|
||||
destdir = os.path.join(SW, 'dist')
|
||||
try:
|
||||
shutil.rmtree(destdir)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
os.mkdir(destdir)
|
||||
dmg = os.path.join(destdir, volname + '.dmg')
|
||||
if os.path.exists(dmg):
|
||||
os.unlink(dmg)
|
||||
tdir = tempfile.mkdtemp()
|
||||
appdir = os.path.join(tdir, os.path.basename(d))
|
||||
shutil.copytree(d, appdir, symlinks=True)
|
||||
if self.sign_installers:
|
||||
with timeit() as times:
|
||||
sign_app(appdir, self.notarize)
|
||||
print('Signing completed in %d minutes %d seconds' % tuple(times))
|
||||
os.symlink('/Applications', os.path.join(tdir, 'Applications'))
|
||||
size_in_mb = int(
|
||||
subprocess.check_output(['du', '-s', '-k', tdir]).decode('utf-8')
|
||||
.split()[0]) / 1024.
|
||||
cmd = [
|
||||
'/usr/bin/hdiutil', 'create', '-srcfolder', tdir, '-volname',
|
||||
volname, '-format', format
|
||||
]
|
||||
if 190 < size_in_mb < 250:
|
||||
# We need -size 255m because of a bug in hdiutil. When the size of
|
||||
# srcfolder is close to 200MB hdiutil fails with
|
||||
# diskimages-helper: resize request is above maximum size allowed.
|
||||
cmd += ['-size', '255m']
|
||||
print('\nCreating dmg...')
|
||||
with timeit() as times:
|
||||
subprocess.check_call(cmd + [dmg])
|
||||
if internet_enable:
|
||||
subprocess.check_call(
|
||||
['/usr/bin/hdiutil', 'internet-enable', '-yes', dmg])
|
||||
print('dmg created in %d minutes and %d seconds' % tuple(times))
|
||||
shutil.rmtree(tdir)
|
||||
size = os.stat(dmg).st_size / (1024 * 1024.)
|
||||
print('\nInstaller size: %.2fMB\n' % size)
|
||||
return dmg
|
||||
|
||||
|
||||
def main():
|
||||
args = globals()['args']
|
||||
ext_dir = globals()['ext_dir']
|
||||
if not args.skip_tests:
|
||||
run_tests = iv['run_tests']
|
||||
run_tests(None, os.path.join(ext_dir, 'src'))
|
||||
Freeze(
|
||||
os.path.join(ext_dir, kitty_constants['appname'] + '.app'),
|
||||
dont_strip=args.dont_strip,
|
||||
sign_installers=args.sign_installers,
|
||||
notarize=args.notarize
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
1
bypy/rsync.conf
Normal file
1
bypy/rsync.conf
Normal file
@@ -0,0 +1 @@
|
||||
to_vm_excludes '/build /dist /kitty/launcher/kitty /.build-cache /tags __pycache__ /*_commands.json *.so *.pyd *.pyc'
|
||||
256
bypy/sources.json
Normal file
256
bypy/sources.json
Normal file
@@ -0,0 +1,256 @@
|
||||
[
|
||||
{
|
||||
"name": "zlib",
|
||||
"unix": {
|
||||
"filename": "zlib-1.2.11.tar.xz",
|
||||
"hash": "sha256:4ff941449631ace0d4d203e3483be9dbc9da454084111f97ea0a2114e19bf066",
|
||||
"urls": ["https://zlib.net/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "bzip2",
|
||||
"os": "linux",
|
||||
"unix": {
|
||||
"filename": "bzip2-1.0.8.tar.gz",
|
||||
"hash": "sha256:ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269",
|
||||
"urls": ["https://www.sourceware.org/pub/bzip2/bzip2-latest.tar.gz"]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "pkg-config",
|
||||
"os": "macos",
|
||||
"unix": {
|
||||
"filename": "pkg-config-0.29.2.tar.gz",
|
||||
"hash": "sha256:6fc69c01688c9458a57eb9a1664c9aba372ccda420a02bf4429fe610e7e7d591",
|
||||
"urls": ["https://pkg-config.freedesktop.org/releases/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"name": "expat",
|
||||
"unix": {
|
||||
"filename": "expat-2.2.6.tar.bz2",
|
||||
"hash": "sha256:17b43c2716d521369f82fc2dc70f359860e90fa440bea65b3b85f0b246ea81f2",
|
||||
"urls": ["https://github.com/libexpat/libexpat/releases/download/R_2_2_6/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"name": "xkbcommon",
|
||||
"os": "linux",
|
||||
"unix": {
|
||||
"filename": "libxkbcommon-0.10.0.tar.xz",
|
||||
"hash": "sha256:57c3630cdc38fb4734cd57fa349e92244f5ae3862813e533cedbd86721a0b6f2",
|
||||
"urls": ["http://xkbcommon.org/download/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "sqlite",
|
||||
"unix": {
|
||||
"filename": "sqlite-autoconf-3310100.tar.gz",
|
||||
"hash": "sha1:0c30f5b22152a8166aa3bebb0f4bc1f3e9cc508b",
|
||||
"urls": ["https://www.sqlite.org/2020/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"name": "libffi",
|
||||
"unix": {
|
||||
"filename": "libffi-3.2.1.tar.gz",
|
||||
"hash": "sha1:280c265b789e041c02e5c97815793dfc283fb1e6",
|
||||
"urls": ["ftp://sourceware.org/pub/libffi/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "openssl",
|
||||
"unix": {
|
||||
"filename": "openssl-1.0.2u.tar.gz",
|
||||
"hash": "sha256:ecd0c6ffb493dd06707d38b14bb4d8c2288bb7033735606569d8f90f89669d16",
|
||||
"urls": ["https://www.openssl.org/source/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "ncurses",
|
||||
"os": "linux",
|
||||
"unix": {
|
||||
"filename": "ncurses-6.2.tar.gz",
|
||||
"hash": "sha256:30306e0c76e0f9f1f0de987cf1c82a5c21e1ce6568b9227f7da5b71cbea86c9d",
|
||||
"urls": ["https://invisible-mirror.net/archives/ncurses/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "readline",
|
||||
"os": "linux",
|
||||
"unix": {
|
||||
"filename": "readline-8.0.tar.gz",
|
||||
"hash": "sha256:e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461",
|
||||
"urls": ["http://ftp.gnu.org/gnu/readline/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "xz",
|
||||
"os": "macos,linux",
|
||||
"unix": {
|
||||
"filename": "xz-5.2.5.tar.gz",
|
||||
"hash": "sha256:f6f4910fd033078738bd82bfba4f49219d03b17eb0794eb91efbae419f4aba10",
|
||||
"urls": ["https://tukaani.org/xz/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "python",
|
||||
"unix": {
|
||||
"filename": "Python-3.8.3.tar.xz",
|
||||
"hash": "sha256:dfab5ec723c218082fe3d5d7ae17ecbdebffa9a1aea4d64aa3a2ecdd2e795864",
|
||||
"urls": ["https://www.python.org/ftp/python/3.8.3/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "setuptools",
|
||||
"unix": {
|
||||
"filename": "setuptools-41.0.1.zip",
|
||||
"hash": "sha256:a222d126f5471598053c9a77f4b5d4f26eaa1f150ad6e01dcf1a42e185d05613",
|
||||
"urls": ["pypi"]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "pygments",
|
||||
"unix": {
|
||||
"filename": "Pygments-2.6.1.tar.gz",
|
||||
"hash": "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44",
|
||||
"urls": ["pypi"]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "cmake",
|
||||
"os": "macos",
|
||||
"unix": {
|
||||
"filename": "cmake-3.6.0.tar.gz",
|
||||
"hash": "sha1:6b0d2d7d93c2e74f8890c4b8b824deb3b36d24b6",
|
||||
"urls": ["https://cmake.org/files/v3.6/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "libpng",
|
||||
"unix": {
|
||||
"filename": "libpng-1.6.37.tar.xz",
|
||||
"hash": "sha256:505e70834d35383537b6491e7ae8641f1a4bed1876dbfe361201fc80868d88ca",
|
||||
"urls": ["http://downloads.sourceforge.net/sourceforge/libpng/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "graphite",
|
||||
"os": "linux",
|
||||
"unix": {
|
||||
"filename": "graphite2-1.3.5.tgz",
|
||||
"hash": "md5:5b8d22a8bbf031838e31432868c0109c",
|
||||
"urls": ["http://downloads.sourceforge.net/silgraphite/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "pcre",
|
||||
"os": "linux",
|
||||
"unix": {
|
||||
"filename": "pcre-8.42.tar.bz2",
|
||||
"hash": "md5:085b6aa253e0f91cae70b3cdbe8c1ac2",
|
||||
"urls": ["https://ftp.pcre.org/pub/pcre/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "iconv",
|
||||
"os": "linux",
|
||||
"unix": {
|
||||
"filename": "libiconv-1.16.tar.gz",
|
||||
"hash": "sha256:e6a1b1b589654277ee790cce3734f07876ac4ccfaecbee8afa0b649cf529cc04",
|
||||
"urls": ["http://ftp.gnu.org/pub/gnu/libiconv/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "glib",
|
||||
"os": "linux",
|
||||
"unix": {
|
||||
"filename": "glib-2.57.1.tar.xz",
|
||||
"hash": "sha256:d029e7c4536835f1f103472f7510332c28d58b9b7d6cd0e9f45c2653e670d9b4",
|
||||
"urls": ["http://ftp.gnome.org/pub/GNOME/sources/glib/2.57/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
"name": "freetype",
|
||||
"os": "linux",
|
||||
"unix": {
|
||||
"filename": "freetype-2.9.1.tar.bz2",
|
||||
"hash": "md5:60ef7d8160cd4bf8cb118ee9d65367ca",
|
||||
"urls": ["https://download.savannah.gnu.org/releases/freetype/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "fontconfig",
|
||||
"os": "linux",
|
||||
"unix": {
|
||||
"filename": "fontconfig-2.12.0.tar.bz2",
|
||||
"hash": "sha256:b433e4efff1f68fdd8aac221ed1df3ff1e580ffedbada020a703fe64017d8224",
|
||||
"urls": ["http://www.fontconfig.org/release/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "harfbuzz",
|
||||
"unix": {
|
||||
"filename": "harfbuzz-1.7.2.tar.bz2",
|
||||
"hash": "sha256:a790585e35c1a87f0dcc23580c84b7cc2324e6f67a2946178d278c2a36c790cb",
|
||||
"urls": ["https://www.freedesktop.org/software/harfbuzz/release/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "libxml2",
|
||||
"os": "linux",
|
||||
"unix": {
|
||||
"filename": "libxml2-2.9.4.tar.gz",
|
||||
"hash": "sha1:958ae70baf186263a4bd801a81dd5d682aedd1db",
|
||||
"urls": ["ftp://xmlsoft.org/libxml2/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "wayland",
|
||||
"os": "linux",
|
||||
"unix": {
|
||||
"filename": "wayland-1.18.0.tar.xz",
|
||||
"hash": "sha256:4675a79f091020817a98fd0484e7208c8762242266967f55a67776936c2e294d",
|
||||
"urls": ["https://wayland.freedesktop.org/releases/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "wayland-protocols",
|
||||
"os": "linux",
|
||||
"unix": {
|
||||
"filename": "wayland-protocols-1.20.tar.xz",
|
||||
"hash": "sha256:9782b7a1a863d82d7c92478497d13c758f52e7da4f197aa16443f73de77e4de7",
|
||||
"urls": ["https://wayland.freedesktop.org/releases/{filename}"]
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
@@ -46,7 +46,7 @@ particular desktop, but it should work for most major desktop environments.
|
||||
# Place the kitty.desktop file somewhere it can be found by the OS
|
||||
cp ~/.local/kitty.app/share/applications/kitty.desktop ~/.local/share/applications
|
||||
# Update the path to the kitty icon in the kitty.desktop file
|
||||
sed -i "s/Icon\=kitty/Icon\=\/home\/$USER\/.local\/kitty.app\/share\/icons\/hicolor\/256x256\/apps\/kitty.png/g" ~/.local/share/applications/kitty.desktop
|
||||
sed -i "s|Icon=kitty|Icon=/home/$USER/.local/kitty.app/share/icons/hicolor/256x256/apps/kitty.png|g" ~/.local/share/applications/kitty.desktop
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
Building kitty from source
|
||||
==============================
|
||||
|
||||
.. image:: https://circleci.com/gh/kovidgoyal/kitty.svg?style=svg
|
||||
.. image:: https://github.com/kovidgoyal/kitty/workflows/CI/badge.svg
|
||||
:alt: Build status
|
||||
:target: https://circleci.com/gh/kovidgoyal/kitty
|
||||
:target: https://github.com/kovidgoyal/kitty/actions?query=workflow%3ACI
|
||||
|
||||
|
||||
|kitty| is designed to run from source, for easy hackability. Make sure
|
||||
|
||||
@@ -4,6 +4,353 @@ Changelog
|
||||
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
||||
To update |kitty|, :doc:`follow the instructions <binary>`.
|
||||
|
||||
0.18.1 [2020-06-23]
|
||||
--------------------
|
||||
|
||||
- macOS: Fix for diff kitten not working with python 3.8 (:iss:`2780`)
|
||||
|
||||
|
||||
0.18.0 [2020-06-20]
|
||||
--------------------
|
||||
|
||||
- Allow multiple overlay windows per normal window
|
||||
|
||||
- Add an option :opt:`confirm_os_window_close` to ask for confirmation
|
||||
when closing an OS window with multiple kitty windows.
|
||||
|
||||
- Tall and Fat layouts: Add a ``mirrored`` option to put the full size window
|
||||
on the opposite edge of the screen (:iss:`2654`)
|
||||
|
||||
- Tall and Fat layouts: Add mappable actions to increase or decrease the number
|
||||
of full size windows (:iss:`2688`)
|
||||
|
||||
- Allow sending arbitrary signals to the current foreground process in a window
|
||||
using either a mapping in kitty.conf or via remote control (:iss:`2778`)
|
||||
|
||||
- Allow sending the back and forward mouse buttons to terminal applications
|
||||
(:pull:`2742`)
|
||||
|
||||
- **Backwards incompatibility**: The numbers used to encode mouse buttons
|
||||
for the ``send_mouse_event`` function that can be used in kittens have
|
||||
been changed (see :ref:`send_mouse_event`).
|
||||
|
||||
- Add a new mappable ``quit`` action to quit kitty completely.
|
||||
|
||||
- Fix marks using different colors with regexes using only a single color
|
||||
(:pull:`2663`)
|
||||
|
||||
- Linux: Workaround for broken Nvidia drivers for old cards (:iss:`456`)
|
||||
|
||||
- Wayland: Fix kitty being killed on some Wayland compositors if a hidden window
|
||||
has a lot of output (:iss:`2329`)
|
||||
|
||||
- BSD: Fix controlling terminal not being established (:pull:`2686`)
|
||||
|
||||
- Add support for the CSI REP escape code (:pull:`2702`)
|
||||
|
||||
- Wayland: Fix mouse cursor rendering on HiDPI screens (:pull:`2709`)
|
||||
|
||||
- X11: Recompile keymaps on XkbNewKeyboardNotify events (:iss:`2726`)
|
||||
|
||||
- X11: Reduce startup time by ~25% by only querying GLX for framebuffer
|
||||
configurations once (:iss:`2754`)
|
||||
|
||||
- macOS: Notarize the kitty application bundle (:iss:`2040`)
|
||||
|
||||
- Fix the kitty shell launched via a mapping needlessly requiring
|
||||
:opt:`allow_remote_control` to be turned on.
|
||||
|
||||
|
||||
0.17.4 [2020-05-09]
|
||||
--------------------
|
||||
|
||||
- Allow showing the name of the current layout and the number of windows
|
||||
in tab titles (:iss:`2634`)
|
||||
|
||||
- macOS: Fix a regression in the previous release that caused ligatures to be
|
||||
not be centered horizontally (:iss:`2591`)
|
||||
|
||||
- By default, double clicking no longer considers the : as part of words, see
|
||||
:opt:`select_by_word_characters` (:iss:`2602`)
|
||||
|
||||
- Fix a regression that caused clicking in the padding/margins of windows in
|
||||
the stack layout to switch the window to the first window (:iss:`2604`)
|
||||
|
||||
- macOS: Fix a regression that broke drag and drop (:iss:`2605`)
|
||||
|
||||
- Report modifier key state when sending wheel events to the terminal program
|
||||
|
||||
- Fix kitty @ send-text not working with text larger than 1024 bytes when using
|
||||
:option:`kitty --listen-on` (:iss:`2607`)
|
||||
|
||||
- Wayland: Fix OS window title not updating for hidden windows (:iss:`2629`)
|
||||
|
||||
- Fix :opt:`background_tint` making the window semi-transparent (:iss:`2618`)
|
||||
|
||||
|
||||
0.17.3 [2020-04-23]
|
||||
--------------------
|
||||
|
||||
- Allow individually setting margins and padding for each edge (left, right,
|
||||
top, bottom). Margins can also be controlled per window via remote control
|
||||
(:iss:`2546`)
|
||||
|
||||
- Fix reverse video not being rendered correctly when using transparency or a
|
||||
background image (:iss:`2419`)
|
||||
|
||||
- Allow mapping arbitrary remote control commands to key presses in
|
||||
:file:`kitty.conf`
|
||||
|
||||
- X11: Fix crash when doing drag and drop from some applications (:iss:`2505`)
|
||||
|
||||
- Fix :option:`launch --stdin-add-formatting` not working (:iss:`2512`)
|
||||
|
||||
- Update to Unicode 13.0 (:iss:`2513`)
|
||||
|
||||
- Render country flags designated by a pair of unicode codepoints
|
||||
in two cells instead of four.
|
||||
|
||||
- diff kitten: New option to control the background color for filler lines in
|
||||
the margin (:iss:`2518`)
|
||||
|
||||
- Fix specifying options for layouts in the startup session file not working
|
||||
(:iss:`2520`)
|
||||
|
||||
- macOS: Fix incorrect horizontal positioning of some full-width East Asian characters
|
||||
(:iss:`1457`)
|
||||
|
||||
- macOS: Render multi-cell PUA characters centered, matching behavior on other
|
||||
platforms
|
||||
|
||||
- Linux: Ignore keys if they are designated as layout/group/mode switch keys
|
||||
(:iss:`2519`)
|
||||
|
||||
- Marks: Fix marks not handling wide characters and tab characters correctly
|
||||
(:iss:`2534`)
|
||||
|
||||
- Add a new :opt:`listen_on` option in kitty.conf to set :option:`kitty --listen-on`
|
||||
globally. Also allow using environment variables in this option (:iss:`2569`).
|
||||
|
||||
- Allow sending mouse events in kittens (:pull:`2538`)
|
||||
|
||||
- icat kitten: Fix display of 16-bit depth images (:iss:`2542`)
|
||||
|
||||
- Add ncurses specific terminfo definitions for strikethrough (:pull:`2567`)
|
||||
|
||||
- Fix a regression in 0.17 that broke displaying graphics over SSH
|
||||
(:iss:`2568`)
|
||||
|
||||
- Fix :option:`--title` not being applied at window creation time (:iss:`2570`)
|
||||
|
||||
0.17.2 [2020-03-29]
|
||||
--------------------
|
||||
|
||||
- Add a :option:`launch --watcher` option that allows defining callbacks
|
||||
that are called for various events in the window's life-cycle (:iss:`2440`)
|
||||
|
||||
- Fix a regression in 0.17 that broke drawing of borders with non-minimal
|
||||
borders (:iss:`2474`)
|
||||
|
||||
- Hints kitten: Allow copying to primary selection as well as clipboard
|
||||
(:pull:`2487`)
|
||||
|
||||
- Add a new mappable action ``close_other_windows_in_tab`` to close all but the
|
||||
active window (:iss:`2484`)
|
||||
|
||||
- Hints kitten: Adjust the default regex used to detect line numbers to handle
|
||||
line+column numbers (:iss:`2268`)
|
||||
|
||||
- Fix blank space at the start of tab bar in the powerline style when first tab is
|
||||
inactive (:iss:`2478`)
|
||||
|
||||
- Fix regression causing incorrect rendering of separators in tab bar when
|
||||
defining a tab bar background color (:pull:`2480`)
|
||||
|
||||
- Fix a regression in 0.17 that broke the kitty @ launch remote command and
|
||||
also broke the --tab-title option when creating a new tab. (:iss:`2488`)
|
||||
|
||||
- Linux: Fix selection of fonts with multiple width variants not preferring
|
||||
the normal width faces (:iss:`2491`)
|
||||
|
||||
|
||||
0.17.1 [2020-03-24]
|
||||
--------------------
|
||||
|
||||
- Fix :opt:`cursor_underline_thickness` not working (:iss:`2465`)
|
||||
|
||||
- Fix a regression in 0.17 that caused tab bar background to be rendered after
|
||||
the last tab as well (:iss:`2464`)
|
||||
|
||||
- macOS: Fix a regression in 0.17 that caused incorrect variants to be
|
||||
automatically selected for some fonts (:iss:`2462`)
|
||||
|
||||
- Fix a regression in 0.17 that caused kitty @ set-colors to require setting
|
||||
cursor_text_color (:iss:`2470`)
|
||||
|
||||
|
||||
0.17.0 [2020-03-24]
|
||||
--------------------
|
||||
|
||||
- :ref:`splits_layout` to arrange windows in arbitrary splits
|
||||
(:iss:`2308`)
|
||||
|
||||
- Add support for specifying a background image, see :opt:`background_image`
|
||||
(:iss:`163` and :pull:`2326`; thanks to Fredrick Brennan.)
|
||||
|
||||
- A new :opt:`background_tint` option to darken the background under the text
|
||||
area when using background images and/or transparent windows.
|
||||
|
||||
- Allow selection of single cells with the mouse. Also improve mouse selection
|
||||
to follow semantics common to most programs (:iss:`945`)
|
||||
|
||||
- New options :opt:`cursor_beam_thickness` and :opt:`cursor_underline_thickness` to control the thickness of the
|
||||
beam and underline cursors (:iss:`2337` and :pull:`2342`)
|
||||
|
||||
- When the application running in the terminal grabs the mouse, pass middle
|
||||
clicks to the application unless :opt:`terminal_select_modifiers` are
|
||||
pressed (:iss:`2368`)
|
||||
|
||||
- A new ``copy_and_clear_or_interrupt`` function (:iss:`2403`)
|
||||
|
||||
- X11: Fix arrow mouse cursor using right pointing instead of the default left
|
||||
pointing arrow (:iss:`2341`)
|
||||
|
||||
- Allow passing the currently active kitty window id in the launch command
|
||||
(:iss:`2391`)
|
||||
|
||||
- unicode input kitten: Allow pressing :kbd:`ctrl+tab` to change the input mode
|
||||
(:iss:`2343`)
|
||||
|
||||
- Fix a bug that prevented using custom functions with the new marks feature
|
||||
(:iss:`2344`)
|
||||
|
||||
- Make the set of URL prefixes that are recognized while hovering with the
|
||||
mouse configurable (:iss:`2416`)
|
||||
|
||||
- Fix border/margin/padding sizes not being recalculated on DPI change
|
||||
(:iss:`2346`)
|
||||
|
||||
- diff kitten: Fix directory diffing with removed binary files failing
|
||||
(:iss:`2378`)
|
||||
|
||||
- macOS: Fix menubar title not updating on OS Window focus change (:iss:`2350`)
|
||||
|
||||
- Fix rendering of combining characters with fonts that have glyphs for
|
||||
precomposed characters but not decomposed versions (:iss:`2365`)
|
||||
|
||||
- Fix incorrect rendering of selection when using rectangular select and
|
||||
scrolling (:iss:`2351`)
|
||||
|
||||
- Allow setting WM_CLASS and WM_NAME when creating new OS windows with the
|
||||
launch command (:option:`launch --os-window-class`)
|
||||
|
||||
- macOS: When switching input method while a pending multi-key input is in
|
||||
progress, clear the pending input (:iss:`2358`)
|
||||
|
||||
- Fix a regression in the previous release that broke switching to neighboring windows
|
||||
in the Grid layout when there are less than four windows (:iss:`2377`)
|
||||
|
||||
- Fix colors in scrollback pager off if the window has redefined terminal
|
||||
colors using escape codes (:iss:`2381`)
|
||||
|
||||
- Fix selection not updating properly while scrolling (:iss:`2442`)
|
||||
|
||||
- Allow extending selections by dragging with right button pressed
|
||||
(:iss:`2445`)
|
||||
|
||||
- Workaround for bug in less that causes colors to reset at wrapped lines
|
||||
(:iss:`2381`)
|
||||
|
||||
- X11/Wayland: Allow drag and drop of text/plain in addition to text/uri-list
|
||||
(:iss:`2441`)
|
||||
|
||||
- Dont strip :code:`&` and :code:`-` from the end of URLs (:iss:`2436`)
|
||||
|
||||
- Fix ``@selection`` placeholder not working with launch command (:iss:`2417`)
|
||||
|
||||
- Drop support for python 3.5
|
||||
|
||||
- Wayland: Fix a crash when drag and dropping into kitty (:iss:`2432`)
|
||||
|
||||
- diff kitten: Fix images lingering as blank rectangles after the kitten quits
|
||||
(:iss:`2449`)
|
||||
|
||||
- diff kitten: Fix images losing position when scrolling using mouse
|
||||
wheel/touchpad
|
||||
|
||||
|
||||
0.16.0 [2020-01-28]
|
||||
--------------------
|
||||
|
||||
- A new :doc:`marks` feature that allows highlighting and scrolling to arbitrary
|
||||
text in the terminal window.
|
||||
|
||||
- hints kitten: Allow pressing :sc:`goto_file_line` to quickly open
|
||||
the selected file at the selected line in vim or a configurable editor (:iss:`2268`)
|
||||
|
||||
- Allow having more than one full height window in the :code:`tall` layout
|
||||
(:iss:`2276`)
|
||||
|
||||
- Allow choosing OpenType features for individual fonts via the
|
||||
:opt:`font_features` option. (:pull:`2248`)
|
||||
|
||||
- Wayland: Fix a freeze in rare circumstances when having multiple OS Windows
|
||||
(:iss:`2307` and :iss:`1722`)
|
||||
|
||||
- Wayland: Fix window titles being set to very long strings on the order of 8KB
|
||||
causing a crash (:iss:`1526`)
|
||||
|
||||
- Add an option :opt:`force_ltr` to turn off the display of text in RTL scripts
|
||||
in right-to-left order (:pull:`2293`)
|
||||
|
||||
- Allow opening new tabs/windows before the current tab/window as well as after
|
||||
it with the :option:`launch --location` option.
|
||||
|
||||
- Add a :opt:`resize_in_steps` option that can be used to resize the OS window
|
||||
in steps as large as character cells (:pull:`2131`)
|
||||
|
||||
- When triple-click+dragging to select multiple lines, extend the selection
|
||||
of the first line to match the rest on the left (:pull:`2284`)
|
||||
|
||||
- macOS: Add a :code:`titlebar-only` setting to
|
||||
:opt:`hide_window_decorations` to only hide the title bar (:pull:`2286`)
|
||||
|
||||
- Fix a segfault when using :option:`kitty --debug-config` with maps (:iss:`2270`)
|
||||
|
||||
- ``goto_tab`` now maps numbers larger than the last tab to the last tab
|
||||
(:iss:`2291`)
|
||||
|
||||
- Fix URL detection not working for urls of the form scheme:///url
|
||||
(:iss:`2292`)
|
||||
|
||||
- When windows are semi-transparent and all contain graphics, correctly render
|
||||
them. (:iss:`2310`)
|
||||
|
||||
0.15.1 [2019-12-21]
|
||||
--------------------
|
||||
|
||||
- Fix a crash/incorrect rendering when detaching a window in some circumstances
|
||||
(:iss:`2173`)
|
||||
|
||||
- hints kitten: Add an option :option:`kitty +kitten hints --ascending` to
|
||||
control if the hints numbers increase or decrease from top to bottom
|
||||
|
||||
- Fix :opt:`background_opacity` incorrectly applying to selected text and
|
||||
reverse video text (:iss:`2177`)
|
||||
|
||||
- Add a new option :opt:`tab_bar_background` to specify a different color
|
||||
for the tab bar (:iss:`2198`)
|
||||
|
||||
- Add a new option :opt:`active_tab_title_template` to specify a different
|
||||
template for active tab titles (:iss:`2198`)
|
||||
|
||||
- Fix lines at the edge of the window at certain windows sizes when drawing
|
||||
images on a transparent window (:iss:`2079`)
|
||||
|
||||
- Fix window not being rendered for the first time until some input has been
|
||||
received from child process (:iss:`2216`)
|
||||
|
||||
|
||||
0.15.0 [2019-11-27]
|
||||
--------------------
|
||||
|
||||
|
||||
118
docs/conf.py
118
docs/conf.py
@@ -7,28 +7,34 @@
|
||||
# full list see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/config
|
||||
|
||||
import importlib
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from functools import partial
|
||||
from typing import (
|
||||
Any, Callable, Dict, Iterable, List, Match, Optional, Sequence, Tuple,
|
||||
Union
|
||||
)
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst.roles import set_classes
|
||||
from pygments.lexer import RegexLexer, bygroups
|
||||
from pygments.token import (
|
||||
from pygments.lexer import RegexLexer, bygroups # type: ignore
|
||||
from pygments.token import ( # type: ignore
|
||||
Comment, Keyword, Literal, Name, Number, String, Whitespace
|
||||
)
|
||||
from sphinx import addnodes
|
||||
from sphinx.environment.adapters.toctree import TocTree
|
||||
from sphinx.util.logging import getLogger
|
||||
from sphinx import addnodes # type: ignore
|
||||
from sphinx.environment.adapters.toctree import TocTree # type: ignore
|
||||
from sphinx.util.logging import getLogger # type: ignore
|
||||
|
||||
kitty_src = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
if kitty_src not in sys.path:
|
||||
sys.path.insert(0, kitty_src)
|
||||
str_version = importlib.import_module('kitty.constants').str_version
|
||||
|
||||
from kitty.conf.definition import Option, Shortcut # noqa
|
||||
from kitty.constants import str_version # noqa
|
||||
|
||||
|
||||
# config {{{
|
||||
# -- Project information -----------------------------------------------------
|
||||
@@ -77,7 +83,7 @@ master_doc = 'index'
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = None
|
||||
language: Optional[str] = None
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
@@ -184,7 +190,7 @@ texinfo_documents = [
|
||||
|
||||
# GitHub linking inline roles {{{
|
||||
|
||||
def num_role(which, name, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||
def num_role(which: str, name: str, rawtext: str, text: str, lineno: int, inliner: Any, options: Any = {}, content: Any = []) -> Tuple[List, List]:
|
||||
' Link to a github issue '
|
||||
try:
|
||||
issue_num = int(text)
|
||||
@@ -202,10 +208,11 @@ def num_role(which, name, rawtext, text, lineno, inliner, options={}, content=[]
|
||||
return [node], []
|
||||
|
||||
|
||||
def commit_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||
def commit_role(name: str, rawtext: str, text: str, lineno: int, inliner: Any, options: Any = {}, content: Any = []) -> Tuple[List, List]:
|
||||
' Link to a github commit '
|
||||
try:
|
||||
commit_id = subprocess.check_output(f'git rev-list --max-count=1 --skip=# {text}'.split()).decode('utf-8').strip()
|
||||
commit_id = subprocess.check_output(
|
||||
f'git rev-list --max-count=1 --skip=# {text}'.split()).decode('utf-8').strip()
|
||||
except Exception:
|
||||
msg = inliner.reporter.error(
|
||||
f'GitHub commit id "{text}" not recognized.', line=lineno)
|
||||
@@ -213,21 +220,22 @@ def commit_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||
return [prb], [msg]
|
||||
url = f'https://github.com/kovidgoyal/kitty/commit/{commit_id}'
|
||||
set_classes(options)
|
||||
short_id = subprocess.check_output(f'git rev-list --max-count=1 --abbrev-commit --skip=# {commit_id}'.split()).decode('utf-8').strip()
|
||||
short_id = subprocess.check_output(
|
||||
f'git rev-list --max-count=1 --abbrev-commit --skip=# {commit_id}'.split()).decode('utf-8').strip()
|
||||
node = nodes.reference(rawtext, f'commit: {short_id}', refuri=url, **options)
|
||||
return [node], []
|
||||
# }}}
|
||||
|
||||
|
||||
# Sidebar ToC {{{
|
||||
def create_toc(app, pagename):
|
||||
def create_toc(app: Any, pagename: str) -> Optional[Any]:
|
||||
tt = TocTree(app.env)
|
||||
toctree = tt.get_toc_for(pagename, app.builder)
|
||||
if toctree is not None:
|
||||
subtree = toctree[toctree.first_child_matching_class(nodes.list_item)]
|
||||
bl = subtree.first_child_matching_class(nodes.bullet_list)
|
||||
if bl is None:
|
||||
return # Empty ToC
|
||||
return None # Empty ToC
|
||||
subtree = subtree[bl]
|
||||
# for li in subtree.traverse(nodes.list_item):
|
||||
# modify_li(li)
|
||||
@@ -235,14 +243,14 @@ def create_toc(app, pagename):
|
||||
return app.builder.render_partial(subtree)['fragment']
|
||||
|
||||
|
||||
def add_html_context(app, pagename, templatename, context, *args):
|
||||
def add_html_context(app: Any, pagename: str, templatename: str, context: Any, *args: Any) -> None:
|
||||
if 'toc' in context:
|
||||
context['toc'] = create_toc(app, pagename) or context['toc']
|
||||
# }}}
|
||||
|
||||
|
||||
# CLI docs {{{
|
||||
def write_cli_docs(all_kitten_names):
|
||||
def write_cli_docs(all_kitten_names: Iterable[str]) -> None:
|
||||
from kitty.launch import options_spec as launch_options_spec
|
||||
from kitty.cli import option_spec_as_rst
|
||||
with open('generated/launch.rst', 'w') as f:
|
||||
@@ -258,16 +266,17 @@ if you specify a program-to-run you can use the special placeholder
|
||||
f.write(option_spec_as_rst(appname='kitty').replace(
|
||||
'kitty --to', 'kitty @ --to'))
|
||||
as_rst = partial(option_spec_as_rst, heading_char='_')
|
||||
from kitty.remote_control import global_options_spec, cli_msg, cmap, all_commands
|
||||
from kitty.rc.base import all_command_names, command_for_name
|
||||
from kitty.remote_control import global_options_spec, cli_msg
|
||||
with open('generated/cli-kitty-at.rst', 'w') as f:
|
||||
p = partial(print, file=f)
|
||||
p('kitty @\n' + '-' * 80)
|
||||
p('.. program::', 'kitty @')
|
||||
p('\n\n' + as_rst(
|
||||
global_options_spec, message=cli_msg, usage='command ...', appname='kitty @'))
|
||||
from kitty.cmds import cli_params_for
|
||||
for cmd_name in all_commands:
|
||||
func = cmap[cmd_name]
|
||||
from kitty.rc.base import cli_params_for
|
||||
for cmd_name in sorted(all_command_names()):
|
||||
func = command_for_name(cmd_name)
|
||||
p(f'.. _at_{func.name}:\n')
|
||||
p('kitty @', func.name + '\n' + '-' * 120)
|
||||
p('.. program::', 'kitty @', func.name)
|
||||
@@ -286,14 +295,14 @@ if you specify a program-to-run you can use the special placeholder
|
||||
# }}}
|
||||
|
||||
|
||||
def write_remote_control_protocol_docs(): # {{{
|
||||
from kitty.cmds import cmap
|
||||
def write_remote_control_protocol_docs() -> None: # {{{
|
||||
from kitty.rc.base import all_command_names, command_for_name, RemoteCommand
|
||||
field_pat = re.compile(r'\s*([a-zA-Z0-9_+]+)\s*:\s*(.+)')
|
||||
|
||||
def format_cmd(p, name, cmd):
|
||||
def format_cmd(p: Callable, name: str, cmd: RemoteCommand) -> None:
|
||||
p(name)
|
||||
p('-' * 80)
|
||||
lines = cmd.__doc__.strip().splitlines()
|
||||
lines = (cmd.__doc__ or '').strip().splitlines()
|
||||
fields = []
|
||||
for line in lines:
|
||||
m = field_pat.match(line)
|
||||
@@ -317,10 +326,10 @@ def write_remote_control_protocol_docs(): # {{{
|
||||
p(' ', desc), p()
|
||||
p(), p()
|
||||
|
||||
with open(f'generated/rc.rst', 'w') as f:
|
||||
with open('generated/rc.rst', 'w') as f:
|
||||
p = partial(print, file=f)
|
||||
for name in sorted(cmap):
|
||||
cmd = cmap[name]
|
||||
for name in sorted(all_command_names()):
|
||||
cmd = command_for_name(name)
|
||||
if not cmd.__doc__:
|
||||
continue
|
||||
name = name.replace('_', '-')
|
||||
@@ -383,7 +392,7 @@ class SessionLexer(RegexLexer):
|
||||
}
|
||||
|
||||
|
||||
def link_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||
def link_role(name: str, rawtext: str, text: str, lineno: int, inliner: Any, options: Any = {}, content: Any = []) -> Tuple[List, List]:
|
||||
m = re.match(r'(.+)\s+<(.+?)>', text)
|
||||
if m is None:
|
||||
msg = inliner.reporter.error(f'link "{text}" not recognized', line=lineno)
|
||||
@@ -395,24 +404,24 @@ def link_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
|
||||
return [node], []
|
||||
|
||||
|
||||
def expand_opt_references(conf_name, text):
|
||||
def expand_opt_references(conf_name: str, text: str) -> str:
|
||||
conf_name += '.'
|
||||
|
||||
def expand(m):
|
||||
def expand(m: Match) -> str:
|
||||
ref = m.group(1)
|
||||
if '<' not in ref and '.' not in ref:
|
||||
full_ref = conf_name + ref
|
||||
return ':opt:`{} <{}>`'.format(ref, full_ref)
|
||||
return m.group()
|
||||
return str(m.group())
|
||||
|
||||
return re.sub(r':opt:`(.+?)`', expand, text)
|
||||
|
||||
|
||||
opt_aliases = {}
|
||||
shortcut_slugs = {}
|
||||
opt_aliases: Dict[str, str] = {}
|
||||
shortcut_slugs: Dict[str, Tuple[str, str]] = {}
|
||||
|
||||
|
||||
def parse_opt_node(env, sig, signode):
|
||||
def parse_opt_node(env: Any, sig: str, signode: Any) -> str:
|
||||
"""Transform an option description into RST nodes."""
|
||||
count = 0
|
||||
firstname = ''
|
||||
@@ -434,22 +443,22 @@ def parse_opt_node(env, sig, signode):
|
||||
return firstname
|
||||
|
||||
|
||||
def parse_shortcut_node(env, sig, signode):
|
||||
def parse_shortcut_node(env: Any, sig: str, signode: Any) -> str:
|
||||
"""Transform a shortcut description into RST nodes."""
|
||||
conf_name, text = sig.split('.', 1)
|
||||
signode += addnodes.desc_name(text, text)
|
||||
return sig
|
||||
|
||||
|
||||
def render_conf(conf_name, all_options):
|
||||
from kitty.conf.definition import merged_opts, Option
|
||||
def render_conf(conf_name: str, all_options: Iterable[Union['Option', Sequence['Shortcut']]]) -> str:
|
||||
from kitty.conf.definition import merged_opts, Option, Group
|
||||
ans = ['.. default-domain:: conf', '']
|
||||
a = ans.append
|
||||
current_group = None
|
||||
all_options = list(all_options)
|
||||
current_group: Optional[Group] = None
|
||||
all_options_ = list(all_options)
|
||||
kitty_mod = 'kitty_mod'
|
||||
|
||||
def render_group(group):
|
||||
def render_group(group: Group) -> None:
|
||||
a('')
|
||||
a(f'.. _conf-{conf_name}-{group.name}:')
|
||||
a('')
|
||||
@@ -461,11 +470,12 @@ def render_conf(conf_name, all_options):
|
||||
a(group.start_text)
|
||||
a('')
|
||||
|
||||
def handle_group_end(group):
|
||||
def handle_group_end(group: Group) -> None:
|
||||
if group.end_text:
|
||||
assert current_group is not None
|
||||
a(''), a(current_group.end_text)
|
||||
|
||||
def handle_group(new_group, new_group_is_shortcut=False):
|
||||
def handle_group(new_group: Group, new_group_is_shortcut: bool = False) -> None:
|
||||
nonlocal current_group
|
||||
if new_group is not current_group:
|
||||
if current_group:
|
||||
@@ -473,14 +483,14 @@ def render_conf(conf_name, all_options):
|
||||
current_group = new_group
|
||||
render_group(current_group)
|
||||
|
||||
def handle_option(i, opt):
|
||||
def handle_option(i: int, opt: Option) -> None:
|
||||
nonlocal kitty_mod
|
||||
if not opt.long_text or not opt.add_to_docs:
|
||||
return
|
||||
handle_group(opt.group)
|
||||
if opt.name == 'kitty_mod':
|
||||
kitty_mod = opt.defval_as_string
|
||||
mopts = list(merged_opts(all_options, opt, i))
|
||||
mopts = list(merged_opts(all_options_, opt, i))
|
||||
a('.. opt:: ' + ', '.join(conf_name + '.' + mo.name for mo in mopts))
|
||||
a('.. code-block:: conf')
|
||||
a('')
|
||||
@@ -492,7 +502,7 @@ def render_conf(conf_name, all_options):
|
||||
a(expand_opt_references(conf_name, opt.long_text))
|
||||
a('')
|
||||
|
||||
def handle_shortcuts(shortcuts):
|
||||
def handle_shortcuts(shortcuts: Sequence[Shortcut]) -> None:
|
||||
sc = shortcuts[0]
|
||||
handle_group(sc.group, True)
|
||||
sc_text = f'{conf_name}.{sc.short_text}'
|
||||
@@ -510,7 +520,7 @@ def render_conf(conf_name, all_options):
|
||||
a(expand_opt_references(conf_name, sc.long_text))
|
||||
a('')
|
||||
|
||||
for i, opt in enumerate(all_options):
|
||||
for i, opt in enumerate(all_options_):
|
||||
if isinstance(opt, Option):
|
||||
handle_option(i, opt)
|
||||
else:
|
||||
@@ -521,7 +531,7 @@ def render_conf(conf_name, all_options):
|
||||
return '\n'.join(ans)
|
||||
|
||||
|
||||
def process_opt_link(env, refnode, has_explicit_title, title, target):
|
||||
def process_opt_link(env: Any, refnode: Any, has_explicit_title: bool, title: str, target: str) -> Tuple[str, str]:
|
||||
conf_name, opt = target.partition('.')[::2]
|
||||
if not opt:
|
||||
conf_name, opt = 'kitty', conf_name
|
||||
@@ -529,7 +539,7 @@ def process_opt_link(env, refnode, has_explicit_title, title, target):
|
||||
return title, opt_aliases.get(full_name, full_name)
|
||||
|
||||
|
||||
def process_shortcut_link(env, refnode, has_explicit_title, title, target):
|
||||
def process_shortcut_link(env: Any, refnode: Any, has_explicit_title: bool, title: str, target: str) -> Tuple[str, str]:
|
||||
conf_name, slug = target.partition('.')[::2]
|
||||
if not slug:
|
||||
conf_name, slug = 'kitty', conf_name
|
||||
@@ -544,7 +554,7 @@ def process_shortcut_link(env, refnode, has_explicit_title, title, target):
|
||||
return title, target
|
||||
|
||||
|
||||
def write_conf_docs(app, all_kitten_names):
|
||||
def write_conf_docs(app: Any, all_kitten_names: Iterable[str]) -> None:
|
||||
app.add_lexer('conf', ConfLexer())
|
||||
app.add_object_type(
|
||||
'opt', 'opt',
|
||||
@@ -565,7 +575,7 @@ def write_conf_docs(app, all_kitten_names):
|
||||
sc_role.warn_dangling = True
|
||||
sc_role.process_link = process_shortcut_link
|
||||
|
||||
def generate_default_config(all_options, name):
|
||||
def generate_default_config(all_options: Dict[str, Union[Option, Sequence[Shortcut]]], name: str) -> None:
|
||||
from kitty.conf.definition import as_conf_file
|
||||
with open(f'generated/conf-{name}.rst', 'w', encoding='utf-8') as f:
|
||||
print('.. highlight:: conf\n', file=f)
|
||||
@@ -587,13 +597,13 @@ def write_conf_docs(app, all_kitten_names):
|
||||
# }}}
|
||||
|
||||
|
||||
def setup(app):
|
||||
def setup(app: Any) -> None:
|
||||
os.makedirs('generated/conf', exist_ok=True)
|
||||
from kittens.runner import all_kitten_names
|
||||
all_kitten_names = all_kitten_names()
|
||||
write_cli_docs(all_kitten_names)
|
||||
kn = all_kitten_names()
|
||||
write_cli_docs(kn)
|
||||
write_remote_control_protocol_docs()
|
||||
write_conf_docs(app, all_kitten_names)
|
||||
write_conf_docs(app, kn)
|
||||
app.add_lexer('session', SessionLexer())
|
||||
app.add_role('link', link_role)
|
||||
app.add_role('iss', partial(num_role, 'issues'))
|
||||
|
||||
@@ -169,7 +169,7 @@ only monospace fonts, since every cell in the grid has to be the same size. If
|
||||
your font is not listed in ``kitty list-fonts`` it means that it is not
|
||||
monospace. On Linux you can list all monospace fonts with::
|
||||
|
||||
fc-list : family spacing | grep -e spacing=100 -e spacing=90
|
||||
fc-list : family spacing outline scalable | grep -e spacing=100 -e spacing=90 | grep -e outline=True | grep -e scalable=True
|
||||
|
||||
Note that the spacing property is calculated by fontconfig based on actual
|
||||
glyph widths in the font. If for some reason fontconfig concludes your favorite
|
||||
@@ -179,7 +179,7 @@ following :file:`~/.config/fontconfig/fonts.conf`::
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<match target="scan">
|
||||
<match target="font">
|
||||
<test name="family">
|
||||
<string>Your Font Family Name</string>
|
||||
</test>
|
||||
|
||||
@@ -30,10 +30,11 @@ alpha-blending and text over graphics.
|
||||
|
||||
Some programs that use the kitty graphics protocol:
|
||||
|
||||
* `termpdf <https://github.com/dsanson/termpdf>`_ - a terminal PDF/DJVU/CBR viewer
|
||||
* `termpdf.py <https://github.com/dsanson/termpdf.py>`_ - a terminal PDF/DJVU/CBR viewer
|
||||
* `ranger <https://github.com/ranger/ranger>`_ - a terminal file manager, with
|
||||
image previews, see this `PR <https://github.com/ranger/ranger/pull/1077>`_
|
||||
* :doc:`kitty-diff <kittens/diff>` - a side-by-side terminal diff program with support for images
|
||||
* `pixcat <https://github.com/mirukana/pixcat>`_ - a third party CLI and python library that wraps the graphics protocol
|
||||
* `neofetch <https://github.com/dylanaraps/neofetch>`_ - A command line system
|
||||
information tool
|
||||
|
||||
@@ -262,8 +263,8 @@ received. Finally, terminals must not display anything, until the entire sequenc
|
||||
received and validated.
|
||||
|
||||
|
||||
Detecting available transmission mediums
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Querying support and available transmission mediums
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Since a client has no a-priori knowledge of whether it shares a filesystem/shared memory
|
||||
with the terminal emulator, it can send an id with the control data, using the ``i`` key
|
||||
@@ -289,6 +290,27 @@ use the *query action*, set ``a=q``. Then the terminal emulator will try to load
|
||||
the image and respond with either OK or an error, as above, but it will not
|
||||
replace an existing image with the same id, nor will it store the image.
|
||||
|
||||
While as of May 2020, kitty is the only terminal emulator to support this
|
||||
graphics protocol, we intend that any terminal emulator that wishes to support
|
||||
it can. To check if a terminal emulator supports the graphics protocol the best way
|
||||
is to send the above *query action* followed by a request for the
|
||||
`primary device attributes <https://vt100.net/docs/vt510-rm/DA1.html>`. If you
|
||||
get back an answer for the device attributes without getting back an answer for
|
||||
the *query action* the terminal emulator does not support the graphics
|
||||
protocol.
|
||||
|
||||
This means that terminal emulators that support the graphics protocol, **must**
|
||||
reply to *query actions* immediately without processing other input. Most
|
||||
terminal emulators handle input in a FIFO manner, anyway.
|
||||
|
||||
So for example, you could send::
|
||||
|
||||
<ESC>_Gi=31,s=1,v=1,a=q,t=d,f=24;<NUL><NUL><NUL><ESC>\<ESC>[c
|
||||
|
||||
If you get back a response to the graphics query, the terminal emulator supports
|
||||
the protocol, if you get back a response to the device attributes query without
|
||||
a response to the graphics query, it does not.
|
||||
|
||||
|
||||
Display images on screen
|
||||
-----------------------------
|
||||
@@ -334,7 +356,9 @@ Finally, you can specify the image *z-index*, i.e. the vertical stacking order.
|
||||
placed in the same location with different z-index values will be blended if
|
||||
they are semi-transparent. You can specify z-index values using the ``z`` key.
|
||||
Negative z-index values mean that the images will be drawn under the text. This
|
||||
allows rendering of text on top of images.
|
||||
allows rendering of text on top of images. Negative z-index values below
|
||||
INT32_MIN/2 (-1,073,741,824) will be drawn under cells with non-default background
|
||||
colors.
|
||||
|
||||
Deleting images
|
||||
---------------------
|
||||
@@ -366,10 +390,10 @@ Value of ``d`` Meaning
|
||||
|
||||
Some examples::
|
||||
|
||||
<ESC>_Ga=d<ESC>\ # delete all visible images
|
||||
<ESC>_Ga=d,i=10<ESC>\ # delete the image with id=10
|
||||
<ESC>_Ga=Z,z=-1<ESC>\ # delete the images with z-index -1, also freeing up image data
|
||||
<ESC>_Ga=P,x=3,y=4<ESC>\ # delete all images that intersect the cell at (3, 4)
|
||||
<ESC>_Ga=d<ESC>\ # delete all visible images
|
||||
<ESC>_Ga=d,d=i,i=10<ESC>\ # delete the image with id=10, without freeing data
|
||||
<ESC>_Ga=d,d=Z,z=-1<ESC>\ # delete the images with z-index -1, also freeing up image data
|
||||
<ESC>_Ga=d,d=p,x=3,y=4<ESC>\ # delete all images that intersect the cell at (3, 4), without freeing data
|
||||
|
||||
Image persistence and storage quotas
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -417,7 +441,7 @@ Key Value Default Description
|
||||
``Y`` Positive integer ``0`` The y-offset within the first cell at which to start displaying the image
|
||||
``c`` Positive integer ``0`` The number of columns to display the image over
|
||||
``r`` Positive integer ``0`` The number of rows to display the image over
|
||||
``z`` Integer ``0`` The *z-index* vertical stacking order of the image
|
||||
``z`` 32-bit integer ``0`` The *z-index* vertical stacking order of the image
|
||||
**Keys for deleting images**
|
||||
-----------------------------------------------------------
|
||||
``d`` Single character. ``a`` What to delete.
|
||||
|
||||
100
docs/index.rst
100
docs/index.rst
@@ -53,7 +53,7 @@ Quickstart
|
||||
--------------
|
||||
|
||||
Pre-built binaries of |kitty| are available for both macOS and Linux.
|
||||
See the :doc:`binary install instructions </binary>`. You can also
|
||||
See the :doc:`binary install instructions </binary>`. You can
|
||||
:doc:`build from source </build>`.
|
||||
|
||||
You can also use your favorite package manager to install the |kitty| package.
|
||||
@@ -65,7 +65,7 @@ You can also use your favorite package manager to install the |kitty| package.
|
||||
`openSUSE <https://build.opensuse.org/package/show/X11:terminals/kitty>`_,
|
||||
`Arch Linux <https://www.archlinux.org/packages/community/x86_64/kitty/>`_,
|
||||
`Gentoo <https://packages.gentoo.org/packages/x11-terms/kitty>`_,
|
||||
`Fedora <https://copr.fedorainfracloud.org/coprs/gagbo/kitty-latest/>`_,
|
||||
`Fedora <https://src.fedoraproject.org/rpms/kitty>`_,
|
||||
`Void Linux <https://github.com/void-linux/void-packages/blob/master/srcpkgs/kitty/template>`_,
|
||||
and `Solus <https://dev.getsol.us/source/kitty/>`_.
|
||||
|
||||
@@ -178,14 +178,25 @@ previously active windows for negative numbers.
|
||||
You can define shortcuts to detach the current window and
|
||||
move it to another tab or another OS window::
|
||||
|
||||
map ctrl+f2 detach_window # moves the window into a new OS window
|
||||
map ctrl+f3 detach_window new-tab # moves the window into a new Tab
|
||||
map ctrl+f4 detach_window ask # asks which tab to move the window into
|
||||
# moves the window into a new OS window
|
||||
map ctrl+f2 detach_window
|
||||
# moves the window into a new Tab
|
||||
map ctrl+f3 detach_window new-tab
|
||||
# asks which tab to move the window into
|
||||
map ctrl+f4 detach_window ask
|
||||
|
||||
Similarly, you can detach the current tab, with::
|
||||
|
||||
map ctrl+f2 detach_tab # moves the tab into a new OS window
|
||||
map ctrl+f4 detach_tab ask # asks which OS Window to move the tab into
|
||||
# moves the tab into a new OS window
|
||||
map ctrl+f2 detach_tab
|
||||
# asks which OS Window to move the tab into
|
||||
map ctrl+f4 detach_tab ask
|
||||
|
||||
Finally, you can define a shortcut to close all windows in a tab other than
|
||||
the currently active window::
|
||||
|
||||
map f9 close_other_windows_in_tab
|
||||
|
||||
|
||||
Other keyboard shortcuts
|
||||
----------------------------------
|
||||
@@ -219,55 +230,26 @@ Reset background opacity :sc:`reset_background_opacity`
|
||||
Layouts
|
||||
----------
|
||||
|
||||
A layout is an arrangement of multiple *windows*. You can create a new window
|
||||
A layout is an arrangement of multiple kitty *windows* inside a top-level OS window. You can create a new window
|
||||
using the :sc:`new_window` key combination.
|
||||
|
||||
Currently, there are five layouts available,
|
||||
Currently, there are six layouts available:
|
||||
|
||||
* **Stack** -- Only a single maximized window is shown at a time
|
||||
* **Tall** -- One window is shown full height on the left, the rest of the windows are shown one below the other on the right
|
||||
* **Fat** -- One window is shown full width on the top, the rest of the windows are shown side-by-side on the bottom
|
||||
* **Fat** -- One (or optionally more) windows are shown full width on the top, the rest of the windows are shown side-by-side on the bottom
|
||||
* **Grid** -- All windows are shown in a grid
|
||||
* **Horizontal** -- All windows are shown side-by-side
|
||||
* **Splits** -- Windows arranged in arbitrary patterns created using horizontal and vertical splits
|
||||
* **Stack** -- Only a single maximized window is shown at a time
|
||||
* **Tall** -- One (or optionally more) windows are shown full height on the left, the rest of the windows are shown one below the other on the right
|
||||
* **Vertical** -- All windows are shown one below the other
|
||||
|
||||
You can switch between layouts using the :sc:`next_layout` key combination. You can
|
||||
also create shortcuts to select particular layouts, and choose which layouts
|
||||
you want to enable/disable, see :ref:`conf-kitty-shortcuts.layout` for examples.
|
||||
By default, all layouts are enabled and you can switch between layouts using
|
||||
the :sc:`next_layout` key combination. You can also create shortcuts to select
|
||||
particular layouts, and choose which layouts you want to enable/disable, see
|
||||
:ref:`conf-kitty-shortcuts.layout` for examples. The first layout listed in
|
||||
:opt:`enabled_layouts` becomes the default layout.
|
||||
|
||||
You can resize windows inside layouts. Press :sc:`start_resizing_window` (also :kbd:`⌘+r` on macOS) to
|
||||
enter resizing mode and follow the on-screen instructions. In a given window
|
||||
layout only some operations may be possible for a particular window. For
|
||||
example, in the Tall layout you can make the first window wider/narrower, but
|
||||
not taller/shorter. Note that what you are resizing is actually not a window,
|
||||
but a row/column in the layout, all windows in that row/column will be resized.
|
||||
|
||||
You can also define shortcuts in :file:`kitty.conf` to make the active window
|
||||
wider, narrower, taller, or shorter by mapping to the ``resize_window``
|
||||
action, for example::
|
||||
|
||||
map ctrl+left resize_window narrower
|
||||
map ctrl+right resize_window wider
|
||||
map ctrl+up resize_window taller
|
||||
map ctrl+down resize_window shorter 3
|
||||
|
||||
The ``resize_window`` action has a second, optional argument to control
|
||||
the resizing increment (a positive integer that defaults to 1).
|
||||
|
||||
|
||||
Some layouts take options to control their behavior. For example, the ``fat``
|
||||
and ``tall`` layouts accept the ``bias`` option to control how the available
|
||||
space is split up. To specify the option, in :opt:`kitty.conf <enabled_layouts>` use::
|
||||
|
||||
enabled_layouts tall:bias=70
|
||||
|
||||
This will make the tall window occupy ``70%`` of available width. ``bias`` can be
|
||||
any number between 10 and 90.
|
||||
|
||||
Writing a new layout only requires about a hundred lines of code, so if there
|
||||
is some layout you want, take a look at `layout.py
|
||||
<https://github.com/kovidgoyal/kitty/blob/master/kitty/layout.py>`_ and submit
|
||||
a pull request!
|
||||
For more details on the layouts and how to use them see :doc:`layouts`.
|
||||
|
||||
.. _kittens:
|
||||
|
||||
@@ -378,11 +360,12 @@ Mouse features
|
||||
* You can triple click to select a line and then drag to select more lines.
|
||||
* You can right click to extend a previous selection.
|
||||
* You can hold down :kbd:`ctrl+alt` and drag with the mouse to select in
|
||||
columns.
|
||||
columns (see also :opt:`rectangle_select_modifiers`).
|
||||
* Selecting text automatically copies it to the primary clipboard (on
|
||||
platforms with a primary clipboard).
|
||||
* You can select text with kitty even when a terminal program has grabbed
|
||||
the mouse by holding down the :kbd:`shift` key.
|
||||
the mouse by holding down the :kbd:`shift` key (see also
|
||||
:opt:`terminal_select_modifiers`).
|
||||
|
||||
|
||||
Font control
|
||||
@@ -441,6 +424,14 @@ internal buffer named ``a`` and :kbd:`F2` to paste from that buffer. The buffer
|
||||
names are arbitrary strings, so you can define as many such buffers as you
|
||||
need.
|
||||
|
||||
Marks
|
||||
-------------
|
||||
|
||||
kitty has the ability to mark text on the screen based on regular expressions.
|
||||
This can be useful to highlight words or phrases when browsing output from long
|
||||
running programs or similar. To learn how this feature works, see :doc:`marks`.
|
||||
|
||||
|
||||
Frequently Asked Questions
|
||||
---------------------------------
|
||||
|
||||
@@ -448,6 +439,15 @@ The list of Frequently Asked Questions (*FAQ*) is :doc:`available here <faq>`.
|
||||
|
||||
.. _completion:
|
||||
|
||||
|
||||
Cool integrations for kitty with other CLI tools
|
||||
--------------------------------------------------
|
||||
|
||||
kitty provides extremely powerful interfaces such as :doc:`remote-control` and
|
||||
:doc:`kittens/custom` and :doc:`kittens/icat`
|
||||
that allow it to be integrated with other tools seamlessly. For a list of such
|
||||
user created integrations, see: :doc:`integrations`.
|
||||
|
||||
Completion for kitty
|
||||
---------------------------------
|
||||
|
||||
|
||||
108
docs/integrations.rst
Normal file
108
docs/integrations.rst
Normal file
@@ -0,0 +1,108 @@
|
||||
Integrations with other tools
|
||||
================================
|
||||
|
||||
kitty provides extremely powerful interfaces such as :doc:`remote-control` and
|
||||
:doc:`kittens/custom` and :doc:`kittens/icat`
|
||||
that allow it to be integrated with other tools seamlessly.
|
||||
|
||||
|
||||
Image and document viewers
|
||||
----------------------------
|
||||
|
||||
Powered by kitty's :doc:`graphics-protocol` there exist many tools for viewing
|
||||
images and other types of documents directly in your terminal, even over SSH.
|
||||
|
||||
`termpdf.py <https://github.com/dsanson/termpdf.py>`_
|
||||
a terminal PDF/DJVU/CBR viewer
|
||||
|
||||
`mdcat <https://github.com/lunaryorn/mdcat>`_
|
||||
Display markdown files nicely formatted with images in the terminal
|
||||
|
||||
`ranger <https://github.com/ranger/ranger>`_
|
||||
a terminal file manager, with previews of file contents powered by kitty's graphics protocol.
|
||||
|
||||
`nnn <https://github.com/jarun/nnn/>`_
|
||||
another terminal file manager, with previews of file contents powered by kitty's graphics protocol.
|
||||
|
||||
`koneko <https://github.com/twenty5151/koneko>`_
|
||||
browse images from the pixiv artist community directly in kitty
|
||||
|
||||
|
||||
System and data visualisation tools
|
||||
---------------------------------------
|
||||
|
||||
`neofetch <https://github.com/dylanaraps/neofetch>`_
|
||||
A command line system information tool that shows images using kitty's graphics protocol
|
||||
|
||||
`matplotlib <https://github.com/jktr/matplotlib-backend-kitty>`_
|
||||
show matplotlib plots directly in kitty
|
||||
|
||||
`KittyTerminalImages.jl <https://github.com/simonschoelly/KittyTerminalImages.jl>`_
|
||||
show images from Julia directly in kitty
|
||||
|
||||
`gnuplot <http://www.gnuplot.info/>`_
|
||||
a graphing and data visualization tool that can be made to display its
|
||||
output in kitty with the following bash snippet::
|
||||
|
||||
function iplot {
|
||||
cat <<EOF | gnuplot
|
||||
set terminal pngcairo enhanced font 'Fira Sans,10'
|
||||
set autoscale
|
||||
set samples 1000
|
||||
set output '|kitty +kitten icat --stdin yes'
|
||||
set object 1 rectangle from screen 0,0 to screen 1,1 fillcolor rgb"#fdf6e3" behind
|
||||
plot $@
|
||||
set output '/dev/null'
|
||||
EOF
|
||||
}
|
||||
|
||||
Add this to bashrc and then to plot a function, simply do::
|
||||
|
||||
iplot 'sin(x*3)*exp(x*.2)'
|
||||
|
||||
|
||||
Editor integration
|
||||
-----------------------
|
||||
|
||||
kitty can be integrated into many different terminal editors to add features
|
||||
such a split windows, previews, REPLs etc.
|
||||
|
||||
|
||||
`kakoune <https://kakoune.org/>`_
|
||||
integrates with kitty to use native kitty windows for its windows/panels and REPLs.
|
||||
|
||||
`vim-slime <https://github.com/jpalardy/vim-slime#kitty>`_
|
||||
uses kitty remote control for a Lisp REPL.
|
||||
|
||||
`vim-kitty-navigator <https://github.com/knubie/vim-kitty-navigator>`_
|
||||
allows you to navigate seamlessly between vim and kitty splits using a consistent set of hotkeys.
|
||||
|
||||
`vim-test <https://github.com/vim-test/vim-test>`_
|
||||
allows easily running tests in a terminal window
|
||||
|
||||
|
||||
Scrollback manipulation
|
||||
-------------------------
|
||||
|
||||
`kitty-search <https://github.com/trygveaa/kitty-kitten-search>`_
|
||||
Live incremental search of the scrollback buffer.
|
||||
|
||||
`kitty-grab <https://github.com/yurikhan/kitty_grab>`_
|
||||
keyboard based text selection for the kitty scrollback buffer.
|
||||
|
||||
|
||||
|
||||
Miscellaneous
|
||||
------------------
|
||||
|
||||
`kitty-smart-tab <https://github.com/yurikhan/kitty-smart-tab>`_
|
||||
use keys to either control tabs or pass them onto running applications if
|
||||
no tabs are present
|
||||
|
||||
`kitty-smart-scroll <https://github.com/yurikhan/kitty-smart-scroll>`_
|
||||
use keys to either scroll or pass them onto running applications if
|
||||
no scrollback buffer is present
|
||||
|
||||
`reload keybindings <https://github.com/kovidgoyal/kitty/issues/1292#issuecomment-582388769>`_
|
||||
reload key bindings from :file:`kitty.conf` without needing to restart
|
||||
kitty
|
||||
@@ -20,24 +20,24 @@ your machine).
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def main(args):
|
||||
# this is the main entry point of the kitten, it will be executed in
|
||||
# the overlay window when the kitten is launched
|
||||
answer = input('Enter some text: ')
|
||||
# whatever this function returns will be available in the
|
||||
# handle_result() function
|
||||
return answer
|
||||
def main(args: List[str]) -> str:
|
||||
# this is the main entry point of the kitten, it will be executed in
|
||||
# the overlay window when the kitten is launched
|
||||
answer = input('Enter some text: ')
|
||||
# whatever this function returns will be available in the
|
||||
# handle_result() function
|
||||
return answer
|
||||
|
||||
def handle_result(args, answer, target_window_id, boss):
|
||||
# get the kitty window into which to paste answer
|
||||
w = boss.window_id_map.get(target_window_id)
|
||||
if w is not None:
|
||||
w.paste(answer)
|
||||
def handle_result(args: List[str], answer: str, target_window_id: int, boss: kitty.boss.Boss) -> None:
|
||||
# get the kitty window into which to paste answer
|
||||
w = boss.window_id_map.get(target_window_id)
|
||||
if w is not None:
|
||||
w.paste(answer)
|
||||
|
||||
|
||||
Now in :file:`kitty.conf` add the lines::
|
||||
|
||||
map ctrl+k kitten mykitten.py
|
||||
map ctrl+k kitten mykitten.py
|
||||
|
||||
|
||||
Start kitty and press :kbd:`ctrl+k` and you should see the kitten running.
|
||||
@@ -53,7 +53,7 @@ Passing arguments to kittens
|
||||
You can pass arguments to kittens by defining them in the map directive in
|
||||
:file:`kitty.conf`. For example::
|
||||
|
||||
map ctrl+k kitten mykitten.py arg1 arg2
|
||||
map ctrl+k kitten mykitten.py arg1 arg2
|
||||
|
||||
These will be available as the ``args`` parameter in the ``main()`` and
|
||||
``handle_result()`` functions. Note also that the current working directory
|
||||
@@ -70,17 +70,18 @@ function, telling kitty what kind of input your kitten would like. For example:
|
||||
|
||||
.. code-block:: py
|
||||
|
||||
# in main, STDIN is for the kitten process and will contain
|
||||
# the contents of the screen
|
||||
def main(args):
|
||||
return sys.stdin.read()
|
||||
# in main, STDIN is for the kitten process and will contain
|
||||
# the contents of the screen
|
||||
def main(args):
|
||||
return sys.stdin.read()
|
||||
|
||||
# in handle_result, STDIN is for the kitty process itself, rather
|
||||
# than the kitten process and should not be read from.
|
||||
def handle_result(args, stdin_data, target_window_id, boss):
|
||||
pass
|
||||
# in handle_result, STDIN is for the kitty process itself, rather
|
||||
# than the kitten process and should not be read from.
|
||||
from kittens.tui.handler import result_handler
|
||||
@result_handler(type_of_input='text')
|
||||
def handle_result(args, stdin_data, target_window_id, boss):
|
||||
pass
|
||||
|
||||
handle_result.type_of_input = 'text'
|
||||
|
||||
This will send the plain text of the active window to the kitten's
|
||||
:file:`STDIN`. For text with formatting escape codes, use ``ansi``
|
||||
@@ -103,23 +104,23 @@ Create a file in the kitty config folder, :file:`~/.config/kitty/zoom_toggle.py`
|
||||
|
||||
.. code-block:: py
|
||||
|
||||
def main(args):
|
||||
pass
|
||||
def main(args):
|
||||
pass
|
||||
|
||||
def handle_result(args, answer, target_window_id, boss):
|
||||
tab = boss.active_tab
|
||||
if tab is not None:
|
||||
if tab.current_layout.name == 'stack':
|
||||
tab.last_used_layout()
|
||||
else:
|
||||
tab.goto_layout('stack')
|
||||
|
||||
handle_result.no_ui = True
|
||||
from kittens.tui.handler import result_handler
|
||||
@result_handler(no_ui=True)
|
||||
def handle_result(args, answer, target_window_id, boss):
|
||||
tab = boss.active_tab
|
||||
if tab is not None:
|
||||
if tab.current_layout.name == 'stack':
|
||||
tab.last_used_layout()
|
||||
else:
|
||||
tab.goto_layout('stack')
|
||||
|
||||
|
||||
Now in kitty.conf add::
|
||||
|
||||
map f11 kitten zoom_toggle.py
|
||||
map f11 kitten zoom_toggle.py
|
||||
|
||||
Pressing :kbd:`F11` will now act as a zoom toggle function. You can get even
|
||||
more fancy, switching the kitty OS window to fullscreen as well as changing the
|
||||
@@ -131,6 +132,35 @@ layout, by simply adding the line::
|
||||
To the ``handle_result()`` function, above.
|
||||
|
||||
|
||||
.. _send_mouse_event:
|
||||
|
||||
Sending mouse events
|
||||
--------------------
|
||||
|
||||
If the program running in a window is receiving mouse events you can simulate
|
||||
those using::
|
||||
|
||||
from kitty.fast_data_types import send_mouse_event
|
||||
send_mouse_event(screen, x, y, button, action, mods)
|
||||
|
||||
``screen`` is the ``screen`` attribute of the window you want to send the event
|
||||
to. ``x`` and ``y`` are the 0-indexed coordinates. ``button`` is a number using
|
||||
the same numbering as X11 (left: ``1``, middle: ``2``, right: ``3``, scroll up:
|
||||
``4``, scroll down: ``5``, scroll left: ``6``, scroll right: ``7``, back:
|
||||
``8``, forward: ``9``). ``action`` is one of ``PRESS``, ``RELEASE``, ``DRAG``
|
||||
or ``MOVE``. ``mods`` is a bitmask of ``GLFW_MOD_{mod}`` where ``{mod}`` is one
|
||||
of ``SHIFT``, ``CONTROL`` or ``ALT``. All the mentioned constants are imported
|
||||
from ``kitty.fast_data_types``.
|
||||
|
||||
For example, to send a left click at position x: 2, y: 3 to the active window::
|
||||
|
||||
from kitty.fast_data_types import send_mouse_event, GLFW_MOUSE_BUTTON_LEFT, PRESS
|
||||
send_mouse_event(boss.active_window.screen, 2, 3, GLFW_MOUSE_BUTTON_LEFT, PRESS, 0)
|
||||
|
||||
The function will only send the event if the program is receiving events of
|
||||
that type, and will return ``True`` if it sent the event, and ``False`` if not.
|
||||
|
||||
|
||||
Debugging kittens
|
||||
--------------------
|
||||
|
||||
@@ -156,11 +186,11 @@ Kittens created by kitty users
|
||||
---------------------------------------------
|
||||
|
||||
`vim-kitty-navigator <https://github.com/knubie/vim-kitty-navigator>`_
|
||||
Allows you to navigate seamlessly between vim and kitty splits using a consistent set of hotkeys.
|
||||
Allows you to navigate seamlessly between vim and kitty splits using a consistent set of hotkeys.
|
||||
|
||||
`smart-scroll <https://github.com/yurikhan/kitty-smart-scroll>`_
|
||||
Makes the kitty scroll bindings work in full screen applications
|
||||
Makes the kitty scroll bindings work in full screen applications
|
||||
|
||||
`insert password <https://github.com/kovidgoyal/kitty/issues/1222>`_
|
||||
Insert a password from a CLI password manager, taking care to only do it at
|
||||
a password prompt.
|
||||
Insert a password from a CLI password manager, taking care to only do it at
|
||||
a password prompt.
|
||||
|
||||
@@ -18,6 +18,16 @@ select anything that looks like a path or filename and then insert it into the
|
||||
terminal, very useful for picking files from the output of a ``git`` or ``ls`` command and
|
||||
adding them to the command line for the next command.
|
||||
|
||||
You can also press :sc:`goto_file_line` to select anything that looks
|
||||
like a path or filename followed by a colon and a line number and open
|
||||
the file in vim at the specified line number. The patterns and editor
|
||||
to be used can be modified using options passed to the kitten. For example::
|
||||
|
||||
map ctrl+g kitten hints --type=linenum --linenum-action=tab nvim +{line} {path}
|
||||
|
||||
will open the selected file in a new tab inside neovim when you press
|
||||
:kbd:`ctrl+g`.
|
||||
|
||||
The hints kitten is very powerful to see more detailed help on its various
|
||||
options and modes of operation, see below. You can use these options to
|
||||
create mappings in :file:`kitty.conf` to select various different text
|
||||
|
||||
@@ -21,6 +21,12 @@ Then you can simply use ``icat image.png`` to view images.
|
||||
`ImageMagick <https://www.imagemagick.org>`_ must be installed for ``icat`` to
|
||||
work.
|
||||
|
||||
.. note::
|
||||
|
||||
kitty's image display protocol may not work when used within a terminal
|
||||
multiplexer such as ``screen`` or ``tmux``, depending on whether the
|
||||
multiplexer has added support for it or not.
|
||||
|
||||
|
||||
.. program:: kitty +kitten icat
|
||||
|
||||
|
||||
@@ -23,6 +23,10 @@ keys/tab to select the character from the displayed matches. You can also type
|
||||
a leading period and the index for the match if you don't like to use arrow
|
||||
keys.
|
||||
|
||||
You can switch between modes using either the function keys or by pressing
|
||||
:kbd:`Ctrl+Tab` and :kbd:`Ctrl+Shift+Tab`.
|
||||
|
||||
|
||||
Command Line Interface
|
||||
-------------------------
|
||||
|
||||
|
||||
@@ -50,6 +50,46 @@ being the top left corner and ``{lines},{columns}`` being the number of rows
|
||||
and columns of the screen.
|
||||
|
||||
|
||||
Special arguments
|
||||
-------------------
|
||||
|
||||
There are a few special placeholder arguments that can be specified as part of
|
||||
the command line. Namely ``@selection`` which is replaced by the current
|
||||
selection and ``@active-kitty-window-id`` which is replaced by the id of the
|
||||
currently active kitty window. For example::
|
||||
|
||||
map f1 launch my-program @active-kitty-window-id
|
||||
|
||||
|
||||
Watching launched windows
|
||||
---------------------------
|
||||
|
||||
The :option:`launch --watcher` option allows you to specify python functions
|
||||
that will be called at specific events, such as when the window is resized or
|
||||
closed. Simply specify the path to a python module that specifies callback
|
||||
functions for the events you are interested in, for example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def on_resize(boss, window, data):
|
||||
# Here data will contain old_geometry and new_geometry
|
||||
|
||||
def on_close(boss, window, data):
|
||||
# called when window is closed, typically when the program running in
|
||||
# it exits.
|
||||
|
||||
|
||||
Every callback is passed a reference to the global ``Boss`` object as well as
|
||||
the ``Window`` object the action is occurring on. The ``data`` object is
|
||||
mapping that contains event dependent data. Some useful methods and attributes
|
||||
for the ``Window`` object are: ``as_text(as_ans=False, add_history=False,
|
||||
add_wrap_markers=False, alternate_screen=False)`` with which you can get the
|
||||
contents of the window and its scrollback buffer. Similarly,
|
||||
``window.child.pid`` is the PID of the processes that was launched
|
||||
in the window and ``window.id`` is the internal kitty ``id`` of the
|
||||
window.
|
||||
|
||||
|
||||
Syntax reference
|
||||
------------------
|
||||
|
||||
|
||||
258
docs/layouts.rst
Normal file
258
docs/layouts.rst
Normal file
@@ -0,0 +1,258 @@
|
||||
Layouts
|
||||
============
|
||||
|
||||
kitty has the ability to define its own windows that can be tiled next to each
|
||||
other in arbitrary arrangements, based on *Layouts*, see below for examples:
|
||||
|
||||
|
||||
.. figure:: screenshots/screenshot.png
|
||||
:alt: Screenshot, showing three programs in the 'Tall' layout
|
||||
:align: center
|
||||
:scale: 100%
|
||||
|
||||
Screenshot, showing vim, tig and git running in |kitty| with the 'Tall' layout
|
||||
|
||||
|
||||
.. figure:: screenshots/splits.png
|
||||
:alt: Screenshot, showing windows in the 'Splits' layout
|
||||
:align: center
|
||||
:scale: 100%
|
||||
|
||||
Screenshot, showing windows with arbitrary arrangement in the 'Splits'
|
||||
layout
|
||||
|
||||
|
||||
There are many different layouts available. They are all enabled by default,
|
||||
you can switch layouts using :sc:`next_layout`. To control which layouts
|
||||
are available use :opt:`enabled_layouts`, the first listed layout becomes
|
||||
the default. Individual layouts and how to use them are described below.
|
||||
|
||||
.. contents::
|
||||
|
||||
|
||||
The Stack Layout
|
||||
------------------
|
||||
|
||||
This is the simplest layout it displays a single window using all available
|
||||
space, other windows are hidden behind it. It has no options::
|
||||
|
||||
enabled_layouts stack
|
||||
|
||||
|
||||
The Tall Layout
|
||||
------------------
|
||||
|
||||
Displays one (or optionally more) full height windows on the left half of the
|
||||
screen. Remaining windows are tiled vertically on the right half of the screen.
|
||||
There are options to control how the screen is split horizontally ``bias``
|
||||
(an integer between ``10`` and ``90``) and options to control how many
|
||||
full-height windows there are ``full_size`` (a positive integer). The
|
||||
``mirrored`` option when set to ``true`` will cause the short windows to be
|
||||
on the left side of the screen instead of the right. The syntax
|
||||
for the options is shown below::
|
||||
|
||||
enabled_layouts tall:bias=50;full_size=1;mirrored=false
|
||||
|
||||
┌──────────────┬───────────────┐
|
||||
│ │ │
|
||||
│ │ │
|
||||
│ │ │
|
||||
│ ├───────────────┤
|
||||
│ │ │
|
||||
│ │ │
|
||||
│ │ │
|
||||
│ ├───────────────┤
|
||||
│ │ │
|
||||
│ │ │
|
||||
│ │ │
|
||||
└──────────────┴───────────────┘
|
||||
|
||||
In addition, you can map keys to increase or decrease the number of full size
|
||||
windows, for example::
|
||||
|
||||
map ctrl+[ layout_action decrease_num_full_size_windows
|
||||
map ctrl+] layout_action increase_num_full_size_windows
|
||||
|
||||
|
||||
The Fat Layout
|
||||
----------------
|
||||
|
||||
Displays one (or optionally more) full width windows on the top half of the
|
||||
screen. Remaining windows are tiled horizontally on the bottom half of the screen.
|
||||
There are options to control how the screen is split vertically ``bias``
|
||||
(an integer between ``10`` and ``90``) and options to control how many
|
||||
full-height windows there are ``full_size`` (a positive integer). The
|
||||
``mirrored`` option when set to ``true`` will cause the narrow windows to be
|
||||
on the top of the screen instead of the bottom. The syntax for the options is
|
||||
shown below::
|
||||
|
||||
enabled_layouts fat:bias=50;full_size=1;mirrored=false
|
||||
|
||||
┌──────────────────────────────┐
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
├─────────┬──────────┬─────────┤
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
└─────────┴──────────┴─────────┘
|
||||
|
||||
|
||||
The Grid Layout
|
||||
--------------------
|
||||
|
||||
Display windows in a balanced grid with all windows the same size except the
|
||||
last column if there are not enough windows to fill the grid. Has no options::
|
||||
|
||||
enabled_layouts grid
|
||||
|
||||
┌─────────┬──────────┬─────────┐
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
├─────────┼──────────┼─────────┤
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
└─────────┴──────────┴─────────┘
|
||||
|
||||
|
||||
.. _splits_layout:
|
||||
|
||||
The Splits Layout
|
||||
--------------------
|
||||
|
||||
This is the most flexible layout. You can create any arrangement of windows
|
||||
by splitting exiting windows repeatedly. To best use this layout you should
|
||||
define a few extra keybindings in :file:`kitty.conf`::
|
||||
|
||||
map F5 launch --location=hsplit
|
||||
map F6 launch --location=vsplit
|
||||
map F7 layout_action rotate
|
||||
|
||||
map shift+up move_window up
|
||||
map shift+left move_window left
|
||||
map shift+right move_window right
|
||||
map shift+down move_window down
|
||||
|
||||
map ctrl+left neighboring_window left
|
||||
map ctrl+right neighboring_window right
|
||||
map ctrl+up neighboring_window up
|
||||
map ctrl+down neighboring_window down
|
||||
|
||||
Now you can create horizontal and vertical splits by using :kbd:`F5` and
|
||||
:kbd:`F6`. You can move them around using :kbd:`shift+arrow keys`
|
||||
and you can move focus to neighboring windows using :kbd:`ctrl+arrow keys`.
|
||||
You can switch an existing split from horizontal to vertical and vice versa
|
||||
using :kbd:`F7`. Finally, windows can be resized using :ref:`window_resizing`.
|
||||
|
||||
This layout takes one option, ``split_axis`` that controls whether new windows
|
||||
are placed into vertical or horizontal splits, by default::
|
||||
|
||||
enabled_layouts splits:split_axis=horizontal
|
||||
|
||||
┌──────────────┬───────────────┐
|
||||
│ │ │
|
||||
│ │ │
|
||||
│ │ │
|
||||
│ ├───────┬───────┤
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ ├───────┴───────┤
|
||||
│ │ │
|
||||
│ │ │
|
||||
│ │ │
|
||||
└──────────────┴───────────────┘
|
||||
|
||||
|
||||
|
||||
The Horizontal Layout
|
||||
------------------------
|
||||
|
||||
All windows are shown side by side. Has no options::
|
||||
|
||||
enabled_layouts horizontal
|
||||
|
||||
┌─────────┬──────────┬─────────┐
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
│ │ │ │
|
||||
└─────────┴──────────┴─────────┘
|
||||
|
||||
|
||||
The Vertical Layout
|
||||
-----------------------
|
||||
|
||||
All windows are shown one below the other. Has no options::
|
||||
|
||||
enabled_layouts vertical
|
||||
|
||||
┌──────────────────────────────┐
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
├──────────────────────────────┤
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
├──────────────────────────────┤
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
└──────────────────────────────┘
|
||||
|
||||
|
||||
.. _window_resizing:
|
||||
|
||||
Resizing windows
|
||||
------------------
|
||||
|
||||
You can resize windows inside layouts. Press :sc:`start_resizing_window` (also
|
||||
:kbd:`⌘+r` on macOS) to enter resizing mode and follow the on-screen
|
||||
instructions. In a given window layout only some operations may be possible
|
||||
for a particular window. For example, in the Tall layout you can make the first
|
||||
window wider/narrower, but not taller/shorter. Note that what you are resizing
|
||||
is actually not a window, but a row/column in the layout, all windows in that
|
||||
row/column will be resized.
|
||||
|
||||
You can also define shortcuts in :file:`kitty.conf` to make the active window
|
||||
wider, narrower, taller, or shorter by mapping to the ``resize_window``
|
||||
action, for example::
|
||||
|
||||
map ctrl+left resize_window narrower
|
||||
map ctrl+right resize_window wider
|
||||
map ctrl+up resize_window taller
|
||||
map ctrl+down resize_window shorter 3
|
||||
|
||||
The ``resize_window`` action has a second, optional argument to control
|
||||
the resizing increment (a positive integer that defaults to 1).
|
||||
|
||||
|
||||
Some layouts take options to control their behavior. For example, the ``fat``
|
||||
and ``tall`` layouts accept the ``bias`` and ``full_size`` options to control
|
||||
how the available space is split up.
|
||||
To specify the option, in :opt:`kitty.conf <enabled_layouts>` use::
|
||||
|
||||
enabled_layouts tall:bias=70;full_size=2
|
||||
|
||||
This will have ``2`` instead of a single tall window, that occupy ``70%``
|
||||
instead of ``50%`` of available width. ``bias`` can be any number between 10
|
||||
and 90.
|
||||
|
||||
Writing a new layout only requires about two hundred lines of code, so if there
|
||||
is some layout you want, take a look at one of the existing layouts in the
|
||||
`layout <https://github.com/kovidgoyal/kitty/tree/master/kitty/layout>`_
|
||||
package and submit a pull request!
|
||||
125
docs/marks.rst
Normal file
125
docs/marks.rst
Normal file
@@ -0,0 +1,125 @@
|
||||
Marks
|
||||
=================
|
||||
|
||||
|
||||
kitty has the ability to mark text on the screen based on regular expressions.
|
||||
This can be useful to highlight words or phrases when browsing output from long
|
||||
running programs or similar. Lets start with a few examples:
|
||||
|
||||
Suppose we want to be able to highlight the word ERROR in the current window.
|
||||
Add the following to :file:`kitty.conf`::
|
||||
|
||||
map f1 toggle_marker text 1 ERROR
|
||||
|
||||
Now when you press :kbd:`F1` all instances of the word :code:`ERROR` will be
|
||||
highlighted. To turn off the highlighting, press :kbd:`F1` again.
|
||||
If you want to make it case-insensitive, use::
|
||||
|
||||
map f1 toggle_marker itext 1 ERROR
|
||||
|
||||
To make it match only complete words, use::
|
||||
|
||||
map f1 toggle_marker regex 1 \bERROR\b
|
||||
|
||||
Suppose you want to highlight both :code:`ERROR` and :code:`WARNING`, case
|
||||
insensitively::
|
||||
|
||||
map f1 toggle_marker iregex 1 \bERROR\b 2 \bWARNING\b
|
||||
|
||||
kitty supports up to 3 mark groups (the numbers in the commands above). You
|
||||
can control the colors used for these groups in :file:`kitty.conf` with::
|
||||
|
||||
mark1_foreground red
|
||||
mark1_background gray
|
||||
mark2_foreground green
|
||||
...
|
||||
|
||||
|
||||
.. note::
|
||||
For performance reasons, matching is done per line only, and only when that line is
|
||||
altered in any way. So you cannot match text that stretches across multiple
|
||||
lines.
|
||||
|
||||
|
||||
Creating markers dynamically
|
||||
---------------------------------
|
||||
|
||||
If you want to create markers dynamically rather than pre-defining them in
|
||||
:file:`kitty.conf` you can do so as follows::
|
||||
|
||||
map f1 create_marker
|
||||
map f2 remove_marker
|
||||
|
||||
Then pressing :kbd:`F1` will allow you to enter the marker definition and set
|
||||
it and pressing :kbd:`F2` will remove the marker. ``create_marker`` accepts
|
||||
the same syntax as ``toggle_marker`` above. Note that while creating
|
||||
markers, the prompt has history so you can easily re-use previous marker
|
||||
expressions.
|
||||
|
||||
You can also use the facilities for :doc:`remote-control` to dynamically
|
||||
add/remove markers.
|
||||
|
||||
|
||||
Scrolling to marks
|
||||
--------------------
|
||||
|
||||
kitty has an action to scroll to the next line that contains a mark. You can
|
||||
use it by mapping it to some shortcut in :file:`kitty.conf`::
|
||||
|
||||
map ctrl+p scroll_to_mark prev
|
||||
map ctrl+n scroll_to_mark next
|
||||
|
||||
Then pressing :kbd:`ctrl+p` will scroll to the first line in the scrollback
|
||||
buffer above the current top line that contains a mark. Pressing :kbd:`ctrl+n`
|
||||
will scroll to show the first line below the current last line that contains
|
||||
a mark. If you wish to jump to a mark of a specific type, you can add that to
|
||||
the mapping::
|
||||
|
||||
map ctrl+1 scroll_to_mark prev 1
|
||||
|
||||
Which will scroll only to marks of type 1.
|
||||
|
||||
|
||||
The full syntax for creating marks
|
||||
-------------------------------------
|
||||
|
||||
The syntax of the :code:`toggle_marker` command is::
|
||||
|
||||
toggle_marker <marker-type> <specification>
|
||||
|
||||
Here :code:`marker-type` is one of:
|
||||
|
||||
* :code:`text` - simple substring matching
|
||||
* :code:`itext` - case-insensitive substring matching
|
||||
* :code:`regex` - A python regular expression
|
||||
* :code:`iregex` - A case-insensitive python regular expression
|
||||
* :code:`function` - An arbitrary function defined in a python file, see
|
||||
:ref:`marker_funcs`.
|
||||
|
||||
.. _marker_funcs:
|
||||
|
||||
Arbitrary marker functions
|
||||
-----------------------------
|
||||
|
||||
You can create your own marker functions. Create a python file named
|
||||
:file:`mymarker.py` and in it create a :code:`marker` function. This
|
||||
function receives the text of the line as input and must yield three numbers,
|
||||
the starting character position, the ending character position and the mark
|
||||
group (1-3). For example:
|
||||
|
||||
.. code-block::
|
||||
|
||||
def marker(text):
|
||||
# Function to highlight the letter X
|
||||
for i, ch in enumerate(text):
|
||||
if ch.lower() == 'x':
|
||||
yield i, i, 3
|
||||
|
||||
|
||||
Save this file somewhere and in :file:`kitty.conf`, use::
|
||||
|
||||
map f1 toggle_marker function /path/to/mymarker.py
|
||||
|
||||
If you save the file in the kitty config directory, you can use::
|
||||
|
||||
map f1 toggle_marker function mymarker.py
|
||||
@@ -219,6 +219,11 @@ first, for example::
|
||||
<ESC>]52;c;!<ESC>\
|
||||
|
||||
Here ``!`` is not valid base64 encoded text, so it clears the clipboard.
|
||||
Further, since it is invalid, it should be ignored by terminal emulators
|
||||
that do not support this extension, thereby making it safe to use, simply
|
||||
always send it before starting a new OSC 52 paste, even if you aren't chunking
|
||||
up large pastes, that way kitty wont concatenate your paste, and it will have
|
||||
no ill-effects in other terminal emulators.
|
||||
|
||||
In case you're using software that can't be easily adapted to this
|
||||
protocol extension, it can be disabled by specifying ``no-append`` to the
|
||||
|
||||
@@ -21,7 +21,7 @@ In order for control to work, :opt:`allow_remote_control` must be enabled in
|
||||
|
||||
Now, in the new |kitty| window, enter the command::
|
||||
|
||||
kitty @ new-window --title Output --keep-focus cat
|
||||
kitty @ launch --title Output --keep-focus cat
|
||||
|
||||
This will open a new window, running the ``cat`` program that will appear next
|
||||
to the current window.
|
||||
@@ -54,7 +54,7 @@ window. Type ``Ctrl+D`` when you are ready to stop.
|
||||
|
||||
Now, let's open a new tab::
|
||||
|
||||
kitty @ new-window --new-tab --tab-title "My Tab" --keep-focus bash
|
||||
kitty @ launch --type=tab --tab-title "My Tab" --keep-focus bash
|
||||
|
||||
This will open a new tab running the bash shell with the title "My Tab".
|
||||
We can change the title of the tab with::
|
||||
@@ -134,6 +134,19 @@ still write to the pipes of any other program on the same computer and
|
||||
therefore can control |kitty|. It can, however, be useful to block programs
|
||||
running on other computers (for example, over ssh) or as other users.
|
||||
|
||||
Mapping key presses to remote control commands
|
||||
--------------------------------------------------
|
||||
|
||||
If you wish to trigger a remote control command easily with just a keypress,
|
||||
you can map it in :file:`kitty.conf`. For example::
|
||||
|
||||
map F1 remote_control set-spacing margin=30
|
||||
|
||||
Then pressing the :kbd:`F1` key will set the active window margins to 30.
|
||||
The syntax for what follows :code:`remote_control` is exactly the same
|
||||
as the syntax for what follows :code:`kitty @` above.
|
||||
|
||||
|
||||
Documentation for the remote control protocol
|
||||
-----------------------------------------------
|
||||
|
||||
|
||||
BIN
docs/screenshots/splits.png
Normal file
BIN
docs/screenshots/splits.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
@@ -4,18 +4,23 @@
|
||||
|
||||
import subprocess
|
||||
from collections import defaultdict
|
||||
from typing import Any, DefaultDict, Dict, FrozenSet, List, Tuple, Union
|
||||
|
||||
KeymapType = Dict[str, Tuple[str, Union[FrozenSet[str], str]]]
|
||||
|
||||
|
||||
def resolve_keys(keymap):
|
||||
ans = defaultdict(list)
|
||||
def resolve_keys(keymap: KeymapType) -> DefaultDict[str, List[str]]:
|
||||
ans: DefaultDict[str, List[str]] = defaultdict(list)
|
||||
for ch, (attr, atype) in keymap.items():
|
||||
if atype not in ('int', 'uint'):
|
||||
atype = 'flag'
|
||||
ans[atype].append(ch)
|
||||
if isinstance(atype, str) and atype in ('int', 'uint'):
|
||||
q = atype
|
||||
else:
|
||||
q = 'flag'
|
||||
ans[q].append(ch)
|
||||
return ans
|
||||
|
||||
|
||||
def enum(keymap):
|
||||
def enum(keymap: KeymapType) -> str:
|
||||
lines = []
|
||||
for ch, (attr, atype) in keymap.items():
|
||||
lines.append(f"{attr}='{ch}'")
|
||||
@@ -26,15 +31,15 @@ def enum(keymap):
|
||||
'''.format(',\n'.join(lines))
|
||||
|
||||
|
||||
def parse_key(keymap):
|
||||
def parse_key(keymap: KeymapType) -> str:
|
||||
lines = []
|
||||
for attr, atype in keymap.values():
|
||||
vs = atype.upper() if atype in ('uint', 'int') else 'FLAG'
|
||||
vs = atype.upper() if isinstance(atype, str) and atype in ('uint', 'int') else 'FLAG'
|
||||
lines.append(f'case {attr}: value_state = {vs}; break;')
|
||||
return ' \n'.join(lines)
|
||||
|
||||
|
||||
def parse_flag(keymap, type_map, command_class):
|
||||
def parse_flag(keymap: KeymapType, type_map: Dict[str, Any], command_class: str) -> str:
|
||||
lines = []
|
||||
for ch in type_map['flag']:
|
||||
attr, allowed_values = keymap[ch]
|
||||
@@ -52,14 +57,14 @@ def parse_flag(keymap, type_map, command_class):
|
||||
return ' \n'.join(lines)
|
||||
|
||||
|
||||
def parse_number(keymap):
|
||||
def parse_number(keymap: KeymapType) -> Tuple[str, str]:
|
||||
int_keys = [f'I({attr})' for attr, atype in keymap.values() if atype == 'int']
|
||||
uint_keys = [f'U({attr})' for attr, atype in keymap.values() if atype == 'uint']
|
||||
return '; '.join(int_keys), '; '.join(uint_keys)
|
||||
|
||||
|
||||
def cmd_for_report(report_name, keymap, type_map, payload_allowed):
|
||||
def group(atype, conv):
|
||||
def cmd_for_report(report_name: str, keymap: KeymapType, type_map: Dict[str, Any], payload_allowed: bool) -> str:
|
||||
def group(atype: str, conv: str) -> Tuple[str, str]:
|
||||
flag_fmt, flag_attrs = [], []
|
||||
cv = {'flag': 'c', 'int': 'i', 'uint': 'I'}[atype]
|
||||
for ch in type_map[atype]:
|
||||
@@ -84,7 +89,15 @@ def cmd_for_report(report_name, keymap, type_map, payload_allowed):
|
||||
return '\n'.join(ans)
|
||||
|
||||
|
||||
def generate(function_name, callback_name, report_name, keymap, command_class, initial_key='a', payload_allowed=True):
|
||||
def generate(
|
||||
function_name: str,
|
||||
callback_name: str,
|
||||
report_name: str,
|
||||
keymap: KeymapType,
|
||||
command_class: str,
|
||||
initial_key: str = 'a',
|
||||
payload_allowed: bool = True
|
||||
) -> str:
|
||||
type_map = resolve_keys(keymap)
|
||||
keys_enum = enum(keymap)
|
||||
handle_key = parse_key(keymap)
|
||||
@@ -124,6 +137,7 @@ static inline void
|
||||
{parr}
|
||||
{keys_enum}
|
||||
enum KEYS key = '{initial_key}';
|
||||
if (screen->parser_buf[pos] == ';') state = AFTER_VALUE;
|
||||
|
||||
while (pos < screen->parser_buf_pos) {{
|
||||
switch(state) {{
|
||||
@@ -225,7 +239,7 @@ static inline void
|
||||
'''
|
||||
|
||||
|
||||
def write_header(text, path):
|
||||
def write_header(text: str, path: str) -> None:
|
||||
with open(path, 'w') as f:
|
||||
print(f'// This file is generated by {__file__} do not edit!', file=f, end='\n\n')
|
||||
print('#pragma once', file=f)
|
||||
@@ -233,9 +247,9 @@ def write_header(text, path):
|
||||
subprocess.check_call(['clang-format', '-i', path])
|
||||
|
||||
|
||||
def graphics_parser():
|
||||
def graphics_parser() -> None:
|
||||
flag = frozenset
|
||||
keymap = {
|
||||
keymap: KeymapType = {
|
||||
'a': ('action', flag('tTqpd')),
|
||||
'd': ('delete_action', flag('aAiIcCpPqQxXyYzZ')),
|
||||
't': ('transmission_type', flag('dfts')),
|
||||
|
||||
205
gen-wcwidth.py
205
gen-wcwidth.py
@@ -12,6 +12,10 @@ from functools import partial
|
||||
from html.entities import html5
|
||||
from itertools import groupby
|
||||
from operator import itemgetter
|
||||
from typing import (
|
||||
Callable, DefaultDict, Dict, FrozenSet, Generator, Iterable, List,
|
||||
Optional, Set, Tuple, Union
|
||||
)
|
||||
from urllib.request import urlopen
|
||||
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
@@ -24,7 +28,7 @@ if len(non_characters) != 66:
|
||||
emoji_skin_tone_modifiers = frozenset(range(0x1f3fb, 0x1F3FF + 1))
|
||||
|
||||
|
||||
def get_data(fname, folder='UCD'):
|
||||
def get_data(fname: str, folder: str = 'UCD') -> Iterable[str]:
|
||||
url = f'https://www.unicode.org/Public/{folder}/latest/{fname}'
|
||||
bn = os.path.basename(url)
|
||||
local = os.path.join('/tmp', bn)
|
||||
@@ -42,24 +46,25 @@ def get_data(fname, folder='UCD'):
|
||||
|
||||
|
||||
# Map of class names to set of codepoints in class
|
||||
class_maps = {}
|
||||
all_symbols = set()
|
||||
name_map = {}
|
||||
word_search_map = defaultdict(set)
|
||||
class_maps: Dict[str, Set[int]] = {}
|
||||
all_symbols: Set[int] = set()
|
||||
name_map: Dict[int, str] = {}
|
||||
word_search_map: DefaultDict[str, Set[int]] = defaultdict(set)
|
||||
zwj = 0x200d
|
||||
marks = set(emoji_skin_tone_modifiers) | {zwj}
|
||||
flag_codepoints = frozenset(range(0x1F1E6, 0x1F1E6 + 26))
|
||||
marks = set(emoji_skin_tone_modifiers) | {zwj} | flag_codepoints
|
||||
not_assigned = set(range(0, sys.maxunicode))
|
||||
|
||||
|
||||
def parse_ucd():
|
||||
def parse_ucd() -> None:
|
||||
|
||||
def add_word(w, c):
|
||||
def add_word(w: str, c: int) -> None:
|
||||
if c <= 32 or c == 127 or 128 <= c <= 159:
|
||||
return
|
||||
if len(w) > 1:
|
||||
word_search_map[w.lower()].add(c)
|
||||
|
||||
first = None
|
||||
first: Optional[int] = None
|
||||
for word, c in html5.items():
|
||||
if len(c) == 1:
|
||||
add_word(word.rstrip(';'), ord(c))
|
||||
@@ -77,7 +82,7 @@ def parse_ucd():
|
||||
category = parts[2]
|
||||
s = class_maps.setdefault(category, set())
|
||||
desc = parts[1]
|
||||
codepoints = (codepoint,)
|
||||
codepoints: Union[Tuple[int, ...], Iterable[int]] = (codepoint,)
|
||||
if first is None:
|
||||
if desc.endswith(', First>'):
|
||||
first = codepoint
|
||||
@@ -99,43 +104,97 @@ def parse_ucd():
|
||||
word_search_map['lamda'] |= word_search_map['lambda']
|
||||
|
||||
|
||||
def split_two(line):
|
||||
spec, rest = line.split(';', 1)
|
||||
spec, rest = spec.strip(), rest.strip().split(' ', 1)[0].strip()
|
||||
def parse_range_spec(spec: str) -> Set[int]:
|
||||
spec = spec.strip()
|
||||
if '..' in spec:
|
||||
chars = tuple(map(lambda x: int(x, 16), filter(None, spec.split('.'))))
|
||||
chars = set(range(chars[0], chars[1] + 1))
|
||||
chars_ = tuple(map(lambda x: int(x, 16), filter(None, spec.split('.'))))
|
||||
chars = set(range(chars_[0], chars_[1] + 1))
|
||||
else:
|
||||
chars = {int(spec, 16)}
|
||||
return chars, rest
|
||||
return chars
|
||||
|
||||
|
||||
all_emoji = set()
|
||||
emoji_categories = {}
|
||||
emoji_presentation_bases = set()
|
||||
def split_two(line: str) -> Tuple[Set[int], str]:
|
||||
spec, rest = line.split(';', 1)
|
||||
spec, rest = spec.strip(), rest.strip().split(' ', 1)[0].strip()
|
||||
return parse_range_spec(spec), rest
|
||||
|
||||
|
||||
def parse_emoji():
|
||||
for line in get_data('emoji-data.txt', 'emoji'):
|
||||
chars, rest = split_two(line)
|
||||
s = emoji_categories.setdefault(rest, set())
|
||||
s.update(chars)
|
||||
all_emoji.update(chars)
|
||||
for line in get_data('emoji-variation-sequences.txt', 'emoji'):
|
||||
base, var, *rest = line.split()
|
||||
if base.startswith('#'):
|
||||
all_emoji: Set[int] = set()
|
||||
emoji_presentation_bases: Set[int] = set()
|
||||
narrow_emoji: Set[int] = set()
|
||||
wide_emoji: Set[int] = set()
|
||||
flags: Dict[int, List[int]] = {}
|
||||
|
||||
|
||||
def parse_basic_emoji(spec: str) -> None:
|
||||
parts = list(filter(None, spec.split()))
|
||||
has_emoji_presentation = len(parts) < 2
|
||||
chars = parse_range_spec(parts[0])
|
||||
all_emoji.update(chars)
|
||||
emoji_presentation_bases.update(chars)
|
||||
(wide_emoji if has_emoji_presentation else narrow_emoji).update(chars)
|
||||
|
||||
|
||||
def parse_keycap_sequence(spec: str) -> None:
|
||||
base, fe0f, cc = list(filter(None, spec.split()))
|
||||
chars = parse_range_spec(base)
|
||||
all_emoji.update(chars)
|
||||
emoji_presentation_bases.update(chars)
|
||||
narrow_emoji.update(chars)
|
||||
|
||||
|
||||
def parse_flag_emoji_sequence(spec: str) -> None:
|
||||
a, b = list(filter(None, spec.split()))
|
||||
left, right = int(a, 16), int(b, 16)
|
||||
chars = {left, right}
|
||||
all_emoji.update(chars)
|
||||
wide_emoji.update(chars)
|
||||
emoji_presentation_bases.update(chars)
|
||||
flags.setdefault(left, []).append(right)
|
||||
|
||||
|
||||
def parse_emoji_tag_sequence(spec: str) -> None:
|
||||
a = int(spec.split()[0], 16)
|
||||
all_emoji.add(a)
|
||||
wide_emoji.add(a)
|
||||
emoji_presentation_bases.add(a)
|
||||
|
||||
|
||||
def parse_emoji_modifier_sequence(spec: str) -> None:
|
||||
a, b = list(filter(None, spec.split()))
|
||||
char, mod = int(a, 16), int(b, 16)
|
||||
mod
|
||||
all_emoji.add(char)
|
||||
wide_emoji.add(char)
|
||||
emoji_presentation_bases.add(char)
|
||||
|
||||
|
||||
def parse_emoji() -> None:
|
||||
for line in get_data('emoji-sequences.txt', 'emoji'):
|
||||
parts = [x.strip() for x in line.split(';')]
|
||||
if len(parts) < 2:
|
||||
continue
|
||||
base = int(base, 16)
|
||||
if var.upper() == 'FE0F':
|
||||
emoji_presentation_bases.add(base)
|
||||
data, etype = parts[:2]
|
||||
if etype == 'Basic_Emoji':
|
||||
parse_basic_emoji(data)
|
||||
elif etype == 'Emoji_Keycap_Sequence':
|
||||
parse_keycap_sequence(data)
|
||||
elif etype == 'RGI_Emoji_Flag_Sequence':
|
||||
parse_flag_emoji_sequence(data)
|
||||
elif etype == 'RGI_Emoji_Tag_Sequence':
|
||||
parse_emoji_tag_sequence(data)
|
||||
elif etype == 'RGI_Emoji_Modifier_Sequence':
|
||||
parse_emoji_modifier_sequence(data)
|
||||
|
||||
|
||||
doublewidth, ambiguous = set(), set()
|
||||
doublewidth: Set[int] = set()
|
||||
ambiguous: Set[int] = set()
|
||||
|
||||
|
||||
def parse_eaw():
|
||||
def parse_eaw() -> None:
|
||||
global doublewidth, ambiguous
|
||||
seen = set()
|
||||
seen: Set[int] = set()
|
||||
for line in get_data('ucd/EastAsianWidth.txt'):
|
||||
chars, eaw = split_two(line)
|
||||
if eaw == 'A':
|
||||
@@ -151,7 +210,7 @@ def parse_eaw():
|
||||
doublewidth |= set(range(0x30000, 0x3FFFD + 1)) - seen
|
||||
|
||||
|
||||
def get_ranges(items):
|
||||
def get_ranges(items: List[int]) -> Generator[Union[int, Tuple[int, int]], None, None]:
|
||||
items.sort()
|
||||
for k, g in groupby(enumerate(items), lambda m: m[0]-m[1]):
|
||||
group = tuple(map(itemgetter(1), g))
|
||||
@@ -162,7 +221,7 @@ def get_ranges(items):
|
||||
yield a, b
|
||||
|
||||
|
||||
def write_case(spec, p):
|
||||
def write_case(spec: Union[Tuple, int], p: Callable) -> None:
|
||||
if isinstance(spec, tuple):
|
||||
p('\t\tcase 0x{:x} ... 0x{:x}:'.format(*spec))
|
||||
else:
|
||||
@@ -170,7 +229,7 @@ def write_case(spec, p):
|
||||
|
||||
|
||||
@contextmanager
|
||||
def create_header(path, include_data_types=True):
|
||||
def create_header(path: str, include_data_types: bool = True) -> Generator[Callable, None, None]:
|
||||
with open(path, 'w') as f:
|
||||
p = partial(print, file=f)
|
||||
p('// unicode data, built from the unicode standard on:', date.today())
|
||||
@@ -187,7 +246,7 @@ def create_header(path, include_data_types=True):
|
||||
p('END_ALLOW_CASE_RANGE')
|
||||
|
||||
|
||||
def gen_emoji():
|
||||
def gen_emoji() -> None:
|
||||
with create_header('kitty/emoji.h') as p:
|
||||
p('static inline bool\nis_emoji(char_type code) {')
|
||||
p('\tswitch(code) {')
|
||||
@@ -198,15 +257,6 @@ def gen_emoji():
|
||||
p('\t}')
|
||||
p('\treturn false;\n}')
|
||||
|
||||
p('static inline bool\nis_emoji_modifier(char_type code) {')
|
||||
p('\tswitch(code) {')
|
||||
for spec in get_ranges(list(emoji_categories['Emoji_Modifier'])):
|
||||
write_case(spec, p)
|
||||
p('\t\t\treturn true;')
|
||||
p('\t\tdefault: return false;')
|
||||
p('\t}')
|
||||
p('\treturn false;\n}')
|
||||
|
||||
p('static inline bool\nis_symbol(char_type code) {')
|
||||
p('\tswitch(code) {')
|
||||
for spec in get_ranges(list(all_symbols)):
|
||||
@@ -217,9 +267,17 @@ def gen_emoji():
|
||||
p('\treturn false;\n}')
|
||||
|
||||
|
||||
def category_test(name, p, classes, comment, static=False, extra_chars=frozenset(), exclude=frozenset()):
|
||||
static = 'static inline ' if static else ''
|
||||
chars = set()
|
||||
def category_test(
|
||||
name: str,
|
||||
p: Callable,
|
||||
classes: Iterable[str],
|
||||
comment: str,
|
||||
use_static: bool = False,
|
||||
extra_chars: Union[FrozenSet[int], Set[int]] = frozenset(),
|
||||
exclude: Union[Set[int], FrozenSet[int]] = frozenset()
|
||||
) -> None:
|
||||
static = 'static inline ' if use_static else ''
|
||||
chars: Set[int] = set()
|
||||
for c in classes:
|
||||
chars |= class_maps[c]
|
||||
chars |= extra_chars
|
||||
@@ -229,12 +287,12 @@ def category_test(name, p, classes, comment, static=False, extra_chars=frozenset
|
||||
p('\tswitch(code) {')
|
||||
for spec in get_ranges(list(chars)):
|
||||
write_case(spec, p)
|
||||
p(f'\t\t\treturn true;')
|
||||
p('\t\t\treturn true;')
|
||||
p('\t} // }}}\n')
|
||||
p('\treturn false;\n}\n')
|
||||
|
||||
|
||||
def codepoint_to_mark_map(p, mark_map):
|
||||
def codepoint_to_mark_map(p: Callable, mark_map: List[int]) -> Dict[int, int]:
|
||||
p('\tswitch(c) { // {{{')
|
||||
rmap = {c: m for m, c in enumerate(mark_map)}
|
||||
for spec in get_ranges(mark_map):
|
||||
@@ -249,14 +307,14 @@ def codepoint_to_mark_map(p, mark_map):
|
||||
return rmap
|
||||
|
||||
|
||||
def classes_to_regex(classes, exclude=''):
|
||||
chars = set()
|
||||
def classes_to_regex(classes: Iterable[str], exclude: str = '') -> Iterable[str]:
|
||||
chars: Set[int] = set()
|
||||
for c in classes:
|
||||
chars |= class_maps[c]
|
||||
for c in map(ord, exclude):
|
||||
chars.discard(c)
|
||||
for x in map(ord, exclude):
|
||||
chars.discard(x)
|
||||
|
||||
def as_string(codepoint):
|
||||
def as_string(codepoint: int) -> str:
|
||||
if codepoint < 256:
|
||||
return r'\x{:02x}'.format(codepoint)
|
||||
if codepoint <= 0xffff:
|
||||
@@ -270,7 +328,7 @@ def classes_to_regex(classes, exclude=''):
|
||||
yield as_string(spec)
|
||||
|
||||
|
||||
def gen_ucd():
|
||||
def gen_ucd() -> None:
|
||||
cz = {c for c in class_maps if c[0] in 'CZ'}
|
||||
with create_header('kitty/unicode-data.c') as p:
|
||||
p('#include "unicode-data.h"')
|
||||
@@ -298,7 +356,9 @@ def gen_ucd():
|
||||
p('}\n')
|
||||
with open('kitty/unicode-data.h') as f:
|
||||
unicode_data = f.read()
|
||||
expected = int(re.search(r'^#define VS15 (\d+)', unicode_data, re.M).group(1))
|
||||
m = re.search(r'^#define VS15 (\d+)', unicode_data, re.M)
|
||||
if m is not None:
|
||||
expected = int(m.group(1))
|
||||
if rmap[0xfe0e] != expected:
|
||||
raise ValueError('The mark for 0xfe0e has changed, you have to update VS15 to {} and VS16 to {} in unicode-data.h'.format(
|
||||
rmap[0xfe0e], rmap[0xfe0f]
|
||||
@@ -307,7 +367,7 @@ def gen_ucd():
|
||||
f.write("url_delimiters = '{}' # noqa".format(''.join(classes_to_regex(cz, exclude='\n'))))
|
||||
|
||||
|
||||
def gen_names():
|
||||
def gen_names() -> None:
|
||||
with create_header('kittens/unicode_input/names.h') as p:
|
||||
mark_to_cp = list(sorted(name_map))
|
||||
cp_to_mark = {cp: m for m, cp in enumerate(mark_to_cp)}
|
||||
@@ -361,29 +421,29 @@ def gen_names():
|
||||
p('}; // }}}\n')
|
||||
|
||||
# The trie
|
||||
p(f'typedef struct {{ uint32_t children_offset; uint32_t match_offset; }} word_trie;\n')
|
||||
all_trie_nodes = []
|
||||
p('typedef struct {{ uint32_t children_offset; uint32_t match_offset; }} word_trie;\n')
|
||||
all_trie_nodes: List['TrieNode'] = [] # noqa
|
||||
|
||||
class TrieNode:
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
self.match_offset = 0
|
||||
self.children_offset = 0
|
||||
self.children = {}
|
||||
self.children: Dict[int, int] = {}
|
||||
|
||||
def add_letter(self, letter):
|
||||
def add_letter(self, letter: int) -> int:
|
||||
if letter not in self.children:
|
||||
self.children[letter] = len(all_trie_nodes)
|
||||
all_trie_nodes.append(TrieNode())
|
||||
return self.children[letter]
|
||||
|
||||
def __str__(self):
|
||||
def __str__(self) -> str:
|
||||
return f'{{ .children_offset={self.children_offset}, .match_offset={self.match_offset} }}'
|
||||
|
||||
root = TrieNode()
|
||||
all_trie_nodes.append(root)
|
||||
|
||||
def add_word(word_idx, word):
|
||||
def add_word(word_idx: int, word: str) -> None:
|
||||
parent = root
|
||||
for letter in map(ord, word):
|
||||
idx = parent.add_letter(letter)
|
||||
@@ -408,10 +468,10 @@ def gen_names():
|
||||
p('}; // }}}\n')
|
||||
|
||||
|
||||
def gen_wcwidth():
|
||||
seen = set()
|
||||
def gen_wcwidth() -> None:
|
||||
seen: Set[int] = set()
|
||||
|
||||
def add(p, comment, chars_, ret):
|
||||
def add(p: Callable, comment: str, chars_: Union[Set[int], FrozenSet[int]], ret: int) -> None:
|
||||
chars = chars_ - seen
|
||||
seen.update(chars)
|
||||
p(f'\t\t// {comment} ({len(chars)} codepoints)' + ' {{' '{')
|
||||
@@ -425,13 +485,14 @@ def gen_wcwidth():
|
||||
p('\tswitch(code) {')
|
||||
|
||||
non_printing = class_maps['Cc'] | class_maps['Cf'] | class_maps['Cs']
|
||||
add(p, 'Flags', flag_codepoints, 2)
|
||||
add(p, 'Marks', marks | {0}, 0)
|
||||
add(p, 'Non-printing characters', non_printing, -1)
|
||||
add(p, 'Private use', class_maps['Co'], -3)
|
||||
add(p, 'Text Presentation', emoji_categories['Emoji'] - emoji_categories['Emoji_Presentation'], 1)
|
||||
add(p, 'Text Presentation', narrow_emoji, 1)
|
||||
add(p, 'East Asian ambiguous width', ambiguous, -2)
|
||||
add(p, 'East Asian double width', doublewidth, 2)
|
||||
add(p, 'Emoji Presentation', emoji_categories['Emoji_Presentation'], 2)
|
||||
add(p, 'Emoji Presentation', wide_emoji, 2)
|
||||
|
||||
add(p, 'Not assigned in the unicode character database', not_assigned, -4)
|
||||
|
||||
|
||||
0
glfw/__init__.py
Normal file
0
glfw/__init__.py
Normal file
71
glfw/backend_utils.c
vendored
71
glfw/backend_utils.c
vendored
@@ -163,6 +163,14 @@ prepareForPoll(EventLoopData *eld, monotonic_t timeout) {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
static inline struct timespec
|
||||
calc_time(monotonic_t nsec) {
|
||||
struct timespec result;
|
||||
result.tv_sec = nsec / (1000LL * 1000LL * 1000LL);
|
||||
result.tv_nsec = nsec % (1000LL * 1000LL * 1000LL);
|
||||
return result;
|
||||
}
|
||||
|
||||
int
|
||||
pollWithTimeout(struct pollfd *fds, nfds_t nfds, monotonic_t timeout) {
|
||||
struct timespec tv = calc_time(timeout);
|
||||
@@ -284,7 +292,7 @@ finalizePollData(EventLoopData *eld) {
|
||||
}
|
||||
|
||||
int
|
||||
pollForEvents(EventLoopData *eld, monotonic_t timeout) {
|
||||
pollForEvents(EventLoopData *eld, monotonic_t timeout, watch_callback_func display_callback) {
|
||||
int read_ok = 0;
|
||||
timeout = prepareForPoll(eld, timeout);
|
||||
EVDBG("pollForEvents final timeout: %.3f", monotonic_t_to_s_double(timeout));
|
||||
@@ -297,6 +305,7 @@ pollForEvents(EventLoopData *eld, monotonic_t timeout) {
|
||||
errno = 0;
|
||||
result = pollWithTimeout(eld->fds, eld->watches_count, timeout);
|
||||
int saved_errno = errno;
|
||||
if (display_callback) display_callback(result, eld->fds[0].revents && eld->watches[0].events, NULL);
|
||||
dispatchTimers(eld);
|
||||
if (result > 0) {
|
||||
dispatchEvents(eld);
|
||||
@@ -311,6 +320,7 @@ pollForEvents(EventLoopData *eld, monotonic_t timeout) {
|
||||
errno = 0;
|
||||
result = poll(eld->fds, eld->watches_count, -1);
|
||||
int saved_errno = errno;
|
||||
if (display_callback) display_callback(result, eld->fds[0].revents && eld->watches[0].events, NULL);
|
||||
dispatchTimers(eld);
|
||||
if (result > 0) {
|
||||
dispatchEvents(eld);
|
||||
@@ -323,53 +333,20 @@ pollForEvents(EventLoopData *eld, monotonic_t timeout) {
|
||||
return read_ok;
|
||||
}
|
||||
|
||||
// Splits and translates a text/uri-list into separate file paths
|
||||
// NOTE: This function destroys the provided string
|
||||
//
|
||||
char** parseUriList(char* text, int* count)
|
||||
{
|
||||
const char* prefix = "file://";
|
||||
char** paths = NULL;
|
||||
char* line;
|
||||
|
||||
*count = 0;
|
||||
|
||||
while ((line = strtok(text, "\r\n")))
|
||||
{
|
||||
text = NULL;
|
||||
|
||||
if (line[0] == '#')
|
||||
continue;
|
||||
|
||||
if (strncmp(line, prefix, strlen(prefix)) == 0)
|
||||
{
|
||||
line += strlen(prefix);
|
||||
// TODO: Validate hostname
|
||||
while (*line != '/')
|
||||
line++;
|
||||
}
|
||||
|
||||
(*count)++;
|
||||
|
||||
char* path = calloc(strlen(line) + 1, 1);
|
||||
paths = realloc(paths, *count * sizeof(char*));
|
||||
paths[*count - 1] = path;
|
||||
|
||||
while (*line)
|
||||
{
|
||||
if (line[0] == '%' && line[1] && line[2])
|
||||
{
|
||||
const char digits[3] = { line[1], line[2], '\0' };
|
||||
*path = strtol(digits, NULL, 16);
|
||||
line += 2;
|
||||
}
|
||||
else
|
||||
*path = *line;
|
||||
|
||||
path++;
|
||||
line++;
|
||||
// Duplicate a UTF-8 encoded string
|
||||
// but cut it so that it has at most max_length bytes plus the null byte.
|
||||
// This does not take combining characters into account.
|
||||
GLFWAPI char* utf_8_strndup(const char* source, size_t max_length) {
|
||||
if (!source) return NULL;
|
||||
size_t length = strnlen(source, max_length);
|
||||
if (length >= max_length) {
|
||||
for (length = max_length; length > 0; length--) {
|
||||
if ((source[length] & 0xC0) != 0x80) break;
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
char* result = malloc(length + 1);
|
||||
memcpy(result, source, length);
|
||||
result[length] = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
4
glfw/backend_utils.h
vendored
4
glfw/backend_utils.h
vendored
@@ -90,9 +90,9 @@ void toggleTimer(EventLoopData *eld, id_type timer_id, int enabled);
|
||||
void changeTimerInterval(EventLoopData *eld, id_type timer_id, monotonic_t interval);
|
||||
monotonic_t prepareForPoll(EventLoopData *eld, monotonic_t timeout);
|
||||
int pollWithTimeout(struct pollfd *fds, nfds_t nfds, monotonic_t timeout);
|
||||
int pollForEvents(EventLoopData *eld, monotonic_t timeout);
|
||||
int pollForEvents(EventLoopData *eld, monotonic_t timeout, watch_callback_func);
|
||||
unsigned dispatchTimers(EventLoopData *eld);
|
||||
void finalizePollData(EventLoopData *eld);
|
||||
bool initPollData(EventLoopData *eld, int display_fd);
|
||||
char** parseUriList(char* text, int* count);
|
||||
void wakeupEventLoop(EventLoopData *eld);
|
||||
char* utf_8_strndup(const char* source, size_t max_length);
|
||||
|
||||
@@ -424,11 +424,7 @@ display_reconfigured(CGDirectDisplayID display UNUSED, CGDisplayChangeSummaryFla
|
||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
|
||||
{
|
||||
(void)sender;
|
||||
_GLFWwindow* window;
|
||||
|
||||
for (window = _glfw.windowListHead; window; window = window->next)
|
||||
_glfwInputWindowCloseRequest(window);
|
||||
|
||||
if (_glfw.callbacks.application_close) _glfw.callbacks.application_close(0);
|
||||
return NSTerminateCancel;
|
||||
}
|
||||
|
||||
@@ -466,9 +462,8 @@ static GLFWapplicationwillfinishlaunchingfun finish_launching_callback = NULL;
|
||||
// In case we are unbundled, make us a proper UI application
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
|
||||
// Menu bar setup must go between sharedApplication above and
|
||||
// finishLaunching below, in order to properly emulate the behavior
|
||||
// of NSApplicationMain
|
||||
// Menu bar setup must go between sharedApplication and finishLaunching
|
||||
// in order to properly emulate the behavior of NSApplicationMain
|
||||
|
||||
if ([[NSBundle mainBundle] pathForResource:@"MainMenu" ofType:@"nib"])
|
||||
{
|
||||
@@ -530,6 +525,32 @@ static GLFWapplicationwillfinishlaunchingfun finish_launching_callback = NULL;
|
||||
@end
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW internal API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void* _glfwLoadLocalVulkanLoaderNS(void)
|
||||
{
|
||||
CFBundleRef bundle = CFBundleGetMainBundle();
|
||||
if (!bundle)
|
||||
return NULL;
|
||||
|
||||
CFURLRef url =
|
||||
CFBundleCopyAuxiliaryExecutableURL(bundle, CFSTR("libvulkan.1.dylib"));
|
||||
if (!url)
|
||||
return NULL;
|
||||
|
||||
char path[PATH_MAX];
|
||||
void* handle = NULL;
|
||||
|
||||
if (CFURLGetFileSystemRepresentation(url, true, (UInt8*) path, sizeof(path) - 1))
|
||||
handle = _glfw_dlopen(path);
|
||||
|
||||
CFRelease(url);
|
||||
return handle;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW platform API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
@@ -648,7 +669,6 @@ int _glfwPlatformInit(void)
|
||||
if (!initializeTIS())
|
||||
return false;
|
||||
|
||||
_glfwInitTimerNS();
|
||||
_glfwInitJoysticksNS();
|
||||
|
||||
_glfwPollMonitorsNS();
|
||||
|
||||
@@ -146,7 +146,7 @@ static bool modeIsGood(CGDisplayModeRef mode)
|
||||
// Convert Core Graphics display mode to GLFW video mode
|
||||
//
|
||||
static GLFWvidmode vidmodeFromCGDisplayMode(CGDisplayModeRef mode,
|
||||
CVDisplayLinkRef link)
|
||||
double fallbackRefreshRate)
|
||||
{
|
||||
GLFWvidmode result;
|
||||
result.width = (int) CGDisplayModeGetWidth(mode);
|
||||
@@ -154,11 +154,7 @@ static GLFWvidmode vidmodeFromCGDisplayMode(CGDisplayModeRef mode,
|
||||
result.refreshRate = (int) round(CGDisplayModeGetRefreshRate(mode));
|
||||
|
||||
if (result.refreshRate == 0)
|
||||
{
|
||||
const CVTime time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(link);
|
||||
if (!(time.flags & kCVTimeIsIndefinite))
|
||||
result.refreshRate = (int) (time.timeScale / (double) time.timeValue);
|
||||
}
|
||||
result.refreshRate = (int) round(fallbackRefreshRate);
|
||||
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED <= 101100
|
||||
CFStringRef format = CGDisplayModeCopyPixelEncoding(mode);
|
||||
@@ -240,6 +236,69 @@ static bool refreshMonitorScreen(_GLFWmonitor* monitor)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns the display refresh rate queried from the I/O registry
|
||||
//
|
||||
static double getFallbackRefreshRate(CGDirectDisplayID displayID)
|
||||
{
|
||||
double refreshRate = 60.0;
|
||||
|
||||
io_iterator_t it;
|
||||
io_service_t service;
|
||||
|
||||
if (IOServiceGetMatchingServices(kIOMasterPortDefault,
|
||||
IOServiceMatching("IOFramebuffer"),
|
||||
&it) != 0)
|
||||
{
|
||||
return refreshRate;
|
||||
}
|
||||
|
||||
while ((service = IOIteratorNext(it)) != 0)
|
||||
{
|
||||
const CFNumberRef indexRef =
|
||||
IORegistryEntryCreateCFProperty(service,
|
||||
CFSTR("IOFramebufferOpenGLIndex"),
|
||||
kCFAllocatorDefault,
|
||||
kNilOptions);
|
||||
if (!indexRef)
|
||||
continue;
|
||||
|
||||
uint32_t index = 0;
|
||||
CFNumberGetValue(indexRef, kCFNumberIntType, &index);
|
||||
CFRelease(indexRef);
|
||||
|
||||
if (CGOpenGLDisplayMaskToDisplayID(1 << index) != displayID)
|
||||
continue;
|
||||
|
||||
const CFNumberRef clockRef =
|
||||
IORegistryEntryCreateCFProperty(service,
|
||||
CFSTR("IOFBCurrentPixelClock"),
|
||||
kCFAllocatorDefault,
|
||||
kNilOptions);
|
||||
const CFNumberRef countRef =
|
||||
IORegistryEntryCreateCFProperty(service,
|
||||
CFSTR("IOFBCurrentPixelCount"),
|
||||
kCFAllocatorDefault,
|
||||
kNilOptions);
|
||||
if (!clockRef || !countRef)
|
||||
break;
|
||||
|
||||
uint32_t clock = 0, count = 0;
|
||||
CFNumberGetValue(clockRef, kCFNumberIntType, &clock);
|
||||
CFNumberGetValue(countRef, kCFNumberIntType, &count);
|
||||
CFRelease(clockRef);
|
||||
CFRelease(countRef);
|
||||
|
||||
if (clock > 0 && count > 0)
|
||||
refreshRate = clock / (double) count;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
IOObjectRelease(it);
|
||||
return refreshRate;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW internal API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
@@ -335,6 +394,11 @@ void _glfwPollMonitorsNS(void)
|
||||
|
||||
free(name);
|
||||
|
||||
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(displays[i]);
|
||||
if (CGDisplayModeGetRefreshRate(mode) == 0.0)
|
||||
monitor->ns.fallbackRefreshRate = getFallbackRefreshRate(displays[i]);
|
||||
CGDisplayModeRelease(mode);
|
||||
|
||||
_glfwInputMonitor(monitor, GLFW_CONNECTED, _GLFW_INSERT_LAST);
|
||||
}
|
||||
|
||||
@@ -360,9 +424,6 @@ void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired)
|
||||
if (_glfwCompareVideoModes(¤t, best) == 0)
|
||||
return;
|
||||
|
||||
CVDisplayLinkRef link;
|
||||
CVDisplayLinkCreateWithCGDisplay(monitor->ns.displayID, &link);
|
||||
|
||||
CFArrayRef modes = CGDisplayCopyAllDisplayModes(monitor->ns.displayID, NULL);
|
||||
const CFIndex count = CFArrayGetCount(modes);
|
||||
CGDisplayModeRef native = NULL;
|
||||
@@ -373,7 +434,8 @@ void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired)
|
||||
if (!modeIsGood(dm))
|
||||
continue;
|
||||
|
||||
const GLFWvidmode mode = vidmodeFromCGDisplayMode(dm, link);
|
||||
const GLFWvidmode mode =
|
||||
vidmodeFromCGDisplayMode(dm, monitor->ns.fallbackRefreshRate);
|
||||
if (_glfwCompareVideoModes(best, &mode) == 0)
|
||||
{
|
||||
native = dm;
|
||||
@@ -392,7 +454,6 @@ void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired)
|
||||
}
|
||||
|
||||
CFRelease(modes);
|
||||
CVDisplayLinkRelease(link);
|
||||
}
|
||||
|
||||
// Restore the previously saved (original) video mode
|
||||
@@ -469,9 +530,6 @@ GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)
|
||||
{
|
||||
*count = 0;
|
||||
|
||||
CVDisplayLinkRef link;
|
||||
CVDisplayLinkCreateWithCGDisplay(monitor->ns.displayID, &link);
|
||||
|
||||
CFArrayRef modes = CGDisplayCopyAllDisplayModes(monitor->ns.displayID, NULL);
|
||||
const CFIndex found = CFArrayGetCount(modes);
|
||||
GLFWvidmode* result = calloc(found, sizeof(GLFWvidmode));
|
||||
@@ -482,7 +540,8 @@ GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)
|
||||
if (!modeIsGood(dm))
|
||||
continue;
|
||||
|
||||
const GLFWvidmode mode = vidmodeFromCGDisplayMode(dm, link);
|
||||
const GLFWvidmode mode =
|
||||
vidmodeFromCGDisplayMode(dm, monitor->ns.fallbackRefreshRate);
|
||||
|
||||
for (CFIndex j = 0; j < *count; j++)
|
||||
{
|
||||
@@ -499,20 +558,14 @@ GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)
|
||||
}
|
||||
|
||||
CFRelease(modes);
|
||||
CVDisplayLinkRelease(link);
|
||||
return result;
|
||||
}
|
||||
|
||||
void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode *mode)
|
||||
{
|
||||
CVDisplayLinkRef link;
|
||||
CVDisplayLinkCreateWithCGDisplay(monitor->ns.displayID, &link);
|
||||
|
||||
CGDisplayModeRef native = CGDisplayCopyDisplayMode(monitor->ns.displayID);
|
||||
*mode = vidmodeFromCGDisplayMode(native, link);
|
||||
*mode = vidmodeFromCGDisplayMode(native, monitor->ns.fallbackRefreshRate);
|
||||
CGDisplayModeRelease(native);
|
||||
|
||||
CVDisplayLinkRelease(link);
|
||||
}
|
||||
|
||||
bool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
|
||||
20
glfw/cocoa_platform.h
vendored
20
glfw/cocoa_platform.h
vendored
@@ -65,13 +65,15 @@ typedef void* CVDisplayLinkRef;
|
||||
#endif
|
||||
|
||||
|
||||
typedef VkFlags VkMacOSSurfaceCreateFlagsMVK;
|
||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int, unsigned long);
|
||||
typedef bool (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||
typedef void (* GLFWapplicationwillfinishlaunchingfun)(void);
|
||||
typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
|
||||
typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);
|
||||
|
||||
typedef VkFlags VkMacOSSurfaceCreateFlagsMVK;
|
||||
typedef VkFlags VkMetalSurfaceCreateFlagsEXT;
|
||||
|
||||
typedef struct VkMacOSSurfaceCreateInfoMVK
|
||||
{
|
||||
VkStructureType sType;
|
||||
@@ -80,7 +82,16 @@ typedef struct VkMacOSSurfaceCreateInfoMVK
|
||||
const void* pView;
|
||||
} VkMacOSSurfaceCreateInfoMVK;
|
||||
|
||||
typedef struct VkMetalSurfaceCreateInfoEXT
|
||||
{
|
||||
VkStructureType sType;
|
||||
const void* pNext;
|
||||
VkMetalSurfaceCreateFlagsEXT flags;
|
||||
const void* pLayer;
|
||||
} VkMetalSurfaceCreateInfoEXT;
|
||||
|
||||
typedef VkResult (APIENTRY *PFN_vkCreateMacOSSurfaceMVK)(VkInstance,const VkMacOSSurfaceCreateInfoMVK*,const VkAllocationCallbacks*,VkSurfaceKHR*);
|
||||
typedef VkResult (APIENTRY *PFN_vkCreateMetalSurfaceEXT)(VkInstance,const VkMetalSurfaceCreateInfoEXT*,const VkAllocationCallbacks*,VkSurfaceKHR*);
|
||||
|
||||
#include "posix_thread.h"
|
||||
#include "cocoa_joystick.h"
|
||||
@@ -92,7 +103,7 @@ typedef VkResult (APIENTRY *PFN_vkCreateMacOSSurfaceMVK)(VkInstance,const VkMacO
|
||||
#define _glfw_dlclose(handle) dlclose(handle)
|
||||
#define _glfw_dlsym(handle, name) dlsym(handle, name)
|
||||
|
||||
#define _GLFW_EGL_NATIVE_WINDOW ((EGLNativeWindowType) window->ns.view)
|
||||
#define _GLFW_EGL_NATIVE_WINDOW ((EGLNativeWindowType) window->ns.layer)
|
||||
#define _GLFW_EGL_NATIVE_DISPLAY EGL_DEFAULT_DISPLAY
|
||||
|
||||
#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowNS ns
|
||||
@@ -201,6 +212,7 @@ typedef struct _GLFWmonitorNS
|
||||
CGDisplayModeRef previousMode;
|
||||
uint32_t unitNumber;
|
||||
id screen;
|
||||
double fallbackRefreshRate;
|
||||
|
||||
} _GLFWmonitorNS;
|
||||
|
||||
@@ -221,14 +233,14 @@ typedef struct _GLFWtimerNS
|
||||
} _GLFWtimerNS;
|
||||
|
||||
|
||||
void _glfwInitTimerNS(void);
|
||||
|
||||
void _glfwPollMonitorsNS(void);
|
||||
void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired);
|
||||
void _glfwRestoreVideoModeNS(_GLFWmonitor* monitor);
|
||||
|
||||
float _glfwTransformYNS(float y);
|
||||
|
||||
void* _glfwLoadLocalVulkanLoaderNS(void);
|
||||
|
||||
void _glfwClearDisplayLinks(void);
|
||||
void _glfwRestartDisplayLinks(void);
|
||||
void _glfwDispatchTickCallback(void);
|
||||
|
||||
61
glfw/cocoa_time.c
vendored
61
glfw/cocoa_time.c
vendored
@@ -1,61 +0,0 @@
|
||||
//========================================================================
|
||||
// GLFW 3.4 macOS - www.glfw.org
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright (c) 2009-2016 Camilla Löwy <elmindreda@glfw.org>
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would
|
||||
// be appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such, and must not
|
||||
// be misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source
|
||||
// distribution.
|
||||
//
|
||||
//========================================================================
|
||||
// It is fine to use C99 in this file because it will not be built with VS
|
||||
//========================================================================
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
#include <mach/mach_time.h>
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW internal API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Initialise timer
|
||||
//
|
||||
void _glfwInitTimerNS(void)
|
||||
{
|
||||
mach_timebase_info_data_t info;
|
||||
mach_timebase_info(&info);
|
||||
|
||||
_glfw.timer.ns.frequency = (unsigned long long)((info.denom * 1e9) / info.numer);
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW platform API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
uint64_t _glfwPlatformGetTimerValue(void)
|
||||
{
|
||||
return mach_absolute_time();
|
||||
}
|
||||
|
||||
uint64_t _glfwPlatformGetTimerFrequency(void)
|
||||
{
|
||||
return _glfw.timer.ns.frequency;
|
||||
}
|
||||
@@ -410,6 +410,7 @@ static int translateKey(unsigned int key, bool apply_keymap)
|
||||
K('[', LEFT_BRACKET);
|
||||
K('\\', BACKSLASH);
|
||||
K(']', RIGHT_BRACKET);
|
||||
K('^', CIRCUMFLEX);
|
||||
K('_', UNDERSCORE);
|
||||
K('`', GRAVE_ACCENT);
|
||||
K(PARAGRAPH_UTF_8, PARAGRAPH);
|
||||
@@ -658,6 +659,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
||||
NSTrackingArea* trackingArea;
|
||||
NSMutableAttributedString* markedText;
|
||||
NSRect markedRect;
|
||||
NSString *input_source_at_last_key_event;
|
||||
}
|
||||
|
||||
- (void) removeGLFWWindow;
|
||||
@@ -676,6 +678,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
||||
trackingArea = nil;
|
||||
markedText = [[NSMutableAttributedString alloc] init];
|
||||
markedRect = NSMakeRect(0.0, 0.0, 0.0, 0.0);
|
||||
input_source_at_last_key_event = nil;
|
||||
|
||||
[self updateTrackingAreas];
|
||||
[self registerForDraggedTypes:@[NSPasteboardTypeFileURL, NSPasteboardTypeString]];
|
||||
@@ -688,6 +691,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
||||
{
|
||||
[trackingArea release];
|
||||
[markedText release];
|
||||
if (input_source_at_last_key_event) [input_source_at_last_key_event release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@@ -964,12 +968,19 @@ is_ascii_control_char(char x) {
|
||||
|
||||
- (void)keyDown:(NSEvent *)event
|
||||
{
|
||||
const bool previous_has_marked_text = [self hasMarkedText];
|
||||
bool input_source_changed = false;
|
||||
NSTextInputContext *inpctx = [NSTextInputContext currentInputContext];
|
||||
if (inpctx && (!input_source_at_last_key_event || ![input_source_at_last_key_event isEqualToString:inpctx.selectedKeyboardInputSource])) {
|
||||
input_source_at_last_key_event = [inpctx.selectedKeyboardInputSource retain];
|
||||
input_source_changed = true;
|
||||
}
|
||||
|
||||
const unsigned int keycode = [event keyCode];
|
||||
const NSUInteger flags = [event modifierFlags];
|
||||
const int mods = translateFlags(flags);
|
||||
const int key = translateKey(keycode, true);
|
||||
const bool process_text = !window->ns.textInputFilterCallback || window->ns.textInputFilterCallback(key, mods, keycode, flags) != 1;
|
||||
const bool previous_has_marked_text = [self hasMarkedText];
|
||||
[self unmarkText];
|
||||
_glfw.ns.text[0] = 0;
|
||||
GLFWkeyevent glfw_keyevent;
|
||||
@@ -983,6 +994,14 @@ is_ascii_control_char(char x) {
|
||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||
}
|
||||
} else {
|
||||
if (input_source_changed) {
|
||||
debug_key(@"Input source changed, clearing pre-edit text and resetting deadkey state\n");
|
||||
glfw_keyevent.text = NULL;
|
||||
glfw_keyevent.ime_state = 1;
|
||||
window->ns.deadKeyState = 0;
|
||||
_glfwInputKeyboard(window, &glfw_keyevent); // clear pre-edit text
|
||||
}
|
||||
|
||||
static UniChar text[256];
|
||||
UniCharCount char_count = 0;
|
||||
const bool in_compose_sequence = window->ns.deadKeyState != 0;
|
||||
@@ -1114,7 +1133,7 @@ is_ascii_control_char(char x) {
|
||||
break;
|
||||
}
|
||||
|
||||
_glfwInputScroll(window, deltaX, deltaY, flags);
|
||||
_glfwInputScroll(window, deltaX, deltaY, flags, translateFlags([event modifierFlags]));
|
||||
}
|
||||
|
||||
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
|
||||
@@ -1140,27 +1159,20 @@ is_ascii_control_char(char x) {
|
||||
const NSUInteger count = [objs count];
|
||||
if (count)
|
||||
{
|
||||
char** paths = calloc(count, sizeof(char*));
|
||||
|
||||
for (NSUInteger i = 0; i < count; i++)
|
||||
{
|
||||
id obj = objs[i];
|
||||
if ([obj isKindOfClass:[NSURL class]]) {
|
||||
paths[i] = _glfw_strdup([obj fileSystemRepresentation]);
|
||||
const char *path = [obj fileSystemRepresentation];
|
||||
_glfwInputDrop(window, "text/plain;charset=utf-8", path, strlen(path));
|
||||
} else if ([obj isKindOfClass:[NSString class]]) {
|
||||
paths[i] = _glfw_strdup([obj UTF8String]);
|
||||
const char *text = [obj UTF8String];
|
||||
_glfwInputDrop(window, "text/plain;charset=utf-8", text, strlen(text));
|
||||
} else {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Cocoa: Object is neither a URL nor a string");
|
||||
paths[i] = _glfw_strdup("");
|
||||
}
|
||||
}
|
||||
|
||||
_glfwInputDrop(window, (int) count, (const char**) paths);
|
||||
|
||||
for (NSUInteger i = 0; i < count; i++)
|
||||
free(paths[i]);
|
||||
free(paths);
|
||||
}
|
||||
|
||||
return YES;
|
||||
@@ -1448,6 +1460,11 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
||||
}
|
||||
else if (ctxconfig->source == GLFW_EGL_CONTEXT_API)
|
||||
{
|
||||
// EGL implementation on macOS use CALayer* EGLNativeWindowType so we
|
||||
// need to get the layer for EGL window surface creation.
|
||||
[window->ns.view setWantsLayer:YES];
|
||||
window->ns.layer = [window->ns.view layer];
|
||||
|
||||
if (!_glfwInitEGL())
|
||||
return false;
|
||||
if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig))
|
||||
@@ -1498,7 +1515,7 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
|
||||
window->ns.object = nil;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowTitle(_GLFWwindow* window UNUSED, const char *title)
|
||||
void _glfwPlatformSetWindowTitle(_GLFWwindow* window UNUSED, const char* title)
|
||||
{
|
||||
NSString* string = @(title);
|
||||
[window->ns.object setTitle:string];
|
||||
@@ -1510,7 +1527,8 @@ void _glfwPlatformSetWindowTitle(_GLFWwindow* window UNUSED, const char *title)
|
||||
void _glfwPlatformSetWindowIcon(_GLFWwindow* window UNUSED,
|
||||
int count UNUSED, const GLFWimage* images UNUSED)
|
||||
{
|
||||
// Regular windows do not have icons
|
||||
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
|
||||
"Cocoa: Regular windows do not have icons on macOS");
|
||||
}
|
||||
|
||||
void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos)
|
||||
@@ -1577,10 +1595,18 @@ void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
|
||||
|
||||
void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom)
|
||||
{
|
||||
if (numer == GLFW_DONT_CARE || denom == GLFW_DONT_CARE)
|
||||
[window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)];
|
||||
else
|
||||
if (numer != GLFW_DONT_CARE && denom != GLFW_DONT_CARE)
|
||||
[window->ns.object setContentAspectRatio:NSMakeSize(numer, denom)];
|
||||
else
|
||||
[window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)];
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window, int widthincr, int heightincr)
|
||||
{
|
||||
if (widthincr != GLFW_DONT_CARE && heightincr != GLFW_DONT_CARE)
|
||||
[window->ns.object setResizeIncrements:NSMakeSize(widthincr, heightincr)];
|
||||
else
|
||||
[window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)];
|
||||
}
|
||||
|
||||
void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height)
|
||||
@@ -1838,6 +1864,17 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity)
|
||||
[window->ns.object setAlphaValue:opacity];
|
||||
}
|
||||
|
||||
void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window UNUSED, bool enabled UNUSED)
|
||||
{
|
||||
_glfwInputError(GLFW_FEATURE_UNIMPLEMENTED,
|
||||
"Cocoa: Raw mouse motion not yet implemented");
|
||||
}
|
||||
|
||||
bool _glfwPlatformRawMouseMotionSupported(void)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
_glfwDispatchRenderFrame(CGDirectDisplayID displayID) {
|
||||
_GLFWwindow *w = _glfw.windowListHead;
|
||||
@@ -2070,11 +2107,16 @@ const char* _glfwPlatformGetClipboardString(void)
|
||||
|
||||
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
|
||||
{
|
||||
if (!_glfw.vk.KHR_surface || !_glfw.vk.MVK_macos_surface)
|
||||
return;
|
||||
|
||||
extensions[0] = "VK_KHR_surface";
|
||||
extensions[1] = "VK_MVK_macos_surface";
|
||||
if (_glfw.vk.KHR_surface && _glfw.vk.EXT_metal_surface)
|
||||
{
|
||||
extensions[0] = "VK_KHR_surface";
|
||||
extensions[1] = "VK_EXT_metal_surface";
|
||||
}
|
||||
else if (_glfw.vk.KHR_surface && _glfw.vk.MVK_macos_surface)
|
||||
{
|
||||
extensions[0] = "VK_KHR_surface";
|
||||
extensions[1] = "VK_MVK_macos_surface";
|
||||
}
|
||||
}
|
||||
|
||||
int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance UNUSED,
|
||||
@@ -2090,19 +2132,6 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
|
||||
VkSurfaceKHR* surface)
|
||||
{
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101100
|
||||
VkResult err;
|
||||
VkMacOSSurfaceCreateInfoMVK sci;
|
||||
PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK;
|
||||
|
||||
vkCreateMacOSSurfaceMVK = (PFN_vkCreateMacOSSurfaceMVK)
|
||||
vkGetInstanceProcAddr(instance, "vkCreateMacOSSurfaceMVK");
|
||||
if (!vkCreateMacOSSurfaceMVK)
|
||||
{
|
||||
_glfwInputError(GLFW_API_UNAVAILABLE,
|
||||
"Cocoa: Vulkan instance missing VK_MVK_macos_surface extension");
|
||||
return VK_ERROR_EXTENSION_NOT_PRESENT;
|
||||
}
|
||||
|
||||
// HACK: Dynamically load Core Animation to avoid adding an extra
|
||||
// dependency for the majority who don't use MoltenVK
|
||||
NSBundle* bundle = [NSBundle bundleWithPath:@"/System/Library/Frameworks/QuartzCore.framework"];
|
||||
@@ -2128,11 +2157,49 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
|
||||
[window->ns.view setLayer:window->ns.layer];
|
||||
[window->ns.view setWantsLayer:YES];
|
||||
|
||||
memset(&sci, 0, sizeof(sci));
|
||||
sci.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
|
||||
sci.pView = window->ns.view;
|
||||
VkResult err;
|
||||
|
||||
if (_glfw.vk.EXT_metal_surface)
|
||||
{
|
||||
VkMetalSurfaceCreateInfoEXT sci;
|
||||
|
||||
PFN_vkCreateMetalSurfaceEXT vkCreateMetalSurfaceEXT;
|
||||
vkCreateMetalSurfaceEXT = (PFN_vkCreateMetalSurfaceEXT)
|
||||
vkGetInstanceProcAddr(instance, "vkCreateMetalSurfaceEXT");
|
||||
if (!vkCreateMetalSurfaceEXT)
|
||||
{
|
||||
_glfwInputError(GLFW_API_UNAVAILABLE,
|
||||
"Cocoa: Vulkan instance missing VK_EXT_metal_surface extension");
|
||||
return VK_ERROR_EXTENSION_NOT_PRESENT;
|
||||
}
|
||||
|
||||
memset(&sci, 0, sizeof(sci));
|
||||
sci.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
|
||||
sci.pLayer = window->ns.layer;
|
||||
|
||||
err = vkCreateMetalSurfaceEXT(instance, &sci, allocator, surface);
|
||||
}
|
||||
else
|
||||
{
|
||||
VkMacOSSurfaceCreateInfoMVK sci;
|
||||
|
||||
PFN_vkCreateMacOSSurfaceMVK vkCreateMacOSSurfaceMVK;
|
||||
vkCreateMacOSSurfaceMVK = (PFN_vkCreateMacOSSurfaceMVK)
|
||||
vkGetInstanceProcAddr(instance, "vkCreateMacOSSurfaceMVK");
|
||||
if (!vkCreateMacOSSurfaceMVK)
|
||||
{
|
||||
_glfwInputError(GLFW_API_UNAVAILABLE,
|
||||
"Cocoa: Vulkan instance missing VK_MVK_macos_surface extension");
|
||||
return VK_ERROR_EXTENSION_NOT_PRESENT;
|
||||
}
|
||||
|
||||
memset(&sci, 0, sizeof(sci));
|
||||
sci.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
|
||||
sci.pView = window->ns.view;
|
||||
|
||||
err = vkCreateMacOSSurfaceMVK(instance, &sci, allocator, surface);
|
||||
}
|
||||
|
||||
err = vkCreateMacOSSurfaceMVK(instance, &sci, allocator, surface);
|
||||
if (err)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
@@ -2178,9 +2245,9 @@ GLFWAPI void glfwCocoaRequestRenderFrame(GLFWwindow *w, GLFWcocoarenderframefun
|
||||
requestRenderFrame((_GLFWwindow*)w, callback);
|
||||
}
|
||||
|
||||
GLFWAPI void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, unsigned short *cocoa_key, int *cocoa_mods) {
|
||||
*cocoa_key = 0;
|
||||
GLFWAPI void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, char *cocoa_key, size_t key_sz, int *cocoa_mods) {
|
||||
*cocoa_mods = 0;
|
||||
memset(cocoa_key, 0, key_sz);
|
||||
|
||||
if (glfw_mods & GLFW_MOD_SHIFT)
|
||||
*cocoa_mods |= NSEventModifierFlagShift;
|
||||
@@ -2193,147 +2260,156 @@ GLFWAPI void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, unsigned sho
|
||||
if (glfw_mods & GLFW_MOD_CAPS_LOCK)
|
||||
*cocoa_mods |= NSEventModifierFlagCapsLock;
|
||||
|
||||
uint32_t utf_8_key = 0;
|
||||
unichar utf_16_key = 0;
|
||||
|
||||
START_ALLOW_CASE_RANGE
|
||||
switch(glfw_key) {
|
||||
#define K(ch, name) case GLFW_KEY_##name: *cocoa_key = ch; break;
|
||||
K('!', EXCLAM);
|
||||
K('"', DOUBLE_QUOTE);
|
||||
K('#', NUMBER_SIGN);
|
||||
K('$', DOLLAR);
|
||||
K('&', AMPERSAND);
|
||||
K('\'', APOSTROPHE);
|
||||
K('(', PARENTHESIS_LEFT);
|
||||
K(')', PARENTHESIS_RIGHT);
|
||||
K('+', PLUS);
|
||||
K(',', COMMA);
|
||||
K('-', MINUS);
|
||||
K('.', PERIOD);
|
||||
K('/', SLASH);
|
||||
K('0', 0);
|
||||
K('1', 1);
|
||||
K('2', 2);
|
||||
K('3', 3);
|
||||
K('5', 5);
|
||||
K('6', 6);
|
||||
K('7', 7);
|
||||
K('8', 8);
|
||||
K('9', 9);
|
||||
K(':', COLON);
|
||||
K(';', SEMICOLON);
|
||||
K('<', LESS);
|
||||
K('=', EQUAL);
|
||||
K('>', GREATER);
|
||||
K('@', AT);
|
||||
K('[', LEFT_BRACKET);
|
||||
K('\\', BACKSLASH);
|
||||
K(']', RIGHT_BRACKET);
|
||||
K('_', UNDERSCORE);
|
||||
K('`', GRAVE_ACCENT);
|
||||
K('a', A);
|
||||
K('b', B);
|
||||
K('c', C);
|
||||
K('d', D);
|
||||
K('e', E);
|
||||
K('f', F);
|
||||
K('g', G);
|
||||
K('h', H);
|
||||
K('i', I);
|
||||
K('j', J);
|
||||
K('k', K);
|
||||
K('l', L);
|
||||
K('m', M);
|
||||
K('n', N);
|
||||
K('o', O);
|
||||
K('p', P);
|
||||
K('q', Q);
|
||||
K('r', R);
|
||||
K('s', S);
|
||||
K('t', T);
|
||||
K('u', U);
|
||||
K('v', V);
|
||||
K('w', W);
|
||||
K('x', X);
|
||||
K('y', Y);
|
||||
K('z', Z);
|
||||
K(PARAGRAPH_UTF_8, PARAGRAPH);
|
||||
K(MASCULINE_UTF_8, MASCULINE);
|
||||
K(S_SHARP_UTF_8, S_SHARP);
|
||||
K(A_GRAVE_LOWER_CASE_UTF_8, A_GRAVE);
|
||||
K(A_DIAERESIS_LOWER_CASE_UTF_8, A_DIAERESIS);
|
||||
K(A_RING_LOWER_CASE_UTF_8, A_RING);
|
||||
K(AE_LOWER_CASE_UTF_8, AE);
|
||||
K(C_CEDILLA_LOWER_CASE_UTF_8, C_CEDILLA);
|
||||
K(E_GRAVE_LOWER_CASE_UTF_8, E_GRAVE);
|
||||
K(E_ACUTE_LOWER_CASE_UTF_8, E_ACUTE);
|
||||
K(I_GRAVE_LOWER_CASE_UTF_8, I_GRAVE);
|
||||
K(N_TILDE_LOWER_CASE_UTF_8, N_TILDE);
|
||||
K(O_GRAVE_LOWER_CASE_UTF_8, O_GRAVE);
|
||||
K(O_DIAERESIS_LOWER_CASE_UTF_8, O_DIAERESIS);
|
||||
K(O_SLASH_LOWER_CASE_UTF_8, O_SLASH);
|
||||
K(U_GRAVE_LOWER_CASE_UTF_8, U_GRAVE);
|
||||
K(U_DIAERESIS_LOWER_CASE_UTF_8, U_DIAERESIS);
|
||||
K(CYRILLIC_A_LOWER_CASE_UTF_8, CYRILLIC_A);
|
||||
K(CYRILLIC_BE_LOWER_CASE_UTF_8, CYRILLIC_BE);
|
||||
K(CYRILLIC_VE_LOWER_CASE_UTF_8, CYRILLIC_VE);
|
||||
K(CYRILLIC_GHE_LOWER_CASE_UTF_8, CYRILLIC_GHE);
|
||||
K(CYRILLIC_DE_LOWER_CASE_UTF_8, CYRILLIC_DE);
|
||||
K(CYRILLIC_IE_LOWER_CASE_UTF_8, CYRILLIC_IE);
|
||||
K(CYRILLIC_ZHE_LOWER_CASE_UTF_8, CYRILLIC_ZHE);
|
||||
K(CYRILLIC_ZE_LOWER_CASE_UTF_8, CYRILLIC_ZE);
|
||||
K(CYRILLIC_I_LOWER_CASE_UTF_8, CYRILLIC_I);
|
||||
K(CYRILLIC_SHORT_I_LOWER_CASE_UTF_8, CYRILLIC_SHORT_I);
|
||||
K(CYRILLIC_KA_LOWER_CASE_UTF_8, CYRILLIC_KA);
|
||||
K(CYRILLIC_EL_LOWER_CASE_UTF_8, CYRILLIC_EL);
|
||||
K(CYRILLIC_EM_LOWER_CASE_UTF_8, CYRILLIC_EM);
|
||||
K(CYRILLIC_EN_LOWER_CASE_UTF_8, CYRILLIC_EN);
|
||||
K(CYRILLIC_O_LOWER_CASE_UTF_8, CYRILLIC_O);
|
||||
K(CYRILLIC_PE_LOWER_CASE_UTF_8, CYRILLIC_PE);
|
||||
K(CYRILLIC_ER_LOWER_CASE_UTF_8, CYRILLIC_ER);
|
||||
K(CYRILLIC_ES_LOWER_CASE_UTF_8, CYRILLIC_ES);
|
||||
K(CYRILLIC_TE_LOWER_CASE_UTF_8, CYRILLIC_TE);
|
||||
K(CYRILLIC_U_LOWER_CASE_UTF_8, CYRILLIC_U);
|
||||
K(CYRILLIC_EF_LOWER_CASE_UTF_8, CYRILLIC_EF);
|
||||
K(CYRILLIC_HA_LOWER_CASE_UTF_8, CYRILLIC_HA);
|
||||
K(CYRILLIC_TSE_LOWER_CASE_UTF_8, CYRILLIC_TSE);
|
||||
K(CYRILLIC_CHE_LOWER_CASE_UTF_8, CYRILLIC_CHE);
|
||||
K(CYRILLIC_SHA_LOWER_CASE_UTF_8, CYRILLIC_SHA);
|
||||
K(CYRILLIC_SHCHA_LOWER_CASE_UTF_8, CYRILLIC_SHCHA);
|
||||
K(CYRILLIC_HARD_SIGN_LOWER_CASE_UTF_8, CYRILLIC_HARD_SIGN);
|
||||
K(CYRILLIC_YERU_LOWER_CASE_UTF_8, CYRILLIC_YERU);
|
||||
K(CYRILLIC_SOFT_SIGN_LOWER_CASE_UTF_8, CYRILLIC_SOFT_SIGN);
|
||||
K(CYRILLIC_E_LOWER_CASE_UTF_8, CYRILLIC_E);
|
||||
K(CYRILLIC_YU_LOWER_CASE_UTF_8, CYRILLIC_YU);
|
||||
K(CYRILLIC_YA_LOWER_CASE_UTF_8, CYRILLIC_YA);
|
||||
K(CYRILLIC_IO_LOWER_CASE_UTF_8, CYRILLIC_IO);
|
||||
#define K8(ch, name) case GLFW_KEY_##name: utf_8_key = ch; break;
|
||||
#define K16(ch, name) case GLFW_KEY_##name: utf_16_key = ch; break;
|
||||
K8('!', EXCLAM);
|
||||
K8('"', DOUBLE_QUOTE);
|
||||
K8('#', NUMBER_SIGN);
|
||||
K8('$', DOLLAR);
|
||||
K8('&', AMPERSAND);
|
||||
K8('\'', APOSTROPHE);
|
||||
K8('(', PARENTHESIS_LEFT);
|
||||
K8(')', PARENTHESIS_RIGHT);
|
||||
K8('+', PLUS);
|
||||
K8(',', COMMA);
|
||||
K8('-', MINUS);
|
||||
K8('.', PERIOD);
|
||||
K8('/', SLASH);
|
||||
K8('0', 0);
|
||||
K8('1', 1);
|
||||
K8('2', 2);
|
||||
K8('3', 3);
|
||||
K8('5', 5);
|
||||
K8('6', 6);
|
||||
K8('7', 7);
|
||||
K8('8', 8);
|
||||
K8('9', 9);
|
||||
K8(':', COLON);
|
||||
K8(';', SEMICOLON);
|
||||
K8('<', LESS);
|
||||
K8('=', EQUAL);
|
||||
K8('>', GREATER);
|
||||
K8('@', AT);
|
||||
K8('[', LEFT_BRACKET);
|
||||
K8('\\', BACKSLASH);
|
||||
K8(']', RIGHT_BRACKET);
|
||||
K8('^', CIRCUMFLEX);
|
||||
K8('_', UNDERSCORE);
|
||||
K8('`', GRAVE_ACCENT);
|
||||
K8('a', A);
|
||||
K8('b', B);
|
||||
K8('c', C);
|
||||
K8('d', D);
|
||||
K8('e', E);
|
||||
K8('f', F);
|
||||
K8('g', G);
|
||||
K8('h', H);
|
||||
K8('i', I);
|
||||
K8('j', J);
|
||||
K8('k', K);
|
||||
K8('l', L);
|
||||
K8('m', M);
|
||||
K8('n', N);
|
||||
K8('o', O);
|
||||
K8('p', P);
|
||||
K8('q', Q);
|
||||
K8('r', R);
|
||||
K8('s', S);
|
||||
K8('t', T);
|
||||
K8('u', U);
|
||||
K8('v', V);
|
||||
K8('w', W);
|
||||
K8('x', X);
|
||||
K8('y', Y);
|
||||
K8('z', Z);
|
||||
K8(PARAGRAPH_UTF_8, PARAGRAPH);
|
||||
K8(MASCULINE_UTF_8, MASCULINE);
|
||||
K8(S_SHARP_UTF_8, S_SHARP);
|
||||
K8(A_GRAVE_LOWER_CASE_UTF_8, A_GRAVE);
|
||||
K8(A_DIAERESIS_LOWER_CASE_UTF_8, A_DIAERESIS);
|
||||
K8(A_RING_LOWER_CASE_UTF_8, A_RING);
|
||||
K8(AE_LOWER_CASE_UTF_8, AE);
|
||||
K8(C_CEDILLA_LOWER_CASE_UTF_8, C_CEDILLA);
|
||||
K8(E_GRAVE_LOWER_CASE_UTF_8, E_GRAVE);
|
||||
K8(E_ACUTE_LOWER_CASE_UTF_8, E_ACUTE);
|
||||
K8(I_GRAVE_LOWER_CASE_UTF_8, I_GRAVE);
|
||||
K8(N_TILDE_LOWER_CASE_UTF_8, N_TILDE);
|
||||
K8(O_GRAVE_LOWER_CASE_UTF_8, O_GRAVE);
|
||||
K8(O_DIAERESIS_LOWER_CASE_UTF_8, O_DIAERESIS);
|
||||
K8(O_SLASH_LOWER_CASE_UTF_8, O_SLASH);
|
||||
K8(U_GRAVE_LOWER_CASE_UTF_8, U_GRAVE);
|
||||
K8(U_DIAERESIS_LOWER_CASE_UTF_8, U_DIAERESIS);
|
||||
K8(CYRILLIC_A_LOWER_CASE_UTF_8, CYRILLIC_A);
|
||||
K8(CYRILLIC_BE_LOWER_CASE_UTF_8, CYRILLIC_BE);
|
||||
K8(CYRILLIC_VE_LOWER_CASE_UTF_8, CYRILLIC_VE);
|
||||
K8(CYRILLIC_GHE_LOWER_CASE_UTF_8, CYRILLIC_GHE);
|
||||
K8(CYRILLIC_DE_LOWER_CASE_UTF_8, CYRILLIC_DE);
|
||||
K8(CYRILLIC_IE_LOWER_CASE_UTF_8, CYRILLIC_IE);
|
||||
K8(CYRILLIC_ZHE_LOWER_CASE_UTF_8, CYRILLIC_ZHE);
|
||||
K8(CYRILLIC_ZE_LOWER_CASE_UTF_8, CYRILLIC_ZE);
|
||||
K8(CYRILLIC_I_LOWER_CASE_UTF_8, CYRILLIC_I);
|
||||
K8(CYRILLIC_SHORT_I_LOWER_CASE_UTF_8, CYRILLIC_SHORT_I);
|
||||
K8(CYRILLIC_KA_LOWER_CASE_UTF_8, CYRILLIC_KA);
|
||||
K8(CYRILLIC_EL_LOWER_CASE_UTF_8, CYRILLIC_EL);
|
||||
K8(CYRILLIC_EM_LOWER_CASE_UTF_8, CYRILLIC_EM);
|
||||
K8(CYRILLIC_EN_LOWER_CASE_UTF_8, CYRILLIC_EN);
|
||||
K8(CYRILLIC_O_LOWER_CASE_UTF_8, CYRILLIC_O);
|
||||
K8(CYRILLIC_PE_LOWER_CASE_UTF_8, CYRILLIC_PE);
|
||||
K8(CYRILLIC_ER_LOWER_CASE_UTF_8, CYRILLIC_ER);
|
||||
K8(CYRILLIC_ES_LOWER_CASE_UTF_8, CYRILLIC_ES);
|
||||
K8(CYRILLIC_TE_LOWER_CASE_UTF_8, CYRILLIC_TE);
|
||||
K8(CYRILLIC_U_LOWER_CASE_UTF_8, CYRILLIC_U);
|
||||
K8(CYRILLIC_EF_LOWER_CASE_UTF_8, CYRILLIC_EF);
|
||||
K8(CYRILLIC_HA_LOWER_CASE_UTF_8, CYRILLIC_HA);
|
||||
K8(CYRILLIC_TSE_LOWER_CASE_UTF_8, CYRILLIC_TSE);
|
||||
K8(CYRILLIC_CHE_LOWER_CASE_UTF_8, CYRILLIC_CHE);
|
||||
K8(CYRILLIC_SHA_LOWER_CASE_UTF_8, CYRILLIC_SHA);
|
||||
K8(CYRILLIC_SHCHA_LOWER_CASE_UTF_8, CYRILLIC_SHCHA);
|
||||
K8(CYRILLIC_HARD_SIGN_LOWER_CASE_UTF_8, CYRILLIC_HARD_SIGN);
|
||||
K8(CYRILLIC_YERU_LOWER_CASE_UTF_8, CYRILLIC_YERU);
|
||||
K8(CYRILLIC_SOFT_SIGN_LOWER_CASE_UTF_8, CYRILLIC_SOFT_SIGN);
|
||||
K8(CYRILLIC_E_LOWER_CASE_UTF_8, CYRILLIC_E);
|
||||
K8(CYRILLIC_YU_LOWER_CASE_UTF_8, CYRILLIC_YU);
|
||||
K8(CYRILLIC_YA_LOWER_CASE_UTF_8, CYRILLIC_YA);
|
||||
K8(CYRILLIC_IO_LOWER_CASE_UTF_8, CYRILLIC_IO);
|
||||
|
||||
K(0x35, ESCAPE);
|
||||
K('\r', ENTER);
|
||||
K('\t', TAB);
|
||||
K(NSBackspaceCharacter, BACKSPACE);
|
||||
K(NSInsertFunctionKey, INSERT);
|
||||
K(NSDeleteCharacter, DELETE);
|
||||
K(NSLeftArrowFunctionKey, LEFT);
|
||||
K(NSRightArrowFunctionKey, RIGHT);
|
||||
K(NSUpArrowFunctionKey, UP);
|
||||
K(NSDownArrowFunctionKey, DOWN);
|
||||
K(NSPageUpFunctionKey, PAGE_UP);
|
||||
K(NSPageDownFunctionKey, PAGE_DOWN);
|
||||
K(NSHomeFunctionKey, HOME);
|
||||
K(NSEndFunctionKey, END);
|
||||
K(NSPrintFunctionKey, PRINT_SCREEN);
|
||||
K8(0x35, ESCAPE);
|
||||
K8('\r', ENTER);
|
||||
K8('\t', TAB);
|
||||
K16(NSBackspaceCharacter, BACKSPACE);
|
||||
K16(NSInsertFunctionKey, INSERT);
|
||||
K16(NSDeleteCharacter, DELETE);
|
||||
K16(NSLeftArrowFunctionKey, LEFT);
|
||||
K16(NSRightArrowFunctionKey, RIGHT);
|
||||
K16(NSUpArrowFunctionKey, UP);
|
||||
K16(NSDownArrowFunctionKey, DOWN);
|
||||
K16(NSPageUpFunctionKey, PAGE_UP);
|
||||
K16(NSPageDownFunctionKey, PAGE_DOWN);
|
||||
K16(NSHomeFunctionKey, HOME);
|
||||
K16(NSEndFunctionKey, END);
|
||||
K16(NSPrintFunctionKey, PRINT_SCREEN);
|
||||
case GLFW_KEY_F1 ... GLFW_KEY_F24:
|
||||
*cocoa_key = NSF1FunctionKey + (glfw_key - GLFW_KEY_F1); break;
|
||||
case GLFW_KEY_KP_0 ... GLFW_KEY_KP_9:
|
||||
*cocoa_key = NSEventModifierFlagNumericPad | (0x52 + (glfw_key - GLFW_KEY_KP_0)); break;
|
||||
K((unichar)(0x41|NSEventModifierFlagNumericPad), KP_DECIMAL);
|
||||
K((unichar)(0x43|NSEventModifierFlagNumericPad), KP_MULTIPLY);
|
||||
K((unichar)(0x45|NSEventModifierFlagNumericPad), KP_ADD);
|
||||
K((unichar)(0x4B|NSEventModifierFlagNumericPad), KP_DIVIDE);
|
||||
K((unichar)(0x4E|NSEventModifierFlagNumericPad), KP_SUBTRACT);
|
||||
K((unichar)(0x51|NSEventModifierFlagNumericPad), KP_EQUAL);
|
||||
#undef K
|
||||
utf_16_key = NSF1FunctionKey + (glfw_key - GLFW_KEY_F1); break;
|
||||
#undef K8
|
||||
#undef K16
|
||||
END_ALLOW_CASE_RANGE
|
||||
}
|
||||
if (utf_16_key != 0) {
|
||||
strncpy(cocoa_key, [[NSString stringWithCharacters:&utf_16_key length:1] UTF8String], key_sz - 1);
|
||||
} else {
|
||||
unsigned str_pos = 0;
|
||||
for (unsigned i = 0; i < 4 && str_pos < key_sz - 1; i++) {
|
||||
uint8_t byte = (utf_8_key >> 24) & 0xff;
|
||||
utf_8_key <<= 8;
|
||||
if (byte != 0) cocoa_key[str_pos++] = byte;
|
||||
}
|
||||
cocoa_key[str_pos] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
168
glfw/context.c
vendored
168
glfw/context.c
vendored
@@ -173,174 +173,6 @@ bool _glfwIsValidContextConfig(const _GLFWctxconfig* ctxconfig)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Chooses the framebuffer config that best matches the desired one
|
||||
//
|
||||
const _GLFWfbconfig* _glfwChooseFBConfig(const _GLFWfbconfig* desired,
|
||||
const _GLFWfbconfig* alternatives,
|
||||
unsigned int count)
|
||||
{
|
||||
unsigned int i;
|
||||
unsigned int missing, leastMissing = UINT_MAX;
|
||||
unsigned int colorDiff, leastColorDiff = UINT_MAX;
|
||||
unsigned int extraDiff, leastExtraDiff = UINT_MAX;
|
||||
const _GLFWfbconfig* current;
|
||||
const _GLFWfbconfig* closest = NULL;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
current = alternatives + i;
|
||||
|
||||
if (desired->stereo > 0 && current->stereo == 0)
|
||||
{
|
||||
// Stereo is a hard constraint
|
||||
continue;
|
||||
}
|
||||
|
||||
if (desired->doublebuffer != current->doublebuffer)
|
||||
{
|
||||
// Double buffering is a hard constraint
|
||||
continue;
|
||||
}
|
||||
|
||||
// Count number of missing buffers
|
||||
{
|
||||
missing = 0;
|
||||
|
||||
if (desired->alphaBits > 0 && current->alphaBits == 0)
|
||||
missing++;
|
||||
|
||||
if (desired->depthBits > 0 && current->depthBits == 0)
|
||||
missing++;
|
||||
|
||||
if (desired->stencilBits > 0 && current->stencilBits == 0)
|
||||
missing++;
|
||||
|
||||
if (desired->auxBuffers > 0 &&
|
||||
current->auxBuffers < desired->auxBuffers)
|
||||
{
|
||||
missing += desired->auxBuffers - current->auxBuffers;
|
||||
}
|
||||
|
||||
if (desired->samples > 0 && current->samples == 0)
|
||||
{
|
||||
// Technically, several multisampling buffers could be
|
||||
// involved, but that's a lower level implementation detail and
|
||||
// not important to us here, so we count them as one
|
||||
missing++;
|
||||
}
|
||||
|
||||
if (desired->transparent != current->transparent)
|
||||
missing++;
|
||||
}
|
||||
|
||||
// These polynomials make many small channel size differences matter
|
||||
// less than one large channel size difference
|
||||
|
||||
// Calculate color channel size difference value
|
||||
{
|
||||
colorDiff = 0;
|
||||
|
||||
if (desired->redBits != GLFW_DONT_CARE)
|
||||
{
|
||||
colorDiff += (desired->redBits - current->redBits) *
|
||||
(desired->redBits - current->redBits);
|
||||
}
|
||||
|
||||
if (desired->greenBits != GLFW_DONT_CARE)
|
||||
{
|
||||
colorDiff += (desired->greenBits - current->greenBits) *
|
||||
(desired->greenBits - current->greenBits);
|
||||
}
|
||||
|
||||
if (desired->blueBits != GLFW_DONT_CARE)
|
||||
{
|
||||
colorDiff += (desired->blueBits - current->blueBits) *
|
||||
(desired->blueBits - current->blueBits);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate non-color channel size difference value
|
||||
{
|
||||
extraDiff = 0;
|
||||
|
||||
if (desired->alphaBits != GLFW_DONT_CARE)
|
||||
{
|
||||
extraDiff += (desired->alphaBits - current->alphaBits) *
|
||||
(desired->alphaBits - current->alphaBits);
|
||||
}
|
||||
|
||||
if (desired->depthBits != GLFW_DONT_CARE)
|
||||
{
|
||||
extraDiff += (desired->depthBits - current->depthBits) *
|
||||
(desired->depthBits - current->depthBits);
|
||||
}
|
||||
|
||||
if (desired->stencilBits != GLFW_DONT_CARE)
|
||||
{
|
||||
extraDiff += (desired->stencilBits - current->stencilBits) *
|
||||
(desired->stencilBits - current->stencilBits);
|
||||
}
|
||||
|
||||
if (desired->accumRedBits != GLFW_DONT_CARE)
|
||||
{
|
||||
extraDiff += (desired->accumRedBits - current->accumRedBits) *
|
||||
(desired->accumRedBits - current->accumRedBits);
|
||||
}
|
||||
|
||||
if (desired->accumGreenBits != GLFW_DONT_CARE)
|
||||
{
|
||||
extraDiff += (desired->accumGreenBits - current->accumGreenBits) *
|
||||
(desired->accumGreenBits - current->accumGreenBits);
|
||||
}
|
||||
|
||||
if (desired->accumBlueBits != GLFW_DONT_CARE)
|
||||
{
|
||||
extraDiff += (desired->accumBlueBits - current->accumBlueBits) *
|
||||
(desired->accumBlueBits - current->accumBlueBits);
|
||||
}
|
||||
|
||||
if (desired->accumAlphaBits != GLFW_DONT_CARE)
|
||||
{
|
||||
extraDiff += (desired->accumAlphaBits - current->accumAlphaBits) *
|
||||
(desired->accumAlphaBits - current->accumAlphaBits);
|
||||
}
|
||||
|
||||
if (desired->samples != GLFW_DONT_CARE)
|
||||
{
|
||||
extraDiff += (desired->samples - current->samples) *
|
||||
(desired->samples - current->samples);
|
||||
}
|
||||
|
||||
if (desired->sRGB && !current->sRGB)
|
||||
extraDiff++;
|
||||
}
|
||||
|
||||
// Figure out if the current one is better than the best one found so far
|
||||
// Least number of missing buffers is the most important heuristic,
|
||||
// then color buffer size match and lastly size match for other buffers
|
||||
|
||||
if (missing < leastMissing)
|
||||
closest = current;
|
||||
else if (missing == leastMissing)
|
||||
{
|
||||
if ((colorDiff < leastColorDiff) ||
|
||||
(colorDiff == leastColorDiff && extraDiff < leastExtraDiff))
|
||||
{
|
||||
closest = current;
|
||||
}
|
||||
}
|
||||
|
||||
if (current == closest)
|
||||
{
|
||||
leastMissing = missing;
|
||||
leastColorDiff = colorDiff;
|
||||
leastExtraDiff = extraDiff;
|
||||
}
|
||||
}
|
||||
|
||||
return closest;
|
||||
}
|
||||
|
||||
// Retrieves the attributes of the current context
|
||||
//
|
||||
bool _glfwRefreshContextAttribs(_GLFWwindow* window,
|
||||
|
||||
143
glfw/egl_context.c
vendored
143
glfw/egl_context.c
vendored
@@ -76,6 +76,7 @@ static const char* getEGLErrorString(EGLint error)
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _GLFW_X11
|
||||
// Returns the specified attribute of the specified EGLConfig
|
||||
//
|
||||
static int getEGLConfigAttrib(EGLConfig config, int attrib)
|
||||
@@ -84,6 +85,7 @@ static int getEGLConfigAttrib(EGLConfig config, int attrib)
|
||||
eglGetConfigAttrib(_glfw.egl.display, config, attrib, &value);
|
||||
return value;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Return the EGLConfig most closely matching the specified hints
|
||||
//
|
||||
@@ -91,101 +93,70 @@ static bool chooseEGLConfig(const _GLFWctxconfig* ctxconfig,
|
||||
const _GLFWfbconfig* desired,
|
||||
EGLConfig* result)
|
||||
{
|
||||
EGLConfig* nativeConfigs;
|
||||
_GLFWfbconfig* usableConfigs;
|
||||
const _GLFWfbconfig* closest;
|
||||
int i, nativeCount, usableCount;
|
||||
EGLConfig configs[512];
|
||||
int i = 0, nativeCount = 0, ans_idx = 0;
|
||||
EGLint attributes[64];
|
||||
#define ATTR(k, v) { attributes[i++] = k; attributes[i++] = v; }
|
||||
ATTR(EGL_COLOR_BUFFER_TYPE, EGL_RGB_BUFFER);
|
||||
ATTR(EGL_SURFACE_TYPE, EGL_WINDOW_BIT);
|
||||
if (ctxconfig->client == GLFW_OPENGL_ES_API) {
|
||||
if (ctxconfig->major == 1) ATTR(EGL_RENDERABLE_TYPE, EGL_OPENGL_ES_BIT)
|
||||
else ATTR(EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT);
|
||||
}
|
||||
else if (ctxconfig->client == GLFW_OPENGL_API) ATTR(EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT);
|
||||
if (desired->samples > 0) ATTR(EGL_SAMPLES, desired->samples);
|
||||
if (desired->depthBits > 0) ATTR(EGL_DEPTH_SIZE, desired->depthBits);
|
||||
if (desired->stencilBits > 0) ATTR(EGL_STENCIL_SIZE, desired->stencilBits);
|
||||
if (desired->redBits > 0) ATTR(EGL_RED_SIZE, desired->redBits);
|
||||
if (desired->greenBits > 0) ATTR(EGL_GREEN_SIZE, desired->greenBits);
|
||||
if (desired->blueBits > 0) ATTR(EGL_BLUE_SIZE, desired->blueBits);
|
||||
if (desired->alphaBits > 0) ATTR(EGL_ALPHA_SIZE, desired->alphaBits);
|
||||
ATTR(EGL_NONE, EGL_NONE);
|
||||
#undef ATTR
|
||||
if (!eglChooseConfig(_glfw.egl.display, attributes, configs, sizeof(configs)/sizeof(configs[0]), &nativeCount)) {
|
||||
_glfwInputError(GLFW_API_UNAVAILABLE, "EGL: eglChooseConfig failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
eglGetConfigs(_glfw.egl.display, NULL, 0, &nativeCount);
|
||||
if (!nativeCount)
|
||||
{
|
||||
_glfwInputError(GLFW_API_UNAVAILABLE, "EGL: No EGLConfigs returned");
|
||||
return false;
|
||||
}
|
||||
|
||||
nativeConfigs = calloc(nativeCount, sizeof(EGLConfig));
|
||||
eglGetConfigs(_glfw.egl.display, nativeConfigs, nativeCount, &nativeCount);
|
||||
|
||||
usableConfigs = calloc(nativeCount, sizeof(_GLFWfbconfig));
|
||||
usableCount = 0;
|
||||
|
||||
for (i = 0; i < nativeCount; i++)
|
||||
{
|
||||
const EGLConfig n = nativeConfigs[i];
|
||||
_GLFWfbconfig* u = usableConfigs + usableCount;
|
||||
|
||||
// Only consider RGB(A) EGLConfigs
|
||||
if (getEGLConfigAttrib(n, EGL_COLOR_BUFFER_TYPE) != EGL_RGB_BUFFER)
|
||||
continue;
|
||||
|
||||
// Only consider window EGLConfigs
|
||||
if (!(getEGLConfigAttrib(n, EGL_SURFACE_TYPE) & EGL_WINDOW_BIT))
|
||||
continue;
|
||||
|
||||
#if defined(_GLFW_X11)
|
||||
XVisualInfo vi = {0};
|
||||
|
||||
// Only consider EGLConfigs with associated Visuals
|
||||
vi.visualid = getEGLConfigAttrib(n, EGL_NATIVE_VISUAL_ID);
|
||||
if (!vi.visualid)
|
||||
continue;
|
||||
|
||||
if (desired->transparent)
|
||||
{
|
||||
int count;
|
||||
XVisualInfo* vis = XGetVisualInfo(_glfw.x11.display,
|
||||
VisualIDMask, &vi,
|
||||
&count);
|
||||
if (vis)
|
||||
const EGLConfig n = configs[i];
|
||||
XVisualInfo vi = {0};
|
||||
|
||||
// Only consider EGLConfigs with associated Visuals
|
||||
vi.visualid = getEGLConfigAttrib(n, EGL_NATIVE_VISUAL_ID);
|
||||
if (!vi.visualid)
|
||||
continue;
|
||||
|
||||
if (desired->transparent)
|
||||
{
|
||||
u->transparent = _glfwIsVisualTransparentX11(vis[0].visual);
|
||||
XFree(vis);
|
||||
int count;
|
||||
XVisualInfo* vis =
|
||||
XGetVisualInfo(_glfw.x11.display, VisualIDMask, &vi, &count);
|
||||
if (vis)
|
||||
{
|
||||
bool transparent = _glfwIsVisualTransparentX11(vis[0].visual);
|
||||
XFree(vis);
|
||||
if (!transparent) continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // _GLFW_X11
|
||||
|
||||
if (ctxconfig->client == GLFW_OPENGL_ES_API)
|
||||
{
|
||||
if (ctxconfig->major == 1)
|
||||
{
|
||||
if (!(getEGLConfigAttrib(n, EGL_RENDERABLE_TYPE) & EGL_OPENGL_ES_BIT))
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(getEGLConfigAttrib(n, EGL_RENDERABLE_TYPE) & EGL_OPENGL_ES2_BIT))
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (ctxconfig->client == GLFW_OPENGL_API)
|
||||
{
|
||||
if (!(getEGLConfigAttrib(n, EGL_RENDERABLE_TYPE) & EGL_OPENGL_BIT))
|
||||
continue;
|
||||
}
|
||||
|
||||
u->redBits = getEGLConfigAttrib(n, EGL_RED_SIZE);
|
||||
u->greenBits = getEGLConfigAttrib(n, EGL_GREEN_SIZE);
|
||||
u->blueBits = getEGLConfigAttrib(n, EGL_BLUE_SIZE);
|
||||
|
||||
u->alphaBits = getEGLConfigAttrib(n, EGL_ALPHA_SIZE);
|
||||
u->depthBits = getEGLConfigAttrib(n, EGL_DEPTH_SIZE);
|
||||
u->stencilBits = getEGLConfigAttrib(n, EGL_STENCIL_SIZE);
|
||||
|
||||
u->samples = getEGLConfigAttrib(n, EGL_SAMPLES);
|
||||
u->doublebuffer = true;
|
||||
|
||||
u->handle = (uintptr_t) n;
|
||||
usableCount++;
|
||||
ans_idx = i;
|
||||
break;
|
||||
}
|
||||
|
||||
closest = _glfwChooseFBConfig(desired, usableConfigs, usableCount);
|
||||
if (closest)
|
||||
*result = (EGLConfig) closest->handle;
|
||||
|
||||
free(nativeConfigs);
|
||||
free(usableConfigs);
|
||||
|
||||
return closest != NULL;
|
||||
*result = configs[ans_idx];
|
||||
return true;
|
||||
}
|
||||
|
||||
static void makeContextCurrentEGL(_GLFWwindow* window)
|
||||
@@ -339,6 +310,7 @@ bool _glfwInitEGL(void)
|
||||
|
||||
glfw_dlsym(_glfw.egl.GetConfigAttrib, _glfw.egl.handle, "eglGetConfigAttrib");
|
||||
glfw_dlsym(_glfw.egl.GetConfigs, _glfw.egl.handle, "eglGetConfigs");
|
||||
glfw_dlsym(_glfw.egl.ChooseConfig, _glfw.egl.handle, "eglChooseConfig");
|
||||
glfw_dlsym(_glfw.egl.GetDisplay, _glfw.egl.handle, "eglGetDisplay");
|
||||
glfw_dlsym(_glfw.egl.GetError, _glfw.egl.handle, "eglGetError");
|
||||
glfw_dlsym(_glfw.egl.Initialize, _glfw.egl.handle, "eglInitialize");
|
||||
@@ -356,6 +328,7 @@ bool _glfwInitEGL(void)
|
||||
|
||||
if (!_glfw.egl.GetConfigAttrib ||
|
||||
!_glfw.egl.GetConfigs ||
|
||||
!_glfw.egl.ChooseConfig ||
|
||||
!_glfw.egl.GetDisplay ||
|
||||
!_glfw.egl.GetError ||
|
||||
!_glfw.egl.Initialize ||
|
||||
@@ -571,18 +544,16 @@ bool _glfwCreateContextEGL(_GLFWwindow* window,
|
||||
}
|
||||
|
||||
// Set up attributes for surface creation
|
||||
index = 0;
|
||||
|
||||
if (fbconfig->sRGB)
|
||||
{
|
||||
int index = 0;
|
||||
|
||||
if (fbconfig->sRGB)
|
||||
{
|
||||
if (_glfw.egl.KHR_gl_colorspace)
|
||||
setAttrib(EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR);
|
||||
}
|
||||
|
||||
setAttrib(EGL_NONE, EGL_NONE);
|
||||
if (_glfw.egl.KHR_gl_colorspace)
|
||||
setAttrib(EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR);
|
||||
}
|
||||
|
||||
setAttrib(EGL_NONE, EGL_NONE);
|
||||
|
||||
window->context.egl.surface =
|
||||
eglCreateWindowSurface(_glfw.egl.display,
|
||||
config,
|
||||
|
||||
3
glfw/egl_context.h
vendored
3
glfw/egl_context.h
vendored
@@ -120,6 +120,7 @@ typedef void* EGLSurface;
|
||||
// EGL function pointer typedefs
|
||||
typedef EGLBoolean (EGLAPIENTRY * PFN_eglGetConfigAttrib)(EGLDisplay,EGLConfig,EGLint,EGLint*);
|
||||
typedef EGLBoolean (EGLAPIENTRY * PFN_eglGetConfigs)(EGLDisplay,EGLConfig*,EGLint,EGLint*);
|
||||
typedef EGLBoolean (EGLAPIENTRY * PFN_eglChooseConfig)(EGLDisplay,EGLint const*,EGLConfig*,EGLint,EGLint*);
|
||||
typedef EGLDisplay (EGLAPIENTRY * PFN_eglGetDisplay)(EGLNativeDisplayType);
|
||||
typedef EGLint (EGLAPIENTRY * PFN_eglGetError)(void);
|
||||
typedef EGLBoolean (EGLAPIENTRY * PFN_eglInitialize)(EGLDisplay,EGLint*,EGLint*);
|
||||
@@ -136,6 +137,7 @@ typedef const char* (EGLAPIENTRY * PFN_eglQueryString)(EGLDisplay,EGLint);
|
||||
typedef GLFWglproc (EGLAPIENTRY * PFN_eglGetProcAddress)(const char*);
|
||||
#define eglGetConfigAttrib _glfw.egl.GetConfigAttrib
|
||||
#define eglGetConfigs _glfw.egl.GetConfigs
|
||||
#define eglChooseConfig _glfw.egl.ChooseConfig
|
||||
#define eglGetDisplay _glfw.egl.GetDisplay
|
||||
#define eglGetError _glfw.egl.GetError
|
||||
#define eglInitialize _glfw.egl.Initialize
|
||||
@@ -185,6 +187,7 @@ typedef struct _GLFWlibraryEGL
|
||||
|
||||
PFN_eglGetConfigAttrib GetConfigAttrib;
|
||||
PFN_eglGetConfigs GetConfigs;
|
||||
PFN_eglChooseConfig ChooseConfig;
|
||||
PFN_eglGetDisplay GetDisplay;
|
||||
PFN_eglGetError GetError;
|
||||
PFN_eglInitialize Initialize;
|
||||
|
||||
73
glfw/glfw.py
73
glfw/glfw.py
@@ -6,20 +6,57 @@ import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from typing import Callable, Dict, List, Optional, Tuple
|
||||
|
||||
_plat = sys.platform.lower()
|
||||
is_linux = 'linux' in _plat
|
||||
base = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def wayland_protocol_file_name(base, ext='c'):
|
||||
class Env:
|
||||
|
||||
cc: str = ''
|
||||
cppflags: List[str] = []
|
||||
cflags: List[str] = []
|
||||
ldflags: List[str] = []
|
||||
library_paths: Dict[str, List[str]] = {}
|
||||
ldpaths: List[str] = []
|
||||
ccver: Tuple[int, int]
|
||||
|
||||
# glfw stuff
|
||||
all_headers: List[str] = []
|
||||
sources: List[str] = []
|
||||
wayland_packagedir: str = ''
|
||||
wayland_scanner: str = ''
|
||||
wayland_scanner_code: str = ''
|
||||
wayland_protocols: Tuple[str, ...] = ()
|
||||
|
||||
def __init__(
|
||||
self, cc: str = '', cppflags: List[str] = [], cflags: List[str] = [], ldflags: List[str] = [],
|
||||
library_paths: Dict[str, List[str]] = {}, ldpaths: Optional[List[str]] = None, ccver: Tuple[int, int] = (0, 0)
|
||||
):
|
||||
self.cc, self.cppflags, self.cflags, self.ldflags, self.library_paths = cc, cppflags, cflags, ldflags, library_paths
|
||||
self.ldpaths, self.ccver = [] if ldpaths is None else ldpaths, ccver
|
||||
|
||||
def copy(self) -> 'Env':
|
||||
ans = Env(self.cc, list(self.cppflags), list(self.cflags), list(self.ldflags), dict(self.library_paths), list(self.ldpaths), self.ccver)
|
||||
ans.all_headers = list(self.all_headers)
|
||||
ans.sources = list(self.sources)
|
||||
ans.wayland_packagedir = self.wayland_packagedir
|
||||
ans.wayland_scanner = self.wayland_scanner
|
||||
ans.wayland_scanner_code = self.wayland_scanner_code
|
||||
ans.wayland_protocols = self.wayland_protocols
|
||||
return ans
|
||||
|
||||
|
||||
def wayland_protocol_file_name(base: str, ext: str = 'c') -> str:
|
||||
base = os.path.basename(base).rpartition('.')[0]
|
||||
return 'wayland-{}-client-protocol.{}'.format(base, ext)
|
||||
|
||||
|
||||
def init_env(env, pkg_config, at_least_version, test_compile, module='x11'):
|
||||
def init_env(env: Env, pkg_config: Callable, at_least_version: Callable, test_compile: Callable, module: str = 'x11') -> Env:
|
||||
ans = env.copy()
|
||||
ans.cflags.append('-fpic')
|
||||
ans.cflags.append('-fPIC')
|
||||
ans.cppflags.append('-D_GLFW_' + module.upper())
|
||||
ans.cppflags.append('-D_GLFW_BUILD_DLL')
|
||||
|
||||
@@ -46,8 +83,8 @@ def init_env(env, pkg_config, at_least_version, test_compile, module='x11'):
|
||||
|
||||
elif module == 'cocoa':
|
||||
ans.cppflags.append('-DGL_SILENCE_DEPRECATION')
|
||||
for f in 'Cocoa IOKit CoreFoundation CoreVideo'.split():
|
||||
ans.ldpaths.extend(('-framework', f))
|
||||
for f_ in 'Cocoa IOKit CoreFoundation CoreVideo'.split():
|
||||
ans.ldpaths.extend(('-framework', f_))
|
||||
|
||||
elif module == 'wayland':
|
||||
at_least_version('wayland-protocols', *sinfo['wayland_protocols'])
|
||||
@@ -59,7 +96,7 @@ def init_env(env, pkg_config, at_least_version, test_compile, module='x11'):
|
||||
for p in ans.wayland_protocols:
|
||||
ans.sources.append(wayland_protocol_file_name(p))
|
||||
ans.all_headers.append(wayland_protocol_file_name(p, 'h'))
|
||||
for dep in 'wayland-egl wayland-client wayland-cursor xkbcommon dbus-1'.split():
|
||||
for dep in 'wayland-client wayland-cursor xkbcommon dbus-1'.split():
|
||||
ans.cflags.extend(pkg_config(dep, '--cflags-only-I'))
|
||||
ans.ldpaths.extend(pkg_config(dep, '--libs'))
|
||||
has_memfd_create = test_compile(env.cc, '-Werror', src='''#define _GNU_SOURCE
|
||||
@@ -74,7 +111,7 @@ def init_env(env, pkg_config, at_least_version, test_compile, module='x11'):
|
||||
return ans
|
||||
|
||||
|
||||
def build_wayland_protocols(env, Command, parallel_run, emphasis, newer, dest_dir):
|
||||
def build_wayland_protocols(env: Env, Command: Callable, parallel_run: Callable, emphasis: Callable, newer: Callable, dest_dir: str) -> None:
|
||||
items = []
|
||||
for protocol in env.wayland_protocols:
|
||||
src = os.path.join(env.wayland_packagedir, protocol)
|
||||
@@ -95,7 +132,7 @@ def build_wayland_protocols(env, Command, parallel_run, emphasis, newer, dest_di
|
||||
|
||||
class Arg:
|
||||
|
||||
def __init__(self, decl):
|
||||
def __init__(self, decl: str):
|
||||
self.type, self.name = decl.rsplit(' ', 1)
|
||||
self.type = self.type.strip()
|
||||
self.name = self.name.strip()
|
||||
@@ -103,13 +140,13 @@ class Arg:
|
||||
self.name = self.name[1:]
|
||||
self.type = self.type + '*'
|
||||
|
||||
def __repr__(self):
|
||||
def __repr__(self) -> str:
|
||||
return 'Arg({}, {})'.format(self.type, self.name)
|
||||
|
||||
|
||||
class Function:
|
||||
|
||||
def __init__(self, declaration, check_fail=True):
|
||||
def __init__(self, declaration: str, check_fail: bool = True):
|
||||
self.check_fail = check_fail
|
||||
m = re.match(
|
||||
r'(.+?)\s+(glfw[A-Z][a-zA-Z0-9]+)[(](.+)[)]$', declaration
|
||||
@@ -128,14 +165,14 @@ class Function:
|
||||
if not self.args:
|
||||
self.args = [Arg('void v')]
|
||||
|
||||
def declaration(self):
|
||||
return 'typedef {restype} (*{name}_func)({args});\n{name}_func {name}_impl;\n#define {name} {name}_impl'.format(
|
||||
def declaration(self) -> str:
|
||||
return 'typedef {restype} (*{name}_func)({args});\nGFW_EXTERN {name}_func {name}_impl;\n#define {name} {name}_impl'.format(
|
||||
restype=self.restype,
|
||||
name=self.name,
|
||||
args=', '.join(a.type for a in self.args)
|
||||
)
|
||||
|
||||
def load(self):
|
||||
def load(self) -> str:
|
||||
ans = '*(void **) (&{name}_impl) = dlsym(handle, "{name}");'.format(
|
||||
name=self.name
|
||||
)
|
||||
@@ -146,7 +183,7 @@ class Function:
|
||||
return ans
|
||||
|
||||
|
||||
def generate_wrappers(glfw_header):
|
||||
def generate_wrappers(glfw_header: str) -> None:
|
||||
with open(glfw_header) as f:
|
||||
src = f.read()
|
||||
functions = []
|
||||
@@ -166,7 +203,7 @@ def generate_wrappers(glfw_header):
|
||||
GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *window, GLFWcocoatogglefullscreenfun callback)
|
||||
GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback)
|
||||
GLFWapplicationwillfinishlaunchingfun glfwSetApplicationWillFinishLaunching(GLFWapplicationwillfinishlaunchingfun callback)
|
||||
void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, void* cocoa_key, void* cocoa_mods)
|
||||
void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, char* cocoa_key, size_t key_sz, int* cocoa_mods)
|
||||
void glfwCocoaRequestRenderFrame(GLFWwindow *w, GLFWcocoarenderframefun callback)
|
||||
void* glfwGetX11Display(void)
|
||||
int32_t glfwGetX11Window(GLFWwindow* window)
|
||||
@@ -197,6 +234,9 @@ const char *action_text, int32_t timeout, GLFWDBusnotificationcreatedfun callbac
|
||||
#include <stdint.h>
|
||||
#include "monotonic.h"
|
||||
|
||||
#ifndef GFW_EXTERN
|
||||
#define GFW_EXTERN extern
|
||||
#endif
|
||||
{}
|
||||
|
||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int,unsigned long);
|
||||
@@ -215,6 +255,7 @@ const char* load_glfw(const char* path);
|
||||
f.write(header)
|
||||
|
||||
code = '''
|
||||
#define GFW_EXTERN
|
||||
#include "data-types.h"
|
||||
#include "glfw-wrapper.h"
|
||||
#include <dlfcn.h>
|
||||
@@ -244,7 +285,7 @@ unload_glfw(void) {
|
||||
f.write(code)
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
generate_wrappers('glfw3.h')
|
||||
|
||||
|
||||
319
glfw/glfw3.h
vendored
319
glfw/glfw3.h
vendored
@@ -195,7 +195,38 @@ extern "C" {
|
||||
|
||||
#endif /*__APPLE__*/
|
||||
|
||||
#elif !defined(GLFW_INCLUDE_NONE)
|
||||
#elif defined(GLFW_INCLUDE_GLU)
|
||||
|
||||
#if defined(__APPLE__)
|
||||
|
||||
#if defined(GLFW_INCLUDE_GLU)
|
||||
#include <OpenGL/glu.h>
|
||||
#endif
|
||||
|
||||
#else /*__APPLE__*/
|
||||
|
||||
#if defined(GLFW_INCLUDE_GLU)
|
||||
#include <GL/glu.h>
|
||||
#endif
|
||||
|
||||
#endif /*__APPLE__*/
|
||||
|
||||
#elif !defined(GLFW_INCLUDE_NONE) && \
|
||||
!defined(__gl_h_) && \
|
||||
!defined(__gles1_gl_h_) && \
|
||||
!defined(__gles2_gl2_h_) && \
|
||||
!defined(__gles2_gl3_h_) && \
|
||||
!defined(__gles2_gl31_h_) && \
|
||||
!defined(__gles2_gl32_h_) && \
|
||||
!defined(__gl_glcorearb_h_) && \
|
||||
!defined(__gl2_h_) /*legacy*/ && \
|
||||
!defined(__gl3_h_) /*legacy*/ && \
|
||||
!defined(__gl31_h_) /*legacy*/ && \
|
||||
!defined(__gl32_h_) /*legacy*/ && \
|
||||
!defined(__glcorearb_h_) /*legacy*/ && \
|
||||
!defined(__GL_H__) /*non-standard*/ && \
|
||||
!defined(__gltypes_h_) /*non-standard*/ && \
|
||||
!defined(__glee_h_) /*non-standard*/
|
||||
|
||||
#if defined(__APPLE__)
|
||||
|
||||
@@ -203,9 +234,6 @@ extern "C" {
|
||||
#define GL_GLEXT_LEGACY
|
||||
#endif
|
||||
#include <OpenGL/gl.h>
|
||||
#if defined(GLFW_INCLUDE_GLU)
|
||||
#include <OpenGL/glu.h>
|
||||
#endif
|
||||
|
||||
#else /*__APPLE__*/
|
||||
|
||||
@@ -213,9 +241,6 @@ extern "C" {
|
||||
#if defined(GLFW_INCLUDE_GLEXT)
|
||||
#include <GL/glext.h>
|
||||
#endif
|
||||
#if defined(GLFW_INCLUDE_GLU)
|
||||
#include <GL/glu.h>
|
||||
#endif
|
||||
|
||||
#endif /*__APPLE__*/
|
||||
|
||||
@@ -405,6 +430,7 @@ extern "C" {
|
||||
#define GLFW_KEY_LEFT_BRACKET 91 /* [ */
|
||||
#define GLFW_KEY_BACKSLASH 92 /* \ */
|
||||
#define GLFW_KEY_RIGHT_BRACKET 93 /* ] */
|
||||
#define GLFW_KEY_CIRCUMFLEX 94 /* ^ */
|
||||
#define GLFW_KEY_UNDERSCORE 95 /* _ */
|
||||
#define GLFW_KEY_GRAVE_ACCENT 96 /* ` */
|
||||
#define GLFW_KEY_WORLD_1 161 /* non-US #1 */
|
||||
@@ -805,6 +831,33 @@ extern "C" {
|
||||
* @analysis Application programmer error. Fix the offending call.
|
||||
*/
|
||||
#define GLFW_NO_WINDOW_CONTEXT 0x0001000A
|
||||
/*! @brief The reuqested feature is not provided by the platform.
|
||||
*
|
||||
* The requested feature is not provided by the platform, so GLFW is unable to
|
||||
* implement it. The documentation for each function notes if it could emit
|
||||
* this error.
|
||||
*
|
||||
* @analysis Platform or platform version limitation. The error can be ignored
|
||||
* unless the feature is critical to the application.
|
||||
*
|
||||
* @par
|
||||
* A function call that emits this error has no effect other than the error and
|
||||
* updating any existing out parameters.
|
||||
*/
|
||||
#define GLFW_FEATURE_UNAVAILABLE 0x0001000C
|
||||
/*! @brief The requested feature is not implemented for the platform.
|
||||
*
|
||||
* The requested feature has not yet been implemented in GLFW for this platform.
|
||||
*
|
||||
* @analysis An incomplete implementation of GLFW for this platform, hopefully
|
||||
* fixed in a future release. The error can be ignored unless the feature is
|
||||
* critical to the application.
|
||||
*
|
||||
* @par
|
||||
* A function call that emits this error has no effect other than the error and
|
||||
* updating any existing out parameters.
|
||||
*/
|
||||
#define GLFW_FEATURE_UNIMPLEMENTED 0x0001000D
|
||||
/*! @} */
|
||||
|
||||
/*! @addtogroup window
|
||||
@@ -1075,6 +1128,7 @@ extern "C" {
|
||||
#define GLFW_STICKY_KEYS 0x00033002
|
||||
#define GLFW_STICKY_MOUSE_BUTTONS 0x00033003
|
||||
#define GLFW_LOCK_KEY_MODS 0x00033004
|
||||
#define GLFW_RAW_MOUSE_MOTION 0x00033005
|
||||
|
||||
#define GLFW_CURSOR_NORMAL 0x00034001
|
||||
#define GLFW_CURSOR_HIDDEN 0x00034002
|
||||
@@ -1318,6 +1372,22 @@ typedef void (* GLFWwindowsizefun)(GLFWwindow*,int,int);
|
||||
*/
|
||||
typedef void (* GLFWwindowclosefun)(GLFWwindow*);
|
||||
|
||||
/*! @brief The function pointer type for application close callbacks.
|
||||
*
|
||||
* This is the function pointer type for application close callbacks. A application
|
||||
* close callback function has the following signature:
|
||||
* @code
|
||||
* void function_name(int flags)
|
||||
* @endcode
|
||||
*
|
||||
* @param[in] flags 0 for a user requested application quit, 1 if a fatal error occurred and application should quit ASAP
|
||||
*
|
||||
* @sa @ref glfwSetApplicationCloseCallback
|
||||
*
|
||||
* @ingroup window
|
||||
*/
|
||||
typedef void (* GLFWapplicationclosefun)(int);
|
||||
|
||||
/*! @brief The function pointer type for window content refresh callbacks.
|
||||
*
|
||||
* This is the function pointer type for window content refresh callbacks.
|
||||
@@ -1549,6 +1619,7 @@ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int);
|
||||
* value 2 for stationary momentum scrolling, value 3 for momentum scrolling
|
||||
* in progress, value 4 for momentum scrolling ended, value 5 for momentum
|
||||
* scrolling cancelled and value 6 if scrolling may begin soon.
|
||||
* @param[int] mods The keyboard modifiers
|
||||
*
|
||||
* @sa @ref scrolling
|
||||
* @sa @ref glfwSetScrollCallback
|
||||
@@ -1558,7 +1629,7 @@ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int);
|
||||
*
|
||||
* @ingroup input
|
||||
*/
|
||||
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int);
|
||||
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int,int);
|
||||
|
||||
/*! @brief The function pointer type for key callbacks.
|
||||
*
|
||||
@@ -1592,19 +1663,22 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int);
|
||||
*/
|
||||
typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*);
|
||||
|
||||
/*! @brief The function pointer type for path drop callbacks.
|
||||
/*! @brief The function pointer type for drag and drop callbacks.
|
||||
*
|
||||
* This is the function pointer type for path drop callbacks. A path drop
|
||||
* This is the function pointer type for drop callbacks. A drop
|
||||
* callback function has the following signature:
|
||||
* @code
|
||||
* void function_name(GLFWwindow* window, int path_count, const char* paths[])
|
||||
* int function_name(GLFWwindow* window, const char* mime, const char* text)
|
||||
* @endcode
|
||||
*
|
||||
* @param[in] window The window that received the event.
|
||||
* @param[in] path_count The number of dropped paths.
|
||||
* @param[in] paths The UTF-8 encoded file and/or directory path names.
|
||||
* @param[in] mime The UTF-8 encoded drop mime-type
|
||||
* @param[in] data The dropped data or NULL for drag enter events
|
||||
* @param[in] sz The size of the dropped data
|
||||
* @return For drag events should return the priority for the specified mime type. A priority of zero
|
||||
* or lower means the mime type is not accepted. Highest priority will be the finally accepted mime-type.
|
||||
*
|
||||
* @pointer_lifetime The path array and its strings are valid until the
|
||||
* @pointer_lifetime The text is valid until the
|
||||
* callback function returns.
|
||||
*
|
||||
* @sa @ref path_drop
|
||||
@@ -1614,7 +1688,7 @@ typedef void (* GLFWkeyboardfun)(GLFWwindow*, GLFWkeyevent*);
|
||||
*
|
||||
* @ingroup input
|
||||
*/
|
||||
typedef void (* GLFWdropfun)(GLFWwindow*,int,const char*[]);
|
||||
typedef int (* GLFWdropfun)(GLFWwindow*, const char *, const char*, size_t);
|
||||
|
||||
typedef void (* GLFWliveresizefun)(GLFWwindow*, bool);
|
||||
|
||||
@@ -1831,6 +1905,8 @@ GLFWAPI void glfwRemoveTimer(unsigned long long);
|
||||
* call this function, as it is called by @ref glfwInit before it returns
|
||||
* failure.
|
||||
*
|
||||
* This function has no effect if GLFW is not initialized.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_PLATFORM_ERROR.
|
||||
*
|
||||
* @remark This function may be called before @ref glfwInit.
|
||||
@@ -2828,21 +2904,21 @@ GLFWAPI void glfwSetWindowTitle(GLFWwindow* window, const char* title);
|
||||
* @param[in] images The images to create the icon from. This is ignored if
|
||||
* count is zero.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
||||
* GLFW_PLATFORM_ERROR.
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
|
||||
* GLFW_PLATFORM_ERROR and @ref GLFW_FEATURE_UNAVAILABLE (see remarks).
|
||||
*
|
||||
* @pointer_lifetime The specified image data is copied before this function
|
||||
* returns.
|
||||
*
|
||||
* @remark @macos The GLFW window has no icon, as it is not a document
|
||||
* window, so this function does nothing. The dock icon will be the same as
|
||||
* @remark @macos Regular windows do not have icons on macOS. This function
|
||||
* will emit @ref GLFW_FEATURE_UNAVAILABLE. The dock icon will be the same as
|
||||
* the application bundle's icon. For more information on bundles, see the
|
||||
* [Bundle Programming Guide](https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/)
|
||||
* in the Mac Developer Library.
|
||||
*
|
||||
* @remark @wayland There is no existing protocol to change an icon, the
|
||||
* window will thus inherit the one defined in the application's desktop file.
|
||||
* This function always emits @ref GLFW_PLATFORM_ERROR.
|
||||
* This function will emit @ref GLFW_FEATURE_UNAVAILABLE.
|
||||
*
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
@@ -2868,12 +2944,12 @@ GLFWAPI void glfwSetWindowIcon(GLFWwindow* window, int count, const GLFWimage* i
|
||||
* @param[out] ypos Where to store the y-coordinate of the upper-left corner of
|
||||
* the content area, or `NULL`.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
||||
* GLFW_PLATFORM_ERROR.
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
|
||||
* GLFW_PLATFORM_ERROR and @ref GLFW_FEATURE_UNAVAILABLE (see remarks).
|
||||
*
|
||||
* @remark @wayland There is no way for an application to retrieve the global
|
||||
* position of its windows, this function will always emit @ref
|
||||
* GLFW_PLATFORM_ERROR.
|
||||
* position of its windows. This function will emit @ref
|
||||
* GLFW_FEATURE_UNAVAILABLE.
|
||||
*
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
@@ -2902,12 +2978,12 @@ GLFWAPI void glfwGetWindowPos(GLFWwindow* window, int* xpos, int* ypos);
|
||||
* @param[in] xpos The x-coordinate of the upper-left corner of the content area.
|
||||
* @param[in] ypos The y-coordinate of the upper-left corner of the content area.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
||||
* GLFW_PLATFORM_ERROR.
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
|
||||
* GLFW_PLATFORM_ERROR and @ref GLFW_FEATURE_UNAVAILABLE (see remarks).
|
||||
*
|
||||
* @remark @wayland There is no way for an application to set the global
|
||||
* position of its windows, this function will always emit @ref
|
||||
* GLFW_PLATFORM_ERROR.
|
||||
* position of its windows. This function will emit @ref
|
||||
* GLFW_FEATURE_UNAVAILABLE.
|
||||
*
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
@@ -3035,6 +3111,46 @@ GLFWAPI void glfwSetWindowSizeLimits(GLFWwindow* window, int minwidth, int minhe
|
||||
*
|
||||
* @ingroup window
|
||||
*/
|
||||
|
||||
GLFWAPI void glfwSetWindowSizeIncrements(GLFWwindow* window, int widthincr, int heightincr);
|
||||
|
||||
/*! @brief Sets the size increments of the specified window.
|
||||
*
|
||||
* This function sets the size increments of the content area of the specified
|
||||
* window. If the window is full screen, the size limits only take effect
|
||||
* once it is made windowed. If the window is not resizable, this function
|
||||
* does nothing.
|
||||
*
|
||||
* The size increments are applied immediately to a windowed mode window and
|
||||
* may cause it to be resized.
|
||||
*
|
||||
* The dimension increments must be greater than zero.
|
||||
*
|
||||
* @param[in] window The window to set limits for.
|
||||
* @param[in] widthincr The width increments, in screen coordinates, of the
|
||||
* content area, or `GLFW_DONT_CARE`.
|
||||
* @param[in] heightincr The height increments, in screen coordinates, of the
|
||||
* content area, or `GLFW_DONT_CARE`.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
|
||||
* GLFW_INVALID_VALUE and @ref GLFW_PLATFORM_ERROR.
|
||||
*
|
||||
* @remark If you set size limits and an aspect ratio that conflict, the
|
||||
* results are undefined.
|
||||
*
|
||||
* @remark @wayland The size limits will not be applied until the window is
|
||||
* actually resized, either by the user or by the compositor.
|
||||
*
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
* @sa @ref window_sizelimits
|
||||
* @sa @ref glfwSetWindowSizeLimits
|
||||
*
|
||||
* @since Added in version 3.2.
|
||||
*
|
||||
* @ingroup window
|
||||
*/
|
||||
|
||||
GLFWAPI void glfwSetWindowAspectRatio(GLFWwindow* window, int numer, int denom);
|
||||
|
||||
/*! @brief Sets the size of the content area of the specified window.
|
||||
@@ -3245,8 +3361,11 @@ GLFWAPI float glfwGetWindowOpacity(GLFWwindow* window);
|
||||
* @param[in] window The window to set the opacity for.
|
||||
* @param[in] opacity The desired opacity of the specified window.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
||||
* GLFW_PLATFORM_ERROR.
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
|
||||
* GLFW_PLATFORM_ERROR and @ref GLFW_FEATURE_UNAVAILABLE (see remarks).
|
||||
*
|
||||
* @remark @wayland There is no way to set an opacity factor for a window.
|
||||
* This function will emit @ref GLFW_FEATURE_UNAVAILABLE.
|
||||
*
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
@@ -3413,11 +3532,11 @@ GLFWAPI void glfwHideWindow(GLFWwindow* window);
|
||||
*
|
||||
* @param[in] window The window to give input focus.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
||||
* GLFW_PLATFORM_ERROR.
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
|
||||
* GLFW_PLATFORM_ERROR and @ref GLFW_FEATURE_UNAVAILABLE (see remarks).
|
||||
*
|
||||
* @remark @wayland It is not possible for an application to bring its windows
|
||||
* to front, this function will always emit @ref GLFW_PLATFORM_ERROR.
|
||||
* @remark @wayland It is not possible for an application to set the input
|
||||
* focus. This function will emit @ref GLFW_FEATURE_UNAVAILABLE.
|
||||
*
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
@@ -3782,6 +3901,7 @@ GLFWAPI GLFWwindowsizefun glfwSetWindowSizeCallback(GLFWwindow* window, GLFWwind
|
||||
* @ingroup window
|
||||
*/
|
||||
GLFWAPI GLFWwindowclosefun glfwSetWindowCloseCallback(GLFWwindow* window, GLFWwindowclosefun callback);
|
||||
GLFWAPI GLFWapplicationclosefun glfwSetApplicationCloseCallback(GLFWapplicationclosefun callback);
|
||||
|
||||
/*! @brief Sets the refresh callback for the specified window.
|
||||
*
|
||||
@@ -4031,11 +4151,13 @@ GLFWAPI void glfwPostEmptyEvent(void);
|
||||
*
|
||||
* This function returns the value of an input option for the specified window.
|
||||
* The mode must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS,
|
||||
* @ref GLFW_STICKY_MOUSE_BUTTONS or @ref GLFW_LOCK_KEY_MODS.
|
||||
* @ref GLFW_STICKY_MOUSE_BUTTONS, @ref GLFW_LOCK_KEY_MODS or
|
||||
* @ref GLFW_RAW_MOUSE_MOTION.
|
||||
*
|
||||
* @param[in] window The window to query.
|
||||
* @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`,
|
||||
* `GLFW_STICKY_MOUSE_BUTTONS` or `GLFW_LOCK_KEY_MODS`.
|
||||
* `GLFW_STICKY_MOUSE_BUTTONS`, `GLFW_LOCK_KEY_MODS` or
|
||||
* `GLFW_RAW_MOUSE_MOTION`.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
||||
* GLFW_INVALID_ENUM.
|
||||
@@ -4054,7 +4176,8 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode);
|
||||
*
|
||||
* This function sets an input mode option for the specified window. The mode
|
||||
* must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS,
|
||||
* @ref GLFW_STICKY_MOUSE_BUTTONS or @ref GLFW_LOCK_KEY_MODS.
|
||||
* @ref GLFW_STICKY_MOUSE_BUTTONS, @ref GLFW_LOCK_KEY_MODS or
|
||||
* @ref GLFW_RAW_MOUSE_MOTION.
|
||||
*
|
||||
* If the mode is `GLFW_CURSOR`, the value must be one of the following cursor
|
||||
* modes:
|
||||
@@ -4086,13 +4209,21 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode);
|
||||
* GLFW_MOD_CAPS_LOCK bit set when the event was generated with Caps Lock on,
|
||||
* and the @ref GLFW_MOD_NUM_LOCK bit when Num Lock was on.
|
||||
*
|
||||
* If the mode is `GLFW_RAW_MOUSE_MOTION`, the value must be either `true`
|
||||
* to enable raw (unscaled and unaccelerated) mouse motion when the cursor is
|
||||
* disabled, or `false` to disable it. If raw motion is not supported,
|
||||
* attempting to set this will emit @ref GLFW_FEATURE_UNAVAILABLE. Call @ref
|
||||
* glfwRawMouseMotionSupported to check for support.
|
||||
*
|
||||
* @param[in] window The window whose input mode to set.
|
||||
* @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`,
|
||||
* `GLFW_STICKY_MOUSE_BUTTONS` or `GLFW_LOCK_KEY_MODS`.
|
||||
* `GLFW_STICKY_MOUSE_BUTTONS`, `GLFW_LOCK_KEY_MODS` or
|
||||
* `GLFW_RAW_MOUSE_MOTION`.
|
||||
* @param[in] value The new value of the specified input mode.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
|
||||
* GLFW_INVALID_ENUM and @ref GLFW_PLATFORM_ERROR.
|
||||
* GLFW_INVALID_ENUM, @ref GLFW_PLATFORM_ERROR and @ref
|
||||
* GLFW_FEATURE_UNAVAILABLE (see above).
|
||||
*
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
@@ -4104,6 +4235,35 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode);
|
||||
*/
|
||||
GLFWAPI void glfwSetInputMode(GLFWwindow* window, int mode, int value);
|
||||
|
||||
/*! @brief Returns whether raw mouse motion is supported.
|
||||
*
|
||||
* This function returns whether raw mouse motion is supported on the current
|
||||
* system. This status does not change after GLFW has been initialized so you
|
||||
* only need to check this once. If you attempt to enable raw motion on
|
||||
* a system that does not support it, @ref GLFW_PLATFORM_ERROR will be emitted.
|
||||
*
|
||||
* Raw mouse motion is closer to the actual motion of the mouse across
|
||||
* a surface. It is not affected by the scaling and acceleration applied to
|
||||
* the motion of the desktop cursor. That processing is suitable for a cursor
|
||||
* while raw motion is better for controlling for example a 3D camera. Because
|
||||
* of this, raw mouse motion is only provided when the cursor is disabled.
|
||||
*
|
||||
* @return `true` if raw mouse motion is supported on the current machine,
|
||||
* or `false` otherwise.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
|
||||
*
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
* @sa @ref raw_mouse_motion
|
||||
* @sa @ref glfwSetInputMode
|
||||
*
|
||||
* @since Added in version 3.3.
|
||||
*
|
||||
* @ingroup input
|
||||
*/
|
||||
GLFWAPI int glfwRawMouseMotionSupported(void);
|
||||
|
||||
/*! @brief Returns the layout-specific name of the specified printable key.
|
||||
*
|
||||
* This function returns the name of the specified printable key, encoded as
|
||||
@@ -4148,6 +4308,7 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* window, int mode, int value);
|
||||
* - `GLFW_KEY_LEFT_BRACKET`
|
||||
* - `GLFW_KEY_BACKSLASH`
|
||||
* - `GLFW_KEY_RIGHT_BRACKET`
|
||||
* - `GLFW_KEY_CIRCUMFLEX`
|
||||
* - `GLFW_KEY_UNDERSCORE`
|
||||
* - `GLFW_KEY_GRAVE_ACCENT`
|
||||
* - `GLFW_KEY_WORLD_1`
|
||||
@@ -5227,9 +5388,6 @@ GLFWAPI const char* glfwGetClipboardString(GLFWwindow* window);
|
||||
* has been set using @ref glfwSetTime it measures time elapsed since GLFW was
|
||||
* initialized.
|
||||
*
|
||||
* This function and @ref glfwSetTime are helper functions on top of @ref
|
||||
* glfwGetTimerFrequency and @ref glfwGetTimerValue.
|
||||
*
|
||||
* The resolution of the timer is system dependent, but is usually on the order
|
||||
* of a few micro- or nanoseconds. It uses the highest-resolution monotonic
|
||||
* time source on each supported platform.
|
||||
@@ -5251,78 +5409,6 @@ GLFWAPI const char* glfwGetClipboardString(GLFWwindow* window);
|
||||
*/
|
||||
GLFWAPI monotonic_t glfwGetTime(void);
|
||||
|
||||
/*! @brief Sets the GLFW time.
|
||||
*
|
||||
* This function sets the current GLFW time, in seconds. The value must be
|
||||
* a positive finite number less than or equal to 18446744073.0, which is
|
||||
* approximately 584.5 years.
|
||||
*
|
||||
* This function and @ref glfwGetTime are helper functions on top of @ref
|
||||
* glfwGetTimerFrequency and @ref glfwGetTimerValue.
|
||||
*
|
||||
* @param[in] time The new value, in seconds.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
||||
* GLFW_INVALID_VALUE.
|
||||
*
|
||||
* @remark The upper limit of GLFW time is calculated as
|
||||
* floor((2<sup>64</sup> - 1) / 10<sup>9</sup>) and is due to implementations
|
||||
* storing nanoseconds in 64 bits. The limit may be increased in the future.
|
||||
*
|
||||
* @thread_safety This function may be called from any thread. Reading and
|
||||
* writing of the internal base time is not atomic, so it needs to be
|
||||
* externally synchronized with calls to @ref glfwGetTime.
|
||||
*
|
||||
* @sa @ref time
|
||||
*
|
||||
* @since Added in version 2.2.
|
||||
*
|
||||
* @ingroup input
|
||||
*/
|
||||
GLFWAPI void glfwSetTime(monotonic_t time);
|
||||
|
||||
/*! @brief Returns the current value of the raw timer.
|
||||
*
|
||||
* This function returns the current value of the raw timer, measured in
|
||||
* 1 / frequency seconds. To get the frequency, call @ref
|
||||
* glfwGetTimerFrequency.
|
||||
*
|
||||
* @return The value of the timer, or zero if an
|
||||
* [error](@ref error_handling) occurred.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
|
||||
*
|
||||
* @thread_safety This function may be called from any thread.
|
||||
*
|
||||
* @sa @ref time
|
||||
* @sa @ref glfwGetTimerFrequency
|
||||
*
|
||||
* @since Added in version 3.2.
|
||||
*
|
||||
* @ingroup input
|
||||
*/
|
||||
GLFWAPI uint64_t glfwGetTimerValue(void);
|
||||
|
||||
/*! @brief Returns the frequency, in Hz, of the raw timer.
|
||||
*
|
||||
* This function returns the frequency, in Hz, of the raw timer.
|
||||
*
|
||||
* @return The frequency of the timer, in Hz, or zero if an
|
||||
* [error](@ref error_handling) occurred.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
|
||||
*
|
||||
* @thread_safety This function may be called from any thread.
|
||||
*
|
||||
* @sa @ref time
|
||||
* @sa @ref glfwGetTimerValue
|
||||
*
|
||||
* @since Added in version 3.2.
|
||||
*
|
||||
* @ingroup input
|
||||
*/
|
||||
GLFWAPI uint64_t glfwGetTimerFrequency(void);
|
||||
|
||||
/*! @brief Makes the context of the specified window current for the calling
|
||||
* thread.
|
||||
*
|
||||
@@ -5599,8 +5685,9 @@ GLFWAPI int glfwVulkanSupported(void);
|
||||
* returned array, as it is an error to specify an extension more than once in
|
||||
* the `VkInstanceCreateInfo` struct.
|
||||
*
|
||||
* @remark @macos This function currently only supports the
|
||||
* `VK_MVK_macos_surface` extension from MoltenVK.
|
||||
* @remark @macos This function currently supports either the
|
||||
* `VK_MVK_macos_surface` extension from MoltenVK or `VK_EXT_metal_surface`
|
||||
* extension.
|
||||
*
|
||||
* @pointer_lifetime The returned array is allocated and freed by GLFW. You
|
||||
* should not free it yourself. It is guaranteed to be valid only until the
|
||||
|
||||
111
glfw/glx_context.c
vendored
111
glfw/glx_context.c
vendored
@@ -47,99 +47,87 @@ static int getGLXFBConfigAttrib(GLXFBConfig fbconfig, int attrib)
|
||||
return value;
|
||||
}
|
||||
|
||||
static GLXFBConfig*
|
||||
choose_fb_config(const _GLFWfbconfig* desired, bool trust_window_bit, int *nelements) {
|
||||
int attrib_list[64];
|
||||
int pos = 0;
|
||||
#define ATTR(x, y) { attrib_list[pos++] = x; attrib_list[pos++] = y; }
|
||||
|
||||
ATTR(GLX_DOUBLEBUFFER, desired->doublebuffer ? True : False);
|
||||
if (desired->stereo > 0) ATTR(GLX_STEREO, desired->stereo ? True : False);
|
||||
if (desired->auxBuffers > 0) ATTR(GLX_AUX_BUFFERS, desired->auxBuffers);
|
||||
if (_glfw.glx.ARB_multisample && desired->samples > 0) ATTR(GLX_SAMPLES, desired->samples);
|
||||
if (desired->depthBits != GLFW_DONT_CARE) ATTR(GLX_DEPTH_SIZE, desired->depthBits);
|
||||
if (desired->stencilBits != GLFW_DONT_CARE) ATTR(GLX_STENCIL_SIZE, desired->stencilBits);
|
||||
if (desired->redBits != GLFW_DONT_CARE) ATTR(GLX_RED_SIZE, desired->redBits);
|
||||
if (desired->greenBits != GLFW_DONT_CARE) ATTR(GLX_GREEN_SIZE, desired->greenBits);
|
||||
if (desired->blueBits != GLFW_DONT_CARE) ATTR(GLX_BLUE_SIZE, desired->blueBits);
|
||||
if (desired->alphaBits != GLFW_DONT_CARE) ATTR(GLX_ALPHA_SIZE, desired->alphaBits);
|
||||
if (desired->accumRedBits != GLFW_DONT_CARE) ATTR(GLX_ACCUM_RED_SIZE, desired->accumRedBits);
|
||||
if (desired->accumGreenBits != GLFW_DONT_CARE) ATTR(GLX_ACCUM_GREEN_SIZE, desired->accumGreenBits);
|
||||
if (desired->accumBlueBits != GLFW_DONT_CARE) ATTR(GLX_ACCUM_BLUE_SIZE, desired->accumBlueBits);
|
||||
if (desired->accumAlphaBits != GLFW_DONT_CARE) ATTR(GLX_ACCUM_ALPHA_SIZE, desired->accumAlphaBits);
|
||||
if (!trust_window_bit) ATTR(GLX_DRAWABLE_TYPE, 0);
|
||||
ATTR(None, None);
|
||||
return glXChooseFBConfig(_glfw.x11.display, _glfw.x11.screen, attrib_list, nelements);
|
||||
#undef ATTR
|
||||
}
|
||||
|
||||
|
||||
// Return the GLXFBConfig most closely matching the specified hints
|
||||
//
|
||||
static bool chooseGLXFBConfig(const _GLFWfbconfig* desired,
|
||||
GLXFBConfig* result)
|
||||
{
|
||||
GLXFBConfig* nativeConfigs;
|
||||
_GLFWfbconfig* usableConfigs;
|
||||
const _GLFWfbconfig* closest;
|
||||
int i, nativeCount, usableCount;
|
||||
int i, nativeCount, ans_idx = 0;
|
||||
const char* vendor;
|
||||
bool trustWindowBit = true;
|
||||
static _GLFWfbconfig prev_desired = {0};
|
||||
static GLXFBConfig prev_result = 0;
|
||||
if (prev_result != 0 && memcmp(&prev_desired, desired, sizeof(_GLFWfbconfig)) == 0) {
|
||||
*result = prev_result;
|
||||
return true;
|
||||
}
|
||||
prev_desired = *desired;
|
||||
|
||||
// HACK: This is a (hopefully temporary) workaround for Chromium
|
||||
// (VirtualBox GL) not setting the window bit on any GLXFBConfigs
|
||||
vendor = glXGetClientString(_glfw.x11.display, GLX_VENDOR);
|
||||
if (vendor && strcmp(vendor, "Chromium") == 0)
|
||||
trustWindowBit = false;
|
||||
|
||||
nativeConfigs =
|
||||
glXGetFBConfigs(_glfw.x11.display, _glfw.x11.screen, &nativeCount);
|
||||
nativeConfigs = choose_fb_config(desired, trustWindowBit, &nativeCount);
|
||||
if (!nativeConfigs || !nativeCount)
|
||||
{
|
||||
_glfwInputError(GLFW_API_UNAVAILABLE, "GLX: No GLXFBConfigs returned");
|
||||
return false;
|
||||
}
|
||||
|
||||
usableConfigs = calloc(nativeCount, sizeof(_GLFWfbconfig));
|
||||
usableCount = 0;
|
||||
|
||||
for (i = 0; i < nativeCount; i++)
|
||||
{
|
||||
const GLXFBConfig n = nativeConfigs[i];
|
||||
_GLFWfbconfig* u = usableConfigs + usableCount;
|
||||
|
||||
// Only consider RGBA GLXFBConfigs
|
||||
if (!(getGLXFBConfigAttrib(n, GLX_RENDER_TYPE) & GLX_RGBA_BIT))
|
||||
continue;
|
||||
|
||||
// Only consider window GLXFBConfigs
|
||||
if (!(getGLXFBConfigAttrib(n, GLX_DRAWABLE_TYPE) & GLX_WINDOW_BIT))
|
||||
{
|
||||
if (trustWindowBit)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (desired->transparent)
|
||||
{
|
||||
bool transparency_matches = true, srgb_matches = true;
|
||||
if (desired->transparent) {
|
||||
transparency_matches = false;
|
||||
XVisualInfo* vi = glXGetVisualFromFBConfig(_glfw.x11.display, n);
|
||||
if (vi)
|
||||
{
|
||||
u->transparent = _glfwIsVisualTransparentX11(vi->visual);
|
||||
XFree(vi);
|
||||
}
|
||||
if (vi && _glfwIsVisualTransparentX11(vi->visual)) transparency_matches = true;
|
||||
}
|
||||
|
||||
u->redBits = getGLXFBConfigAttrib(n, GLX_RED_SIZE);
|
||||
u->greenBits = getGLXFBConfigAttrib(n, GLX_GREEN_SIZE);
|
||||
u->blueBits = getGLXFBConfigAttrib(n, GLX_BLUE_SIZE);
|
||||
if (desired->sRGB && (_glfw.glx.ARB_framebuffer_sRGB || _glfw.glx.EXT_framebuffer_sRGB)) {
|
||||
srgb_matches = getGLXFBConfigAttrib(n, GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB) ? true : false;
|
||||
}
|
||||
|
||||
u->alphaBits = getGLXFBConfigAttrib(n, GLX_ALPHA_SIZE);
|
||||
u->depthBits = getGLXFBConfigAttrib(n, GLX_DEPTH_SIZE);
|
||||
u->stencilBits = getGLXFBConfigAttrib(n, GLX_STENCIL_SIZE);
|
||||
if (transparency_matches && srgb_matches) {
|
||||
ans_idx = i; break;
|
||||
}
|
||||
|
||||
u->accumRedBits = getGLXFBConfigAttrib(n, GLX_ACCUM_RED_SIZE);
|
||||
u->accumGreenBits = getGLXFBConfigAttrib(n, GLX_ACCUM_GREEN_SIZE);
|
||||
u->accumBlueBits = getGLXFBConfigAttrib(n, GLX_ACCUM_BLUE_SIZE);
|
||||
u->accumAlphaBits = getGLXFBConfigAttrib(n, GLX_ACCUM_ALPHA_SIZE);
|
||||
|
||||
u->auxBuffers = getGLXFBConfigAttrib(n, GLX_AUX_BUFFERS);
|
||||
|
||||
if (getGLXFBConfigAttrib(n, GLX_STEREO))
|
||||
u->stereo = true;
|
||||
if (getGLXFBConfigAttrib(n, GLX_DOUBLEBUFFER))
|
||||
u->doublebuffer = true;
|
||||
|
||||
if (_glfw.glx.ARB_multisample)
|
||||
u->samples = getGLXFBConfigAttrib(n, GLX_SAMPLES);
|
||||
|
||||
if (_glfw.glx.ARB_framebuffer_sRGB || _glfw.glx.EXT_framebuffer_sRGB)
|
||||
u->sRGB = getGLXFBConfigAttrib(n, GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB);
|
||||
|
||||
u->handle = (uintptr_t) n;
|
||||
usableCount++;
|
||||
}
|
||||
|
||||
closest = _glfwChooseFBConfig(desired, usableConfigs, usableCount);
|
||||
if (closest)
|
||||
*result = (GLXFBConfig) closest->handle;
|
||||
*result = nativeConfigs[ans_idx];
|
||||
prev_result = nativeConfigs[ans_idx];
|
||||
|
||||
XFree(nativeConfigs);
|
||||
free(usableConfigs);
|
||||
|
||||
return closest != NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Create the OpenGL context using legacy API
|
||||
@@ -287,6 +275,7 @@ bool _glfwInitGLX(void)
|
||||
|
||||
glfw_dlsym(_glfw.glx.GetFBConfigs, _glfw.glx.handle, "glXGetFBConfigs");
|
||||
glfw_dlsym(_glfw.glx.GetFBConfigAttrib, _glfw.glx.handle, "glXGetFBConfigAttrib");
|
||||
glfw_dlsym(_glfw.glx.ChooseFBConfig, _glfw.glx.handle, "glXChooseFBConfig");
|
||||
glfw_dlsym(_glfw.glx.GetClientString, _glfw.glx.handle, "glXGetClientString");
|
||||
glfw_dlsym(_glfw.glx.QueryExtension, _glfw.glx.handle, "glXQueryExtension");
|
||||
glfw_dlsym(_glfw.glx.QueryVersion, _glfw.glx.handle, "glXQueryVersion");
|
||||
|
||||
3
glfw/glx_context.h
vendored
3
glfw/glx_context.h
vendored
@@ -73,6 +73,7 @@ typedef struct __GLXcontext* GLXContext;
|
||||
typedef void (*__GLXextproc)(void);
|
||||
|
||||
typedef int (*PFNGLXGETFBCONFIGATTRIBPROC)(Display*,GLXFBConfig,int,int*);
|
||||
typedef GLXFBConfig* (*PFNGLXCHOOSEFBCONFIGPROC)(Display*,int,const int*,int*);
|
||||
typedef const char* (*PFNGLXGETCLIENTSTRINGPROC)(Display*,int);
|
||||
typedef Bool (*PFNGLXQUERYEXTENSIONPROC)(Display*,int*,int*);
|
||||
typedef Bool (*PFNGLXQUERYVERSIONPROC)(Display*,int*,int*);
|
||||
@@ -95,6 +96,7 @@ typedef GLXContext (*PFNGLXCREATECONTEXTATTRIBSARBPROC)(Display*,GLXFBConfig,GLX
|
||||
// libGL.so function pointer typedefs
|
||||
#define glXGetFBConfigs _glfw.glx.GetFBConfigs
|
||||
#define glXGetFBConfigAttrib _glfw.glx.GetFBConfigAttrib
|
||||
#define glXChooseFBConfig _glfw.glx.ChooseFBConfig
|
||||
#define glXGetClientString _glfw.glx.GetClientString
|
||||
#define glXQueryExtension _glfw.glx.QueryExtension
|
||||
#define glXQueryVersion _glfw.glx.QueryVersion
|
||||
@@ -134,6 +136,7 @@ typedef struct _GLFWlibraryGLX
|
||||
// GLX 1.3 functions
|
||||
PFNGLXGETFBCONFIGSPROC GetFBConfigs;
|
||||
PFNGLXGETFBCONFIGATTRIBPROC GetFBConfigAttrib;
|
||||
PFNGLXCHOOSEFBCONFIGPROC ChooseFBConfig;
|
||||
PFNGLXGETCLIENTSTRINGPROC GetClientString;
|
||||
PFNGLXQUERYEXTENSIONPROC QueryExtension;
|
||||
PFNGLXQUERYVERSIONPROC QueryVersion;
|
||||
|
||||
13
glfw/init.c
vendored
13
glfw/init.c
vendored
@@ -27,7 +27,6 @@
|
||||
// Please use C89 style variable declarations in this file because VS 2010
|
||||
//========================================================================
|
||||
|
||||
#define MONOTONIC_START_MODULE
|
||||
#include "internal.h"
|
||||
#include "mappings.h"
|
||||
|
||||
@@ -169,6 +168,10 @@ void _glfwInputError(int code, const char* format, ...)
|
||||
strcpy(description, "The requested format is unavailable");
|
||||
else if (code == GLFW_NO_WINDOW_CONTEXT)
|
||||
strcpy(description, "The specified window has no context");
|
||||
else if (code == GLFW_FEATURE_UNAVAILABLE)
|
||||
strcpy(description, "The requested feature cannot be implemented for this platform");
|
||||
else if (code == GLFW_FEATURE_UNIMPLEMENTED)
|
||||
strcpy(description, "The requested feature has not yet been implemented for this platform");
|
||||
else
|
||||
strcpy(description, "ERROR: UNKNOWN GLFW ERROR");
|
||||
}
|
||||
@@ -241,7 +244,6 @@ GLFWAPI int glfwInit(monotonic_t start_time)
|
||||
_glfwPlatformSetTls(&_glfw.errorSlot, &_glfwMainThreadError);
|
||||
|
||||
_glfw.initialized = true;
|
||||
_glfw.timer.offset = _glfwPlatformGetTimerValue();
|
||||
|
||||
glfwDefaultWindowHints();
|
||||
|
||||
@@ -365,3 +367,10 @@ GLFWAPI void glfwUpdateTimer(unsigned long long timer_id, monotonic_t interval,
|
||||
GLFWAPI void glfwRemoveTimer(unsigned long long timer_id) {
|
||||
_glfwPlatformRemoveTimer(timer_id);
|
||||
}
|
||||
|
||||
GLFWAPI GLFWapplicationclosefun glfwSetApplicationCloseCallback(GLFWapplicationclosefun cbfun)
|
||||
{
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
_GLFW_SWAP_POINTERS(_glfw.callbacks.application_close, cbfun);
|
||||
return cbfun;
|
||||
}
|
||||
|
||||
73
glfw/input.c
vendored
73
glfw/input.c
vendored
@@ -300,10 +300,10 @@ void _glfwInputKeyboard(_GLFWwindow* window, GLFWkeyevent* ev)
|
||||
|
||||
// Notifies shared code of a scroll event
|
||||
//
|
||||
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags)
|
||||
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags, int mods)
|
||||
{
|
||||
if (window->callbacks.scroll)
|
||||
window->callbacks.scroll((GLFWwindow*) window, xoffset, yoffset, flags);
|
||||
window->callbacks.scroll((GLFWwindow*) window, xoffset, yoffset, flags, mods);
|
||||
}
|
||||
|
||||
// Notifies shared code of a mouse button click event
|
||||
@@ -350,10 +350,11 @@ void _glfwInputCursorEnter(_GLFWwindow* window, bool entered)
|
||||
|
||||
// Notifies shared code of files or directories dropped on a window
|
||||
//
|
||||
void _glfwInputDrop(_GLFWwindow* window, int count, const char** paths)
|
||||
int _glfwInputDrop(_GLFWwindow* window, const char *mime, const char *text, size_t sz)
|
||||
{
|
||||
if (window->callbacks.drop)
|
||||
window->callbacks.drop((GLFWwindow*) window, count, paths);
|
||||
return window->callbacks.drop((GLFWwindow*) window, mime, text, sz);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Notifies shared code of a joystick connection or disconnection
|
||||
@@ -510,6 +511,7 @@ const char* _glfwGetKeyName(int key)
|
||||
case GLFW_KEY_LEFT_BRACKET: return "LEFT_BRACKET";
|
||||
case GLFW_KEY_BACKSLASH: return "BACKSLASH";
|
||||
case GLFW_KEY_RIGHT_BRACKET: return "RIGHT_BRACKET";
|
||||
case GLFW_KEY_CIRCUMFLEX: return "CIRCUMFLEX";
|
||||
case GLFW_KEY_UNDERSCORE: return "UNDERSCORE";
|
||||
case GLFW_KEY_GRAVE_ACCENT: return "GRAVE_ACCENT";
|
||||
case GLFW_KEY_WORLD_1: return "WORLD_1";
|
||||
@@ -673,6 +675,8 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* handle, int mode)
|
||||
return window->stickyMouseButtons;
|
||||
case GLFW_LOCK_KEY_MODS:
|
||||
return window->lockKeyMods;
|
||||
case GLFW_RAW_MOUSE_MOTION:
|
||||
return window->rawMouseMotion;
|
||||
}
|
||||
|
||||
_glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode);
|
||||
@@ -752,10 +756,32 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value)
|
||||
{
|
||||
window->lockKeyMods = value ? true : false;
|
||||
}
|
||||
else if (mode == GLFW_RAW_MOUSE_MOTION)
|
||||
{
|
||||
if (!_glfwPlatformRawMouseMotionSupported())
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Raw mouse motion is not supported on this system");
|
||||
return;
|
||||
}
|
||||
|
||||
value = value ? true : false;
|
||||
if (window->rawMouseMotion == value)
|
||||
return;
|
||||
|
||||
window->rawMouseMotion = value;
|
||||
_glfwPlatformSetRawMouseMotion(window, value);
|
||||
}
|
||||
else
|
||||
_glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode);
|
||||
}
|
||||
|
||||
GLFWAPI int glfwRawMouseMotionSupported(void)
|
||||
{
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(false);
|
||||
return _glfwPlatformRawMouseMotionSupported();
|
||||
}
|
||||
|
||||
GLFWAPI const char* glfwGetKeyName(int key, int native_key)
|
||||
{
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
@@ -1410,8 +1436,18 @@ GLFWAPI int glfwGetGamepadState(int jid, GLFWgamepadstate* state)
|
||||
if (e->type == _GLFW_JOYSTICK_AXIS)
|
||||
{
|
||||
const float value = js->axes[e->index] * e->axisScale + e->axisOffset;
|
||||
if (value > 0.f)
|
||||
state->buttons[i] = GLFW_PRESS;
|
||||
// HACK: This should be baked into the value transform
|
||||
// TODO: Bake into transform when implementing output modifiers
|
||||
if (e->axisOffset < 0 || (e->axisOffset == 0 && e->axisScale > 0))
|
||||
{
|
||||
if (value >= 0.f)
|
||||
state->buttons[i] = GLFW_PRESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (value <= 0.f)
|
||||
state->buttons[i] = GLFW_PRESS;
|
||||
}
|
||||
}
|
||||
else if (e->type == _GLFW_JOYSTICK_HATBIT)
|
||||
{
|
||||
@@ -1483,28 +1519,3 @@ GLFWAPI monotonic_t glfwGetTime(void)
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(0);
|
||||
return monotonic();
|
||||
}
|
||||
|
||||
GLFWAPI void glfwSetTime(monotonic_t time)
|
||||
{
|
||||
_GLFW_REQUIRE_INIT();
|
||||
|
||||
if (time < 0)
|
||||
{
|
||||
_glfwInputError(GLFW_INVALID_VALUE, "Invalid time %f", monotonic_t_to_s_double(time));
|
||||
return;
|
||||
}
|
||||
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
GLFWAPI uint64_t glfwGetTimerValue(void)
|
||||
{
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(0);
|
||||
return _glfwPlatformGetTimerValue();
|
||||
}
|
||||
|
||||
GLFWAPI uint64_t glfwGetTimerFrequency(void)
|
||||
{
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(0);
|
||||
return _glfwPlatformGetTimerFrequency();
|
||||
}
|
||||
|
||||
25
glfw/internal.h
vendored
25
glfw/internal.h
vendored
@@ -132,6 +132,7 @@ typedef enum VkStructureType
|
||||
VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR = 1000006000,
|
||||
VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR = 1000009000,
|
||||
VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK = 1000123000,
|
||||
VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT = 1000217000,
|
||||
VK_STRUCTURE_TYPE_MAX_ENUM = 0x7FFFFFFF
|
||||
} VkStructureType;
|
||||
|
||||
@@ -418,6 +419,7 @@ struct _GLFWwindow
|
||||
int minwidth, minheight;
|
||||
int maxwidth, maxheight;
|
||||
int numer, denom;
|
||||
int widthincr, heightincr;
|
||||
|
||||
bool stickyKeys;
|
||||
bool stickyMouseButtons;
|
||||
@@ -427,6 +429,7 @@ struct _GLFWwindow
|
||||
char keys[GLFW_KEY_LAST + 1];
|
||||
// Virtual cursor position when cursor is disabled
|
||||
double virtualCursorPosX, virtualCursorPosY;
|
||||
bool rawMouseMotion;
|
||||
|
||||
_GLFWcontext context;
|
||||
|
||||
@@ -574,12 +577,6 @@ struct _GLFWlibrary
|
||||
_GLFWtls contextSlot;
|
||||
_GLFWmutex errorLock;
|
||||
|
||||
struct {
|
||||
uint64_t offset;
|
||||
// This is defined in the platform's time.h
|
||||
_GLFW_PLATFORM_LIBRARY_TIMER_STATE;
|
||||
} timer;
|
||||
|
||||
struct {
|
||||
bool available;
|
||||
void* handle;
|
||||
@@ -593,6 +590,7 @@ struct _GLFWlibrary
|
||||
bool KHR_win32_surface;
|
||||
#elif defined(_GLFW_COCOA)
|
||||
bool MVK_macos_surface;
|
||||
bool EXT_metal_surface;
|
||||
#elif defined(_GLFW_X11)
|
||||
bool KHR_xlib_surface;
|
||||
bool KHR_xcb_surface;
|
||||
@@ -604,8 +602,10 @@ struct _GLFWlibrary
|
||||
struct {
|
||||
GLFWmonitorfun monitor;
|
||||
GLFWjoystickfun joystick;
|
||||
GLFWapplicationclosefun application_close;
|
||||
} callbacks;
|
||||
|
||||
|
||||
// This is defined in the window API's platform.h
|
||||
_GLFW_PLATFORM_LIBRARY_WINDOW_STATE;
|
||||
// This is defined in the context API's context.h
|
||||
@@ -634,6 +634,8 @@ const char* _glfwPlatformGetVersionString(void);
|
||||
void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos);
|
||||
void _glfwPlatformSetCursorPos(_GLFWwindow* window, double xpos, double ypos);
|
||||
void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode);
|
||||
void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window, bool enabled);
|
||||
bool _glfwPlatformRawMouseMotionSupported(void);
|
||||
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
const GLFWimage* image, int xhot, int yhot, int count);
|
||||
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, GLFWCursorShape shape);
|
||||
@@ -663,9 +665,6 @@ const char* _glfwPlatformGetPrimarySelectionString(void);
|
||||
int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode);
|
||||
void _glfwPlatformUpdateGamepadGUID(char* guid);
|
||||
|
||||
uint64_t _glfwPlatformGetTimerValue(void);
|
||||
uint64_t _glfwPlatformGetTimerFrequency(void);
|
||||
|
||||
int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
||||
const _GLFWwndconfig* wndconfig,
|
||||
const _GLFWctxconfig* ctxconfig,
|
||||
@@ -682,6 +681,7 @@ void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
|
||||
int minwidth, int minheight,
|
||||
int maxwidth, int maxheight);
|
||||
void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom);
|
||||
void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window, int widthincr, int heightincr);
|
||||
void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height);
|
||||
void _glfwInputLiveResize(_GLFWwindow* window, bool started);
|
||||
void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
||||
@@ -760,11 +760,11 @@ void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor);
|
||||
|
||||
void _glfwInitializeKeyEvent(GLFWkeyevent *ev, int key, int native_key, int action, int mods);
|
||||
void _glfwInputKeyboard(_GLFWwindow *window, GLFWkeyevent *ev);
|
||||
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags);
|
||||
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags, int mods);
|
||||
void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods);
|
||||
void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos);
|
||||
void _glfwInputCursorEnter(_GLFWwindow* window, bool entered);
|
||||
void _glfwInputDrop(_GLFWwindow* window, int count, const char** names);
|
||||
int _glfwInputDrop(_GLFWwindow* window, const char *mime, const char *text, size_t sz);
|
||||
void _glfwInputJoystick(_GLFWjoystick* js, int event);
|
||||
void _glfwInputJoystickAxis(_GLFWjoystick* js, int axis, float value);
|
||||
void _glfwInputJoystickButton(_GLFWjoystick* js, int button, char value);
|
||||
@@ -794,9 +794,6 @@ void _glfwDebug(const char* format, ...);
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool _glfwStringInExtensionString(const char* string, const char* extensions);
|
||||
const _GLFWfbconfig* _glfwChooseFBConfig(const _GLFWfbconfig* desired,
|
||||
const _GLFWfbconfig* alternatives,
|
||||
unsigned int count);
|
||||
bool _glfwRefreshContextAttribs(_GLFWwindow* window,
|
||||
const _GLFWctxconfig* ctxconfig);
|
||||
bool _glfwIsValidContextConfig(const _GLFWctxconfig* ctxconfig);
|
||||
|
||||
10
glfw/monotonic.c
vendored
Normal file
10
glfw/monotonic.c
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* monotonic.c
|
||||
* Copyright (C) 2019 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
#define MONOTONIC_IMPLEMENTATION
|
||||
#include "../kitty/monotonic.h"
|
||||
@@ -293,8 +293,7 @@ bool _glfwCreateContextNSGL(_GLFWwindow* window,
|
||||
forParameter:NSOpenGLContextParameterSurfaceOpacity];
|
||||
}
|
||||
|
||||
if (window->ns.retina)
|
||||
[window->ns.view setWantsBestResolutionOpenGLSurface:YES];
|
||||
[window->ns.view setWantsBestResolutionOpenGLSurface:window->ns.retina];
|
||||
|
||||
GLint interval = 0;
|
||||
[window->context.nsgl.object setValues:&interval
|
||||
|
||||
7
glfw/null_init.c
vendored
7
glfw/null_init.c
vendored
@@ -29,6 +29,8 @@
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW platform API //////
|
||||
@@ -36,12 +38,14 @@
|
||||
|
||||
int _glfwPlatformInit(void)
|
||||
{
|
||||
_glfwInitTimerPOSIX();
|
||||
_glfwPollMonitorsNull();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void _glfwPlatformTerminate(void)
|
||||
{
|
||||
free(_glfw.null.clipboardString);
|
||||
_glfwTerminateOSMesa();
|
||||
}
|
||||
|
||||
@@ -49,4 +53,3 @@ const char* _glfwPlatformGetVersionString(void)
|
||||
{
|
||||
return _GLFW_VERSION_NUMBER " null OSMesa";
|
||||
}
|
||||
|
||||
|
||||
96
glfw/null_monitor.c
vendored
96
glfw/null_monitor.c
vendored
@@ -29,13 +29,45 @@
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
// The the sole (fake) video mode of our (sole) fake monitor
|
||||
//
|
||||
static GLFWvidmode getVideoMode(void)
|
||||
{
|
||||
GLFWvidmode mode;
|
||||
mode.width = 1920;
|
||||
mode.height = 1080;
|
||||
mode.redBits = 8;
|
||||
mode.greenBits = 8;
|
||||
mode.blueBits = 8;
|
||||
mode.refreshRate = 60;
|
||||
return mode;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW internal API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void _glfwPollMonitorsNull(void)
|
||||
{
|
||||
const float dpi = 141.f;
|
||||
const GLFWvidmode mode = getVideoMode();
|
||||
_GLFWmonitor* monitor = _glfwAllocMonitor("Null SuperNoop 0",
|
||||
(int) (mode.width * 25.4f / dpi),
|
||||
(int) (mode.height * 25.4f / dpi));
|
||||
_glfwInputMonitor(monitor, GLFW_CONNECTED, _GLFW_INSERT_FIRST);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW platform API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor UNUSED)
|
||||
void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor)
|
||||
{
|
||||
_glfwFreeGammaArrays(&monitor->null.ramp);
|
||||
}
|
||||
|
||||
void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor UNUSED, int* xpos UNUSED, int* ypos UNUSED)
|
||||
@@ -52,26 +84,72 @@ void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor UNUSED,
|
||||
}
|
||||
|
||||
void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor UNUSED,
|
||||
int* xpos UNUSED, int* ypos UNUSED,
|
||||
int* width UNUSED, int* height UNUSED)
|
||||
int* xpos, int* ypos,
|
||||
int* width, int* height)
|
||||
{
|
||||
const GLFWvidmode mode = getVideoMode();
|
||||
|
||||
if (xpos)
|
||||
*xpos = 10;
|
||||
if (ypos)
|
||||
ypos = 0;
|
||||
if (width)
|
||||
*width = mode.width;
|
||||
if (height)
|
||||
*height = mode.height - 10;
|
||||
}
|
||||
|
||||
GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor UNUSED, int* found UNUSED)
|
||||
GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor UNUSED, int* found)
|
||||
{
|
||||
return NULL;
|
||||
GLFWvidmode* mode = calloc(1, sizeof(GLFWvidmode));
|
||||
*mode = getVideoMode();
|
||||
*found = 1;
|
||||
return mode;
|
||||
}
|
||||
|
||||
void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor UNUSED, GLFWvidmode* mode UNUSED)
|
||||
void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor UNUSED, GLFWvidmode* mode)
|
||||
{
|
||||
*mode = getVideoMode();
|
||||
}
|
||||
|
||||
bool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor UNUSED, GLFWgammaramp* ramp UNUSED)
|
||||
bool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
{
|
||||
return false;
|
||||
if (!monitor->null.ramp.size)
|
||||
{
|
||||
_glfwAllocGammaArrays(&monitor->null.ramp, 256);
|
||||
|
||||
for (unsigned int i = 0; i < monitor->null.ramp.size; i++)
|
||||
{
|
||||
const float gamma = 2.2f;
|
||||
float value;
|
||||
value = i / (float) (monitor->null.ramp.size - 1);
|
||||
value = powf(value, 1.f / gamma) * 65535.f + 0.5f;
|
||||
value = _glfw_fminf(value, 65535.f);
|
||||
|
||||
monitor->null.ramp.red[i] = (unsigned short) value;
|
||||
monitor->null.ramp.green[i] = (unsigned short) value;
|
||||
monitor->null.ramp.blue[i] = (unsigned short) value;
|
||||
}
|
||||
}
|
||||
|
||||
_glfwAllocGammaArrays(ramp, monitor->null.ramp.size);
|
||||
memcpy(ramp->red, monitor->null.ramp.red, sizeof(short) * ramp->size);
|
||||
memcpy(ramp->green, monitor->null.ramp.green, sizeof(short) * ramp->size);
|
||||
memcpy(ramp->blue, monitor->null.ramp.blue, sizeof(short) * ramp->size);
|
||||
return true;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor UNUSED, const GLFWgammaramp* ramp UNUSED)
|
||||
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
|
||||
{
|
||||
if (monitor->null.ramp.size != ramp->size)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Null: Gamma ramp size must match current ramp size");
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(monitor->null.ramp.red, ramp->red, sizeof(short) * ramp->size);
|
||||
memcpy(monitor->null.ramp.green, ramp->green, sizeof(short) * ramp->size);
|
||||
memcpy(monitor->null.ramp.blue, ramp->blue, sizeof(short) * ramp->size);
|
||||
}
|
||||
|
||||
|
||||
39
glfw/null_platform.h
vendored
39
glfw/null_platform.h
vendored
@@ -27,12 +27,12 @@
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowNull null
|
||||
#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowNull null
|
||||
#define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE _GLFWlibraryNull null
|
||||
#define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorNull null
|
||||
|
||||
#define _GLFW_PLATFORM_CONTEXT_STATE
|
||||
#define _GLFW_PLATFORM_MONITOR_STATE
|
||||
#define _GLFW_PLATFORM_CURSOR_STATE
|
||||
#define _GLFW_PLATFORM_LIBRARY_WINDOW_STATE
|
||||
#define _GLFW_PLATFORM_LIBRARY_CONTEXT_STATE
|
||||
#define _GLFW_EGL_CONTEXT_STATE
|
||||
#define _GLFW_EGL_LIBRARY_CONTEXT_STATE
|
||||
@@ -56,7 +56,36 @@
|
||||
//
|
||||
typedef struct _GLFWwindowNull
|
||||
{
|
||||
int width;
|
||||
int height;
|
||||
int xpos;
|
||||
int ypos;
|
||||
int width;
|
||||
int height;
|
||||
char* title;
|
||||
bool visible;
|
||||
bool iconified;
|
||||
bool maximized;
|
||||
bool resizable;
|
||||
bool decorated;
|
||||
bool floating;
|
||||
bool transparent;
|
||||
float opacity;
|
||||
} _GLFWwindowNull;
|
||||
|
||||
// Null-specific per-monitor data
|
||||
//
|
||||
typedef struct _GLFWmonitorNull
|
||||
{
|
||||
GLFWgammaramp ramp;
|
||||
} _GLFWmonitorNull;
|
||||
|
||||
// Null-specific global data
|
||||
//
|
||||
typedef struct _GLFWlibraryNull
|
||||
{
|
||||
int xcursor;
|
||||
int ycursor;
|
||||
char* clipboardString;
|
||||
_GLFWwindow* focusedWindow;
|
||||
} _GLFWlibraryNull;
|
||||
|
||||
void _glfwPollMonitorsNull(void);
|
||||
|
||||
500
glfw/null_window.c
vendored
500
glfw/null_window.c
vendored
@@ -30,12 +30,71 @@
|
||||
#include "internal.h"
|
||||
#include "../kitty/monotonic.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
static void applySizeLimits(_GLFWwindow* window, int* width, int* height)
|
||||
{
|
||||
if (window->numer != GLFW_DONT_CARE && window->denom != GLFW_DONT_CARE)
|
||||
{
|
||||
const float ratio = (float) window->numer / (float) window->denom;
|
||||
*height = (int) (*width / ratio);
|
||||
}
|
||||
|
||||
if (window->minwidth != GLFW_DONT_CARE && *width < window->minwidth)
|
||||
*width = window->minwidth;
|
||||
else if (window->maxwidth != GLFW_DONT_CARE && *width > window->maxwidth)
|
||||
*width = window->maxwidth;
|
||||
|
||||
if (window->minheight != GLFW_DONT_CARE && *height < window->minheight)
|
||||
*height = window->minheight;
|
||||
else if (window->maxheight != GLFW_DONT_CARE && *height > window->maxheight)
|
||||
*height = window->maxheight;
|
||||
}
|
||||
|
||||
static void fitToMonitor(_GLFWwindow* window)
|
||||
{
|
||||
GLFWvidmode mode;
|
||||
_glfwPlatformGetVideoMode(window->monitor, &mode);
|
||||
_glfwPlatformGetMonitorPos(window->monitor,
|
||||
&window->null.xpos,
|
||||
&window->null.ypos);
|
||||
window->null.width = mode.width;
|
||||
window->null.height = mode.height;
|
||||
}
|
||||
|
||||
static void acquireMonitor(_GLFWwindow* window)
|
||||
{
|
||||
_glfwInputMonitorWindow(window->monitor, window);
|
||||
}
|
||||
|
||||
static void releaseMonitor(_GLFWwindow* window)
|
||||
{
|
||||
if (window->monitor->window != window)
|
||||
return;
|
||||
|
||||
_glfwInputMonitorWindow(window->monitor, NULL);
|
||||
}
|
||||
|
||||
static int createNativeWindow(_GLFWwindow* window,
|
||||
const _GLFWwndconfig* wndconfig)
|
||||
const _GLFWwndconfig* wndconfig,
|
||||
const _GLFWfbconfig* fbconfig)
|
||||
{
|
||||
window->null.width = wndconfig->width;
|
||||
window->null.height = wndconfig->height;
|
||||
if (window->monitor)
|
||||
fitToMonitor(window);
|
||||
else
|
||||
{
|
||||
window->null.xpos = 17;
|
||||
window->null.ypos = 17;
|
||||
window->null.width = wndconfig->width;
|
||||
window->null.height = wndconfig->height;
|
||||
}
|
||||
|
||||
window->null.visible = wndconfig->visible;
|
||||
window->null.decorated = wndconfig->decorated;
|
||||
window->null.maximized = wndconfig->maximized;
|
||||
window->null.floating = wndconfig->floating;
|
||||
window->null.transparent = fbconfig->transparent;
|
||||
window->null.opacity = 1.f;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -50,7 +109,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
||||
const _GLFWctxconfig* ctxconfig,
|
||||
const _GLFWfbconfig* fbconfig)
|
||||
{
|
||||
if (!createNativeWindow(window, wndconfig))
|
||||
if (!createNativeWindow(window, wndconfig, fbconfig))
|
||||
return false;
|
||||
|
||||
if (ctxconfig->client != GLFW_NO_API)
|
||||
@@ -70,11 +129,24 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
||||
}
|
||||
}
|
||||
|
||||
if (window->monitor)
|
||||
{
|
||||
_glfwPlatformShowWindow(window);
|
||||
_glfwPlatformFocusWindow(window);
|
||||
acquireMonitor(window);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void _glfwPlatformDestroyWindow(_GLFWwindow* window)
|
||||
{
|
||||
if (window->monitor)
|
||||
releaseMonitor(window);
|
||||
|
||||
if (_glfw.null.focusedWindow == window)
|
||||
_glfw.null.focusedWindow = NULL;
|
||||
|
||||
if (window->context.destroy)
|
||||
window->context.destroy(window);
|
||||
}
|
||||
@@ -88,20 +160,60 @@ void _glfwPlatformSetWindowIcon(_GLFWwindow* window UNUSED, int count UNUSED,
|
||||
{
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowMonitor(_GLFWwindow* window UNUSED,
|
||||
_GLFWmonitor* monitor UNUSED,
|
||||
int xpos UNUSED, int ypos UNUSED,
|
||||
int width UNUSED, int height UNUSED,
|
||||
void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
|
||||
_GLFWmonitor* monitor,
|
||||
int xpos, int ypos,
|
||||
int width, int height,
|
||||
int refreshRate UNUSED)
|
||||
{
|
||||
if (window->monitor == monitor)
|
||||
{
|
||||
if (!monitor)
|
||||
{
|
||||
_glfwPlatformSetWindowPos(window, xpos, ypos);
|
||||
_glfwPlatformSetWindowSize(window, width, height);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (window->monitor)
|
||||
releaseMonitor(window);
|
||||
|
||||
_glfwInputWindowMonitor(window, monitor);
|
||||
|
||||
if (window->monitor)
|
||||
{
|
||||
window->null.visible = true;
|
||||
acquireMonitor(window);
|
||||
fitToMonitor(window);
|
||||
}
|
||||
else
|
||||
{
|
||||
_glfwPlatformSetWindowPos(window, xpos, ypos);
|
||||
_glfwPlatformSetWindowSize(window, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
void _glfwPlatformGetWindowPos(_GLFWwindow* window UNUSED, int* xpos UNUSED, int* ypos UNUSED)
|
||||
void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos)
|
||||
{
|
||||
if (xpos)
|
||||
*xpos = window->null.xpos;
|
||||
if (ypos)
|
||||
*ypos = window->null.ypos;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowPos(_GLFWwindow* window UNUSED, int xpos UNUSED, int ypos UNUSED)
|
||||
void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos)
|
||||
{
|
||||
if (window->monitor)
|
||||
return;
|
||||
|
||||
if (window->null.xpos != xpos || window->null.ypos != ypos)
|
||||
{
|
||||
window->null.xpos = xpos;
|
||||
window->null.ypos = ypos;
|
||||
_glfwInputWindowPos(window, xpos, ypos);
|
||||
}
|
||||
}
|
||||
|
||||
void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height)
|
||||
@@ -114,17 +226,37 @@ void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height)
|
||||
|
||||
void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
|
||||
{
|
||||
window->null.width = width;
|
||||
window->null.height = height;
|
||||
if (window->monitor)
|
||||
return;
|
||||
|
||||
if (window->null.width != width || window->null.height != height)
|
||||
{
|
||||
window->null.width = width;
|
||||
window->null.height = height;
|
||||
_glfwInputWindowSize(window, width, height);
|
||||
_glfwInputFramebufferSize(window, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window UNUSED,
|
||||
void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
|
||||
int minwidth UNUSED, int minheight UNUSED,
|
||||
int maxwidth UNUSED, int maxheight UNUSED)
|
||||
{
|
||||
int width = window->null.width;
|
||||
int height = window->null.height;
|
||||
applySizeLimits(window, &width, &height);
|
||||
_glfwPlatformSetWindowSize(window, width, height);
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window UNUSED, int n UNUSED, int d UNUSED)
|
||||
void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int n UNUSED, int d UNUSED)
|
||||
{
|
||||
int width = window->null.width;
|
||||
int height = window->null.height;
|
||||
applySizeLimits(window, &width, &height);
|
||||
_glfwPlatformSetWindowSize(window, width, height);
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window UNUSED, int widthincr UNUSED, int heightincr UNUSED)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -136,10 +268,17 @@ void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* heigh
|
||||
*height = window->null.height;
|
||||
}
|
||||
|
||||
void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window UNUSED,
|
||||
int* left UNUSED, int* top UNUSED,
|
||||
int* right UNUSED, int* bottom UNUSED)
|
||||
void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
||||
int* left, int* top,
|
||||
int* right, int* bottom)
|
||||
{
|
||||
if (window->null.decorated && !window->monitor)
|
||||
{
|
||||
*left = 1;
|
||||
*top = 10;
|
||||
*right = 1;
|
||||
*bottom = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void _glfwPlatformGetWindowContentScale(_GLFWwindow* window UNUSED,
|
||||
@@ -156,59 +295,107 @@ monotonic_t _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window UNUSED)
|
||||
return ms_to_monotonic_t(500ll);
|
||||
}
|
||||
|
||||
void _glfwPlatformIconifyWindow(_GLFWwindow* window UNUSED)
|
||||
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
|
||||
{
|
||||
if (_glfw.null.focusedWindow == window)
|
||||
{
|
||||
_glfw.null.focusedWindow = NULL;
|
||||
_glfwInputWindowFocus(window, false);
|
||||
}
|
||||
|
||||
if (!window->null.iconified)
|
||||
{
|
||||
window->null.iconified = true;
|
||||
_glfwInputWindowIconify(window, true);
|
||||
|
||||
if (window->monitor)
|
||||
releaseMonitor(window);
|
||||
}
|
||||
}
|
||||
|
||||
void _glfwPlatformRestoreWindow(_GLFWwindow* window)
|
||||
{
|
||||
if (window->null.iconified)
|
||||
{
|
||||
window->null.iconified = false;
|
||||
_glfwInputWindowIconify(window, false);
|
||||
|
||||
if (window->monitor)
|
||||
acquireMonitor(window);
|
||||
}
|
||||
else if (window->null.maximized)
|
||||
{
|
||||
window->null.maximized = false;
|
||||
_glfwInputWindowMaximize(window, false);
|
||||
}
|
||||
}
|
||||
|
||||
void _glfwPlatformMaximizeWindow(_GLFWwindow* window)
|
||||
{
|
||||
if (!window->null.maximized)
|
||||
{
|
||||
window->null.maximized = true;
|
||||
_glfwInputWindowMaximize(window, true);
|
||||
}
|
||||
}
|
||||
|
||||
int _glfwPlatformWindowMaximized(_GLFWwindow* window)
|
||||
{
|
||||
return window->null.maximized;
|
||||
}
|
||||
|
||||
int _glfwPlatformWindowHovered(_GLFWwindow* window)
|
||||
{
|
||||
return _glfw.null.xcursor >= window->null.xpos &&
|
||||
_glfw.null.ycursor >= window->null.ypos &&
|
||||
_glfw.null.xcursor <= window->null.xpos + window->null.width - 1 &&
|
||||
_glfw.null.ycursor <= window->null.ypos + window->null.height - 1;
|
||||
}
|
||||
|
||||
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
|
||||
{
|
||||
return window->null.transparent;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowResizable(_GLFWwindow* window, bool enabled)
|
||||
{
|
||||
window->null.resizable = enabled;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, bool enabled)
|
||||
{
|
||||
window->null.decorated = enabled;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled)
|
||||
{
|
||||
window->null.floating = enabled;
|
||||
}
|
||||
|
||||
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window)
|
||||
{
|
||||
return window->null.opacity;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity)
|
||||
{
|
||||
window->null.opacity = opacity;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window UNUSED, bool enabled UNUSED)
|
||||
{
|
||||
}
|
||||
|
||||
void _glfwPlatformRestoreWindow(_GLFWwindow* window UNUSED)
|
||||
bool _glfwPlatformRawMouseMotionSupported(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void _glfwPlatformMaximizeWindow(_GLFWwindow* window UNUSED)
|
||||
void _glfwPlatformShowWindow(_GLFWwindow* window)
|
||||
{
|
||||
window->null.visible = true;
|
||||
}
|
||||
|
||||
int _glfwPlatformWindowMaximized(_GLFWwindow* window UNUSED)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int _glfwPlatformWindowHovered(_GLFWwindow* window UNUSED)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window UNUSED)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowResizable(_GLFWwindow* window UNUSED, bool enabled UNUSED)
|
||||
{
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowDecorated(_GLFWwindow* window UNUSED, bool enabled UNUSED)
|
||||
{
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowFloating(_GLFWwindow* window UNUSED, bool enabled UNUSED)
|
||||
{
|
||||
}
|
||||
|
||||
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window UNUSED)
|
||||
{
|
||||
return 1.f;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowOpacity(_GLFWwindow* window UNUSED, float opacity UNUSED)
|
||||
{
|
||||
}
|
||||
|
||||
void _glfwPlatformShowWindow(_GLFWwindow* window UNUSED)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void _glfwPlatformRequestWindowAttention(_GLFWwindow* window UNUSED)
|
||||
{
|
||||
}
|
||||
@@ -218,21 +405,41 @@ int _glfwPlatformWindowBell(_GLFWwindow* window UNUSED)
|
||||
return false;
|
||||
}
|
||||
|
||||
void _glfwPlatformUnhideWindow(_GLFWwindow* window UNUSED)
|
||||
void _glfwPlatformHideWindow(_GLFWwindow* window)
|
||||
{
|
||||
if (_glfw.null.focusedWindow == window)
|
||||
{
|
||||
_glfw.null.focusedWindow = NULL;
|
||||
_glfwInputWindowFocus(window, false);
|
||||
}
|
||||
|
||||
window->null.visible = false;
|
||||
}
|
||||
|
||||
void _glfwPlatformHideWindow(_GLFWwindow* window UNUSED)
|
||||
void _glfwPlatformFocusWindow(_GLFWwindow* window)
|
||||
{
|
||||
if (_glfw.null.focusedWindow == window)
|
||||
return;
|
||||
|
||||
if (!window->null.visible)
|
||||
return;
|
||||
|
||||
_GLFWwindow* previous = _glfw.null.focusedWindow;
|
||||
_glfw.null.focusedWindow = window;
|
||||
|
||||
if (previous)
|
||||
{
|
||||
_glfwInputWindowFocus(previous, false);
|
||||
if (previous->monitor && previous->autoIconify)
|
||||
_glfwPlatformIconifyWindow(previous);
|
||||
}
|
||||
|
||||
_glfwInputWindowFocus(window, true);
|
||||
}
|
||||
|
||||
void _glfwPlatformFocusWindow(_GLFWwindow* window UNUSED)
|
||||
int _glfwPlatformWindowFocused(_GLFWwindow* window)
|
||||
{
|
||||
}
|
||||
|
||||
int _glfwPlatformWindowFocused(_GLFWwindow* window UNUSED)
|
||||
{
|
||||
return false;
|
||||
return _glfw.null.focusedWindow == window;
|
||||
}
|
||||
|
||||
int _glfwPlatformWindowOccluded(_GLFWwindow* window UNUSED)
|
||||
@@ -240,14 +447,14 @@ int _glfwPlatformWindowOccluded(_GLFWwindow* window UNUSED)
|
||||
return false;
|
||||
}
|
||||
|
||||
int _glfwPlatformWindowIconified(_GLFWwindow* window UNUSED)
|
||||
int _glfwPlatformWindowIconified(_GLFWwindow* window)
|
||||
{
|
||||
return false;
|
||||
return window->null.iconified;
|
||||
}
|
||||
|
||||
int _glfwPlatformWindowVisible(_GLFWwindow* window UNUSED)
|
||||
int _glfwPlatformWindowVisible(_GLFWwindow* window)
|
||||
{
|
||||
return false;
|
||||
return window->null.visible;
|
||||
}
|
||||
|
||||
void _glfwPlatformPollEvents(void)
|
||||
@@ -266,12 +473,18 @@ void _glfwPlatformPostEmptyEvent(void)
|
||||
{
|
||||
}
|
||||
|
||||
void _glfwPlatformGetCursorPos(_GLFWwindow* window UNUSED, double* xpos UNUSED, double* ypos UNUSED)
|
||||
void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos)
|
||||
{
|
||||
if (xpos)
|
||||
*xpos = _glfw.null.xcursor - window->null.xpos;
|
||||
if (ypos)
|
||||
*ypos = _glfw.null.ycursor - window->null.ypos;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetCursorPos(_GLFWwindow* window UNUSED, double x UNUSED, double y UNUSED)
|
||||
void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y)
|
||||
{
|
||||
_glfw.null.xcursor = window->null.xpos + (int) x;
|
||||
_glfw.null.ycursor = window->null.ypos + (int) y;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetCursorMode(_GLFWwindow* window UNUSED, int mode UNUSED)
|
||||
@@ -298,23 +511,142 @@ void _glfwPlatformSetCursor(_GLFWwindow* window UNUSED, _GLFWcursor* cursor UNUS
|
||||
{
|
||||
}
|
||||
|
||||
void _glfwPlatformSetClipboardString(const char* string UNUSED)
|
||||
void _glfwPlatformSetClipboardString(const char* string)
|
||||
{
|
||||
char* copy = _glfw_strdup(string);
|
||||
free(_glfw.null.clipboardString);
|
||||
_glfw.null.clipboardString = copy;
|
||||
}
|
||||
|
||||
const char* _glfwPlatformGetClipboardString(void)
|
||||
{
|
||||
return _glfw.null.clipboardString;
|
||||
}
|
||||
|
||||
const char* _glfwPlatformGetNativeKeyName(int native_key)
|
||||
{
|
||||
switch (scancode)
|
||||
{
|
||||
case GLFW_KEY_APOSTROPHE:
|
||||
return "'";
|
||||
case GLFW_KEY_COMMA:
|
||||
return ",";
|
||||
case GLFW_KEY_MINUS:
|
||||
case GLFW_KEY_KP_SUBTRACT:
|
||||
return "-";
|
||||
case GLFW_KEY_PERIOD:
|
||||
case GLFW_KEY_KP_DECIMAL:
|
||||
return ".";
|
||||
case GLFW_KEY_SLASH:
|
||||
case GLFW_KEY_KP_DIVIDE:
|
||||
return "/";
|
||||
case GLFW_KEY_SEMICOLON:
|
||||
return ";";
|
||||
case GLFW_KEY_EQUAL:
|
||||
case GLFW_KEY_KP_EQUAL:
|
||||
return "=";
|
||||
case GLFW_KEY_LEFT_BRACKET:
|
||||
return "[";
|
||||
case GLFW_KEY_RIGHT_BRACKET:
|
||||
return "]";
|
||||
case GLFW_KEY_KP_MULTIPLY:
|
||||
return "*";
|
||||
case GLFW_KEY_KP_ADD:
|
||||
return "+";
|
||||
case GLFW_KEY_BACKSLASH:
|
||||
case GLFW_KEY_WORLD_1:
|
||||
case GLFW_KEY_WORLD_2:
|
||||
return "\\";
|
||||
case GLFW_KEY_0:
|
||||
case GLFW_KEY_KP_0:
|
||||
return "0";
|
||||
case GLFW_KEY_1:
|
||||
case GLFW_KEY_KP_1:
|
||||
return "1";
|
||||
case GLFW_KEY_2:
|
||||
case GLFW_KEY_KP_2:
|
||||
return "2";
|
||||
case GLFW_KEY_3:
|
||||
case GLFW_KEY_KP_3:
|
||||
return "3";
|
||||
case GLFW_KEY_4:
|
||||
case GLFW_KEY_KP_4:
|
||||
return "4";
|
||||
case GLFW_KEY_5:
|
||||
case GLFW_KEY_KP_5:
|
||||
return "5";
|
||||
case GLFW_KEY_6:
|
||||
case GLFW_KEY_KP_6:
|
||||
return "6";
|
||||
case GLFW_KEY_7:
|
||||
case GLFW_KEY_KP_7:
|
||||
return "7";
|
||||
case GLFW_KEY_8:
|
||||
case GLFW_KEY_KP_8:
|
||||
return "8";
|
||||
case GLFW_KEY_9:
|
||||
case GLFW_KEY_KP_9:
|
||||
return "9";
|
||||
case GLFW_KEY_A:
|
||||
return "a";
|
||||
case GLFW_KEY_B:
|
||||
return "b";
|
||||
case GLFW_KEY_C:
|
||||
return "c";
|
||||
case GLFW_KEY_D:
|
||||
return "d";
|
||||
case GLFW_KEY_E:
|
||||
return "e";
|
||||
case GLFW_KEY_F:
|
||||
return "f";
|
||||
case GLFW_KEY_G:
|
||||
return "g";
|
||||
case GLFW_KEY_H:
|
||||
return "h";
|
||||
case GLFW_KEY_I:
|
||||
return "i";
|
||||
case GLFW_KEY_J:
|
||||
return "j";
|
||||
case GLFW_KEY_K:
|
||||
return "k";
|
||||
case GLFW_KEY_L:
|
||||
return "l";
|
||||
case GLFW_KEY_M:
|
||||
return "m";
|
||||
case GLFW_KEY_N:
|
||||
return "n";
|
||||
case GLFW_KEY_O:
|
||||
return "o";
|
||||
case GLFW_KEY_P:
|
||||
return "p";
|
||||
case GLFW_KEY_Q:
|
||||
return "q";
|
||||
case GLFW_KEY_R:
|
||||
return "r";
|
||||
case GLFW_KEY_S:
|
||||
return "s";
|
||||
case GLFW_KEY_T:
|
||||
return "t";
|
||||
case GLFW_KEY_U:
|
||||
return "u";
|
||||
case GLFW_KEY_V:
|
||||
return "v";
|
||||
case GLFW_KEY_W:
|
||||
return "w";
|
||||
case GLFW_KEY_X:
|
||||
return "x";
|
||||
case GLFW_KEY_Y:
|
||||
return "y";
|
||||
case GLFW_KEY_Z:
|
||||
return "z";
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* _glfwPlatformGetNativeKeyName(int native_key UNUSED)
|
||||
int _glfwPlatformGetNativeKeyForKey(int key)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
int _glfwPlatformGetNativeKeyForKey(int key UNUSED)
|
||||
{
|
||||
return -1;
|
||||
return key;
|
||||
}
|
||||
|
||||
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions UNUSED)
|
||||
@@ -334,6 +666,6 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance UNUSED,
|
||||
VkSurfaceKHR* surface UNUSED)
|
||||
{
|
||||
// This seems like the most appropriate error to return here
|
||||
return VK_ERROR_INITIALIZATION_FAILED;
|
||||
return VK_ERROR_EXTENSION_NOT_PRESENT;
|
||||
}
|
||||
|
||||
|
||||
87
glfw/posix_time.c
vendored
87
glfw/posix_time.c
vendored
@@ -1,87 +0,0 @@
|
||||
//========================================================================
|
||||
// GLFW 3.4 POSIX - www.glfw.org
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright (c) 2002-2006 Marcus Geelnard
|
||||
// Copyright (c) 2006-2017 Camilla Löwy <elmindreda@glfw.org>
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would
|
||||
// be appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such, and must not
|
||||
// be misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source
|
||||
// distribution.
|
||||
//
|
||||
//========================================================================
|
||||
// It is fine to use C99 in this file because it will not be built with VS
|
||||
//========================================================================
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <time.h>
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW internal API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Initialise timer
|
||||
//
|
||||
void _glfwInitTimerPOSIX(void)
|
||||
{
|
||||
#if defined(CLOCK_MONOTONIC)
|
||||
struct timespec ts;
|
||||
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0)
|
||||
{
|
||||
_glfw.timer.posix.monotonic = true;
|
||||
_glfw.timer.posix.frequency = 1000000000;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
_glfw.timer.posix.monotonic = false;
|
||||
_glfw.timer.posix.frequency = 1000000;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW platform API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
uint64_t _glfwPlatformGetTimerValue(void)
|
||||
{
|
||||
#if defined(CLOCK_MONOTONIC)
|
||||
if (_glfw.timer.posix.monotonic)
|
||||
{
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (uint64_t) ts.tv_sec * (uint64_t) 1000000000 + (uint64_t) ts.tv_nsec;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
struct timeval tv;
|
||||
gettimeofday(&tv, NULL);
|
||||
return (uint64_t) tv.tv_sec * (uint64_t) 1000000 + (uint64_t) tv.tv_usec;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t _glfwPlatformGetTimerFrequency(void)
|
||||
{
|
||||
return _glfw.timer.posix.frequency;
|
||||
}
|
||||
|
||||
44
glfw/posix_time.h
vendored
44
glfw/posix_time.h
vendored
@@ -1,44 +0,0 @@
|
||||
//========================================================================
|
||||
// GLFW 3.4 POSIX - www.glfw.org
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright (c) 2002-2006 Marcus Geelnard
|
||||
// Copyright (c) 2006-2017 Camilla Löwy <elmindreda@glfw.org>
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would
|
||||
// be appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such, and must not
|
||||
// be misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source
|
||||
// distribution.
|
||||
//
|
||||
//========================================================================
|
||||
|
||||
#define _GLFW_PLATFORM_LIBRARY_TIMER_STATE _GLFWtimerPOSIX posix
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
// POSIX-specific global timer data
|
||||
//
|
||||
typedef struct _GLFWtimerPOSIX
|
||||
{
|
||||
bool monotonic;
|
||||
uint64_t frequency;
|
||||
|
||||
} _GLFWtimerPOSIX;
|
||||
|
||||
|
||||
void _glfwInitTimerPOSIX(void);
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
"cocoa_joystick.m",
|
||||
"cocoa_monitor.m",
|
||||
"cocoa_window.m",
|
||||
"cocoa_time.c",
|
||||
"posix_thread.c",
|
||||
"nsgl_context.m",
|
||||
"egl_context.c",
|
||||
@@ -31,6 +30,7 @@
|
||||
"input.c",
|
||||
"monitor.c",
|
||||
"vulkan.c",
|
||||
"monotonic.c",
|
||||
"window.c"
|
||||
]
|
||||
},
|
||||
@@ -38,7 +38,6 @@
|
||||
"headers": [
|
||||
"null_platform.h",
|
||||
"null_joystick.h",
|
||||
"posix_time.h",
|
||||
"posix_thread.h",
|
||||
"osmesa_context.h"
|
||||
],
|
||||
@@ -47,7 +46,6 @@
|
||||
"null_monitor.c",
|
||||
"null_window.c",
|
||||
"null_joystick.c",
|
||||
"posix_time.c",
|
||||
"posix_thread.c",
|
||||
"osmesa_context.c"
|
||||
]
|
||||
@@ -55,8 +53,8 @@
|
||||
"wayland": {
|
||||
"headers": [
|
||||
"wl_platform.h",
|
||||
"posix_time.h",
|
||||
"posix_thread.h",
|
||||
"wl_cursors.h",
|
||||
"xkb_glfw.h",
|
||||
"dbus_glfw.h",
|
||||
"ibus_glfw.h",
|
||||
@@ -81,7 +79,7 @@
|
||||
"wl_init.c",
|
||||
"wl_monitor.c",
|
||||
"wl_window.c",
|
||||
"posix_time.c",
|
||||
"wl_cursors.c",
|
||||
"posix_thread.c",
|
||||
"xkb_glfw.c",
|
||||
"dbus_glfw.c",
|
||||
@@ -125,7 +123,6 @@
|
||||
"dbus_glfw.h",
|
||||
"ibus_glfw.h",
|
||||
"backend_utils.h",
|
||||
"posix_time.h",
|
||||
"posix_thread.h",
|
||||
"glx_context.h",
|
||||
"egl_context.h",
|
||||
@@ -142,7 +139,6 @@
|
||||
"xkb_glfw.c",
|
||||
"dbus_glfw.c",
|
||||
"ibus_glfw.c",
|
||||
"posix_time.c",
|
||||
"posix_thread.c",
|
||||
"glx_context.c",
|
||||
"egl_context.c",
|
||||
|
||||
4
glfw/vulkan.c
vendored
4
glfw/vulkan.c
vendored
@@ -57,6 +57,8 @@ bool _glfwInitVulkan(int mode)
|
||||
_glfw.vk.handle = _glfw_dlopen("vulkan-1.dll");
|
||||
#elif defined(_GLFW_COCOA)
|
||||
_glfw.vk.handle = _glfw_dlopen("libvulkan.1.dylib");
|
||||
if (!_glfw.vk.handle)
|
||||
_glfw.vk.handle = _glfwLoadLocalVulkanLoaderNS();
|
||||
#else
|
||||
_glfw.vk.handle = _glfw_dlopen("libvulkan.so.1");
|
||||
#endif
|
||||
@@ -130,6 +132,8 @@ bool _glfwInitVulkan(int mode)
|
||||
#elif defined(_GLFW_COCOA)
|
||||
else if (strcmp(ep[i].extensionName, "VK_MVK_macos_surface") == 0)
|
||||
_glfw.vk.MVK_macos_surface = true;
|
||||
else if (strcmp(ep[i].extensionName, "VK_EXT_metal_surface") == 0)
|
||||
_glfw.vk.EXT_metal_surface = true;
|
||||
#elif defined(_GLFW_X11)
|
||||
else if (strcmp(ep[i].extensionName, "VK_KHR_xlib_surface") == 0)
|
||||
_glfw.vk.KHR_xlib_surface = true;
|
||||
|
||||
16
glfw/window.c
vendored
16
glfw/window.c
vendored
@@ -254,6 +254,8 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
|
||||
window->maxheight = GLFW_DONT_CARE;
|
||||
window->numer = GLFW_DONT_CARE;
|
||||
window->denom = GLFW_DONT_CARE;
|
||||
window->widthincr = GLFW_DONT_CARE;
|
||||
window->heightincr = GLFW_DONT_CARE;
|
||||
|
||||
// Open the actual window and create its context
|
||||
if (!_glfwPlatformCreateWindow(window, &wndconfig, &ctxconfig, &fbconfig))
|
||||
@@ -690,6 +692,20 @@ GLFWAPI void glfwSetWindowAspectRatio(GLFWwindow* handle, int numer, int denom)
|
||||
_glfwPlatformSetWindowAspectRatio(window, numer, denom);
|
||||
}
|
||||
|
||||
GLFWAPI void glfwSetWindowSizeIncrements(GLFWwindow* handle, int widthincr, int heightincr)
|
||||
{
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
assert(window != NULL);
|
||||
assert(widthincr >= 0);
|
||||
assert(heightincr >= 0);
|
||||
|
||||
_GLFW_REQUIRE_INIT();
|
||||
|
||||
window->widthincr = widthincr;
|
||||
window->heightincr = heightincr;
|
||||
_glfwPlatformSetWindowSizeIncrements(window, window->widthincr, window->heightincr);
|
||||
}
|
||||
|
||||
GLFWAPI void glfwGetFramebufferSize(GLFWwindow* handle, int* width, int* height)
|
||||
{
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
|
||||
164
glfw/wl_cursors.c
vendored
Normal file
164
glfw/wl_cursors.c
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
// Future devs supporting whatever Wayland protocol stabilizes for cursor selection: see _themeAdd.
|
||||
|
||||
#include "wl_cursors.h"
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct {
|
||||
struct wl_cursor_theme *theme;
|
||||
int px;
|
||||
int refcount;
|
||||
} _themeData;
|
||||
|
||||
struct _wlCursorThemeManager {
|
||||
size_t count;
|
||||
/** Pointer to the head of an unsorted array of themes with no sentinel.
|
||||
*
|
||||
* The lack of sort (and thus forcing a linear search) is intentional;
|
||||
* in most cases, users are likely to have 1-2 different cursor sizes loaded.
|
||||
* For those cases, we get no benefit from sorting and added constant overheads.
|
||||
*
|
||||
* Don't change this to a flexible array member because that complicates growing/shrinking.
|
||||
*/
|
||||
_themeData *themes;
|
||||
};
|
||||
|
||||
static void
|
||||
_themeInit(_themeData *dest, const char *name, int px) {
|
||||
dest->px = px;
|
||||
dest->refcount = 1;
|
||||
if (_glfw.wl.shm) {
|
||||
dest->theme = wl_cursor_theme_load(name, px, _glfw.wl.shm);
|
||||
if(!dest->theme) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Unable to load cursor theme");
|
||||
}
|
||||
} else {
|
||||
dest->theme = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static struct wl_cursor_theme*
|
||||
_themeAdd(int px, _wlCursorThemeManager *manager) {
|
||||
++manager->count;
|
||||
_themeData *temp = realloc(manager->themes, sizeof(_themeData)*manager->count);
|
||||
if (!temp) {
|
||||
_glfwInputError(GLFW_OUT_OF_MEMORY,
|
||||
"OOM during cursor theme management.");
|
||||
return NULL;
|
||||
} else {
|
||||
manager->themes = temp;
|
||||
_themeInit(manager->themes + manager->count-1,
|
||||
getenv("XCURSOR_THEME"),
|
||||
px);
|
||||
return manager->themes[manager->count-1].theme;
|
||||
}
|
||||
}
|
||||
|
||||
//WARNING: No input safety checks.
|
||||
static inline void _themeInc(_themeData
|
||||
*theme) {
|
||||
++(theme->refcount);
|
||||
}
|
||||
|
||||
// WARNING: No input safety checks.
|
||||
// In particular, doesn't check if theme is actually managed by the manager.
|
||||
static void
|
||||
_themeDec(_themeData *theme, _wlCursorThemeManager *manager) {
|
||||
if (--(theme->refcount) == 0) {
|
||||
wl_cursor_theme_destroy(theme->theme);
|
||||
if (--(manager->count) > 0) {
|
||||
const _themeData *last_theme = (manager->themes)+(manager->count);
|
||||
*theme = *last_theme;
|
||||
_themeData *temp = realloc(manager->themes, (manager->count)*sizeof(_themeData));
|
||||
// We're shrinking here, so it's not catastrophic if realloc fails.
|
||||
if (temp) manager->themes = temp;
|
||||
} else {
|
||||
free(manager->themes);
|
||||
manager->themes = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static _wlCursorThemeManager _default = {0};
|
||||
|
||||
_wlCursorThemeManager*
|
||||
_wlCursorThemeManagerDefault() {
|
||||
return &_default;
|
||||
}
|
||||
|
||||
void
|
||||
_wlCursorThemeManagerDestroy(_wlCursorThemeManager *manager) {
|
||||
if (manager) {
|
||||
for (size_t i = 0; i < manager->count; ++i) {
|
||||
wl_cursor_theme_destroy(manager->themes[i].theme);
|
||||
}
|
||||
free(manager->themes);
|
||||
}
|
||||
}
|
||||
|
||||
static struct wl_cursor_theme*
|
||||
_wlCursorThemeManagerGet(_wlCursorThemeManager *manager, int px) {
|
||||
_themeData *themedata = NULL;
|
||||
for (size_t i = 0; i < manager->count; ++i) {
|
||||
_themeData *temp = manager->themes+i;
|
||||
if (temp->px == px) {
|
||||
themedata = temp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (themedata != NULL) {
|
||||
_themeInc(themedata);
|
||||
return themedata->theme;
|
||||
}
|
||||
return _themeAdd(px, manager);
|
||||
}
|
||||
|
||||
struct wl_cursor_theme*
|
||||
_wlCursorThemeManage(_wlCursorThemeManager *manager, struct wl_cursor_theme *theme, int px) {
|
||||
//WARNING: Multiple returns.
|
||||
if (manager == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
if (theme != NULL) {
|
||||
// Search for the provided theme in the manager.
|
||||
_themeData *themedata = NULL;
|
||||
for (size_t i = 0; i < manager->count; ++i) {
|
||||
_themeData *temp = manager->themes+i;
|
||||
if (temp->theme == theme) {
|
||||
themedata = temp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (themedata != NULL) {
|
||||
// Search succeeded. Check if we can avoid unnecessary operations.
|
||||
if (themedata->px == px) return theme;
|
||||
_themeDec(themedata, manager);
|
||||
} else {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland internal: managed theme isn't in the provided manager");
|
||||
return theme;
|
||||
//^ This is probably the sanest behavior for this situation: do nothing.
|
||||
}
|
||||
}
|
||||
return px > 0 ? _wlCursorThemeManagerGet(manager, px) : NULL;
|
||||
}
|
||||
|
||||
int
|
||||
_wlCursorPxFromScale(int scale) {
|
||||
const char *envStr = getenv("XCURSOR_SIZE");
|
||||
if(envStr != NULL) {
|
||||
const int retval = atoi(envStr);
|
||||
//^ atoi here is fine since 0 is an invalid value.
|
||||
if(retval > 0 && retval <= INT_MAX/scale) {
|
||||
return retval*scale;
|
||||
}
|
||||
}
|
||||
return 32*scale;
|
||||
}
|
||||
30
glfw/wl_cursors.h
vendored
Normal file
30
glfw/wl_cursors.h
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
// Declarations for a HiDPI-aware cursor theme manager.
|
||||
|
||||
#include <wayland-cursor.h>
|
||||
|
||||
typedef struct _wlCursorThemeManager _wlCursorThemeManager;
|
||||
|
||||
/** Returns a pointer to a wlCursorThemeManagerInstance.
|
||||
* Repeatedly calling this function will return the same instance.
|
||||
*
|
||||
* The retrieved instance must be destroyed with _wlCursorThemeManagerDestroy.
|
||||
*/
|
||||
_wlCursorThemeManager* _wlCursorThemeManagerDefault(void);
|
||||
|
||||
/** Set a wl_cursor_theme pointer variable to a pointer to a managed cursor theme.
|
||||
* Pass the desired px as the third argument.
|
||||
* Returns a pointer to a managed theme, or NULL if the desired px is 0 or an error occurs.
|
||||
*
|
||||
* The passed theme pointer must either be NULL or a pointer to a theme managed by the passed manager.
|
||||
* The provided pointer may be invalidated if it's non-NULL.
|
||||
*/
|
||||
struct wl_cursor_theme*
|
||||
_wlCursorThemeManage(_wlCursorThemeManager*, struct wl_cursor_theme*, int);
|
||||
|
||||
void _wlCursorThemeManagerDestroy(_wlCursorThemeManager*);
|
||||
|
||||
/** Helper method to determine the appropriate size in pixels for a given scale.
|
||||
*
|
||||
* Reads XCURSOR_SIZE if it's set and is valid, else defaults to 32*scale.
|
||||
*/
|
||||
int _wlCursorPxFromScale(int);
|
||||
74
glfw/wl_init.c
vendored
74
glfw/wl_init.c
vendored
@@ -113,7 +113,7 @@ static void pointerHandleEnter(void* data UNUSED,
|
||||
}
|
||||
|
||||
window->wl.decorations.focus = focus;
|
||||
_glfw.wl.pointerSerial = serial;
|
||||
_glfw.wl.serial = serial;
|
||||
_glfw.wl.pointerFocus = window;
|
||||
|
||||
window->wl.hovered = true;
|
||||
@@ -134,19 +134,26 @@ static void pointerHandleLeave(void* data UNUSED,
|
||||
|
||||
window->wl.hovered = false;
|
||||
|
||||
_glfw.wl.pointerSerial = serial;
|
||||
_glfw.wl.serial = serial;
|
||||
_glfw.wl.pointerFocus = NULL;
|
||||
_glfwInputCursorEnter(window, false);
|
||||
_glfw.wl.cursorPreviousShape = GLFW_INVALID_CURSOR;
|
||||
}
|
||||
|
||||
static void setCursor(GLFWCursorShape shape)
|
||||
static void setCursor(GLFWCursorShape shape, _GLFWwindow* window)
|
||||
{
|
||||
struct wl_buffer* buffer;
|
||||
struct wl_cursor* cursor;
|
||||
struct wl_cursor_image* image;
|
||||
struct wl_surface* surface = _glfw.wl.cursorSurface;
|
||||
const int scale = window->wl.scale;
|
||||
|
||||
cursor = _glfwLoadCursor(shape);
|
||||
window->wl.cursorTheme = _wlCursorThemeManage(
|
||||
_glfw.wl.cursorThemeManager,
|
||||
window->wl.cursorTheme,
|
||||
_wlCursorPxFromScale(scale)
|
||||
);
|
||||
cursor = _glfwLoadCursor(shape, window->wl.cursorTheme);
|
||||
if (!cursor) return;
|
||||
// TODO: handle animated cursors too.
|
||||
image = cursor->images[0];
|
||||
@@ -157,10 +164,11 @@ static void setCursor(GLFWCursorShape shape)
|
||||
buffer = wl_cursor_image_get_buffer(image);
|
||||
if (!buffer)
|
||||
return;
|
||||
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
|
||||
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.serial,
|
||||
surface,
|
||||
image->hotspot_x,
|
||||
image->hotspot_y);
|
||||
image->hotspot_x / scale,
|
||||
image->hotspot_y / scale);
|
||||
wl_surface_set_buffer_scale(surface, scale);
|
||||
wl_surface_attach(surface, buffer, 0, 0);
|
||||
wl_surface_damage(surface, 0, 0,
|
||||
image->width, image->height);
|
||||
@@ -192,6 +200,7 @@ static void pointerHandleMotion(void* data UNUSED,
|
||||
window->wl.cursorPosX = x;
|
||||
window->wl.cursorPosY = y;
|
||||
_glfwInputCursorPos(window, x, y);
|
||||
_glfw.wl.cursorPreviousShape = GLFW_INVALID_CURSOR;
|
||||
return;
|
||||
case topDecoration:
|
||||
if (y < _GLFW_DECORATION_WIDTH)
|
||||
@@ -223,7 +232,7 @@ static void pointerHandleMotion(void* data UNUSED,
|
||||
assert(0);
|
||||
}
|
||||
if (_glfw.wl.cursorPreviousShape != cursorShape)
|
||||
setCursor(cursorShape);
|
||||
setCursor(cursorShape, window);
|
||||
}
|
||||
|
||||
static void pointerHandleButton(void* data UNUSED,
|
||||
@@ -299,7 +308,7 @@ static void pointerHandleButton(void* data UNUSED,
|
||||
if (window->wl.decorations.focus != mainWindow)
|
||||
return;
|
||||
|
||||
_glfw.wl.pointerSerial = serial;
|
||||
_glfw.wl.serial = serial;
|
||||
|
||||
/* Makes left, right and middle 0, 1 and 2. Overall order follows evdev
|
||||
* codes. */
|
||||
@@ -332,7 +341,7 @@ static void pointerHandleAxis(void* data UNUSED,
|
||||
else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
|
||||
y = wl_fixed_to_double(value) * -1;
|
||||
|
||||
_glfwInputScroll(window, x, y, 1);
|
||||
_glfwInputScroll(window, x, y, 1, _glfw.wl.xkb.states.modifiers);
|
||||
}
|
||||
|
||||
static const struct wl_pointer_listener pointerListener = {
|
||||
@@ -370,7 +379,7 @@ static void keyboardHandleKeymap(void* data UNUSED,
|
||||
|
||||
static void keyboardHandleEnter(void* data UNUSED,
|
||||
struct wl_keyboard* keyboard UNUSED,
|
||||
uint32_t serial UNUSED,
|
||||
uint32_t serial,
|
||||
struct wl_surface* surface,
|
||||
struct wl_array* keys)
|
||||
{
|
||||
@@ -386,6 +395,7 @@ static void keyboardHandleEnter(void* data UNUSED,
|
||||
return;
|
||||
}
|
||||
|
||||
_glfw.wl.serial = serial;
|
||||
_glfw.wl.keyboardFocus = window;
|
||||
_glfwInputWindowFocus(window, true);
|
||||
uint32_t* key;
|
||||
@@ -401,7 +411,7 @@ static void keyboardHandleEnter(void* data UNUSED,
|
||||
|
||||
static void keyboardHandleLeave(void* data UNUSED,
|
||||
struct wl_keyboard* keyboard UNUSED,
|
||||
uint32_t serial UNUSED,
|
||||
uint32_t serial,
|
||||
struct wl_surface* surface UNUSED)
|
||||
{
|
||||
_GLFWwindow* window = _glfw.wl.keyboardFocus;
|
||||
@@ -409,6 +419,7 @@ static void keyboardHandleLeave(void* data UNUSED,
|
||||
if (!window)
|
||||
return;
|
||||
|
||||
_glfw.wl.serial = serial;
|
||||
_glfw.wl.keyboardFocus = NULL;
|
||||
_glfwInputWindowFocus(window, false);
|
||||
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 0);
|
||||
@@ -425,7 +436,7 @@ dispatchPendingKeyRepeats(id_type timer_id UNUSED, void *data UNUSED) {
|
||||
|
||||
static void keyboardHandleKey(void* data UNUSED,
|
||||
struct wl_keyboard* keyboard UNUSED,
|
||||
uint32_t serial UNUSED,
|
||||
uint32_t serial,
|
||||
uint32_t time UNUSED,
|
||||
uint32_t key,
|
||||
uint32_t state)
|
||||
@@ -434,6 +445,8 @@ static void keyboardHandleKey(void* data UNUSED,
|
||||
if (!window)
|
||||
return;
|
||||
int action = state == WL_KEYBOARD_KEY_STATE_PRESSED ? GLFW_PRESS : GLFW_RELEASE;
|
||||
|
||||
_glfw.wl.serial = serial;
|
||||
glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, key, action);
|
||||
bool repeatable = false;
|
||||
_glfw.wl.keyRepeatInfo.key = 0;
|
||||
@@ -452,12 +465,13 @@ static void keyboardHandleKey(void* data UNUSED,
|
||||
|
||||
static void keyboardHandleModifiers(void* data UNUSED,
|
||||
struct wl_keyboard* keyboard UNUSED,
|
||||
uint32_t serial UNUSED,
|
||||
uint32_t serial,
|
||||
uint32_t modsDepressed,
|
||||
uint32_t modsLatched,
|
||||
uint32_t modsLocked,
|
||||
uint32_t group)
|
||||
{
|
||||
_glfw.wl.serial = serial;
|
||||
glfw_xkb_update_modifiers(&_glfw.wl.xkb, modsDepressed, modsLatched, modsLocked, 0, 0, group);
|
||||
}
|
||||
|
||||
@@ -764,7 +778,6 @@ int _glfwPlatformInit(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
_glfwInitTimerPOSIX();
|
||||
if (!_glfw.wl.wmBase)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
@@ -774,26 +787,14 @@ int _glfwPlatformInit(void)
|
||||
|
||||
if (_glfw.wl.shm)
|
||||
{
|
||||
const char *cursorTheme = getenv("XCURSOR_THEME"), *cursorSizeStr = getenv("XCURSOR_SIZE");
|
||||
char *cursorSizeEnd;
|
||||
int cursorSize = 32;
|
||||
if (cursorSizeStr)
|
||||
{
|
||||
errno = 0;
|
||||
long cursorSizeLong = strtol(cursorSizeStr, &cursorSizeEnd, 10);
|
||||
if (!*cursorSizeEnd && !errno && cursorSizeLong > 0 && cursorSizeLong <= INT_MAX)
|
||||
cursorSize = (int)cursorSizeLong;
|
||||
}
|
||||
_glfw.wl.cursorTheme = wl_cursor_theme_load(cursorTheme, cursorSize, _glfw.wl.shm);
|
||||
if (!_glfw.wl.cursorTheme)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Unable to load default cursor theme");
|
||||
return false;
|
||||
}
|
||||
_glfw.wl.cursorSurface =
|
||||
_glfw.wl.cursorThemeManager = _wlCursorThemeManagerDefault();
|
||||
_glfw.wl.cursorSurface =
|
||||
wl_compositor_create_surface(_glfw.wl.compositor);
|
||||
}
|
||||
else
|
||||
{
|
||||
_glfw.wl.cursorThemeManager = NULL;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -813,8 +814,6 @@ void _glfwPlatformTerminate(void)
|
||||
glfw_xkb_release(&_glfw.wl.xkb);
|
||||
glfw_dbus_terminate(&_glfw.wl.dbus);
|
||||
|
||||
if (_glfw.wl.cursorTheme)
|
||||
wl_cursor_theme_destroy(_glfw.wl.cursorTheme);
|
||||
if (_glfw.wl.cursor.handle)
|
||||
{
|
||||
_glfw_dlclose(_glfw.wl.cursor.handle);
|
||||
@@ -823,6 +822,9 @@ void _glfwPlatformTerminate(void)
|
||||
|
||||
if (_glfw.wl.cursorSurface)
|
||||
wl_surface_destroy(_glfw.wl.cursorSurface);
|
||||
if (_glfw.wl.cursorThemeManager) {
|
||||
_wlCursorThemeManagerDestroy(_glfw.wl.cursorThemeManager);
|
||||
}
|
||||
if (_glfw.wl.subcompositor)
|
||||
wl_subcompositor_destroy(_glfw.wl.subcompositor);
|
||||
if (_glfw.wl.compositor)
|
||||
@@ -851,7 +853,7 @@ void _glfwPlatformTerminate(void)
|
||||
wl_data_source_destroy(_glfw.wl.dataSourceForClipboard);
|
||||
for (size_t doi=0; doi < arraysz(_glfw.wl.dataOffers); doi++) {
|
||||
if (_glfw.wl.dataOffers[doi].id) {
|
||||
wl_data_offer_destroy(_glfw.wl.dataOffers[doi].id);
|
||||
destroy_data_offer(&_glfw.wl.dataOffers[doi]);
|
||||
}
|
||||
}
|
||||
if (_glfw.wl.dataDevice)
|
||||
|
||||
4
glfw/wl_monitor.c
vendored
4
glfw/wl_monitor.c
vendored
@@ -202,7 +202,7 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode)
|
||||
|
||||
bool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor UNUSED, GLFWgammaramp* ramp UNUSED)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
|
||||
"Wayland: Gamma ramp access is not available");
|
||||
return false;
|
||||
}
|
||||
@@ -210,7 +210,7 @@ bool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor UNUSED, GLFWgammaramp* ramp
|
||||
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor UNUSED,
|
||||
const GLFWgammaramp* ramp UNUSED)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
|
||||
"Wayland: Gamma ramp access is not available");
|
||||
}
|
||||
|
||||
|
||||
53
glfw/wl_platform.h
vendored
53
glfw/wl_platform.h
vendored
@@ -43,7 +43,6 @@ typedef VkResult (APIENTRY *PFN_vkCreateWaylandSurfaceKHR)(VkInstance,const VkWa
|
||||
typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR)(VkPhysicalDevice,uint32_t,struct wl_display*);
|
||||
|
||||
#include "posix_thread.h"
|
||||
#include "posix_time.h"
|
||||
#ifdef __linux__
|
||||
#include "linux_joystick.h"
|
||||
#else
|
||||
@@ -53,6 +52,7 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
|
||||
#include "xkb_glfw.h"
|
||||
#include "egl_context.h"
|
||||
#include "osmesa_context.h"
|
||||
#include "wl_cursors.h"
|
||||
|
||||
#include "wayland-xdg-shell-client-protocol.h"
|
||||
#include "wayland-viewporter-client-protocol.h"
|
||||
@@ -77,18 +77,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
|
||||
#define _GLFW_PLATFORM_CONTEXT_STATE
|
||||
#define _GLFW_PLATFORM_LIBRARY_CONTEXT_STATE
|
||||
|
||||
struct wl_cursor_image {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t hotspot_x;
|
||||
uint32_t hotspot_y;
|
||||
uint32_t delay;
|
||||
};
|
||||
struct wl_cursor {
|
||||
unsigned int image_count;
|
||||
struct wl_cursor_image** images;
|
||||
char* name;
|
||||
};
|
||||
typedef struct wl_cursor_theme* (* PFN_wl_cursor_theme_load)(const char*, int, struct wl_shm*);
|
||||
typedef void (* PFN_wl_cursor_theme_destroy)(struct wl_cursor_theme*);
|
||||
typedef struct wl_cursor* (* PFN_wl_cursor_theme_get_cursor)(struct wl_cursor_theme*, const char*);
|
||||
@@ -149,6 +137,7 @@ typedef struct _GLFWwindowWayland
|
||||
|
||||
_GLFWcursor* currentCursor;
|
||||
double cursorPosX, cursorPosY;
|
||||
struct wl_cursor_theme* cursorTheme;
|
||||
|
||||
char* title;
|
||||
char appId[256];
|
||||
@@ -196,28 +185,19 @@ typedef enum _GLFWWaylandOfferType
|
||||
|
||||
typedef struct _GLFWWaylandDataOffer
|
||||
{
|
||||
struct wl_data_offer *id;
|
||||
const char *mime;
|
||||
void *id;
|
||||
_GLFWWaylandOfferType offer_type;
|
||||
size_t idx;
|
||||
int is_self_offer;
|
||||
int has_uri_list;
|
||||
bool is_self_offer;
|
||||
bool is_primary;
|
||||
const char *plain_text_mime, *mime_for_drop;
|
||||
uint32_t source_actions;
|
||||
uint32_t dnd_action;
|
||||
struct wl_surface *surface;
|
||||
const char **mimes;
|
||||
size_t mimes_capacity, mimes_count;
|
||||
} _GLFWWaylandDataOffer;
|
||||
|
||||
typedef struct _GLFWWaylandPrimaryOffer
|
||||
{
|
||||
struct zwp_primary_selection_offer_v1 *id;
|
||||
const char *mime;
|
||||
_GLFWWaylandOfferType offer_type;
|
||||
size_t idx;
|
||||
int is_self_offer;
|
||||
int has_uri_list;
|
||||
struct wl_surface *surface;
|
||||
} _GLFWWaylandPrimaryOffer;
|
||||
|
||||
// Wayland-specific global data
|
||||
//
|
||||
typedef struct _GLFWlibraryWayland
|
||||
@@ -246,10 +226,9 @@ typedef struct _GLFWlibraryWayland
|
||||
int compositorVersion;
|
||||
int seatVersion;
|
||||
|
||||
struct wl_cursor_theme* cursorTheme;
|
||||
struct wl_surface* cursorSurface;
|
||||
GLFWCursorShape cursorPreviousShape;
|
||||
uint32_t pointerSerial;
|
||||
uint32_t serial;
|
||||
|
||||
int32_t keyboardRepeatRate;
|
||||
monotonic_t keyboardRepeatDelay;
|
||||
@@ -288,8 +267,8 @@ typedef struct _GLFWlibraryWayland
|
||||
size_t dataOffersCounter;
|
||||
_GLFWWaylandDataOffer dataOffers[8];
|
||||
char* primarySelectionString;
|
||||
size_t primarySelectionOffersCounter;
|
||||
_GLFWWaylandPrimaryOffer primarySelectionOffers[8];
|
||||
|
||||
_wlCursorThemeManager* cursorThemeManager;
|
||||
} _GLFWlibraryWayland;
|
||||
|
||||
// Wayland-specific per-monitor data
|
||||
@@ -303,8 +282,7 @@ typedef struct _GLFWmonitorWayland
|
||||
int x;
|
||||
int y;
|
||||
int scale;
|
||||
|
||||
} _GLFWmonitorWayland;
|
||||
} _GLFWmonitorWayland;
|
||||
|
||||
// Wayland-specific per-cursor data
|
||||
//
|
||||
@@ -315,6 +293,10 @@ typedef struct _GLFWcursorWayland
|
||||
int width, height;
|
||||
int xhot, yhot;
|
||||
int currentImage;
|
||||
/** The scale of the cursor, or 0 if the cursor should be loaded late, or -1 if the cursor variable itself is unused. */
|
||||
int scale;
|
||||
/** Cursor shape stored to allow late cursor loading in setCursorImage. */
|
||||
GLFWCursorShape shape;
|
||||
} _GLFWcursorWayland;
|
||||
|
||||
|
||||
@@ -322,4 +304,5 @@ void _glfwAddOutputWayland(uint32_t name, uint32_t version);
|
||||
void _glfwSetupWaylandDataDevice(void);
|
||||
void _glfwSetupWaylandPrimarySelectionDevice(void);
|
||||
void animateCursorImage(id_type timer_id, void *data);
|
||||
struct wl_cursor* _glfwLoadCursor(GLFWCursorShape);
|
||||
struct wl_cursor* _glfwLoadCursor(GLFWCursorShape, struct wl_cursor_theme*);
|
||||
void destroy_data_offer(_GLFWWaylandDataOffer*);
|
||||
|
||||
515
glfw/wl_window.c
vendored
515
glfw/wl_window.c
vendored
@@ -41,8 +41,67 @@
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
static void
|
||||
load_cursor_theme(_GLFWwindow* window) {
|
||||
window->wl.cursorTheme = _wlCursorThemeManage(
|
||||
_glfw.wl.cursorThemeManager, window->wl.cursorTheme, _wlCursorPxFromScale(window->wl.scale));
|
||||
}
|
||||
|
||||
#define URI_LIST_MIME "text/uri-list"
|
||||
static void
|
||||
setCursorImage(_GLFWwindow* window)
|
||||
{
|
||||
_GLFWcursorWayland defaultCursor = {.shape = GLFW_ARROW_CURSOR};
|
||||
_GLFWcursorWayland* cursorWayland = window->cursor ? &window->cursor->wl : &defaultCursor;
|
||||
struct wl_cursor_image* image = NULL;
|
||||
struct wl_buffer* buffer = NULL;
|
||||
struct wl_surface* surface = _glfw.wl.cursorSurface;
|
||||
const int scale = window->wl.scale;
|
||||
|
||||
if (cursorWayland->scale < 0) {
|
||||
buffer = cursorWayland->buffer;
|
||||
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0);
|
||||
} else
|
||||
{
|
||||
if (cursorWayland->scale != scale) {
|
||||
if (!window->wl.cursorTheme) load_cursor_theme(window);
|
||||
struct wl_cursor *newCursor = _glfwLoadCursor(cursorWayland->shape, window->wl.cursorTheme);
|
||||
if (newCursor != NULL) {
|
||||
cursorWayland->cursor = newCursor;
|
||||
cursorWayland->scale = scale;
|
||||
} else {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: late cursor load failed; proceeding with existing cursor");
|
||||
}
|
||||
}
|
||||
if (!cursorWayland->cursor)
|
||||
return;
|
||||
image = cursorWayland->cursor->images[cursorWayland->currentImage];
|
||||
buffer = wl_cursor_image_get_buffer(image);
|
||||
if (image->delay && window->cursor) {
|
||||
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, ms_to_monotonic_t(image->delay));
|
||||
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 1);
|
||||
} else {
|
||||
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0);
|
||||
}
|
||||
|
||||
if (!buffer)
|
||||
return;
|
||||
|
||||
cursorWayland->width = image->width;
|
||||
cursorWayland->height = image->height;
|
||||
cursorWayland->xhot = image->hotspot_x;
|
||||
cursorWayland->yhot = image->hotspot_y;
|
||||
}
|
||||
|
||||
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.serial,
|
||||
surface,
|
||||
cursorWayland->xhot / scale,
|
||||
cursorWayland->yhot / scale);
|
||||
wl_surface_set_buffer_scale(surface, scale);
|
||||
wl_surface_attach(surface, buffer, 0, 0);
|
||||
wl_surface_damage(surface, 0, 0,
|
||||
cursorWayland->width, cursorWayland->height);
|
||||
wl_surface_commit(surface);
|
||||
}
|
||||
|
||||
|
||||
static bool checkScaleChange(_GLFWwindow* window)
|
||||
@@ -73,6 +132,8 @@ static bool checkScaleChange(_GLFWwindow* window)
|
||||
{
|
||||
window->wl.scale = scale;
|
||||
wl_surface_set_buffer_scale(window->wl.surface, scale);
|
||||
load_cursor_theme(window);
|
||||
setCursorImage(window);
|
||||
return true;
|
||||
}
|
||||
if (window->wl.monitorsCount > 0 && !window->wl.initial_scale_notified) {
|
||||
@@ -388,9 +449,9 @@ static const struct zxdg_toplevel_decoration_v1_listener xdgDecorationListener =
|
||||
xdgDecorationHandleConfigure,
|
||||
};
|
||||
|
||||
static void handleEnter(void *data,
|
||||
struct wl_surface *surface UNUSED,
|
||||
struct wl_output *output)
|
||||
static void surfaceHandleEnter(void *data,
|
||||
struct wl_surface *surface UNUSED,
|
||||
struct wl_output *output)
|
||||
{
|
||||
_GLFWwindow* window = data;
|
||||
_GLFWmonitor* monitor = wl_output_get_user_data(output);
|
||||
@@ -411,9 +472,9 @@ static void handleEnter(void *data,
|
||||
}
|
||||
}
|
||||
|
||||
static void handleLeave(void *data,
|
||||
struct wl_surface *surface UNUSED,
|
||||
struct wl_output *output)
|
||||
static void surfaceHandleLeave(void *data,
|
||||
struct wl_surface *surface UNUSED,
|
||||
struct wl_output *output)
|
||||
{
|
||||
_GLFWwindow* window = data;
|
||||
_GLFWmonitor* monitor = wl_output_get_user_data(output);
|
||||
@@ -436,8 +497,8 @@ static void handleLeave(void *data,
|
||||
}
|
||||
|
||||
static const struct wl_surface_listener surfaceListener = {
|
||||
handleEnter,
|
||||
handleLeave
|
||||
surfaceHandleEnter,
|
||||
surfaceHandleLeave
|
||||
};
|
||||
|
||||
static void setIdleInhibitor(_GLFWwindow* window, bool enable)
|
||||
@@ -679,56 +740,16 @@ static bool createXdgSurface(_GLFWwindow* window)
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
setCursorImage(_GLFWcursorWayland* cursorWayland)
|
||||
{
|
||||
struct wl_cursor_image* image;
|
||||
struct wl_buffer* buffer;
|
||||
struct wl_surface* surface = _glfw.wl.cursorSurface;
|
||||
|
||||
if (!cursorWayland->cursor) {
|
||||
buffer = cursorWayland->buffer;
|
||||
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0);
|
||||
} else
|
||||
{
|
||||
image = cursorWayland->cursor->images[cursorWayland->currentImage];
|
||||
buffer = wl_cursor_image_get_buffer(image);
|
||||
if (image->delay) {
|
||||
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, ms_to_monotonic_t(image->delay));
|
||||
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 1);
|
||||
} else {
|
||||
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0);
|
||||
}
|
||||
|
||||
if (!buffer)
|
||||
return;
|
||||
|
||||
cursorWayland->width = image->width;
|
||||
cursorWayland->height = image->height;
|
||||
cursorWayland->xhot = image->hotspot_x;
|
||||
cursorWayland->yhot = image->hotspot_y;
|
||||
}
|
||||
|
||||
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
|
||||
surface,
|
||||
cursorWayland->xhot,
|
||||
cursorWayland->yhot);
|
||||
wl_surface_attach(surface, buffer, 0, 0);
|
||||
wl_surface_damage(surface, 0, 0,
|
||||
cursorWayland->width, cursorWayland->height);
|
||||
wl_surface_commit(surface);
|
||||
}
|
||||
|
||||
static void
|
||||
incrementCursorImage(_GLFWwindow* window)
|
||||
{
|
||||
if (window && window->wl.decorations.focus == mainWindow) {
|
||||
if (window && window->wl.decorations.focus == mainWindow && window->cursorMode != GLFW_CURSOR_HIDDEN) {
|
||||
_GLFWcursor* cursor = window->wl.currentCursor;
|
||||
if (cursor && cursor->wl.cursor)
|
||||
{
|
||||
cursor->wl.currentImage += 1;
|
||||
cursor->wl.currentImage %= cursor->wl.cursor->image_count;
|
||||
setCursorImage(&cursor->wl);
|
||||
setCursorImage(window);
|
||||
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, cursor->wl.cursor->image_count > 1);
|
||||
return;
|
||||
}
|
||||
@@ -744,16 +765,26 @@ animateCursorImage(id_type timer_id UNUSED, void *data UNUSED) {
|
||||
static void
|
||||
abortOnFatalError(int last_error) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: fatal display error: %s", strerror(last_error));
|
||||
_GLFWwindow* window = _glfw.windowListHead;
|
||||
while (window)
|
||||
{
|
||||
_glfwInputWindowCloseRequest(window);
|
||||
window = window->next;
|
||||
if (_glfw.callbacks.application_close) _glfw.callbacks.application_close(1);
|
||||
else {
|
||||
_GLFWwindow* window = _glfw.windowListHead;
|
||||
while (window)
|
||||
{
|
||||
_glfwInputWindowCloseRequest(window);
|
||||
window = window->next;
|
||||
}
|
||||
}
|
||||
// ensure the tick callback is called
|
||||
_glfw.wl.eventLoopData.wakeup_data_read = true;
|
||||
}
|
||||
|
||||
static void
|
||||
wayland_read_events(int poll_result, int events, void *data UNUSED) {
|
||||
EVDBG("wayland_read_events poll_result: %d events: %d", poll_result, events);
|
||||
if (poll_result > 0 && events) wl_display_read_events(_glfw.wl.display);
|
||||
else wl_display_cancel_read(_glfw.wl.display);
|
||||
}
|
||||
|
||||
static void
|
||||
handleEvents(monotonic_t timeout)
|
||||
{
|
||||
@@ -762,17 +793,9 @@ handleEvents(monotonic_t timeout)
|
||||
EVDBG("starting handleEvents(%.2f)", monotonic_t_to_s_double(timeout));
|
||||
|
||||
while (wl_display_prepare_read(display) != 0) {
|
||||
while(1) {
|
||||
errno = 0;
|
||||
int num_dispatched = wl_display_dispatch_pending(display);
|
||||
if (num_dispatched < 0) {
|
||||
if (errno == EAGAIN) continue;
|
||||
int last_error = wl_display_get_error(display);
|
||||
if (last_error) abortOnFatalError(last_error);
|
||||
wl_display_cancel_read(display);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
if (wl_display_dispatch_pending(display) == -1) {
|
||||
abortOnFatalError(errno);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -782,23 +805,22 @@ handleEvents(monotonic_t timeout)
|
||||
errno = 0;
|
||||
if (wl_display_flush(display) < 0 && errno != EAGAIN)
|
||||
{
|
||||
abortOnFatalError(errno);
|
||||
wl_display_cancel_read(display);
|
||||
abortOnFatalError(errno);
|
||||
return;
|
||||
}
|
||||
|
||||
bool display_read_ok = pollForEvents(&_glfw.wl.eventLoopData, timeout);
|
||||
// we pass in wayland_read_events to ensure that the above wl_display_prepare_read call
|
||||
// is followed by either wl_display_cancel_read or wl_display_read_events
|
||||
// before any events/timers are dispatched. This allows other wayland functions
|
||||
// to be called in the event/timer handlers without causing a deadlock
|
||||
bool display_read_ok = pollForEvents(&_glfw.wl.eventLoopData, timeout, wayland_read_events);
|
||||
EVDBG("display_read_ok: %d", display_read_ok);
|
||||
if (display_read_ok) {
|
||||
wl_display_read_events(display);
|
||||
int num = wl_display_dispatch_pending(display);
|
||||
(void)num;
|
||||
EVDBG("dispatched %d Wayland events", num);
|
||||
}
|
||||
else
|
||||
{
|
||||
wl_display_cancel_read(display);
|
||||
}
|
||||
glfw_ibus_dispatch(&_glfw.wl.xkb.ibus);
|
||||
glfw_dbus_session_bus_dispatch();
|
||||
EVDBG("other dispatch done");
|
||||
@@ -806,24 +828,25 @@ handleEvents(monotonic_t timeout)
|
||||
}
|
||||
|
||||
static struct wl_cursor*
|
||||
try_cursor_names(int arg_count, ...) {
|
||||
try_cursor_names(struct wl_cursor_theme* theme, int arg_count, ...) {
|
||||
struct wl_cursor* ans = NULL;
|
||||
va_list ap;
|
||||
va_start(ap, arg_count);
|
||||
for (int i = 0; i < arg_count && !ans; i++) {
|
||||
const char *name = va_arg(ap, const char *);
|
||||
ans = wl_cursor_theme_get_cursor(_glfw.wl.cursorTheme, name);
|
||||
ans = wl_cursor_theme_get_cursor(theme, name);
|
||||
}
|
||||
va_end(ap);
|
||||
return ans;
|
||||
}
|
||||
|
||||
struct wl_cursor* _glfwLoadCursor(GLFWCursorShape shape)
|
||||
struct wl_cursor* _glfwLoadCursor(GLFWCursorShape shape, struct wl_cursor_theme* theme)
|
||||
{
|
||||
static bool warnings[GLFW_INVALID_CURSOR] = {0};
|
||||
if (!theme) return NULL;
|
||||
#define NUMARGS(...) (sizeof((const char*[]){__VA_ARGS__})/sizeof(const char*))
|
||||
#define C(name, ...) case name: { \
|
||||
ans = try_cursor_names(NUMARGS(__VA_ARGS__), __VA_ARGS__); \
|
||||
ans = try_cursor_names(theme, NUMARGS(__VA_ARGS__), __VA_ARGS__); \
|
||||
if (!ans && !warnings[name]) {\
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Could not find standard cursor: %s", #name); \
|
||||
warnings[name] = true; \
|
||||
@@ -833,7 +856,7 @@ struct wl_cursor* _glfwLoadCursor(GLFWCursorShape shape)
|
||||
struct wl_cursor* ans = NULL;
|
||||
switch (shape)
|
||||
{
|
||||
C(GLFW_ARROW_CURSOR, "arrow", "left_ptr", "default")
|
||||
C(GLFW_ARROW_CURSOR, "left_ptr", "arrow", "default")
|
||||
C(GLFW_IBEAM_CURSOR, "xterm", "ibeam", "text")
|
||||
C(GLFW_CROSSHAIR_CURSOR, "crosshair", "cross")
|
||||
C(GLFW_HAND_CURSOR, "hand2", "grab", "grabbing", "closedhand")
|
||||
@@ -903,6 +926,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
||||
}
|
||||
|
||||
window->wl.currentCursor = NULL;
|
||||
// Don't set window->wl.cursorTheme to NULL here.
|
||||
|
||||
window->wl.monitors = calloc(1, sizeof(_GLFWmonitor*));
|
||||
window->wl.monitorsCount = 0;
|
||||
@@ -913,6 +937,12 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
||||
|
||||
void _glfwPlatformDestroyWindow(_GLFWwindow* window)
|
||||
{
|
||||
if (window->wl.cursorTheme) {
|
||||
_wlCursorThemeManage(_glfw.wl.cursorThemeManager,
|
||||
window->wl.cursorTheme,
|
||||
0);
|
||||
window->wl.cursorTheme = NULL;
|
||||
}
|
||||
if (window == _glfw.wl.pointerFocus)
|
||||
{
|
||||
_glfw.wl.pointerFocus = NULL;
|
||||
@@ -931,6 +961,7 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
|
||||
window->context.destroy(window);
|
||||
|
||||
destroyDecorations(window);
|
||||
|
||||
if (window->wl.xdg.decoration)
|
||||
zxdg_toplevel_decoration_v1_destroy(window->wl.xdg.decoration);
|
||||
|
||||
@@ -959,16 +990,19 @@ void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title)
|
||||
{
|
||||
if (window->wl.title)
|
||||
free(window->wl.title);
|
||||
window->wl.title = _glfw_strdup(title);
|
||||
// Wayland cannot handle requests larger than ~8200 bytes. Sending
|
||||
// one causes an abort(). Since titles this large are meaningless anyway
|
||||
// ensure they do not happen.
|
||||
window->wl.title = utf_8_strndup(title, 2048);
|
||||
if (window->wl.xdg.toplevel)
|
||||
xdg_toplevel_set_title(window->wl.xdg.toplevel, title);
|
||||
xdg_toplevel_set_title(window->wl.xdg.toplevel, window->wl.title);
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowIcon(_GLFWwindow* window UNUSED,
|
||||
int count UNUSED, const GLFWimage* images UNUSED)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Setting window icon not supported");
|
||||
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
|
||||
"Wayland: The platform does not support setting the window icon");
|
||||
}
|
||||
|
||||
void _glfwPlatformGetWindowPos(_GLFWwindow* window UNUSED, int* xpos UNUSED, int* ypos UNUSED)
|
||||
@@ -976,16 +1010,16 @@ void _glfwPlatformGetWindowPos(_GLFWwindow* window UNUSED, int* xpos UNUSED, int
|
||||
// A Wayland client is not aware of its position, so just warn and leave it
|
||||
// as (0, 0)
|
||||
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Window position retrieval not supported");
|
||||
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
|
||||
"Wayland: The platform does not provide the window position");
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowPos(_GLFWwindow* window UNUSED, int xpos UNUSED, int ypos UNUSED)
|
||||
{
|
||||
// A Wayland client can not set its position, so just warn
|
||||
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Window position setting not supported");
|
||||
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
|
||||
"Wayland: The platform does not support setting the window position");
|
||||
}
|
||||
|
||||
void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height)
|
||||
@@ -1023,6 +1057,15 @@ void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
|
||||
|
||||
void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window UNUSED,
|
||||
int numer UNUSED, int denom UNUSED)
|
||||
{
|
||||
// TODO: find out how to trigger a resize.
|
||||
// The actual limits are checked in the xdg_toplevel::configure handler.
|
||||
_glfwInputError(GLFW_FEATURE_UNIMPLEMENTED,
|
||||
"Wayland: Window aspect ratio not yet implemented");
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window UNUSED,
|
||||
int widthincr UNUSED, int heightincr UNUSED)
|
||||
{
|
||||
// TODO: find out how to trigger a resize.
|
||||
// The actual limits are checked in the xdg_toplevel::configure handler.
|
||||
@@ -1123,7 +1166,7 @@ void _glfwPlatformRequestWindowAttention(_GLFWwindow* window UNUSED)
|
||||
// TODO
|
||||
static bool notified = false;
|
||||
if (!notified) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
_glfwInputError(GLFW_FEATURE_UNIMPLEMENTED,
|
||||
"Wayland: Window attention request not implemented yet");
|
||||
notified = true;
|
||||
}
|
||||
@@ -1144,8 +1187,8 @@ int _glfwPlatformWindowBell(_GLFWwindow* window UNUSED)
|
||||
|
||||
void _glfwPlatformFocusWindow(_GLFWwindow* window UNUSED)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Focusing a window requires user interaction");
|
||||
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
|
||||
"Wayland: The platform does not support setting the input focus");
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
|
||||
@@ -1198,7 +1241,7 @@ int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
|
||||
void _glfwPlatformSetWindowResizable(_GLFWwindow* window UNUSED, bool enabled UNUSED)
|
||||
{
|
||||
// TODO
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
_glfwInputError(GLFW_FEATURE_UNIMPLEMENTED,
|
||||
"Wayland: Window attribute setting not implemented yet");
|
||||
}
|
||||
|
||||
@@ -1216,7 +1259,7 @@ void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, bool enabled)
|
||||
void _glfwPlatformSetWindowFloating(_GLFWwindow* window UNUSED, bool enabled UNUSED)
|
||||
{
|
||||
// TODO
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
_glfwInputError(GLFW_FEATURE_UNIMPLEMENTED,
|
||||
"Wayland: Window attribute setting not implemented yet");
|
||||
}
|
||||
|
||||
@@ -1227,6 +1270,18 @@ float _glfwPlatformGetWindowOpacity(_GLFWwindow* window UNUSED)
|
||||
|
||||
void _glfwPlatformSetWindowOpacity(_GLFWwindow* window UNUSED, float opacity UNUSED)
|
||||
{
|
||||
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
|
||||
"Wayland: The platform does not support setting the window opacity");
|
||||
}
|
||||
|
||||
void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window UNUSED, bool enabled UNUSED)
|
||||
{
|
||||
// This is handled in relativePointerHandleRelativeMotion
|
||||
}
|
||||
|
||||
bool _glfwPlatformRawMouseMotionSupported(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void _glfwPlatformPollEvents(void)
|
||||
@@ -1299,17 +1354,19 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
cursor->wl.height = image->height;
|
||||
cursor->wl.xhot = xhot;
|
||||
cursor->wl.yhot = yhot;
|
||||
cursor->wl.scale = -1;
|
||||
cursor->wl.shape = GLFW_INVALID_CURSOR;
|
||||
return true;
|
||||
}
|
||||
|
||||
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, GLFWCursorShape shape)
|
||||
{
|
||||
struct wl_cursor* standardCursor;
|
||||
|
||||
standardCursor = _glfwLoadCursor(shape);
|
||||
if (!standardCursor) return false;
|
||||
cursor->wl.cursor = standardCursor;
|
||||
// Don't actually load the cursor at this point,
|
||||
// because there's not enough info to be properly HiDPI aware.
|
||||
cursor->wl.cursor = NULL;
|
||||
cursor->wl.currentImage = 0;
|
||||
cursor->wl.scale = 0;
|
||||
cursor->wl.shape = shape;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1323,31 +1380,42 @@ void _glfwPlatformDestroyCursor(_GLFWcursor* cursor)
|
||||
wl_buffer_destroy(cursor->wl.buffer);
|
||||
}
|
||||
|
||||
static void handleRelativeMotion(void* data,
|
||||
struct zwp_relative_pointer_v1* pointer UNUSED,
|
||||
uint32_t timeHi UNUSED,
|
||||
uint32_t timeLo UNUSED,
|
||||
wl_fixed_t dx UNUSED,
|
||||
wl_fixed_t dy UNUSED,
|
||||
wl_fixed_t dxUnaccel,
|
||||
wl_fixed_t dyUnaccel)
|
||||
static void relativePointerHandleRelativeMotion(void* data,
|
||||
struct zwp_relative_pointer_v1* pointer UNUSED,
|
||||
uint32_t timeHi UNUSED,
|
||||
uint32_t timeLo UNUSED,
|
||||
wl_fixed_t dx,
|
||||
wl_fixed_t dy,
|
||||
wl_fixed_t dxUnaccel,
|
||||
wl_fixed_t dyUnaccel)
|
||||
{
|
||||
_GLFWwindow* window = data;
|
||||
double xpos = window->virtualCursorPosX;
|
||||
double ypos = window->virtualCursorPosY;
|
||||
|
||||
if (window->cursorMode != GLFW_CURSOR_DISABLED)
|
||||
return;
|
||||
|
||||
_glfwInputCursorPos(window,
|
||||
window->virtualCursorPosX + wl_fixed_to_double(dxUnaccel),
|
||||
window->virtualCursorPosY + wl_fixed_to_double(dyUnaccel));
|
||||
if (window->rawMouseMotion)
|
||||
{
|
||||
xpos += wl_fixed_to_double(dxUnaccel);
|
||||
ypos += wl_fixed_to_double(dyUnaccel);
|
||||
}
|
||||
else
|
||||
{
|
||||
xpos += wl_fixed_to_double(dx);
|
||||
ypos += wl_fixed_to_double(dy);
|
||||
}
|
||||
|
||||
_glfwInputCursorPos(window, xpos, ypos);
|
||||
}
|
||||
|
||||
static const struct zwp_relative_pointer_v1_listener relativePointerListener = {
|
||||
handleRelativeMotion
|
||||
relativePointerHandleRelativeMotion
|
||||
};
|
||||
|
||||
static void handleLocked(void* data UNUSED,
|
||||
struct zwp_locked_pointer_v1* lockedPointer UNUSED)
|
||||
static void lockedPointerHandleLocked(void* data UNUSED,
|
||||
struct zwp_locked_pointer_v1* lockedPointer UNUSED)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1367,14 +1435,14 @@ static void unlockPointer(_GLFWwindow* window)
|
||||
|
||||
static void lockPointer(_GLFWwindow* window UNUSED);
|
||||
|
||||
static void handleUnlocked(void* data UNUSED,
|
||||
struct zwp_locked_pointer_v1* lockedPointer UNUSED)
|
||||
static void lockedPointerHandleUnlocked(void* data UNUSED,
|
||||
struct zwp_locked_pointer_v1* lockedPointer UNUSED)
|
||||
{
|
||||
}
|
||||
|
||||
static const struct zwp_locked_pointer_v1_listener lockedPointerListener = {
|
||||
handleLocked,
|
||||
handleUnlocked
|
||||
lockedPointerHandleLocked,
|
||||
lockedPointerHandleUnlocked
|
||||
};
|
||||
|
||||
static void lockPointer(_GLFWwindow* window)
|
||||
@@ -1411,7 +1479,7 @@ static void lockPointer(_GLFWwindow* window)
|
||||
window->wl.pointerLock.relativePointer = relativePointer;
|
||||
window->wl.pointerLock.lockedPointer = lockedPointer;
|
||||
|
||||
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
|
||||
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.serial,
|
||||
NULL, 0, 0);
|
||||
}
|
||||
|
||||
@@ -1422,8 +1490,6 @@ static bool isPointerLocked(_GLFWwindow* window)
|
||||
|
||||
void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
|
||||
{
|
||||
struct wl_cursor* defaultCursor;
|
||||
|
||||
if (!_glfw.wl.pointer)
|
||||
return;
|
||||
|
||||
@@ -1440,21 +1506,7 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
|
||||
|
||||
if (window->cursorMode == GLFW_CURSOR_NORMAL)
|
||||
{
|
||||
if (cursor)
|
||||
setCursorImage(&cursor->wl);
|
||||
else
|
||||
{
|
||||
defaultCursor = _glfwLoadCursor(GLFW_ARROW_CURSOR);
|
||||
if (!defaultCursor) return;
|
||||
_GLFWcursorWayland cursorWayland = {
|
||||
defaultCursor,
|
||||
NULL,
|
||||
0, 0,
|
||||
0, 0,
|
||||
0
|
||||
};
|
||||
setCursorImage(&cursorWayland);
|
||||
}
|
||||
setCursorImage(window);
|
||||
}
|
||||
else if (window->cursorMode == GLFW_CURSOR_DISABLED)
|
||||
{
|
||||
@@ -1463,8 +1515,7 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
|
||||
}
|
||||
else if (window->cursorMode == GLFW_CURSOR_HIDDEN)
|
||||
{
|
||||
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
|
||||
NULL, 0, 0);
|
||||
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.serial, NULL, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1499,10 +1550,11 @@ static void _glfwSendPrimarySelectionText(void *data UNUSED, struct zwp_primary_
|
||||
send_text(_glfw.wl.primarySelectionString, fd);
|
||||
}
|
||||
|
||||
static char* read_offer_string(int data_pipe) {
|
||||
static char* read_offer_string(int data_pipe, size_t *data_sz) {
|
||||
wl_display_flush(_glfw.wl.display);
|
||||
size_t sz = 0, capacity = 0;
|
||||
size_t capacity = 0;
|
||||
char *buf = NULL;
|
||||
*data_sz = 0;
|
||||
struct pollfd fds;
|
||||
fds.fd = data_pipe;
|
||||
fds.events = POLLIN;
|
||||
@@ -1523,20 +1575,20 @@ static char* read_offer_string(int data_pipe) {
|
||||
if (!ret) {
|
||||
bail("Wayland: Failed to read clipboard data from pipe (timed out)");
|
||||
}
|
||||
if (capacity <= sz || capacity - sz <= 64) {
|
||||
if (capacity <= *data_sz || capacity - *data_sz <= 64) {
|
||||
capacity += 4096;
|
||||
buf = realloc(buf, capacity);
|
||||
if (!buf) {
|
||||
bail("Wayland: Failed to allocate memory to read clipboard data");
|
||||
}
|
||||
}
|
||||
ret = read(data_pipe, buf + sz, capacity - sz - 1);
|
||||
ret = read(data_pipe, buf + *data_sz, capacity - *data_sz - 1);
|
||||
if (ret == -1) {
|
||||
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue;
|
||||
bail("Wayland: Failed to read clipboard data from pipe with error: %s", strerror(errno));
|
||||
}
|
||||
if (ret == 0) { close(data_pipe); buf[sz] = 0; return buf; }
|
||||
sz += ret;
|
||||
if (ret == 0) { close(data_pipe); buf[*data_sz] = 0; return buf; }
|
||||
*data_sz += ret;
|
||||
start = glfwGetTime();
|
||||
}
|
||||
bail("Wayland: Failed to read clipboard data from pipe (timed out)");
|
||||
@@ -1549,15 +1601,16 @@ static char* read_primary_selection_offer(struct zwp_primary_selection_offer_v1
|
||||
if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL;
|
||||
zwp_primary_selection_offer_v1_receive(primary_selection_offer, mime, pipefd[1]);
|
||||
close(pipefd[1]);
|
||||
return read_offer_string(pipefd[0]);
|
||||
size_t sz = 0;
|
||||
return read_offer_string(pipefd[0], &sz);
|
||||
}
|
||||
|
||||
static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime) {
|
||||
static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime, size_t *sz) {
|
||||
int pipefd[2];
|
||||
if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL;
|
||||
wl_data_offer_receive(data_offer, mime, pipefd[1]);
|
||||
close(pipefd[1]);
|
||||
return read_offer_string(pipefd[0]);
|
||||
return read_offer_string(pipefd[0], sz);
|
||||
}
|
||||
|
||||
static void data_source_canceled(void *data UNUSED, struct wl_data_source *wl_data_source) {
|
||||
@@ -1586,20 +1639,23 @@ static const struct zwp_primary_selection_source_v1_listener primary_selection_s
|
||||
.cancelled = primary_selection_source_canceled,
|
||||
};
|
||||
|
||||
void
|
||||
destroy_data_offer(_GLFWWaylandDataOffer *offer) {
|
||||
if (offer->id) {
|
||||
if (offer->is_primary) zwp_primary_selection_offer_v1_destroy(offer->id);
|
||||
else wl_data_offer_destroy(offer->id);
|
||||
}
|
||||
if (offer->mimes) {
|
||||
for (size_t i = 0; i < offer->mimes_count; i++) free((char*)offer->mimes[i]);
|
||||
free(offer->mimes);
|
||||
}
|
||||
memset(offer, 0, sizeof(_GLFWWaylandDataOffer));
|
||||
}
|
||||
|
||||
static void prune_unclaimed_data_offers(void) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].id && !_glfw.wl.dataOffers[i].offer_type) {
|
||||
wl_data_offer_destroy(_glfw.wl.dataOffers[i].id);
|
||||
memset(_glfw.wl.dataOffers + i, 0, sizeof(_glfw.wl.dataOffers[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void prune_unclaimed_primary_selection_offers(void) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) {
|
||||
if (_glfw.wl.primarySelectionOffers[i].id && !_glfw.wl.dataOffers[i].offer_type) {
|
||||
zwp_primary_selection_offer_v1_destroy(_glfw.wl.primarySelectionOffers[i].id);
|
||||
memset(_glfw.wl.primarySelectionOffers + i, 0, sizeof(_glfw.wl.primarySelectionOffers[0]));
|
||||
destroy_data_offer(&_glfw.wl.dataOffers[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1618,25 +1674,32 @@ static void mark_selection_offer(void *data UNUSED, struct wl_data_device *data_
|
||||
|
||||
static void mark_primary_selection_offer(void *data UNUSED, struct zwp_primary_selection_device_v1* primary_selection_device UNUSED,
|
||||
struct zwp_primary_selection_offer_v1 *primary_selection_offer) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) {
|
||||
if (_glfw.wl.primarySelectionOffers[i].id == primary_selection_offer) {
|
||||
_glfw.wl.primarySelectionOffers[i].offer_type = PRIMARY_SELECTION;
|
||||
} else if (_glfw.wl.primarySelectionOffers[i].offer_type == PRIMARY_SELECTION) {
|
||||
_glfw.wl.primarySelectionOffers[i].offer_type = EXPIRED; // previous selection offer
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].id == primary_selection_offer) {
|
||||
_glfw.wl.dataOffers[i].offer_type = PRIMARY_SELECTION;
|
||||
} else if (_glfw.wl.dataOffers[i].offer_type == PRIMARY_SELECTION) {
|
||||
_glfw.wl.dataOffers[i].offer_type = EXPIRED; // previous selection offer
|
||||
}
|
||||
}
|
||||
prune_unclaimed_primary_selection_offers();
|
||||
prune_unclaimed_data_offers();
|
||||
}
|
||||
|
||||
static void set_offer_mimetype(struct _GLFWWaylandDataOffer* offer, const char* mime) {
|
||||
if (strcmp(mime, "text/plain;charset=utf-8") == 0)
|
||||
offer->mime = "text/plain;charset=utf-8";
|
||||
else if (!offer->mime && strcmp(mime, "text/plain") == 0)
|
||||
offer->mime = "text/plain";
|
||||
else if (strcmp(mime, clipboard_mime()) == 0)
|
||||
offer->is_self_offer = 1;
|
||||
else if (strcmp(mime, URI_LIST_MIME) == 0)
|
||||
offer->has_uri_list = 1;
|
||||
static void
|
||||
set_offer_mimetype(_GLFWWaylandDataOffer* offer, const char* mime) {
|
||||
if (strcmp(mime, "text/plain;charset=utf-8") == 0) {
|
||||
offer->plain_text_mime = "text/plain;charset=utf-8";
|
||||
} else if (!offer->plain_text_mime && strcmp(mime, "text/plain")) {
|
||||
offer->plain_text_mime = "text/plain";
|
||||
}
|
||||
if (strcmp(mime, clipboard_mime()) == 0) {
|
||||
offer->is_self_offer = true;
|
||||
}
|
||||
if (!offer->mimes || offer->mimes_count >= offer->mimes_capacity - 1) {
|
||||
offer->mimes = realloc(offer->mimes, sizeof(char*) * (offer->mimes_capacity + 64));
|
||||
if (offer->mimes) offer->mimes_capacity += 64;
|
||||
else return;
|
||||
}
|
||||
offer->mimes[offer->mimes_count++] = _glfw_strdup(mime);
|
||||
}
|
||||
|
||||
static void handle_offer_mimetype(void *data UNUSED, struct wl_data_offer* id, const char *mime) {
|
||||
@@ -1649,9 +1712,9 @@ static void handle_offer_mimetype(void *data UNUSED, struct wl_data_offer* id, c
|
||||
}
|
||||
|
||||
static void handle_primary_selection_offer_mimetype(void *data UNUSED, struct zwp_primary_selection_offer_v1* id, const char *mime) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) {
|
||||
if (_glfw.wl.primarySelectionOffers[i].id == id) {
|
||||
set_offer_mimetype((struct _GLFWWaylandDataOffer*)&_glfw.wl.primarySelectionOffers[i], mime);
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].id == id) {
|
||||
set_offer_mimetype((_GLFWWaylandDataOffer*)&_glfw.wl.dataOffers[i], mime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1686,7 +1749,8 @@ static const struct zwp_primary_selection_offer_v1_listener primary_selection_of
|
||||
.offer = handle_primary_selection_offer_mimetype,
|
||||
};
|
||||
|
||||
static void handle_data_offer(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, struct wl_data_offer *id) {
|
||||
static size_t
|
||||
handle_data_offer_generic(void *id, bool is_primary) {
|
||||
size_t smallest_idx = SIZE_MAX, pos = 0;
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].idx && _glfw.wl.dataOffers[i].idx < smallest_idx) {
|
||||
@@ -1694,47 +1758,48 @@ static void handle_data_offer(void *data UNUSED, struct wl_data_device *wl_data_
|
||||
pos = i;
|
||||
}
|
||||
if (_glfw.wl.dataOffers[i].id == NULL) {
|
||||
_glfw.wl.dataOffers[i].id = id;
|
||||
_glfw.wl.dataOffers[i].idx = ++_glfw.wl.dataOffersCounter;
|
||||
pos = i;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
if (_glfw.wl.dataOffers[pos].id) wl_data_offer_destroy(_glfw.wl.dataOffers[pos].id);
|
||||
memset(_glfw.wl.dataOffers + pos, 0, sizeof(_glfw.wl.dataOffers[0]));
|
||||
_glfw.wl.dataOffers[pos].id = id;
|
||||
_glfw.wl.dataOffers[pos].idx = ++_glfw.wl.dataOffersCounter;
|
||||
if (_glfw.wl.dataOffers[pos].id) destroy_data_offer(&_glfw.wl.dataOffers[pos]);
|
||||
end:
|
||||
_glfw.wl.dataOffers[pos].id = id;
|
||||
_glfw.wl.dataOffers[pos].is_primary = is_primary;
|
||||
_glfw.wl.dataOffers[pos].idx = ++_glfw.wl.dataOffersCounter;
|
||||
return pos;
|
||||
}
|
||||
|
||||
static void handle_data_offer(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, struct wl_data_offer *id) {
|
||||
handle_data_offer_generic(id, false);
|
||||
wl_data_offer_add_listener(id, &data_offer_listener, NULL);
|
||||
}
|
||||
|
||||
static void handle_primary_selection_offer(void *data UNUSED, struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1 UNUSED, struct zwp_primary_selection_offer_v1 *id) {
|
||||
size_t smallest_idx = SIZE_MAX, pos = 0;
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) {
|
||||
if (_glfw.wl.primarySelectionOffers[i].idx && _glfw.wl.primarySelectionOffers[i].idx < smallest_idx) {
|
||||
smallest_idx = _glfw.wl.primarySelectionOffers[i].idx;
|
||||
pos = i;
|
||||
}
|
||||
if (_glfw.wl.primarySelectionOffers[i].id == NULL) {
|
||||
_glfw.wl.primarySelectionOffers[i].id = id;
|
||||
_glfw.wl.primarySelectionOffers[i].idx = ++_glfw.wl.primarySelectionOffersCounter;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
if (_glfw.wl.primarySelectionOffers[pos].id) zwp_primary_selection_offer_v1_destroy(_glfw.wl.primarySelectionOffers[pos].id);
|
||||
memset(_glfw.wl.primarySelectionOffers + pos, 0, sizeof(_glfw.wl.primarySelectionOffers[0]));
|
||||
_glfw.wl.primarySelectionOffers[pos].id = id;
|
||||
_glfw.wl.primarySelectionOffers[pos].idx = ++_glfw.wl.primarySelectionOffersCounter;
|
||||
end:
|
||||
handle_data_offer_generic(id, true);
|
||||
zwp_primary_selection_offer_v1_add_listener(id, &primary_selection_offer_listener, NULL);
|
||||
}
|
||||
|
||||
static void drag_enter(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED, uint32_t serial, struct wl_surface *surface, wl_fixed_t x UNUSED, wl_fixed_t y UNUSED, struct wl_data_offer *id) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].id == id) {
|
||||
_glfw.wl.dataOffers[i].offer_type = DRAG_AND_DROP;
|
||||
_glfw.wl.dataOffers[i].surface = surface;
|
||||
const char *mime = _glfw.wl.dataOffers[i].has_uri_list ? URI_LIST_MIME : NULL;
|
||||
wl_data_offer_accept(id, serial, mime);
|
||||
_GLFWWaylandDataOffer *d = _glfw.wl.dataOffers + i;
|
||||
if (d->id == id) {
|
||||
d->offer_type = DRAG_AND_DROP;
|
||||
d->surface = surface;
|
||||
_GLFWwindow* window = _glfw.windowListHead;
|
||||
int format_priority = 0;
|
||||
while (window)
|
||||
{
|
||||
if (window->wl.surface == surface) {
|
||||
for (size_t x = 0; x < d->mimes_count; x++) {
|
||||
int prio = _glfwInputDrop(window, d->mimes[x], NULL, 0);
|
||||
if (prio > format_priority) d->mime_for_drop = d->mimes[x];
|
||||
}
|
||||
break;
|
||||
}
|
||||
window = window->next;
|
||||
}
|
||||
wl_data_offer_accept(id, serial, d->mime_for_drop);
|
||||
} else if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) {
|
||||
_glfw.wl.dataOffers[i].offer_type = EXPIRED; // previous drag offer
|
||||
}
|
||||
@@ -1745,39 +1810,34 @@ static void drag_enter(void *data UNUSED, struct wl_data_device *wl_data_device
|
||||
static void drag_leave(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) {
|
||||
wl_data_offer_destroy(_glfw.wl.dataOffers[i].id);
|
||||
memset(_glfw.wl.dataOffers + i, 0, sizeof(_glfw.wl.dataOffers[0]));
|
||||
destroy_data_offer(&_glfw.wl.dataOffers[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void drop(void *data, struct wl_data_device *wl_data_device UNUSED) {
|
||||
static void drop(void *data UNUSED, struct wl_data_device *wl_data_device UNUSED) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) {
|
||||
char *uri_list = read_data_offer(_glfw.wl.dataOffers[i].id, URI_LIST_MIME);
|
||||
if (uri_list) {
|
||||
wl_data_offer_finish(_glfw.wl.dataOffers[i].id);
|
||||
int count;
|
||||
char** paths = parseUriList(data, &count);
|
||||
if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP && _glfw.wl.dataOffers[i].mime_for_drop) {
|
||||
size_t sz = 0;
|
||||
char *data = read_data_offer(_glfw.wl.dataOffers[i].id, _glfw.wl.dataOffers[i].mime_for_drop, &sz);
|
||||
if (data) {
|
||||
// We dont do finish as this requires version 3 for wl_data_device_manager
|
||||
// which then requires more work with calling set_actions for drag and drop to function
|
||||
// wl_data_offer_finish(_glfw.wl.dataOffers[i].id);
|
||||
|
||||
_GLFWwindow* window = _glfw.windowListHead;
|
||||
while (window)
|
||||
{
|
||||
if (window->wl.surface == _glfw.wl.dataOffers[i].surface) {
|
||||
_glfwInputDrop(window, count, (const char**) paths);
|
||||
_glfwInputDrop(window, _glfw.wl.dataOffers[i].mime_for_drop, data, sz);
|
||||
break;
|
||||
}
|
||||
window = window->next;
|
||||
}
|
||||
|
||||
|
||||
for (int k = 0; k < count; k++)
|
||||
free(paths[k]);
|
||||
free(paths);
|
||||
free(uri_list);
|
||||
free(data);
|
||||
}
|
||||
wl_data_offer_destroy(_glfw.wl.dataOffers[i].id);
|
||||
memset(_glfw.wl.dataOffers + i, 0, sizeof(_glfw.wl.dataOffers[0]));
|
||||
destroy_data_offer(&_glfw.wl.dataOffers[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1882,10 +1942,12 @@ void _glfwPlatformSetClipboardString(const char* string)
|
||||
const char* _glfwPlatformGetClipboardString(void)
|
||||
{
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].id && _glfw.wl.dataOffers[i].mime && _glfw.wl.dataOffers[i].offer_type == CLIPBOARD) {
|
||||
if (_glfw.wl.dataOffers[i].is_self_offer) return _glfw.wl.clipboardString;
|
||||
_GLFWWaylandDataOffer *d = _glfw.wl.dataOffers + i;
|
||||
if (d->id && d->offer_type == CLIPBOARD && d->plain_text_mime) {
|
||||
if (d->is_self_offer) return _glfw.wl.clipboardString;
|
||||
free(_glfw.wl.pasteString);
|
||||
_glfw.wl.pasteString = read_data_offer(_glfw.wl.dataOffers[i].id, _glfw.wl.dataOffers[i].mime);
|
||||
size_t sz = 0;
|
||||
_glfw.wl.pasteString = read_data_offer(d->id, d->plain_text_mime, &sz);
|
||||
return _glfw.wl.pasteString;
|
||||
}
|
||||
}
|
||||
@@ -1932,13 +1994,14 @@ const char* _glfwPlatformGetPrimarySelectionString(void)
|
||||
{
|
||||
if (_glfw.wl.dataSourceForPrimarySelection != NULL) return _glfw.wl.primarySelectionString;
|
||||
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) {
|
||||
if (_glfw.wl.primarySelectionOffers[i].id && _glfw.wl.primarySelectionOffers[i].mime && _glfw.wl.primarySelectionOffers[i].offer_type == PRIMARY_SELECTION) {
|
||||
if (_glfw.wl.primarySelectionOffers[i].is_self_offer) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
_GLFWWaylandDataOffer *d = _glfw.wl.dataOffers + i;
|
||||
if (d->id && d->is_primary && d->offer_type == PRIMARY_SELECTION && d->plain_text_mime) {
|
||||
if (d->is_self_offer) {
|
||||
return _glfw.wl.primarySelectionString;
|
||||
}
|
||||
free(_glfw.wl.pasteString);
|
||||
_glfw.wl.pasteString = read_primary_selection_offer(_glfw.wl.primarySelectionOffers[i].id, _glfw.wl.primarySelectionOffers[i].mime);
|
||||
_glfw.wl.pasteString = read_primary_selection_offer(d->id, d->plain_text_mime);
|
||||
return _glfw.wl.pasteString;
|
||||
}
|
||||
}
|
||||
|
||||
59
glfw/x11_init.c
vendored
59
glfw/x11_init.c
vendored
@@ -42,11 +42,11 @@
|
||||
#include <unistd.h>
|
||||
|
||||
|
||||
// Check whether the specified atom is supported
|
||||
// Return the atom ID only if it is listed in the specified array
|
||||
//
|
||||
static Atom getSupportedAtom(Atom* supportedAtoms,
|
||||
unsigned long atomCount,
|
||||
const char* atomName)
|
||||
static Atom getAtomIfSupported(Atom* supportedAtoms,
|
||||
unsigned long atomCount,
|
||||
const char* atomName)
|
||||
{
|
||||
const Atom atom = XInternAtom(_glfw.x11.display, atomName, False);
|
||||
|
||||
@@ -64,9 +64,8 @@ static Atom getSupportedAtom(Atom* supportedAtoms,
|
||||
static void detectEWMH(void)
|
||||
{
|
||||
// First we read the _NET_SUPPORTING_WM_CHECK property on the root window
|
||||
Window* windowFromRoot = NULL;
|
||||
|
||||
// Then we look for the _NET_SUPPORTING_WM_CHECK property of the root window
|
||||
Window* windowFromRoot = NULL;
|
||||
if (!_glfwGetWindowPropertyX11(_glfw.x11.root,
|
||||
_glfw.x11.NET_SUPPORTING_WM_CHECK,
|
||||
XA_WINDOW,
|
||||
@@ -93,6 +92,7 @@ static void detectEWMH(void)
|
||||
_glfwReleaseErrorHandlerX11();
|
||||
|
||||
// If the property exists, it should contain the XID of the window
|
||||
|
||||
if (*windowFromRoot != *windowFromChild)
|
||||
{
|
||||
XFree(windowFromRoot);
|
||||
@@ -103,49 +103,50 @@ static void detectEWMH(void)
|
||||
XFree(windowFromRoot);
|
||||
XFree(windowFromChild);
|
||||
|
||||
// We are now fairly sure that an EWMH-compliant window manager is running
|
||||
// We are now fairly sure that an EWMH-compliant WM is currently running
|
||||
// We can now start querying the WM about what features it supports by
|
||||
// looking in the _NET_SUPPORTED property on the root window
|
||||
// It should contain a list of supported EWMH protocol and state atoms
|
||||
|
||||
Atom* supportedAtoms = NULL;
|
||||
const unsigned long atomCount = _glfwGetWindowPropertyX11(_glfw.x11.root,
|
||||
_glfw.x11.NET_SUPPORTED,
|
||||
XA_ATOM,
|
||||
(unsigned char**) &supportedAtoms);
|
||||
const unsigned long atomCount =
|
||||
_glfwGetWindowPropertyX11(_glfw.x11.root,
|
||||
_glfw.x11.NET_SUPPORTED,
|
||||
XA_ATOM,
|
||||
(unsigned char**) &supportedAtoms);
|
||||
if (!supportedAtoms)
|
||||
return;
|
||||
|
||||
// See which of the atoms we support that are supported by the WM
|
||||
|
||||
_glfw.x11.NET_WM_STATE =
|
||||
getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE");
|
||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE");
|
||||
_glfw.x11.NET_WM_STATE_ABOVE =
|
||||
getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE_ABOVE");
|
||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_ABOVE");
|
||||
_glfw.x11.NET_WM_STATE_FULLSCREEN =
|
||||
getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE_FULLSCREEN");
|
||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_FULLSCREEN");
|
||||
_glfw.x11.NET_WM_STATE_MAXIMIZED_VERT =
|
||||
getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE_MAXIMIZED_VERT");
|
||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_MAXIMIZED_VERT");
|
||||
_glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ =
|
||||
getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE_MAXIMIZED_HORZ");
|
||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_MAXIMIZED_HORZ");
|
||||
_glfw.x11.NET_WM_STATE_DEMANDS_ATTENTION =
|
||||
getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE_DEMANDS_ATTENTION");
|
||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_DEMANDS_ATTENTION");
|
||||
_glfw.x11.NET_WM_FULLSCREEN_MONITORS =
|
||||
getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_FULLSCREEN_MONITORS");
|
||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_FULLSCREEN_MONITORS");
|
||||
_glfw.x11.NET_WM_WINDOW_TYPE =
|
||||
getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE");
|
||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE");
|
||||
_glfw.x11.NET_WM_WINDOW_TYPE_NORMAL =
|
||||
getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE_NORMAL");
|
||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE_NORMAL");
|
||||
_glfw.x11.NET_WORKAREA =
|
||||
getSupportedAtom(supportedAtoms, atomCount, "_NET_WORKAREA");
|
||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WORKAREA");
|
||||
_glfw.x11.NET_CURRENT_DESKTOP =
|
||||
getSupportedAtom(supportedAtoms, atomCount, "_NET_CURRENT_DESKTOP");
|
||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_CURRENT_DESKTOP");
|
||||
_glfw.x11.NET_ACTIVE_WINDOW =
|
||||
getSupportedAtom(supportedAtoms, atomCount, "_NET_ACTIVE_WINDOW");
|
||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_ACTIVE_WINDOW");
|
||||
_glfw.x11.NET_FRAME_EXTENTS =
|
||||
getSupportedAtom(supportedAtoms, atomCount, "_NET_FRAME_EXTENTS");
|
||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_FRAME_EXTENTS");
|
||||
_glfw.x11.NET_REQUEST_FRAME_EXTENTS =
|
||||
getSupportedAtom(supportedAtoms, atomCount, "_NET_REQUEST_FRAME_EXTENTS");
|
||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_REQUEST_FRAME_EXTENTS");
|
||||
|
||||
XFree(supportedAtoms);
|
||||
}
|
||||
@@ -384,7 +385,6 @@ static bool initExtensions(void)
|
||||
_glfw.x11.XdndFinished = XInternAtom(_glfw.x11.display, "XdndFinished", False);
|
||||
_glfw.x11.XdndSelection = XInternAtom(_glfw.x11.display, "XdndSelection", False);
|
||||
_glfw.x11.XdndTypeList = XInternAtom(_glfw.x11.display, "XdndTypeList", False);
|
||||
_glfw.x11.text_uri_list = XInternAtom(_glfw.x11.display, "text/uri-list", False);
|
||||
|
||||
// ICCCM, EWMH and Motif window property atoms
|
||||
// These can be set safely even without WM support
|
||||
@@ -503,8 +503,11 @@ static Window createHelperWindow(void)
|
||||
|
||||
// X error handler
|
||||
//
|
||||
static int errorHandler(Display *display UNUSED, XErrorEvent* event)
|
||||
static int errorHandler(Display *display, XErrorEvent* event)
|
||||
{
|
||||
if (_glfw.x11.display != display)
|
||||
return 0;
|
||||
|
||||
_glfw.x11.errorCode = event->error_code;
|
||||
return 0;
|
||||
}
|
||||
@@ -635,8 +638,6 @@ int _glfwPlatformInit(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
_glfwInitTimerPOSIX();
|
||||
|
||||
_glfwPollMonitorsX11();
|
||||
return true;
|
||||
}
|
||||
|
||||
27
glfw/x11_monitor.c
vendored
27
glfw/x11_monitor.c
vendored
@@ -241,7 +241,8 @@ void _glfwSetVideoModeX11(_GLFWmonitor* monitor, const GLFWvidmode* desired)
|
||||
if (_glfwCompareVideoModes(¤t, best) == 0)
|
||||
return;
|
||||
|
||||
XRRScreenResources* sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
||||
XRRScreenResources* sr =
|
||||
XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
||||
XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
|
||||
XRROutputInfo* oi = XRRGetOutputInfo(_glfw.x11.display, sr, monitor->x11.output);
|
||||
|
||||
@@ -289,7 +290,8 @@ void _glfwRestoreVideoModeX11(_GLFWmonitor* monitor)
|
||||
if (monitor->x11.oldMode == None)
|
||||
return;
|
||||
|
||||
XRRScreenResources* sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
||||
XRRScreenResources* sr =
|
||||
XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
||||
XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
|
||||
|
||||
XRRSetCrtcConfig(_glfw.x11.display,
|
||||
@@ -321,9 +323,10 @@ void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos)
|
||||
{
|
||||
if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken)
|
||||
{
|
||||
|
||||
XRRScreenResources* sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
||||
XRRScreenResources* sr =
|
||||
XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
||||
XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
|
||||
|
||||
if (ci)
|
||||
{
|
||||
if (xpos)
|
||||
@@ -333,6 +336,7 @@ void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos)
|
||||
|
||||
XRRFreeCrtcInfo(ci);
|
||||
}
|
||||
|
||||
XRRFreeScreenResources(sr);
|
||||
}
|
||||
}
|
||||
@@ -352,8 +356,8 @@ void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor, int* xpos, int* ypos
|
||||
|
||||
if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken)
|
||||
{
|
||||
|
||||
XRRScreenResources* sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
||||
XRRScreenResources* sr =
|
||||
XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
||||
XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
|
||||
|
||||
areaX = ci->x;
|
||||
@@ -446,7 +450,8 @@ GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)
|
||||
|
||||
if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken)
|
||||
{
|
||||
XRRScreenResources* sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
||||
XRRScreenResources* sr =
|
||||
XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
||||
XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
|
||||
XRROutputInfo* oi = XRRGetOutputInfo(_glfw.x11.display, sr, monitor->x11.output);
|
||||
|
||||
@@ -493,17 +498,19 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode)
|
||||
{
|
||||
if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken)
|
||||
{
|
||||
|
||||
XRRScreenResources* sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
||||
XRRScreenResources* sr =
|
||||
XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
||||
XRRCrtcInfo* ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
|
||||
|
||||
if (ci) {
|
||||
if (ci)
|
||||
{
|
||||
const XRRModeInfo* mi = getModeInfo(sr, ci->mode);
|
||||
if (mi) // mi can be NULL if the monitor has been disconnected
|
||||
*mode = vidmodeFromModeInfo(mi, ci);
|
||||
|
||||
XRRFreeCrtcInfo(ci);
|
||||
}
|
||||
|
||||
XRRFreeScreenResources(sr);
|
||||
}
|
||||
else
|
||||
|
||||
6
glfw/x11_platform.h
vendored
6
glfw/x11_platform.h
vendored
@@ -152,7 +152,6 @@ typedef VkResult (APIENTRY *PFN_vkCreateXcbSurfaceKHR)(VkInstance,const VkXcbSur
|
||||
typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR)(VkPhysicalDevice,uint32_t,xcb_connection_t*,xcb_visualid_t);
|
||||
|
||||
#include "posix_thread.h"
|
||||
#include "posix_time.h"
|
||||
#include "glx_context.h"
|
||||
#include "egl_context.h"
|
||||
#include "osmesa_context.h"
|
||||
@@ -181,6 +180,7 @@ typedef struct _GLFWwindowX11
|
||||
{
|
||||
Colormap colormap;
|
||||
Window handle;
|
||||
Window parent;
|
||||
|
||||
bool iconified;
|
||||
bool maximized;
|
||||
@@ -266,7 +266,6 @@ typedef struct _GLFWlibraryX11
|
||||
Atom XdndFinished;
|
||||
Atom XdndSelection;
|
||||
Atom XdndTypeList;
|
||||
Atom text_uri_list;
|
||||
|
||||
// Selection (clipboard) atoms
|
||||
Atom TARGETS;
|
||||
@@ -327,7 +326,8 @@ typedef struct _GLFWlibraryX11
|
||||
struct {
|
||||
int version;
|
||||
Window source;
|
||||
Atom format;
|
||||
char format[128];
|
||||
int format_priority;
|
||||
} xdnd;
|
||||
|
||||
struct {
|
||||
|
||||
413
glfw/x11_window.c
vendored
413
glfw/x11_window.c
vendored
@@ -68,7 +68,7 @@ static unsigned _glfwDispatchX11Events(void);
|
||||
static void
|
||||
handleEvents(monotonic_t timeout) {
|
||||
EVDBG("starting handleEvents(%.2f)", monotonic_t_to_s_double(timeout));
|
||||
int display_read_ok = pollForEvents(&_glfw.x11.eventLoopData, timeout);
|
||||
int display_read_ok = pollForEvents(&_glfw.x11.eventLoopData, timeout, NULL);
|
||||
EVDBG("display_read_ok: %d", display_read_ok);
|
||||
if (display_read_ok) {
|
||||
unsigned dispatched = _glfwDispatchX11Events();
|
||||
@@ -255,6 +255,14 @@ static void updateNormalHints(_GLFWwindow* window, int width, int height)
|
||||
hints->min_aspect.x = hints->max_aspect.x = window->numer;
|
||||
hints->min_aspect.y = hints->max_aspect.y = window->denom;
|
||||
}
|
||||
|
||||
if (window->widthincr != GLFW_DONT_CARE &&
|
||||
window->heightincr != GLFW_DONT_CARE)
|
||||
{
|
||||
hints->flags |= PResizeInc;
|
||||
hints->width_inc = window->widthincr;
|
||||
hints->height_inc = window->heightincr;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -447,22 +455,41 @@ static void updateCursorImage(_GLFWwindow* window)
|
||||
}
|
||||
}
|
||||
|
||||
// Enable XI2 raw mouse motion events
|
||||
//
|
||||
static void enableRawMouseMotion(_GLFWwindow* window UNUSED)
|
||||
{
|
||||
XIEventMask em;
|
||||
unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 };
|
||||
|
||||
em.deviceid = XIAllMasterDevices;
|
||||
em.mask_len = sizeof(mask);
|
||||
em.mask = mask;
|
||||
XISetMask(mask, XI_RawMotion);
|
||||
|
||||
XISelectEvents(_glfw.x11.display, _glfw.x11.root, &em, 1);
|
||||
}
|
||||
|
||||
// Disable XI2 raw mouse motion events
|
||||
//
|
||||
static void disableRawMouseMotion(_GLFWwindow* window UNUSED)
|
||||
{
|
||||
XIEventMask em;
|
||||
unsigned char mask[] = { 0 };
|
||||
|
||||
em.deviceid = XIAllMasterDevices;
|
||||
em.mask_len = sizeof(mask);
|
||||
em.mask = mask;
|
||||
|
||||
XISelectEvents(_glfw.x11.display, _glfw.x11.root, &em, 1);
|
||||
}
|
||||
|
||||
// Apply disabled cursor mode to a focused window
|
||||
//
|
||||
static void disableCursor(_GLFWwindow* window)
|
||||
{
|
||||
if (_glfw.x11.xi.available)
|
||||
{
|
||||
XIEventMask em;
|
||||
unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 };
|
||||
|
||||
em.deviceid = XIAllMasterDevices;
|
||||
em.mask_len = sizeof(mask);
|
||||
em.mask = mask;
|
||||
XISetMask(mask, XI_RawMotion);
|
||||
|
||||
XISelectEvents(_glfw.x11.display, _glfw.x11.root, &em, 1);
|
||||
}
|
||||
if (window->rawMouseMotion)
|
||||
enableRawMouseMotion(window);
|
||||
|
||||
_glfw.x11.disabledCursorWindow = window;
|
||||
_glfwPlatformGetCursorPos(window,
|
||||
@@ -482,17 +509,8 @@ static void disableCursor(_GLFWwindow* window)
|
||||
//
|
||||
static void enableCursor(_GLFWwindow* window)
|
||||
{
|
||||
if (_glfw.x11.xi.available)
|
||||
{
|
||||
XIEventMask em;
|
||||
unsigned char mask[] = { 0 };
|
||||
|
||||
em.deviceid = XIAllMasterDevices;
|
||||
em.mask_len = sizeof(mask);
|
||||
em.mask = mask;
|
||||
|
||||
XISelectEvents(_glfw.x11.display, _glfw.x11.root, &em, 1);
|
||||
}
|
||||
if (window->rawMouseMotion)
|
||||
disableRawMouseMotion(window);
|
||||
|
||||
_glfw.x11.disabledCursorWindow = NULL;
|
||||
XUngrabPointer(_glfw.x11.display, CurrentTime);
|
||||
@@ -525,46 +543,41 @@ static bool createNativeWindow(_GLFWwindow* window,
|
||||
|
||||
window->x11.transparent = _glfwIsVisualTransparentX11(visual);
|
||||
|
||||
// Create the actual window
|
||||
XSetWindowAttributes wa = { 0 };
|
||||
wa.colormap = window->x11.colormap;
|
||||
wa.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask |
|
||||
PointerMotionMask | ButtonPressMask | ButtonReleaseMask |
|
||||
ExposureMask | FocusChangeMask | VisibilityChangeMask |
|
||||
EnterWindowMask | LeaveWindowMask | PropertyChangeMask;
|
||||
|
||||
_glfwGrabErrorHandlerX11();
|
||||
|
||||
window->x11.parent = _glfw.x11.root;
|
||||
window->x11.handle = XCreateWindow(_glfw.x11.display,
|
||||
_glfw.x11.root,
|
||||
0, 0, // Position
|
||||
width, height,
|
||||
0, // Border width
|
||||
depth, // Color depth
|
||||
InputOutput,
|
||||
visual,
|
||||
CWBorderPixel | CWColormap | CWEventMask,
|
||||
&wa);
|
||||
|
||||
_glfwReleaseErrorHandlerX11();
|
||||
|
||||
if (!window->x11.handle)
|
||||
{
|
||||
XSetWindowAttributes wa;
|
||||
const unsigned long wamask = CWBorderPixel | CWColormap | CWEventMask;
|
||||
|
||||
wa.colormap = window->x11.colormap;
|
||||
wa.border_pixel = 0;
|
||||
wa.event_mask = StructureNotifyMask | KeyPressMask | KeyReleaseMask |
|
||||
PointerMotionMask | ButtonPressMask | ButtonReleaseMask |
|
||||
ExposureMask | FocusChangeMask | VisibilityChangeMask |
|
||||
EnterWindowMask | LeaveWindowMask | PropertyChangeMask;
|
||||
|
||||
_glfwGrabErrorHandlerX11();
|
||||
|
||||
window->x11.handle = XCreateWindow(_glfw.x11.display,
|
||||
_glfw.x11.root,
|
||||
0, 0,
|
||||
width, height,
|
||||
0, // Border width
|
||||
depth, // Color depth
|
||||
InputOutput,
|
||||
visual,
|
||||
wamask,
|
||||
&wa);
|
||||
|
||||
_glfwReleaseErrorHandlerX11();
|
||||
|
||||
if (!window->x11.handle)
|
||||
{
|
||||
_glfwInputErrorX11(GLFW_PLATFORM_ERROR,
|
||||
"X11: Failed to create window");
|
||||
return false;
|
||||
}
|
||||
|
||||
XSaveContext(_glfw.x11.display,
|
||||
window->x11.handle,
|
||||
_glfw.x11.context,
|
||||
(XPointer) window);
|
||||
_glfwInputErrorX11(GLFW_PLATFORM_ERROR,
|
||||
"X11: Failed to create window");
|
||||
return false;
|
||||
}
|
||||
|
||||
XSaveContext(_glfw.x11.display,
|
||||
window->x11.handle,
|
||||
_glfw.x11.context,
|
||||
(XPointer) window);
|
||||
|
||||
if (!wndconfig->decorated)
|
||||
_glfwPlatformSetWindowDecorated(window, false);
|
||||
|
||||
@@ -594,7 +607,7 @@ static bool createNativeWindow(_GLFWwindow* window,
|
||||
{
|
||||
XChangeProperty(_glfw.x11.display, window->x11.handle,
|
||||
_glfw.x11.NET_WM_STATE, XA_ATOM, 32,
|
||||
PropModeReplace, (unsigned char*) &states, count);
|
||||
PropModeReplace, (unsigned char*) states, count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1072,6 +1085,21 @@ static void onConfigChange(void)
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
get_atom_names(Atom *atoms, int count, char **atom_names) {
|
||||
_glfwGrabErrorHandlerX11();
|
||||
XGetAtomNames(_glfw.x11.display, atoms, count, atom_names);
|
||||
_glfwReleaseErrorHandlerX11();
|
||||
if (_glfw.x11.errorCode != Success) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
_glfwGrabErrorHandlerX11();
|
||||
atom_names[i] = XGetAtomName(_glfw.x11.display, atoms[i]);
|
||||
_glfwReleaseErrorHandlerX11();
|
||||
if (_glfw.x11.errorCode != Success) atom_names[i] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process the specified X event
|
||||
//
|
||||
static void processEvent(XEvent *event)
|
||||
@@ -1104,6 +1132,7 @@ static void processEvent(XEvent *event)
|
||||
_GLFWwindow* window = _glfw.x11.disabledCursorWindow;
|
||||
|
||||
if (window &&
|
||||
window->rawMouseMotion &&
|
||||
event->xcookie.extension == _glfw.x11.xi.majorOpcode &&
|
||||
XGetEventData(_glfw.x11.display, &event->xcookie) &&
|
||||
event->xcookie.evtype == XI_RawMotion)
|
||||
@@ -1148,8 +1177,11 @@ static void processEvent(XEvent *event)
|
||||
{
|
||||
XkbEvent *kb_event = (XkbEvent*)event;
|
||||
switch(kb_event->any.xkb_type) {
|
||||
case XkbNewKeyboardNotify:
|
||||
if(!glfw_xkb_update_x11_keyboard_id(&_glfw.x11.xkb)) return;
|
||||
case XkbNewKeyboardNotify: {
|
||||
int32_t old_id = _glfw.x11.xkb.keyboard_device_id;
|
||||
if (!glfw_xkb_update_x11_keyboard_id(&_glfw.x11.xkb)) return;
|
||||
if (old_id != _glfw.x11.xkb.keyboard_device_id) keymap_dirty = true;
|
||||
}
|
||||
/* fallthrough */
|
||||
case XkbMapNotify:
|
||||
{
|
||||
@@ -1182,6 +1214,12 @@ static void processEvent(XEvent *event)
|
||||
|
||||
switch (event->type)
|
||||
{
|
||||
case ReparentNotify:
|
||||
{
|
||||
window->x11.parent = event->xreparent.parent;
|
||||
return;
|
||||
}
|
||||
|
||||
case KeyPress:
|
||||
{
|
||||
UPDATE_KEYMAP_IF_NEEDED;
|
||||
@@ -1241,13 +1279,13 @@ static void processEvent(XEvent *event)
|
||||
|
||||
// Modern X provides scroll events as mouse button presses
|
||||
else if (event->xbutton.button == Button4)
|
||||
_glfwInputScroll(window, 0.0, 1.0, 0);
|
||||
_glfwInputScroll(window, 0.0, 1.0, 0, mods);
|
||||
else if (event->xbutton.button == Button5)
|
||||
_glfwInputScroll(window, 0.0, -1.0, 0);
|
||||
_glfwInputScroll(window, 0.0, -1.0, 0, mods);
|
||||
else if (event->xbutton.button == Button6)
|
||||
_glfwInputScroll(window, 1.0, 0.0, 0);
|
||||
_glfwInputScroll(window, 1.0, 0.0, 0, mods);
|
||||
else if (event->xbutton.button == Button7)
|
||||
_glfwInputScroll(window, -1.0, 0.0, 0);
|
||||
_glfwInputScroll(window, -1.0, 0.0, 0, mods);
|
||||
|
||||
else
|
||||
{
|
||||
@@ -1339,7 +1377,7 @@ static void processEvent(XEvent *event)
|
||||
{
|
||||
if (_glfw.x11.disabledCursorWindow != window)
|
||||
return;
|
||||
if (_glfw.x11.xi.available)
|
||||
if (window->rawMouseMotion)
|
||||
return;
|
||||
|
||||
const int dx = x - window->x11.lastCursorPosX;
|
||||
@@ -1375,20 +1413,36 @@ static void processEvent(XEvent *event)
|
||||
window->x11.height = event->xconfigure.height;
|
||||
}
|
||||
|
||||
if (event->xconfigure.x != window->x11.xpos ||
|
||||
event->xconfigure.y != window->x11.ypos)
|
||||
{
|
||||
if (event->xany.send_event)
|
||||
{
|
||||
_glfwInputWindowPos(window,
|
||||
event->xconfigure.x,
|
||||
event->xconfigure.y);
|
||||
int xpos = event->xconfigure.x;
|
||||
int ypos = event->xconfigure.y;
|
||||
|
||||
window->x11.xpos = event->xconfigure.x;
|
||||
window->x11.ypos = event->xconfigure.y;
|
||||
// NOTE: ConfigureNotify events from the server are in local
|
||||
// coordinates, so if we are reparented we need to translate
|
||||
// the position into root (screen) coordinates
|
||||
if (!event->xany.send_event && window->x11.parent != _glfw.x11.root)
|
||||
{
|
||||
Window dummy;
|
||||
_glfwGrabErrorHandlerX11();
|
||||
XTranslateCoordinates(_glfw.x11.display,
|
||||
window->x11.parent,
|
||||
_glfw.x11.root,
|
||||
xpos, ypos,
|
||||
&xpos, &ypos,
|
||||
&dummy);
|
||||
_glfwReleaseErrorHandlerX11();
|
||||
if (_glfw.x11.errorCode != Success) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to translate ConfigureNotiy co-ords for reparented window");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (xpos != window->x11.xpos || ypos != window->x11.ypos)
|
||||
{
|
||||
_glfwInputWindowPos(window, xpos, ypos);
|
||||
window->x11.xpos = xpos;
|
||||
window->x11.ypos = ypos;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1435,7 +1489,8 @@ static void processEvent(XEvent *event)
|
||||
|
||||
_glfw.x11.xdnd.source = event->xclient.data.l[0];
|
||||
_glfw.x11.xdnd.version = event->xclient.data.l[1] >> 24;
|
||||
_glfw.x11.xdnd.format = None;
|
||||
memset(_glfw.x11.xdnd.format, 0, sizeof(_glfw.x11.xdnd.format));
|
||||
_glfw.x11.xdnd.format_priority = 0;
|
||||
|
||||
if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION)
|
||||
return;
|
||||
@@ -1452,14 +1507,22 @@ static void processEvent(XEvent *event)
|
||||
count = 3;
|
||||
formats = (Atom*) event->xclient.data.l + 2;
|
||||
}
|
||||
char **atom_names = calloc(count, sizeof(char**));
|
||||
if (atom_names) {
|
||||
get_atom_names(formats, count, atom_names);
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
if (formats[i] == _glfw.x11.text_uri_list)
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
_glfw.x11.xdnd.format = _glfw.x11.text_uri_list;
|
||||
break;
|
||||
if (atom_names[i]) {
|
||||
int prio = _glfwInputDrop(window, atom_names[i], NULL, 0);
|
||||
if (prio > _glfw.x11.xdnd.format_priority) {
|
||||
_glfw.x11.xdnd.format_priority = prio;
|
||||
strncpy(_glfw.x11.xdnd.format, atom_names[i], arraysz(_glfw.x11.xdnd.format) - 1);
|
||||
}
|
||||
XFree(atom_names[i]);
|
||||
}
|
||||
}
|
||||
free(atom_names);
|
||||
}
|
||||
|
||||
if (list && formats)
|
||||
@@ -1473,7 +1536,7 @@ static void processEvent(XEvent *event)
|
||||
if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION)
|
||||
return;
|
||||
|
||||
if (_glfw.x11.xdnd.format)
|
||||
if (_glfw.x11.xdnd.format_priority > 0)
|
||||
{
|
||||
if (_glfw.x11.xdnd.version >= 1)
|
||||
time = event->xclient.data.l[2];
|
||||
@@ -1481,7 +1544,7 @@ static void processEvent(XEvent *event)
|
||||
// Request the chosen format from the source window
|
||||
XConvertSelection(_glfw.x11.display,
|
||||
_glfw.x11.XdndSelection,
|
||||
_glfw.x11.xdnd.format,
|
||||
XInternAtom(_glfw.x11.display, _glfw.x11.xdnd.format, 0),
|
||||
_glfw.x11.XdndSelection,
|
||||
window->x11.handle,
|
||||
time);
|
||||
@@ -1507,17 +1570,21 @@ static void processEvent(XEvent *event)
|
||||
const int xabs = (event->xclient.data.l[2] >> 16) & 0xffff;
|
||||
const int yabs = (event->xclient.data.l[2]) & 0xffff;
|
||||
Window dummy;
|
||||
int xpos, ypos;
|
||||
int xpos = 0, ypos = 0;
|
||||
|
||||
if (_glfw.x11.xdnd.version > _GLFW_XDND_VERSION)
|
||||
return;
|
||||
|
||||
_glfwGrabErrorHandlerX11();
|
||||
XTranslateCoordinates(_glfw.x11.display,
|
||||
_glfw.x11.root,
|
||||
window->x11.handle,
|
||||
xabs, yabs,
|
||||
&xpos, &ypos,
|
||||
&dummy);
|
||||
_glfwReleaseErrorHandlerX11();
|
||||
if (_glfw.x11.errorCode != Success)
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to get DND event position");
|
||||
|
||||
_glfwInputCursorPos(window, xpos, ypos);
|
||||
|
||||
@@ -1529,7 +1596,7 @@ static void processEvent(XEvent *event)
|
||||
reply.xclient.data.l[2] = 0; // Specify an empty rectangle
|
||||
reply.xclient.data.l[3] = 0;
|
||||
|
||||
if (_glfw.x11.xdnd.format)
|
||||
if (_glfw.x11.xdnd.format_priority > 0)
|
||||
{
|
||||
// Reply that we are ready to copy the dragged data
|
||||
reply.xclient.data.l[1] = 1; // Accept with no rectangle
|
||||
@@ -1559,14 +1626,7 @@ static void processEvent(XEvent *event)
|
||||
|
||||
if (result)
|
||||
{
|
||||
int i, count;
|
||||
char** paths = parseUriList(data, &count);
|
||||
|
||||
_glfwInputDrop(window, count, (const char**) paths);
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
free(paths[i]);
|
||||
free(paths);
|
||||
_glfwInputDrop(window, _glfw.x11.xdnd.format, data, result);
|
||||
}
|
||||
|
||||
if (data)
|
||||
@@ -1779,7 +1839,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
||||
const _GLFWctxconfig* ctxconfig,
|
||||
const _GLFWfbconfig* fbconfig)
|
||||
{
|
||||
Visual* visual;
|
||||
Visual* visual = NULL;
|
||||
int depth;
|
||||
|
||||
if (ctxconfig->client != GLFW_NO_API)
|
||||
@@ -1805,8 +1865,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
||||
}
|
||||
}
|
||||
|
||||
if (ctxconfig->client == GLFW_NO_API ||
|
||||
ctxconfig->source == GLFW_OSMESA_CONTEXT_API)
|
||||
if (!visual)
|
||||
{
|
||||
visual = DefaultVisual(_glfw.x11.display, _glfw.x11.screen);
|
||||
depth = DefaultDepth(_glfw.x11.display, _glfw.x11.screen);
|
||||
@@ -1952,10 +2011,14 @@ void _glfwPlatformSetWindowIcon(_GLFWwindow* window,
|
||||
void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos)
|
||||
{
|
||||
Window dummy;
|
||||
int x, y;
|
||||
int x = 0, y = 0;
|
||||
|
||||
_glfwGrabErrorHandlerX11();
|
||||
XTranslateCoordinates(_glfw.x11.display, window->x11.handle, _glfw.x11.root,
|
||||
0, 0, &x, &y, &dummy);
|
||||
_glfwReleaseErrorHandlerX11();
|
||||
if (_glfw.x11.errorCode != Success)
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to get window position");
|
||||
|
||||
if (xpos)
|
||||
*xpos = x;
|
||||
@@ -2034,6 +2097,14 @@ void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer UNUSED, in
|
||||
XFlush(_glfw.x11.display);
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window, int widthincr UNUSED, int heightincr UNUSED)
|
||||
{
|
||||
int width, height;
|
||||
_glfwPlatformGetWindowSize(window, &width, &height);
|
||||
updateNormalHints(window, width, height);
|
||||
XFlush(_glfw.x11.display);
|
||||
}
|
||||
|
||||
void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height)
|
||||
{
|
||||
_glfwPlatformGetWindowSize(window, width, height);
|
||||
@@ -2146,18 +2217,67 @@ void _glfwPlatformRestoreWindow(_GLFWwindow* window)
|
||||
|
||||
void _glfwPlatformMaximizeWindow(_GLFWwindow* window)
|
||||
{
|
||||
if (_glfw.x11.NET_WM_STATE &&
|
||||
_glfw.x11.NET_WM_STATE_MAXIMIZED_VERT &&
|
||||
_glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ)
|
||||
if (!_glfw.x11.NET_WM_STATE ||
|
||||
!_glfw.x11.NET_WM_STATE_MAXIMIZED_VERT ||
|
||||
!_glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_glfwPlatformWindowVisible(window))
|
||||
{
|
||||
sendEventToWM(window,
|
||||
_glfw.x11.NET_WM_STATE,
|
||||
_NET_WM_STATE_ADD,
|
||||
_glfw.x11.NET_WM_STATE_MAXIMIZED_VERT,
|
||||
_glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ,
|
||||
1, 0);
|
||||
XFlush(_glfw.x11.display);
|
||||
_glfw.x11.NET_WM_STATE,
|
||||
_NET_WM_STATE_ADD,
|
||||
_glfw.x11.NET_WM_STATE_MAXIMIZED_VERT,
|
||||
_glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ,
|
||||
1, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
Atom* states = NULL;
|
||||
unsigned long count =
|
||||
_glfwGetWindowPropertyX11(window->x11.handle,
|
||||
_glfw.x11.NET_WM_STATE,
|
||||
XA_ATOM,
|
||||
(unsigned char**) &states);
|
||||
|
||||
// NOTE: We don't check for failure as this property may not exist yet
|
||||
// and that's fine (and we'll create it implicitly with append)
|
||||
|
||||
Atom missing[2] =
|
||||
{
|
||||
_glfw.x11.NET_WM_STATE_MAXIMIZED_VERT,
|
||||
_glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ
|
||||
};
|
||||
unsigned long missingCount = 2;
|
||||
|
||||
for (unsigned long i = 0; i < count; i++)
|
||||
{
|
||||
for (unsigned long j = 0; j < missingCount; j++)
|
||||
{
|
||||
if (states[i] == missing[j])
|
||||
{
|
||||
missing[j] = missing[missingCount - 1];
|
||||
missingCount--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (states)
|
||||
XFree(states);
|
||||
|
||||
if (!missingCount)
|
||||
return;
|
||||
|
||||
XChangeProperty(_glfw.x11.display, window->x11.handle,
|
||||
_glfw.x11.NET_WM_STATE, XA_ATOM, 32,
|
||||
PropModeAppend,
|
||||
(unsigned char*) missing,
|
||||
missingCount);
|
||||
}
|
||||
|
||||
XFlush(_glfw.x11.display);
|
||||
}
|
||||
|
||||
void _glfwPlatformShowWindow(_GLFWwindow* window)
|
||||
@@ -2177,6 +2297,9 @@ void _glfwPlatformHideWindow(_GLFWwindow* window)
|
||||
|
||||
void _glfwPlatformRequestWindowAttention(_GLFWwindow* window)
|
||||
{
|
||||
if (!_glfw.x11.NET_WM_STATE || !_glfw.x11.NET_WM_STATE_DEMANDS_ATTENTION)
|
||||
return;
|
||||
|
||||
sendEventToWM(window,
|
||||
_glfw.x11.NET_WM_STATE,
|
||||
_NET_WM_STATE_ADD,
|
||||
@@ -2193,7 +2316,7 @@ void _glfwPlatformFocusWindow(_GLFWwindow* window)
|
||||
{
|
||||
if (_glfw.x11.NET_ACTIVE_WINDOW)
|
||||
sendEventToWM(window, _glfw.x11.NET_ACTIVE_WINDOW, 1, 0, 0, 0, 0);
|
||||
else
|
||||
else if (_glfwPlatformWindowVisible(window))
|
||||
{
|
||||
XRaiseWindow(_glfw.x11.display, window->x11.handle);
|
||||
XSetInputFocus(_glfw.x11.display, window->x11.handle,
|
||||
@@ -2294,6 +2417,7 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
|
||||
{
|
||||
return maximized;
|
||||
}
|
||||
|
||||
const unsigned long count =
|
||||
_glfwGetWindowPropertyX11(window->x11.handle,
|
||||
_glfw.x11.NET_WM_STATE,
|
||||
@@ -2382,7 +2506,7 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled)
|
||||
|
||||
if (_glfwPlatformWindowVisible(window))
|
||||
{
|
||||
const Atom action = enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
|
||||
const long action = enabled ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
|
||||
sendEventToWM(window,
|
||||
_glfw.x11.NET_WM_STATE,
|
||||
action,
|
||||
@@ -2391,15 +2515,16 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled)
|
||||
}
|
||||
else
|
||||
{
|
||||
Atom* states;
|
||||
Atom* states = NULL;
|
||||
unsigned long i, count;
|
||||
|
||||
count = _glfwGetWindowPropertyX11(window->x11.handle,
|
||||
_glfw.x11.NET_WM_STATE,
|
||||
XA_ATOM,
|
||||
(unsigned char**) &states);
|
||||
if (!states)
|
||||
return;
|
||||
|
||||
// NOTE: We don't check for failure as this property may not exist yet
|
||||
// and that's fine (and we'll create it implicitly with append)
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
@@ -2409,32 +2534,36 @@ void _glfwPlatformSetWindowFloating(_GLFWwindow* window, bool enabled)
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == count)
|
||||
{
|
||||
XChangeProperty(_glfw.x11.display, window->x11.handle,
|
||||
_glfw.x11.NET_WM_STATE, XA_ATOM, 32,
|
||||
PropModeAppend,
|
||||
(unsigned char*) &_glfw.x11.NET_WM_STATE_ABOVE,
|
||||
1);
|
||||
}
|
||||
if (i < count)
|
||||
return;
|
||||
|
||||
XChangeProperty(_glfw.x11.display, window->x11.handle,
|
||||
_glfw.x11.NET_WM_STATE, XA_ATOM, 32,
|
||||
PropModeAppend,
|
||||
(unsigned char*) &_glfw.x11.NET_WM_STATE_ABOVE,
|
||||
1);
|
||||
}
|
||||
else
|
||||
else if (states)
|
||||
{
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
if (states[i] == _glfw.x11.NET_WM_STATE_ABOVE)
|
||||
{
|
||||
states[i] = states[count - 1];
|
||||
count--;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == count)
|
||||
return;
|
||||
|
||||
states[i] = states[count - 1];
|
||||
count--;
|
||||
|
||||
XChangeProperty(_glfw.x11.display, window->x11.handle,
|
||||
_glfw.x11.NET_WM_STATE, XA_ATOM, 32,
|
||||
PropModeReplace, (unsigned char*) &states, count);
|
||||
PropModeReplace, (unsigned char*) states, count);
|
||||
}
|
||||
|
||||
XFree(states);
|
||||
if (states)
|
||||
XFree(states);
|
||||
}
|
||||
|
||||
XFlush(_glfw.x11.display);
|
||||
@@ -2515,6 +2644,25 @@ _glfwDispatchX11Events(void) {
|
||||
return dispatched;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetRawMouseMotion(_GLFWwindow *window, bool enabled)
|
||||
{
|
||||
if (!_glfw.x11.xi.available)
|
||||
return;
|
||||
|
||||
if (_glfw.x11.disabledCursorWindow != window)
|
||||
return;
|
||||
|
||||
if (enabled)
|
||||
enableRawMouseMotion(window);
|
||||
else
|
||||
disableRawMouseMotion(window);
|
||||
}
|
||||
|
||||
bool _glfwPlatformRawMouseMotionSupported(void)
|
||||
{
|
||||
return _glfw.x11.xi.available;
|
||||
}
|
||||
|
||||
void _glfwPlatformPollEvents(void)
|
||||
{
|
||||
_glfwDispatchX11Events();
|
||||
@@ -2608,7 +2756,7 @@ int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, GLFWCursorShape shape
|
||||
int native = 0;
|
||||
#define C(name, val) case name: native = val; break;
|
||||
switch(shape) {
|
||||
C(GLFW_ARROW_CURSOR, XC_arrow);
|
||||
C(GLFW_ARROW_CURSOR, XC_left_ptr);
|
||||
C(GLFW_IBEAM_CURSOR, XC_xterm);
|
||||
C(GLFW_CROSSHAIR_CURSOR, XC_crosshair);
|
||||
C(GLFW_HAND_CURSOR, XC_hand2);
|
||||
@@ -2651,8 +2799,9 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor UNUSED)
|
||||
|
||||
void _glfwPlatformSetClipboardString(const char* string)
|
||||
{
|
||||
char* copy = _glfw_strdup(string);
|
||||
free(_glfw.x11.clipboardString);
|
||||
_glfw.x11.clipboardString = _glfw_strdup(string);
|
||||
_glfw.x11.clipboardString = copy;
|
||||
|
||||
XSetSelectionOwner(_glfw.x11.display,
|
||||
_glfw.x11.CLIPBOARD,
|
||||
|
||||
4
glfw/xkb_glfw.c
vendored
4
glfw/xkb_glfw.c
vendored
@@ -61,6 +61,7 @@ START_ALLOW_CASE_RANGE
|
||||
S(bracketleft, LEFT_BRACKET); \
|
||||
S(backslash, BACKSLASH); \
|
||||
S(bracketright, RIGHT_BRACKET); \
|
||||
S(asciicircum, CIRCUMFLEX); \
|
||||
S(underscore, UNDERSCORE); \
|
||||
S(grave, GRAVE_ACCENT); \
|
||||
R(a, z, A, Z); \
|
||||
@@ -619,6 +620,9 @@ glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t
|
||||
}
|
||||
if (key_text[0]) { debug("%s: %s ", text_type, key_text); }
|
||||
}
|
||||
if (xkb_sym == XKB_KEY_ISO_First_Group || xkb_sym == XKB_KEY_ISO_Last_Group || xkb_sym == XKB_KEY_ISO_Next_Group || xkb_sym == XKB_KEY_ISO_Prev_Group || xkb_sym == XKB_KEY_Mode_switch) {
|
||||
return;
|
||||
}
|
||||
int glfw_sym = glfw_key_for_sym(xkb_sym);
|
||||
bool is_fallback = false;
|
||||
if (glfw_sym == GLFW_KEY_UNKNOWN && !key_text[0]) {
|
||||
|
||||
@@ -3,28 +3,36 @@
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import sys
|
||||
from contextlib import suppress
|
||||
from typing import TYPE_CHECKING, List, Optional, Tuple
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import AskCLIOptions
|
||||
from kitty.constants import cache_dir
|
||||
from kitty.typing import BossType
|
||||
|
||||
from ..tui.operations import alternate_screen, styled
|
||||
from ..tui.handler import result_handler
|
||||
from ..tui.operations import alternate_screen, set_cursor_visible, styled
|
||||
|
||||
readline = None
|
||||
if TYPE_CHECKING:
|
||||
import readline
|
||||
else:
|
||||
readline = None
|
||||
|
||||
|
||||
def get_history_items():
|
||||
def get_history_items() -> List[str]:
|
||||
return list(map(readline.get_history_item, range(1, readline.get_current_history_length() + 1)))
|
||||
|
||||
|
||||
def sort_key(item):
|
||||
def sort_key(item: str) -> Tuple[int, str]:
|
||||
return len(item), item.lower()
|
||||
|
||||
|
||||
class HistoryCompleter:
|
||||
|
||||
def __init__(self, name=None):
|
||||
self.matches = []
|
||||
def __init__(self, name: Optional[str] = None):
|
||||
self.matches: List[str] = []
|
||||
self.history_path = None
|
||||
if name:
|
||||
ddir = os.path.join(cache_dir(), 'ask')
|
||||
@@ -32,7 +40,7 @@ class HistoryCompleter:
|
||||
os.makedirs(ddir)
|
||||
self.history_path = os.path.join(ddir, name)
|
||||
|
||||
def complete(self, text, state):
|
||||
def complete(self, text: str, state: int) -> Optional[str]:
|
||||
response = None
|
||||
if state == 0:
|
||||
history_values = get_history_items()
|
||||
@@ -47,22 +55,22 @@ class HistoryCompleter:
|
||||
response = None
|
||||
return response
|
||||
|
||||
def __enter__(self):
|
||||
def __enter__(self) -> 'HistoryCompleter':
|
||||
if self.history_path:
|
||||
with suppress(Exception):
|
||||
readline.read_history_file(self.history_path)
|
||||
readline.set_completer(self.complete)
|
||||
return self
|
||||
|
||||
def __exit__(self, *a):
|
||||
def __exit__(self, *a: object) -> None:
|
||||
if self.history_path:
|
||||
readline.write_history_file(self.history_path)
|
||||
|
||||
|
||||
def option_text():
|
||||
def option_text() -> str:
|
||||
return '''\
|
||||
--type -t
|
||||
choices=line
|
||||
choices=line,yesno
|
||||
default=line
|
||||
Type of input. Defaults to asking for a line of text.
|
||||
|
||||
@@ -78,44 +86,77 @@ be used for completions and via the browse history readline bindings.
|
||||
'''
|
||||
|
||||
|
||||
def main(args):
|
||||
try:
|
||||
from typing import TypedDict
|
||||
except ImportError:
|
||||
TypedDict = dict
|
||||
|
||||
|
||||
class Response(TypedDict):
|
||||
items: List[str]
|
||||
response: Optional[str]
|
||||
|
||||
|
||||
def yesno(cli_opts: AskCLIOptions, items: List[str]) -> Response:
|
||||
import tty
|
||||
with alternate_screen():
|
||||
if cli_opts.message:
|
||||
print(styled(cli_opts.message, bold=True))
|
||||
print()
|
||||
print(' ', styled('Y', fg='green') + 'es', ' ', styled('N', fg='red') + 'o', set_cursor_visible(False))
|
||||
sys.stdout.flush()
|
||||
tty.setraw(sys.stdin.fileno())
|
||||
try:
|
||||
response = sys.stdin.buffer.read(1)
|
||||
yes = response in (b'y', b'Y', b'\r', b'\n' b' ')
|
||||
return {'items': items, 'response': 'y' if yes else 'n'}
|
||||
finally:
|
||||
sys.stdout.write(set_cursor_visible(True))
|
||||
tty.setcbreak(sys.stdin.fileno())
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def main(args: List[str]) -> Response:
|
||||
# For some reason importing readline in a key handler in the main kitty process
|
||||
# causes a crash of the python interpreter, probably because of some global
|
||||
# lock
|
||||
global readline
|
||||
import readline as rl
|
||||
readline = rl
|
||||
from kitty.shell import init_readline
|
||||
msg = 'Ask the user for input'
|
||||
try:
|
||||
args, items = parse_args(args[1:], option_text, '', msg, 'kitty ask')
|
||||
cli_opts, items = parse_args(args[1:], option_text, '', msg, 'kitty ask', result_class=AskCLIOptions)
|
||||
except SystemExit as e:
|
||||
if e.code != 0:
|
||||
print(e.args[0])
|
||||
input('Press enter to quit...')
|
||||
raise SystemExit(e.code)
|
||||
|
||||
init_readline(readline)
|
||||
ans = {'items': items}
|
||||
if cli_opts.type == 'yesno':
|
||||
return yesno(cli_opts, items)
|
||||
|
||||
with alternate_screen(), HistoryCompleter(args.name):
|
||||
if args.message:
|
||||
print(styled(args.message, bold=True))
|
||||
import readline as rl
|
||||
readline = rl
|
||||
from kitty.shell import init_readline
|
||||
init_readline(readline)
|
||||
response = None
|
||||
|
||||
with alternate_screen(), HistoryCompleter(cli_opts.name):
|
||||
if cli_opts.message:
|
||||
print(styled(cli_opts.message, bold=True))
|
||||
|
||||
prompt = '> '
|
||||
with suppress(KeyboardInterrupt, EOFError):
|
||||
ans['response'] = input(prompt)
|
||||
return ans
|
||||
response = input(prompt)
|
||||
return {'items': items, 'response': response}
|
||||
|
||||
|
||||
def handle_result(args, data, target_window_id, boss):
|
||||
if 'response' in data:
|
||||
@result_handler()
|
||||
def handle_result(args: List[str], data: Response, target_window_id: int, boss: BossType) -> None:
|
||||
if data['response'] is not None:
|
||||
func, *args = data['items']
|
||||
getattr(boss, func)(data['response'], *args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
ans = main(sys.argv)
|
||||
if ans:
|
||||
print(ans)
|
||||
|
||||
@@ -3,39 +3,40 @@
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import sys
|
||||
from typing import Iterable, List, Union
|
||||
|
||||
from kitty.key_encoding import KeyEvent
|
||||
|
||||
from . import subseq_matcher
|
||||
from ..tui.handler import Handler
|
||||
from ..tui.loop import Loop
|
||||
|
||||
from . import subseq_matcher
|
||||
|
||||
|
||||
def match(
|
||||
input_data,
|
||||
query,
|
||||
threads=0,
|
||||
positions=False,
|
||||
level1='/',
|
||||
level2='-_0123456789',
|
||||
level3='.',
|
||||
limit=0,
|
||||
mark_before='',
|
||||
mark_after='',
|
||||
delimiter='\n'
|
||||
):
|
||||
input_data: Union[str, bytes, Iterable[Union[str, bytes]]],
|
||||
query: str,
|
||||
threads: int = 0,
|
||||
positions: bool = False,
|
||||
level1: str = '/',
|
||||
level2: str = '-_0123456789',
|
||||
level3: str = '.',
|
||||
limit: int = 0,
|
||||
mark_before: str = '',
|
||||
mark_after: str = '',
|
||||
delimiter: str = '\n'
|
||||
) -> List[str]:
|
||||
if isinstance(input_data, str):
|
||||
input_data = input_data.encode('utf-8')
|
||||
if isinstance(input_data, bytes):
|
||||
input_data = input_data.split(delimiter.encode('utf-8'))
|
||||
idata = [x.encode('utf-8') for x in input_data.split(delimiter)]
|
||||
elif isinstance(input_data, bytes):
|
||||
idata = input_data.split(delimiter.encode('utf-8'))
|
||||
else:
|
||||
input_data = [x.encode('utf-8') if isinstance(x, str) else x for x in input_data]
|
||||
idata = [x.encode('utf-8') if isinstance(x, str) else x for x in input_data]
|
||||
query = query.lower()
|
||||
level1 = level1.lower()
|
||||
level2 = level2.lower()
|
||||
level3 = level3.lower()
|
||||
data = subseq_matcher.match(
|
||||
input_data, (level1, level2, level3), query,
|
||||
idata, (level1, level2, level3), query,
|
||||
positions, limit, threads,
|
||||
mark_before, mark_after, delimiter)
|
||||
if data is None:
|
||||
@@ -45,23 +46,23 @@ def match(
|
||||
|
||||
class ChooseHandler(Handler):
|
||||
|
||||
def initialize(self):
|
||||
def initialize(self) -> None:
|
||||
pass
|
||||
|
||||
def on_text(self, text, in_bracketed_paste=False):
|
||||
def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
|
||||
pass
|
||||
|
||||
def on_key(self, key_event):
|
||||
def on_key(self, key_event: KeyEvent) -> None:
|
||||
pass
|
||||
|
||||
def on_interrupt(self):
|
||||
def on_interrupt(self) -> None:
|
||||
self.quit_loop(1)
|
||||
|
||||
def on_eot(self):
|
||||
def on_eot(self) -> None:
|
||||
self.quit_loop(1)
|
||||
|
||||
|
||||
def main(args):
|
||||
def main(args: List[str]) -> None:
|
||||
loop = Loop()
|
||||
handler = ChooseHandler()
|
||||
loop.loop(handler)
|
||||
|
||||
9
kittens/choose/subseq_matcher.pyi
Normal file
9
kittens/choose/subseq_matcher.pyi
Normal file
@@ -0,0 +1,9 @@
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
|
||||
def match(
|
||||
lines: List[bytes], levels: Tuple[str, str, str], needle: str,
|
||||
output_positions: bool, limit: int, num_threads: int, mark_before: str,
|
||||
mark_after: str, delimiter: str
|
||||
) -> Optional[str]:
|
||||
pass
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import List, NoReturn, Optional
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import ClipboardCLIOptions
|
||||
|
||||
from ..tui.handler import Handler
|
||||
from ..tui.loop import Loop
|
||||
@@ -13,12 +15,12 @@ from ..tui.loop import Loop
|
||||
|
||||
class Clipboard(Handler):
|
||||
|
||||
def __init__(self, data_to_send, args):
|
||||
def __init__(self, data_to_send: Optional[bytes], args: ClipboardCLIOptions):
|
||||
self.args = args
|
||||
self.clipboard_contents = None
|
||||
self.clipboard_contents: Optional[str] = None
|
||||
self.data_to_send = data_to_send
|
||||
|
||||
def initialize(self):
|
||||
def initialize(self) -> None:
|
||||
if self.data_to_send is not None:
|
||||
self.cmd.write_to_clipboard(self.data_to_send, self.args.use_primary)
|
||||
if not self.args.get_clipboard:
|
||||
@@ -32,17 +34,17 @@ class Clipboard(Handler):
|
||||
return
|
||||
self.cmd.request_from_clipboard(self.args.use_primary)
|
||||
|
||||
def on_clipboard_response(self, text, from_primary=False):
|
||||
def on_clipboard_response(self, text: str, from_primary: bool = False) -> None:
|
||||
self.clipboard_contents = text
|
||||
self.quit_loop(0)
|
||||
|
||||
def on_capability_response(self, name, val):
|
||||
def on_capability_response(self, name: str, val: str) -> None:
|
||||
self.quit_loop(0)
|
||||
|
||||
def on_interrupt(self):
|
||||
def on_interrupt(self) -> None:
|
||||
self.quit_loop(1)
|
||||
|
||||
def on_eot(self):
|
||||
def on_eot(self) -> None:
|
||||
self.quit_loop(1)
|
||||
|
||||
|
||||
@@ -80,16 +82,16 @@ To set the clipboard text, pipe in the new text on stdin. Use the
|
||||
usage = ''
|
||||
|
||||
|
||||
def main(args):
|
||||
args, items = parse_args(args[1:], OPTIONS, usage, help_text, 'kitty +kitten clipboard')
|
||||
def main(args: List[str]) -> NoReturn:
|
||||
cli_opts, items = parse_args(args[1:], OPTIONS, usage, help_text, 'kitty +kitten clipboard', result_class=ClipboardCLIOptions)
|
||||
if items:
|
||||
raise SystemExit('Unrecognized extra command line arguments')
|
||||
data = None
|
||||
data: Optional[bytes] = None
|
||||
if not sys.stdin.isatty():
|
||||
data = sys.stdin.buffer.read()
|
||||
sys.stdin = open(os.ctermid(), 'r')
|
||||
loop = Loop()
|
||||
handler = Clipboard(data, args)
|
||||
handler = Clipboard(data, cli_opts)
|
||||
loop.loop(handler)
|
||||
if loop.return_code == 0 and handler.clipboard_contents:
|
||||
sys.stdout.write(handler.clipboard_contents)
|
||||
@@ -100,6 +102,7 @@ def main(args):
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
||||
elif __name__ == '__doc__':
|
||||
sys.cli_docs['usage'] = usage
|
||||
sys.cli_docs['options'] = OPTIONS
|
||||
sys.cli_docs['help_text'] = help_text
|
||||
cd = sys.cli_docs # type: ignore
|
||||
cd['usage'] = usage
|
||||
cd['options'] = OPTIONS
|
||||
cd['help_text'] = help_text
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
class GlobalData:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.title = ''
|
||||
self.cmd = ''
|
||||
|
||||
|
||||
global_data = GlobalData
|
||||
|
||||
@@ -4,86 +4,94 @@
|
||||
|
||||
import os
|
||||
import re
|
||||
from contextlib import suppress
|
||||
from functools import lru_cache
|
||||
from hashlib import md5
|
||||
from mimetypes import guess_type
|
||||
from contextlib import suppress
|
||||
from typing import TYPE_CHECKING, Dict, List, Set, Optional, Iterator, Tuple, Union
|
||||
|
||||
path_name_map = {}
|
||||
if TYPE_CHECKING:
|
||||
from .highlight import DiffHighlight # noqa
|
||||
|
||||
|
||||
path_name_map: Dict[str, str] = {}
|
||||
|
||||
|
||||
class Segment:
|
||||
|
||||
__slots__ = ('start', 'end', 'start_code', 'end_code')
|
||||
|
||||
def __init__(self, start, start_code):
|
||||
def __init__(self, start: int, start_code: str):
|
||||
self.start = start
|
||||
self.start_code = start_code
|
||||
self.end: Optional[int] = None
|
||||
self.end_code: Optional[str] = None
|
||||
|
||||
def __repr__(self):
|
||||
def __repr__(self) -> str:
|
||||
return 'Segment(start={!r}, start_code={!r}, end={!r}, end_code={!r})'.format(
|
||||
self.start, self.start_code, getattr(self, 'end', None), getattr(self, 'end_code', None)
|
||||
)
|
||||
self.start, self.start_code, self.end, self.end_code)
|
||||
|
||||
|
||||
class Collection:
|
||||
|
||||
def __init__(self):
|
||||
self.changes = {}
|
||||
self.renames = {}
|
||||
self.adds = set()
|
||||
self.removes = set()
|
||||
self.all_paths = []
|
||||
self.type_map = {}
|
||||
def __init__(self) -> None:
|
||||
self.changes: Dict[str, str] = {}
|
||||
self.renames: Dict[str, str] = {}
|
||||
self.adds: Set[str] = set()
|
||||
self.removes: Set[str] = set()
|
||||
self.all_paths: List[str] = []
|
||||
self.type_map: Dict[str, str] = {}
|
||||
self.added_count = self.removed_count = 0
|
||||
|
||||
def add_change(self, left_path, right_path):
|
||||
def add_change(self, left_path: str, right_path: str) -> None:
|
||||
self.changes[left_path] = right_path
|
||||
self.all_paths.append(left_path)
|
||||
self.type_map[left_path] = 'diff'
|
||||
|
||||
def add_rename(self, left_path, right_path):
|
||||
def add_rename(self, left_path: str, right_path: str) -> None:
|
||||
self.renames[left_path] = right_path
|
||||
self.all_paths.append(left_path)
|
||||
self.type_map[left_path] = 'rename'
|
||||
|
||||
def add_add(self, right_path):
|
||||
def add_add(self, right_path: str) -> None:
|
||||
self.adds.add(right_path)
|
||||
self.all_paths.append(right_path)
|
||||
self.type_map[right_path] = 'add'
|
||||
if isinstance(data_for_path(right_path), str):
|
||||
self.added_count += len(lines_for_path(right_path))
|
||||
|
||||
def add_removal(self, left_path):
|
||||
def add_removal(self, left_path: str) -> None:
|
||||
self.removes.add(left_path)
|
||||
self.all_paths.append(left_path)
|
||||
self.type_map[left_path] = 'removal'
|
||||
if isinstance(data_for_path(left_path), str):
|
||||
self.removed_count += len(lines_for_path(left_path))
|
||||
|
||||
def finalize(self):
|
||||
def finalize(self) -> None:
|
||||
self.all_paths.sort(key=path_name_map.get)
|
||||
|
||||
def __iter__(self):
|
||||
def __iter__(self) -> Iterator[Tuple[str, str, Optional[str]]]:
|
||||
for path in self.all_paths:
|
||||
typ = self.type_map[path]
|
||||
if typ == 'diff':
|
||||
data = self.changes[path]
|
||||
data: Optional[str] = self.changes[path]
|
||||
elif typ == 'rename':
|
||||
data = self.renames[path]
|
||||
else:
|
||||
data = None
|
||||
yield path, typ, data
|
||||
|
||||
def __len__(self):
|
||||
def __len__(self) -> int:
|
||||
return len(self.all_paths)
|
||||
|
||||
|
||||
def collect_files(collection, left, right):
|
||||
left_names, right_names = set(), set()
|
||||
left_path_map, right_path_map = {}, {}
|
||||
def collect_files(collection: Collection, left: str, right: str) -> None:
|
||||
left_names: Set[str] = set()
|
||||
right_names: Set[str] = set()
|
||||
left_path_map: Dict[str, str] = {}
|
||||
right_path_map: Dict[str, str] = {}
|
||||
|
||||
def walk(base, names, pmap):
|
||||
def walk(base: str, names: Set[str], pmap: Dict[str, str]) -> None:
|
||||
for dirpath, dirnames, filenames in os.walk(base):
|
||||
for filename in filenames:
|
||||
path = os.path.abspath(os.path.join(dirpath, filename))
|
||||
@@ -91,7 +99,8 @@ def collect_files(collection, left, right):
|
||||
names.add(name)
|
||||
pmap[name] = path
|
||||
|
||||
walk(left, left_names, left_path_map), walk(right, right_names, right_path_map)
|
||||
walk(left, left_names, left_path_map)
|
||||
walk(right, right_names, right_path_map)
|
||||
common_names = left_names & right_names
|
||||
changed_names = {n for n in common_names if data_for_path(left_path_map[n]) != data_for_path(right_path_map[n])}
|
||||
for n in changed_names:
|
||||
@@ -117,50 +126,56 @@ def collect_files(collection, left, right):
|
||||
sanitize_pat = re.compile('[\x00-\x09\x0b-\x1f\x7f\x80-\x9f]')
|
||||
|
||||
|
||||
def sanitize(text):
|
||||
def sanitize(text: str) -> str:
|
||||
ntext = text.replace('\r\n', '⏎\n')
|
||||
return sanitize_pat.sub('░', ntext)
|
||||
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def mime_type_for_path(path):
|
||||
def mime_type_for_path(path: str) -> str:
|
||||
return guess_type(path)[0] or 'application/octet-stream'
|
||||
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def raw_data_for_path(path):
|
||||
def raw_data_for_path(path: str) -> bytes:
|
||||
with open(path, 'rb') as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def is_image(path):
|
||||
return mime_type_for_path(path).startswith('image/')
|
||||
def is_image(path: Optional[str]) -> bool:
|
||||
return mime_type_for_path(path).startswith('image/') if path else False
|
||||
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def data_for_path(path):
|
||||
ans = raw_data_for_path(path)
|
||||
def data_for_path(path: str) -> Union[str, bytes]:
|
||||
raw_bytes = raw_data_for_path(path)
|
||||
if not is_image(path) and not os.path.samefile(path, os.devnull):
|
||||
with suppress(UnicodeDecodeError):
|
||||
ans = ans.decode('utf-8')
|
||||
return ans
|
||||
return raw_bytes.decode('utf-8')
|
||||
return raw_bytes
|
||||
|
||||
|
||||
class LinesForPath:
|
||||
|
||||
replace_tab_by = ' ' * 4
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def __call__(self, path: str) -> Tuple[str, ...]:
|
||||
data = data_for_path(path)
|
||||
assert isinstance(data, str)
|
||||
data = data.replace('\t', self.replace_tab_by)
|
||||
return tuple(sanitize(data).splitlines())
|
||||
|
||||
|
||||
lines_for_path = LinesForPath()
|
||||
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def lines_for_path(path):
|
||||
data = data_for_path(path).replace('\t', lines_for_path.replace_tab_by)
|
||||
return tuple(sanitize(data).splitlines())
|
||||
|
||||
|
||||
lines_for_path.replace_tab_by = ' ' * 4
|
||||
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def hash_for_path(path):
|
||||
def hash_for_path(path: str) -> bytes:
|
||||
return md5(raw_data_for_path(path)).digest()
|
||||
|
||||
|
||||
def create_collection(left, right):
|
||||
def create_collection(left: str, right: str) -> Collection:
|
||||
collection = Collection()
|
||||
if os.path.isdir(left):
|
||||
collect_files(collection, left, right)
|
||||
@@ -173,13 +188,13 @@ def create_collection(left, right):
|
||||
return collection
|
||||
|
||||
|
||||
highlight_data = {}
|
||||
highlight_data: Dict[str, 'DiffHighlight'] = {}
|
||||
|
||||
|
||||
def set_highlight_data(data):
|
||||
def set_highlight_data(data: Dict[str, 'DiffHighlight']) -> None:
|
||||
global highlight_data
|
||||
highlight_data = data
|
||||
|
||||
|
||||
def highlights_for_path(path):
|
||||
def highlights_for_path(path: str) -> 'DiffHighlight':
|
||||
return highlight_data.get(path, [])
|
||||
|
||||
@@ -3,27 +3,30 @@
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
from typing import Any, Dict, Iterable, Optional, Tuple, Type, Union
|
||||
|
||||
from kitty.conf.utils import (
|
||||
init_config as _init_config, key_func, load_config as _load_config, merge_dicts,
|
||||
parse_config_base, parse_kittens_key, resolve_config
|
||||
)
|
||||
from kitty.conf.definition import config_lines
|
||||
from kitty.conf.utils import (
|
||||
init_config as _init_config, key_func, load_config as _load_config,
|
||||
merge_dicts, parse_config_base, parse_kittens_key, resolve_config
|
||||
)
|
||||
from kitty.constants import config_dir
|
||||
from kitty.options_stub import DiffOptions
|
||||
from kitty.cli_stub import DiffCLIOptions
|
||||
from kitty.rgb import color_as_sgr
|
||||
|
||||
from .config_data import type_map, all_options
|
||||
from .config_data import all_options, type_convert
|
||||
|
||||
defaults = None
|
||||
defaults: Optional[DiffOptions] = None
|
||||
|
||||
formats = {
|
||||
formats: Dict[str, str] = {
|
||||
'title': '',
|
||||
'margin': '',
|
||||
'text': '',
|
||||
}
|
||||
|
||||
|
||||
def set_formats(opts):
|
||||
def set_formats(opts: DiffOptions) -> None:
|
||||
formats['text'] = '48' + color_as_sgr(opts.background)
|
||||
formats['title'] = '38' + color_as_sgr(opts.title_fg) + ';48' + color_as_sgr(opts.title_bg) + ';1'
|
||||
formats['margin'] = '38' + color_as_sgr(opts.margin_fg) + ';48' + color_as_sgr(opts.margin_bg)
|
||||
@@ -32,6 +35,7 @@ def set_formats(opts):
|
||||
formats['added'] = '48' + color_as_sgr(opts.added_bg)
|
||||
formats['removed'] = '48' + color_as_sgr(opts.removed_bg)
|
||||
formats['filler'] = '48' + color_as_sgr(opts.filler_bg)
|
||||
formats['margin_filler'] = '48' + color_as_sgr(opts.margin_filler_bg or opts.filler_bg)
|
||||
formats['hunk_margin'] = '38' + color_as_sgr(opts.margin_fg) + ';48' + color_as_sgr(opts.hunk_margin_bg)
|
||||
formats['hunk'] = '38' + color_as_sgr(opts.margin_fg) + ';48' + color_as_sgr(opts.hunk_bg)
|
||||
formats['removed_highlight'] = '48' + color_as_sgr(opts.highlight_removed_bg)
|
||||
@@ -42,7 +46,7 @@ func_with_args, args_funcs = key_func()
|
||||
|
||||
|
||||
@func_with_args('scroll_by')
|
||||
def parse_scroll_by(func, rest):
|
||||
def parse_scroll_by(func: str, rest: str) -> Tuple[str, int]:
|
||||
try:
|
||||
return func, int(rest)
|
||||
except Exception:
|
||||
@@ -50,7 +54,7 @@ def parse_scroll_by(func, rest):
|
||||
|
||||
|
||||
@func_with_args('scroll_to')
|
||||
def parse_scroll_to(func, rest):
|
||||
def parse_scroll_to(func: str, rest: str) -> Tuple[str, str]:
|
||||
rest = rest.lower()
|
||||
if rest not in {'start', 'end', 'next-change', 'prev-change', 'next-page', 'prev-page', 'next-match', 'prev-match'}:
|
||||
rest = 'start'
|
||||
@@ -58,7 +62,7 @@ def parse_scroll_to(func, rest):
|
||||
|
||||
|
||||
@func_with_args('change_context')
|
||||
def parse_change_context(func, rest):
|
||||
def parse_change_context(func: str, rest: str) -> Tuple[str, Union[int, str]]:
|
||||
rest = rest.lower()
|
||||
if rest in {'all', 'default'}:
|
||||
return func, rest
|
||||
@@ -70,26 +74,29 @@ def parse_change_context(func, rest):
|
||||
|
||||
|
||||
@func_with_args('start_search')
|
||||
def parse_start_search(func, rest):
|
||||
rest = rest.lower().split()
|
||||
is_regex = rest and rest[0] == 'regex'
|
||||
is_backward = len(rest) > 1 and rest[1] == 'backward'
|
||||
def parse_start_search(func: str, rest: str) -> Tuple[str, Tuple[bool, bool]]:
|
||||
rest_ = rest.lower().split()
|
||||
is_regex = bool(rest_ and rest_[0] == 'regex')
|
||||
is_backward = bool(len(rest_) > 1 and rest_[1] == 'backward')
|
||||
return func, (is_regex, is_backward)
|
||||
|
||||
|
||||
def special_handling(key, val, ans):
|
||||
def special_handling(key: str, val: str, ans: Dict) -> bool:
|
||||
if key == 'map':
|
||||
action, *key_def = parse_kittens_key(val, args_funcs)
|
||||
ans['key_definitions'][tuple(key_def)] = action
|
||||
return True
|
||||
x = parse_kittens_key(val, args_funcs)
|
||||
if x is not None:
|
||||
action, key_def = x
|
||||
ans['key_definitions'][key_def] = action
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def parse_config(lines, check_keys=True):
|
||||
ans = {'key_definitions': {}}
|
||||
def parse_config(lines: Iterable[str], check_keys: bool = True) -> Dict[str, Any]:
|
||||
ans: Dict[str, Any] = {'key_definitions': {}}
|
||||
parse_config_base(
|
||||
lines,
|
||||
defaults,
|
||||
type_map,
|
||||
type_convert,
|
||||
special_handling,
|
||||
ans,
|
||||
check_keys=check_keys
|
||||
@@ -97,7 +104,7 @@ def parse_config(lines, check_keys=True):
|
||||
return ans
|
||||
|
||||
|
||||
def merge_configs(defaults, vals):
|
||||
def merge_configs(defaults: Dict, vals: Dict) -> Dict:
|
||||
ans = {}
|
||||
for k, v in defaults.items():
|
||||
if isinstance(v, dict):
|
||||
@@ -108,14 +115,16 @@ def merge_configs(defaults, vals):
|
||||
return ans
|
||||
|
||||
|
||||
def parse_defaults(lines, check_keys=False):
|
||||
def parse_defaults(lines: Iterable[str], check_keys: bool = False) -> Dict[str, Any]:
|
||||
return parse_config(lines, check_keys)
|
||||
|
||||
|
||||
Options, defaults = _init_config(config_lines(all_options), parse_defaults)
|
||||
x = _init_config(config_lines(all_options), parse_defaults)
|
||||
Options: Type[DiffOptions] = x[0]
|
||||
defaults = x[1]
|
||||
|
||||
|
||||
def load_config(*paths, overrides=None):
|
||||
def load_config(*paths: str, overrides: Optional[Iterable[str]] = None) -> DiffOptions:
|
||||
return _load_config(Options, defaults, parse_config, merge_configs, *paths, overrides=overrides)
|
||||
|
||||
|
||||
@@ -123,7 +132,7 @@ SYSTEM_CONF = '/etc/xdg/kitty/diff.conf'
|
||||
defconf = os.path.join(config_dir, 'diff.conf')
|
||||
|
||||
|
||||
def init_config(args):
|
||||
def init_config(args: DiffCLIOptions) -> DiffOptions:
|
||||
config = tuple(resolve_config(SYSTEM_CONF, defconf, args.config))
|
||||
overrides = (a.replace('=', ' ', 1) for a in args.override or ())
|
||||
opts = load_config(*config, overrides=overrides)
|
||||
|
||||
@@ -4,17 +4,18 @@
|
||||
|
||||
|
||||
# Utils {{{
|
||||
from gettext import gettext as _
|
||||
from functools import partial
|
||||
from gettext import gettext as _
|
||||
from typing import Any, Dict, Sequence, Union
|
||||
|
||||
from kitty.conf.definition import option_func
|
||||
from kitty.conf.definition import Option, Shortcut, option_func
|
||||
from kitty.conf.utils import (
|
||||
positive_int, python_string, to_color
|
||||
positive_int, python_string, to_color, to_color_or_none
|
||||
)
|
||||
|
||||
# }}}
|
||||
|
||||
all_options = {}
|
||||
all_options: Dict[str, Union[Option, Sequence[Shortcut]]] = {}
|
||||
o, k, g, all_groups = option_func(all_options, {
|
||||
'colors': [_('Colors')],
|
||||
'diff': [_('Diffing'), ],
|
||||
@@ -25,7 +26,7 @@ o, k, g, all_groups = option_func(all_options, {
|
||||
g('diff')
|
||||
|
||||
|
||||
def syntax_aliases(raw):
|
||||
def syntax_aliases(raw: str) -> Dict[str, str]:
|
||||
ans = {}
|
||||
for x in raw.split():
|
||||
a, b = x.partition(':')[::2]
|
||||
@@ -34,7 +35,7 @@ def syntax_aliases(raw):
|
||||
return ans
|
||||
|
||||
|
||||
o('syntax_aliases', 'pyj:py recipe:py', option_type=syntax_aliases, long_text=_('''
|
||||
o('syntax_aliases', 'pyj:py pyi:py recipe:py', option_type=syntax_aliases, long_text=_('''
|
||||
File extension aliases for syntax highlight
|
||||
For example, to syntax highlight :file:`file.xyz` as
|
||||
:file:`file.abc` use a setting of :code:`xyz:abc`
|
||||
@@ -79,6 +80,8 @@ c('highlight_added_bg', '#acf2bd')
|
||||
c('added_margin_bg', '#cdffd8')
|
||||
|
||||
c('filler_bg', '#fafbfc', long_text=_('Filler (empty) line background'))
|
||||
c('margin_filler_bg', 'none', option_type=to_color_or_none, long_text=_(
|
||||
'Filler (empty) line background in margins, defaults to the filler background'))
|
||||
|
||||
c('hunk_margin_bg', '#dbedff', long_text=_('Hunk header colors'))
|
||||
c('hunk_bg', '#f1f8ff')
|
||||
@@ -121,4 +124,9 @@ k('prev_match', '<', 'scroll_to prev-match', _('Scroll to previous search match'
|
||||
k('search_forward_simple', 'f', 'start_search substring forward', _('Search forward (no regex)'))
|
||||
k('search_backward_simple', 'b', 'start_search substring backward', _('Search backward (no regex)'))
|
||||
|
||||
type_map = {o.name: o.option_type for o in all_options.values() if hasattr(o, 'option_type')}
|
||||
|
||||
def type_convert(name: str, val: Any) -> Any:
|
||||
o = all_options.get(name)
|
||||
if isinstance(o, Option):
|
||||
val = o.option_type(val)
|
||||
return val
|
||||
|
||||
14
kittens/diff/diff_speedup.pyi
Normal file
14
kittens/diff/diff_speedup.pyi
Normal file
@@ -0,0 +1,14 @@
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from .collect import Segment
|
||||
|
||||
|
||||
def split_with_highlights(
|
||||
line: str, truncate_points: List[int], fg_highlights: List,
|
||||
bg_highlight: Optional[Segment]
|
||||
) -> List:
|
||||
pass
|
||||
|
||||
|
||||
def changed_center(left_prefix: str, right_postfix: str) -> Tuple[int, int]:
|
||||
pass
|
||||
@@ -5,15 +5,17 @@
|
||||
import concurrent
|
||||
import os
|
||||
import re
|
||||
from typing import IO, Dict, Iterable, List, Optional, Tuple, Union, cast
|
||||
|
||||
from pygments import highlight
|
||||
from pygments.formatter import Formatter
|
||||
from pygments.lexers import get_lexer_for_filename
|
||||
from pygments.util import ClassNotFound
|
||||
from pygments import highlight # type: ignore
|
||||
from pygments.formatter import Formatter # type: ignore
|
||||
from pygments.lexers import get_lexer_for_filename # type: ignore
|
||||
from pygments.util import ClassNotFound # type: ignore
|
||||
|
||||
from kitty.multiprocessing import get_process_pool_executor
|
||||
from kitty.rgb import color_as_sgr, parse_sharp
|
||||
|
||||
from .collect import Segment, data_for_path, lines_for_path
|
||||
from .collect import Collection, Segment, data_for_path, lines_for_path
|
||||
|
||||
|
||||
class StyleNotFound(Exception):
|
||||
@@ -22,7 +24,7 @@ class StyleNotFound(Exception):
|
||||
|
||||
class DiffFormatter(Formatter):
|
||||
|
||||
def __init__(self, style='default'):
|
||||
def __init__(self, style: str = 'default') -> None:
|
||||
try:
|
||||
Formatter.__init__(self, style=style)
|
||||
initialized = True
|
||||
@@ -31,30 +33,34 @@ class DiffFormatter(Formatter):
|
||||
if not initialized:
|
||||
raise StyleNotFound('pygments style "{}" not found'.format(style))
|
||||
|
||||
self.styles = {}
|
||||
for token, style in self.style:
|
||||
self.styles: Dict[str, Tuple[str, str]] = {}
|
||||
for token, token_style in self.style:
|
||||
start = []
|
||||
end = []
|
||||
fstart = fend = ''
|
||||
# a style item is a tuple in the following form:
|
||||
# colors are readily specified in hex: 'RRGGBB'
|
||||
if style['color']:
|
||||
start.append('38' + color_as_sgr(parse_sharp(style['color'])))
|
||||
end.append('39')
|
||||
if style['bold']:
|
||||
col = token_style['color']
|
||||
if col:
|
||||
pc = parse_sharp(col)
|
||||
if pc is not None:
|
||||
start.append('38' + color_as_sgr(pc))
|
||||
end.append('39')
|
||||
if token_style['bold']:
|
||||
start.append('1')
|
||||
end.append('22')
|
||||
if style['italic']:
|
||||
if token_style['italic']:
|
||||
start.append('3')
|
||||
end.append('23')
|
||||
if style['underline']:
|
||||
if token_style['underline']:
|
||||
start.append('4')
|
||||
end.append('24')
|
||||
if start:
|
||||
start = '\033[{}m'.format(';'.join(start))
|
||||
end = '\033[{}m'.format(';'.join(end))
|
||||
self.styles[token] = start or '', end or ''
|
||||
fstart = '\033[{}m'.format(';'.join(start))
|
||||
fend = '\033[{}m'.format(';'.join(end))
|
||||
self.styles[token] = fstart, fend
|
||||
|
||||
def format(self, tokensource, outfile):
|
||||
def format(self, tokensource: Iterable[Tuple[str, str]], outfile: IO[str]) -> None:
|
||||
for ttype, value in tokensource:
|
||||
not_found = True
|
||||
if value.rstrip('\n'):
|
||||
@@ -76,15 +82,15 @@ class DiffFormatter(Formatter):
|
||||
outfile.write(value)
|
||||
|
||||
|
||||
formatter = None
|
||||
formatter: Optional[DiffFormatter] = None
|
||||
|
||||
|
||||
def initialize_highlighter(style='default'):
|
||||
def initialize_highlighter(style: str = 'default') -> None:
|
||||
global formatter
|
||||
formatter = DiffFormatter(style)
|
||||
|
||||
|
||||
def highlight_data(code, filename, aliases=None):
|
||||
def highlight_data(code: str, filename: str, aliases: Optional[Dict[str, str]] = None) -> Optional[str]:
|
||||
if aliases:
|
||||
base, ext = os.path.splitext(filename)
|
||||
alias = aliases.get(ext[1:])
|
||||
@@ -93,17 +99,16 @@ def highlight_data(code, filename, aliases=None):
|
||||
try:
|
||||
lexer = get_lexer_for_filename(filename, stripnl=False)
|
||||
except ClassNotFound:
|
||||
pass
|
||||
else:
|
||||
return highlight(code, lexer, formatter)
|
||||
return None
|
||||
return cast(str, highlight(code, lexer, formatter))
|
||||
|
||||
|
||||
split_pat = re.compile(r'(\033\[.*?m)')
|
||||
|
||||
|
||||
def highlight_line(line):
|
||||
ans = []
|
||||
current = None
|
||||
def highlight_line(line: str) -> List[Segment]:
|
||||
ans: List[Segment] = []
|
||||
current: Optional[Segment] = None
|
||||
pos = 0
|
||||
for x in split_pat.split(line):
|
||||
if x.startswith('\033'):
|
||||
@@ -119,8 +124,11 @@ def highlight_line(line):
|
||||
return ans
|
||||
|
||||
|
||||
def highlight_for_diff(path, aliases):
|
||||
ans = []
|
||||
DiffHighlight = List[List[Segment]]
|
||||
|
||||
|
||||
def highlight_for_diff(path: str, aliases: Dict[str, str]) -> DiffHighlight:
|
||||
ans: DiffHighlight = []
|
||||
lines = lines_for_path(path)
|
||||
hd = highlight_data('\n'.join(lines), path, aliases)
|
||||
if hd is not None:
|
||||
@@ -129,11 +137,10 @@ def highlight_for_diff(path, aliases):
|
||||
return ans
|
||||
|
||||
|
||||
def highlight_collection(collection, aliases=None):
|
||||
def highlight_collection(collection: Collection, aliases: Optional[Dict[str, str]] = None) -> Union[str, Dict[str, DiffHighlight]]:
|
||||
jobs = {}
|
||||
ans = {}
|
||||
with concurrent.futures.ProcessPoolExecutor(max_workers=os.cpu_count()) as executor:
|
||||
highlight_collection.processes = executor._processes
|
||||
ans: Dict[str, DiffHighlight] = {}
|
||||
with get_process_pool_executor(prefer_fork=True) as executor:
|
||||
for path, item_type, other_path in collection:
|
||||
if item_type != 'rename':
|
||||
for p in (path, other_path):
|
||||
@@ -151,13 +158,14 @@ def highlight_collection(collection, aliases=None):
|
||||
return ans
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
from .config import defaults
|
||||
# kitty +runpy "from kittens.diff.highlight import main; main()" file
|
||||
import sys
|
||||
initialize_highlighter()
|
||||
with open(sys.argv[-1]) as f:
|
||||
highlighted = highlight_data(f.read(), f.name, defaults.syntax_aliases)
|
||||
if highlighted is None:
|
||||
raise SystemExit('Unknown filetype: {}'.format(sys.argv[-1]))
|
||||
print(highlighted)
|
||||
if defaults is not None:
|
||||
with open(sys.argv[-1]) as f:
|
||||
highlighted = highlight_data(f.read(), f.name, defaults.syntax_aliases)
|
||||
if highlighted is None:
|
||||
raise SystemExit('Unknown filetype: {}'.format(sys.argv[-1]))
|
||||
print(highlighted)
|
||||
|
||||
@@ -13,42 +13,57 @@ from collections import defaultdict
|
||||
from contextlib import suppress
|
||||
from functools import partial
|
||||
from gettext import gettext as _
|
||||
from typing import DefaultDict, Dict, Iterable, List, Optional, Tuple, Union
|
||||
|
||||
from kitty.cli import CONFIG_HELP, parse_args
|
||||
from kitty.cli_stub import DiffCLIOptions
|
||||
from kitty.conf.utils import KittensKeyAction
|
||||
from kitty.constants import appname
|
||||
from kitty.fast_data_types import wcswidth
|
||||
from kitty.key_encoding import ESCAPE, RELEASE, enter_key
|
||||
from kitty.key_encoding import RELEASE, KeyEvent, enter_key, key_defs as K
|
||||
from kitty.options_stub import DiffOptions
|
||||
from kitty.utils import ScreenSize
|
||||
|
||||
from ..tui.handler import Handler
|
||||
from ..tui.images import ImageManager
|
||||
from ..tui.line_edit import LineEdit
|
||||
from ..tui.loop import Loop
|
||||
from ..tui.operations import styled
|
||||
from . import global_data
|
||||
from .collect import (
|
||||
create_collection, data_for_path, lines_for_path, sanitize,
|
||||
Collection, create_collection, data_for_path, lines_for_path, sanitize,
|
||||
set_highlight_data
|
||||
)
|
||||
from .config import init_config
|
||||
from .patch import Differ, set_diff_command, worker_processes
|
||||
from .render import ImageSupportWarning, LineRef, render_diff
|
||||
from .patch import Differ, Patch, set_diff_command, worker_processes
|
||||
from .render import (
|
||||
ImagePlacement, ImageSupportWarning, Line, LineRef, Reference, render_diff
|
||||
)
|
||||
from .search import BadRegex, Search
|
||||
from ..tui.handler import Handler
|
||||
from ..tui.images import ImageManager, Placement
|
||||
from ..tui.line_edit import LineEdit
|
||||
from ..tui.loop import Loop
|
||||
from ..tui.operations import styled
|
||||
|
||||
try:
|
||||
from .highlight import initialize_highlighter, highlight_collection
|
||||
from .highlight import initialize_highlighter, highlight_collection, DiffHighlight
|
||||
has_highlighter = True
|
||||
DiffHighlight
|
||||
except ImportError:
|
||||
initialize_highlighter = highlight_collection = None
|
||||
has_highlighter = False
|
||||
|
||||
def highlight_collection(collection: 'Collection', aliases: Optional[Dict[str, str]] = None) -> Union[str, Dict[str, 'DiffHighlight']]:
|
||||
return ''
|
||||
|
||||
|
||||
INITIALIZING, COLLECTED, DIFFED, COMMAND, MESSAGE = range(5)
|
||||
ESCAPE = K['ESCAPE']
|
||||
|
||||
|
||||
def generate_diff(collection, context):
|
||||
def generate_diff(collection: Collection, context: int) -> Union[str, Dict[str, Patch]]:
|
||||
d = Differ()
|
||||
|
||||
for path, item_type, changed_path in collection:
|
||||
if item_type == 'diff':
|
||||
is_binary = isinstance(data_for_path(path), bytes) or isinstance(data_for_path(changed_path), bytes)
|
||||
if not is_binary:
|
||||
assert changed_path is not None
|
||||
d.add_diff(path, changed_path)
|
||||
|
||||
return d(context)
|
||||
@@ -58,35 +73,35 @@ class DiffHandler(Handler):
|
||||
|
||||
image_manager_class = ImageManager
|
||||
|
||||
def __init__(self, args, opts, left, right):
|
||||
def __init__(self, args: DiffCLIOptions, opts: DiffOptions, left: str, right: str) -> None:
|
||||
self.state = INITIALIZING
|
||||
self.message = ''
|
||||
self.current_search_is_regex = True
|
||||
self.current_search = None
|
||||
self.current_search: Optional[Search] = None
|
||||
self.line_edit = LineEdit()
|
||||
self.opts = opts
|
||||
self.left, self.right = left, right
|
||||
self.report_traceback_on_exit = None
|
||||
self.report_traceback_on_exit: Union[str, Dict[str, Patch], None] = None
|
||||
self.args = args
|
||||
self.scroll_pos = self.max_scroll_pos = 0
|
||||
self.current_context_count = self.original_context_count = self.args.context
|
||||
if self.current_context_count < 0:
|
||||
self.current_context_count = self.original_context_count = self.opts.num_context_lines
|
||||
self.highlighting_done = False
|
||||
self.restore_position = None
|
||||
self.restore_position: Optional[Reference] = None
|
||||
for key_def, action in self.opts.key_definitions.items():
|
||||
self.add_shortcut(action, *key_def)
|
||||
|
||||
def perform_action(self, action):
|
||||
def perform_action(self, action: KittensKeyAction) -> None:
|
||||
func, args = action
|
||||
if func == 'quit':
|
||||
self.quit_loop(0)
|
||||
return
|
||||
if self.state <= DIFFED:
|
||||
if func == 'scroll_by':
|
||||
return self.scroll_lines(*args)
|
||||
return self.scroll_lines(int(args[0]))
|
||||
if func == 'scroll_to':
|
||||
where = args[0]
|
||||
where = str(args[0])
|
||||
if 'change' in where:
|
||||
return self.scroll_to_next_change(backwards='prev' in where)
|
||||
if 'match' in where:
|
||||
@@ -104,28 +119,28 @@ class DiffHandler(Handler):
|
||||
elif to == 'default':
|
||||
new_ctx = self.original_context_count
|
||||
else:
|
||||
new_ctx += to
|
||||
new_ctx += int(to)
|
||||
return self.change_context_count(new_ctx)
|
||||
if func == 'start_search':
|
||||
self.start_search(*args)
|
||||
self.start_search(bool(args[0]), bool(args[1]))
|
||||
return
|
||||
|
||||
def create_collection(self):
|
||||
def create_collection(self) -> None:
|
||||
|
||||
def collect_done(collection):
|
||||
def collect_done(collection: Collection) -> None:
|
||||
self.collection = collection
|
||||
self.state = COLLECTED
|
||||
self.generate_diff()
|
||||
|
||||
def collect(left, right):
|
||||
def collect(left: str, right: str) -> None:
|
||||
collection = create_collection(left, right)
|
||||
self.asyncio_loop.call_soon_threadsafe(collect_done, collection)
|
||||
|
||||
self.asyncio_loop.run_in_executor(None, collect, self.left, self.right)
|
||||
|
||||
def generate_diff(self):
|
||||
def generate_diff(self) -> None:
|
||||
|
||||
def diff_done(diff_map):
|
||||
def diff_done(diff_map: Union[str, Dict[str, Patch]]) -> None:
|
||||
if isinstance(diff_map, str):
|
||||
self.report_traceback_on_exit = diff_map
|
||||
self.quit_loop(1)
|
||||
@@ -139,7 +154,7 @@ class DiffHandler(Handler):
|
||||
self.current_position = self.restore_position
|
||||
self.restore_position = None
|
||||
self.draw_screen()
|
||||
if initialize_highlighter is not None and not self.highlighting_done:
|
||||
if has_highlighter and not self.highlighting_done:
|
||||
from .highlight import StyleNotFound
|
||||
self.highlighting_done = True
|
||||
try:
|
||||
@@ -150,15 +165,15 @@ class DiffHandler(Handler):
|
||||
return
|
||||
self.syntax_highlight()
|
||||
|
||||
def diff(collection, current_context_count):
|
||||
def diff(collection: Collection, current_context_count: int) -> None:
|
||||
diff_map = generate_diff(collection, current_context_count)
|
||||
self.asyncio_loop.call_soon_threadsafe(diff_done, diff_map)
|
||||
|
||||
self.asyncio_loop.run_in_executor(None, diff, self.collection, self.current_context_count)
|
||||
|
||||
def syntax_highlight(self):
|
||||
def syntax_highlight(self) -> None:
|
||||
|
||||
def highlighting_done(hdata):
|
||||
def highlighting_done(hdata: Union[str, Dict[str, 'DiffHighlight']]) -> None:
|
||||
if isinstance(hdata, str):
|
||||
self.report_traceback_on_exit = hdata
|
||||
self.quit_loop(1)
|
||||
@@ -167,23 +182,23 @@ class DiffHandler(Handler):
|
||||
self.render_diff()
|
||||
self.draw_screen()
|
||||
|
||||
def highlight(*a):
|
||||
result = highlight_collection(*a)
|
||||
def highlight(collection: Collection, aliases: Optional[Dict[str, str]] = None) -> None:
|
||||
result = highlight_collection(collection, aliases)
|
||||
self.asyncio_loop.call_soon_threadsafe(highlighting_done, result)
|
||||
|
||||
self.asyncio_loop.run_in_executor(None, highlight, self.collection, self.opts.syntax_aliases)
|
||||
|
||||
def calculate_statistics(self):
|
||||
def calculate_statistics(self) -> None:
|
||||
self.added_count = self.collection.added_count
|
||||
self.removed_count = self.collection.removed_count
|
||||
for patch in self.diff_map.values():
|
||||
self.added_count += patch.added_count
|
||||
self.removed_count += patch.removed_count
|
||||
|
||||
def render_diff(self):
|
||||
self.diff_lines = tuple(render_diff(self.collection, self.diff_map, self.args, self.screen_size.cols, self.image_manager))
|
||||
def render_diff(self) -> None:
|
||||
self.diff_lines: Tuple[Line, ...] = tuple(render_diff(self.collection, self.diff_map, self.args, self.screen_size.cols, self.image_manager))
|
||||
self.margin_size = render_diff.margin_size
|
||||
self.ref_path_map = defaultdict(list)
|
||||
self.ref_path_map: DefaultDict[str, List[Tuple[int, Reference]]] = defaultdict(list)
|
||||
for i, l in enumerate(self.diff_lines):
|
||||
self.ref_path_map[l.ref.path].append((i, l.ref))
|
||||
self.max_scroll_pos = len(self.diff_lines) - self.num_lines
|
||||
@@ -191,11 +206,11 @@ class DiffHandler(Handler):
|
||||
self.current_search(self.diff_lines, self.margin_size, self.screen_size.cols)
|
||||
|
||||
@property
|
||||
def current_position(self):
|
||||
def current_position(self) -> Reference:
|
||||
return self.diff_lines[min(len(self.diff_lines) - 1, self.scroll_pos)].ref
|
||||
|
||||
@current_position.setter
|
||||
def current_position(self, ref):
|
||||
def current_position(self, ref: Reference) -> None:
|
||||
num = None
|
||||
if isinstance(ref.extra, LineRef):
|
||||
sln = ref.extra.src_line_number
|
||||
@@ -215,10 +230,10 @@ class DiffHandler(Handler):
|
||||
self.scroll_pos = max(0, min(num, self.max_scroll_pos))
|
||||
|
||||
@property
|
||||
def num_lines(self):
|
||||
def num_lines(self) -> int:
|
||||
return self.screen_size.rows - 1
|
||||
|
||||
def scroll_to_next_change(self, backwards=False):
|
||||
def scroll_to_next_change(self, backwards: bool = False) -> None:
|
||||
if backwards:
|
||||
r = range(self.scroll_pos - 1, -1, -1)
|
||||
else:
|
||||
@@ -230,7 +245,7 @@ class DiffHandler(Handler):
|
||||
return
|
||||
self.cmd.bell()
|
||||
|
||||
def scroll_to_next_match(self, backwards=False, include_current=False):
|
||||
def scroll_to_next_match(self, backwards: bool = False, include_current: bool = False) -> None:
|
||||
if self.current_search is not None:
|
||||
offset = 0 if include_current else 1
|
||||
if backwards:
|
||||
@@ -243,10 +258,10 @@ class DiffHandler(Handler):
|
||||
return
|
||||
self.cmd.bell()
|
||||
|
||||
def set_scrolling_region(self):
|
||||
def set_scrolling_region(self) -> None:
|
||||
self.cmd.set_scrolling_region(self.screen_size, 0, self.num_lines - 2)
|
||||
|
||||
def scroll_lines(self, amt=1):
|
||||
def scroll_lines(self, amt: int = 1) -> None:
|
||||
new_pos = max(0, min(self.scroll_pos + amt, self.max_scroll_pos))
|
||||
if new_pos == self.scroll_pos:
|
||||
self.cmd.bell()
|
||||
@@ -266,30 +281,30 @@ class DiffHandler(Handler):
|
||||
self.draw_lines(amt, self.num_lines - amt)
|
||||
self.draw_status_line()
|
||||
|
||||
def init_terminal_state(self):
|
||||
def init_terminal_state(self) -> None:
|
||||
self.cmd.set_line_wrapping(False)
|
||||
self.cmd.set_window_title(main.title)
|
||||
self.cmd.set_window_title(global_data.title)
|
||||
self.cmd.set_default_colors(
|
||||
fg=self.opts.foreground, bg=self.opts.background,
|
||||
cursor=self.opts.foreground, select_fg=self.opts.select_fg,
|
||||
select_bg=self.opts.select_bg)
|
||||
self.cmd.set_cursor_shape('bar')
|
||||
|
||||
def finalize(self):
|
||||
def finalize(self) -> None:
|
||||
self.cmd.set_default_colors()
|
||||
self.cmd.set_cursor_visible(True)
|
||||
self.cmd.set_scrolling_region()
|
||||
|
||||
def initialize(self):
|
||||
def initialize(self) -> None:
|
||||
self.init_terminal_state()
|
||||
self.set_scrolling_region()
|
||||
self.draw_screen()
|
||||
self.create_collection()
|
||||
|
||||
def enforce_cursor_state(self):
|
||||
def enforce_cursor_state(self) -> None:
|
||||
self.cmd.set_cursor_visible(self.state == COMMAND)
|
||||
|
||||
def draw_lines(self, num, offset=0):
|
||||
def draw_lines(self, num: int, offset: int = 0) -> None:
|
||||
offset += self.scroll_pos
|
||||
image_involved = False
|
||||
limit = len(self.diff_lines)
|
||||
@@ -310,7 +325,43 @@ class DiffHandler(Handler):
|
||||
if image_involved:
|
||||
self.place_images()
|
||||
|
||||
def place_images(self):
|
||||
def update_image_placement_for_resend(self, image_id: int, pl: Placement) -> bool:
|
||||
offset = self.scroll_pos
|
||||
limit = len(self.diff_lines)
|
||||
in_image = False
|
||||
|
||||
def adjust(row: int, candidate: ImagePlacement, is_left: bool) -> bool:
|
||||
if candidate.image.image_id == image_id:
|
||||
q = self.xpos_for_image(row, candidate, is_left)
|
||||
if q is not None:
|
||||
pl['x'] = q[0]
|
||||
pl['y'] = row
|
||||
return True
|
||||
return False
|
||||
|
||||
for row in range(self.num_lines):
|
||||
lpos = offset + row
|
||||
if lpos >= limit:
|
||||
break
|
||||
line = self.diff_lines[lpos]
|
||||
if in_image:
|
||||
if line.image_data is None:
|
||||
in_image = False
|
||||
continue
|
||||
if line.image_data is not None:
|
||||
left_placement, right_placement = line.image_data
|
||||
if left_placement is not None:
|
||||
if adjust(row, left_placement, True):
|
||||
return True
|
||||
in_image = True
|
||||
if right_placement is not None:
|
||||
if adjust(row, right_placement, False):
|
||||
return True
|
||||
in_image = True
|
||||
return False
|
||||
|
||||
def place_images(self) -> None:
|
||||
self.image_manager.update_image_placement_for_resend = self.update_image_placement_for_resend
|
||||
self.cmd.clear_images_on_screen()
|
||||
offset = self.scroll_pos
|
||||
limit = len(self.diff_lines)
|
||||
@@ -333,19 +384,26 @@ class DiffHandler(Handler):
|
||||
self.place_image(row, right_placement, False)
|
||||
in_image = True
|
||||
|
||||
def place_image(self, row, placement, is_left):
|
||||
def xpos_for_image(self, row: int, placement: ImagePlacement, is_left: bool) -> Optional[Tuple[int, float]]:
|
||||
xpos = (0 if is_left else (self.screen_size.cols // 2)) + placement.image.margin_size
|
||||
image_height_in_rows = placement.image.rows
|
||||
topmost_visible_row = placement.row
|
||||
num_visible_rows = image_height_in_rows - topmost_visible_row
|
||||
visible_frac = min(num_visible_rows / image_height_in_rows, 1)
|
||||
if visible_frac > 0:
|
||||
if visible_frac <= 0:
|
||||
return None
|
||||
return xpos, visible_frac
|
||||
|
||||
def place_image(self, row: int, placement: ImagePlacement, is_left: bool) -> None:
|
||||
q = self.xpos_for_image(row, placement, is_left)
|
||||
if q is not None:
|
||||
xpos, visible_frac = q
|
||||
height = int(visible_frac * placement.image.height)
|
||||
top = placement.image.height - height
|
||||
self.image_manager.show_image(placement.image.image_id, xpos, row, src_rect=(
|
||||
0, top, placement.image.width, height))
|
||||
|
||||
def draw_screen(self):
|
||||
def draw_screen(self) -> None:
|
||||
self.enforce_cursor_state()
|
||||
if self.state < DIFFED:
|
||||
self.cmd.clear_screen()
|
||||
@@ -356,7 +414,7 @@ class DiffHandler(Handler):
|
||||
self.draw_lines(self.num_lines)
|
||||
self.draw_status_line()
|
||||
|
||||
def draw_status_line(self):
|
||||
def draw_status_line(self) -> None:
|
||||
if self.state < DIFFED:
|
||||
return
|
||||
self.enforce_cursor_state()
|
||||
@@ -383,7 +441,7 @@ class DiffHandler(Handler):
|
||||
text = '{}{}{}'.format(prefix, ' ' * filler, suffix)
|
||||
self.write(text)
|
||||
|
||||
def change_context_count(self, new_ctx):
|
||||
def change_context_count(self, new_ctx: int) -> None:
|
||||
new_ctx = max(0, new_ctx)
|
||||
if new_ctx != self.current_context_count:
|
||||
self.current_context_count = new_ctx
|
||||
@@ -392,7 +450,7 @@ class DiffHandler(Handler):
|
||||
self.restore_position = self.current_position
|
||||
self.draw_screen()
|
||||
|
||||
def start_search(self, is_regex, is_backward):
|
||||
def start_search(self, is_regex: bool, is_backward: bool) -> None:
|
||||
if self.state != DIFFED:
|
||||
self.cmd.bell()
|
||||
return
|
||||
@@ -402,7 +460,7 @@ class DiffHandler(Handler):
|
||||
self.current_search_is_regex = is_regex
|
||||
self.draw_status_line()
|
||||
|
||||
def do_search(self):
|
||||
def do_search(self) -> None:
|
||||
self.current_search = None
|
||||
query = self.line_edit.current_input
|
||||
if len(query) < 2:
|
||||
@@ -421,7 +479,7 @@ class DiffHandler(Handler):
|
||||
self.message = sanitize(_('No matches found'))
|
||||
self.cmd.bell()
|
||||
|
||||
def on_text(self, text, in_bracketed_paste=False):
|
||||
def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
|
||||
if self.state is COMMAND:
|
||||
self.line_edit.on_text(text, in_bracketed_paste)
|
||||
self.draw_status_line()
|
||||
@@ -434,7 +492,7 @@ class DiffHandler(Handler):
|
||||
if action is not None:
|
||||
return self.perform_action(action)
|
||||
|
||||
def on_key(self, key_event):
|
||||
def on_key(self, key_event: KeyEvent) -> None:
|
||||
if self.state is MESSAGE:
|
||||
if key_event.type is not RELEASE:
|
||||
self.state = DIFFED
|
||||
@@ -467,7 +525,7 @@ class DiffHandler(Handler):
|
||||
if action is not None:
|
||||
return self.perform_action(action)
|
||||
|
||||
def on_resize(self, screen_size):
|
||||
def on_resize(self, screen_size: ScreenSize) -> None:
|
||||
self.screen_size = screen_size
|
||||
self.set_scrolling_region()
|
||||
if self.state > COLLECTED:
|
||||
@@ -475,10 +533,10 @@ class DiffHandler(Handler):
|
||||
self.render_diff()
|
||||
self.draw_screen()
|
||||
|
||||
def on_interrupt(self):
|
||||
def on_interrupt(self) -> None:
|
||||
self.quit_loop(1)
|
||||
|
||||
def on_eot(self):
|
||||
def on_eot(self) -> None:
|
||||
self.quit_loop(1)
|
||||
|
||||
|
||||
@@ -503,23 +561,28 @@ Syntax: :italic:`name=value`. For example: :italic:`-o background=gray`
|
||||
'''.format, config_help=CONFIG_HELP.format(conf_name='diff', appname=appname))
|
||||
|
||||
|
||||
def showwarning(message, category, filename, lineno, file=None, line=None):
|
||||
if category is ImageSupportWarning:
|
||||
showwarning.warnings.append(message)
|
||||
class ShowWarning:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.warnings: List[str] = []
|
||||
|
||||
def __call__(self, message: str, category: object, filename: str, lineno: int, file: object = None, line: object = None) -> None:
|
||||
if category is ImageSupportWarning:
|
||||
showwarning.warnings.append(message)
|
||||
|
||||
|
||||
showwarning.warnings = []
|
||||
showwarning = ShowWarning()
|
||||
help_text = 'Show a side-by-side diff of the specified files/directories. You can also use ssh:hostname:remote-file-path to diff remote files.'
|
||||
usage = 'file_or_directory_left file_or_directory_right'
|
||||
|
||||
|
||||
def terminate_processes(processes):
|
||||
def terminate_processes(processes: Iterable[int]) -> None:
|
||||
for pid in processes:
|
||||
with suppress(Exception):
|
||||
os.kill(pid, signal.SIGKILL)
|
||||
|
||||
|
||||
def get_remote_file(path):
|
||||
def get_remote_file(path: str) -> str:
|
||||
if path.startswith('ssh:'):
|
||||
parts = path.split(':', 2)
|
||||
if len(parts) == 3:
|
||||
@@ -533,16 +596,16 @@ def get_remote_file(path):
|
||||
return path
|
||||
|
||||
|
||||
def main(args):
|
||||
def main(args: List[str]) -> None:
|
||||
warnings.showwarning = showwarning
|
||||
args, items = parse_args(args[1:], OPTIONS, usage, help_text, 'kitty +kitten diff')
|
||||
cli_opts, items = parse_args(args[1:], OPTIONS, usage, help_text, 'kitty +kitten diff', result_class=DiffCLIOptions)
|
||||
if len(items) != 2:
|
||||
raise SystemExit('You must specify exactly two files/directories to compare')
|
||||
left, right = items
|
||||
main.title = _('{} vs. {}').format(left, right)
|
||||
global_data.title = _('{} vs. {}').format(left, right)
|
||||
if os.path.isdir(left) != os.path.isdir(right):
|
||||
raise SystemExit('The items to be diffed should both be either directories or files. Comparing a directory to a file is not valid.')
|
||||
opts = init_config(args)
|
||||
opts = init_config(cli_opts)
|
||||
set_diff_command(opts.diff_cmd)
|
||||
lines_for_path.replace_tab_by = opts.replace_tab_by
|
||||
left, right = map(get_remote_file, (left, right))
|
||||
@@ -551,7 +614,7 @@ def main(args):
|
||||
raise SystemExit('{} does not exist'.format(f))
|
||||
|
||||
loop = Loop()
|
||||
handler = DiffHandler(args, opts, left, right)
|
||||
handler = DiffHandler(cli_opts, opts, left, right)
|
||||
loop.loop(handler)
|
||||
for message in showwarning.warnings:
|
||||
from kitty.utils import safe_print
|
||||
@@ -569,9 +632,10 @@ def main(args):
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
||||
elif __name__ == '__doc__':
|
||||
sys.cli_docs['usage'] = usage
|
||||
sys.cli_docs['options'] = OPTIONS
|
||||
sys.cli_docs['help_text'] = help_text
|
||||
cd = sys.cli_docs # type: ignore
|
||||
cd['usage'] = usage
|
||||
cd['options'] = OPTIONS
|
||||
cd['help_text'] = help_text
|
||||
elif __name__ == '__conf__':
|
||||
from .config import all_options
|
||||
sys.all_options = all_options
|
||||
from .config_data import all_options
|
||||
sys.all_options = all_options # type: ignore
|
||||
|
||||
@@ -7,40 +7,44 @@ import os
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
from typing import Dict, Iterator, List, Optional, Sequence, Tuple, Union
|
||||
|
||||
from . import global_data
|
||||
from .collect import lines_for_path
|
||||
from .diff_speedup import changed_center
|
||||
|
||||
left_lines = right_lines = None
|
||||
left_lines: Tuple[str, ...] = ()
|
||||
right_lines: Tuple[str, ...] = ()
|
||||
GIT_DIFF = 'git diff --no-color --no-ext-diff --exit-code -U_CONTEXT_ --no-index --'
|
||||
DIFF_DIFF = 'diff -p -U _CONTEXT_ --'
|
||||
worker_processes = []
|
||||
worker_processes: List[int] = []
|
||||
|
||||
|
||||
def find_differ():
|
||||
def find_differ() -> Optional[str]:
|
||||
if shutil.which('git') and subprocess.Popen(['git', '--help'], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL).wait() == 0:
|
||||
return GIT_DIFF
|
||||
if shutil.which('diff'):
|
||||
return DIFF_DIFF
|
||||
|
||||
|
||||
def set_diff_command(opt):
|
||||
def set_diff_command(opt: str) -> None:
|
||||
if opt == 'auto':
|
||||
cmd = find_differ()
|
||||
if cmd is None:
|
||||
raise SystemExit('Failed to find either the git or diff programs on your system')
|
||||
else:
|
||||
cmd = opt
|
||||
set_diff_command.cmd = cmd
|
||||
global_data.cmd = cmd
|
||||
|
||||
|
||||
def run_diff(file1, file2, context=3):
|
||||
def run_diff(file1: str, file2: str, context: int = 3) -> Tuple[bool, Union[int, bool], str]:
|
||||
# returns: ok, is_different, patch
|
||||
cmd = shlex.split(set_diff_command.cmd.replace('_CONTEXT_', str(context)))
|
||||
cmd = shlex.split(global_data.cmd.replace('_CONTEXT_', str(context)))
|
||||
# we resolve symlinks because git diff does not follow symlinks, while diff
|
||||
# does. We want consistent behavior, also for integration with git difftool
|
||||
# we always want symlinks to be followed.
|
||||
path1, path2 = map(os.path.realpath, (file1, file2))
|
||||
path1 = os.path.realpath(file1)
|
||||
path2 = os.path.realpath(file2)
|
||||
p = subprocess.Popen(
|
||||
cmd + [path1, path2],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL)
|
||||
@@ -57,49 +61,49 @@ class Chunk:
|
||||
|
||||
__slots__ = ('is_context', 'left_start', 'right_start', 'left_count', 'right_count', 'centers')
|
||||
|
||||
def __init__(self, left_start, right_start, is_context=False):
|
||||
def __init__(self, left_start: int, right_start: int, is_context: bool = False) -> None:
|
||||
self.is_context = is_context
|
||||
self.left_start = left_start
|
||||
self.right_start = right_start
|
||||
self.left_count = self.right_count = 0
|
||||
self.centers = None
|
||||
self.centers: Optional[Tuple[Tuple[int, int], ...]] = None
|
||||
|
||||
def add_line(self):
|
||||
def add_line(self) -> None:
|
||||
self.right_count += 1
|
||||
|
||||
def remove_line(self):
|
||||
def remove_line(self) -> None:
|
||||
self.left_count += 1
|
||||
|
||||
def context_line(self):
|
||||
def context_line(self) -> None:
|
||||
self.left_count += 1
|
||||
self.right_count += 1
|
||||
|
||||
def finalize(self):
|
||||
def finalize(self) -> None:
|
||||
if not self.is_context and self.left_count == self.right_count:
|
||||
self.centers = tuple(
|
||||
changed_center(left_lines[self.left_start + i], right_lines[self.right_start + i])
|
||||
for i in range(self.left_count)
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
def __repr__(self) -> str:
|
||||
return 'Chunk(is_context={}, left_start={}, left_count={}, right_start={}, right_count={})'.format(
|
||||
self.is_context, self.left_start, self.left_count, self.right_start, self.right_count)
|
||||
|
||||
|
||||
class Hunk:
|
||||
|
||||
def __init__(self, title, left, right):
|
||||
def __init__(self, title: str, left: Tuple[int, int], right: Tuple[int, int]) -> None:
|
||||
self.left_start, self.left_count = left
|
||||
self.right_start, self.right_count = right
|
||||
self.left_start -= 1 # 0-index
|
||||
self.right_start -= 1 # 0-index
|
||||
self.title = title
|
||||
self.added_count = self.removed_count = 0
|
||||
self.chunks = []
|
||||
self.current_chunk = None
|
||||
self.chunks: List[Chunk] = []
|
||||
self.current_chunk: Optional[Chunk] = None
|
||||
self.largest_line_number = max(self.left_start + self.left_count, self.right_start + self.right_count)
|
||||
|
||||
def new_chunk(self, is_context=False):
|
||||
def new_chunk(self, is_context: bool = False) -> Chunk:
|
||||
if self.chunks:
|
||||
c = self.chunks[-1]
|
||||
left_start = c.left_start + c.left_count
|
||||
@@ -109,36 +113,40 @@ class Hunk:
|
||||
right_start = self.right_start
|
||||
return Chunk(left_start, right_start, is_context)
|
||||
|
||||
def ensure_diff_chunk(self):
|
||||
def ensure_diff_chunk(self) -> None:
|
||||
if self.current_chunk is None:
|
||||
self.current_chunk = self.new_chunk(is_context=False)
|
||||
elif self.current_chunk.is_context:
|
||||
self.chunks.append(self.current_chunk)
|
||||
self.current_chunk = self.new_chunk(is_context=False)
|
||||
|
||||
def ensure_context_chunk(self):
|
||||
def ensure_context_chunk(self) -> None:
|
||||
if self.current_chunk is None:
|
||||
self.current_chunk = self.new_chunk(is_context=True)
|
||||
elif not self.current_chunk.is_context:
|
||||
self.chunks.append(self.current_chunk)
|
||||
self.current_chunk = self.new_chunk(is_context=True)
|
||||
|
||||
def add_line(self):
|
||||
def add_line(self) -> None:
|
||||
self.ensure_diff_chunk()
|
||||
self.current_chunk.add_line()
|
||||
if self.current_chunk is not None:
|
||||
self.current_chunk.add_line()
|
||||
self.added_count += 1
|
||||
|
||||
def remove_line(self):
|
||||
def remove_line(self) -> None:
|
||||
self.ensure_diff_chunk()
|
||||
self.current_chunk.remove_line()
|
||||
if self.current_chunk is not None:
|
||||
self.current_chunk.remove_line()
|
||||
self.removed_count += 1
|
||||
|
||||
def context_line(self):
|
||||
def context_line(self) -> None:
|
||||
self.ensure_context_chunk()
|
||||
self.current_chunk.context_line()
|
||||
if self.current_chunk is not None:
|
||||
self.current_chunk.context_line()
|
||||
|
||||
def finalize(self):
|
||||
self.chunks.append(self.current_chunk)
|
||||
def finalize(self) -> None:
|
||||
if self.current_chunk is not None:
|
||||
self.chunks.append(self.current_chunk)
|
||||
del self.current_chunk
|
||||
# Sanity check
|
||||
c = self.chunks[-1]
|
||||
@@ -150,15 +158,15 @@ class Hunk:
|
||||
c.finalize()
|
||||
|
||||
|
||||
def parse_range(x):
|
||||
def parse_range(x: str) -> Tuple[int, int]:
|
||||
parts = x[1:].split(',', 1)
|
||||
start = abs(int(parts[0]))
|
||||
count = 1 if len(parts) < 2 else int(parts[1])
|
||||
return start, count
|
||||
|
||||
|
||||
def parse_hunk_header(line):
|
||||
parts = tuple(filter(None, line.split('@@', 2)))
|
||||
def parse_hunk_header(line: str) -> Hunk:
|
||||
parts: Tuple[str, ...] = tuple(filter(None, line.split('@@', 2)))
|
||||
linespec = parts[0].strip()
|
||||
title = ''
|
||||
if len(parts) == 2:
|
||||
@@ -169,20 +177,20 @@ def parse_hunk_header(line):
|
||||
|
||||
class Patch:
|
||||
|
||||
def __init__(self, all_hunks):
|
||||
def __init__(self, all_hunks: Sequence[Hunk]):
|
||||
self.all_hunks = all_hunks
|
||||
self.largest_line_number = self.all_hunks[-1].largest_line_number if self.all_hunks else 0
|
||||
self.added_count = sum(h.added_count for h in all_hunks)
|
||||
self.removed_count = sum(h.removed_count for h in all_hunks)
|
||||
|
||||
def __iter__(self):
|
||||
def __iter__(self) -> Iterator[Hunk]:
|
||||
return iter(self.all_hunks)
|
||||
|
||||
def __len__(self):
|
||||
def __len__(self) -> int:
|
||||
return len(self.all_hunks)
|
||||
|
||||
|
||||
def parse_patch(raw):
|
||||
def parse_patch(raw: str) -> Patch:
|
||||
all_hunks = []
|
||||
current_hunk = None
|
||||
for line in raw.splitlines():
|
||||
@@ -208,22 +216,23 @@ def parse_patch(raw):
|
||||
|
||||
class Differ:
|
||||
|
||||
diff_executor = None
|
||||
diff_executor: Optional[concurrent.futures.ThreadPoolExecutor] = None
|
||||
|
||||
def __init__(self):
|
||||
self.jmap = {}
|
||||
self.jobs = []
|
||||
def __init__(self) -> None:
|
||||
self.jmap: Dict[str, str] = {}
|
||||
self.jobs: List[str] = []
|
||||
if Differ.diff_executor is None:
|
||||
Differ.diff_executor = self.diff_executor = concurrent.futures.ThreadPoolExecutor(max_workers=os.cpu_count())
|
||||
|
||||
def add_diff(self, file1, file2):
|
||||
def add_diff(self, file1: str, file2: str) -> None:
|
||||
self.jmap[file1] = file2
|
||||
self.jobs.append(file1)
|
||||
|
||||
def __call__(self, context=3):
|
||||
def __call__(self, context: int = 3) -> Union[str, Dict[str, Patch]]:
|
||||
global left_lines, right_lines
|
||||
ans = {}
|
||||
executor = Differ.diff_executor
|
||||
ans: Dict[str, Patch] = {}
|
||||
executor = self.diff_executor
|
||||
assert executor is not None
|
||||
jobs = {executor.submit(run_diff, key, self.jmap[key], context): key for key in self.jobs}
|
||||
for future in concurrent.futures.as_completed(jobs):
|
||||
key = jobs[future]
|
||||
|
||||
@@ -3,40 +3,44 @@
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import warnings
|
||||
from functools import lru_cache
|
||||
from gettext import gettext as _
|
||||
from itertools import repeat, zip_longest
|
||||
from math import ceil
|
||||
from typing import Callable, Dict, Generator, Iterable, List, Optional, Tuple
|
||||
|
||||
from kitty.fast_data_types import truncate_point_for_length, wcswidth
|
||||
from kitty.cli_stub import DiffCLIOptions
|
||||
from kitty.utils import ScreenSize
|
||||
|
||||
from ..tui.images import can_display_images
|
||||
from .collect import (
|
||||
Segment, data_for_path, highlights_for_path, is_image, lines_for_path,
|
||||
path_name_map, sanitize
|
||||
Collection, Segment, data_for_path, highlights_for_path, is_image,
|
||||
lines_for_path, path_name_map, sanitize
|
||||
)
|
||||
from .config import formats
|
||||
from .diff_speedup import split_with_highlights as _split_with_highlights
|
||||
from .patch import Chunk, Hunk, Patch
|
||||
from ..tui.images import ImageManager, can_display_images
|
||||
|
||||
|
||||
class ImageSupportWarning(Warning):
|
||||
pass
|
||||
|
||||
|
||||
def images_supported():
|
||||
ans = getattr(images_supported, 'ans', None)
|
||||
if ans is None:
|
||||
images_supported.ans = ans = can_display_images()
|
||||
if not ans:
|
||||
warnings.warn('ImageMagick not found images cannot be displayed', ImageSupportWarning)
|
||||
@lru_cache(maxsize=2)
|
||||
def images_supported() -> bool:
|
||||
ans = can_display_images()
|
||||
if not ans:
|
||||
warnings.warn('ImageMagick not found images cannot be displayed', ImageSupportWarning)
|
||||
return ans
|
||||
|
||||
|
||||
class Ref:
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
def __setattr__(self, name: str, value: object) -> None:
|
||||
raise AttributeError("can't set attribute")
|
||||
|
||||
def __repr__(self):
|
||||
def __repr__(self) -> str:
|
||||
return '{}({})'.format(self.__class__.__name__, ', '.join(
|
||||
'{}={}'.format(n, getattr(self, n)) for n in self.__slots__ if n != '_hash'))
|
||||
|
||||
@@ -44,8 +48,10 @@ class Ref:
|
||||
class LineRef(Ref):
|
||||
|
||||
__slots__ = ('src_line_number', 'wrapped_line_idx')
|
||||
src_line_number: int
|
||||
wrapped_line_idx: int
|
||||
|
||||
def __init__(self, sln, wli=0):
|
||||
def __init__(self, sln: int, wli: int = 0) -> None:
|
||||
object.__setattr__(self, 'src_line_number', sln)
|
||||
object.__setattr__(self, 'wrapped_line_idx', wli)
|
||||
|
||||
@@ -53,8 +59,10 @@ class LineRef(Ref):
|
||||
class Reference(Ref):
|
||||
|
||||
__slots__ = ('path', 'extra')
|
||||
path: str
|
||||
extra: Optional[LineRef]
|
||||
|
||||
def __init__(self, path, extra=None):
|
||||
def __init__(self, path: str, extra: Optional[LineRef] = None) -> None:
|
||||
object.__setattr__(self, 'path', path)
|
||||
object.__setattr__(self, 'extra', extra)
|
||||
|
||||
@@ -63,35 +71,41 @@ class Line:
|
||||
|
||||
__slots__ = ('text', 'ref', 'is_change_start', 'image_data')
|
||||
|
||||
def __init__(self, text, ref, change_start=False, image_data=None):
|
||||
def __init__(
|
||||
self,
|
||||
text: str,
|
||||
ref: Reference,
|
||||
change_start: bool = False,
|
||||
image_data: Optional[Tuple[Optional['ImagePlacement'], Optional['ImagePlacement']]] = None
|
||||
) -> None:
|
||||
self.text = text
|
||||
self.ref = ref
|
||||
self.is_change_start = change_start
|
||||
self.image_data = image_data
|
||||
|
||||
|
||||
def yield_lines_from(iterator, reference, is_change_start=True):
|
||||
def yield_lines_from(iterator: Iterable[str], reference: Reference, is_change_start: bool = True) -> Generator[Line, None, None]:
|
||||
for text in iterator:
|
||||
yield Line(text, reference, is_change_start)
|
||||
is_change_start = False
|
||||
|
||||
|
||||
def human_readable(size, sep=' '):
|
||||
def human_readable(size: int, sep: str = ' ') -> str:
|
||||
""" Convert a size in bytes into a human readable form """
|
||||
divisor, suffix = 1, "B"
|
||||
for i, candidate in enumerate(('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB')):
|
||||
if size < (1 << ((i + 1) * 10)):
|
||||
divisor, suffix = (1 << (i * 10)), candidate
|
||||
break
|
||||
size = str(float(size)/divisor)
|
||||
if size.find(".") > -1:
|
||||
size = size[:size.find(".")+2]
|
||||
if size.endswith('.0'):
|
||||
size = size[:-2]
|
||||
return size + sep + suffix
|
||||
s = str(float(size)/divisor)
|
||||
if s.find(".") > -1:
|
||||
s = s[:s.find(".")+2]
|
||||
if s.endswith('.0'):
|
||||
s = s[:-2]
|
||||
return s + sep + suffix
|
||||
|
||||
|
||||
def fit_in(text, count):
|
||||
def fit_in(text: str, count: int) -> str:
|
||||
p = truncate_point_for_length(text, count)
|
||||
if p >= len(text):
|
||||
return text
|
||||
@@ -100,19 +114,19 @@ def fit_in(text, count):
|
||||
return text[:p] + '…'
|
||||
|
||||
|
||||
def fill_in(text, sz):
|
||||
def fill_in(text: str, sz: int) -> str:
|
||||
w = wcswidth(text)
|
||||
if w < sz:
|
||||
text += ' ' * (sz - w)
|
||||
return text
|
||||
|
||||
|
||||
def place_in(text, sz):
|
||||
def place_in(text: str, sz: int) -> str:
|
||||
return fill_in(fit_in(text, sz), sz)
|
||||
|
||||
|
||||
def format_func(which):
|
||||
def formatted(text):
|
||||
def format_func(which: str) -> Callable[[str], str]:
|
||||
def formatted(text: str) -> str:
|
||||
fmt = formats[which]
|
||||
return '\x1b[' + fmt + 'm' + text + '\x1b[0m'
|
||||
formatted.__name__ = which + '_format'
|
||||
@@ -127,44 +141,47 @@ removed_format = format_func('removed')
|
||||
removed_margin_format = format_func('removed_margin')
|
||||
added_margin_format = format_func('added_margin')
|
||||
filler_format = format_func('filler')
|
||||
margin_filler_format = format_func('margin_filler')
|
||||
hunk_margin_format = format_func('hunk_margin')
|
||||
hunk_format = format_func('hunk')
|
||||
highlight_map = {'remove': ('removed_highlight', 'removed'), 'add': ('added_highlight', 'added')}
|
||||
|
||||
|
||||
def highlight_boundaries(ltype):
|
||||
def highlight_boundaries(ltype: str) -> Tuple[str, str]:
|
||||
s, e = highlight_map[ltype]
|
||||
start = '\x1b[' + formats[s] + 'm'
|
||||
stop = '\x1b[' + formats[e] + 'm'
|
||||
return start, stop
|
||||
|
||||
|
||||
def title_lines(left_path, right_path, args, columns, margin_size):
|
||||
def title_lines(left_path: Optional[str], right_path: Optional[str], args: DiffCLIOptions, columns: int, margin_size: int) -> Generator[str, None, None]:
|
||||
m = ' ' * margin_size
|
||||
left_name, right_name = map(path_name_map.get, (left_path, right_path))
|
||||
left_name = path_name_map.get(left_path) if left_path else None
|
||||
right_name = path_name_map.get(right_path) if right_path else None
|
||||
if right_name and right_name != left_name:
|
||||
n1 = fit_in(m + sanitize(left_name), columns // 2 - margin_size)
|
||||
n1 = fit_in(m + sanitize(left_name or ''), columns // 2 - margin_size)
|
||||
n1 = place_in(n1, columns // 2)
|
||||
n2 = fit_in(m + sanitize(right_name), columns // 2 - margin_size)
|
||||
n2 = place_in(n2, columns // 2)
|
||||
name = n1 + n2
|
||||
else:
|
||||
name = place_in(m + sanitize(left_name), columns)
|
||||
name = place_in(m + sanitize(left_name or ''), columns)
|
||||
yield title_format(place_in(name, columns))
|
||||
yield title_format('━' * columns)
|
||||
|
||||
|
||||
def binary_lines(path, other_path, columns, margin_size):
|
||||
def binary_lines(path: Optional[str], other_path: Optional[str], columns: int, margin_size: int) -> Generator[str, None, None]:
|
||||
template = _('Binary file: {}')
|
||||
available_cols = columns // 2 - margin_size
|
||||
|
||||
def fl(path, fmt):
|
||||
def fl(path: str, fmt: Callable[[str], str]) -> str:
|
||||
text = template.format(human_readable(len(data_for_path(path))))
|
||||
text = place_in(text, available_cols)
|
||||
return margin_format(' ' * margin_size) + fmt(text)
|
||||
|
||||
if path is None:
|
||||
filler = render_diff_line('', '', 'filler', margin_size, available_cols)
|
||||
assert other_path is not None
|
||||
yield filler + fl(other_path, added_format)
|
||||
elif other_path is None:
|
||||
filler = render_diff_line('', '', 'filler', margin_size, available_cols)
|
||||
@@ -173,7 +190,7 @@ def binary_lines(path, other_path, columns, margin_size):
|
||||
yield fl(path, removed_format) + fl(other_path, added_format)
|
||||
|
||||
|
||||
def split_to_size(line, width):
|
||||
def split_to_size(line: str, width: int) -> Generator[str, None, None]:
|
||||
if not line:
|
||||
yield line
|
||||
while line:
|
||||
@@ -182,7 +199,7 @@ def split_to_size(line, width):
|
||||
line = line[p:]
|
||||
|
||||
|
||||
def truncate_points(line, width):
|
||||
def truncate_points(line: str, width: int) -> Generator[int, None, None]:
|
||||
pos = 0
|
||||
sz = len(line)
|
||||
while True:
|
||||
@@ -193,18 +210,18 @@ def truncate_points(line, width):
|
||||
break
|
||||
|
||||
|
||||
def split_with_highlights(line, width, highlights, bg_highlight=None):
|
||||
def split_with_highlights(line: str, width: int, highlights: List, bg_highlight: Optional[Segment] = None) -> List:
|
||||
truncate_pts = list(truncate_points(line, width))
|
||||
return _split_with_highlights(line, truncate_pts, highlights, bg_highlight)
|
||||
|
||||
|
||||
margin_bg_map = {'filler': filler_format, 'remove': removed_margin_format, 'add': added_margin_format, 'context': margin_format}
|
||||
margin_bg_map = {'filler': margin_filler_format, 'remove': removed_margin_format, 'add': added_margin_format, 'context': margin_format}
|
||||
text_bg_map = {'filler': filler_format, 'remove': removed_format, 'add': added_format, 'context': text_format}
|
||||
|
||||
|
||||
class DiffData:
|
||||
|
||||
def __init__(self, left_path, right_path, available_cols, margin_size):
|
||||
def __init__(self, left_path: str, right_path: str, available_cols: int, margin_size: int):
|
||||
self.left_path, self.right_path = left_path, right_path
|
||||
self.available_cols = available_cols
|
||||
self.margin_size = margin_size
|
||||
@@ -215,24 +232,28 @@ class DiffData:
|
||||
self.left_hdata = highlights_for_path(left_path)
|
||||
self.right_hdata = highlights_for_path(right_path)
|
||||
|
||||
def left_highlights_for_line(self, line_num):
|
||||
def left_highlights_for_line(self, line_num: int) -> List[Segment]:
|
||||
if line_num < len(self.left_hdata):
|
||||
return self.left_hdata[line_num]
|
||||
return []
|
||||
|
||||
def right_highlights_for_line(self, line_num):
|
||||
def right_highlights_for_line(self, line_num: int) -> List[Segment]:
|
||||
if line_num < len(self.right_hdata):
|
||||
return self.right_hdata[line_num]
|
||||
return []
|
||||
|
||||
|
||||
def render_diff_line(number, text, ltype, margin_size, available_cols):
|
||||
margin = margin_bg_map[ltype](place_in(number, margin_size))
|
||||
def render_diff_line(number: Optional[str], text: str, ltype: str, margin_size: int, available_cols: int) -> str:
|
||||
margin = margin_bg_map[ltype](place_in(number or '', margin_size))
|
||||
content = text_bg_map[ltype](fill_in(text or '', available_cols))
|
||||
return margin + content
|
||||
|
||||
|
||||
def render_diff_pair(left_line_number, left, left_is_change, right_line_number, right, right_is_change, is_first, margin_size, available_cols):
|
||||
def render_diff_pair(
|
||||
left_line_number: Optional[str], left: str, left_is_change: bool,
|
||||
right_line_number: Optional[str], right: str, right_is_change: bool,
|
||||
is_first: bool, margin_size: int, available_cols: int
|
||||
) -> str:
|
||||
ltype = 'filler' if left_line_number is None else ('remove' if left_is_change else 'context')
|
||||
rtype = 'filler' if right_line_number is None else ('add' if right_is_change else 'context')
|
||||
return (
|
||||
@@ -241,14 +262,22 @@ def render_diff_pair(left_line_number, left, left_is_change, right_line_number,
|
||||
)
|
||||
|
||||
|
||||
def hunk_title(hunk_num, hunk, margin_size, available_cols):
|
||||
def hunk_title(hunk_num: int, hunk: Hunk, margin_size: int, available_cols: int) -> str:
|
||||
m = hunk_margin_format(' ' * margin_size)
|
||||
t = '@@ -{},{} +{},{} @@ {}'.format(hunk.left_start + 1, hunk.left_count, hunk.right_start + 1, hunk.right_count, hunk.title)
|
||||
return m + hunk_format(place_in(t, available_cols))
|
||||
|
||||
|
||||
def render_half_line(line_number, line, highlights, ltype, margin_size, available_cols, changed_center=None):
|
||||
bg_highlight = None
|
||||
def render_half_line(
|
||||
line_number: int,
|
||||
line: str,
|
||||
highlights: List,
|
||||
ltype: str,
|
||||
margin_size: int,
|
||||
available_cols: int,
|
||||
changed_center: Optional[Tuple[int, int]] = None
|
||||
) -> Generator[str, None, None]:
|
||||
bg_highlight: Optional[Segment] = None
|
||||
if changed_center is not None and changed_center[0]:
|
||||
prefix_count, suffix_count = changed_center
|
||||
line_sz = len(line)
|
||||
@@ -259,40 +288,41 @@ def render_half_line(line_number, line, highlights, ltype, margin_size, availabl
|
||||
seg.end_code = stop
|
||||
bg_highlight = seg
|
||||
if highlights or bg_highlight:
|
||||
lines = split_with_highlights(line, available_cols, highlights, bg_highlight)
|
||||
lines: Iterable[str] = split_with_highlights(line, available_cols, highlights, bg_highlight)
|
||||
else:
|
||||
lines = split_to_size(line, available_cols)
|
||||
line_number = str(line_number + 1)
|
||||
lnum = str(line_number + 1)
|
||||
for line in lines:
|
||||
yield render_diff_line(line_number, line, ltype, margin_size, available_cols)
|
||||
line_number = ''
|
||||
yield render_diff_line(lnum, line, ltype, margin_size, available_cols)
|
||||
lnum = ''
|
||||
|
||||
|
||||
def lines_for_chunk(data, hunk_num, chunk, chunk_num):
|
||||
def lines_for_chunk(data: DiffData, hunk_num: int, chunk: Chunk, chunk_num: int) -> Generator[Line, None, None]:
|
||||
if chunk.is_context:
|
||||
for i in range(chunk.left_count):
|
||||
left_line_number = line_ref = chunk.left_start + i
|
||||
right_line_number = chunk.right_start + i
|
||||
highlights = data.left_highlights_for_line(left_line_number)
|
||||
if highlights:
|
||||
lines = split_with_highlights(data.left_lines[left_line_number], data.available_cols, highlights)
|
||||
lines: Iterable[str] = split_with_highlights(data.left_lines[left_line_number], data.available_cols, highlights)
|
||||
else:
|
||||
lines = split_to_size(data.left_lines[left_line_number], data.available_cols)
|
||||
left_line_number = str(left_line_number + 1)
|
||||
right_line_number = str(right_line_number + 1)
|
||||
left_line_number_s = str(left_line_number + 1)
|
||||
right_line_number_s = str(right_line_number + 1)
|
||||
for wli, text in enumerate(lines):
|
||||
line = render_diff_line(left_line_number, text, 'context', data.margin_size, data.available_cols)
|
||||
if right_line_number == left_line_number:
|
||||
line = render_diff_line(left_line_number_s, text, 'context', data.margin_size, data.available_cols)
|
||||
if right_line_number_s == left_line_number_s:
|
||||
r = line
|
||||
else:
|
||||
r = render_diff_line(right_line_number, text, 'context', data.margin_size, data.available_cols)
|
||||
r = render_diff_line(right_line_number_s, text, 'context', data.margin_size, data.available_cols)
|
||||
ref = Reference(data.left_path, LineRef(line_ref, wli))
|
||||
yield Line(line + r, ref)
|
||||
left_line_number = right_line_number = ''
|
||||
left_line_number_s = right_line_number_s = ''
|
||||
else:
|
||||
common = min(chunk.left_count, chunk.right_count)
|
||||
for i in range(max(chunk.left_count, chunk.right_count)):
|
||||
ll, rl = [], []
|
||||
ll: List[str] = []
|
||||
rl: List[str] = []
|
||||
if i < chunk.left_count:
|
||||
rln = ref_ln = chunk.left_start + i
|
||||
ll.extend(render_half_line(
|
||||
@@ -327,7 +357,7 @@ def lines_for_chunk(data, hunk_num, chunk, chunk_num):
|
||||
yield Line(left_line + right_line, ref, i == 0 and wli == 0)
|
||||
|
||||
|
||||
def lines_for_diff(left_path, right_path, hunks, args, columns, margin_size):
|
||||
def lines_for_diff(left_path: str, right_path: str, hunks: Iterable[Hunk], args: DiffCLIOptions, columns: int, margin_size: int) -> Generator[Line, None, None]:
|
||||
available_cols = columns // 2 - margin_size
|
||||
data = DiffData(left_path, right_path, available_cols, margin_size)
|
||||
|
||||
@@ -337,7 +367,7 @@ def lines_for_diff(left_path, right_path, hunks, args, columns, margin_size):
|
||||
yield from lines_for_chunk(data, hunk_num, chunk, cnum)
|
||||
|
||||
|
||||
def all_lines(path, args, columns, margin_size, is_add=True):
|
||||
def all_lines(path: str, args: DiffCLIOptions, columns: int, margin_size: int, is_add: bool = True) -> Generator[Line, None, None]:
|
||||
available_cols = columns // 2 - margin_size
|
||||
ltype = 'add' if is_add else 'remove'
|
||||
lines = lines_for_path(path)
|
||||
@@ -345,7 +375,7 @@ def all_lines(path, args, columns, margin_size, is_add=True):
|
||||
msg_written = False
|
||||
hdata = highlights_for_path(path)
|
||||
|
||||
def highlights(num):
|
||||
def highlights(num: int) -> List[Segment]:
|
||||
return hdata[num] if num < len(hdata) else []
|
||||
|
||||
for line_number, line in enumerate(lines):
|
||||
@@ -362,7 +392,7 @@ def all_lines(path, args, columns, margin_size, is_add=True):
|
||||
yield Line(text, ref, line_number == 0 and i == 0)
|
||||
|
||||
|
||||
def rename_lines(path, other_path, args, columns, margin_size):
|
||||
def rename_lines(path: str, other_path: str, args: DiffCLIOptions, columns: int, margin_size: int) -> Generator[str, None, None]:
|
||||
m = ' ' * margin_size
|
||||
for line in split_to_size(_('The file {0} was renamed to {1}').format(
|
||||
sanitize(path_name_map[path]), sanitize(path_name_map[other_path])), columns - margin_size):
|
||||
@@ -371,7 +401,7 @@ def rename_lines(path, other_path, args, columns, margin_size):
|
||||
|
||||
class Image:
|
||||
|
||||
def __init__(self, image_id, width, height, margin_size, screen_size):
|
||||
def __init__(self, image_id: int, width: int, height: int, margin_size: int, screen_size: ScreenSize) -> None:
|
||||
self.image_id = image_id
|
||||
self.width, self.height = width, height
|
||||
self.rows = int(ceil(self.height / screen_size.cell_height))
|
||||
@@ -381,18 +411,23 @@ class Image:
|
||||
|
||||
class ImagePlacement:
|
||||
|
||||
def __init__(self, image, row):
|
||||
def __init__(self, image: Image, row: int) -> None:
|
||||
self.image = image
|
||||
self.row = row
|
||||
|
||||
|
||||
def render_image(path, is_left, available_cols, margin_size, image_manager):
|
||||
def render_image(
|
||||
path: str,
|
||||
is_left: bool,
|
||||
available_cols: int, margin_size: int,
|
||||
image_manager: ImageManager
|
||||
) -> Generator[Tuple[str, Reference, Optional[ImagePlacement]], None, None]:
|
||||
lnum = 0
|
||||
margin_fmt = removed_margin_format if is_left else added_margin_format
|
||||
m = margin_fmt(' ' * margin_size)
|
||||
fmt = removed_format if is_left else added_format
|
||||
|
||||
def yield_split(text):
|
||||
def yield_split(text: str) -> Generator[Tuple[str, Reference, Optional[ImagePlacement]], None, None]:
|
||||
nonlocal lnum
|
||||
for i, line in enumerate(split_to_size(text, available_cols)):
|
||||
yield m + fmt(place_in(line, available_cols)), Reference(path, LineRef(lnum, i)), None
|
||||
@@ -414,9 +449,16 @@ def render_image(path, is_left, available_cols, margin_size, image_manager):
|
||||
lnum += 1
|
||||
|
||||
|
||||
def image_lines(left_path, right_path, columns, margin_size, image_manager):
|
||||
def image_lines(
|
||||
left_path: Optional[str],
|
||||
right_path: Optional[str],
|
||||
columns: int,
|
||||
margin_size: int,
|
||||
image_manager: ImageManager
|
||||
) -> Generator[Line, None, None]:
|
||||
available_cols = columns // 2 - margin_size
|
||||
left_lines, right_lines = iter(()), iter(())
|
||||
left_lines: Iterable[Tuple[str, Reference, Optional[ImagePlacement]]] = iter(())
|
||||
right_lines: Iterable[Tuple[str, Reference, Optional[ImagePlacement]]] = iter(())
|
||||
if left_path is not None:
|
||||
left_lines = render_image(left_path, True, available_cols, margin_size, image_manager)
|
||||
if right_path is not None:
|
||||
@@ -439,52 +481,68 @@ def image_lines(left_path, right_path, columns, margin_size, image_manager):
|
||||
is_change_start = False
|
||||
|
||||
|
||||
def render_diff(collection, diff_map, args, columns, image_manager):
|
||||
largest_line_number = 0
|
||||
for path, item_type, other_path in collection:
|
||||
if item_type == 'diff':
|
||||
patch = diff_map.get(path)
|
||||
if patch is not None:
|
||||
largest_line_number = max(largest_line_number, patch.largest_line_number)
|
||||
class RenderDiff:
|
||||
|
||||
margin_size = render_diff.margin_size = max(3, len(str(largest_line_number)) + 1)
|
||||
last_item_num = len(collection) - 1
|
||||
margin_size: int = 0
|
||||
|
||||
for i, (path, item_type, other_path) in enumerate(collection):
|
||||
item_ref = Reference(path)
|
||||
is_binary = isinstance(data_for_path(path), bytes)
|
||||
if not is_binary and item_type == 'diff' and isinstance(data_for_path(other_path), bytes):
|
||||
is_binary = True
|
||||
is_img = is_binary and (is_image(path) or is_image(other_path)) and images_supported()
|
||||
yield from yield_lines_from(title_lines(path, other_path, args, columns, margin_size), item_ref, False)
|
||||
if item_type == 'diff':
|
||||
if is_binary:
|
||||
if is_img:
|
||||
ans = image_lines(path, other_path, columns, margin_size, image_manager)
|
||||
def __call__(
|
||||
self,
|
||||
collection: Collection,
|
||||
diff_map: Dict[str, Patch],
|
||||
args: DiffCLIOptions,
|
||||
columns: int,
|
||||
image_manager: ImageManager
|
||||
) -> Generator[Line, None, None]:
|
||||
largest_line_number = 0
|
||||
for path, item_type, other_path in collection:
|
||||
if item_type == 'diff':
|
||||
patch = diff_map.get(path)
|
||||
if patch is not None:
|
||||
largest_line_number = max(largest_line_number, patch.largest_line_number)
|
||||
|
||||
margin_size = self.margin_size = max(3, len(str(largest_line_number)) + 1)
|
||||
last_item_num = len(collection) - 1
|
||||
|
||||
for i, (path, item_type, other_path) in enumerate(collection):
|
||||
item_ref = Reference(path)
|
||||
is_binary = isinstance(data_for_path(path), bytes)
|
||||
if not is_binary and item_type == 'diff' and isinstance(data_for_path(other_path), bytes):
|
||||
is_binary = True
|
||||
is_img = is_binary and (is_image(path) or is_image(other_path)) and images_supported()
|
||||
yield from yield_lines_from(title_lines(path, other_path, args, columns, margin_size), item_ref, False)
|
||||
if item_type == 'diff':
|
||||
if is_binary:
|
||||
if is_img:
|
||||
ans = image_lines(path, other_path, columns, margin_size, image_manager)
|
||||
else:
|
||||
ans = yield_lines_from(binary_lines(path, other_path, columns, margin_size), item_ref)
|
||||
else:
|
||||
ans = yield_lines_from(binary_lines(path, other_path, columns, margin_size), item_ref)
|
||||
else:
|
||||
ans = lines_for_diff(path, other_path, diff_map[path], args, columns, margin_size)
|
||||
elif item_type == 'add':
|
||||
if is_binary:
|
||||
if is_img:
|
||||
ans = image_lines(None, path, columns, margin_size, image_manager)
|
||||
assert other_path is not None
|
||||
ans = lines_for_diff(path, other_path, diff_map[path], args, columns, margin_size)
|
||||
elif item_type == 'add':
|
||||
if is_binary:
|
||||
if is_img:
|
||||
ans = image_lines(None, path, columns, margin_size, image_manager)
|
||||
else:
|
||||
ans = yield_lines_from(binary_lines(None, path, columns, margin_size), item_ref)
|
||||
else:
|
||||
ans = yield_lines_from(binary_lines(None, path, columns, margin_size), item_ref)
|
||||
else:
|
||||
ans = all_lines(path, args, columns, margin_size, is_add=True)
|
||||
elif item_type == 'removal':
|
||||
if is_binary:
|
||||
if is_img:
|
||||
ans = image_lines(path, None, columns, margin_size, image_manager)
|
||||
ans = all_lines(path, args, columns, margin_size, is_add=True)
|
||||
elif item_type == 'removal':
|
||||
if is_binary:
|
||||
if is_img:
|
||||
ans = image_lines(path, None, columns, margin_size, image_manager)
|
||||
else:
|
||||
ans = yield_lines_from(binary_lines(path, None, columns, margin_size), item_ref)
|
||||
else:
|
||||
ans = yield_lines_from(binary_lines(path, None, columns, margin_size), item_ref)
|
||||
ans = all_lines(path, args, columns, margin_size, is_add=False)
|
||||
elif item_type == 'rename':
|
||||
assert other_path is not None
|
||||
ans = yield_lines_from(rename_lines(path, other_path, args, columns, margin_size), item_ref)
|
||||
else:
|
||||
ans = all_lines(path, args, columns, margin_size, is_add=False)
|
||||
elif item_type == 'rename':
|
||||
ans = yield_lines_from(rename_lines(path, other_path, args, columns, margin_size), item_ref)
|
||||
else:
|
||||
raise ValueError('Unsupported item type: {}'.format(item_type))
|
||||
yield from ans
|
||||
if i < last_item_num:
|
||||
yield Line('', item_ref)
|
||||
raise ValueError('Unsupported item type: {}'.format(item_type))
|
||||
yield from ans
|
||||
if i < last_item_num:
|
||||
yield Line('', item_ref)
|
||||
|
||||
|
||||
render_diff = RenderDiff()
|
||||
|
||||
@@ -3,11 +3,17 @@
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import re
|
||||
from typing import TYPE_CHECKING, Callable, Dict, Iterable, List, Tuple
|
||||
|
||||
from kitty.fast_data_types import wcswidth
|
||||
from kitty.options_stub import DiffOptions
|
||||
|
||||
from ..tui.operations import styled
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .render import Line
|
||||
Line
|
||||
|
||||
|
||||
class BadRegex(ValueError):
|
||||
pass
|
||||
@@ -15,8 +21,8 @@ class BadRegex(ValueError):
|
||||
|
||||
class Search:
|
||||
|
||||
def __init__(self, opts, query, is_regex, is_backward):
|
||||
self.matches = {}
|
||||
def __init__(self, opts: DiffOptions, query: str, is_regex: bool, is_backward: bool):
|
||||
self.matches: Dict[int, List[Tuple[int, str]]] = {}
|
||||
self.count = 0
|
||||
self.style = styled('|', fg=opts.search_fg, bg=opts.search_bg).split('|', 1)[0]
|
||||
if not is_regex:
|
||||
@@ -26,7 +32,7 @@ class Search:
|
||||
except Exception:
|
||||
raise BadRegex('Not a valid regex: {}'.format(query))
|
||||
|
||||
def __call__(self, diff_lines, margin_size, cols):
|
||||
def __call__(self, diff_lines: Iterable['Line'], margin_size: int, cols: int) -> bool:
|
||||
self.matches = {}
|
||||
self.count = 0
|
||||
half_width = cols // 2
|
||||
@@ -38,7 +44,7 @@ class Search:
|
||||
left, right = text[margin_size:half_width + 1], text[right_offset:]
|
||||
matches = []
|
||||
|
||||
def add(which, offset):
|
||||
def add(which: str, offset: int) -> None:
|
||||
for m in find(which):
|
||||
before = which[:m.start()]
|
||||
matches.append((wcswidth(before) + offset, m.group()))
|
||||
@@ -50,13 +56,13 @@ class Search:
|
||||
self.matches[i] = matches
|
||||
return bool(self.matches)
|
||||
|
||||
def __contains__(self, i):
|
||||
def __contains__(self, i: int) -> bool:
|
||||
return i in self.matches
|
||||
|
||||
def __len__(self):
|
||||
def __len__(self) -> int:
|
||||
return self.count
|
||||
|
||||
def highlight_line(self, write, line_num):
|
||||
def highlight_line(self, write: Callable[[str], None], line_num: int) -> bool:
|
||||
highlights = self.matches.get(line_num)
|
||||
if not highlights:
|
||||
return False
|
||||
|
||||
@@ -9,33 +9,52 @@ import sys
|
||||
from functools import lru_cache
|
||||
from gettext import gettext as _
|
||||
from itertools import repeat
|
||||
from typing import (
|
||||
Any, Callable, Dict, Generator, Iterable, List, Optional, Pattern,
|
||||
Sequence, Set, Tuple, Type, cast
|
||||
)
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import HintsCLIOptions
|
||||
from kitty.fast_data_types import set_clipboard_string
|
||||
from kitty.key_encoding import ESCAPE, backspace_key, enter_key
|
||||
from kitty.utils import screen_size_function
|
||||
from kitty.key_encoding import (
|
||||
KeyEvent, backspace_key, enter_key, key_defs as K
|
||||
)
|
||||
from kitty.typing import BossType, KittyCommonOpts
|
||||
from kitty.utils import ScreenSize, screen_size_function, set_primary_selection
|
||||
|
||||
from ..tui.handler import Handler
|
||||
from ..tui.handler import Handler, result_handler
|
||||
from ..tui.loop import Loop
|
||||
from ..tui.operations import faint, styled
|
||||
|
||||
URL_PREFIXES = 'http https file ftp'.split()
|
||||
|
||||
@lru_cache()
|
||||
def kitty_common_opts() -> KittyCommonOpts:
|
||||
import json
|
||||
v = os.environ.get('KITTY_COMMON_OPTS')
|
||||
if v:
|
||||
return cast(KittyCommonOpts, json.loads(v))
|
||||
from kitty.config import common_opts_as_dict
|
||||
return common_opts_as_dict()
|
||||
|
||||
|
||||
DEFAULT_HINT_ALPHABET = string.digits + string.ascii_lowercase
|
||||
screen_size = screen_size_function()
|
||||
DEFAULT_REGEX = r'(?m)^\s*(.+)\s*$'
|
||||
ESCAPE = K['ESCAPE']
|
||||
|
||||
|
||||
class Mark:
|
||||
|
||||
__slots__ = ('index', 'start', 'end', 'text', 'groupdict')
|
||||
|
||||
def __init__(self, index, start, end, text, groupdict):
|
||||
def __init__(self, index: int, start: int, end: int, text: str, groupdict: Any):
|
||||
self.index, self.start, self.end = index, start, end
|
||||
self.text = text
|
||||
self.groupdict = groupdict
|
||||
|
||||
|
||||
@lru_cache(maxsize=2048)
|
||||
def encode_hint(num, alphabet):
|
||||
def encode_hint(num: int, alphabet: str) -> str:
|
||||
res = ''
|
||||
d = len(alphabet)
|
||||
while not res or num > 0:
|
||||
@@ -44,7 +63,7 @@ def encode_hint(num, alphabet):
|
||||
return res
|
||||
|
||||
|
||||
def decode_hint(x, alphabet=DEFAULT_HINT_ALPHABET):
|
||||
def decode_hint(x: str, alphabet: str = DEFAULT_HINT_ALPHABET) -> int:
|
||||
base = len(alphabet)
|
||||
index_map = {c: i for i, c in enumerate(alphabet)}
|
||||
i = 0
|
||||
@@ -53,7 +72,7 @@ def decode_hint(x, alphabet=DEFAULT_HINT_ALPHABET):
|
||||
return i
|
||||
|
||||
|
||||
def highlight_mark(m, text, current_input, alphabet):
|
||||
def highlight_mark(m: Mark, text: str, current_input: str, alphabet: str) -> str:
|
||||
hint = encode_hint(m.index, alphabet)
|
||||
if current_input and not hint.startswith(current_input):
|
||||
return faint(text)
|
||||
@@ -69,7 +88,7 @@ def highlight_mark(m, text, current_input, alphabet):
|
||||
)
|
||||
|
||||
|
||||
def render(text, current_input, all_marks, ignore_mark_indices, alphabet):
|
||||
def render(text: str, current_input: str, all_marks: Sequence[Mark], ignore_mark_indices: Set[int], alphabet: str) -> str:
|
||||
for mark in reversed(all_marks):
|
||||
if mark.index in ignore_mark_indices:
|
||||
continue
|
||||
@@ -83,47 +102,47 @@ def render(text, current_input, all_marks, ignore_mark_indices, alphabet):
|
||||
|
||||
class Hints(Handler):
|
||||
|
||||
def __init__(self, text, all_marks, index_map, args):
|
||||
def __init__(self, text: str, all_marks: Sequence[Mark], index_map: Dict[int, Mark], args: HintsCLIOptions):
|
||||
self.text, self.index_map = text, index_map
|
||||
self.alphabet = args.alphabet or DEFAULT_HINT_ALPHABET
|
||||
self.all_marks = all_marks
|
||||
self.ignore_mark_indices = set()
|
||||
self.ignore_mark_indices: Set[int] = set()
|
||||
self.args = args
|
||||
self.window_title = _('Choose URL') if args.type == 'url' else _('Choose text')
|
||||
self.multiple = args.multiple
|
||||
self.match_suffix = self.get_match_suffix(args)
|
||||
self.chosen = []
|
||||
self.chosen: List[Mark] = []
|
||||
self.reset()
|
||||
|
||||
@property
|
||||
def text_matches(self):
|
||||
def text_matches(self) -> List[str]:
|
||||
return [m.text + self.match_suffix for m in self.chosen]
|
||||
|
||||
@property
|
||||
def groupdicts(self):
|
||||
def groupdicts(self) -> List[Any]:
|
||||
return [m.groupdict for m in self.chosen]
|
||||
|
||||
def get_match_suffix(self, args):
|
||||
def get_match_suffix(self, args: HintsCLIOptions) -> str:
|
||||
if args.add_trailing_space == 'always':
|
||||
return ' '
|
||||
if args.add_trailing_space == 'never':
|
||||
return ''
|
||||
return ' ' if args.multiple else ''
|
||||
|
||||
def reset(self):
|
||||
def reset(self) -> None:
|
||||
self.current_input = ''
|
||||
self.current_text = None
|
||||
self.current_text: Optional[str] = None
|
||||
|
||||
def init_terminal_state(self):
|
||||
def init_terminal_state(self) -> None:
|
||||
self.cmd.set_cursor_visible(False)
|
||||
self.cmd.set_window_title(self.window_title)
|
||||
self.cmd.set_line_wrapping(False)
|
||||
|
||||
def initialize(self):
|
||||
def initialize(self) -> None:
|
||||
self.init_terminal_state()
|
||||
self.draw_screen()
|
||||
|
||||
def on_text(self, text, in_bracketed_paste):
|
||||
def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
|
||||
changed = False
|
||||
for c in text:
|
||||
if c in self.alphabet:
|
||||
@@ -145,7 +164,7 @@ class Hints(Handler):
|
||||
self.current_text = None
|
||||
self.draw_screen()
|
||||
|
||||
def on_key(self, key_event):
|
||||
def on_key(self, key_event: KeyEvent) -> None:
|
||||
if key_event is backspace_key:
|
||||
self.current_input = self.current_input[:-1]
|
||||
self.current_text = None
|
||||
@@ -168,23 +187,23 @@ class Hints(Handler):
|
||||
elif key_event.key is ESCAPE:
|
||||
self.quit_loop(0 if self.multiple else 1)
|
||||
|
||||
def on_interrupt(self):
|
||||
def on_interrupt(self) -> None:
|
||||
self.quit_loop(1)
|
||||
|
||||
def on_eot(self):
|
||||
def on_eot(self) -> None:
|
||||
self.quit_loop(1)
|
||||
|
||||
def on_resize(self, new_size):
|
||||
def on_resize(self, new_size: ScreenSize) -> None:
|
||||
self.draw_screen()
|
||||
|
||||
def draw_screen(self):
|
||||
def draw_screen(self) -> None:
|
||||
if self.current_text is None:
|
||||
self.current_text = render(self.text, self.current_input, self.all_marks, self.ignore_mark_indices, self.alphabet)
|
||||
self.cmd.clear_screen()
|
||||
self.write(self.current_text)
|
||||
|
||||
|
||||
def regex_finditer(pat, minimum_match_length, text):
|
||||
def regex_finditer(pat: Pattern, minimum_match_length: int, text: str) -> Generator[Tuple[int, int, Dict], None, None]:
|
||||
has_named_groups = bool(pat.groupindex)
|
||||
for m in pat.finditer(text):
|
||||
s, e = m.span(0 if has_named_groups else pat.groups)
|
||||
@@ -196,16 +215,17 @@ def regex_finditer(pat, minimum_match_length, text):
|
||||
|
||||
closing_bracket_map = {'(': ')', '[': ']', '{': '}', '<': '>', '*': '*', '"': '"', "'": "'"}
|
||||
opening_brackets = ''.join(closing_bracket_map)
|
||||
postprocessor_map = {}
|
||||
PostprocessorFunc = Callable[[str, int, int], Tuple[int, int]]
|
||||
postprocessor_map: Dict[str, PostprocessorFunc] = {}
|
||||
|
||||
|
||||
def postprocessor(func):
|
||||
def postprocessor(func: PostprocessorFunc) -> PostprocessorFunc:
|
||||
postprocessor_map[func.__name__] = func
|
||||
return func
|
||||
|
||||
|
||||
@postprocessor
|
||||
def url(text, s, e):
|
||||
def url(text: str, s: int, e: int) -> Tuple[int, int]:
|
||||
if s > 4 and text[s - 5:s] == 'link:': # asciidoc URLs
|
||||
url = text[s:e]
|
||||
idx = url.rfind('[')
|
||||
@@ -227,7 +247,7 @@ def url(text, s, e):
|
||||
|
||||
|
||||
@postprocessor
|
||||
def brackets(text, s, e):
|
||||
def brackets(text: str, s: int, e: int) -> Tuple[int, int]:
|
||||
# Remove matching brackets
|
||||
if s < e <= len(text):
|
||||
before = text[s]
|
||||
@@ -238,7 +258,7 @@ def brackets(text, s, e):
|
||||
|
||||
|
||||
@postprocessor
|
||||
def quotes(text, s, e):
|
||||
def quotes(text: str, s: int, e: int) -> Tuple[int, int]:
|
||||
# Remove matching quotes
|
||||
if s < e <= len(text):
|
||||
before = text[s]
|
||||
@@ -248,7 +268,7 @@ def quotes(text, s, e):
|
||||
return s, e
|
||||
|
||||
|
||||
def mark(pattern, post_processors, text, args):
|
||||
def mark(pattern: str, post_processors: Iterable[PostprocessorFunc], text: str, args: HintsCLIOptions) -> Generator[Mark, None, None]:
|
||||
pat = re.compile(pattern)
|
||||
for idx, (s, e, groupdict) in enumerate(regex_finditer(pat, args.minimum_match_length, text)):
|
||||
for func in post_processors:
|
||||
@@ -257,7 +277,7 @@ def mark(pattern, post_processors, text, args):
|
||||
yield Mark(idx, s, e, mark_text, groupdict)
|
||||
|
||||
|
||||
def run_loop(args, text, all_marks, index_map, extra_cli_args=()):
|
||||
def run_loop(args: HintsCLIOptions, text: str, all_marks: Sequence[Mark], index_map: Dict[int, Mark], extra_cli_args: Sequence[str] = ()) -> Dict[str, Any]:
|
||||
loop = Loop()
|
||||
handler = Hints(text, all_marks, index_map, args)
|
||||
loop.loop(handler)
|
||||
@@ -265,21 +285,25 @@ def run_loop(args, text, all_marks, index_map, extra_cli_args=()):
|
||||
return {
|
||||
'match': handler.text_matches, 'programs': args.program,
|
||||
'multiple_joiner': args.multiple_joiner, 'customize_processing': args.customize_processing,
|
||||
'type': args.type, 'groupdicts': handler.groupdicts, 'extra_cli_args': extra_cli_args
|
||||
'type': args.type, 'groupdicts': handler.groupdicts, 'extra_cli_args': extra_cli_args, 'linenum_action': args.linenum_action
|
||||
}
|
||||
raise SystemExit(loop.return_code)
|
||||
|
||||
|
||||
def escape(chars):
|
||||
def escape(chars: str) -> str:
|
||||
return chars.replace('\\', '\\\\').replace('-', r'\-').replace(']', r'\]')
|
||||
|
||||
|
||||
def functions_for(args):
|
||||
def functions_for(args: HintsCLIOptions) -> Tuple[str, List[PostprocessorFunc]]:
|
||||
post_processors = []
|
||||
if args.type == 'url':
|
||||
if args.url_prefixes == 'default':
|
||||
url_prefixes = kitty_common_opts()['url_prefixes']
|
||||
else:
|
||||
url_prefixes = tuple(args.url_prefixes.split(','))
|
||||
from .url_regex import url_delimiters
|
||||
pattern = '(?:{})://[^{}]{{3,}}'.format(
|
||||
'|'.join(args.url_prefixes.split(',')), url_delimiters
|
||||
'|'.join(url_prefixes), url_delimiters
|
||||
)
|
||||
post_processors.append(url)
|
||||
elif args.type == 'path':
|
||||
@@ -292,8 +316,7 @@ def functions_for(args):
|
||||
elif args.type == 'word':
|
||||
chars = args.word_characters
|
||||
if chars is None:
|
||||
import json
|
||||
chars = json.loads(os.environ['KITTY_COMMON_OPTS'])['select_by_word_characters']
|
||||
chars = kitty_common_opts()['select_by_word_characters']
|
||||
pattern = r'(?u)[{}\w]{{{},}}'.format(escape(chars), args.minimum_match_length)
|
||||
post_processors.extend((brackets, quotes))
|
||||
else:
|
||||
@@ -301,8 +324,8 @@ def functions_for(args):
|
||||
return pattern, post_processors
|
||||
|
||||
|
||||
def convert_text(text, cols):
|
||||
lines = []
|
||||
def convert_text(text: str, cols: int) -> str:
|
||||
lines: List[str] = []
|
||||
empty_line = '\0' * cols
|
||||
for full_line in text.split('\n'):
|
||||
if full_line:
|
||||
@@ -315,29 +338,40 @@ def convert_text(text, cols):
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def parse_input(text):
|
||||
def parse_input(text: str) -> str:
|
||||
try:
|
||||
cols = int(os.environ['OVERLAID_WINDOW_COLS'])
|
||||
except KeyError:
|
||||
cols = screen_size().cols
|
||||
cols = screen_size_function()().cols
|
||||
return convert_text(text, cols)
|
||||
|
||||
|
||||
def load_custom_processor(customize_processing):
|
||||
from kitty.constants import config_dir
|
||||
customize_processing = os.path.expandvars(os.path.expanduser(customize_processing))
|
||||
if os.path.isabs(customize_processing):
|
||||
custom_path = customize_processing
|
||||
else:
|
||||
custom_path = os.path.join(config_dir, customize_processing)
|
||||
def linenum_marks(text: str, args: HintsCLIOptions, Mark: Type[Mark], extra_cli_args: Sequence[str], *a: Any) -> Generator[Mark, None, None]:
|
||||
regex = args.regex
|
||||
if regex == DEFAULT_REGEX:
|
||||
regex = r'(?P<path>(?:\S*/\S+?)|(?:\S+[.][a-zA-Z0-9]{2,7})):(?P<line>\d+)'
|
||||
yield from mark(regex, [brackets, quotes], text, args)
|
||||
|
||||
|
||||
def load_custom_processor(customize_processing: str) -> Any:
|
||||
if customize_processing.startswith('::import::'):
|
||||
import importlib
|
||||
m = importlib.import_module(customize_processing[len('::import::'):])
|
||||
return {k: getattr(m, k) for k in dir(m)}
|
||||
if customize_processing == '::linenum::':
|
||||
return {'mark': linenum_marks, 'handle_result': linenum_handle_result}
|
||||
from kitty.constants import resolve_custom_file
|
||||
custom_path = resolve_custom_file(customize_processing)
|
||||
import runpy
|
||||
return runpy.run_path(custom_path, run_name='__main__')
|
||||
|
||||
|
||||
def run(args, text, extra_cli_args=()):
|
||||
def run(args: HintsCLIOptions, text: str, extra_cli_args: Sequence[str] = ()) -> Optional[Dict[str, Any]]:
|
||||
try:
|
||||
text = parse_input(text)
|
||||
pattern, post_processors = functions_for(args)
|
||||
if args.type == 'linenum':
|
||||
args.customize_processing = '::linenum::'
|
||||
if args.customize_processing:
|
||||
m = load_custom_processor(args.customize_processing)
|
||||
if 'mark' in m:
|
||||
@@ -350,12 +384,15 @@ def run(args, text, extra_cli_args=()):
|
||||
input(_('No {} found, press Enter to quit.').format(
|
||||
'URLs' if args.type == 'url' else 'matches'
|
||||
))
|
||||
return
|
||||
return None
|
||||
|
||||
largest_index = all_marks[-1].index
|
||||
offset = max(0, args.hints_offset)
|
||||
for m in all_marks:
|
||||
m.index = largest_index - m.index + offset
|
||||
if args.ascending:
|
||||
m.index += offset
|
||||
else:
|
||||
m.index = largest_index - m.index + offset
|
||||
index_map = {m.index: m for m in all_marks}
|
||||
except Exception:
|
||||
import traceback
|
||||
@@ -372,19 +409,25 @@ OPTIONS = r'''
|
||||
type=list
|
||||
What program to use to open matched text. Defaults to the default open program
|
||||
for the operating system. Use a value of :file:`-` to paste the match into the
|
||||
terminal window instead. A value of :file:`@` will copy the match to the clipboard.
|
||||
A value of :file:`default` will run the default open program. Can be specified
|
||||
multiple times to run multiple programs.
|
||||
terminal window instead. A value of :file:`@` will copy the match to the
|
||||
clipboard. A value of :file:`*` will copy the match to the primary selection
|
||||
(on systems that support primary selections). A value of :file:`default` will
|
||||
run the default open program. Can be specified multiple times to run multiple
|
||||
programs.
|
||||
|
||||
|
||||
--type
|
||||
default=url
|
||||
choices=url,regex,path,line,hash,word
|
||||
The type of text to search for.
|
||||
choices=url,regex,path,line,hash,word,linenum
|
||||
The type of text to search for. A value of :code:`linenum` is special, it looks
|
||||
for error messages using the pattern specified with :option:`--regex`, which
|
||||
must have the named groups, :code:`path` and :code:`line`. If not specified,
|
||||
will look for :code:`path:line`. The :option:`--linenum-action` option
|
||||
controls what to do with the selected error message, other options are ignored.
|
||||
|
||||
|
||||
--regex
|
||||
default=(?m)^\s*(.+)\s*$
|
||||
default={default_regex}
|
||||
The regular expression to use when :option:`kitty +kitten hints --type`=regex.
|
||||
The regular expression is in python syntax. If you specify a numbered group in
|
||||
the regular expression only the group will be matched. This allow you to match
|
||||
@@ -396,9 +439,23 @@ the program will be passed arguments corresponding to each named group of
|
||||
the form key=value.
|
||||
|
||||
|
||||
--linenum-action
|
||||
default=self
|
||||
type=choice
|
||||
choices=self,window,tab,os_window,background
|
||||
The action to perform on the matched errors. The actual action is whatever
|
||||
arguments are provided to the kitten, for example:
|
||||
:code:`kitty + kitten hints --type=linenum vim +{line} {path}`
|
||||
will open the matched path at the matched line number in vim. This option
|
||||
controls where the action is executed: :code:`self` means the current window,
|
||||
:code:`window` a new kitty window, :code:`tab` a new tab, :code:`os_window`
|
||||
a new OS window and :code:`background` run in the background.
|
||||
|
||||
|
||||
--url-prefixes
|
||||
default={0}
|
||||
Comma separated list of recognized URL prefixes.
|
||||
default=default
|
||||
Comma separated list of recognized URL prefixes. Defaults, to
|
||||
the list of prefixes defined in kitty.conf.
|
||||
|
||||
|
||||
--word-characters
|
||||
@@ -451,6 +508,11 @@ unless you specify the hints offset as zero the first match will be highlighted
|
||||
the second character you specify.
|
||||
|
||||
|
||||
--ascending
|
||||
type=bool-set
|
||||
Have the hints increase from top to bottom instead of decreasing from top to bottom.
|
||||
|
||||
|
||||
--customize-processing
|
||||
Name of a python file in the kitty config directory which will be imported to provide
|
||||
custom implementations for pattern finding and performing actions
|
||||
@@ -458,58 +520,93 @@ on selected matches. See https://sw.kovidgoyal.net/kitty/kittens/hints.html
|
||||
for details. You can also specify absolute paths to load the script from elsewhere.
|
||||
|
||||
|
||||
'''.format(','.join(sorted(URL_PREFIXES))).format
|
||||
'''.format(
|
||||
default_regex=DEFAULT_REGEX,
|
||||
line='{{line}}', path='{{path}}'
|
||||
).format
|
||||
help_text = 'Select text from the screen using the keyboard. Defaults to searching for URLs.'
|
||||
usage = ''
|
||||
|
||||
|
||||
def parse_hints_args(args):
|
||||
return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten hints')
|
||||
def parse_hints_args(args: List[str]) -> Tuple[HintsCLIOptions, List[str]]:
|
||||
return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten hints', result_class=HintsCLIOptions)
|
||||
|
||||
|
||||
def main(args):
|
||||
def main(args: List[str]) -> Optional[Dict[str, Any]]:
|
||||
text = ''
|
||||
if sys.stdin.isatty():
|
||||
if '--help' not in args and '-h' not in args:
|
||||
print('You must pass the text to be hinted on STDIN', file=sys.stderr)
|
||||
input(_('Press Enter to quit'))
|
||||
return
|
||||
return None
|
||||
else:
|
||||
text = sys.stdin.buffer.read().decode('utf-8')
|
||||
sys.stdin = open(os.ctermid())
|
||||
try:
|
||||
args, items = parse_hints_args(args[1:])
|
||||
opts, items = parse_hints_args(args[1:])
|
||||
except SystemExit as e:
|
||||
if e.code != 0:
|
||||
print(e.args[0], file=sys.stderr)
|
||||
input(_('Press Enter to quit'))
|
||||
return
|
||||
if items and not args.customize_processing:
|
||||
return None
|
||||
if items and not (opts.customize_processing or opts.type == 'linenum'):
|
||||
print('Extra command line arguments present: {}'.format(' '.join(items)), file=sys.stderr)
|
||||
input(_('Press Enter to quit'))
|
||||
return run(args, text, items)
|
||||
return run(opts, text, items)
|
||||
|
||||
|
||||
def handle_result(args, data, target_window_id, boss):
|
||||
def linenum_handle_result(args: List[str], data: Dict[str, Any], target_window_id: int, boss: BossType, extra_cli_args: Sequence[str], *a: Any) -> None:
|
||||
for m, g in zip(data['match'], data['groupdicts']):
|
||||
if m:
|
||||
path, line = g['path'], g['line']
|
||||
path = path.split(':')[-1]
|
||||
line = int(line)
|
||||
break
|
||||
else:
|
||||
return
|
||||
|
||||
cmd = [x.format(path=path, line=line) for x in extra_cli_args or ('vim', '+{line}', '{path}')]
|
||||
w = boss.window_id_map.get(target_window_id)
|
||||
action = data['linenum_action']
|
||||
|
||||
if action == 'self':
|
||||
if w is not None:
|
||||
import shlex
|
||||
text = ' '.join(shlex.quote(arg) for arg in cmd)
|
||||
w.paste_bytes(text + '\r')
|
||||
elif action == 'background':
|
||||
import subprocess
|
||||
subprocess.Popen(cmd)
|
||||
else:
|
||||
getattr(boss, {
|
||||
'window': 'new_window_with_cwd', 'tab': 'new_tab_with_cwd', 'os_window': 'new_os_window_with_cwd'
|
||||
}[action])(*cmd)
|
||||
|
||||
|
||||
@result_handler(type_of_input='screen')
|
||||
def handle_result(args: List[str], data: Dict[str, Any], target_window_id: int, boss: BossType) -> None:
|
||||
if data['customize_processing']:
|
||||
m = load_custom_processor(data['customize_processing'])
|
||||
if 'handle_result' in m:
|
||||
return m['handle_result'](args, data, target_window_id, boss, data['extra_cli_args'])
|
||||
m['handle_result'](args, data, target_window_id, boss, data['extra_cli_args'])
|
||||
return None
|
||||
|
||||
programs = data['programs'] or ('default',)
|
||||
matches, groupdicts = [], []
|
||||
matches: List[str] = []
|
||||
groupdicts = []
|
||||
for m, g in zip(data['match'], data['groupdicts']):
|
||||
if m:
|
||||
matches.append(m), groupdicts.append(g)
|
||||
matches.append(m)
|
||||
groupdicts.append(g)
|
||||
joiner = data['multiple_joiner']
|
||||
try:
|
||||
is_int = int(joiner)
|
||||
is_int: Optional[int] = int(joiner)
|
||||
except Exception:
|
||||
is_int = None
|
||||
text_type = data['type']
|
||||
|
||||
@lru_cache()
|
||||
def joined_text():
|
||||
def joined_text() -> str:
|
||||
if is_int is not None:
|
||||
try:
|
||||
return matches[is_int]
|
||||
@@ -531,6 +628,8 @@ def handle_result(args, data, target_window_id, boss):
|
||||
w.paste(joined_text())
|
||||
elif program == '@':
|
||||
set_clipboard_string(joined_text())
|
||||
elif program == '*':
|
||||
set_primary_selection(joined_text())
|
||||
else:
|
||||
cwd = None
|
||||
w = boss.window_id_map.get(target_window_id)
|
||||
@@ -545,16 +644,14 @@ def handle_result(args, data, target_window_id, boss):
|
||||
boss.open_url(m, program, cwd=cwd)
|
||||
|
||||
|
||||
handle_result.type_of_input = 'screen'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Run with kitty +kitten hints
|
||||
ans = main(sys.argv)
|
||||
if ans:
|
||||
print(ans)
|
||||
elif __name__ == '__doc__':
|
||||
sys.cli_docs['usage'] = usage
|
||||
sys.cli_docs['options'] = OPTIONS
|
||||
sys.cli_docs['help_text'] = help_text
|
||||
cd = sys.cli_docs # type: ignore
|
||||
cd['usage'] = usage
|
||||
cd['options'] = OPTIONS
|
||||
cd['help_text'] = help_text
|
||||
# }}}
|
||||
|
||||
@@ -2,25 +2,35 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import contextlib
|
||||
import mimetypes
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
import signal
|
||||
import sys
|
||||
import zlib
|
||||
from base64 import standard_b64encode
|
||||
from collections import namedtuple
|
||||
from functools import lru_cache
|
||||
from math import ceil
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import (
|
||||
Dict, Generator, List, NamedTuple, Optional, Pattern, Tuple, Union
|
||||
)
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import IcatCLIOptions
|
||||
from kitty.constants import appname
|
||||
from kitty.utils import TTYIO, fit_image, screen_size_function
|
||||
from kitty.typing import GRT_f, GRT_t
|
||||
from kitty.utils import (
|
||||
TTYIO, ScreenSize, ScreenSizeGetter, fit_image, screen_size_function
|
||||
)
|
||||
|
||||
from ..tui.images import (
|
||||
ConvertFailed, NoImageMagick, OpenFailed, convert, fsenc, identify
|
||||
ConvertFailed, GraphicsCommand, NoImageMagick, OpenFailed, convert, fsenc,
|
||||
identify
|
||||
)
|
||||
from ..tui.operations import clear_images_on_screen, serialize_gr_command
|
||||
from ..tui.operations import clear_images_on_screen
|
||||
|
||||
OPTIONS = '''\
|
||||
--align
|
||||
@@ -93,38 +103,43 @@ but you can turn it off or on explicitly, if needed.
|
||||
--silent
|
||||
type=bool-set
|
||||
Do not print out anything to stdout during operation.
|
||||
|
||||
|
||||
--z-index -z
|
||||
default=0
|
||||
Z-index of the image. When negative, text will be displayed on top of the image. Use
|
||||
a double minus for values under the threshold for drawing images under cell background
|
||||
colors. For example, --1 evaluates as -1,073,741,825.
|
||||
'''
|
||||
|
||||
|
||||
screen_size = None
|
||||
screen_size: Optional[ScreenSizeGetter] = None
|
||||
can_transfer_with_files = False
|
||||
|
||||
|
||||
def get_screen_size_function():
|
||||
def get_screen_size_function() -> ScreenSizeGetter:
|
||||
global screen_size
|
||||
if screen_size is None:
|
||||
screen_size = screen_size_function()
|
||||
return screen_size
|
||||
|
||||
|
||||
def get_screen_size():
|
||||
def get_screen_size() -> ScreenSize:
|
||||
screen_size = get_screen_size_function()
|
||||
return screen_size()
|
||||
|
||||
|
||||
def options_spec():
|
||||
if not hasattr(options_spec, 'ans'):
|
||||
options_spec.ans = OPTIONS.format(
|
||||
appname='{}-icat'.format(appname),
|
||||
)
|
||||
return options_spec.ans
|
||||
@lru_cache(maxsize=2)
|
||||
def options_spec() -> str:
|
||||
return OPTIONS.format(appname='{}-icat'.format(appname))
|
||||
|
||||
|
||||
def write_gr_cmd(cmd, payload=None):
|
||||
sys.stdout.buffer.write(serialize_gr_command(cmd, payload))
|
||||
def write_gr_cmd(cmd: GraphicsCommand, payload: Optional[bytes] = None) -> None:
|
||||
sys.stdout.buffer.write(cmd.serialize(payload or b''))
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def calculate_in_cell_x_offset(width, cell_width, align):
|
||||
def calculate_in_cell_x_offset(width: int, cell_width: int, align: str) -> int:
|
||||
if align == 'left':
|
||||
return 0
|
||||
extra_pixels = width % cell_width
|
||||
@@ -135,7 +150,7 @@ def calculate_in_cell_x_offset(width, cell_width, align):
|
||||
return (cell_width - extra_pixels) // 2
|
||||
|
||||
|
||||
def set_cursor(cmd, width, height, align):
|
||||
def set_cursor(cmd: GraphicsCommand, width: int, height: int, align: str) -> None:
|
||||
ss = get_screen_size()
|
||||
cw = int(ss.width / ss.cols)
|
||||
num_of_cells_needed = int(ceil(width / cw))
|
||||
@@ -143,9 +158,9 @@ def set_cursor(cmd, width, height, align):
|
||||
w, h = fit_image(width, height, ss.width, height)
|
||||
ch = int(ss.height / ss.rows)
|
||||
num_of_rows_needed = int(ceil(height / ch))
|
||||
cmd['c'], cmd['r'] = ss.cols, num_of_rows_needed
|
||||
cmd.c, cmd.r = ss.cols, num_of_rows_needed
|
||||
else:
|
||||
cmd['X'] = calculate_in_cell_x_offset(width, cw, align)
|
||||
cmd.X = calculate_in_cell_x_offset(width, cw, align)
|
||||
extra_cells = 0
|
||||
if align == 'center':
|
||||
extra_cells = (ss.cols - num_of_cells_needed) // 2
|
||||
@@ -155,12 +170,12 @@ def set_cursor(cmd, width, height, align):
|
||||
sys.stdout.buffer.write(b' ' * extra_cells)
|
||||
|
||||
|
||||
def set_cursor_for_place(place, cmd, width, height, align):
|
||||
def set_cursor_for_place(place: 'Place', cmd: GraphicsCommand, width: int, height: int, align: str) -> None:
|
||||
x = place.left + 1
|
||||
ss = get_screen_size()
|
||||
cw = int(ss.width / ss.cols)
|
||||
num_of_cells_needed = int(ceil(width / cw))
|
||||
cmd['X'] = calculate_in_cell_x_offset(width, cw, align)
|
||||
cmd.X = calculate_in_cell_x_offset(width, cw, align)
|
||||
extra_cells = 0
|
||||
if align == 'center':
|
||||
extra_cells = (place.width - num_of_cells_needed) // 2
|
||||
@@ -169,27 +184,38 @@ def set_cursor_for_place(place, cmd, width, height, align):
|
||||
sys.stdout.buffer.write('\033[{};{}H'.format(place.top + 1, x + extra_cells).encode('ascii'))
|
||||
|
||||
|
||||
def write_chunked(cmd, data):
|
||||
if cmd['f'] != 100:
|
||||
def write_chunked(cmd: GraphicsCommand, data: bytes) -> None:
|
||||
if cmd.f != 100:
|
||||
data = zlib.compress(data)
|
||||
cmd['o'] = 'z'
|
||||
cmd.o = 'z'
|
||||
data = standard_b64encode(data)
|
||||
while data:
|
||||
chunk, data = data[:4096], data[4096:]
|
||||
m = 1 if data else 0
|
||||
cmd['m'] = m
|
||||
cmd.m = 1 if data else 0
|
||||
write_gr_cmd(cmd, chunk)
|
||||
cmd.clear()
|
||||
|
||||
|
||||
def show(outfile, width, height, fmt, transmit_mode='t', align='center', place=None):
|
||||
cmd = {'a': 'T', 'f': fmt, 's': width, 'v': height}
|
||||
def show(
|
||||
outfile: str,
|
||||
width: int, height: int, zindex: int,
|
||||
fmt: 'GRT_f',
|
||||
transmit_mode: 'GRT_t' = 't',
|
||||
align: str = 'center',
|
||||
place: Optional['Place'] = None
|
||||
) -> None:
|
||||
cmd = GraphicsCommand()
|
||||
cmd.a = 'T'
|
||||
cmd.f = fmt
|
||||
cmd.s = width
|
||||
cmd.v = height
|
||||
cmd.z = zindex
|
||||
if place:
|
||||
set_cursor_for_place(place, cmd, width, height, align)
|
||||
else:
|
||||
set_cursor(cmd, width, height, align)
|
||||
if detect_support.has_files:
|
||||
cmd['t'] = transmit_mode
|
||||
if can_transfer_with_files:
|
||||
cmd.t = transmit_mode
|
||||
write_gr_cmd(cmd, standard_b64encode(os.path.abspath(outfile).encode(fsenc)))
|
||||
else:
|
||||
with open(outfile, 'rb') as f:
|
||||
@@ -197,35 +223,49 @@ def show(outfile, width, height, fmt, transmit_mode='t', align='center', place=N
|
||||
if transmit_mode == 't':
|
||||
os.unlink(outfile)
|
||||
if fmt == 100:
|
||||
cmd['S'] = len(data)
|
||||
cmd.S = len(data)
|
||||
write_chunked(cmd, data)
|
||||
|
||||
|
||||
def process(path, args, is_tempfile):
|
||||
def parse_z_index(val: str) -> int:
|
||||
origin = 0
|
||||
if val.startswith('--'):
|
||||
val = val[1:]
|
||||
origin = -1073741824
|
||||
return origin + int(val)
|
||||
|
||||
|
||||
class ParsedOpts:
|
||||
|
||||
place: Optional['Place'] = None
|
||||
z_index: int = 0
|
||||
|
||||
|
||||
def process(path: str, args: IcatCLIOptions, parsed_opts: ParsedOpts, is_tempfile: bool) -> bool:
|
||||
m = identify(path)
|
||||
ss = get_screen_size()
|
||||
available_width = args.place.width * (ss.width / ss.cols) if args.place else ss.width
|
||||
available_height = args.place.height * (ss.height / ss.rows) if args.place else 10 * m.height
|
||||
available_width = parsed_opts.place.width * (ss.width // ss.cols) if parsed_opts.place else ss.width
|
||||
available_height = parsed_opts.place.height * (ss.height // ss.rows) if parsed_opts.place else 10 * m.height
|
||||
needs_scaling = m.width > available_width or m.height > available_height
|
||||
needs_scaling = needs_scaling or args.scale_up
|
||||
file_removed = False
|
||||
if m.fmt == 'png' and not needs_scaling:
|
||||
outfile = path
|
||||
transmit_mode = 't' if is_tempfile else 'f'
|
||||
fmt = 100
|
||||
transmit_mode: 'GRT_t' = 't' if is_tempfile else 'f'
|
||||
fmt: 'GRT_f' = 100
|
||||
width, height = m.width, m.height
|
||||
file_removed = transmit_mode == 't'
|
||||
else:
|
||||
fmt = 24 if m.mode == 'rgb' else 32
|
||||
transmit_mode = 't'
|
||||
outfile, width, height = convert(path, m, available_width, available_height, args.scale_up)
|
||||
show(outfile, width, height, fmt, transmit_mode, align=args.align, place=args.place)
|
||||
show(outfile, width, height, parsed_opts.z_index, fmt, transmit_mode, align=args.align, place=parsed_opts.place)
|
||||
if not args.place:
|
||||
print() # ensure cursor is on a new line
|
||||
return file_removed
|
||||
|
||||
|
||||
def scan(d):
|
||||
def scan(d: str) -> Generator[Tuple[str, str], None, None]:
|
||||
for dirpath, dirnames, filenames in os.walk(d):
|
||||
for f in filenames:
|
||||
mt = mimetypes.guess_type(f)[0]
|
||||
@@ -233,23 +273,24 @@ def scan(d):
|
||||
yield os.path.join(dirpath, f), mt
|
||||
|
||||
|
||||
def detect_support(wait_for=10, silent=False):
|
||||
def detect_support(wait_for: float = 10, silent: bool = False) -> bool:
|
||||
global can_transfer_with_files
|
||||
if not silent:
|
||||
print('Checking for graphics ({}s max. wait)...'.format(wait_for), end='\r')
|
||||
sys.stdout.flush()
|
||||
try:
|
||||
received = b''
|
||||
responses = {}
|
||||
responses: Dict[int, bool] = {}
|
||||
|
||||
def parse_responses():
|
||||
def parse_responses() -> None:
|
||||
for m in re.finditer(b'\033_Gi=([1|2]);(.+?)\033\\\\', received):
|
||||
iid = m.group(1)
|
||||
if iid in (b'1', b'2'):
|
||||
iid = int(iid.decode('ascii'))
|
||||
if iid not in responses:
|
||||
responses[iid] = m.group(2) == b'OK'
|
||||
iid_ = int(iid.decode('ascii'))
|
||||
if iid_ not in responses:
|
||||
responses[iid_] = m.group(2) == b'OK'
|
||||
|
||||
def more_needed(data):
|
||||
def more_needed(data: bytes) -> bool:
|
||||
nonlocal received
|
||||
received += data
|
||||
parse_responses()
|
||||
@@ -257,23 +298,36 @@ def detect_support(wait_for=10, silent=False):
|
||||
|
||||
with NamedTemporaryFile() as f:
|
||||
f.write(b'abcd'), f.flush()
|
||||
write_gr_cmd(dict(a='q', s=1, v=1, i=1), standard_b64encode(b'abcd'))
|
||||
write_gr_cmd(dict(a='q', s=1, v=1, i=2, t='f'), standard_b64encode(f.name.encode(fsenc)))
|
||||
gc = GraphicsCommand()
|
||||
gc.a = 'q'
|
||||
gc.s = gc.v = gc.i = 1
|
||||
write_gr_cmd(gc, standard_b64encode(b'abcd'))
|
||||
gc.t = 'f'
|
||||
gc.i = 2
|
||||
write_gr_cmd(gc, standard_b64encode(f.name.encode(fsenc)))
|
||||
with TTYIO() as io:
|
||||
io.recv(more_needed, timeout=float(wait_for))
|
||||
io.recv(more_needed, timeout=wait_for)
|
||||
finally:
|
||||
if not silent:
|
||||
sys.stdout.buffer.write(b'\033[J'), sys.stdout.flush()
|
||||
detect_support.has_files = bool(responses.get(2))
|
||||
can_transfer_with_files = bool(responses.get(2))
|
||||
return responses.get(1, False)
|
||||
|
||||
|
||||
def parse_place(raw):
|
||||
class Place(NamedTuple):
|
||||
width: int
|
||||
height: int
|
||||
left: int
|
||||
top: int
|
||||
|
||||
|
||||
def parse_place(raw: str) -> Optional[Place]:
|
||||
if raw:
|
||||
area, pos = raw.split('@', 1)
|
||||
w, h = map(int, area.split('x'))
|
||||
l, t = map(int, pos.split('x'))
|
||||
return namedtuple('Place', 'width height left top')(w, h, l, t)
|
||||
return Place(w, h, l, t)
|
||||
return None
|
||||
|
||||
|
||||
help_text = (
|
||||
@@ -287,7 +341,23 @@ help_text = (
|
||||
usage = 'image-file-or-url-or-directory ...'
|
||||
|
||||
|
||||
def process_single_item(item, args, url_pat=None, maybe_dir=True):
|
||||
@contextlib.contextmanager
|
||||
def socket_timeout(seconds: int) -> Generator[None, None, None]:
|
||||
old = socket.getdefaulttimeout()
|
||||
socket.setdefaulttimeout(seconds)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
socket.setdefaulttimeout(old)
|
||||
|
||||
|
||||
def process_single_item(
|
||||
item: Union[bytes, str],
|
||||
args: IcatCLIOptions,
|
||||
parsed_opts: ParsedOpts,
|
||||
url_pat: Optional[Pattern] = None,
|
||||
maybe_dir: bool = True
|
||||
) -> None:
|
||||
is_tempfile = False
|
||||
file_removed = False
|
||||
try:
|
||||
@@ -300,38 +370,41 @@ def process_single_item(item, args, url_pat=None, maybe_dir=True):
|
||||
from urllib.request import urlretrieve
|
||||
with NamedTemporaryFile(prefix='url-image-data-', delete=False) as tf:
|
||||
try:
|
||||
urlretrieve(item, filename=tf.name)
|
||||
with socket_timeout(30):
|
||||
urlretrieve(item, filename=tf.name)
|
||||
except Exception as e:
|
||||
raise SystemExit('Failed to download image at URL: {} with error: {}'.format(item, e))
|
||||
item = tf.name
|
||||
is_tempfile = True
|
||||
file_removed = process(item, args, is_tempfile)
|
||||
file_removed = process(item, args, parsed_opts, is_tempfile)
|
||||
elif item.lower().startswith('file://'):
|
||||
from urllib.parse import urlparse
|
||||
from urllib.request import url2pathname
|
||||
item = urlparse(item)
|
||||
pitem = urlparse(item)
|
||||
if os.sep == '\\':
|
||||
item = item.netloc + item.path
|
||||
item = pitem.netloc + pitem.path
|
||||
else:
|
||||
item = item.path
|
||||
item = pitem.path
|
||||
item = url2pathname(item)
|
||||
file_removed = process(item, args, is_tempfile)
|
||||
file_removed = process(item, args, parsed_opts, is_tempfile)
|
||||
else:
|
||||
if maybe_dir and os.path.isdir(item):
|
||||
for (x, mt) in scan(item):
|
||||
process_single_item(x, args, url_pat=None, maybe_dir=False)
|
||||
process_single_item(x, args, parsed_opts, url_pat=None, maybe_dir=False)
|
||||
else:
|
||||
file_removed = process(item, args, is_tempfile)
|
||||
file_removed = process(item, args, parsed_opts, is_tempfile)
|
||||
finally:
|
||||
if is_tempfile and not file_removed:
|
||||
os.remove(item)
|
||||
|
||||
|
||||
def main(args=sys.argv):
|
||||
args, items = parse_args(args[1:], options_spec, usage, help_text, '{} +kitten icat'.format(appname))
|
||||
def main(args: List[str] = sys.argv) -> None:
|
||||
global can_transfer_with_files
|
||||
cli_opts, items_ = parse_args(args[1:], options_spec, usage, help_text, '{} +kitten icat'.format(appname), result_class=IcatCLIOptions)
|
||||
items: List[Union[str, bytes]] = list(items_)
|
||||
|
||||
if args.print_window_size:
|
||||
screen_size_function.ans = None
|
||||
if cli_opts.print_window_size:
|
||||
screen_size_function.cache_clear()
|
||||
with open(os.ctermid()) as tty:
|
||||
ss = screen_size_function(tty)()
|
||||
print('{}x{}'.format(ss.width, ss.height), end='')
|
||||
@@ -340,7 +413,7 @@ def main(args=sys.argv):
|
||||
if not sys.stdout.isatty():
|
||||
sys.stdout = open(os.ctermid(), 'w')
|
||||
stdin_data = None
|
||||
if args.stdin == 'yes' or (not sys.stdin.isatty() and args.stdin == 'detect'):
|
||||
if cli_opts.stdin == 'yes' or (not sys.stdin.isatty() and cli_opts.stdin == 'detect'):
|
||||
stdin_data = sys.stdin.buffer.read()
|
||||
if stdin_data:
|
||||
items.insert(0, stdin_data)
|
||||
@@ -350,48 +423,55 @@ def main(args=sys.argv):
|
||||
screen_size = get_screen_size_function()
|
||||
signal.signal(signal.SIGWINCH, lambda signum, frame: setattr(screen_size, 'changed', True))
|
||||
if screen_size().width == 0:
|
||||
if args.detect_support:
|
||||
if cli_opts.detect_support:
|
||||
raise SystemExit(1)
|
||||
raise SystemExit(
|
||||
'Terminal does not support reporting screen sizes via the TIOCGWINSZ ioctl'
|
||||
)
|
||||
try:
|
||||
args.place = parse_place(args.place)
|
||||
except Exception:
|
||||
raise SystemExit('Not a valid place specification: {}'.format(args.place))
|
||||
parsed_opts = ParsedOpts()
|
||||
if cli_opts.place:
|
||||
try:
|
||||
parsed_opts.place = parse_place(cli_opts.place)
|
||||
except Exception:
|
||||
raise SystemExit('Not a valid place specification: {}'.format(cli_opts.place))
|
||||
|
||||
if args.detect_support:
|
||||
if not detect_support(wait_for=args.detection_timeout, silent=True):
|
||||
try:
|
||||
parsed_opts.z_index = parse_z_index(cli_opts.z_index)
|
||||
except Exception:
|
||||
raise SystemExit('Not a valid z-index specification: {}'.format(cli_opts.z_index))
|
||||
|
||||
if cli_opts.detect_support:
|
||||
if not detect_support(wait_for=cli_opts.detection_timeout, silent=True):
|
||||
raise SystemExit(1)
|
||||
print('file' if detect_support.has_files else 'stream', end='', file=sys.stderr)
|
||||
print('file' if can_transfer_with_files else 'stream', end='', file=sys.stderr)
|
||||
return
|
||||
if args.transfer_mode == 'detect':
|
||||
if not detect_support(wait_for=args.detection_timeout, silent=args.silent):
|
||||
if cli_opts.transfer_mode == 'detect':
|
||||
if not detect_support(wait_for=cli_opts.detection_timeout, silent=cli_opts.silent):
|
||||
raise SystemExit('This terminal emulator does not support the graphics protocol, use a terminal emulator such as kitty that does support it')
|
||||
else:
|
||||
detect_support.has_files = args.transfer_mode == 'file'
|
||||
can_transfer_with_files = cli_opts.transfer_mode == 'file'
|
||||
errors = []
|
||||
if args.clear:
|
||||
sys.stdout.buffer.write(clear_images_on_screen(delete_data=True))
|
||||
if cli_opts.clear:
|
||||
sys.stdout.write(clear_images_on_screen(delete_data=True))
|
||||
if not items:
|
||||
return
|
||||
if not items:
|
||||
raise SystemExit('You must specify at least one file to cat')
|
||||
if args.place:
|
||||
if parsed_opts.place:
|
||||
if len(items) > 1 or (isinstance(items[0], str) and os.path.isdir(items[0])):
|
||||
raise SystemExit(f'The --place option can only be used with a single image, not {items}')
|
||||
sys.stdout.buffer.write(b'\0337') # save cursor
|
||||
url_pat = re.compile(r'(?:https?|ftp)://', flags=re.I)
|
||||
for item in items:
|
||||
try:
|
||||
process_single_item(item, args, url_pat)
|
||||
process_single_item(item, cli_opts, parsed_opts, url_pat)
|
||||
except NoImageMagick as e:
|
||||
raise SystemExit(str(e))
|
||||
except ConvertFailed as e:
|
||||
raise SystemExit(str(e))
|
||||
except OpenFailed as e:
|
||||
errors.append(e)
|
||||
if args.place:
|
||||
if parsed_opts.place:
|
||||
sys.stdout.buffer.write(b'\0338') # restore cursor
|
||||
if not errors:
|
||||
return
|
||||
@@ -403,6 +483,7 @@ def main(args=sys.argv):
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
elif __name__ == '__doc__':
|
||||
sys.cli_docs['usage'] = usage
|
||||
sys.cli_docs['options'] = options_spec
|
||||
sys.cli_docs['help_text'] = help_text
|
||||
cd = sys.cli_docs # type: ignore
|
||||
cd['usage'] = usage
|
||||
cd['options'] = options_spec
|
||||
cd['help_text'] = help_text
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
from kitty.key_encoding import (
|
||||
ALT, CTRL, PRESS, RELEASE, REPEAT, SHIFT, SUPER, encode_key_event
|
||||
ALT, CTRL, PRESS, RELEASE, REPEAT, SHIFT, SUPER, KeyEvent,
|
||||
encode_key_event
|
||||
)
|
||||
|
||||
from ..tui.handler import Handler
|
||||
@@ -14,41 +16,41 @@ from ..tui.loop import Loop
|
||||
|
||||
class KeysHandler(Handler):
|
||||
|
||||
def initialize(self):
|
||||
def initialize(self) -> None:
|
||||
self.cmd.set_window_title('Kitty extended keyboard protocol demo')
|
||||
self.cmd.set_cursor_visible(False)
|
||||
self.print('Press any keys - Ctrl+C or Ctrl+D will terminate')
|
||||
|
||||
def on_text(self, text, in_bracketed_paste=False):
|
||||
def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
|
||||
self.print('Text input: ' + text)
|
||||
|
||||
def on_key(self, key_event):
|
||||
def on_key(self, key_event: KeyEvent) -> None:
|
||||
etype = {
|
||||
PRESS: 'PRESS',
|
||||
REPEAT: 'REPEAT',
|
||||
RELEASE: 'RELEASE'
|
||||
}[key_event.type]
|
||||
mods = []
|
||||
lmods = []
|
||||
for m, name in {
|
||||
SHIFT: 'Shift',
|
||||
ALT: 'Alt',
|
||||
CTRL: 'Ctrl',
|
||||
SUPER: 'Super'}.items():
|
||||
if key_event.mods & m:
|
||||
mods.append(name)
|
||||
mods = '+'.join(mods)
|
||||
lmods.append(name)
|
||||
mods = '+'.join(lmods)
|
||||
if mods:
|
||||
mods += '+'
|
||||
self.print('Key {}: {}{} [{}]'.format(etype, mods, key_event.key, encode_key_event(key_event)))
|
||||
|
||||
def on_interrupt(self):
|
||||
def on_interrupt(self) -> None:
|
||||
self.quit_loop(0)
|
||||
|
||||
def on_eot(self):
|
||||
def on_eot(self) -> None:
|
||||
self.quit_loop(0)
|
||||
|
||||
|
||||
def main(args):
|
||||
def main(args: List[str]) -> None:
|
||||
loop = Loop()
|
||||
handler = KeysHandler()
|
||||
loop.loop(handler)
|
||||
|
||||
@@ -6,8 +6,11 @@ import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Callable, Dict, List, Tuple
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import PanelCLIOptions
|
||||
from kitty.options_stub import Options
|
||||
from kitty.constants import is_macos
|
||||
|
||||
OPTIONS = r'''
|
||||
@@ -42,19 +45,19 @@ Syntax: :italic:`name=value`. For example: :option:`kitty +kitten panel -o` font
|
||||
'''.format
|
||||
|
||||
|
||||
args = None
|
||||
args = PanelCLIOptions()
|
||||
help_text = 'Use a command line program to draw a GPU accelerated panel on your X11 desktop'
|
||||
usage = 'program-to-run'
|
||||
|
||||
|
||||
def parse_panel_args(args):
|
||||
return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten panel')
|
||||
def parse_panel_args(args: List[str]) -> Tuple[PanelCLIOptions, List[str]]:
|
||||
return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten panel', result_class=PanelCLIOptions)
|
||||
|
||||
|
||||
def call_xprop(*cmd, silent=False):
|
||||
cmd = ['xprop'] + list(cmd)
|
||||
def call_xprop(*cmd: str, silent: bool = False) -> None:
|
||||
cmd_ = ['xprop'] + list(cmd)
|
||||
try:
|
||||
cp = subprocess.run(cmd, stdout=subprocess.DEVNULL if silent else None)
|
||||
cp = subprocess.run(cmd_, stdout=subprocess.DEVNULL if silent else None)
|
||||
except FileNotFoundError:
|
||||
raise SystemExit('You must have the xprop program installed')
|
||||
if cp.returncode != 0:
|
||||
@@ -62,11 +65,11 @@ def call_xprop(*cmd, silent=False):
|
||||
|
||||
|
||||
def create_strut(
|
||||
win_id,
|
||||
left=0, right=0, top=0, bottom=0, left_start_y=0, left_end_y=0,
|
||||
right_start_y=0, right_end_y=0, top_start_x=0, top_end_x=0,
|
||||
bottom_start_x=0, bottom_end_x=0
|
||||
):
|
||||
win_id: int,
|
||||
left: int = 0, right: int = 0, top: int = 0, bottom: int = 0, left_start_y: int = 0, left_end_y: int = 0,
|
||||
right_start_y: int = 0, right_end_y: int = 0, top_start_x: int = 0, top_end_x: int = 0,
|
||||
bottom_start_x: int = 0, bottom_end_x: int = 0
|
||||
) -> None:
|
||||
call_xprop(
|
||||
'-id',
|
||||
str(int(win_id)), '-format', '_NET_WM_STRUT_PARTIAL', '32cccccccccccc',
|
||||
@@ -77,50 +80,54 @@ def create_strut(
|
||||
)
|
||||
|
||||
|
||||
def create_top_strut(win_id, width, height):
|
||||
def create_top_strut(win_id: int, width: int, height: int) -> None:
|
||||
create_strut(win_id, top=height, top_end_x=width)
|
||||
|
||||
|
||||
def create_bottom_strut(win_id, width, height):
|
||||
def create_bottom_strut(win_id: int, width: int, height: int) -> None:
|
||||
create_strut(win_id, bottom=height, bottom_end_x=width)
|
||||
|
||||
|
||||
def create_left_strut(win_id, width, height):
|
||||
def create_left_strut(win_id: int, width: int, height: int) -> None:
|
||||
create_strut(win_id, left=width, left_end_y=height)
|
||||
|
||||
|
||||
def create_right_strut(win_id, width, height):
|
||||
def create_right_strut(win_id: int, width: int, height: int) -> None:
|
||||
create_strut(win_id, right=width, right_end_y=height)
|
||||
|
||||
|
||||
def setup_x11_window(win_id):
|
||||
window_width = window_height = 0
|
||||
|
||||
|
||||
def setup_x11_window(win_id: int) -> None:
|
||||
call_xprop(
|
||||
'-id', str(win_id), '-format', '_NET_WM_WINDOW_TYPE', '32a',
|
||||
'-set', '_NET_WM_WINDOW_TYPE', '_NET_WM_WINDOW_TYPE_DOCK'
|
||||
)
|
||||
func = globals()['create_{}_strut'.format(args.edge)]
|
||||
func(win_id, initial_window_size_func.width, initial_window_size_func.height)
|
||||
func(win_id, window_width, window_height)
|
||||
|
||||
|
||||
def initial_window_size_func(opts, *a):
|
||||
def initial_window_size_func(opts: Options, cached_values: Dict) -> Callable[[int, int, float, float, float, float], Tuple[int, int]]:
|
||||
from kitty.fast_data_types import glfw_primary_monitor_size, set_smallest_allowed_resize
|
||||
|
||||
def initial_window_size(cell_width, cell_height, dpi_x, dpi_y, xscale, yscale):
|
||||
def initial_window_size(cell_width: int, cell_height: int, dpi_x: float, dpi_y: float, xscale: float, yscale: float) -> Tuple[int, int]:
|
||||
global window_width, window_height
|
||||
monitor_width, monitor_height = glfw_primary_monitor_size()
|
||||
if args.edge in {'top', 'bottom'}:
|
||||
h = initial_window_size_func.height = cell_height * args.lines + 1
|
||||
initial_window_size_func.width = monitor_width
|
||||
h = window_height = cell_height * args.lines + 1
|
||||
window_width = monitor_width
|
||||
set_smallest_allowed_resize(100, h)
|
||||
else:
|
||||
w = initial_window_size_func.width = cell_width * args.columns + 1
|
||||
initial_window_size_func.height = monitor_height
|
||||
w = window_width = cell_width * args.columns + 1
|
||||
window_height = monitor_height
|
||||
set_smallest_allowed_resize(w, 100)
|
||||
return initial_window_size_func.width, initial_window_size_func.height
|
||||
return window_width, window_height
|
||||
|
||||
return initial_window_size
|
||||
|
||||
|
||||
def main(sys_args):
|
||||
def main(sys_args: List[str]) -> None:
|
||||
global args
|
||||
if is_macos or not os.environ.get('DISPLAY'):
|
||||
raise SystemExit('Currently the panel kitten is supported only on X11 desktops')
|
||||
@@ -130,21 +137,22 @@ def main(sys_args):
|
||||
if not items:
|
||||
raise SystemExit('You must specify the program to run')
|
||||
sys.argv = ['kitty']
|
||||
if args.config:
|
||||
sys.argv.append('--config={}'.format(args.config))
|
||||
for config in args.config:
|
||||
sys.argv.append('--config={}'.format(config))
|
||||
for override in args.override:
|
||||
sys.argv.append('--override={}'.format(override))
|
||||
sys.argv.extend(items)
|
||||
from kitty.main import run_app, main
|
||||
from kitty.main import run_app, main as real_main
|
||||
run_app.cached_values_name = 'panel'
|
||||
run_app.first_window_callback = setup_x11_window
|
||||
run_app.initial_window_size_func = initial_window_size_func
|
||||
main()
|
||||
real_main()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
||||
elif __name__ == '__doc__':
|
||||
sys.cli_docs['usage'] = usage
|
||||
sys.cli_docs['options'] = OPTIONS
|
||||
sys.cli_docs['help_text'] = help_text
|
||||
cd: dict = sys.cli_docs # type: ignore
|
||||
cd['usage'] = usage
|
||||
cd['options'] = OPTIONS
|
||||
cd['help_text'] = help_text
|
||||
|
||||
@@ -4,28 +4,36 @@
|
||||
|
||||
|
||||
import sys
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cmds import cmap, parse_subcommand_cli
|
||||
from kitty.cli_stub import RCOptions, ResizeCLIOptions
|
||||
from kitty.constants import version
|
||||
from kitty.key_encoding import CTRL, ESCAPE, RELEASE, N, S, T, W
|
||||
from kitty.key_encoding import CTRL, RELEASE, KeyEvent, key_defs as K
|
||||
from kitty.rc.base import command_for_name, parse_subcommand_cli
|
||||
from kitty.remote_control import encode_send, parse_rc_args
|
||||
from kitty.utils import ScreenSize
|
||||
|
||||
from ..tui.handler import Handler
|
||||
from ..tui.loop import Loop
|
||||
from ..tui.operations import styled
|
||||
|
||||
global_opts = None
|
||||
global_opts = RCOptions()
|
||||
ESCAPE = K['ESCAPE']
|
||||
N = K['N']
|
||||
S = K['S']
|
||||
T = K['T']
|
||||
W = K['W']
|
||||
|
||||
|
||||
class Resize(Handler):
|
||||
|
||||
print_on_fail = None
|
||||
print_on_fail: Optional[str] = None
|
||||
|
||||
def __init__(self, opts):
|
||||
def __init__(self, opts: ResizeCLIOptions):
|
||||
self.opts = opts
|
||||
|
||||
def initialize(self):
|
||||
def initialize(self) -> None:
|
||||
global global_opts
|
||||
global_opts = parse_rc_args(['kitty', '@resize-window'])[0]
|
||||
self.original_size = self.screen_size
|
||||
@@ -33,8 +41,8 @@ class Resize(Handler):
|
||||
self.cmd.set_line_wrapping(False)
|
||||
self.draw_screen()
|
||||
|
||||
def do_window_resize(self, is_decrease=False, is_horizontal=True, reset=False, multiplier=1):
|
||||
resize_window = cmap['resize-window']
|
||||
def do_window_resize(self, is_decrease: bool = False, is_horizontal: bool = True, reset: bool = False, multiplier: int = 1) -> None:
|
||||
resize_window = command_for_name('resize_window')
|
||||
increment = self.opts.horizontal_increment if is_horizontal else self.opts.vertical_increment
|
||||
increment *= multiplier
|
||||
if is_decrease:
|
||||
@@ -42,11 +50,11 @@ class Resize(Handler):
|
||||
axis = 'reset' if reset else ('horizontal' if is_horizontal else 'vertical')
|
||||
cmdline = [resize_window.name, '--self', '--increment={}'.format(increment), '--axis=' + axis]
|
||||
opts, items = parse_subcommand_cli(resize_window, cmdline)
|
||||
payload = resize_window(global_opts, opts, items)
|
||||
payload = resize_window.message_to_kitty(global_opts, opts, items)
|
||||
send = {'cmd': resize_window.name, 'version': version, 'payload': payload, 'no_response': False}
|
||||
self.write(encode_send(send))
|
||||
|
||||
def on_kitty_cmd_response(self, response):
|
||||
def on_kitty_cmd_response(self, response: Dict[str, Any]) -> None:
|
||||
if not response.get('ok'):
|
||||
err = response['error']
|
||||
if response.get('tb'):
|
||||
@@ -58,14 +66,14 @@ class Resize(Handler):
|
||||
if res:
|
||||
self.cmd.bell()
|
||||
|
||||
def on_text(self, text, in_bracketed_paste=False):
|
||||
def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
|
||||
text = text.upper()
|
||||
if text in 'WNTSR':
|
||||
self.do_window_resize(is_decrease=text in 'NS', is_horizontal=text in 'WN', reset=text == 'R')
|
||||
elif text == 'Q':
|
||||
self.quit_loop(0)
|
||||
|
||||
def on_key(self, key_event):
|
||||
def on_key(self, key_event: KeyEvent) -> None:
|
||||
if key_event.type is RELEASE:
|
||||
return
|
||||
if key_event.key is ESCAPE:
|
||||
@@ -73,10 +81,10 @@ class Resize(Handler):
|
||||
elif key_event.key in (W, N, T, S) and key_event.mods & CTRL:
|
||||
self.do_window_resize(is_decrease=key_event.key in (N, S), is_horizontal=key_event.key in (W, N), multiplier=2)
|
||||
|
||||
def on_resize(self, new_size):
|
||||
def on_resize(self, new_size: ScreenSize) -> None:
|
||||
self.draw_screen()
|
||||
|
||||
def draw_screen(self):
|
||||
def draw_screen(self) -> None:
|
||||
self.cmd.clear_screen()
|
||||
print = self.print
|
||||
print(styled('Resize this window', bold=True, fg='gray', fg_intense=True))
|
||||
@@ -94,7 +102,7 @@ class Resize(Handler):
|
||||
print(styled('Sizes', bold=True, fg='white', fg_intense=True))
|
||||
print('Original: {} rows {} cols'.format(self.original_size.rows, self.original_size.cols))
|
||||
print('Current: {} rows {} cols'.format(
|
||||
styled(self.screen_size.rows, fg='magenta'), styled(self.screen_size.cols, fg='magenta')))
|
||||
styled(str(self.screen_size.rows), fg='magenta'), styled(str(self.screen_size.cols), fg='magenta')))
|
||||
|
||||
|
||||
OPTIONS = r'''
|
||||
@@ -111,10 +119,10 @@ The base vertical increment.
|
||||
'''.format
|
||||
|
||||
|
||||
def main(args):
|
||||
def main(args: List[str]) -> None:
|
||||
msg = 'Resize the current window'
|
||||
try:
|
||||
args, items = parse_args(args[1:], OPTIONS, '', msg, 'resize_window')
|
||||
cli_opts, items = parse_args(args[1:], OPTIONS, '', msg, 'resize_window', result_class=ResizeCLIOptions)
|
||||
except SystemExit as e:
|
||||
if e.code != 0:
|
||||
print(e.args[0], file=sys.stderr)
|
||||
@@ -122,7 +130,7 @@ def main(args):
|
||||
return
|
||||
|
||||
loop = Loop()
|
||||
handler = Resize(args)
|
||||
handler = Resize(cli_opts)
|
||||
loop.loop(handler)
|
||||
if handler.print_on_fail:
|
||||
print(handler.print_on_fail, file=sys.stderr)
|
||||
|
||||
@@ -6,17 +6,17 @@
|
||||
import importlib
|
||||
import os
|
||||
import sys
|
||||
from contextlib import suppress
|
||||
from functools import partial
|
||||
from functools import lru_cache, partial
|
||||
from typing import Any, Dict, FrozenSet, List
|
||||
|
||||
aliases = {'url_hints': 'hints'}
|
||||
|
||||
|
||||
def resolved_kitten(k):
|
||||
def resolved_kitten(k: str) -> str:
|
||||
return aliases.get(k, k).replace('-', '_')
|
||||
|
||||
|
||||
def path_to_custom_kitten(config_dir, kitten):
|
||||
def path_to_custom_kitten(config_dir: str, kitten: str) -> str:
|
||||
path = os.path.expanduser(kitten)
|
||||
if not os.path.isabs(path):
|
||||
path = os.path.join(config_dir, path)
|
||||
@@ -24,7 +24,7 @@ def path_to_custom_kitten(config_dir, kitten):
|
||||
return path
|
||||
|
||||
|
||||
def import_kitten_main_module(config_dir, kitten):
|
||||
def import_kitten_main_module(config_dir: str, kitten: str) -> Dict[str, Any]:
|
||||
if kitten.endswith('.py'):
|
||||
path_modified = False
|
||||
path = path_to_custom_kitten(config_dir, kitten)
|
||||
@@ -43,26 +43,26 @@ def import_kitten_main_module(config_dir, kitten):
|
||||
|
||||
kitten = resolved_kitten(kitten)
|
||||
m = importlib.import_module('kittens.{}.main'.format(kitten))
|
||||
return {'start': m.main, 'end': getattr(m, 'handle_result', lambda *a, **k: None)}
|
||||
return {'start': getattr(m, 'main'), 'end': getattr(m, 'handle_result', lambda *a, **k: None)}
|
||||
|
||||
|
||||
def create_kitten_handler(kitten, orig_args):
|
||||
def create_kitten_handler(kitten: str, orig_args: List[str]) -> Any:
|
||||
from kitty.constants import config_dir
|
||||
kitten = resolved_kitten(kitten)
|
||||
m = import_kitten_main_module(config_dir, kitten)
|
||||
ans = partial(m['end'], [kitten] + orig_args)
|
||||
ans.type_of_input = getattr(m['end'], 'type_of_input', None)
|
||||
ans.no_ui = getattr(m['end'], 'no_ui', False)
|
||||
setattr(ans, 'type_of_input', getattr(m['end'], 'type_of_input', None))
|
||||
setattr(ans, 'no_ui', getattr(m['end'], 'no_ui', False))
|
||||
return ans
|
||||
|
||||
|
||||
def set_debug(kitten):
|
||||
def set_debug(kitten: str) -> None:
|
||||
from kittens.tui.loop import debug
|
||||
import builtins
|
||||
builtins.debug = debug
|
||||
setattr(builtins, 'debug', debug)
|
||||
|
||||
|
||||
def launch(args):
|
||||
def launch(args: List[str]) -> None:
|
||||
config_dir, kitten = args[:2]
|
||||
kitten = resolved_kitten(kitten)
|
||||
del args[:2]
|
||||
@@ -84,7 +84,7 @@ def launch(args):
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def deserialize(output):
|
||||
def deserialize(output: str) -> Any:
|
||||
import json
|
||||
if output.startswith('OK: '):
|
||||
try:
|
||||
@@ -94,14 +94,16 @@ def deserialize(output):
|
||||
raise ValueError('Failed to parse kitten output: {!r}'.format(output))
|
||||
|
||||
|
||||
def run_kitten(kitten, run_name='__main__'):
|
||||
def run_kitten(kitten: str, run_name: str = '__main__') -> None:
|
||||
import runpy
|
||||
original_kitten_name = kitten
|
||||
kitten = resolved_kitten(kitten)
|
||||
set_debug(kitten)
|
||||
with suppress(ImportError):
|
||||
try:
|
||||
runpy.run_module('kittens.{}.main'.format(kitten), run_name=run_name)
|
||||
return
|
||||
except ImportError:
|
||||
pass
|
||||
# Look for a custom kitten
|
||||
if not kitten.endswith('.py'):
|
||||
kitten += '.py'
|
||||
@@ -116,21 +118,19 @@ def run_kitten(kitten, run_name='__main__'):
|
||||
m['main'](sys.argv)
|
||||
|
||||
|
||||
def all_kitten_names():
|
||||
ans = getattr(all_kitten_names, 'ans', None)
|
||||
if ans is None:
|
||||
n = []
|
||||
import glob
|
||||
base = os.path.dirname(os.path.abspath(__file__))
|
||||
for x in glob.glob(os.path.join(base, '*', '__init__.py')):
|
||||
q = os.path.basename(os.path.dirname(x))
|
||||
if q != 'tui':
|
||||
n.append(q)
|
||||
all_kitten_names.ans = ans = frozenset(n)
|
||||
return ans
|
||||
@lru_cache(maxsize=2)
|
||||
def all_kitten_names() -> FrozenSet[str]:
|
||||
n = []
|
||||
import glob
|
||||
base = os.path.dirname(os.path.abspath(__file__))
|
||||
for x in glob.glob(os.path.join(base, '*', '__init__.py')):
|
||||
q = os.path.basename(os.path.dirname(x))
|
||||
if q != 'tui':
|
||||
n.append(q)
|
||||
return frozenset(n)
|
||||
|
||||
|
||||
def list_kittens():
|
||||
def list_kittens() -> None:
|
||||
print('You must specify the name of a kitten to run')
|
||||
print('Choose from:')
|
||||
print()
|
||||
@@ -138,24 +138,24 @@ def list_kittens():
|
||||
print(kitten)
|
||||
|
||||
|
||||
def get_kitten_cli_docs(kitten):
|
||||
sys.cli_docs = {}
|
||||
def get_kitten_cli_docs(kitten: str) -> Any:
|
||||
setattr(sys, 'cli_docs', {})
|
||||
run_kitten(kitten, run_name='__doc__')
|
||||
ans = sys.cli_docs
|
||||
del sys.cli_docs
|
||||
ans = getattr(sys, 'cli_docs')
|
||||
delattr(sys, 'cli_docs')
|
||||
if 'help_text' in ans and 'usage' in ans and 'options' in ans:
|
||||
return ans
|
||||
|
||||
|
||||
def get_kitten_conf_docs(kitten):
|
||||
sys.all_options = None
|
||||
def get_kitten_conf_docs(kitten: str) -> Any:
|
||||
setattr(sys, 'all_options', None)
|
||||
run_kitten(kitten, run_name='__conf__')
|
||||
ans = sys.all_options
|
||||
del sys.all_options
|
||||
ans = getattr(sys, 'all_options')
|
||||
delattr(sys, 'all_options')
|
||||
return ans
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
try:
|
||||
args = sys.argv[1:]
|
||||
launch(args)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user