Compare commits
1607 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17e4995e93 | ||
|
|
e161b5a4de | ||
|
|
52b643b6c6 | ||
|
|
9bdb647454 | ||
|
|
0cabc3e109 | ||
|
|
d06d6d7646 | ||
|
|
f1dc072045 | ||
|
|
9adc474e3c | ||
|
|
370aa3aaa6 | ||
|
|
bed4f33be8 | ||
|
|
8ce80d8962 | ||
|
|
27ae9104ac | ||
|
|
331f1b7f2b | ||
|
|
df1a99a974 | ||
|
|
7ea4270c88 | ||
|
|
a8480a4ca6 | ||
|
|
783bfb2823 | ||
|
|
a88164e3a2 | ||
|
|
a7cbe3776d | ||
|
|
78d0cc40a3 | ||
|
|
01720a8d4f | ||
|
|
1d45cf4f91 | ||
|
|
a9da57d9b3 | ||
|
|
a280328731 | ||
|
|
17d2315d0c | ||
|
|
e27920527c | ||
|
|
960f5ff065 | ||
|
|
8fe936882d | ||
|
|
682428fb54 | ||
|
|
1c6bae636b | ||
|
|
c201bac900 | ||
|
|
5eaa935ede | ||
|
|
a73f09cf89 | ||
|
|
092dc3d01f | ||
|
|
5c0d477a18 | ||
|
|
414ca86e3f | ||
|
|
fbbfb25702 | ||
|
|
6ea812679f | ||
|
|
5a997a5f7a | ||
|
|
47641456da | ||
|
|
077f71cad5 | ||
|
|
8f71f6112a | ||
|
|
8bdd4d0596 | ||
|
|
df45a4e759 | ||
|
|
70111d130e | ||
|
|
ce6dacd0d4 | ||
|
|
f3c434b7c1 | ||
|
|
54b6344985 | ||
|
|
18fc8dad37 | ||
|
|
9b5034f904 | ||
|
|
f2c8819d25 | ||
|
|
53482f4c84 | ||
|
|
e1c50cf124 | ||
|
|
aacb4db2dc | ||
|
|
8638e42135 | ||
|
|
4b322560c3 | ||
|
|
0a4ad1fe12 | ||
|
|
907f658fcc | ||
|
|
1e7e10f96f | ||
|
|
5338fcdc1e | ||
|
|
f8991ce3c8 | ||
|
|
b408abe304 | ||
|
|
084671b26e | ||
|
|
1cc69b3edd | ||
|
|
d88105319d | ||
|
|
eb50fac8de | ||
|
|
4185e30d73 | ||
|
|
3a126ffa9d | ||
|
|
4f5e3f6cf1 | ||
|
|
dd6e206f9b | ||
|
|
ea920ffdf9 | ||
|
|
366d65f592 | ||
|
|
5e645a7be7 | ||
|
|
e3040a9c91 | ||
|
|
a01d68ed85 | ||
|
|
f070b17fee | ||
|
|
22d562ca41 | ||
|
|
102197fabe | ||
|
|
83f505e209 | ||
|
|
5a86bc2411 | ||
|
|
b9c324586e | ||
|
|
41fb3c79c5 | ||
|
|
5066623089 | ||
|
|
8e98b4123e | ||
|
|
8f88aab9d2 | ||
|
|
d8bbb16d5e | ||
|
|
60791bb57b | ||
|
|
11f98592f7 | ||
|
|
97467acb1f | ||
|
|
cc1f0bc3fe | ||
|
|
a009d6b258 | ||
|
|
0903ae7b4d | ||
|
|
35c1ebd3f9 | ||
|
|
352f940010 | ||
|
|
1f00c27097 | ||
|
|
6b2d37366f | ||
|
|
a4075ff041 | ||
|
|
7a526d9588 | ||
|
|
5b4e4f032d | ||
|
|
0a1b399447 | ||
|
|
6a64df1afb | ||
|
|
dc1851acdb | ||
|
|
096c1e328e | ||
|
|
1a44c2117b | ||
|
|
bd13238d9b | ||
|
|
b1c27d4c2f | ||
|
|
dc0093cb51 | ||
|
|
b1934ce267 | ||
|
|
16f767de7e | ||
|
|
93e9332474 | ||
|
|
81cc09aa61 | ||
|
|
e5bc7255b2 | ||
|
|
75048d56bc | ||
|
|
19f853c29f | ||
|
|
26ca3f9bad | ||
|
|
bf7d27691c | ||
|
|
22532b4805 | ||
|
|
68a006444e | ||
|
|
2d846f53a1 | ||
|
|
63077e5432 | ||
|
|
5edb1e2d6b | ||
|
|
0c7b4df6fc | ||
|
|
03abbb315a | ||
|
|
dc03c14af2 | ||
|
|
821f52a748 | ||
|
|
750f2fa4d0 | ||
|
|
009fd6418c | ||
|
|
ce161e610f | ||
|
|
b1b9c3704b | ||
|
|
fd245e894b | ||
|
|
59170a3627 | ||
|
|
61143557a9 | ||
|
|
9a77fdcd04 | ||
|
|
0c8ec3c57b | ||
|
|
b799eba81e | ||
|
|
9d1de50bf9 | ||
|
|
d55a9b582b | ||
|
|
865fc24975 | ||
|
|
45b0788f28 | ||
|
|
bc73273cb3 | ||
|
|
dd5cfe38b7 | ||
|
|
6b8e5ea225 | ||
|
|
60310ced05 | ||
|
|
f61b007645 | ||
|
|
d4e34a4c31 | ||
|
|
15524ee0fb | ||
|
|
58a4b4218e | ||
|
|
ac67d9c72d | ||
|
|
b9692d480a | ||
|
|
3a2da22509 | ||
|
|
bf8d0c9732 | ||
|
|
836b652f4d | ||
|
|
69255f7525 | ||
|
|
6e41409a3f | ||
|
|
7fe5d7b58f | ||
|
|
f15d27bb62 | ||
|
|
a068e3e655 | ||
|
|
d248d5ad75 | ||
|
|
44866a6e79 | ||
|
|
1fe84f6057 | ||
|
|
41207aa830 | ||
|
|
66a2e6f80d | ||
|
|
397ac36011 | ||
|
|
02063a5a6a | ||
|
|
b3926ae9e7 | ||
|
|
3743ae50e7 | ||
|
|
2205bf4426 | ||
|
|
def35078d1 | ||
|
|
1b5fac3189 | ||
|
|
dab7f71d2f | ||
|
|
1223130ef7 | ||
|
|
4e90ae68a4 | ||
|
|
e16e3c1582 | ||
|
|
ddf36383b4 | ||
|
|
77a3916f6f | ||
|
|
5d42d1f955 | ||
|
|
a2c68a927e | ||
|
|
cab7856495 | ||
|
|
c317c934f3 | ||
|
|
4d21be9eb5 | ||
|
|
fd71d2035d | ||
|
|
7ebb281855 | ||
|
|
2d3da1db6d | ||
|
|
458adf967a | ||
|
|
eec4c6b802 | ||
|
|
0b9b207513 | ||
|
|
7e8017604e | ||
|
|
520b935cd3 | ||
|
|
9cb83a6ec2 | ||
|
|
7c2317d301 | ||
|
|
7237e5cf9c | ||
|
|
cd5dab581b | ||
|
|
481cebbd29 | ||
|
|
4623f580b9 | ||
|
|
6291d0d400 | ||
|
|
36d82267bb | ||
|
|
f9feb4954a | ||
|
|
d192dcb0d7 | ||
|
|
143fd6e4dd | ||
|
|
df06578c2d | ||
|
|
73a055fe12 | ||
|
|
1fa1a478d9 | ||
|
|
5a3a547c65 | ||
|
|
373ab95f14 | ||
|
|
7f866b2b1f | ||
|
|
24db38ba7e | ||
|
|
efdfaaec30 | ||
|
|
d694f48c91 | ||
|
|
5562a4d52f | ||
|
|
ce4c71c465 | ||
|
|
31df90a64e | ||
|
|
22d69d24d0 | ||
|
|
d76e0850ae | ||
|
|
b520882b62 | ||
|
|
79acab1547 | ||
|
|
4690f3c7c0 | ||
|
|
cf1d9eb303 | ||
|
|
ba2a3f228d | ||
|
|
a757587ea2 | ||
|
|
ea756db544 | ||
|
|
2d1a2c30bf | ||
|
|
6ace082bc2 | ||
|
|
7e161ea94b | ||
|
|
d01d5297b8 | ||
|
|
7a1140cd03 | ||
|
|
aab81c2d32 | ||
|
|
49418c2f53 | ||
|
|
a548e3eb4d | ||
|
|
779a7b7deb | ||
|
|
c83a8b0773 | ||
|
|
035c3de4bb | ||
|
|
e2543e8968 | ||
|
|
bc2492c212 | ||
|
|
c03310b5e5 | ||
|
|
bd33cef092 | ||
|
|
832506d785 | ||
|
|
13b09346b9 | ||
|
|
1c10c5fcc4 | ||
|
|
510c5bd73b | ||
|
|
774fdd7e94 | ||
|
|
58497161c0 | ||
|
|
f831c34813 | ||
|
|
456af90ad2 | ||
|
|
c18bff7821 | ||
|
|
080d1bf935 | ||
|
|
4f9ed6546a | ||
|
|
c76bbeabd6 | ||
|
|
0f24ce60f8 | ||
|
|
6a06769931 | ||
|
|
468b6fab02 | ||
|
|
306f9e5735 | ||
|
|
f4ac03b791 | ||
|
|
b8abdd2b50 | ||
|
|
e673747ef4 | ||
|
|
6e4376e44e | ||
|
|
f6801d48d1 | ||
|
|
ca57198927 | ||
|
|
e78000c879 | ||
|
|
68cf9f7514 | ||
|
|
4556f5b8f1 | ||
|
|
32d8aac808 | ||
|
|
2e81d00c92 | ||
|
|
cd92d50a0d | ||
|
|
155dd426c5 | ||
|
|
a8f2816ac2 | ||
|
|
0ddc5d030c | ||
|
|
a2c2d81373 | ||
|
|
307ce1cf87 | ||
|
|
61ceb12e07 | ||
|
|
b45dc20693 | ||
|
|
bcedbe5ec1 | ||
|
|
038639e2d3 | ||
|
|
c857492f37 | ||
|
|
42923ab673 | ||
|
|
9afd0309fc | ||
|
|
b9d098de2d | ||
|
|
09e6f740de | ||
|
|
1023084eb9 | ||
|
|
aa9679e56b | ||
|
|
d8284befd3 | ||
|
|
e4ee2cf995 | ||
|
|
e3f6f47f10 | ||
|
|
e5941b1b44 | ||
|
|
65c64d8745 | ||
|
|
40093a4702 | ||
|
|
3815cba8f3 | ||
|
|
7410cd62f9 | ||
|
|
ae0a8e73d2 | ||
|
|
8f3a8c828f | ||
|
|
6efb5dc2c2 | ||
|
|
bb33f6c0ac | ||
|
|
d34284ee1c | ||
|
|
74cae7b787 | ||
|
|
c6360537e5 | ||
|
|
7d28d7413f | ||
|
|
d3f3e9ec38 | ||
|
|
928becece4 | ||
|
|
80db167abb | ||
|
|
a5a6880a19 | ||
|
|
da13d03b98 | ||
|
|
0a5c409a12 | ||
|
|
eeb772ad07 | ||
|
|
2b66775f45 | ||
|
|
3f65ce0e71 | ||
|
|
d235b673d1 | ||
|
|
0ab618c2dc | ||
|
|
fefafda9a0 | ||
|
|
902373ed20 | ||
|
|
aac57550c9 | ||
|
|
95e05ce9ec | ||
|
|
b6c6316b7b | ||
|
|
91576cc42f | ||
|
|
032e01ebf7 | ||
|
|
bd70100e09 | ||
|
|
ff6d2e3c10 | ||
|
|
2f292bb5d2 | ||
|
|
52cf443daf | ||
|
|
f03a665e09 | ||
|
|
66e7919171 | ||
|
|
1289dd2ff0 | ||
|
|
fa50e0d6cc | ||
|
|
8c7a5288ae | ||
|
|
7fe5c79d53 | ||
|
|
ea5ffa4304 | ||
|
|
89b32e4545 | ||
|
|
879b27a045 | ||
|
|
9cb0e4d09d | ||
|
|
94410f2866 | ||
|
|
a3ba8647ba | ||
|
|
f35ee1bb40 | ||
|
|
23b3b171fa | ||
|
|
30b25709d2 | ||
|
|
9c488bb877 | ||
|
|
14a33dbd94 | ||
|
|
e37ee422b6 | ||
|
|
d7d96c2e7b | ||
|
|
fda4aa21a1 | ||
|
|
3cbca4955e | ||
|
|
8e0e70e2d6 | ||
|
|
7a348d6ef1 | ||
|
|
cf8f904720 | ||
|
|
fe53555dba | ||
|
|
b644a42a48 | ||
|
|
5c9c9a67bc | ||
|
|
76f6288e69 | ||
|
|
6422b323c6 | ||
|
|
a622a149f6 | ||
|
|
fa6527cdf3 | ||
|
|
0b293428c4 | ||
|
|
26e8a5186a | ||
|
|
bde737fa38 | ||
|
|
71e09ba1fb | ||
|
|
3e69cf81af | ||
|
|
f5d2c35755 | ||
|
|
38a7fa73e3 | ||
|
|
fdd42d5f19 | ||
|
|
fe75493c37 | ||
|
|
e187265d04 | ||
|
|
0a985134e2 | ||
|
|
e555d963b1 | ||
|
|
3601488b26 | ||
|
|
f29ce19097 | ||
|
|
f6ab641b00 | ||
|
|
722a1020fa | ||
|
|
fbd19f3bde | ||
|
|
3cf874f6e9 | ||
|
|
02b206eeb8 | ||
|
|
5c50e3869c | ||
|
|
6f66bbd424 | ||
|
|
2697ddaec3 | ||
|
|
d6dcdf0751 | ||
|
|
11724c8a5f | ||
|
|
b6792a6d71 | ||
|
|
f0d61c2de9 | ||
|
|
35ae689818 | ||
|
|
1305199bdf | ||
|
|
c62c31f776 | ||
|
|
defef3f88c | ||
|
|
3ee9f723f2 | ||
|
|
a8725d6307 | ||
|
|
4c72f92939 | ||
|
|
2150f261ee | ||
|
|
f720bc9fab | ||
|
|
d92f89e47f | ||
|
|
c54a4021ef | ||
|
|
375fa73826 | ||
|
|
5eb2142d70 | ||
|
|
00223c5bba | ||
|
|
aa9080d375 | ||
|
|
8d45f5011b | ||
|
|
b3f096b03b | ||
|
|
a721ffeb7d | ||
|
|
6a1b456bac | ||
|
|
63a08dc6cc | ||
|
|
4969611bdb | ||
|
|
5d3a9f2628 | ||
|
|
15a7aeff4d | ||
|
|
f9a22d0bc7 | ||
|
|
402c8b6803 | ||
|
|
ac60715ee2 | ||
|
|
cfc6bd4da5 | ||
|
|
4d3f3b5e91 | ||
|
|
55b5a45e27 | ||
|
|
fbce5e7524 | ||
|
|
daeb1b6c50 | ||
|
|
8fb2c209a9 | ||
|
|
2b676b63b1 | ||
|
|
c0d06adcb5 | ||
|
|
51bba9110e | ||
|
|
4fc91dcc03 | ||
|
|
3c0667afd6 | ||
|
|
03dc24f913 | ||
|
|
73b5857916 | ||
|
|
08d74a6c56 | ||
|
|
4290a8f9ba | ||
|
|
c6dd6ccc68 | ||
|
|
fd7cc66689 | ||
|
|
1c19fc77f1 | ||
|
|
4080a09748 | ||
|
|
b91eaa3b2a | ||
|
|
bdb25831a7 | ||
|
|
a131f9a82e | ||
|
|
ffefd0f581 | ||
|
|
43c0e0f586 | ||
|
|
76669ad14d | ||
|
|
a9bb341e43 | ||
|
|
0757fcc451 | ||
|
|
dd4051bfd5 | ||
|
|
a6dcbe9c1d | ||
|
|
efaf9faa38 | ||
|
|
36631ddc49 | ||
|
|
e5e8cc72c6 | ||
|
|
38547c9e7a | ||
|
|
e00a5ce911 | ||
|
|
0af48a4d05 | ||
|
|
c0d80f9a47 | ||
|
|
d2dabc7d57 | ||
|
|
81db5eb82f | ||
|
|
1addfea261 | ||
|
|
e5be31ee14 | ||
|
|
9cef714078 | ||
|
|
a5a4a1bf8f | ||
|
|
281ad13f6b | ||
|
|
5ad2ac259b | ||
|
|
a2f022d166 | ||
|
|
4df6c172a3 | ||
|
|
9db1d581f7 | ||
|
|
b3bc5a5565 | ||
|
|
fc20e8d04b | ||
|
|
86af1d5c16 | ||
|
|
d8574f8f85 | ||
|
|
bdd04f37d6 | ||
|
|
a4e43b3925 | ||
|
|
5e880b92f5 | ||
|
|
d4c103e53e | ||
|
|
3e4df7d812 | ||
|
|
3b18f90319 | ||
|
|
e31587ec1e | ||
|
|
5dc97af4d5 | ||
|
|
81916d0f02 | ||
|
|
ae93d95bbe | ||
|
|
dd1fcf6855 | ||
|
|
4b05822474 | ||
|
|
337f1fad3f | ||
|
|
463c9debe7 | ||
|
|
9225919112 | ||
|
|
e70c021371 | ||
|
|
f3b3d6c0ef | ||
|
|
0c0b9e6b9c | ||
|
|
d9215feda5 | ||
|
|
d54fe3c16a | ||
|
|
36dd5b2d00 | ||
|
|
87108a18ef | ||
|
|
234274c1df | ||
|
|
b42677b343 | ||
|
|
c03af4d294 | ||
|
|
8d12e60f62 | ||
|
|
5a71b8c209 | ||
|
|
16775c5539 | ||
|
|
8b8263d0ff | ||
|
|
9ddd7d070e | ||
|
|
780d5400cb | ||
|
|
63d7b2a80b | ||
|
|
1958d67847 | ||
|
|
737919505d | ||
|
|
d60ecfa8a6 | ||
|
|
e3b22a87e4 | ||
|
|
c7d47a59f1 | ||
|
|
7eae176b88 | ||
|
|
9361c62a1c | ||
|
|
018811c96c | ||
|
|
129646c199 | ||
|
|
1881f4582e | ||
|
|
a5d05a2954 | ||
|
|
59b43ee844 | ||
|
|
29bde6c72c | ||
|
|
a7ce642a00 | ||
|
|
723a9c91b5 | ||
|
|
9e2c96653f | ||
|
|
4974219e0f | ||
|
|
a4de4b7c6f | ||
|
|
eae4899df4 | ||
|
|
87b4800fdf | ||
|
|
64156fd6e6 | ||
|
|
1485981b11 | ||
|
|
8d76cf8d32 | ||
|
|
7c23536bfe | ||
|
|
f919efcd42 | ||
|
|
388e47a2df | ||
|
|
dc787a6529 | ||
|
|
cfb6d93dc0 | ||
|
|
1523fef000 | ||
|
|
31dcb13836 | ||
|
|
0c82832356 | ||
|
|
ffea66357a | ||
|
|
6b48624b81 | ||
|
|
5a425ccaad | ||
|
|
2f2dbfb45f | ||
|
|
13a266aa42 | ||
|
|
67b12159f4 | ||
|
|
8ad55f7562 | ||
|
|
e1ab2383b3 | ||
|
|
f77d07259a | ||
|
|
2e07f90baf | ||
|
|
8574e136cd | ||
|
|
c8e8cb5ad5 | ||
|
|
f127523ae9 | ||
|
|
22f7145e34 | ||
|
|
1f115870bb | ||
|
|
3237db00fc | ||
|
|
ea583f60b3 | ||
|
|
b7816d26be | ||
|
|
19bf07abd9 | ||
|
|
a008c627e3 | ||
|
|
0068ae8f66 | ||
|
|
5436408463 | ||
|
|
d260d2f480 | ||
|
|
00ef9c1955 | ||
|
|
5509673ff3 | ||
|
|
3c4a411cad | ||
|
|
f945ef8ee8 | ||
|
|
260249491d | ||
|
|
595a78c956 | ||
|
|
cbbf8a2d34 | ||
|
|
88567f69b2 | ||
|
|
fe91af5e09 | ||
|
|
5e2255591c | ||
|
|
01969cae1a | ||
|
|
b89dfc6d1d | ||
|
|
59edf1d349 | ||
|
|
9419fbc77c | ||
|
|
d27cf045e5 | ||
|
|
f7d44330a2 | ||
|
|
834385baff | ||
|
|
c0f17c279e | ||
|
|
7264bea8c9 | ||
|
|
1058c999c8 | ||
|
|
bf7dd1c369 | ||
|
|
936a7a5f97 | ||
|
|
350060e0f6 | ||
|
|
430bd23870 | ||
|
|
9f2b2eac85 | ||
|
|
fd36435262 | ||
|
|
27418eed88 | ||
|
|
48f1690913 | ||
|
|
eff239a195 | ||
|
|
5e5cae8391 | ||
|
|
57310a772a | ||
|
|
066465bce7 | ||
|
|
1792c2268a | ||
|
|
32059dba7e | ||
|
|
c8296a44eb | ||
|
|
565526624f | ||
|
|
f57832f501 | ||
|
|
e536ef7844 | ||
|
|
0dab006733 | ||
|
|
75ead358a2 | ||
|
|
654bd23109 | ||
|
|
6b04c42730 | ||
|
|
8f5302a650 | ||
|
|
74b1cac344 | ||
|
|
7d5849cc17 | ||
|
|
d2a2af9672 | ||
|
|
2633356842 | ||
|
|
3bd4fd999a | ||
|
|
262e2fb7a3 | ||
|
|
97716fea8b | ||
|
|
bf74413c1f | ||
|
|
79ca0408e7 | ||
|
|
97acb60981 | ||
|
|
3662efdf80 | ||
|
|
e608a945de | ||
|
|
5771bd0c01 | ||
|
|
4396dede85 | ||
|
|
cbc569af64 | ||
|
|
90c1745976 | ||
|
|
c4ab964d09 | ||
|
|
04022ed363 | ||
|
|
79cfc1e70a | ||
|
|
e7c14c78d0 | ||
|
|
cb2389efa5 | ||
|
|
49f5f25fb9 | ||
|
|
ff4353b209 | ||
|
|
c07dc220a8 | ||
|
|
4316018966 | ||
|
|
a0bf6177e2 | ||
|
|
3078b9074a | ||
|
|
dffb87ced9 | ||
|
|
4adea5b7fe | ||
|
|
317b108497 | ||
|
|
7c41737370 | ||
|
|
4b6bae576d | ||
|
|
8221713995 | ||
|
|
ef49634353 | ||
|
|
d6f42a11d7 | ||
|
|
800dbf1f4d | ||
|
|
b1e08adbce | ||
|
|
e7f38929d9 | ||
|
|
d0e133885c | ||
|
|
2cacd7a64a | ||
|
|
a44c89504b | ||
|
|
8807f6d539 | ||
|
|
2f83bbdc85 | ||
|
|
41a841c83d | ||
|
|
0cf8876f8a | ||
|
|
a3a89b3e21 | ||
|
|
2ddbe2a2bc | ||
|
|
707963b694 | ||
|
|
1811949706 | ||
|
|
bc38bd75fd | ||
|
|
54ec486d3a | ||
|
|
5666b1b0fd | ||
|
|
ef9b765f81 | ||
|
|
a2bb360eec | ||
|
|
e3b8de1ac0 | ||
|
|
4cbd2a0ee0 | ||
|
|
7cf9b21fc0 | ||
|
|
454c2e32e7 | ||
|
|
53dc079c10 | ||
|
|
08c697e99a | ||
|
|
1dce092ac0 | ||
|
|
24c2d27eea | ||
|
|
b04b483b3f | ||
|
|
a7c997c6ef | ||
|
|
266e51310c | ||
|
|
928a4db817 | ||
|
|
fd631bf402 | ||
|
|
dc403156a9 | ||
|
|
2cc359ccc8 | ||
|
|
cbbda23e01 | ||
|
|
1ff4f2df4f | ||
|
|
8796168469 | ||
|
|
d16ad40bbf | ||
|
|
5c0858d6b7 | ||
|
|
a32a8d096d | ||
|
|
67f556bd7c | ||
|
|
7737369fc9 | ||
|
|
25a7ec9a07 | ||
|
|
c2a2b4c087 | ||
|
|
3bf20594b7 | ||
|
|
3a8bab90dc | ||
|
|
0ff2446a1a | ||
|
|
946d44c43f | ||
|
|
26d4f5bcc9 | ||
|
|
d84efe105c | ||
|
|
f657e6e916 | ||
|
|
c5afceb4cb | ||
|
|
18c3e46ac6 | ||
|
|
3c29ce936b | ||
|
|
c9d986f9a8 | ||
|
|
d4b67f3b6a | ||
|
|
6faa908733 | ||
|
|
ced741b247 | ||
|
|
c47ccc8a59 | ||
|
|
3326e9ef49 | ||
|
|
134fce8507 | ||
|
|
5d89a6c3c4 | ||
|
|
833e9625f9 | ||
|
|
21b572d69a | ||
|
|
12d9787d0e | ||
|
|
8c6b391eda | ||
|
|
8c2e435793 | ||
|
|
10cf7f06c6 | ||
|
|
4575a14873 | ||
|
|
d679ea2cdf | ||
|
|
54378de52b | ||
|
|
63287e4115 | ||
|
|
d0efe00449 | ||
|
|
d703cb51cd | ||
|
|
005a9c7090 | ||
|
|
f4de6d2a10 | ||
|
|
e60b331152 | ||
|
|
3d79eb5730 | ||
|
|
af7f4e97cf | ||
|
|
aad8cb3d1e | ||
|
|
40a9ab8929 | ||
|
|
e433b90297 | ||
|
|
1ac7d9c10d | ||
|
|
ef5f0025e1 | ||
|
|
32804f01b2 | ||
|
|
7ecc1b7950 | ||
|
|
a7f6105393 | ||
|
|
081390b5a2 | ||
|
|
61094c6bfb | ||
|
|
6672904e64 | ||
|
|
905c4d641c | ||
|
|
fa7a6dfd4a | ||
|
|
cb452ba9fc | ||
|
|
85169c989f | ||
|
|
e8a321d097 | ||
|
|
3a198833da | ||
|
|
ea8fb10c05 | ||
|
|
67115530b4 | ||
|
|
7325921070 | ||
|
|
f86ce03d3b | ||
|
|
5cf10023c9 | ||
|
|
93d08011f1 | ||
|
|
7f77f7e408 | ||
|
|
65c3630099 | ||
|
|
457aab7c41 | ||
|
|
dcec926590 | ||
|
|
0a2455c8be | ||
|
|
bacca88213 | ||
|
|
bd1b3d9f6e | ||
|
|
364533b1ed | ||
|
|
d7985689c9 | ||
|
|
6d6bba4a4c | ||
|
|
5cc5759f3e | ||
|
|
a388a658ce | ||
|
|
b5e2871aa0 | ||
|
|
192eccc6cc | ||
|
|
a4b2e2a196 | ||
|
|
6f4968305a | ||
|
|
79c8862d4c | ||
|
|
441e4edfb2 | ||
|
|
ef71b071db | ||
|
|
b33a684357 | ||
|
|
43b35f6f4e | ||
|
|
3b2c4561c2 | ||
|
|
e69b02ad46 | ||
|
|
85b6053380 | ||
|
|
249df69ac9 | ||
|
|
4a4500d56b | ||
|
|
be22f49012 | ||
|
|
f9695a7947 | ||
|
|
29d9b70f0c | ||
|
|
42a8ca0842 | ||
|
|
91c61478dd | ||
|
|
fb482e28f6 | ||
|
|
387333492b | ||
|
|
9c58cb3f41 | ||
|
|
b2e610f9b1 | ||
|
|
a960937095 | ||
|
|
0cda5d43a6 | ||
|
|
a75d59643c | ||
|
|
bf35817d73 | ||
|
|
f103f8d5db | ||
|
|
b382587776 | ||
|
|
6fe0c8ba2f | ||
|
|
7b2991de02 | ||
|
|
1b90c03304 | ||
|
|
0aa05b02e8 | ||
|
|
5412a0126c | ||
|
|
841b368021 | ||
|
|
b1f9139ca5 | ||
|
|
4a49c3940a | ||
|
|
ee12349a50 | ||
|
|
69c847a48f | ||
|
|
03705cbec0 | ||
|
|
ff2ff9c04f | ||
|
|
a22ba9f739 | ||
|
|
c86f8a698c | ||
|
|
fa4711bd04 | ||
|
|
a939bbb3ec | ||
|
|
174bc25afb | ||
|
|
2ffba1c422 | ||
|
|
0913b64c6b | ||
|
|
32e59257d2 | ||
|
|
6f8c884bb5 | ||
|
|
80c5ac891d | ||
|
|
ccb60687b6 | ||
|
|
6a2393299b | ||
|
|
ab66b3f4c0 | ||
|
|
c8292d77f1 | ||
|
|
7280c712d6 | ||
|
|
9be2247081 | ||
|
|
e68b5fa504 | ||
|
|
c2ef6c986b | ||
|
|
619fcb0681 | ||
|
|
10a39d3757 | ||
|
|
d2a15a2009 | ||
|
|
235eb868b2 | ||
|
|
5703a3370e | ||
|
|
5dca2a1a25 | ||
|
|
4b18b575cd | ||
|
|
818f68ec53 | ||
|
|
cd54fefa99 | ||
|
|
4ab5456ead | ||
|
|
10d11bc749 | ||
|
|
19ffbc6f3d | ||
|
|
79b1af28b4 | ||
|
|
05d768d8df | ||
|
|
7bb310d3af | ||
|
|
9d56f8eed2 | ||
|
|
42a5129553 | ||
|
|
d6ed20323b | ||
|
|
63fdbd3fa0 | ||
|
|
99fde8723a | ||
|
|
526a331f47 | ||
|
|
e18b6638bb | ||
|
|
3c3e7b7f70 | ||
|
|
67f03621ae | ||
|
|
10cef16210 | ||
|
|
6a79b450f7 | ||
|
|
eb4ee13f73 | ||
|
|
cf287015de | ||
|
|
246277e7af | ||
|
|
13758e9600 | ||
|
|
73e4deb1c2 | ||
|
|
68d589826a | ||
|
|
7457746d65 | ||
|
|
eec8f04e93 | ||
|
|
3a7d26a3ef | ||
|
|
91b15d1a90 | ||
|
|
12b0f632bd | ||
|
|
84cb2638d6 | ||
|
|
7eb6cb2407 | ||
|
|
ce3062cbde | ||
|
|
e599a2c87f | ||
|
|
15e1f376a4 | ||
|
|
82d0bd9364 | ||
|
|
6a9f3feba2 | ||
|
|
2e31178980 | ||
|
|
141b9c8f08 | ||
|
|
17629add66 | ||
|
|
352a78f056 | ||
|
|
2ef5d0a140 | ||
|
|
08b4741024 | ||
|
|
11ffa961bc | ||
|
|
ad6d928c12 | ||
|
|
74b768bfc2 | ||
|
|
9b703078dd | ||
|
|
43e93414ea | ||
|
|
6c3a439455 | ||
|
|
a7bc2fcba8 | ||
|
|
417c8887b9 | ||
|
|
47feb73cdf | ||
|
|
0aa1bacbe7 | ||
|
|
63512f0512 | ||
|
|
a32251cab4 | ||
|
|
2ca8ae8e5f | ||
|
|
2d466f343d | ||
|
|
4596dc39ce | ||
|
|
605882582e | ||
|
|
a5876e5231 | ||
|
|
e127579ae6 | ||
|
|
b3be6792fd | ||
|
|
6c25f0cf4b | ||
|
|
a0bff4abab | ||
|
|
82410c58ed | ||
|
|
3a87cfce3e | ||
|
|
15ffd8e6d8 | ||
|
|
33e16df586 | ||
|
|
4432c1a2ea | ||
|
|
45540561cc | ||
|
|
f90753c69b | ||
|
|
bab914c497 | ||
|
|
d3bb69a0ac | ||
|
|
e356af2dc4 | ||
|
|
e9e7b97c48 | ||
|
|
1c7d6f8bc6 | ||
|
|
82a0e56eb2 | ||
|
|
54fd2c6773 | ||
|
|
fb4c7db25e | ||
|
|
c85af36116 | ||
|
|
780b5ca8bd | ||
|
|
20ad7ca437 | ||
|
|
d39036de2a | ||
|
|
4ac4ee643e | ||
|
|
47f35a06e6 | ||
|
|
77f7ce82c0 | ||
|
|
10b74d0703 | ||
|
|
393820e77a | ||
|
|
1b8805bc6f | ||
|
|
07dc83670b | ||
|
|
1429be3a19 | ||
|
|
4b69a600e5 | ||
|
|
ce9a5528bc | ||
|
|
2ea3fa36d2 | ||
|
|
14262b158d | ||
|
|
2fd013b593 | ||
|
|
1325844539 | ||
|
|
3a21605b05 | ||
|
|
cea9ff30dc | ||
|
|
63da2e1ec6 | ||
|
|
083deec663 | ||
|
|
8b91c227f6 | ||
|
|
06bd1f5d48 | ||
|
|
a7f0a471ed | ||
|
|
2a7aa46b77 | ||
|
|
c04b002d4e | ||
|
|
46840df1ad | ||
|
|
aaf0dea8dc | ||
|
|
abaafc2d68 | ||
|
|
c97556de65 | ||
|
|
bbf7504303 | ||
|
|
126468a5dd | ||
|
|
8280899ce1 | ||
|
|
b15c9f7419 | ||
|
|
2e8ef66496 | ||
|
|
72f92b395f | ||
|
|
c9c8eb6e94 | ||
|
|
59ded41f7a | ||
|
|
10ad56885e | ||
|
|
d1eb9340ac | ||
|
|
a856c64104 | ||
|
|
c0b11c5656 | ||
|
|
935c4ded6b | ||
|
|
f5de08d5fa | ||
|
|
9d471782dd | ||
|
|
dea3df66f8 | ||
|
|
ec80a1532c | ||
|
|
93563f1280 | ||
|
|
e309e54002 | ||
|
|
0b3228ab16 | ||
|
|
3f28cf45d4 | ||
|
|
88b829fc9b | ||
|
|
458ac6953f | ||
|
|
a6855bde3c | ||
|
|
f6f2f00c53 | ||
|
|
00324c96fc | ||
|
|
2435a8ccfd | ||
|
|
de122ed727 | ||
|
|
c2ead407ae | ||
|
|
16b322616a | ||
|
|
e0b4c7edc5 | ||
|
|
a8ab4eaf23 | ||
|
|
3847837bd0 | ||
|
|
1747bbbbcb | ||
|
|
90e80477e9 | ||
|
|
4f90110a7c | ||
|
|
28c616d3ff | ||
|
|
f5126c664c | ||
|
|
0f340086c0 | ||
|
|
5c34f16ab4 | ||
|
|
03fe928e3f | ||
|
|
55b53bcac6 | ||
|
|
57d044861f | ||
|
|
aab149366d | ||
|
|
896f93822d | ||
|
|
6890e265b6 | ||
|
|
48a4edc199 | ||
|
|
d63be90a7f | ||
|
|
6af020e4ef | ||
|
|
ca24e9ca67 | ||
|
|
fbfaefd72a | ||
|
|
94b3776066 | ||
|
|
15e4be9051 | ||
|
|
ae8f3de070 | ||
|
|
c8d18ffe26 | ||
|
|
4d80427908 | ||
|
|
0e69324469 | ||
|
|
cf22729dfa | ||
|
|
1402c5bbfa | ||
|
|
f0f0c8f4fe | ||
|
|
96faac95db | ||
|
|
08041415d1 | ||
|
|
647b18d345 | ||
|
|
a116e3cadd | ||
|
|
52c9b5f5b8 | ||
|
|
bbc9f588f1 | ||
|
|
13a3c6b5b2 | ||
|
|
fc15b20f24 | ||
|
|
cd8f2e5cc6 | ||
|
|
267798a277 | ||
|
|
921ac12e90 | ||
|
|
901e00cab1 | ||
|
|
d0769f3979 | ||
|
|
7e4dd8cf1e | ||
|
|
5ea0519f62 | ||
|
|
a1029418f8 | ||
|
|
98eacb2067 | ||
|
|
2cef9abbd3 | ||
|
|
0fa158c809 | ||
|
|
c54a6b398b | ||
|
|
27ea367123 | ||
|
|
42178a4570 | ||
|
|
e98ecc1c4f | ||
|
|
0d64246209 | ||
|
|
a0495219ab | ||
|
|
9d98aa3159 | ||
|
|
5c82f4beca | ||
|
|
13539bd8c6 | ||
|
|
26b8ab9adf | ||
|
|
dbb97a62bf | ||
|
|
b1fa1c9820 | ||
|
|
2dc9488909 | ||
|
|
e2a251224d | ||
|
|
2a2dca8a58 | ||
|
|
e1f3cb85c0 | ||
|
|
13cf38d6c6 | ||
|
|
5180a41e87 | ||
|
|
1c44da2b4a | ||
|
|
bcd78c3940 | ||
|
|
fe2f3d9870 | ||
|
|
dcf2152a7a | ||
|
|
58a3baaf0f | ||
|
|
b247759d30 | ||
|
|
ba8d30896b | ||
|
|
4d30ae55f3 | ||
|
|
983ab3c67d | ||
|
|
0bf228b030 | ||
|
|
29b3a92ffc | ||
|
|
23d73ec933 | ||
|
|
bc650f6230 | ||
|
|
5272318d67 | ||
|
|
5ba887ff68 | ||
|
|
67d70f8b8f | ||
|
|
0fac6e4de9 | ||
|
|
1e7a11b278 | ||
|
|
6741ac2087 | ||
|
|
7b2e29a6a8 | ||
|
|
6f147544d2 | ||
|
|
89570d3b52 | ||
|
|
dd4a65e0c9 | ||
|
|
79fba9dd9a | ||
|
|
b123c21010 | ||
|
|
9c5d1f9771 | ||
|
|
5131224b59 | ||
|
|
be89da2271 | ||
|
|
45b408a78b | ||
|
|
0e7da8643e | ||
|
|
410b1816ad | ||
|
|
bcfa2a64e4 | ||
|
|
91c00fb5ac | ||
|
|
c58d217d32 | ||
|
|
a9b6047741 | ||
|
|
6b432bee4f | ||
|
|
087f0b449f | ||
|
|
96160b52b6 | ||
|
|
d17a6cd3a3 | ||
|
|
7e1380cc0d | ||
|
|
9a99554ed3 | ||
|
|
19799bd538 | ||
|
|
902e94ceac | ||
|
|
5bb2cb06a9 | ||
|
|
1559a2e15a | ||
|
|
35f94963cd | ||
|
|
f0d497dfe0 | ||
|
|
5bdf27fd78 | ||
|
|
f05783e64d | ||
|
|
c455fea729 | ||
|
|
5bf5454dbe | ||
|
|
b60d9f73ce | ||
|
|
31fe35cd38 | ||
|
|
607bc845fc | ||
|
|
a420d9d19c | ||
|
|
4b81e4936e | ||
|
|
295743a96e | ||
|
|
d3a1848c27 | ||
|
|
9cec924f32 | ||
|
|
67c963ab89 | ||
|
|
0f7ced25c1 | ||
|
|
42948ffff0 | ||
|
|
922ce5bdea | ||
|
|
1b42486026 | ||
|
|
95f1a971b2 | ||
|
|
7968c88c67 | ||
|
|
992e90b0a3 | ||
|
|
afada84f52 | ||
|
|
b978ff8930 | ||
|
|
053cede581 | ||
|
|
03720402a7 | ||
|
|
fca0999814 | ||
|
|
78056c659c | ||
|
|
30e1b4680d | ||
|
|
89a0c04d19 | ||
|
|
1ba027c277 | ||
|
|
d8e43a3412 | ||
|
|
644e46e91d | ||
|
|
e2a1f8dde7 | ||
|
|
7d09c6b762 | ||
|
|
6604e0d015 | ||
|
|
bb2ec9cde7 | ||
|
|
89a2545855 | ||
|
|
49f8c0eae6 | ||
|
|
268d687814 | ||
|
|
db5b4da388 | ||
|
|
71f8e50460 | ||
|
|
22fbdbca40 | ||
|
|
6253ee2a74 | ||
|
|
5b28aed0b1 | ||
|
|
9eabc9ecf1 | ||
|
|
a77852466c | ||
|
|
c68b82e4d0 | ||
|
|
16a4845a72 | ||
|
|
16da031f68 | ||
|
|
43b2935e52 | ||
|
|
8c7968a4d6 | ||
|
|
fba759a47c | ||
|
|
3bcb694b1b | ||
|
|
555020cfd5 | ||
|
|
391e2b8488 | ||
|
|
c856d5c058 | ||
|
|
f5a70adf0b | ||
|
|
e330c38d4a | ||
|
|
d595084395 | ||
|
|
b32ee5ee46 | ||
|
|
098dd10789 | ||
|
|
b5fa643c5d | ||
|
|
664dd29970 | ||
|
|
b7a4281985 | ||
|
|
9e9aa52553 | ||
|
|
77fbdfbb53 | ||
|
|
214416f1e3 | ||
|
|
800437ec6b | ||
|
|
957eafbef0 | ||
|
|
7f228bcbf5 | ||
|
|
fd4a682c5b | ||
|
|
ba9afc61a4 | ||
|
|
9a70709bba | ||
|
|
c437a36794 | ||
|
|
0e0578ff49 | ||
|
|
64a202a125 | ||
|
|
740d1ca146 | ||
|
|
7b75f055ea | ||
|
|
8d6a3cff25 | ||
|
|
10fdde6647 | ||
|
|
e1c45dc23f | ||
|
|
2efb8f6dc2 | ||
|
|
3b77f3b4bc | ||
|
|
31f763282f | ||
|
|
39434fb1f6 | ||
|
|
44ccdd36d6 | ||
|
|
4cc0138a28 | ||
|
|
6ca39d9d83 | ||
|
|
26c099639c | ||
|
|
329043ba08 | ||
|
|
e289f4959f | ||
|
|
b6760a59fa | ||
|
|
cd35f92607 | ||
|
|
ea8f223298 | ||
|
|
a4c3154dab | ||
|
|
6aa3405c78 | ||
|
|
da618584af | ||
|
|
9bc31a5651 | ||
|
|
314dd97059 | ||
|
|
0b66f20934 | ||
|
|
aba634ce88 | ||
|
|
033950dae9 | ||
|
|
a59e1adee1 | ||
|
|
5350eb29c1 | ||
|
|
2bb42e67d7 | ||
|
|
8745c4cd4a | ||
|
|
3288400005 | ||
|
|
1f499a7c6c | ||
|
|
012731c3be | ||
|
|
5deed81737 | ||
|
|
6f6e23bf61 | ||
|
|
d2d0487356 | ||
|
|
30df4e5de2 | ||
|
|
3f4588c408 | ||
|
|
4e4ca366d9 | ||
|
|
040ff0cc9d | ||
|
|
b01ad4bb3b | ||
|
|
4476251b76 | ||
|
|
0f4cb1955f | ||
|
|
c161ee7b5e | ||
|
|
a5bb2548a6 | ||
|
|
2898a3ef8c | ||
|
|
a0568334b5 | ||
|
|
4005cfdff2 | ||
|
|
1d8c5e65b9 | ||
|
|
56f45e8b0f | ||
|
|
48a9584688 | ||
|
|
bfcd324993 | ||
|
|
a75235a260 | ||
|
|
e8de2def96 | ||
|
|
5ffc4c6498 | ||
|
|
d0c50248ea | ||
|
|
ca2a121696 | ||
|
|
02d06d6726 | ||
|
|
18bf76d49e | ||
|
|
6961845167 | ||
|
|
4c5f7f50f7 | ||
|
|
572e920466 | ||
|
|
b81fb3c865 | ||
|
|
814dd8a275 | ||
|
|
1619687d1d | ||
|
|
65fc61a507 | ||
|
|
d027f524ce | ||
|
|
df5e6e1563 | ||
|
|
5541e3c2ff | ||
|
|
57dfc379ae | ||
|
|
b5467b8e26 | ||
|
|
a9f1928913 | ||
|
|
ae16fed39b | ||
|
|
848a795d26 | ||
|
|
deb8c3dacd | ||
|
|
3cb572b47a | ||
|
|
df3846fbb8 | ||
|
|
d73ceb26ae | ||
|
|
da9ffc9b93 | ||
|
|
63fa0c4e94 | ||
|
|
ce91b6d9d9 | ||
|
|
c982f24faf | ||
|
|
9f865adb83 | ||
|
|
b54dd1cb48 | ||
|
|
ef621aa099 | ||
|
|
df4738f080 | ||
|
|
8d0a06325c | ||
|
|
f3329fcd34 | ||
|
|
af48547d8b | ||
|
|
f228f8368a | ||
|
|
9bb2c1a27b | ||
|
|
aa0b07a90d | ||
|
|
d4075bfa9a | ||
|
|
7fd119d0eb | ||
|
|
2b7455e353 | ||
|
|
f42d2c63a6 | ||
|
|
6be3ae9efc | ||
|
|
2fd2e07342 | ||
|
|
1e0e32e282 | ||
|
|
b2d8f24499 | ||
|
|
12c99741a0 | ||
|
|
91dbcf4df0 | ||
|
|
6ebd82a84c | ||
|
|
c07178f43a | ||
|
|
78a5957863 | ||
|
|
cd6882d21e | ||
|
|
926a2828ab | ||
|
|
daa7df96d1 | ||
|
|
84ca83ef69 | ||
|
|
ac9c85e783 | ||
|
|
331f6d4903 | ||
|
|
2e422e5ba8 | ||
|
|
20cffc728e | ||
|
|
b06cbc9465 | ||
|
|
1348327414 | ||
|
|
cd3eeb7a95 | ||
|
|
7be6b28011 | ||
|
|
ef1bee0f48 | ||
|
|
702da542dd | ||
|
|
5ba92d06cf | ||
|
|
34a7b42063 | ||
|
|
963d1b58f8 | ||
|
|
fe598bac8a | ||
|
|
45736d7f1d | ||
|
|
0fe4f5a686 | ||
|
|
e9ce5c02d0 | ||
|
|
5916d82580 | ||
|
|
2c83b9902e | ||
|
|
fe07825ad9 | ||
|
|
56e83d7d07 | ||
|
|
c7e3c92a0a | ||
|
|
1a643441f3 | ||
|
|
2aee746da9 | ||
|
|
e64b1ba67c | ||
|
|
31be4f041e | ||
|
|
1f5ebc408d | ||
|
|
b4d9a22df8 | ||
|
|
d4168e3b80 | ||
|
|
241c7af855 | ||
|
|
080fcd8fc1 | ||
|
|
1aad4c7ec3 | ||
|
|
e0e0582b53 | ||
|
|
0a6276b007 | ||
|
|
2a2256f7d9 | ||
|
|
ed81a60065 | ||
|
|
5ad55dd165 | ||
|
|
6860f7ffd6 | ||
|
|
33a3e3fb61 | ||
|
|
d4df3f67b6 | ||
|
|
f15a2f0c1f | ||
|
|
252288aff2 | ||
|
|
65a20a2407 | ||
|
|
5f180dbd9a | ||
|
|
ee4e9aa678 | ||
|
|
9f1c9529aa | ||
|
|
5bba1a0851 | ||
|
|
e9823eb40c | ||
|
|
14fc3f6ac4 | ||
|
|
74714f942a | ||
|
|
0d116e6ef0 | ||
|
|
06108d66b1 | ||
|
|
21eece4c6d | ||
|
|
4dd696e4e0 | ||
|
|
d04d4444af | ||
|
|
a487a78200 | ||
|
|
f06a72b418 | ||
|
|
3270c9a527 | ||
|
|
a7b7fb560a | ||
|
|
384eba6cd4 | ||
|
|
dc7e3ba1ec | ||
|
|
b1e0adcfdc | ||
|
|
2a5922ce92 | ||
|
|
93694925ce | ||
|
|
adfcbb004f | ||
|
|
6595898b73 | ||
|
|
81b33f1f6f | ||
|
|
a1c4ffec20 | ||
|
|
89854cca8b | ||
|
|
4fe5211ed7 | ||
|
|
795b32efd7 | ||
|
|
153064ef3a | ||
|
|
183a31c884 | ||
|
|
adf47bed89 | ||
|
|
cc297a8984 | ||
|
|
4ef17edace | ||
|
|
e56fa9d31f | ||
|
|
791d4b30fb | ||
|
|
a9a9f1ac00 | ||
|
|
fd6bc55db6 | ||
|
|
97d7329d14 | ||
|
|
609d42e2bc | ||
|
|
3463931dad | ||
|
|
f637bf2377 | ||
|
|
2271eb5e60 | ||
|
|
a5cbb552f0 | ||
|
|
e43ca5b031 | ||
|
|
cc7e668ad6 | ||
|
|
a01dbbdf04 | ||
|
|
e0ce0a67cc | ||
|
|
0d83a8866b | ||
|
|
f7a7e39a36 | ||
|
|
67cb466ea9 | ||
|
|
376688ab9f | ||
|
|
8668087d15 | ||
|
|
9b1c23987c | ||
|
|
784daa1a4b | ||
|
|
06f9b73a7b | ||
|
|
62c0272df2 | ||
|
|
7ce2b4c641 | ||
|
|
08c2de541f | ||
|
|
94551623ec | ||
|
|
cb0d23bae8 | ||
|
|
cb5157f2d1 | ||
|
|
32dcbbb5e1 | ||
|
|
4eedf0d36c | ||
|
|
be458303c5 | ||
|
|
78542ede14 | ||
|
|
df8800b7ff | ||
|
|
9c30cd8891 | ||
|
|
dda28efd66 | ||
|
|
b78183972c | ||
|
|
fc06196a3e | ||
|
|
2a1809a2d8 | ||
|
|
ac5c312267 | ||
|
|
8f3174038b | ||
|
|
2d8113e0e0 | ||
|
|
e881850bb4 | ||
|
|
4cbae1db89 | ||
|
|
97bce7be18 | ||
|
|
5d2a276172 | ||
|
|
762dfe8315 | ||
|
|
1c0d1ec8a9 | ||
|
|
e650699217 | ||
|
|
5ff2b5be1c | ||
|
|
f1841d7c81 | ||
|
|
fd6231a778 | ||
|
|
751a52153d | ||
|
|
61b733bae9 | ||
|
|
624e96df9b | ||
|
|
a3f1a44d83 | ||
|
|
ed1547938e | ||
|
|
f96a13e9e4 | ||
|
|
7b70ed96b6 | ||
|
|
9379853d47 | ||
|
|
6ccfebd9e3 | ||
|
|
45186a17ca | ||
|
|
24bb4585af | ||
|
|
feb96f1be8 | ||
|
|
b7d3d64975 | ||
|
|
6114b49630 | ||
|
|
87d95caae0 | ||
|
|
9535bc94ff | ||
|
|
0841be7803 | ||
|
|
6cec89181b | ||
|
|
cb78e17594 | ||
|
|
285c399ae5 | ||
|
|
c9b14f6f78 | ||
|
|
6a101c51d2 | ||
|
|
8998970adc | ||
|
|
991fbacb99 | ||
|
|
72f3e8cd40 | ||
|
|
cf8113ea24 | ||
|
|
5d1cf49fb6 | ||
|
|
90f3c0f9af | ||
|
|
d02598688a | ||
|
|
1991f1ccd7 | ||
|
|
8fad8972d5 | ||
|
|
fc3e78e226 | ||
|
|
15aace29a1 | ||
|
|
12a3a2d7fe | ||
|
|
7e26a340a3 | ||
|
|
f0764c24d2 | ||
|
|
d080bf3b9c | ||
|
|
51698dff07 | ||
|
|
4154926be7 | ||
|
|
806ca2b224 | ||
|
|
92bbf08343 | ||
|
|
dc84e3de67 | ||
|
|
8011801549 | ||
|
|
dfec88ed15 | ||
|
|
5d3e2c7111 | ||
|
|
af1c48e0f7 | ||
|
|
d4bc906c5c | ||
|
|
62fe467336 | ||
|
|
ef3e9d1c2e | ||
|
|
09ddbbf600 | ||
|
|
4195c239c8 | ||
|
|
a6c3c57d40 | ||
|
|
de9263a117 | ||
|
|
7b7f1ecc54 | ||
|
|
d1b028c27a | ||
|
|
055aae2382 | ||
|
|
dbb084da7a | ||
|
|
8332cd2f79 | ||
|
|
4e29c0c16b | ||
|
|
320d45a3f5 | ||
|
|
73795b5257 | ||
|
|
16e59784c6 | ||
|
|
65a7ec3f85 | ||
|
|
360ff35061 | ||
|
|
5098792723 | ||
|
|
a4883e6d41 | ||
|
|
44c9d66dd4 | ||
|
|
c7718d078c | ||
|
|
2525994db4 | ||
|
|
81e6afa9b3 | ||
|
|
d8f5ccf9e4 | ||
|
|
774489c578 | ||
|
|
39bb0ecfcf | ||
|
|
b222ab1bf6 | ||
|
|
7a31c7ff50 | ||
|
|
132f87d152 | ||
|
|
aedda4bf9b | ||
|
|
40bba70bfe | ||
|
|
8202247b27 | ||
|
|
e2117ee8a4 | ||
|
|
e3a4150fea | ||
|
|
0ce2a23af3 | ||
|
|
6b2337c9c0 | ||
|
|
54661b7fc4 | ||
|
|
a1f895b781 | ||
|
|
199c271edc | ||
|
|
9ac744ae63 | ||
|
|
93b05bcd1e | ||
|
|
546cdbefae | ||
|
|
352c07987b | ||
|
|
f6a1eb19d7 | ||
|
|
a352804c14 | ||
|
|
28ae8c4d20 | ||
|
|
1eef584382 | ||
|
|
4e1cd0590b | ||
|
|
fa670e1f5d | ||
|
|
c7fa6f620a | ||
|
|
04690c8c7c | ||
|
|
4345ea6602 | ||
|
|
d39c212336 | ||
|
|
54d2f06abe | ||
|
|
237bfc9a6e | ||
|
|
29b3d49cd5 | ||
|
|
fe8892ec54 | ||
|
|
2d8dd7a53e | ||
|
|
b493599690 | ||
|
|
d9371dc265 | ||
|
|
ef2a12fbdf | ||
|
|
7215c6d6be | ||
|
|
bd9e1f58fe | ||
|
|
79dec269e1 | ||
|
|
d6ffbf4f3c | ||
|
|
32588939ae | ||
|
|
a02e07bfe8 | ||
|
|
03df0c3cca | ||
|
|
1aa50b73a1 | ||
|
|
c910f483bf | ||
|
|
14f4a8d28e | ||
|
|
ee931a17b0 | ||
|
|
0a2ba3fb66 | ||
|
|
512db660a1 | ||
|
|
b0666c9180 | ||
|
|
57d3d09679 | ||
|
|
3a17f54bca | ||
|
|
4c2800b294 | ||
|
|
8358377ec3 | ||
|
|
3ab12c616c | ||
|
|
ef4e784121 | ||
|
|
1ead20a5b9 | ||
|
|
31dfee8a95 | ||
|
|
d6492264c7 | ||
|
|
7281b9dbb1 | ||
|
|
4b63ee036e | ||
|
|
fb8eb69c20 | ||
|
|
4b1fa2609d | ||
|
|
a8b756f040 | ||
|
|
a94a0f3026 | ||
|
|
71c902eaf8 | ||
|
|
6e7af8ce2e | ||
|
|
6c80cd040c | ||
|
|
4ed413eaa7 | ||
|
|
86357d2c38 | ||
|
|
c0d4d01b88 | ||
|
|
b3fcb53625 | ||
|
|
4f29dea8ef | ||
|
|
4f8ef05058 | ||
|
|
7fc1735a21 | ||
|
|
1e49038747 | ||
|
|
295fbe42ad | ||
|
|
60d33f2847 | ||
|
|
cd369f633a | ||
|
|
c3df6d2fe3 | ||
|
|
72dfa74b3e | ||
|
|
1411269187 | ||
|
|
185d959f25 | ||
|
|
1652d41992 | ||
|
|
297dea881d | ||
|
|
01df828353 | ||
|
|
617115d447 | ||
|
|
31f9978599 | ||
|
|
dc068c16e8 | ||
|
|
89626a462f | ||
|
|
a043588b4a | ||
|
|
2e08ee16c2 | ||
|
|
2cc506476b | ||
|
|
0a462ad706 | ||
|
|
81c1bfe1c5 | ||
|
|
8ce80e39d9 | ||
|
|
fa823a37ae | ||
|
|
17f67fbe28 | ||
|
|
560b78bc35 | ||
|
|
f023f047ff | ||
|
|
5673359be2 | ||
|
|
666696d142 | ||
|
|
f95bc48f9b | ||
|
|
8d18fc09fa | ||
|
|
a3d42a90f2 | ||
|
|
1da5f2ecc6 | ||
|
|
d226e82f68 | ||
|
|
60cc2cb26c | ||
|
|
1ac90cf464 | ||
|
|
d3ffaebd17 | ||
|
|
bf01c549f5 | ||
|
|
6912d8c086 | ||
|
|
a89e1b5573 | ||
|
|
3a9c6088b2 | ||
|
|
fba629f385 | ||
|
|
90477825c2 | ||
|
|
1e56d39777 | ||
|
|
2829971412 | ||
|
|
c147fda6ca | ||
|
|
a27dfd8b7c | ||
|
|
a7c65efc45 | ||
|
|
646a524c8e | ||
|
|
f8d01d2c4f | ||
|
|
1d846f3744 | ||
|
|
6bbe58f876 | ||
|
|
36719fb086 | ||
|
|
78fd7a8458 | ||
|
|
f5976d0d63 | ||
|
|
b313fc3fbe | ||
|
|
cf667b8c47 | ||
|
|
7e3bd8586f | ||
|
|
d5df301317 | ||
|
|
628246c3da | ||
|
|
d228acd30a | ||
|
|
18cd97d914 | ||
|
|
5f13946bac | ||
|
|
8fb24fbc1e | ||
|
|
f0d4952055 | ||
|
|
7113580db2 | ||
|
|
163c211219 | ||
|
|
6993157b3a | ||
|
|
e2efdf674d | ||
|
|
9fcb8e5b6e | ||
|
|
a1a637c7f1 | ||
|
|
4f87f3e9f4 | ||
|
|
da6faa656c | ||
|
|
e1ad75e932 | ||
|
|
646bdbd002 | ||
|
|
9184de1997 | ||
|
|
eabc29ec4d | ||
|
|
e338cdab31 | ||
|
|
7001d609fa | ||
|
|
c8399ce3d2 | ||
|
|
3e1d701dce | ||
|
|
a385c39c2e | ||
|
|
5d1cf339a7 | ||
|
|
fafb8518e0 | ||
|
|
6807f4d4ea | ||
|
|
0ed7b2f3c0 | ||
|
|
116128ebb5 | ||
|
|
0c870c5fcd | ||
|
|
90bc3ab770 | ||
|
|
0021b0c242 | ||
|
|
6dc076f5d1 | ||
|
|
2dd1201c3f | ||
|
|
9be68966b4 | ||
|
|
47d482dca9 | ||
|
|
18dd13c872 | ||
|
|
c71bb877c3 | ||
|
|
eee8106f9e | ||
|
|
80ad596d3d | ||
|
|
9dbafcc368 | ||
|
|
9f5f113840 | ||
|
|
6afbdbe94f | ||
|
|
061a0c8cb6 |
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -15,6 +15,8 @@ kittens/diff/options/parse.py linguist-generated=true
|
|||||||
glfw/*.c linguist-vendored=true
|
glfw/*.c linguist-vendored=true
|
||||||
glfw/*.h linguist-vendored=true
|
glfw/*.h linguist-vendored=true
|
||||||
kittens/unicode_input/names.h linguist-generated=true
|
kittens/unicode_input/names.h linguist-generated=true
|
||||||
|
tools/wcswidth/std.go linguist-generated=true
|
||||||
|
|
||||||
*.py text diff=python
|
*.py text diff=python
|
||||||
*.m text diff=objc
|
*.m text diff=objc
|
||||||
|
*.go text diff=go
|
||||||
|
|||||||
17
.github/workflows/ci.py
vendored
17
.github/workflows/ci.py
vendored
@@ -30,8 +30,13 @@ def install_deps():
|
|||||||
print('Installing kitty dependencies...')
|
print('Installing kitty dependencies...')
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
if is_macos:
|
if is_macos:
|
||||||
items = (x.split()[1].strip('"') for x in open('Brewfile').readlines() if x.strip().startswith('brew '))
|
items = [x.split()[1].strip('"') for x in open('Brewfile').readlines() if x.strip().startswith('brew ')]
|
||||||
run('brew', 'install', 'fish', *items)
|
openssl = 'openssl'
|
||||||
|
items.remove('go') # already installed by ci.yml
|
||||||
|
import ssl
|
||||||
|
if ssl.OPENSSL_VERSION_INFO[0] == 1:
|
||||||
|
openssl += '@1.1'
|
||||||
|
run('brew', 'install', 'fish', openssl, *items)
|
||||||
else:
|
else:
|
||||||
run('sudo apt-get update')
|
run('sudo apt-get update')
|
||||||
run('sudo apt-get install -y libgl1-mesa-dev libxi-dev libxrandr-dev libxinerama-dev ca-certificates'
|
run('sudo apt-get install -y libgl1-mesa-dev libxi-dev libxrandr-dev libxinerama-dev ca-certificates'
|
||||||
@@ -59,7 +64,7 @@ def build_kitty():
|
|||||||
|
|
||||||
|
|
||||||
def test_kitty():
|
def test_kitty():
|
||||||
run('./kitty/launcher/kitty +launch test.py')
|
run('./test.py')
|
||||||
|
|
||||||
|
|
||||||
def package_kitty():
|
def package_kitty():
|
||||||
@@ -125,6 +130,12 @@ def main():
|
|||||||
package_kitty()
|
package_kitty()
|
||||||
elif action == 'test':
|
elif action == 'test':
|
||||||
test_kitty()
|
test_kitty()
|
||||||
|
elif action == 'gofmt':
|
||||||
|
q = subprocess.check_output('gofmt -s -l tools'.split())
|
||||||
|
if q.strip():
|
||||||
|
q = '\n'.join(filter(lambda x: not x.rstrip().endswith('_generated.go'), q.decode().strip().splitlines())).strip()
|
||||||
|
if q:
|
||||||
|
raise SystemExit(q)
|
||||||
else:
|
else:
|
||||||
raise SystemExit(f'Unknown action: {action}')
|
raise SystemExit(f'Unknown action: {action}')
|
||||||
|
|
||||||
|
|||||||
156
.github/workflows/ci.yml
vendored
156
.github/workflows/ci.yml
vendored
@@ -5,6 +5,10 @@ env:
|
|||||||
ASAN_OPTIONS: leak_check_at_exit=0
|
ASAN_OPTIONS: leak_check_at_exit=0
|
||||||
LC_ALL: en_US.UTF-8
|
LC_ALL: en_US.UTF-8
|
||||||
LANG: en_US.UTF-8
|
LANG: en_US.UTF-8
|
||||||
|
GO_INSTALL_VERSION: ">=1.20.0"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
linux:
|
linux:
|
||||||
@@ -24,11 +28,11 @@ jobs:
|
|||||||
sanitize: 0
|
sanitize: 0
|
||||||
|
|
||||||
- python: b
|
- python: b
|
||||||
pyver: "3.9"
|
pyver: "3.11"
|
||||||
sanitize: 1
|
sanitize: 1
|
||||||
|
|
||||||
- python: c
|
- python: c
|
||||||
pyver: "3.10"
|
pyver: "3.9"
|
||||||
sanitize: 1
|
sanitize: 1
|
||||||
|
|
||||||
|
|
||||||
@@ -41,20 +45,26 @@ jobs:
|
|||||||
cc: gcc
|
cc: gcc
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 10
|
fetch-depth: 10
|
||||||
- name: Set up Python ${{ matrix.pyver }}
|
|
||||||
uses: actions/setup-python@v3
|
|
||||||
with:
|
|
||||||
python-version: ${{ matrix.pyver }}
|
|
||||||
|
|
||||||
- name: Build kitty
|
- name: Set up Python ${{ matrix.pyver }}
|
||||||
run: python .github/workflows/ci.py build
|
uses: actions/setup-python@v3
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.pyver }}
|
||||||
|
|
||||||
- name: Test kitty
|
- name: Install Go
|
||||||
run: python .github/workflows/ci.py test
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_INSTALL_VERSION }}
|
||||||
|
|
||||||
|
- name: Build kitty
|
||||||
|
run: python .github/workflows/ci.py build
|
||||||
|
|
||||||
|
- name: Test kitty
|
||||||
|
run: python .github/workflows/ci.py test
|
||||||
|
|
||||||
linux-package:
|
linux-package:
|
||||||
name: Linux package
|
name: Linux package
|
||||||
@@ -62,36 +72,53 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CFLAGS: -funsigned-char
|
CFLAGS: -funsigned-char
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # needed for :commit: docs role
|
fetch-depth: 0 # needed for :commit: docs role
|
||||||
|
|
||||||
- name: Test for trailing whitespace
|
- 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
|
run: if grep -Inr '\s$' kitty kitty_tests kittens docs *.py *.asciidoc *.rst *.go .gitattributes .gitignore; then echo Trailing whitespace found, aborting.; exit 1; fi
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v3
|
uses: actions/setup-python@v3
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: "3.10"
|
||||||
|
|
||||||
- name: Install build-only deps
|
- name: Install Go
|
||||||
run: pip install -r docs/requirements.txt flake8 mypy types-requests types-docutils
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_INSTALL_VERSION }}
|
||||||
|
|
||||||
- name: Run pyflakes
|
- name: Install build-only deps
|
||||||
run: python -m flake8 --count .
|
run: python -m pip install -r docs/requirements.txt ruff mypy types-requests types-docutils
|
||||||
|
|
||||||
- name: Build kitty package
|
- name: Run ruff
|
||||||
run: python .github/workflows/ci.py package
|
run: ruff .
|
||||||
|
|
||||||
- name: Run mypy
|
- name: Run gofmt
|
||||||
run: python test.py mypy
|
run: go version && python .github/workflows/ci.py gofmt
|
||||||
|
|
||||||
- name: Build man page
|
- name: Build kitty package
|
||||||
run: make FAIL_WARN=1 man
|
run: python .github/workflows/ci.py package
|
||||||
|
|
||||||
- name: Build HTML docs
|
- name: Build kitty
|
||||||
run: make FAIL_WARN=1 html
|
run: python setup.py build --debug
|
||||||
|
|
||||||
|
- name: Build static kitty-tool
|
||||||
|
run: python setup.py build-static-binaries
|
||||||
|
|
||||||
|
- name: Run mypy
|
||||||
|
run: which python && python -m mypy --version && ./test.py mypy
|
||||||
|
|
||||||
|
- name: Run go vet
|
||||||
|
run: go version && go vet ./...
|
||||||
|
|
||||||
|
- name: Build man page
|
||||||
|
run: make FAIL_WARN=1 man
|
||||||
|
|
||||||
|
- name: Build HTML docs
|
||||||
|
run: make FAIL_WARN=1 html
|
||||||
|
|
||||||
bundle:
|
bundle:
|
||||||
name: Bundle test (${{ matrix.os }})
|
name: Bundle test (${{ matrix.os }})
|
||||||
@@ -102,31 +129,52 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
KITTY_BUNDLE: 1
|
KITTY_BUNDLE: 1
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@master
|
uses: actions/checkout@master
|
||||||
with:
|
with:
|
||||||
fetch-depth: 10
|
fetch-depth: 10
|
||||||
|
|
||||||
- name: Build kitty
|
- name: Install Go
|
||||||
run: which python3 && python3 .github/workflows/ci.py build
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_INSTALL_VERSION }}
|
||||||
|
|
||||||
- name: Test kitty
|
- name: Build kitty
|
||||||
run: python3 .github/workflows/ci.py test
|
run: which python3 && python3 .github/workflows/ci.py build
|
||||||
|
|
||||||
|
- name: Test kitty
|
||||||
|
run: python3 .github/workflows/ci.py test
|
||||||
|
|
||||||
brew:
|
brew:
|
||||||
name: macOS Brew
|
name: macOS Brew
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@master
|
uses: actions/checkout@master
|
||||||
with:
|
with:
|
||||||
fetch-depth: 10
|
fetch-depth: 0 # needed for :commit: docs role
|
||||||
|
|
||||||
- name: Build kitty
|
- name: Set up Python
|
||||||
run: python3 .github/workflows/ci.py build
|
uses: actions/setup-python@v3
|
||||||
|
with:
|
||||||
|
python-version: "3.10"
|
||||||
|
|
||||||
- name: Test kitty
|
- name: Install Go
|
||||||
run: python3 .github/workflows/ci.py test
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_INSTALL_VERSION }}
|
||||||
|
|
||||||
- name: Build kitty package
|
- name: Build kitty
|
||||||
run: python3 .github/workflows/ci.py package
|
run: python3 .github/workflows/ci.py build
|
||||||
|
|
||||||
|
- name: Test kitty
|
||||||
|
run: python3 .github/workflows/ci.py test
|
||||||
|
|
||||||
|
- name: Install deps for docs
|
||||||
|
run: python3 -m pip install -r docs/requirements.txt
|
||||||
|
|
||||||
|
- name: Builds docs
|
||||||
|
run: make FAIL_WARN=1 docs
|
||||||
|
|
||||||
|
- name: Build kitty package
|
||||||
|
run: python3 .github/workflows/ci.py package
|
||||||
|
|||||||
15
.github/workflows/codeql-analysis.yml
vendored
15
.github/workflows/codeql-analysis.yml
vendored
@@ -9,12 +9,25 @@ on:
|
|||||||
schedule:
|
schedule:
|
||||||
- cron: '0 22 * * 5'
|
- cron: '0 22 * * 5'
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
CodeQL-Build:
|
CodeQL-Build:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
security-events: write # to upload SARIF results (github/codeql-action/analyze)
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
|
- name: Install Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: ">=1.20.0"
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
@@ -24,7 +37,7 @@ jobs:
|
|||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v1
|
uses: github/codeql-action/init@v2
|
||||||
with:
|
with:
|
||||||
languages: python, c
|
languages: python, c
|
||||||
setup-python-dependencies: false
|
setup-python-dependencies: false
|
||||||
|
|||||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -2,15 +2,17 @@
|
|||||||
*.pyc
|
*.pyc
|
||||||
*.pyo
|
*.pyo
|
||||||
*_stub.pyi
|
*_stub.pyi
|
||||||
|
*_generated.go
|
||||||
|
*_generated.h
|
||||||
/.dmypy.json
|
/.dmypy.json
|
||||||
/tags
|
/tags
|
||||||
/build/
|
/build/
|
||||||
/linux-package/
|
/linux-package/
|
||||||
/kitty.app/
|
/kitty.app/
|
||||||
/compile_commands.json
|
|
||||||
/link_commands.json
|
|
||||||
/glad/out/
|
/glad/out/
|
||||||
/kitty/launcher/
|
/kitty/launcher/kitt*
|
||||||
|
/tools/cmd/at/*_generated.go
|
||||||
|
*_generated.go
|
||||||
/*.dSYM/
|
/*.dSYM/
|
||||||
__pycache__/
|
__pycache__/
|
||||||
/glfw/wayland-*-client-protocol.[ch]
|
/glfw/wayland-*-client-protocol.[ch]
|
||||||
@@ -18,5 +20,6 @@ __pycache__/
|
|||||||
/docs/generated/
|
/docs/generated/
|
||||||
/.mypy_cache
|
/.mypy_cache
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.cache
|
||||||
bypy/b
|
bypy/b
|
||||||
bypy/virtual-machines.conf
|
bypy/virtual-machines.conf
|
||||||
|
|||||||
1
Brewfile
1
Brewfile
@@ -5,3 +5,4 @@ brew "python"
|
|||||||
brew "imagemagick"
|
brew "imagemagick"
|
||||||
brew "harfbuzz"
|
brew "harfbuzz"
|
||||||
brew "sphinx-doc"
|
brew "sphinx-doc"
|
||||||
|
brew "go"
|
||||||
|
|||||||
@@ -7,6 +7,11 @@ When reporting a bug, provide full details of your environment, that means, at
|
|||||||
a minimum, kitty version, OS and OS version, kitty config (ideally a minimal
|
a minimum, kitty version, OS and OS version, kitty config (ideally a minimal
|
||||||
config to reproduce the issue with).
|
config to reproduce the issue with).
|
||||||
|
|
||||||
|
Note that bugs and feature requests are often closed quickly as they are either
|
||||||
|
fixed or deemed wontfix/invalid. In my experience, this is the only scaleable way to
|
||||||
|
manage a bug tracker. Feel free to continue to post to a closed bug report
|
||||||
|
if you would like to discuss the issue further. Being closed does not mean you
|
||||||
|
will not get any more responses.
|
||||||
|
|
||||||
### Contributing code
|
### Contributing code
|
||||||
|
|
||||||
|
|||||||
6
Makefile
6
Makefile
@@ -35,6 +35,12 @@ profile:
|
|||||||
app:
|
app:
|
||||||
python3 setup.py kitty.app $(VVAL)
|
python3 setup.py kitty.app $(VVAL)
|
||||||
|
|
||||||
|
linux-package: FORCE
|
||||||
|
rm -rf linux-package
|
||||||
|
python3 setup.py linux-package
|
||||||
|
|
||||||
|
FORCE:
|
||||||
|
|
||||||
man:
|
man:
|
||||||
$(MAKE) -C docs man
|
$(MAKE) -C docs man
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,8 @@ import sys
|
|||||||
import tempfile
|
import tempfile
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
|
||||||
from bypy.constants import (
|
from bypy.constants import LIBDIR, PREFIX, PYTHON, ismacos, worker_env
|
||||||
LIBDIR, PREFIX, PYTHON, SRC as KITTY_DIR, ismacos, worker_env
|
from bypy.constants import SRC as KITTY_DIR
|
||||||
)
|
|
||||||
from bypy.utils import run_shell, walk
|
from bypy.utils import run_shell, walk
|
||||||
|
|
||||||
|
|
||||||
@@ -63,20 +62,31 @@ def build_frozen_launcher(extra_include_dirs):
|
|||||||
|
|
||||||
def run_tests(kitty_exe):
|
def run_tests(kitty_exe):
|
||||||
with tempfile.TemporaryDirectory() as tdir:
|
with tempfile.TemporaryDirectory() as tdir:
|
||||||
env = {
|
uenv = {
|
||||||
'KITTY_CONFIG_DIRECTORY': os.path.join(tdir, 'conf'),
|
'KITTY_CONFIG_DIRECTORY': os.path.join(tdir, 'conf'),
|
||||||
'KITTY_CACHE_DIRECTORY': os.path.join(tdir, 'cache')
|
'KITTY_CACHE_DIRECTORY': os.path.join(tdir, 'cache')
|
||||||
}
|
}
|
||||||
[os.mkdir(x) for x in env.values()]
|
[os.mkdir(x) for x in uenv.values()]
|
||||||
cmd = [kitty_exe, '+runpy', 'from kitty_tests.main import run_tests; run_tests()']
|
env = os.environ.copy()
|
||||||
|
env.update(uenv)
|
||||||
|
cmd = [kitty_exe, '+runpy', 'from kitty_tests.main import run_tests; run_tests(report_env=True)']
|
||||||
print(*map(shlex.quote, cmd), flush=True)
|
print(*map(shlex.quote, cmd), flush=True)
|
||||||
if subprocess.call(cmd, env=env) != 0:
|
if subprocess.call(cmd, env=env, cwd=build_frozen_launcher.writeable_src_dir) != 0:
|
||||||
print('Checking of kitty build failed', file=sys.stderr)
|
print('Checking of kitty build failed, in directory:', build_frozen_launcher.writeable_src_dir, file=sys.stderr)
|
||||||
os.chdir(os.path.dirname(kitty_exe))
|
os.chdir(os.path.dirname(kitty_exe))
|
||||||
run_shell()
|
run_shell()
|
||||||
raise SystemExit('Checking of kitty build failed')
|
raise SystemExit('Checking of kitty build failed')
|
||||||
|
|
||||||
|
|
||||||
|
def build_frozen_tools(kitty_exe):
|
||||||
|
cmd = SETUP_CMD + ['--prefix', os.path.dirname(kitty_exe)] + ['build-frozen-tools']
|
||||||
|
if run(*cmd, cwd=build_frozen_launcher.writeable_src_dir) != 0:
|
||||||
|
print('Building of frozen kitten failed', file=sys.stderr)
|
||||||
|
os.chdir(KITTY_DIR)
|
||||||
|
run_shell()
|
||||||
|
raise SystemExit('Building of kitten launcher failed')
|
||||||
|
|
||||||
|
|
||||||
def sanitize_source_folder(path: str) -> None:
|
def sanitize_source_folder(path: str) -> None:
|
||||||
for q in walk(path):
|
for q in walk(path):
|
||||||
if os.path.splitext(q)[1] not in ('.py', '.glsl', '.ttf', '.otf'):
|
if os.path.splitext(q)[1] not in ('.py', '.glsl', '.ttf', '.otf'):
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
image 'https://cloud-images.ubuntu.com/releases/bionic/release/ubuntu-18.04-server-cloudimg-{}.img'
|
image 'https://cloud-images.ubuntu.com/releases/bionic/release/ubuntu-18.04-server-cloudimg-{}.img'
|
||||||
|
|
||||||
deps 'bison flex libxcursor-dev libxrandr-dev libxi-dev libxinerama-dev libgl1-mesa-dev libxcb-xkb-dev libfontconfig1-dev libdbus-1-dev'
|
deps 'bison flex libxcursor-dev libxrandr-dev libxi-dev libxinerama-dev libgl1-mesa-dev libx11-xcb-dev libxcb-xkb-dev libfontconfig1-dev libdbus-1-dev'
|
||||||
|
|||||||
@@ -10,12 +10,8 @@ import subprocess
|
|||||||
import tarfile
|
import tarfile
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from bypy.constants import (
|
from bypy.constants import OUTPUT_DIR, PREFIX, is64bit, python_major_minor_version
|
||||||
OUTPUT_DIR, PREFIX, is64bit, python_major_minor_version
|
from bypy.freeze import extract_extension_modules, freeze_python, path_to_freeze_dir
|
||||||
)
|
|
||||||
from bypy.freeze import (
|
|
||||||
extract_extension_modules, freeze_python, path_to_freeze_dir
|
|
||||||
)
|
|
||||||
from bypy.utils import get_dll_path, mkdtemp, py_compile, walk
|
from bypy.utils import get_dll_path, mkdtemp, py_compile, walk
|
||||||
|
|
||||||
j = os.path.join
|
j = os.path.join
|
||||||
@@ -143,6 +139,7 @@ def copy_python(env):
|
|||||||
iv['sanitize_source_folder'](os.path.join(env.py_dir, x))
|
iv['sanitize_source_folder'](os.path.join(env.py_dir, x))
|
||||||
py_compile(env.py_dir)
|
py_compile(env.py_dir)
|
||||||
freeze_python(env.py_dir, pdir, env.obj_dir, ext_map, develop_mode_env_var='KITTY_DEVELOP_FROM', remove_pyc_files=True)
|
freeze_python(env.py_dir, pdir, env.obj_dir, ext_map, develop_mode_env_var='KITTY_DEVELOP_FROM', remove_pyc_files=True)
|
||||||
|
shutil.rmtree(env.py_dir)
|
||||||
|
|
||||||
|
|
||||||
def build_launcher(env):
|
def build_launcher(env):
|
||||||
@@ -219,7 +216,7 @@ def create_tarfile(env, compression_level='9'):
|
|||||||
print('Compressing archive...')
|
print('Compressing archive...')
|
||||||
ans = f'{dist.rpartition(".")[0]}.txz'
|
ans = f'{dist.rpartition(".")[0]}.txz'
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
subprocess.check_call(['xz', '--threads=0', '-f', f'-{compression_level}', dist])
|
subprocess.check_call(['xz', '--verbose', '--threads=0', '-f', f'-{compression_level}', dist])
|
||||||
secs = time.time() - start_time
|
secs = time.time() - start_time
|
||||||
print('Compressed in {} minutes {} seconds'.format(secs // 60, secs % 60))
|
print('Compressed in {} minutes {} seconds'.format(secs // 60, secs % 60))
|
||||||
os.rename(f'{dist}.xz', ans)
|
os.rename(f'{dist}.xz', ans)
|
||||||
@@ -237,10 +234,12 @@ def main():
|
|||||||
files = find_binaries(env)
|
files = find_binaries(env)
|
||||||
fix_permissions(files)
|
fix_permissions(files)
|
||||||
add_ca_certs(env)
|
add_ca_certs(env)
|
||||||
|
kitty_exe = os.path.join(env.base, 'bin', 'kitty')
|
||||||
|
iv['build_frozen_tools'](kitty_exe)
|
||||||
if not args.dont_strip:
|
if not args.dont_strip:
|
||||||
strip_binaries(files)
|
strip_binaries(files)
|
||||||
if not args.skip_tests:
|
if not args.skip_tests:
|
||||||
iv['run_tests'](os.path.join(env.base, 'bin', 'kitty'))
|
iv['run_tests'](kitty_exe)
|
||||||
create_tarfile(env, args.compression_level)
|
create_tarfile(env, args.compression_level)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Requires installation of XCode 10.3 and Python 3 and
|
# Requires installation of XCode 10.3 and go 1.19 and Python 3 and
|
||||||
# python3 -m pip install certifi
|
# python3 -m pip install certifi
|
||||||
|
|
||||||
vm_name 'macos-kitty'
|
vm_name 'macos-kitty'
|
||||||
|
|||||||
@@ -13,16 +13,9 @@ import tempfile
|
|||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
from bypy.constants import PREFIX, PYTHON, SW, python_major_minor_version
|
from bypy.constants import PREFIX, PYTHON, SW, python_major_minor_version
|
||||||
from bypy.freeze import (
|
from bypy.freeze import extract_extension_modules, freeze_python, path_to_freeze_dir
|
||||||
extract_extension_modules, freeze_python, path_to_freeze_dir
|
from bypy.macos_sign import codesign, create_entitlements_file, make_certificate_useable, notarize_app, verify_signature
|
||||||
)
|
from bypy.utils import current_dir, mkdtemp, py_compile, run_shell, timeit, walk
|
||||||
from bypy.macos_sign import (
|
|
||||||
codesign, create_entitlements_file, make_certificate_useable, notarize_app,
|
|
||||||
verify_signature
|
|
||||||
)
|
|
||||||
from bypy.utils import (
|
|
||||||
current_dir, mkdtemp, py_compile, run_shell, timeit, walk
|
|
||||||
)
|
|
||||||
|
|
||||||
iv = globals()['init_env']
|
iv = globals()['init_env']
|
||||||
kitty_constants = iv['kitty_constants']
|
kitty_constants = iv['kitty_constants']
|
||||||
@@ -113,6 +106,9 @@ def do_sign(app_dir):
|
|||||||
codesign(fw)
|
codesign(fw)
|
||||||
items = set(os.listdir('.')) - fw
|
items = set(os.listdir('.')) - fw
|
||||||
codesign(expand_dirs(items))
|
codesign(expand_dirs(items))
|
||||||
|
# Sign kitten
|
||||||
|
with current_dir('MacOS'):
|
||||||
|
codesign('kitten')
|
||||||
|
|
||||||
# Now sign the main app
|
# Now sign the main app
|
||||||
codesign(app_dir)
|
codesign(app_dir)
|
||||||
@@ -171,6 +167,7 @@ class Freeze(object):
|
|||||||
self.add_misc_libraries()
|
self.add_misc_libraries()
|
||||||
self.freeze_python()
|
self.freeze_python()
|
||||||
self.add_ca_certs()
|
self.add_ca_certs()
|
||||||
|
self.build_frozen_tools()
|
||||||
if not self.dont_strip:
|
if not self.dont_strip:
|
||||||
self.strip_files()
|
self.strip_files()
|
||||||
if not self.skip_tests:
|
if not self.skip_tests:
|
||||||
@@ -369,6 +366,7 @@ class Freeze(object):
|
|||||||
iv['sanitize_source_folder'](join(self.python_stdlib, x))
|
iv['sanitize_source_folder'](join(self.python_stdlib, x))
|
||||||
self.compile_py_modules()
|
self.compile_py_modules()
|
||||||
freeze_python(self.python_stdlib, pdir, self.obj_dir, ext_map, develop_mode_env_var='KITTY_DEVELOP_FROM', remove_pyc_files=True)
|
freeze_python(self.python_stdlib, pdir, self.obj_dir, ext_map, develop_mode_env_var='KITTY_DEVELOP_FROM', remove_pyc_files=True)
|
||||||
|
shutil.rmtree(self.python_stdlib)
|
||||||
iv['build_frozen_launcher']([path_to_freeze_dir(), self.obj_dir])
|
iv['build_frozen_launcher']([path_to_freeze_dir(), self.obj_dir])
|
||||||
os.rename(join(dirname(self.contents_dir), 'bin', 'kitty'), join(self.contents_dir, 'MacOS', 'kitty'))
|
os.rename(join(dirname(self.contents_dir), 'bin', 'kitty'), join(self.contents_dir, 'MacOS', 'kitty'))
|
||||||
shutil.rmtree(join(dirname(self.contents_dir), 'bin'))
|
shutil.rmtree(join(dirname(self.contents_dir), 'bin'))
|
||||||
@@ -377,6 +375,10 @@ class Freeze(object):
|
|||||||
if f.endswith('.so') or f.endswith('.dylib'):
|
if f.endswith('.so') or f.endswith('.dylib'):
|
||||||
self.fix_dependencies_in_lib(f)
|
self.fix_dependencies_in_lib(f)
|
||||||
|
|
||||||
|
@flush
|
||||||
|
def build_frozen_tools(self):
|
||||||
|
iv['build_frozen_tools'](join(self.contents_dir, 'MacOS', 'kitty'))
|
||||||
|
|
||||||
@flush
|
@flush
|
||||||
def add_site_packages(self):
|
def add_site_packages(self):
|
||||||
print('\nAdding site-packages')
|
print('\nAdding site-packages')
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
to_vm_excludes '/build /dist /kitty/launcher/kitty /.build-cache /tags __pycache__ /*_commands.json *.so *.pyd *.pyc'
|
to_vm_excludes '/build /dist /kitty/launcher/kitty* /.build-cache /tags __pycache__ /*_commands.json *.so *.pyd *.pyc *_generated.go'
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
{
|
{
|
||||||
"name": "zlib",
|
"name": "zlib",
|
||||||
"unix": {
|
"unix": {
|
||||||
"filename": "zlib-1.2.11.tar.xz",
|
"filename": "zlib-1.2.13.tar.xz",
|
||||||
"hash": "sha256:4ff941449631ace0d4d203e3483be9dbc9da454084111f97ea0a2114e19bf066",
|
"hash": "sha256:d14c38e313afc35a9a8760dadf26042f51ea0f5d154b0630a31da0540107fb98",
|
||||||
"urls": ["https://zlib.net/{filename}"]
|
"urls": ["https://zlib.net/{filename}"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -21,10 +21,17 @@ kitty/options/parse.py
|
|||||||
kitty/options/to-c-generated.h
|
kitty/options/to-c-generated.h
|
||||||
kittens/diff/options/types.py
|
kittens/diff/options/types.py
|
||||||
kittens/diff/options/parse.py
|
kittens/diff/options/parse.py
|
||||||
|
tools/wcswidth/std.go
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
ignored = []
|
||||||
|
for line in subprocess.check_output(['git', 'status', '--ignored', '--porcelain']).decode().splitlines():
|
||||||
|
if line.startswith('!! '):
|
||||||
|
ignored.append(line[3:])
|
||||||
|
files_to_exclude += '\n'.join(ignored)
|
||||||
|
|
||||||
p = subprocess.Popen([
|
p = subprocess.Popen([
|
||||||
'cloc', '--exclude-list-file', '/dev/stdin', 'kitty', 'kittens'
|
'cloc', '--exclude-list-file', '/dev/stdin', 'kitty', 'kittens', 'tools',
|
||||||
], stdin=subprocess.PIPE)
|
], stdin=subprocess.PIPE)
|
||||||
p.communicate(files_to_exclude.encode('utf-8'))
|
p.communicate(files_to_exclude.encode('utf-8'))
|
||||||
raise SystemExit(p.wait())
|
raise SystemExit(p.wait())
|
||||||
|
|||||||
2
docs/_static/custom.css
vendored
2
docs/_static/custom.css
vendored
@@ -10,7 +10,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-logo {
|
.sidebar-logo {
|
||||||
max-height: 128px;
|
height: 128px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.major-features li {
|
.major-features li {
|
||||||
|
|||||||
18
docs/_templates/base.html
vendored
18
docs/_templates/base.html
vendored
@@ -4,18 +4,14 @@
|
|||||||
{{ super() }}
|
{{ super() }}
|
||||||
|
|
||||||
{%- if analytics_id %}
|
{%- if analytics_id %}
|
||||||
<script type="text/javascript">
|
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||||
var _gaq = _gaq || [];
|
<script async src="https://www.googletagmanager.com/gtag/js?id={{ analytics_id }}"></script>
|
||||||
_gaq.push(['_setAccount', '{{ analytics_id }}']);
|
<script>
|
||||||
_gaq.push(['_setDomainName', 'none']);
|
window.dataLayer = window.dataLayer || [];
|
||||||
_gaq.push(['_setAllowLinker', true]);
|
function gtag(){dataLayer.push(arguments);}
|
||||||
_gaq.push(['_trackPageview']);
|
gtag('js', new Date());
|
||||||
|
|
||||||
(function() {
|
gtag('config', '{{ analytics_id }}');
|
||||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
|
||||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
|
||||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
|
||||||
})();
|
|
||||||
</script>
|
</script>
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ Browse scrollback in less :sc:`show_scrollback`
|
|||||||
Browse last cmd output :sc:`show_last_command_output` (see :ref:`shell_integration`)
|
Browse last cmd output :sc:`show_last_command_output` (see :ref:`shell_integration`)
|
||||||
========================= =======================
|
========================= =======================
|
||||||
|
|
||||||
|
The scroll actions only take effect when the terminal is in the main screen.
|
||||||
|
When the alternate screen is active (for example when using a full screen
|
||||||
|
program like an editor) the key events are instead passed to program running in the
|
||||||
|
terminal.
|
||||||
|
|
||||||
Tabs
|
Tabs
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
|
|
||||||
@@ -123,6 +128,7 @@ The full list of actions that can be mapped to key presses is available
|
|||||||
================================== =======================
|
================================== =======================
|
||||||
Action Shortcut
|
Action Shortcut
|
||||||
================================== =======================
|
================================== =======================
|
||||||
|
Show this help :sc:`show_kitty_doc`
|
||||||
Copy to clipboard :sc:`copy_to_clipboard` (also :kbd:`⌘+c` on macOS)
|
Copy to clipboard :sc:`copy_to_clipboard` (also :kbd:`⌘+c` on macOS)
|
||||||
Paste from clipboard :sc:`paste_from_clipboard` (also :kbd:`⌘+v` on macOS)
|
Paste from clipboard :sc:`paste_from_clipboard` (also :kbd:`⌘+v` on macOS)
|
||||||
Paste from selection :sc:`paste_from_selection`
|
Paste from selection :sc:`paste_from_selection`
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ simply re-run the command.
|
|||||||
.. warning::
|
.. warning::
|
||||||
**Do not** copy the kitty binary out of the installation folder. If you want
|
**Do not** copy the kitty binary out of the installation folder. If you want
|
||||||
to add it to your :envvar:`PATH`, create a symlink in :file:`~/.local/bin` or
|
to add it to your :envvar:`PATH`, create a symlink in :file:`~/.local/bin` or
|
||||||
:file:`/usr/bin` or wherever.
|
:file:`/usr/bin` or wherever. You should create a symlink for the :file:`kitten`
|
||||||
|
binary as well.
|
||||||
|
|
||||||
|
|
||||||
Manually installing
|
Manually installing
|
||||||
@@ -46,9 +47,9 @@ particular desktop, but it should work for most major desktop environments.
|
|||||||
|
|
||||||
.. code-block:: sh
|
.. code-block:: sh
|
||||||
|
|
||||||
# Create a symbolic link to add kitty to PATH (assuming ~/.local/bin is in
|
# Create symbolic links to add kitty and kitten to PATH (assuming ~/.local/bin is in
|
||||||
# your system-wide PATH)
|
# your system-wide PATH)
|
||||||
ln -s ~/.local/kitty.app/bin/kitty ~/.local/bin/
|
ln -sf ~/.local/kitty.app/bin/kitty ~/.local/kitty.app/bin/kitten ~/.local/bin/
|
||||||
# Place the kitty.desktop file somewhere it can be found by the OS
|
# Place the kitty.desktop file somewhere it can be found by the OS
|
||||||
cp ~/.local/kitty.app/share/applications/kitty.desktop ~/.local/share/applications/
|
cp ~/.local/kitty.app/share/applications/kitty.desktop ~/.local/share/applications/
|
||||||
# If you want to open text files and images in kitty via your file manager also add the kitty-open.desktop file
|
# If you want to open text files and images in kitty via your file manager also add the kitty-open.desktop file
|
||||||
@@ -85,7 +86,7 @@ Customizing the installation
|
|||||||
|
|
||||||
.. code-block:: sh
|
.. code-block:: sh
|
||||||
|
|
||||||
_kitty_install_cmd \\
|
_kitty_install_cmd \
|
||||||
installer=nightly
|
installer=nightly
|
||||||
|
|
||||||
If you want to install it in parallel to the released kitty specify a
|
If you want to install it in parallel to the released kitty specify a
|
||||||
@@ -93,14 +94,14 @@ Customizing the installation
|
|||||||
|
|
||||||
.. code-block:: sh
|
.. code-block:: sh
|
||||||
|
|
||||||
_kitty_install_cmd \\
|
_kitty_install_cmd \
|
||||||
installer=nightly dest=/some/other/location
|
installer=nightly dest=/some/other/location
|
||||||
|
|
||||||
* You can specify a different install location, with ``dest``:
|
* You can specify a different install location, with ``dest``:
|
||||||
|
|
||||||
.. code-block:: sh
|
.. code-block:: sh
|
||||||
|
|
||||||
_kitty_install_cmd \\
|
_kitty_install_cmd \
|
||||||
dest=/some/other/location
|
dest=/some/other/location
|
||||||
|
|
||||||
* You can tell the installer not to launch |kitty| after installing it with
|
* You can tell the installer not to launch |kitty| after installing it with
|
||||||
@@ -108,14 +109,14 @@ Customizing the installation
|
|||||||
|
|
||||||
.. code-block:: sh
|
.. code-block:: sh
|
||||||
|
|
||||||
_kitty_install_cmd \\
|
_kitty_install_cmd \
|
||||||
launch=n
|
launch=n
|
||||||
|
|
||||||
* You can use a previously downloaded dmg/tarball, with ``installer``:
|
* You can use a previously downloaded dmg/tarball, with ``installer``:
|
||||||
|
|
||||||
.. code-block:: sh
|
.. code-block:: sh
|
||||||
|
|
||||||
_kitty_install_cmd \\
|
_kitty_install_cmd \
|
||||||
installer=/path/to/dmg or tarball
|
installer=/path/to/dmg or tarball
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -29,22 +29,24 @@ Dependencies
|
|||||||
|
|
||||||
Run-time dependencies:
|
Run-time dependencies:
|
||||||
|
|
||||||
* ``python`` >= 3.6
|
* ``python`` >= 3.8
|
||||||
* ``harfbuzz`` >= 2.2.0
|
* ``harfbuzz`` >= 2.2.0
|
||||||
* ``zlib``
|
* ``zlib``
|
||||||
* ``libpng``
|
* ``libpng``
|
||||||
* ``liblcms2``
|
* ``liblcms2``
|
||||||
* ``librsync``
|
* ``librsync``
|
||||||
|
* ``openssl``
|
||||||
* ``freetype`` (not needed on macOS)
|
* ``freetype`` (not needed on macOS)
|
||||||
* ``fontconfig`` (not needed on macOS)
|
* ``fontconfig`` (not needed on macOS)
|
||||||
* ``libcanberra`` (not needed on macOS)
|
* ``libcanberra`` (not needed on macOS)
|
||||||
* ``ImageMagick`` (optional, needed to use the ``kitty +kitten icat`` tool to display images in the terminal)
|
* ``ImageMagick`` (optional, needed to display uncommon image formats in the terminal)
|
||||||
* ``pygments`` (optional, needed for syntax highlighting in ``kitty +kitten diff``)
|
* ``pygments`` (optional, needed for syntax highlighting in ``kitty +kitten diff``)
|
||||||
|
|
||||||
|
|
||||||
Build-time dependencies:
|
Build-time dependencies:
|
||||||
|
|
||||||
* ``gcc`` or ``clang``
|
* ``gcc`` or ``clang``
|
||||||
|
* ``go >= 1.20`` (see :file:`go.mod` for go packages used during building)
|
||||||
* ``pkg-config``
|
* ``pkg-config``
|
||||||
* For building on Linux in addition to the above dependencies you might also
|
* For building on Linux in addition to the above dependencies you might also
|
||||||
need to install the following packages, if they are not already installed by
|
need to install the following packages, if they are not already installed by
|
||||||
@@ -82,14 +84,21 @@ You can run |kitty|, as::
|
|||||||
If that works, you can create a symlink to the launcher in :file:`~/bin` or some
|
If that works, you can create a symlink to the launcher in :file:`~/bin` or some
|
||||||
other directory on your PATH so that you can run |kitty| using just ``kitty``.
|
other directory on your PATH so that you can run |kitty| using just ``kitty``.
|
||||||
|
|
||||||
|
To have the kitty documentation available locally, run::
|
||||||
|
|
||||||
|
python3 -m pip install -r docs/requirements.txt && make docs
|
||||||
|
|
||||||
|
|
||||||
Building kitty.app on macOS from source
|
Building kitty.app on macOS from source
|
||||||
-------------------------------------------
|
-------------------------------------------
|
||||||
|
|
||||||
Run::
|
Run::
|
||||||
|
|
||||||
|
python3 -m pip install -r docs/requirements.txt && make docs
|
||||||
make app
|
make app
|
||||||
|
|
||||||
|
Building the docs needs to be done only once.
|
||||||
|
|
||||||
This :file:`kitty.app` unlike the released one does not include its own copy of
|
This :file:`kitty.app` unlike the released one does not include its own copy of
|
||||||
Python and the other dependencies. So if you ever un-install/upgrade those
|
Python and the other dependencies. So if you ever un-install/upgrade those
|
||||||
dependencies you might have to rebuild the app.
|
dependencies you might have to rebuild the app.
|
||||||
@@ -152,7 +161,7 @@ While |kitty| does use Python, it is not a traditional Python package, so please
|
|||||||
do not install it in site-packages.
|
do not install it in site-packages.
|
||||||
Instead run::
|
Instead run::
|
||||||
|
|
||||||
python3 setup.py linux-package
|
make linux-package
|
||||||
|
|
||||||
This will install |kitty| into the directory :file:`linux-package`. You can run
|
This will install |kitty| into the directory :file:`linux-package`. You can run
|
||||||
|kitty| with :file:`linux-package/bin/kitty`. All the files needed to run kitty
|
|kitty| with :file:`linux-package/bin/kitty`. All the files needed to run kitty
|
||||||
@@ -175,8 +184,11 @@ You should probably split |kitty| into three packages:
|
|||||||
:code:`kitty`
|
:code:`kitty`
|
||||||
Installs the main program
|
Installs the main program
|
||||||
|
|
||||||
This allows users to install the terminfo and shell integration files on servers
|
This allows users to install the terminfo and shell integration files on
|
||||||
into which they ssh, without needing to install all of |kitty|.
|
servers into which they ssh, without needing to install all of |kitty|. The
|
||||||
|
shell integration files **must** still be present in
|
||||||
|
:file:`lib/kitty/shell-integration` when installing the kitty main package as
|
||||||
|
the kitty program expects to find them there.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
You need a couple of extra dependencies to build linux-package. :file:`tic`
|
You need a couple of extra dependencies to build linux-package. :file:`tic`
|
||||||
|
|||||||
@@ -35,6 +35,272 @@ mouse anywhere in the current command to move the cursor there. See
|
|||||||
Detailed list of changes
|
Detailed list of changes
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
|
||||||
|
0.27.1 [2023-02-07]
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- Fix :opt:`modify_font` not working for strikethrough position (:iss:`5946`)
|
||||||
|
|
||||||
|
- Fix a regression causing the ``edit-in-kitty`` command not working if :file:`kitten` is not added
|
||||||
|
to PATH (:iss:`5956`)
|
||||||
|
|
||||||
|
- icat kitten: Fix a regression that broke display of animated GIFs over SSH (:iss:`5958`)
|
||||||
|
|
||||||
|
- Wayland GNOME: Fix for ibus not working when using XWayland (:iss:`5967`)
|
||||||
|
|
||||||
|
- Fix regression in previous release that caused incorrect entries in terminfo for modifier+F3 key combinations (:pull:`5970`)
|
||||||
|
|
||||||
|
- Bring back the deprecated and removed ``kitty +complete`` and delegate it to :program:`kitten` for backward compatibility (:pull:`5977`)
|
||||||
|
|
||||||
|
- Bump the version of Go needed to build kitty to ``1.20`` so we can use the Go stdlib ecdh package for crypto.
|
||||||
|
|
||||||
|
|
||||||
|
0.27.0 [2023-01-31]
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- A new statically compiled, standalone executable, ``kitten`` (written in Go)
|
||||||
|
that can be used on all UNIX-like servers for remote control (``kitten @``),
|
||||||
|
viewing images (``kitten icat``), manipulating the clipboard (``kitten clipboard``), etc.
|
||||||
|
|
||||||
|
- :doc:`clipboard kitten </kittens/clipboard>`: Allow copying arbitrary data types to/from the clipboard, not just plain text
|
||||||
|
|
||||||
|
- Speed up the ``kitty @`` executable by ~10x reducing the time for typical
|
||||||
|
remote control commands from ~50ms to ~5ms
|
||||||
|
|
||||||
|
- icat kitten: Speed up by using POSIX shared memory when possible to transfer
|
||||||
|
image data to the terminal. Also support common image formats
|
||||||
|
GIF/PNG/JPEG/WEBP/TIFF/BMP out of the box without needing ImageMagick.
|
||||||
|
|
||||||
|
- Option :opt:`show_hyperlink_targets` to show the target of terminal hyperlinks when hovering over them with the mouse (:pull:`5830`)
|
||||||
|
|
||||||
|
- Keyboard protocol: Remove ``CSI R`` from the allowed encodings of the :kbd:`F3` key as it conflicts with the *Cursor Position Report* escape code (:disc:`5813`)
|
||||||
|
|
||||||
|
- Allow using the cwd of the original process for :option:`launch --cwd` (:iss:`5672`)
|
||||||
|
|
||||||
|
- Session files: Expand environment variables (:disc:`5917`)
|
||||||
|
|
||||||
|
- Pass key events mapped to scroll actions to the program running in the terminal when the terminal is in alternate screen mode (:iss:`5839`)
|
||||||
|
|
||||||
|
- Implement :ref:`edit-in-kitty <edit_file>` using the new ``kitten`` static executable (:iss:`5546`, :iss:`5630`)
|
||||||
|
|
||||||
|
- Add an option :opt:`background_tint_gaps` to control background image tinting for window gaps (:iss:`5596`)
|
||||||
|
|
||||||
|
- A new option :opt:`undercurl_style` to control the rendering of undercurls (:pull:`5883`)
|
||||||
|
|
||||||
|
- Bash integration: Fix ``clone-in-kitty`` not working on bash >= 5.2 if environment variable values contain newlines or other special characters (:iss:`5629`)
|
||||||
|
|
||||||
|
- A new :ac:`sleep` action useful in combine based mappings to make kitty sleep before executing the next action
|
||||||
|
|
||||||
|
- Wayland GNOME: Workaround for latest mutter release breaking full screen for semi-transparent kitty windows (:iss:`5677`)
|
||||||
|
|
||||||
|
- A new option :opt:`tab_title_max_length` to limit the length of tab (:iss:`5718`)
|
||||||
|
|
||||||
|
- When drawing the tab bar have the default left and right margins drawn in a color matching the neighboring tab (:iss:`5719`)
|
||||||
|
|
||||||
|
- When using the :code:`include` directive in :file:`kitty.conf` make the environment variable :envvar:`KITTY_OS` available for OS specific config
|
||||||
|
|
||||||
|
- Wayland: Fix signal handling not working with some GPU drivers (:iss:`4636`)
|
||||||
|
|
||||||
|
- Remote control: When matching windows allow using negative id numbers to match recently created windows (:iss:`5753`)
|
||||||
|
|
||||||
|
- ZSH Integration: Bind :kbd:`alt+left` and :kbd:`alt+right` to move by word if not already bound. This mimics the default bindings in Terminal.app (:iss:`5793`)
|
||||||
|
|
||||||
|
- macOS: Allow to customize :sc:`Hide <hide_macos_app>`, :sc:`Hide Others <hide_macos_other_apps>`, :sc:`Minimize <minimize_macos_window>`, and :sc:`Quit <quit>` global menu shortcuts. Note that :opt:`clear_all_shortcuts` will remove these shortcuts now (:iss:`948`)
|
||||||
|
|
||||||
|
- When a multi-key sequence does not match any action, send all key events to the child program (:pull:`5841`)
|
||||||
|
|
||||||
|
- broadcast kitten: Allow pressing a key to stop echoing of input into the broadcast window itself (:disc:`5868`)
|
||||||
|
|
||||||
|
- When reporting unused activity in a window, ignore activity that occurs soon after a window resize (:iss:`5881`)
|
||||||
|
|
||||||
|
- Fix using :opt:`cursor` = ``none`` not working on text that has reverse video (:iss:`5897`)
|
||||||
|
|
||||||
|
- Fix ssh kitten not working on FreeBSD (:iss:`5928`)
|
||||||
|
|
||||||
|
- macOS: Export kitty selected text to the system for use with services that accept it (patch by Sertaç Ö. Yıldız)
|
||||||
|
|
||||||
|
|
||||||
|
0.26.5 [2022-11-07]
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- Splits layout: Add a new mappable action to move the active window to the screen edge (:iss:`5643`)
|
||||||
|
|
||||||
|
- ssh kitten: Allow using absolute paths for the location of transferred data (:iss:`5607`)
|
||||||
|
|
||||||
|
- Fix a regression in the previous release that caused a :opt:`resize_draw_strategy` of ``static`` to not work (:iss:`5601`)
|
||||||
|
|
||||||
|
- Wayland KDE: Fix abort when pasting into Firefox (:iss:`5603`)
|
||||||
|
|
||||||
|
- Wayland GNOME: Fix ghosting when using :opt:`background_tint` (:iss:`5605`)
|
||||||
|
|
||||||
|
- Fix cursor position at x=0 changing to x=1 on resize (:iss:`5635`)
|
||||||
|
|
||||||
|
- Wayland GNOME: Fix incorrect window size in some circumstances when switching between windows with window decorations disabled (:iss:`4802`)
|
||||||
|
|
||||||
|
- Wayland: Fix high CPU usage when using some input methods (:pull:`5369`)
|
||||||
|
|
||||||
|
- Remote control: When matching window by `state:focused` and no window currently has keyboard focus, match the window belonging to the OS window that was last focused (:iss:`5602`)
|
||||||
|
|
||||||
|
|
||||||
|
0.26.4 [2022-10-17]
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- macOS: Allow changing the kitty icon by placing a custom icon in the kitty config folder (:pull:`5464`)
|
||||||
|
|
||||||
|
- Allow centering the :opt:`background_image` (:iss:`5525`)
|
||||||
|
|
||||||
|
- X11: Fix a regression in the previous release that caused pasting from GTK based applications to have extra newlines (:iss:`5528`)
|
||||||
|
|
||||||
|
- Tab bar: Improve empty space management when some tabs have short titles, allocate the saved space to the active tab (:iss:`5548`)
|
||||||
|
|
||||||
|
- Fix :opt:`background_tint` not applying to window margins and padding (:iss:`3933`)
|
||||||
|
|
||||||
|
- Wayland: Fix background image scaling using tiled mode on high DPI screens
|
||||||
|
|
||||||
|
- Wayland: Fix an abort when changing background colors with :opt:`wayland_titlebar_color` set to ``background`` (:iss:`5562`)
|
||||||
|
|
||||||
|
- Update to Unicode 15.0 (:pull:`5542`)
|
||||||
|
|
||||||
|
- GNOME Wayland: Fix a memory leak in gnome-shell when using client side decorations
|
||||||
|
|
||||||
|
|
||||||
|
0.26.3 [2022-09-22]
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- Wayland: Mark windows in which a bell occurs as urgent on compositors that support the xdg-activation protocol
|
||||||
|
|
||||||
|
- Allow passing null bytes through the system clipboard (:iss:`5483`)
|
||||||
|
|
||||||
|
- ssh kitten: Fix :envvar:`KITTY_PUBLIC_KEY` not being encoded properly when transmitting (:iss:`5496`)
|
||||||
|
|
||||||
|
- Sessions: Allow controlling which OS Window is active via the ``focus_os_window`` directive
|
||||||
|
|
||||||
|
- Wayland: Fix for bug in NVIDIA drivers that prevents transparency working (:iss:`5479`)
|
||||||
|
|
||||||
|
- Wayland: Fix for a bug that could cause kitty to become non-responsive when
|
||||||
|
using multiple OS windows in a single instance on some compositors (:iss:`5495`)
|
||||||
|
|
||||||
|
- Wayland: Fix for a bug preventing kitty from starting on Hyprland when using a non-unit scale (:iss:`5467`)
|
||||||
|
|
||||||
|
- Wayland: Generate a XDG_ACTIVATION_TOKEN when opening URLs or running programs in the background via the launch action
|
||||||
|
|
||||||
|
- Fix a regression that caused kitty not to restore SIGPIPE after python nukes it when launching children. Affects bash which does not sanitize its signal mask. (:iss:`5500`)
|
||||||
|
|
||||||
|
- Fix a use-after-free when handling fake mouse clicks and the action causes windows to be removed/re-allocated (:iss:`5506`)
|
||||||
|
|
||||||
|
|
||||||
|
0.26.2 [2022-09-05]
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- Allow creating :code:`overlay-main` windows, which are treated as the active window unlike normal overlays (:iss:`5392`)
|
||||||
|
|
||||||
|
- hints kitten: Allow using :doc:`launch` as the program to run, to open the result in a new kitty tab/window/etc. (:iss:`5462`)
|
||||||
|
|
||||||
|
- hyperlinked_grep kitten: Allow control over which parts of ``rg`` output are hyperlinked (:pull:`5428`)
|
||||||
|
|
||||||
|
- Fix regression in 0.26.0 that caused launching kitty without working STDIO handles to result in high CPU usage and prewarming failing (:iss:`5444`)
|
||||||
|
|
||||||
|
- :doc:`/launch`: Allow setting the margin and padding for newly created windows (:iss:`5463`)
|
||||||
|
|
||||||
|
- macOS: Fix regression in 0.26.0 that caused asking the user for a line of input such as for :ac:`set_tab_title` to not work (:iss:`5447`)
|
||||||
|
|
||||||
|
- hints kitten: hyperlink matching: Fix hints occasionally matching text on subsequent line as part of hyperlink (:pull:`5450`)
|
||||||
|
|
||||||
|
- Fix a regression in 0.26.0 that broke mapping of native keys whose key codes did not fit in 21 bits (:iss:`5452`)
|
||||||
|
|
||||||
|
- Wayland: Fix remembering window size not accurate when client side decorations are present
|
||||||
|
|
||||||
|
- Fix an issue where notification identifiers were not sanitized leading to
|
||||||
|
code execution if the user clicked on a notification popup from a malicious
|
||||||
|
source. Thanks to Carter Sande for discovering this vulnerability.
|
||||||
|
|
||||||
|
|
||||||
|
0.26.1 [2022-08-30]
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- ssh kitten: Fix executable permission missing from kitty bootstrap script (:iss:`5438`)
|
||||||
|
|
||||||
|
- Fix a regression in 0.26.0 that caused kitty to no longer set the ``LANG`` environment variable on macOS (:iss:`5439`)
|
||||||
|
|
||||||
|
- Allow specifying a title when using the :ac:`set_tab_title` action (:iss:`5441`)
|
||||||
|
|
||||||
|
|
||||||
|
0.26.0 [2022-08-29]
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- A new option :opt:`remote_control_password` to use fine grained permissions for what can be remote controlled (:disc:`5320`)
|
||||||
|
|
||||||
|
- Reduce startup latency by ~30 milliseconds when running kittens via key bindings inside kitty (:iss:`5159`)
|
||||||
|
|
||||||
|
- A new option :opt:`modify_font` to adjust various font metrics like underlines, cell sizes etc. (:pull:`5265`)
|
||||||
|
|
||||||
|
- A new shortcut :sc:`show_kitty_doc` to display the kitty docs in a browser
|
||||||
|
|
||||||
|
- Graphics protocol: Only delete temp files if they have the string
|
||||||
|
:code:`tty-graphics-protocol` in their file paths. This prevents deletion of arbitrary files in :file:`/tmp`.
|
||||||
|
|
||||||
|
- Deprecate the ``adjust_baseline``, ``adjust_line_height`` and ``adjust_column_width`` options in favor of :opt:`modify_font`
|
||||||
|
|
||||||
|
- Wayland: Fix a regression in the previous release that caused mouse cursor
|
||||||
|
animation and keyboard repeat to stop working when switching seats (:iss:`5188`)
|
||||||
|
|
||||||
|
- Allow resizing windows created in session files (:pull:`5196`)
|
||||||
|
|
||||||
|
- Fix horizontal wheel events not being reported to client programs when they grab the mouse (:iss:`2819`)
|
||||||
|
|
||||||
|
- macOS: Remote control: Fix unable to launch a new OS window or background process when there is no OS window (:iss:`5210`)
|
||||||
|
|
||||||
|
- macOS: Fix unable to open new tab or new window when there is no OS window (:iss:`5276`)
|
||||||
|
|
||||||
|
- kitty @ set-colors: Fix changing inactive_tab_foreground not working (:iss:`5214`)
|
||||||
|
|
||||||
|
- macOS: Fix a regression that caused switching keyboard input using Eisu and
|
||||||
|
Kana keys not working (:iss:`5232`)
|
||||||
|
|
||||||
|
- Add a mappable action to toggle the mirrored setting for the tall and fat
|
||||||
|
layouts (:pull:`5344`)
|
||||||
|
|
||||||
|
- Add a mappable action to switch between predefined bias values for the tall and fat
|
||||||
|
layouts (:pull:`5352`)
|
||||||
|
|
||||||
|
- Wayland: Reduce flicker at startup by not using render frames immediately after a resize (:iss:`5235`)
|
||||||
|
|
||||||
|
- Linux: Update cursor position after all key presses not just pre-edit text
|
||||||
|
changes (:iss:`5241`)
|
||||||
|
|
||||||
|
- ssh kitten: Allow ssh kitten to work from inside tmux, provided the tmux
|
||||||
|
session inherits the correct KITTY env vars (:iss:`5227`)
|
||||||
|
|
||||||
|
- ssh kitten: A new option :code:`--symlink-strategy` to control how symlinks
|
||||||
|
are copied to the remote machine (:iss:`5249`)
|
||||||
|
|
||||||
|
- ssh kitten: Allow pressing :kbd:`Ctrl+C` to abort ssh before the connection is
|
||||||
|
completed (:iss:`5271`)
|
||||||
|
|
||||||
|
- Bash integration: Fix declare not creating global variables in .bashrc (:iss:`5254`)
|
||||||
|
|
||||||
|
- Bash integration: Fix the inherit_errexit option being set by shell integration (:iss:`5349`)
|
||||||
|
|
||||||
|
- :command:`kitty @ scroll-window` allow scrolling by fractions of a screen
|
||||||
|
(:iss:`5294`)
|
||||||
|
|
||||||
|
- remote files kitten: Fix working with files whose names have characters that
|
||||||
|
need to be quoted in shell scripts (:iss:`5313`)
|
||||||
|
|
||||||
|
- Expand ~ in paths configured in :opt:`editor` and :opt:`exe_search_path` (:disc:`5298`)
|
||||||
|
|
||||||
|
- Allow showing the working directory of the active window in tab titles
|
||||||
|
(:pull:`5314`)
|
||||||
|
|
||||||
|
- ssh kitten: Allow completion of ssh options between the destination and command (:iss:`5322`)
|
||||||
|
|
||||||
|
- macOS: Fix speaking selected text not working (:iss:`5357`)
|
||||||
|
|
||||||
|
- Allow ignoring failure to close windows/tabs via rc commands (:disc:`5406`)
|
||||||
|
|
||||||
|
- Fix hyperlinks not present when fetching text from the history buffer
|
||||||
|
(:iss:`5427`)
|
||||||
|
|
||||||
|
|
||||||
0.25.2 [2022-06-07]
|
0.25.2 [2022-06-07]
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@@ -687,7 +953,7 @@ Detailed list of changes
|
|||||||
0.21.2 [2021-06-28]
|
0.21.2 [2021-06-28]
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
- A new :opt:`adjust_baseline` option to adjust the vertical alignment of text
|
- A new ``adjust_baseline`` option to adjust the vertical alignment of text
|
||||||
inside a line (:pull:`3734`)
|
inside a line (:pull:`3734`)
|
||||||
|
|
||||||
- A new :opt:`url_excluded_characters` option to exclude additional characters
|
- A new :opt:`url_excluded_characters` option to exclude additional characters
|
||||||
@@ -861,7 +1127,7 @@ Detailed list of changes
|
|||||||
scrollback buffer onto the screen. Useful, for example, to restore
|
scrollback buffer onto the screen. Useful, for example, to restore
|
||||||
the screen after showing completions below the shell prompt.
|
the screen after showing completions below the shell prompt.
|
||||||
|
|
||||||
- A new remote control command :ref:`at_env` to change the default
|
- A new remote control command :ref:`at-env` to change the default
|
||||||
environment passed to newly created windows (:iss:`3529`)
|
environment passed to newly created windows (:iss:`3529`)
|
||||||
|
|
||||||
- Linux: Fix binary kitty builds not able to load fonts in WOFF2 format
|
- Linux: Fix binary kitty builds not able to load fonts in WOFF2 format
|
||||||
@@ -886,7 +1152,7 @@ Detailed list of changes
|
|||||||
kitty window title bar
|
kitty window title bar
|
||||||
|
|
||||||
- Fix reading :option:`kitty --session` from ``STDIN`` not working when the
|
- Fix reading :option:`kitty --session` from ``STDIN`` not working when the
|
||||||
:option:`kitty --detach` option is used (:iss:`3523`)
|
:code:`kitty --detach` option is used (:iss:`3523`)
|
||||||
|
|
||||||
- Special case rendering of the few remaining Powerline box drawing chars
|
- Special case rendering of the few remaining Powerline box drawing chars
|
||||||
(:iss:`3535`)
|
(:iss:`3535`)
|
||||||
@@ -1444,6 +1710,7 @@ Detailed list of changes
|
|||||||
|
|
||||||
- Fix :option:`--title` not being applied at window creation time (:iss:`2570`)
|
- Fix :option:`--title` not being applied at window creation time (:iss:`2570`)
|
||||||
|
|
||||||
|
|
||||||
0.17.2 [2020-03-29]
|
0.17.2 [2020-03-29]
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@@ -1628,6 +1895,7 @@ Detailed list of changes
|
|||||||
- When windows are semi-transparent and all contain graphics, correctly render
|
- When windows are semi-transparent and all contain graphics, correctly render
|
||||||
them. (:iss:`2310`)
|
them. (:iss:`2310`)
|
||||||
|
|
||||||
|
|
||||||
0.15.1 [2019-12-21]
|
0.15.1 [2019-12-21]
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@@ -1736,6 +2004,7 @@ Detailed list of changes
|
|||||||
|
|
||||||
- Use selection foreground color for underlines as well (:iss:`1982`)
|
- Use selection foreground color for underlines as well (:iss:`1982`)
|
||||||
|
|
||||||
|
|
||||||
0.14.4 [2019-08-31]
|
0.14.4 [2019-08-31]
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@@ -2029,7 +2298,7 @@ Detailed list of changes
|
|||||||
- Allow the user to control the resize debounce time via
|
- Allow the user to control the resize debounce time via
|
||||||
:opt:`resize_debounce_time`.
|
:opt:`resize_debounce_time`.
|
||||||
|
|
||||||
- Remote control: Make the :ref:`at_set-font-size` command more capable.
|
- Remote control: Make the :ref:`at-set-font-size` command more capable.
|
||||||
It can now increment font size and reset it. It also only acts on the
|
It can now increment font size and reset it. It also only acts on the
|
||||||
active top-level window, by default (:iss:`1581`)
|
active top-level window, by default (:iss:`1581`)
|
||||||
|
|
||||||
@@ -2042,6 +2311,7 @@ Detailed list of changes
|
|||||||
- Mouse selection: When extending by word, fix extending selection to non-word
|
- Mouse selection: When extending by word, fix extending selection to non-word
|
||||||
characters not working well (:iss:`1616`)
|
characters not working well (:iss:`1616`)
|
||||||
|
|
||||||
|
|
||||||
0.13.3 [2019-01-19]
|
0.13.3 [2019-01-19]
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@@ -2135,6 +2405,7 @@ Detailed list of changes
|
|||||||
- Fix resizing window smaller and then restoring causing some wrapped lines to not
|
- Fix resizing window smaller and then restoring causing some wrapped lines to not
|
||||||
be properly unwrapped (:iss:`1206`)
|
be properly unwrapped (:iss:`1206`)
|
||||||
|
|
||||||
|
|
||||||
0.13.0 [2018-12-05]
|
0.13.0 [2018-12-05]
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@@ -2232,6 +2503,7 @@ Detailed list of changes
|
|||||||
- Fix hover detection of URLs not working when hovering over the first colon
|
- Fix hover detection of URLs not working when hovering over the first colon
|
||||||
and slash characters in short URLs (:iss:`1201`)
|
and slash characters in short URLs (:iss:`1201`)
|
||||||
|
|
||||||
|
|
||||||
0.12.3 [2018-09-29]
|
0.12.3 [2018-09-29]
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@@ -2308,6 +2580,7 @@ Detailed list of changes
|
|||||||
- Fix using :opt:`focus_follows_mouse` causing text selection with the
|
- Fix using :opt:`focus_follows_mouse` causing text selection with the
|
||||||
mouse to malfunction when using multiple kitty windows (:iss:`1002`)
|
mouse to malfunction when using multiple kitty windows (:iss:`1002`)
|
||||||
|
|
||||||
|
|
||||||
0.12.1 [2018-09-08]
|
0.12.1 [2018-09-08]
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@@ -2338,7 +2611,7 @@ Detailed list of changes
|
|||||||
|
|
||||||
- Fix a regression in 0.12.0 that broke dynamic resizing of layouts (:iss:`860`)
|
- Fix a regression in 0.12.0 that broke dynamic resizing of layouts (:iss:`860`)
|
||||||
|
|
||||||
- Wayland: Allow using the :option:`kitty --class` command line flag
|
- Wayland: Allow using the :code:`kitty --class` command line flag
|
||||||
to set the app id (:iss:`862`)
|
to set the app id (:iss:`862`)
|
||||||
|
|
||||||
- Add completion of the kitty command for the fish shell (:pull:`829`)
|
- Add completion of the kitty command for the fish shell (:pull:`829`)
|
||||||
@@ -2351,6 +2624,7 @@ Detailed list of changes
|
|||||||
- macOS: Diff kitten: Fix syntax highlighting not working because of
|
- macOS: Diff kitten: Fix syntax highlighting not working because of
|
||||||
a bug in the 0.12.0 macOS package
|
a bug in the 0.12.0 macOS package
|
||||||
|
|
||||||
|
|
||||||
0.12.0 [2018-09-01]
|
0.12.0 [2018-09-01]
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@@ -2381,7 +2655,7 @@ Detailed list of changes
|
|||||||
window is not the active window (:iss:`729`)
|
window is not the active window (:iss:`729`)
|
||||||
|
|
||||||
- Remote control: Allow changing the current window layout with a new
|
- Remote control: Allow changing the current window layout with a new
|
||||||
:ref:`at_goto-layout` command (:iss:`845`)
|
:ref:`at-goto-layout` command (:iss:`845`)
|
||||||
|
|
||||||
- Remote control: Allow matching windows by the environment variables of their
|
- Remote control: Allow matching windows by the environment variables of their
|
||||||
child process as well
|
child process as well
|
||||||
@@ -2861,6 +3135,7 @@ Detailed list of changes
|
|||||||
|
|
||||||
- Fix a crash when getting the contents of the scrollback buffer as text
|
- Fix a crash when getting the contents of the scrollback buffer as text
|
||||||
|
|
||||||
|
|
||||||
0.8.1 [2018-03-09]
|
0.8.1 [2018-03-09]
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@@ -2938,6 +3213,7 @@ Detailed list of changes
|
|||||||
- Browsing the scrollback buffer now happens in an overlay window instead of a
|
- Browsing the scrollback buffer now happens in an overlay window instead of a
|
||||||
new window/tab.
|
new window/tab.
|
||||||
|
|
||||||
|
|
||||||
0.7.1 [2018-01-31]
|
0.7.1 [2018-01-31]
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|||||||
160
docs/clipboard.rst
Normal file
160
docs/clipboard.rst
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
Copying all data types to the clipboard
|
||||||
|
==============================================
|
||||||
|
|
||||||
|
There already exists an escape code to allow terminal programs to
|
||||||
|
read/write plain text data from the system clipboard, *OSC 52*.
|
||||||
|
kitty introduces a more advanced protocol that supports:
|
||||||
|
|
||||||
|
* Copy arbitrary data including images, rich text documents, etc.
|
||||||
|
* Allow terminals to ask the user for permission to access the clipboard and
|
||||||
|
report permission denied
|
||||||
|
|
||||||
|
The escape code is *OSC 5522*, an extension of *OSC 52*. The basic format
|
||||||
|
of the escape code is::
|
||||||
|
|
||||||
|
<OSC>5522;metadata;payload<ST>
|
||||||
|
|
||||||
|
Here, *metadata* is a colon separated list of key-value pairs and payload is
|
||||||
|
base64 encoded data. :code:`OSC` is :code:`<ESC>[`.
|
||||||
|
:code:`ST` is the string terminator, :code:`<ESC>\\`.
|
||||||
|
|
||||||
|
Reading data from the system clipboard
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
To read data from the system clipboard, the escape code is::
|
||||||
|
|
||||||
|
<OSC>5522;type=read;<base 64 encoded space separated list of mime types to read><ST>
|
||||||
|
|
||||||
|
For example, to read plain text and PNG data, the payload would be::
|
||||||
|
|
||||||
|
text/plain image/png
|
||||||
|
|
||||||
|
encoded as base64. To read from the primary selection instead of the
|
||||||
|
clipboard, add the key ``loc=primary`` to the metadata section.
|
||||||
|
|
||||||
|
To get the list of MIME types available on the clipboard the payload must be
|
||||||
|
just a period (``.``), encoded as base64.
|
||||||
|
|
||||||
|
The terminal emulator will reply with a sequence of escape codes of the form::
|
||||||
|
|
||||||
|
<OSC>5522;type=read:status=OK<ST>
|
||||||
|
<OSC>5522;type=read:status=DATA:mime=<base 64 encoded mime type>;<base64 encoded data><ST>
|
||||||
|
<OSC>5522;type=read:status=DATA:mime=<base 64 encoded mime type>;<base64 encoded data><ST>
|
||||||
|
.
|
||||||
|
.
|
||||||
|
.
|
||||||
|
<OSC>5522;type=read:status=DONE<ST>
|
||||||
|
|
||||||
|
Here, the ``status=DATA`` packets deliver the data (as base64 encoded bytes)
|
||||||
|
associated with each MIME type. The terminal emulator should chunk up the data
|
||||||
|
for an individual type. A recommended size for each chunk is 4096 bytes. All
|
||||||
|
the chunks for a given type must be transmitted sequentially and only once they
|
||||||
|
are done the chunks for the next type, if any, should be sent. The end of data
|
||||||
|
is indicated by a ``status=DONE`` packet.
|
||||||
|
|
||||||
|
If an error occurs, instead of the opening ``status=OK`` packet the terminal
|
||||||
|
must send a ``status=ERRORCODE`` packet. The error code must be one of:
|
||||||
|
|
||||||
|
``status=ENOSYS``
|
||||||
|
Sent if the requested clipboard type is not available. For example, primary
|
||||||
|
selection is not available on all systems and ``loc=primary`` was used.
|
||||||
|
|
||||||
|
``status=EPERM``
|
||||||
|
Sent if permission to read from the clipboard was denied by the system or
|
||||||
|
the user.
|
||||||
|
|
||||||
|
``status=EBUSY``
|
||||||
|
Sent if there is some temporary problem, such as multiple clients in a
|
||||||
|
multiplexer trying to access the clipboard simultaneously.
|
||||||
|
|
||||||
|
Terminals should ask the user for permission before allowing a read request.
|
||||||
|
However, if a read request only wishes to list the available data types on the
|
||||||
|
clipboard, it should be allowed without a permission prompt. This is so that
|
||||||
|
the user is not presented with a double permission prompt for reading the
|
||||||
|
available MIME types and then reading the actual data.
|
||||||
|
|
||||||
|
|
||||||
|
Writing data to the system clipboard
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
To write data to the system clipboard, the terminal programs sends the
|
||||||
|
following sequence of packets::
|
||||||
|
|
||||||
|
<OSC>5522;type=write<ST>
|
||||||
|
<OSC>5522;type=wdata:mime=<base64 encoded mime type>;<base 64 encoded chunk of data for this type><ST>
|
||||||
|
<OSC>5522;type=wdata:mime=<base64 encoded mime type>;<base 64 encoded chunk of data for this type><ST>
|
||||||
|
.
|
||||||
|
.
|
||||||
|
.
|
||||||
|
<OSC>5522;type=wdata<ST>
|
||||||
|
|
||||||
|
The final packet with no mime and no data indicates end of transmission. The
|
||||||
|
data for every MIME type should be split into chunks of no more than 4096
|
||||||
|
bytes. All the chunks for a given MIME type must be sent sequentially, before
|
||||||
|
sending chunks for the next MIME type. After the transmission is complete, the
|
||||||
|
terminal replies with a single packet indicating success::
|
||||||
|
|
||||||
|
<OSC>5522;type=write:status=DONE<ST>
|
||||||
|
|
||||||
|
If an error occurs the terminal can, at any time, send an error packet of the
|
||||||
|
form::
|
||||||
|
|
||||||
|
<OSC>5522;type=write:status=ERRORCODE<ST>
|
||||||
|
|
||||||
|
Here ``ERRORCODE`` must be one of:
|
||||||
|
|
||||||
|
``status=EIO``
|
||||||
|
An I/O error occurred while processing the data
|
||||||
|
``status=EINVAL``
|
||||||
|
One of the packets was invalid, usually because of invalid base64 encoding.
|
||||||
|
``status=ENOSYS``
|
||||||
|
The client asked to write to the primary selection with (``loc=primary``) and that is not
|
||||||
|
available on the system
|
||||||
|
``status=EPERM``
|
||||||
|
Sent if permission to write to the clipboard was denied by the system or
|
||||||
|
the user.
|
||||||
|
``status=EBUSY``
|
||||||
|
Sent if there is some temporary problem, such as multiple clients in a
|
||||||
|
multiplexer trying to access the clipboard simultaneously.
|
||||||
|
|
||||||
|
Once an error occurs, the terminal must ignore all further OSC 5522 write related packets until it
|
||||||
|
sees the start of a new write with a ``type=write`` packet.
|
||||||
|
|
||||||
|
The client can send to the primary selection instead of the clipboard by adding
|
||||||
|
``loc=primary`` to the initial ``type=write`` packet.
|
||||||
|
|
||||||
|
Finally, clients have the ability to *alias* MIME types when sending data to
|
||||||
|
the clipboard. To do that, the client must send a ``type=walias`` packet of the
|
||||||
|
form::
|
||||||
|
|
||||||
|
<OSC>5522;type=walias;mime=<base64 encoded target MIME type>;<base64 encoded, space separated list of aliases><ST>
|
||||||
|
|
||||||
|
The effect of an alias is that the system clipboard will make available all the
|
||||||
|
aliased MIME types, with the same data as was transmitted for the target MIME
|
||||||
|
type. This saves bandwidth, allowing the client to only transmit one copy of
|
||||||
|
the data, but create multiple references to it in the system clipboard. Alias
|
||||||
|
packets can be sent anytime after the initial write packet and before the end
|
||||||
|
of data packet.
|
||||||
|
|
||||||
|
|
||||||
|
Support for terminal multiplexers
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
Since this protocol involves two way communication between the terminal
|
||||||
|
emulator and the client program, multiplexers need a way to know which window
|
||||||
|
to send responses from the terminal to. In order to make this possible, the
|
||||||
|
metadata portion of this escape code includes an optional ``id`` field. If
|
||||||
|
present the terminal emulator must send it back unchanged with every response.
|
||||||
|
Valid ids must include only characters from the set: ``[a-zA-Z0-9-_+.]``. Any
|
||||||
|
other characters must be stripped out from the id by the terminal emulator
|
||||||
|
before retransmitting it.
|
||||||
|
|
||||||
|
Note that when using a terminal multiplexer it is possible for two different
|
||||||
|
programs to tread on each others clipboard requests. This is fundamentally
|
||||||
|
unavoidable since the system clipboard is a single global shared resource.
|
||||||
|
However, there is an additional complication where responses form this protocol
|
||||||
|
could get lost if, for instance, multiple write requests are received
|
||||||
|
simultaneously. It is up to well designed multiplexers to ensure that only a
|
||||||
|
single request is in flight at a time. The multiplexer can abort requests by
|
||||||
|
sending back the ``EBUSY`` error code indicating some other window is trying
|
||||||
|
to access the clipboard.
|
||||||
41
docs/conf.py
41
docs/conf.py
@@ -18,9 +18,7 @@ from typing import Any, Callable, Dict, Iterable, List, Tuple
|
|||||||
from docutils import nodes
|
from docutils import nodes
|
||||||
from docutils.parsers.rst.roles import set_classes
|
from docutils.parsers.rst.roles import set_classes
|
||||||
from pygments.lexer import RegexLexer, bygroups # type: ignore
|
from pygments.lexer import RegexLexer, bygroups # type: ignore
|
||||||
from pygments.token import ( # type: ignore
|
from pygments.token import Comment, Keyword, Literal, Name, Number, String, Whitespace # type: ignore
|
||||||
Comment, Keyword, Literal, Name, Number, String, Whitespace
|
|
||||||
)
|
|
||||||
from sphinx import addnodes, version_info
|
from sphinx import addnodes, version_info
|
||||||
from sphinx.util.logging import getLogger
|
from sphinx.util.logging import getLogger
|
||||||
|
|
||||||
@@ -120,10 +118,23 @@ html_title = 'kitty'
|
|||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
# further. For a list of options available for each theme, see the
|
# further. For a list of options available for each theme, see the
|
||||||
# documentation.
|
# documentation.
|
||||||
#
|
github_icon_path = 'M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z' # noqa
|
||||||
|
|
||||||
html_theme_options: Dict[str, Any] = {
|
html_theme_options: Dict[str, Any] = {
|
||||||
'sidebar_hide_name': True,
|
'sidebar_hide_name': True,
|
||||||
'navigation_with_keys': True,
|
'navigation_with_keys': True,
|
||||||
|
'footer_icons': [
|
||||||
|
{
|
||||||
|
"name": "GitHub",
|
||||||
|
"url": "https://github.com/kovidgoyal/kitty",
|
||||||
|
"html": f"""
|
||||||
|
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 16 16">
|
||||||
|
<path fill-rule="evenodd" d="{github_icon_path}"></path>
|
||||||
|
</svg>
|
||||||
|
""",
|
||||||
|
"class": "",
|
||||||
|
},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -202,10 +213,10 @@ def commit_role(
|
|||||||
|
|
||||||
# CLI docs {{{
|
# CLI docs {{{
|
||||||
def write_cli_docs(all_kitten_names: Iterable[str]) -> None:
|
def write_cli_docs(all_kitten_names: Iterable[str]) -> None:
|
||||||
from kitty.cli import option_spec_as_rst
|
|
||||||
from kitty.launch import options_spec as launch_options_spec
|
|
||||||
from kittens.ssh.copy import option_text
|
from kittens.ssh.copy import option_text
|
||||||
from kittens.ssh.options.definition import copy_message
|
from kittens.ssh.options.definition import copy_message
|
||||||
|
from kitty.cli import option_spec_as_rst
|
||||||
|
from kitty.launch import options_spec as launch_options_spec
|
||||||
with open('generated/ssh-copy.rst', 'w') as f:
|
with open('generated/ssh-copy.rst', 'w') as f:
|
||||||
f.write(option_spec_as_rst(
|
f.write(option_spec_as_rst(
|
||||||
appname='copy', ospec=option_text, heading_char='^',
|
appname='copy', ospec=option_text, heading_char='^',
|
||||||
@@ -236,12 +247,14 @@ if you specify a program-to-run you can use the special placeholder
|
|||||||
from kitty.rc.base import cli_params_for
|
from kitty.rc.base import cli_params_for
|
||||||
for cmd_name in sorted(all_command_names()):
|
for cmd_name in sorted(all_command_names()):
|
||||||
func = command_for_name(cmd_name)
|
func = command_for_name(cmd_name)
|
||||||
p(f'.. _at_{func.name}:\n')
|
p(f'.. _at-{func.name}:\n')
|
||||||
p('kitty @', func.name)
|
p('kitty @', func.name)
|
||||||
p('-' * 120)
|
p('-' * 120)
|
||||||
p('.. program::', 'kitty @', func.name)
|
p('.. program::', 'kitty @', func.name)
|
||||||
p('\n\n' + as_rst(*cli_params_for(func)))
|
p('\n\n' + as_rst(*cli_params_for(func)))
|
||||||
from kittens.runner import get_kitten_cli_docs
|
from kittens.runner import get_kitten_cli_docs
|
||||||
|
from kitty.fast_data_types import wrapped_kitten_names
|
||||||
|
|
||||||
for kitten in all_kitten_names:
|
for kitten in all_kitten_names:
|
||||||
data = get_kitten_cli_docs(kitten)
|
data = get_kitten_cli_docs(kitten)
|
||||||
if data:
|
if data:
|
||||||
@@ -250,7 +263,11 @@ if you specify a program-to-run you can use the special placeholder
|
|||||||
p('.. program::', 'kitty +kitten', kitten)
|
p('.. program::', 'kitty +kitten', kitten)
|
||||||
p('\nSource code for', kitten)
|
p('\nSource code for', kitten)
|
||||||
p('-' * 72)
|
p('-' * 72)
|
||||||
p(f'\nThe source code for this kitten is `available on GitHub <https://github.com/kovidgoyal/kitty/tree/master/kittens/{kitten}>`_.')
|
if kitten in wrapped_kitten_names():
|
||||||
|
scurl = f'https://github.com/kovidgoyal/kitty/tree/master/tools/cmd/{kitten}'
|
||||||
|
else:
|
||||||
|
scurl = f'https://github.com/kovidgoyal/kitty/tree/master/kittens/{kitten}'
|
||||||
|
p(f'\nThe source code for this kitten is `available on GitHub <{scurl}>`_.')
|
||||||
p('\nCommand Line Interface')
|
p('\nCommand Line Interface')
|
||||||
p('-' * 72)
|
p('-' * 72)
|
||||||
p('\n\n' + option_spec_as_rst(
|
p('\n\n' + option_spec_as_rst(
|
||||||
@@ -261,10 +278,8 @@ if you specify a program-to-run you can use the special placeholder
|
|||||||
|
|
||||||
|
|
||||||
def write_remote_control_protocol_docs() -> None: # {{{
|
def write_remote_control_protocol_docs() -> None: # {{{
|
||||||
from kitty.rc.base import (
|
from kitty.rc.base import RemoteCommand, all_command_names, command_for_name
|
||||||
RemoteCommand, all_command_names, command_for_name
|
field_pat = re.compile(r'\s*([^:]+?)\s*:\s*(.+)')
|
||||||
)
|
|
||||||
field_pat = re.compile(r'\s*([a-zA-Z0-9_+]+)\s*:\s*(.+)')
|
|
||||||
|
|
||||||
def format_cmd(p: Callable[..., None], name: str, cmd: RemoteCommand) -> None:
|
def format_cmd(p: Callable[..., None], name: str, cmd: RemoteCommand) -> None:
|
||||||
p(name)
|
p(name)
|
||||||
@@ -276,7 +291,7 @@ def write_remote_control_protocol_docs() -> None: # {{{
|
|||||||
if m is None:
|
if m is None:
|
||||||
p(line)
|
p(line)
|
||||||
else:
|
else:
|
||||||
fields.append((m.group(1), m.group(2)))
|
fields.append((m.group(1).split('/')[0], m.group(2)))
|
||||||
if fields:
|
if fields:
|
||||||
p('\nFields are:\n')
|
p('\nFields are:\n')
|
||||||
for (name, desc) in fields:
|
for (name, desc) in fields:
|
||||||
|
|||||||
@@ -27,11 +27,13 @@ line.
|
|||||||
|
|
||||||
.. _include:
|
.. _include:
|
||||||
|
|
||||||
You can include secondary config files via the :code:`include` directive. If
|
You can include secondary config files via the :code:`include` directive. If
|
||||||
you use a relative path for :code:`include`, it is resolved with respect to the
|
you use a relative path for :code:`include`, it is resolved with respect to the
|
||||||
location of the current config file. Note that environment variables are
|
location of the current config file. Note that environment variables are
|
||||||
expanded, so :code:`${USER}.conf` becomes :file:`name.conf` if
|
expanded, so :code:`${USER}.conf` becomes :file:`name.conf` if
|
||||||
:code:`USER=name`. Also, you can use :code:`globinclude` to include files
|
:code:`USER=name`. A special environment variable :envvar:`KITTY_OS` is available,
|
||||||
|
to detect the operating system. It is ``linux``, ``macos`` or ``bsd``.
|
||||||
|
Also, you can use :code:`globinclude` to include files
|
||||||
matching a shell glob pattern and :code:`envinclude` to include configuration
|
matching a shell glob pattern and :code:`envinclude` to include configuration
|
||||||
from environment variables. For example::
|
from environment variables. For example::
|
||||||
|
|
||||||
@@ -66,6 +68,11 @@ Sample kitty.conf
|
|||||||
pre-existing :file:`kitty.conf`, then that will be used instead, delete it to
|
pre-existing :file:`kitty.conf`, then that will be used instead, delete it to
|
||||||
see the sample file.
|
see the sample file.
|
||||||
|
|
||||||
|
A default configuration file can also be generated by running::
|
||||||
|
|
||||||
|
kitty +runpy 'from kitty.config import *; print(commented_out_default_config())'
|
||||||
|
|
||||||
|
This will print the commented out default config file to :file:`STDOUT`.
|
||||||
|
|
||||||
All mappable actions
|
All mappable actions
|
||||||
------------------------
|
------------------------
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ and the terminal emulator should hold off displaying it. A value of ``1`` means
|
|||||||
the notification is done, and should be displayed. You can specify the title or
|
the notification is done, and should be displayed. You can specify the title or
|
||||||
body multiple times and the terminal emulator will concatenate them, thereby
|
body multiple times and the terminal emulator will concatenate them, thereby
|
||||||
allowing arbitrarily long text (terminal emulators are free to impose a sensible
|
allowing arbitrarily long text (terminal emulators are free to impose a sensible
|
||||||
limit to avoid Denial-of-Service attacks).
|
limit to avoid Denial-of-Service attacks). The size of the payload must be no
|
||||||
|
longer than ``2048`` bytes, *before being encoded*.
|
||||||
|
|
||||||
Both the ``title`` and ``body`` payloads must be either UTF-8 encoded plain
|
Both the ``title`` and ``body`` payloads must be either UTF-8 encoded plain
|
||||||
text with no embedded escape codes, or UTF-8 text that is Base64 encoded, in
|
text with no embedded escape codes, or UTF-8 text that is Base64 encoded, in
|
||||||
|
|||||||
54
docs/extract-rst-targets.py
Executable file
54
docs/extract-rst-targets.py
Executable file
@@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# License: GPLv3 Copyright: 2022, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from typing import Dict, Iterator
|
||||||
|
|
||||||
|
tgt_pat = re.compile(r'^.. _(\S+?):$', re.MULTILINE)
|
||||||
|
title_pat = re.compile('^(.+)\n[-=^#*]{5,}$', re.MULTILINE)
|
||||||
|
|
||||||
|
|
||||||
|
def find_explicit_targets(text: str) -> Iterator[str]:
|
||||||
|
for m in tgt_pat.finditer(text):
|
||||||
|
yield m.group(1)
|
||||||
|
|
||||||
|
|
||||||
|
def find_page_title(text: str) -> str:
|
||||||
|
for m in title_pat.finditer(text):
|
||||||
|
return m.group(1)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> Dict[str, Dict[str, str]]:
|
||||||
|
refs = {}
|
||||||
|
docs = {}
|
||||||
|
base = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
for dirpath, dirnames, filenames in os.walk(base):
|
||||||
|
if 'generated' in dirnames:
|
||||||
|
dirnames.remove('generated')
|
||||||
|
for f in filenames:
|
||||||
|
if f.endswith('.rst'):
|
||||||
|
with open(os.path.join(dirpath, f)) as stream:
|
||||||
|
raw = stream.read()
|
||||||
|
href = os.path.relpath(stream.name, base).replace(os.sep, '/')
|
||||||
|
href = href.rpartition('.')[0] + '/'
|
||||||
|
docs[href.rstrip('/')] = find_page_title(raw)
|
||||||
|
first_line = raw.lstrip('\n').partition('\n')[0]
|
||||||
|
first_target_added = False
|
||||||
|
for explicit_target in find_explicit_targets(raw):
|
||||||
|
# Shorten the reference link to the top of the page.
|
||||||
|
# Note that anchor links should still be used in HTML docs
|
||||||
|
# to allow jumping within the same page.
|
||||||
|
if not first_target_added:
|
||||||
|
first_target_added = True
|
||||||
|
if first_line.startswith(f'.. _{explicit_target}:'):
|
||||||
|
refs[explicit_target] = href
|
||||||
|
continue
|
||||||
|
refs[explicit_target] = href + f'#{explicit_target.replace("_", "-")}'
|
||||||
|
return {'ref': refs, 'doc': docs}
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import json
|
||||||
|
print(json.dumps(main(), indent=2))
|
||||||
224
docs/faq.rst
224
docs/faq.rst
@@ -27,96 +27,145 @@ turned off for specific symbols using :opt:`narrow_symbols`.
|
|||||||
Using a color theme with a background color does not work well in vim?
|
Using a color theme with a background color does not work well in vim?
|
||||||
-----------------------------------------------------------------------
|
-----------------------------------------------------------------------
|
||||||
|
|
||||||
First make sure you have not changed the :envvar:`TERM` environment variable, it
|
Sadly, vim has very poor out-of-the-box detection for modern terminal features.
|
||||||
should be ``xterm-kitty``. vim uses *background color erase* even if the
|
Furthermore, it `recently broke detection even more <https://github.com/vim/vim/issues/11729>`__.
|
||||||
terminfo file does not contain the ``bce`` capability. This is a bug in vim. You
|
It kind of, but not really, supports terminfo, except it overrides it with its own hard-coded
|
||||||
can work around it by adding the following to your vimrc::
|
values when it feels like it. Worst of all, it has no ability to detect modern
|
||||||
|
features not present in terminfo, at all, even security sensitive ones like
|
||||||
|
bracketed paste.
|
||||||
|
|
||||||
|
Thankfully, probably as a consequence of this lack of detection, vim allows users to
|
||||||
|
configure these low level details. So, to make vim work well with any modern
|
||||||
|
terminal, including kitty, add the following to your :file:`~/.vimrc`.
|
||||||
|
|
||||||
|
.. code-block:: vim
|
||||||
|
|
||||||
|
" Mouse support
|
||||||
|
set mouse=a
|
||||||
|
set ttymouse=sgr
|
||||||
|
set balloonevalterm
|
||||||
|
" Styled and colored underline support
|
||||||
|
let &t_AU = "\e[58:5:%dm"
|
||||||
|
let &t_8u = "\e[58:2:%lu:%lu:%lum"
|
||||||
|
let &t_Us = "\e[4:2m"
|
||||||
|
let &t_Cs = "\e[4:3m"
|
||||||
|
let &t_ds = "\e[4:4m"
|
||||||
|
let &t_Ds = "\e[4:5m"
|
||||||
|
let &t_Ce = "\e[4:0m"
|
||||||
|
" Strikethrough
|
||||||
|
let &t_Ts = "\e[9m"
|
||||||
|
let &t_Te = "\e[29m"
|
||||||
|
" Truecolor support
|
||||||
|
let &t_8f = "\e[38:2:%lu:%lu:%lum"
|
||||||
|
let &t_8b = "\e[48:2:%lu:%lu:%lum"
|
||||||
|
let &t_RF = "\e]10;?\e\\"
|
||||||
|
let &t_RB = "\e]11;?\e\\"
|
||||||
|
" Bracketed paste
|
||||||
|
let &t_BE = "\e[?2004h"
|
||||||
|
let &t_BD = "\e[?2004l"
|
||||||
|
let &t_PS = "\e[200~"
|
||||||
|
let &t_PE = "\e[201~"
|
||||||
|
" Cursor control
|
||||||
|
let &t_RC = "\e[?12$p"
|
||||||
|
let &t_SH = "\e[%d q"
|
||||||
|
let &t_RS = "\eP$q q\e\\"
|
||||||
|
let &t_SI = "\e[5 q"
|
||||||
|
let &t_SR = "\e[3 q"
|
||||||
|
let &t_EI = "\e[1 q"
|
||||||
|
let &t_VS = "\e[?12l"
|
||||||
|
" Focus tracking
|
||||||
|
let &t_fe = "\e[?1004h"
|
||||||
|
let &t_fd = "\e[?1004l"
|
||||||
|
execute "set <FocusGained>=\<Esc>[I"
|
||||||
|
execute "set <FocusLost>=\<Esc>[O"
|
||||||
|
" Window title
|
||||||
|
let &t_ST = "\e[22;2t"
|
||||||
|
let &t_RT = "\e[23;2t"
|
||||||
|
|
||||||
|
" vim hardcodes background color erase even if the terminfo file does
|
||||||
|
" not contain bce. This causes incorrect background rendering when
|
||||||
|
" using a color theme with a background color in terminals such as
|
||||||
|
" kitty that do not support background color erase.
|
||||||
let &t_ut=''
|
let &t_ut=''
|
||||||
|
|
||||||
See :doc:`here <deccara>` for why |kitty| does not support background color
|
These settings must be placed **before** setting the ``colorscheme``. It is
|
||||||
erase.
|
also important that the value of the vim ``term`` variable is not changed
|
||||||
|
after these settings.
|
||||||
|
|
||||||
|
I get errors about the terminal being unknown or opening the terminal failing or functional keys like arrow keys don't work?
|
||||||
|
-------------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
I get errors about the terminal being unknown or opening the terminal failing when SSHing into a different computer?
|
These issues all have the same root cause: the kitty terminfo files not being
|
||||||
-----------------------------------------------------------------------------------------------------------------------
|
available. The most common way this happens is SSHing into a computer that does
|
||||||
|
not have the kitty terminfo files. The simplest fix for that is running::
|
||||||
This happens because the |kitty| terminfo files are not available on the server.
|
|
||||||
You can ssh in using the following command which will automatically copy the
|
|
||||||
terminfo files to the server::
|
|
||||||
|
|
||||||
kitty +kitten ssh myserver
|
kitty +kitten ssh myserver
|
||||||
|
|
||||||
|
It will automatically copy over the terminfo files and also magically enable
|
||||||
|
:doc:`shell integration </shell-integration>` on the remote machine.
|
||||||
|
|
||||||
This :doc:`ssh kitten <kittens/ssh>` takes all the same command line arguments
|
This :doc:`ssh kitten <kittens/ssh>` takes all the same command line arguments
|
||||||
as :program:`ssh`, you can alias it to something small in your shell's rc files
|
as :program:`ssh`, you can alias it to something small in your shell's rc files
|
||||||
to avoid having to type it each time::
|
to avoid having to type it each time::
|
||||||
|
|
||||||
alias s="kitty +kitten ssh"
|
alias s="kitty +kitten ssh"
|
||||||
|
|
||||||
If the ssh kitten fails, use the following one-liner instead (it is slower as it
|
If this does not work, see :ref:`manual_terminfo_copy` for alternative ways to
|
||||||
needs to ssh into the server twice, but will work with most servers)::
|
get the kitty terminfo files onto a remote computer.
|
||||||
|
|
||||||
infocmp -a xterm-kitty | ssh myserver tic -x -o \~/.terminfo /dev/stdin
|
The next most common reason for this is if you are running commands as root
|
||||||
|
using :program:`sudo` or :program:`su`. These programs often filter the
|
||||||
|
:envvar:`TERMINFO` environment variable which is what points to the kitty
|
||||||
|
terminfo files.
|
||||||
|
|
||||||
If you are behind a proxy (like Balabit) that prevents this, or :program:`tic`
|
First, make sure the :envvar:`TERM` is set to ``xterm-kitty`` in the sudo
|
||||||
comes with macOS that does not support reading from STDIN, you must redirect the
|
environment. By default, it should be automatically copied over.
|
||||||
first command to a file, copy that to the server and run :program:`tic`
|
|
||||||
manually. If you connect to a server, embedded or Android system that doesn't
|
|
||||||
have :program:`tic`, copy over your local file terminfo to the other system as
|
|
||||||
:file:`~/.terminfo/x/xterm-kitty`.
|
|
||||||
|
|
||||||
Really, the correct solution for this is to convince the OpenSSH maintainers to
|
If you are using a well maintained Linux distribution, it will have a
|
||||||
have :program:`ssh` do this automatically, if possible, when connecting to a
|
``kitty-terminfo`` package that you can simply install to make the kitty
|
||||||
server, so that all terminals work transparently.
|
terminfo files available system-wide. Then the problem will no longer occur.
|
||||||
|
|
||||||
If the server is running FreeBSD, or another system that relies on termcap
|
Alternately, you can configure :program:`sudo` to preserve :envvar:`TERMINFO`
|
||||||
rather than terminfo, you will need to convert the terminfo file on your local
|
by running ``sudo visudo`` and adding the following line::
|
||||||
machine by running (on local machine with |kitty|)::
|
|
||||||
|
|
||||||
infocmp -CrT0 xterm-kitty
|
|
||||||
|
|
||||||
The output of this command is the termcap description, which should be appended
|
|
||||||
to :file:`/usr/share/misc/termcap` on the remote server. Then run the following
|
|
||||||
command to apply your change (on the server)::
|
|
||||||
|
|
||||||
cap_mkdb /usr/share/misc/termcap
|
|
||||||
|
|
||||||
|
|
||||||
Keys such as arrow keys, backspace, delete, home/end, etc. do not work when using su or sudo?
|
|
||||||
-------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
Make sure the :envvar:`TERM` environment variable, is ``xterm-kitty``. And
|
|
||||||
either the :envvar:`TERMINFO` environment variable points to a directory
|
|
||||||
containing :file:`x/xterm-kitty` or that file is under :file:`~/.terminfo/x/`.
|
|
||||||
|
|
||||||
For macOS, you may also need to put that file under :file:`~/.terminfo/78/`::
|
|
||||||
|
|
||||||
mkdir -p ~/.terminfo/{78,x}
|
|
||||||
ln -snf ../x/xterm-kitty ~/.terminfo/78/xterm-kitty
|
|
||||||
tic -x -o ~/.terminfo "$KITTY_INSTALLATION_DIR/terminfo/kitty.terminfo"
|
|
||||||
|
|
||||||
Note that :program:`sudo` might remove :envvar:`TERMINFO`. Then setting it at
|
|
||||||
the shell prompt can be too late, because command line editing may not be
|
|
||||||
reinitialized. In that case you can either ask :program:`sudo` to set it or if
|
|
||||||
that is not supported, insert an :program:`env` command before starting the
|
|
||||||
shell, or, if not possible, after sudo start another shell providing the right
|
|
||||||
terminfo path::
|
|
||||||
|
|
||||||
sudo … TERMINFO=$HOME/.terminfo bash -i
|
|
||||||
sudo … env TERMINFO=$HOME/.terminfo bash -i
|
|
||||||
TERMINFO=/home/ORIGINALUSER/.terminfo exec bash -i
|
|
||||||
|
|
||||||
You can configure :program:`sudo` to preserve :envvar:`TERMINFO` by running
|
|
||||||
``sudo visudo`` and adding the following line::
|
|
||||||
|
|
||||||
Defaults env_keep += "TERM TERMINFO"
|
Defaults env_keep += "TERM TERMINFO"
|
||||||
|
|
||||||
|
If none of these are suitable for you, you can run sudo as follows::
|
||||||
|
|
||||||
|
sudo TERMINFO="$TERMINFO" -s -H
|
||||||
|
|
||||||
|
This will start a new root shell with the correct :envvar:`TERMINFO` value from your
|
||||||
|
current environment copied over.
|
||||||
|
|
||||||
If you have double width characters in your prompt, you may also need to
|
If you have double width characters in your prompt, you may also need to
|
||||||
explicitly set a UTF-8 locale, like::
|
explicitly set a UTF-8 locale, like::
|
||||||
|
|
||||||
export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
|
export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
|
||||||
|
|
||||||
|
|
||||||
|
I cannot use the key combination X in program Y?
|
||||||
|
-------------------------------------------------------
|
||||||
|
|
||||||
|
First, run::
|
||||||
|
|
||||||
|
kitty +kitten show_key -m kitty
|
||||||
|
|
||||||
|
Press the key combination X. If the kitten reports the key press
|
||||||
|
that means kitty is correctly sending the key press to terminal programs.
|
||||||
|
You need to report the issue to the developer of the terminal program. Most
|
||||||
|
likely they have not added support for :doc:`/keyboard-protocol`.
|
||||||
|
|
||||||
|
If the kitten does not report it, it means that the key is bound to some action
|
||||||
|
in kitty. You can unbind it in :file:`kitty.conf` with:
|
||||||
|
|
||||||
|
.. code-block:: conf
|
||||||
|
|
||||||
|
map X no_op
|
||||||
|
|
||||||
|
Here X is the keys you press on the keyboard. So for example
|
||||||
|
:kbd:`ctrl+shift+1`.
|
||||||
|
|
||||||
|
|
||||||
How do I change the colors in a running kitty instance?
|
How do I change the colors in a running kitty instance?
|
||||||
------------------------------------------------------------
|
------------------------------------------------------------
|
||||||
|
|
||||||
@@ -132,8 +181,8 @@ You can also define keyboard shortcuts to set colors, for example::
|
|||||||
map f1 set_colors --configured /path/to/some/config/file/colors.conf
|
map f1 set_colors --configured /path/to/some/config/file/colors.conf
|
||||||
|
|
||||||
Or you can enable :doc:`remote control <remote-control>` for |kitty| and use
|
Or you can enable :doc:`remote control <remote-control>` for |kitty| and use
|
||||||
:ref:`at_set-colors`. The shortcut mapping technique has the same syntax as the
|
:ref:`at-set-colors`. The shortcut mapping technique has the same syntax as the
|
||||||
remote control command, for details, see :ref:`at_set-colors`.
|
remote control command, for details, see :ref:`at-set-colors`.
|
||||||
|
|
||||||
To change colors when SSHing into a remote host, use the :opt:`color_scheme
|
To change colors when SSHing into a remote host, use the :opt:`color_scheme
|
||||||
<kitten-ssh.color_scheme>` setting for the :doc:`ssh kitten <kittens/ssh>`.
|
<kitten-ssh.color_scheme>` setting for the :doc:`ssh kitten <kittens/ssh>`.
|
||||||
@@ -205,13 +254,15 @@ fonts to be freely resizable, so it does not support bitmapped fonts.
|
|||||||
.. note::
|
.. note::
|
||||||
If you are trying to use a font patched with `Nerd Fonts
|
If you are trying to use a font patched with `Nerd Fonts
|
||||||
<https://nerdfonts.com/>`__ symbols, don't do that as patching destroys
|
<https://nerdfonts.com/>`__ symbols, don't do that as patching destroys
|
||||||
fonts. There is no need, simply install the standalone ``Symbols Nerd Font``
|
fonts. There is no need, simply install the standalone ``Symbols Nerd Font Mono``
|
||||||
(the file :file:`NerdFontsSymbolsOnly.zip` from the `Nerd Fonts releases page
|
(the file :file:`NerdFontsSymbolsOnly.zip` from the `Nerd Fonts releases page
|
||||||
<https://github.com/ryanoasis/nerd-fonts/releases>`__). kitty should pick up
|
<https://github.com/ryanoasis/nerd-fonts/releases>`__). kitty should pick up
|
||||||
symbols from it automatically, and you can tell it to do so explicitly in
|
symbols from it automatically, and you can tell it to do so explicitly in
|
||||||
case it doesn't with the :opt:`symbol_map` directive::
|
case it doesn't with the :opt:`symbol_map` directive::
|
||||||
|
|
||||||
symbol_map U+23FB-U+23FE,U+2665,U+26A1,U+2B58,U+E000-U+E00A,U+E0A0-U+E0A3,U+E0B0-U+E0C8,U+E0CA,U+E0CC-U+E0D2,U+E0D4,U+E200-U+E2A9,U+E300-U+E3E3,U+E5FA-U+E62F,U+E700-U+E7C5,U+F000-U+F2E0,U+F300-U+F31C,U+F400-U+F4A9,U+F500-U+F8FF Symbols Nerd Font
|
# Nerd Fonts v2.2.2
|
||||||
|
|
||||||
|
symbol_map U+23FB-U+23FE,U+2665,U+26A1,U+2B58,U+E000-U+E00A,U+E0A0-U+E0A3,U+E0B0-U+E0C8,U+E0CA,U+E0CC-U+E0D2,U+E0D4,U+E200-U+E2A9,U+E300-U+E3E3,U+E5FA-U+E634,U+E700-U+E7C5,U+EA60-U+EBEB,U+F000-U+F2E0,U+F300-U+F32F,U+F400-U+F4A9,U+F500-U+F8FF Symbols Nerd Font Mono
|
||||||
|
|
||||||
Those Unicode symbols beyond the ``E000-F8FF`` Unicode private use area are
|
Those Unicode symbols beyond the ``E000-F8FF`` Unicode private use area are
|
||||||
not included.
|
not included.
|
||||||
@@ -283,13 +334,40 @@ homepage:
|
|||||||
:target: https://github.com/igrmk/whiskers
|
:target: https://github.com/igrmk/whiskers
|
||||||
:width: 256
|
:width: 256
|
||||||
|
|
||||||
On macOS you can change the icon by following the steps:
|
.. image:: https://github.com/samholmes/whiskers/raw/main/whiskers.png
|
||||||
|
:target: https://github.com/samholmes/whiskers
|
||||||
|
:width: 256
|
||||||
|
|
||||||
|
On macOS you can put :file:`kitty.app.icns` or :file:`kitty.app.png` in the
|
||||||
|
:ref:`kitty configuration directory <confloc>`, and this icon will be applied
|
||||||
|
automatically at startup. Unfortunately, Apple's Dock does not change its
|
||||||
|
cached icon so the custom icon will revert when kitty is quit. Run the
|
||||||
|
following to force the Dock to update its cached icons:
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
rm /var/folders/*/*/*/com.apple.dock.iconcache; killall Dock
|
||||||
|
|
||||||
|
If you prefer not to keep a custom icon in the kitty config folder, you can
|
||||||
|
also set it with the following command:
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
# Set kitty.icns as the icon for currently running kitty
|
||||||
|
kitty +runpy 'from kitty.fast_data_types import cocoa_set_app_icon; import sys; cocoa_set_app_icon(*sys.argv[1:]); print("OK")' kitty.icns
|
||||||
|
|
||||||
|
# Set the icon for app bundle specified by the path
|
||||||
|
kitty +runpy 'from kitty.fast_data_types import cocoa_set_app_icon; import sys; cocoa_set_app_icon(*sys.argv[1:]); print("OK")' /path/to/icon.png /Applications/kitty.app
|
||||||
|
|
||||||
|
You can also change the icon manually by following the steps:
|
||||||
|
|
||||||
#. Find :file:`kitty.app` in the Applications folder, select it and press :kbd:`⌘+I`
|
#. Find :file:`kitty.app` in the Applications folder, select it and press :kbd:`⌘+I`
|
||||||
#. Drag :file:`kitty.icns` onto the application icon in the kitty info pane
|
#. Drag :file:`kitty.icns` onto the application icon in the kitty info pane
|
||||||
#. Delete the icon cache and restart Dock::
|
#. Delete the icon cache and restart Dock:
|
||||||
|
|
||||||
$ rm /var/folders/*/*/*/com.apple.dock.iconcache; killall Dock
|
.. code-block:: sh
|
||||||
|
|
||||||
|
rm /var/folders/*/*/*/com.apple.dock.iconcache; killall Dock
|
||||||
|
|
||||||
|
|
||||||
How do I map key presses in kitty to different keys in the terminal program?
|
How do I map key presses in kitty to different keys in the terminal program?
|
||||||
@@ -308,7 +386,11 @@ the :sc:`send_text <send_text>` you can use the ``show_key`` kitten. Run::
|
|||||||
Then press the key you want to emulate. Note that this kitten will only show
|
Then press the key you want to emulate. Note that this kitten will only show
|
||||||
keys that actually reach the terminal program, in particular, keys mapped to
|
keys that actually reach the terminal program, in particular, keys mapped to
|
||||||
actions in kitty will not be shown. To check those first map them to
|
actions in kitty will not be shown. To check those first map them to
|
||||||
:ac:`no_op`.
|
:ac:`no_op`. You can also start a kitty instance without any shortcuts to
|
||||||
|
interfere::
|
||||||
|
|
||||||
|
kitty -o clear_all_shortcuts=yes kitty +kitten show_key
|
||||||
|
|
||||||
|
|
||||||
How do I open a new window or tab with the same working directory as the current window?
|
How do I open a new window or tab with the same working directory as the current window?
|
||||||
--------------------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -30,9 +30,14 @@ Glossary
|
|||||||
overlay
|
overlay
|
||||||
An *overlay window* is a :term:`kitty window <window>` that is placed on
|
An *overlay window* is a :term:`kitty window <window>` that is placed on
|
||||||
top of an existing kitty window, entirely covering it. Overlays are used
|
top of an existing kitty window, entirely covering it. Overlays are used
|
||||||
throught kitty, for example, to display the :ref:`the scrollback buffer <scrollback>`,
|
throughout kitty, for example, to display the :ref:`the scrollback buffer <scrollback>`,
|
||||||
to display :doc:`hints </kittens/hints>`, for :doc:`unicode input
|
to display :doc:`hints </kittens/hints>`, for :doc:`unicode input
|
||||||
</kittens/unicode_input>` etc.
|
</kittens/unicode_input>` etc. Normal overlays are meant for short
|
||||||
|
duration popups and so are not considered the :italic:`active window`
|
||||||
|
when determining the current working directory or getting input text for
|
||||||
|
kittens, launch commands, etc. To create an overlay considered as a
|
||||||
|
:italic:`main window` use the :code:`overlay-main` argument to
|
||||||
|
:doc:`launch`.
|
||||||
|
|
||||||
hyperlinks
|
hyperlinks
|
||||||
Terminals can have hyperlinks, just like the internet. In kitty you can
|
Terminals can have hyperlinks, just like the internet. In kitty you can
|
||||||
@@ -107,6 +112,11 @@ Variables that influence kitty behavior
|
|||||||
Set this to the directory path of the kitty source code and its Python code
|
Set this to the directory path of the kitty source code and its Python code
|
||||||
will be loaded from there. Only works with official binary builds.
|
will be loaded from there. Only works with official binary builds.
|
||||||
|
|
||||||
|
.. envvar:: KITTY_RC_PASSWORD
|
||||||
|
|
||||||
|
Set this to a pass phrase to use the ``kitty @`` remote control command with
|
||||||
|
:opt:`remote_control_password`.
|
||||||
|
|
||||||
|
|
||||||
Variables that kitty sets when running child programs
|
Variables that kitty sets when running child programs
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
@@ -116,53 +126,49 @@ Variables that kitty sets when running child programs
|
|||||||
This is only set on macOS. If the country and language from the macOS user
|
This is only set on macOS. If the country and language from the macOS user
|
||||||
settings form an invalid locale, it will be set to :code:`en_US.UTF-8`.
|
settings form an invalid locale, it will be set to :code:`en_US.UTF-8`.
|
||||||
|
|
||||||
|
|
||||||
.. envvar:: PATH
|
.. envvar:: PATH
|
||||||
|
|
||||||
kitty prepends itself to the PATH of its own environment to ensure the
|
kitty prepends itself to the PATH of its own environment to ensure the
|
||||||
functions calling :program:`kitty` will work properly.
|
functions calling :program:`kitty` will work properly.
|
||||||
|
|
||||||
|
|
||||||
.. envvar:: KITTY_WINDOW_ID
|
.. envvar:: KITTY_WINDOW_ID
|
||||||
|
|
||||||
An integer that is the id for the kitty :term:`window` the program is running in.
|
An integer that is the id for the kitty :term:`window` the program is running in.
|
||||||
Can be used with the :doc:`kitty remote control facility <remote-control>`.
|
Can be used with the :doc:`kitty remote control facility <remote-control>`.
|
||||||
|
|
||||||
|
|
||||||
.. envvar:: KITTY_PID
|
.. envvar:: KITTY_PID
|
||||||
|
|
||||||
An integer that is the process id for the kitty process in which the program
|
An integer that is the process id for the kitty process in which the program
|
||||||
is running. Allows programs to tell kitty to reload its config by sending it
|
is running. Allows programs to tell kitty to reload its config by sending it
|
||||||
the SIGUSR1 signal.
|
the SIGUSR1 signal.
|
||||||
|
|
||||||
|
.. envvar:: KITTY_PUBLIC_KEY
|
||||||
|
|
||||||
|
A public key that programs can use to communicate securely with kitty using
|
||||||
|
the remote control protocol. The format is: :code:`protocol:key data`.
|
||||||
|
|
||||||
.. envvar:: WINDOWID
|
.. envvar:: WINDOWID
|
||||||
|
|
||||||
The id for the :term:`OS Window <os_window>` the program is running in. Only available
|
The id for the :term:`OS Window <os_window>` the program is running in. Only available
|
||||||
on platforms that have ids for their windows, such as X11 and macOS.
|
on platforms that have ids for their windows, such as X11 and macOS.
|
||||||
|
|
||||||
|
|
||||||
.. envvar:: TERM
|
.. envvar:: TERM
|
||||||
|
|
||||||
The name of the terminal, defaults to ``xterm-kitty``. See :opt:`term`.
|
The name of the terminal, defaults to ``xterm-kitty``. See :opt:`term`.
|
||||||
|
|
||||||
|
|
||||||
.. envvar:: TERMINFO
|
.. envvar:: TERMINFO
|
||||||
|
|
||||||
Path to a directory containing the kitty terminfo database.
|
Path to a directory containing the kitty terminfo database.
|
||||||
|
|
||||||
|
|
||||||
.. envvar:: KITTY_INSTALLATION_DIR
|
.. envvar:: KITTY_INSTALLATION_DIR
|
||||||
|
|
||||||
Path to the kitty installation directory.
|
Path to the kitty installation directory.
|
||||||
|
|
||||||
|
|
||||||
.. envvar:: COLORTERM
|
.. envvar:: COLORTERM
|
||||||
|
|
||||||
Set to the value ``truecolor`` to indicate that kitty supports 16 million
|
Set to the value ``truecolor`` to indicate that kitty supports 16 million
|
||||||
colors.
|
colors.
|
||||||
|
|
||||||
|
|
||||||
.. envvar:: KITTY_LISTEN_ON
|
.. envvar:: KITTY_LISTEN_ON
|
||||||
|
|
||||||
Set when the :doc:`remote control <remote-control>` facility is enabled and
|
Set when the :doc:`remote control <remote-control>` facility is enabled and
|
||||||
@@ -170,46 +176,44 @@ Variables that kitty sets when running child programs
|
|||||||
Contains the path to the socket. Avoid the need to use :option:`kitty @ --to` when
|
Contains the path to the socket. Avoid the need to use :option:`kitty @ --to` when
|
||||||
issuing remote control commands.
|
issuing remote control commands.
|
||||||
|
|
||||||
|
|
||||||
.. envvar:: KITTY_PIPE_DATA
|
.. envvar:: KITTY_PIPE_DATA
|
||||||
|
|
||||||
Set to data describing the layout of the screen when running child
|
Set to data describing the layout of the screen when running child
|
||||||
programs using :option:`launch --stdin-source` with the contents of the
|
programs using :option:`launch --stdin-source` with the contents of the
|
||||||
screen/scrollback piped to them.
|
screen/scrollback piped to them.
|
||||||
|
|
||||||
|
|
||||||
.. envvar:: KITTY_CHILD_CMDLINE
|
.. envvar:: KITTY_CHILD_CMDLINE
|
||||||
|
|
||||||
Set to the command line of the child process running in the kitty
|
Set to the command line of the child process running in the kitty
|
||||||
window when calling the notification callback program on terminal bell, see
|
window when calling the notification callback program on terminal bell, see
|
||||||
:opt:`command_on_bell`.
|
:opt:`command_on_bell`.
|
||||||
|
|
||||||
|
|
||||||
.. envvar:: KITTY_COMMON_OPTS
|
.. envvar:: KITTY_COMMON_OPTS
|
||||||
|
|
||||||
Set with the values of some common kitty options when running
|
Set with the values of some common kitty options when running
|
||||||
kittens, so kittens can use them without needing to load :file:`kitty.conf`.
|
kittens, so kittens can use them without needing to load :file:`kitty.conf`.
|
||||||
|
|
||||||
|
|
||||||
.. envvar:: KITTY_SHELL_INTEGRATION
|
.. envvar:: KITTY_SHELL_INTEGRATION
|
||||||
|
|
||||||
Set when enabling :ref:`shell_integration`. It is automatically removed by
|
Set when enabling :ref:`shell_integration`. It is automatically removed by
|
||||||
the shell integration scripts.
|
the shell integration scripts.
|
||||||
|
|
||||||
|
|
||||||
.. envvar:: ZDOTDIR
|
.. envvar:: ZDOTDIR
|
||||||
|
|
||||||
Set when enabling :ref:`shell_integration` with :program:`zsh`, allowing
|
Set when enabling :ref:`shell_integration` with :program:`zsh`, allowing
|
||||||
:program:`zsh` to automatically load the integration script.
|
:program:`zsh` to automatically load the integration script.
|
||||||
|
|
||||||
|
|
||||||
.. envvar:: XDG_DATA_DIRS
|
.. envvar:: XDG_DATA_DIRS
|
||||||
|
|
||||||
Set when enabling :ref:`shell_integration` with :program:`fish`, allowing
|
Set when enabling :ref:`shell_integration` with :program:`fish`, allowing
|
||||||
:program:`fish` to automatically load the integration script.
|
:program:`fish` to automatically load the integration script.
|
||||||
|
|
||||||
|
|
||||||
.. envvar:: ENV
|
.. envvar:: ENV
|
||||||
|
|
||||||
Set when enabling :ref:`shell_integration` with :program:`bash`, allowing
|
Set when enabling :ref:`shell_integration` with :program:`bash`, allowing
|
||||||
:program:`bash` to automatically load the integration script.
|
:program:`bash` to automatically load the integration script.
|
||||||
|
|
||||||
|
.. envvar:: KITTY_OS
|
||||||
|
|
||||||
|
Set when using the include directive in kitty.conf. Can take values:
|
||||||
|
``linux``, ``macos``, ``bsd``.
|
||||||
|
|||||||
@@ -28,21 +28,22 @@ alpha-blending and text over graphics.
|
|||||||
Some programs and libraries that use the kitty graphics protocol:
|
Some programs and libraries that use the kitty graphics protocol:
|
||||||
|
|
||||||
* `termpdf.py <https://github.com/dsanson/termpdf.py>`_ - 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
|
* `ranger <https://github.com/ranger/ranger>`_ - a terminal file manager, with image previews
|
||||||
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
|
* :doc:`kitty-diff <kittens/diff>` - a side-by-side terminal diff program with support for images
|
||||||
* `tpix <https://github.com/jesvedberg/tpix>` - a statically compiled binary that can be used to display images and easily installed on remote servers without root access
|
* `tpix <https://github.com/jesvedberg/tpix>`_ - a statically compiled binary that can be used to display images and easily installed on remote servers without root access
|
||||||
|
* `mpv <https://github.com/mpv-player/mpv/commit/874e28f4a41a916bb567a882063dd2589e9234e1>`_ - A video player that can play videos in the terminal
|
||||||
* `pixcat <https://github.com/mirukana/pixcat>`_ - a third party CLI and python library that wraps the graphics protocol
|
* `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
|
* `neofetch <https://github.com/dylanaraps/neofetch>`_ - A command line system
|
||||||
information tool
|
information tool
|
||||||
* `viu <https://github.com/atanunq/viu>`_ - a terminal image viewer
|
* `viu <https://github.com/atanunq/viu>`_ - a terminal image viewer
|
||||||
* `glkitty <https://github.com/michaeljclark/glkitty>`_ - C library to draw OpenGL shaders in the terminal with a glgears demo
|
|
||||||
* `ctx.graphics <https://ctx.graphics/>`_ - Library for drawing graphics
|
* `ctx.graphics <https://ctx.graphics/>`_ - Library for drawing graphics
|
||||||
* `timg <https://github.com/hzeller/timg>`_ - a terminal image and video viewer
|
* `timg <https://github.com/hzeller/timg>`_ - a terminal image and video viewer
|
||||||
* `notcurses <https://github.com/dankamongmen/notcurses>`_ - C library for terminal graphics with bindings for C++, Rust and Python
|
* `notcurses <https://github.com/dankamongmen/notcurses>`_ - C library for terminal graphics with bindings for C++, Rust and Python
|
||||||
* `rasterm <https://github.com/BourgeoisBear/rasterm>`_ - Go library to display images in the terminal
|
* `rasterm <https://github.com/BourgeoisBear/rasterm>`_ - Go library to display images in the terminal
|
||||||
* `chafa <https://github.com/hpjansson/chafa>`_ - a terminal image viewer
|
* `chafa <https://github.com/hpjansson/chafa>`_ - a terminal image viewer
|
||||||
* `hologram.nvim <https://github.com/edluffy/hologram.nvim>`_ - view images inside nvim
|
* `hologram.nvim <https://github.com/edluffy/hologram.nvim>`_ - view images inside nvim
|
||||||
|
* `term-image <https://github.com/AnonymouX47/term-image>`_ - A Python library, CLI and TUI to display and browse images in the terminal
|
||||||
|
* `glkitty <https://github.com/michaeljclark/glkitty>`_ - C library to draw OpenGL shaders in the terminal with a glgears demo
|
||||||
|
|
||||||
Other terminals that have implemented the graphics protocol:
|
Other terminals that have implemented the graphics protocol:
|
||||||
|
|
||||||
@@ -87,6 +88,17 @@ code to demonstrate its use
|
|||||||
'number of rows: {} number of columns: {}'
|
'number of rows: {} number of columns: {}'
|
||||||
'screen width: {} screen height: {}').format(*buf))
|
'screen width: {} screen height: {}').format(*buf))
|
||||||
|
|
||||||
|
.. tab:: Go
|
||||||
|
|
||||||
|
.. code-block:: go
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
fd, err := unix.Open(fd, unix.O_NOCTTY|unix.O_CLOEXEC|unix.O_NDELAY|unix.O_RDWR, 0666)
|
||||||
|
sz, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
|
||||||
|
fmt.Println("rows: %v columns: %v width: %v height %v", sz.Row, sz.Col, sz.Xpixel, sz.Ypixel)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Note that some terminals return ``0`` for the width and height values. Such
|
Note that some terminals return ``0`` for the width and height values. Such
|
||||||
terminals should be modified to return the correct values. Examples of
|
terminals should be modified to return the correct values. Examples of
|
||||||
terminals that return correct values: ``kitty, xterm``
|
terminals that return correct values: ``kitty, xterm``
|
||||||
@@ -100,47 +112,71 @@ kitty.
|
|||||||
A minimal example
|
A minimal example
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
Some minimal python code to display PNG images in kitty, using the most basic
|
Some minimal code to display PNG images in kitty, using the most basic
|
||||||
features of the graphics protocol:
|
features of the graphics protocol:
|
||||||
|
|
||||||
.. code-block:: python
|
.. tab:: Bash
|
||||||
|
|
||||||
import sys
|
.. code-block:: sh
|
||||||
from base64 import standard_b64encode
|
|
||||||
|
#!/bin/bash
|
||||||
|
transmit_png() {
|
||||||
|
data=$(base64 "$1")
|
||||||
|
data="${data//[[:space:]]}"
|
||||||
|
builtin local pos=0
|
||||||
|
builtin local chunk_size=4096
|
||||||
|
while [ $pos -lt ${#data} ]; do
|
||||||
|
builtin printf "\e_G"
|
||||||
|
[ $pos = "0" ] && printf "a=T,f=100,"
|
||||||
|
builtin local chunk="${data:$pos:$chunk_size}"
|
||||||
|
pos=$(($pos+$chunk_size))
|
||||||
|
[ $pos -lt ${#data} ] && builtin printf "m=1"
|
||||||
|
[ ${#chunk} -gt 0 ] && builtin printf ";%s" "${chunk}"
|
||||||
|
builtin printf "\e\\"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
transmit_png "$1"
|
||||||
|
|
||||||
|
.. tab:: Python
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
#!/usr/bin/python
|
||||||
|
import sys
|
||||||
|
from base64 import standard_b64encode
|
||||||
|
|
||||||
|
def serialize_gr_command(**cmd):
|
||||||
|
payload = cmd.pop('payload', None)
|
||||||
|
cmd = ','.join(f'{k}={v}' for k, v in cmd.items())
|
||||||
|
ans = []
|
||||||
|
w = ans.append
|
||||||
|
w(b'\033_G'), w(cmd.encode('ascii'))
|
||||||
|
if payload:
|
||||||
|
w(b';')
|
||||||
|
w(payload)
|
||||||
|
w(b'\033\\')
|
||||||
|
return b''.join(ans)
|
||||||
|
|
||||||
|
def write_chunked(**cmd):
|
||||||
|
data = standard_b64encode(cmd.pop('data'))
|
||||||
|
while data:
|
||||||
|
chunk, data = data[:4096], data[4096:]
|
||||||
|
m = 1 if data else 0
|
||||||
|
sys.stdout.buffer.write(serialize_gr_command(payload=chunk, m=m,
|
||||||
|
**cmd))
|
||||||
|
sys.stdout.flush()
|
||||||
|
cmd.clear()
|
||||||
|
|
||||||
|
with open(sys.argv[-1], 'rb') as f:
|
||||||
|
write_chunked(a='T', f=100, data=f.read())
|
||||||
|
|
||||||
|
|
||||||
def serialize_gr_command(**cmd):
|
Save this script as :file:`send-png`, then you can use it to display any PNG
|
||||||
payload = cmd.pop('payload', None)
|
|
||||||
cmd = ','.join(f'{k}={v}' for k, v in cmd.items())
|
|
||||||
ans = []
|
|
||||||
w = ans.append
|
|
||||||
w(b'\033_G'), w(cmd.encode('ascii'))
|
|
||||||
if payload:
|
|
||||||
w(b';')
|
|
||||||
w(payload)
|
|
||||||
w(b'\033\\')
|
|
||||||
return b''.join(ans)
|
|
||||||
|
|
||||||
|
|
||||||
def write_chunked(**cmd):
|
|
||||||
data = standard_b64encode(cmd.pop('data'))
|
|
||||||
while data:
|
|
||||||
chunk, data = data[:4096], data[4096:]
|
|
||||||
m = 1 if data else 0
|
|
||||||
sys.stdout.buffer.write(serialize_gr_command(payload=chunk, m=m,
|
|
||||||
**cmd))
|
|
||||||
sys.stdout.flush()
|
|
||||||
cmd.clear()
|
|
||||||
|
|
||||||
|
|
||||||
with open(sys.argv[-1], 'rb') as f:
|
|
||||||
write_chunked(a='T', f=100, data=f.read())
|
|
||||||
|
|
||||||
|
|
||||||
Save this script as :file:`png.py`, then you can use it to display any PNG
|
|
||||||
file in kitty as::
|
file in kitty as::
|
||||||
|
|
||||||
python png.py file.png
|
chmod +x send-png
|
||||||
|
./send-png file.png
|
||||||
|
|
||||||
|
|
||||||
The graphics escape code
|
The graphics escape code
|
||||||
@@ -236,7 +272,8 @@ Value of `t` Meaning
|
|||||||
the terminal emulator should only delete the file if it
|
the terminal emulator should only delete the file if it
|
||||||
is in a known temporary directory, such as :file:`/tmp`,
|
is in a known temporary directory, such as :file:`/tmp`,
|
||||||
:file:`/dev/shm`, :file:`TMPDIR env var if present` and any platform
|
:file:`/dev/shm`, :file:`TMPDIR env var if present` and any platform
|
||||||
specific temporary directories.
|
specific temporary directories and the file has the
|
||||||
|
string :code:`tty-graphics-protocol` in its full file path.
|
||||||
``s`` A *shared memory object*, which on POSIX systems is a
|
``s`` A *shared memory object*, which on POSIX systems is a
|
||||||
`POSIX shared memory object <https://pubs.opengroup.org/onlinepubs/9699919799/functions/shm_open.html>`_
|
`POSIX shared memory object <https://pubs.opengroup.org/onlinepubs/9699919799/functions/shm_open.html>`_
|
||||||
and on Windows is a
|
and on Windows is a
|
||||||
@@ -293,12 +330,13 @@ sequence of escape codes to the terminal emulator::
|
|||||||
<ESC>_Gm=0;<encoded pixel data last chunk><ESC>\
|
<ESC>_Gm=0;<encoded pixel data last chunk><ESC>\
|
||||||
|
|
||||||
Note that only the first escape code needs to have the full set of control
|
Note that only the first escape code needs to have the full set of control
|
||||||
codes such as width, height, format etc. Subsequent chunks **must** have
|
codes such as width, height, format, etc. Subsequent chunks **must** have only
|
||||||
only the ``m`` key. The client **must** finish sending all chunks for a single image
|
the ``m`` and optionally ``q`` keys. When sending animation frame data, subsequent
|
||||||
before sending any other graphics related escape codes. Note that the cursor
|
chunks **must** also specify the ``a=f`` key. The client **must** finish sending
|
||||||
position used to display the image **must** be the position when the final chunk is
|
all chunks for a single image before sending any other graphics related escape
|
||||||
received. Finally, terminals must not display anything, until the entire sequence is
|
codes. Note that the cursor position used to display the image **must** be the
|
||||||
received and validated.
|
position when the final chunk is received. Finally, terminals must not display
|
||||||
|
anything, until the entire sequence is received and validated.
|
||||||
|
|
||||||
|
|
||||||
Querying support and available transmission mediums
|
Querying support and available transmission mediums
|
||||||
@@ -417,7 +455,9 @@ 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
|
Negative z-index values mean that the images will be drawn under the text. This
|
||||||
allows rendering of text on top of images. Negative z-index values below
|
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
|
INT32_MIN/2 (-1,073,741,824) will be drawn under cells with non-default background
|
||||||
colors.
|
colors. If two images with the same z-index overlap then the image with the
|
||||||
|
lower id is considered to have the lower z-index. If the images have the same
|
||||||
|
z-index and the same id, then the behavior is undefined.
|
||||||
|
|
||||||
.. note:: After placing an image on the screen the cursor must be moved to the
|
.. note:: After placing an image on the screen the cursor must be moved to the
|
||||||
right by the number of cols in the image placement rectangle and down by the
|
right by the number of cols in the image placement rectangle and down by the
|
||||||
|
|||||||
@@ -47,6 +47,12 @@ graphics protocol.
|
|||||||
Another terminal file manager, with previews of file contents powered by kitty's
|
Another terminal file manager, with previews of file contents powered by kitty's
|
||||||
graphics protocol.
|
graphics protocol.
|
||||||
|
|
||||||
|
.. _tool_term_image:
|
||||||
|
|
||||||
|
`term-image <https://github.com/AnonymouX47/term-image>`__
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
Tool to browse images in a terminal using kitty's graphics protocol.
|
||||||
|
|
||||||
.. _tool_koneko:
|
.. _tool_koneko:
|
||||||
|
|
||||||
`koneko <https://github.com/twenty5151/koneko>`_
|
`koneko <https://github.com/twenty5151/koneko>`_
|
||||||
@@ -74,6 +80,12 @@ base application that uses kitty's graphics protocol for images.
|
|||||||
A text mode WWW browser that supports kitty's graphics protocol to display
|
A text mode WWW browser that supports kitty's graphics protocol to display
|
||||||
images.
|
images.
|
||||||
|
|
||||||
|
.. _tool_mpv:
|
||||||
|
|
||||||
|
`mpv <https://github.com/mpv-player/mpv/commit/874e28f4a41a916bb567a882063dd2589e9234e1>`_
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
A video player that can play videos in the terminal.
|
||||||
|
|
||||||
.. _tool_timg:
|
.. _tool_timg:
|
||||||
|
|
||||||
`timg <https://github.com/hzeller/timg>`_
|
`timg <https://github.com/hzeller/timg>`_
|
||||||
|
|||||||
@@ -35,9 +35,13 @@ inside the kitty terminal to report key events.
|
|||||||
In addition to kitty, this protocol is also implemented in:
|
In addition to kitty, this protocol is also implemented in:
|
||||||
|
|
||||||
* The `foot terminal <https://codeberg.org/dnkl/foot/issues/319>`__
|
* The `foot terminal <https://codeberg.org/dnkl/foot/issues/319>`__
|
||||||
|
* The `WezTerm terminal <https://wezfurlong.org/wezterm/config/lua/config/enable_kitty_keyboard.html>`__
|
||||||
* The `notcurses library
|
* The `notcurses library
|
||||||
<https://github.com/dankamongmen/notcurses/issues/2131>`__
|
<https://github.com/dankamongmen/notcurses/issues/2131>`__
|
||||||
* The `neovim text editor <https://github.com/neovim/neovim/pull/18181>`__
|
* The `crossterm library
|
||||||
|
<https://github.com/crossterm-rs/crossterm/pull/688>`__
|
||||||
|
* The `Vim text editor <https://github.com/vim/vim/commit/63a2e360cca2c70ab0a85d14771d3259d4b3aafa>`__
|
||||||
|
* The `Neovim text editor <https://github.com/neovim/neovim/pull/18181>`__
|
||||||
* The `kakoune text editor <https://github.com/mawww/kakoune/issues/4103>`__
|
* The `kakoune text editor <https://github.com/mawww/kakoune/issues/4103>`__
|
||||||
* The `dte text editor <https://gitlab.com/craigbarnes/dte/-/issues/138>`__
|
* The `dte text editor <https://gitlab.com/craigbarnes/dte/-/issues/138>`__
|
||||||
|
|
||||||
@@ -57,14 +61,14 @@ without too many changes, do the following:
|
|||||||
that are easy to parse unambiguously.
|
that are easy to parse unambiguously.
|
||||||
#. Emit the escape sequence ``CSI < u`` at application exit if using the main
|
#. Emit the escape sequence ``CSI < u`` at application exit if using the main
|
||||||
screen or just before leaving alternate screen mode if using the alternate screen,
|
screen or just before leaving alternate screen mode if using the alternate screen,
|
||||||
to restore the previously used keyboard mode.
|
to restore whatever the keyboard mode was before step 1.
|
||||||
|
|
||||||
Key events will all be delivered to your application either as plain UTF-8
|
Key events will all be delivered to your application either as plain UTF-8
|
||||||
text, or using the following escape codes, for those keys that do not produce
|
text, or using the following escape codes, for those keys that do not produce
|
||||||
text (``CSI`` is the bytes ``0x1b 0x5b``)::
|
text (``CSI`` is the bytes ``0x1b 0x5b``)::
|
||||||
|
|
||||||
CSI number ; modifiers [u~]
|
CSI number ; modifiers [u~]
|
||||||
CSI 1; modifiers [ABCDEFHPQRS]
|
CSI 1; modifiers [ABCDEFHPQS]
|
||||||
0x0d - for the Enter key
|
0x0d - for the Enter key
|
||||||
0x7f or 0x08 - for Backspace
|
0x7f or 0x08 - for Backspace
|
||||||
0x09 - for Tab
|
0x09 - for Tab
|
||||||
@@ -79,7 +83,7 @@ The second form is used for a few functional keys, such as the :kbd:`Home`,
|
|||||||
:kbd:`End`, :kbd:`Arrow` keys and :kbd:`F1` ... :kbd:`F4`, they are enumerated in
|
:kbd:`End`, :kbd:`Arrow` keys and :kbd:`F1` ... :kbd:`F4`, they are enumerated in
|
||||||
the :ref:`functional` table below. Note that if no modifiers are present the
|
the :ref:`functional` table below. Note that if no modifiers are present the
|
||||||
parameters are omitted entirely giving an escape code of the form ``CSI
|
parameters are omitted entirely giving an escape code of the form ``CSI
|
||||||
[ABCDEFHPQRS]``.
|
[ABCDEFHPQS]``.
|
||||||
|
|
||||||
If you want support for more advanced features such as repeat and release
|
If you want support for more advanced features such as repeat and release
|
||||||
events, alternate keys for shortcut matching et cetera, these can be turned on
|
events, alternate keys for shortcut matching et cetera, these can be turned on
|
||||||
@@ -310,7 +314,7 @@ With this flag turned on, all key events that do not generate text are
|
|||||||
represented in one of the following two forms::
|
represented in one of the following two forms::
|
||||||
|
|
||||||
CSI number; modifier u
|
CSI number; modifier u
|
||||||
CSI 1; modifier [~ABCDEFHPQRS]
|
CSI 1; modifier [~ABCDEFHPQS]
|
||||||
|
|
||||||
This makes it very easy to parse key events in an application. In particular,
|
This makes it very easy to parse key events in an application. In particular,
|
||||||
:kbd:`ctrl+c` will no longer generate the ``SIGINT`` signal, but instead be
|
:kbd:`ctrl+c` will no longer generate the ``SIGINT`` signal, but instead be
|
||||||
@@ -401,7 +405,7 @@ Legacy functional keys
|
|||||||
These keys are encoded using three schemes::
|
These keys are encoded using three schemes::
|
||||||
|
|
||||||
CSI number ; modifier ~
|
CSI number ; modifier ~
|
||||||
CSI 1 ; modifier {ABCDEFHPQRS}
|
CSI 1 ; modifier {ABCDEFHPQS}
|
||||||
SS3 {ABCDEFHPQRS}
|
SS3 {ABCDEFHPQRS}
|
||||||
|
|
||||||
In the above, if there are no modifiers, the modifier parameter is omitted.
|
In the above, if there are no modifiers, the modifier parameter is omitted.
|
||||||
@@ -529,7 +533,7 @@ compatibility reasons.
|
|||||||
"NUM_LOCK", "``57360 u``", "PRINT_SCREEN", "``57361 u``"
|
"NUM_LOCK", "``57360 u``", "PRINT_SCREEN", "``57361 u``"
|
||||||
"PAUSE", "``57362 u``", "MENU", "``57363 u``"
|
"PAUSE", "``57362 u``", "MENU", "``57363 u``"
|
||||||
"F1", "``1 P or 11 ~``", "F2", "``1 Q or 12 ~``"
|
"F1", "``1 P or 11 ~``", "F2", "``1 Q or 12 ~``"
|
||||||
"F3", "``1 R or 13 ~``", "F4", "``1 S or 14 ~``"
|
"F3", "``13 ~``", "F4", "``1 S or 14 ~``"
|
||||||
"F5", "``15 ~``", "F6", "``17 ~``"
|
"F5", "``15 ~``", "F6", "``17 ~``"
|
||||||
"F7", "``18 ~``", "F8", "``19 ~``"
|
"F7", "``18 ~``", "F8", "``19 ~``"
|
||||||
"F9", "``20 ~``", "F10", "``21 ~``"
|
"F9", "``20 ~``", "F10", "``21 ~``"
|
||||||
@@ -578,8 +582,14 @@ compatibility reasons.
|
|||||||
.. end functional key table
|
.. end functional key table
|
||||||
.. }}}
|
.. }}}
|
||||||
|
|
||||||
Note that the escape codes above of the form ``CSI 1 letter`` will omit the
|
.. note::
|
||||||
``1`` if there are no modifiers, since ``1`` is the default value.
|
The escape codes above of the form ``CSI 1 letter`` will omit the
|
||||||
|
``1`` if there are no modifiers, since ``1`` is the default value.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
The original version of this specification allowed F3 to be encoded as both
|
||||||
|
CSI R and CSI ~. However, CSI R conflicts with the Cursor Position Report,
|
||||||
|
so it was removed.
|
||||||
|
|
||||||
.. _ctrl_mapping:
|
.. _ctrl_mapping:
|
||||||
|
|
||||||
@@ -672,3 +682,31 @@ specification.
|
|||||||
* Handwaves that :kbd:`ctrl` *tends to* mask with ``0x1f``. In actual fact it
|
* Handwaves that :kbd:`ctrl` *tends to* mask with ``0x1f``. In actual fact it
|
||||||
does this only for some keys. The action of :kbd:`ctrl` is not specified and
|
does this only for some keys. The action of :kbd:`ctrl` is not specified and
|
||||||
varies between terminals, historically because of different keyboard layouts.
|
varies between terminals, historically because of different keyboard layouts.
|
||||||
|
|
||||||
|
|
||||||
|
Why xterm's modifyOtherKeys should not be used
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
* Does not support release events
|
||||||
|
|
||||||
|
* Does not fix the issue of :kbd:`Esc` key presses not being distinguishable from
|
||||||
|
escape codes.
|
||||||
|
|
||||||
|
* Does not fix the issue of some keypresses generating identical bytes and thus
|
||||||
|
being indistinguishable
|
||||||
|
|
||||||
|
* There is no robust way to query it or manage its state from a program running
|
||||||
|
in the terminal.
|
||||||
|
|
||||||
|
* No support for shifted keys.
|
||||||
|
|
||||||
|
* No support for alternate keyboard layouts.
|
||||||
|
|
||||||
|
* No support for modifiers beyond the basic four.
|
||||||
|
|
||||||
|
* No support for lock keys like Num lock and Caps lock.
|
||||||
|
|
||||||
|
* Is completely unspecified. The most discussion of it available anywhere is
|
||||||
|
`here <https://invisible-island.net/xterm/modified-keys.html>`__
|
||||||
|
And it contains no specification of what numbers to assign to what function
|
||||||
|
keys beyond running a Perl script on an X11 system!!
|
||||||
|
|||||||
@@ -11,14 +11,46 @@ from the shell. It even works over SSH. Using it is as simple as::
|
|||||||
|
|
||||||
echo hooray | kitty +kitten clipboard
|
echo hooray | kitty +kitten clipboard
|
||||||
|
|
||||||
All text received on :file:`stdin` is copied to the clipboard.
|
All text received on :file:`STDIN` is copied to the clipboard.
|
||||||
|
|
||||||
To get text from the clipboard you have to enable reading of the clipboard
|
To get text from the clipboard::
|
||||||
in :opt:`clipboard_control` in :file:`kitty.conf`. Once you do that, you can
|
|
||||||
use::
|
|
||||||
|
|
||||||
kitty +kitten clipboard --get-clipboard
|
kitty +kitten clipboard --get-clipboard
|
||||||
|
|
||||||
|
The text will be written to :file:`STDOUT`. Note that by default kitty asks for
|
||||||
|
permission when a program attempts to read the clipboard. This can be
|
||||||
|
controlled via :opt:`clipboard_control`.
|
||||||
|
|
||||||
|
.. versionadded:: 0.27.0
|
||||||
|
Support for copying arbitrary data types
|
||||||
|
|
||||||
|
The clipboard kitten can be used to send/receive
|
||||||
|
more than just plain text from the system clipboard. You can transfer arbitrary
|
||||||
|
data types. Best illustrated with some examples::
|
||||||
|
|
||||||
|
# Copy an image to the clipboard:
|
||||||
|
kitty +kitten clipboard picture.png
|
||||||
|
|
||||||
|
# Copy an image and some text to the clipboard:
|
||||||
|
kitty +kitten clipboard picture.jpg text.txt
|
||||||
|
|
||||||
|
# Copy text from STDIN and an image to the clipboard:
|
||||||
|
echo hello | kitty +kitten clipboard picture.png /dev/stdin
|
||||||
|
|
||||||
|
# Copy any raster image available on the clipboard to a PNG file:
|
||||||
|
kitty +kitten clipboard -g picture.png
|
||||||
|
|
||||||
|
# Copy an image to a file and text to STDOUT:
|
||||||
|
kitty +kitten clipboard -g picture.png /dev/stdout
|
||||||
|
|
||||||
|
# List the formats available on the system clipboard
|
||||||
|
kitty +kitten clipboard -g -m . /dev/stdout
|
||||||
|
|
||||||
|
Normally, the kitten guesses MIME types based on the file names. To control the
|
||||||
|
MIME types precisely, use the :option:`--mime <kitty +kitten clipboard --mime>` option.
|
||||||
|
|
||||||
|
This kitten uses a new protocol developed by kitty to function, for details,
|
||||||
|
see :doc:`/clipboard`.
|
||||||
|
|
||||||
.. program:: kitty +kitten clipboard
|
.. program:: kitty +kitten clipboard
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,24 @@ kittens. Look in the `kittens sub-directory
|
|||||||
code for those. Or see below for a list of :ref:`third-party kittens
|
code for those. Or see below for a list of :ref:`third-party kittens
|
||||||
<external_kittens>`, that other kitty users have created.
|
<external_kittens>`, that other kitty users have created.
|
||||||
|
|
||||||
|
kitty API to use with kittens
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
Kittens have full access to internal kitty APIs. However these are neither
|
||||||
|
entirely stable nor documented. You can instead use the kitty
|
||||||
|
:doc:`Remote control API </remote-control>`. Simply call
|
||||||
|
:code:`boss.call_remote_control()`, with the same arguments you
|
||||||
|
would pass to ``kitty @``. For example:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
def handle_result(args: List[str], answer: str, target_window_id: int, boss: Boss) -> None:
|
||||||
|
# get the kitty window to which to send text
|
||||||
|
w = boss.window_id_map.get(target_window_id)
|
||||||
|
if w is not None:
|
||||||
|
boss.call_remote_control(w, ('send-text', 'hello world'))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Passing arguments to kittens
|
Passing arguments to kittens
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|||||||
@@ -17,12 +17,12 @@ following contents:
|
|||||||
# by the hyperlink_grep kitten and nothing else so far.
|
# by the hyperlink_grep kitten and nothing else so far.
|
||||||
protocol file
|
protocol file
|
||||||
fragment_matches [0-9]+
|
fragment_matches [0-9]+
|
||||||
action launch --type=overlay vim +${FRAGMENT} ${FILE_PATH}
|
action launch --type=overlay --cwd=current vim +${FRAGMENT} ${FILE_PATH}
|
||||||
|
|
||||||
# Open text files without fragments in the editor
|
# Open text files without fragments in the editor
|
||||||
protocol file
|
protocol file
|
||||||
mime text/*
|
mime text/*
|
||||||
action launch --type=overlay ${EDITOR} ${FILE_PATH}
|
action launch --type=overlay --cwd=current ${EDITOR} ${FILE_PATH}
|
||||||
|
|
||||||
Now, run a search with::
|
Now, run a search with::
|
||||||
|
|
||||||
@@ -43,37 +43,19 @@ You can now run searches with::
|
|||||||
|
|
||||||
hg some-search-term
|
hg some-search-term
|
||||||
|
|
||||||
If you want to enable completion, for the kitten, you can delegate completion
|
|
||||||
to :program:`rg`. How to do that varies based on the shell:
|
|
||||||
|
|
||||||
|
|
||||||
.. tab:: zsh
|
|
||||||
|
|
||||||
Instead of using an alias, create a simple wrapper script named
|
|
||||||
:program:`hg` somewhere in your :envvar:`PATH`:
|
|
||||||
|
|
||||||
.. code-block:: sh
|
|
||||||
|
|
||||||
#!/bin/sh
|
|
||||||
exec kitty +kitten hyperlinked_grep "$@"
|
|
||||||
|
|
||||||
Then, add the following to :file:`.zshrc`::
|
|
||||||
|
|
||||||
compdef _rg hg
|
|
||||||
|
|
||||||
.. tab:: fish
|
|
||||||
|
|
||||||
You can combine both the aliasing/wrapping and pointing fish to ripgrep's
|
|
||||||
autocompletion with a fish wrapper function in your :file:`config.fish`
|
|
||||||
or :file:`~/.config/fish/functions/hg.fish`:
|
|
||||||
|
|
||||||
.. code-block:: fish
|
|
||||||
|
|
||||||
function hg --wraps rg; kitty +kitten hyperlinked_grep $argv; end
|
|
||||||
|
|
||||||
To learn more about kitty's powerful framework for customizing URL click
|
To learn more about kitty's powerful framework for customizing URL click
|
||||||
actions, see :doc:`here </open_actions>`.
|
actions, see :doc:`here </open_actions>`.
|
||||||
|
|
||||||
|
By default, this kitten adds hyperlinks for several parts of ripgrep output:
|
||||||
|
the per-file header, match context lines, and match lines. You can control
|
||||||
|
which items are linked with a :code:`--kitten hyperlink` flag. For example,
|
||||||
|
:code:`--kitten hyperlink=matching_lines` will only add hyperlinks to the
|
||||||
|
match lines. :code:`--kitten hyperlink=file_headers,context_lines` will link
|
||||||
|
file headers and context lines but not match lines. :code:`--kitten
|
||||||
|
hyperlink=none` will cause the command line to be passed to directly to
|
||||||
|
:command:`rg` so no hyperlinking will be performed. :code:`--kitten hyperlink`
|
||||||
|
may be specified multiple times.
|
||||||
|
|
||||||
Hopefully, someday this functionality will make it into some `upstream grep
|
Hopefully, someday this functionality will make it into some `upstream grep
|
||||||
<https://github.com/BurntSushi/ripgrep/issues/665>`__ program directly removing
|
<https://github.com/BurntSushi/ripgrep/issues/665>`__ program directly removing
|
||||||
the need for this kitten.
|
the need for this kitten.
|
||||||
@@ -83,3 +65,9 @@ the need for this kitten.
|
|||||||
While you can pass any of ripgrep's comand line options to the kitten and
|
While you can pass any of ripgrep's comand line options to the kitten and
|
||||||
they will be forwarded to :program:`rg`, do not use options that change the
|
they will be forwarded to :program:`rg`, do not use options that change the
|
||||||
output formatting as the kitten works by parsing the output from ripgrep.
|
output formatting as the kitten works by parsing the output from ripgrep.
|
||||||
|
The unsupported options are: :code:`--context-separator`,
|
||||||
|
:code:`--field-context-separator`, :code:`--field-match-separator`,
|
||||||
|
:code:`--json`, :code:`-I --no-filename`, :code:`-0 --null`,
|
||||||
|
:code:`--null-data`, :code:`--path-separator`. If you specify options via
|
||||||
|
configuration file, then any changes to the default output format will not be
|
||||||
|
supported, not just the ones listed.
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ The ``icat`` kitten can be used to display arbitrary images in the |kitty|
|
|||||||
terminal. Using it is as simple as::
|
terminal. Using it is as simple as::
|
||||||
|
|
||||||
kitty +kitten icat image.jpeg
|
kitty +kitten icat image.jpeg
|
||||||
|
kitten icat image.jpeg
|
||||||
|
|
||||||
It supports all image types supported by `ImageMagick
|
It supports all image types supported by `ImageMagick
|
||||||
<https://www.imagemagick.org>`__. It even works over SSH. For details, see the
|
<https://www.imagemagick.org>`__. It even works over SSH. For details, see the
|
||||||
@@ -20,8 +21,9 @@ Then you can simply use ``icat image.png`` to view images.
|
|||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
`ImageMagick <https://www.imagemagick.org>`__ must be installed for icat
|
`ImageMagick <https://www.imagemagick.org>`__ must be installed for the
|
||||||
kitten to work.
|
full range of image types. Without it only PNG/JPG/GIF/BMP/TIFF/WEBP are
|
||||||
|
supported.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
@@ -35,16 +37,15 @@ Then you can simply use ``icat image.png`` to view images.
|
|||||||
|
|
||||||
The ``icat`` kitten has various command line arguments to allow it to be used
|
The ``icat`` kitten has various command line arguments to allow it to be used
|
||||||
from inside other programs to display images. In particular, :option:`--place`,
|
from inside other programs to display images. In particular, :option:`--place`,
|
||||||
:option:`--detect-support`, :option:`--silent` and
|
:option:`--detect-support` and :option:`--print-window-size`.
|
||||||
:option:`--print-window-size`.
|
|
||||||
|
|
||||||
If you are trying to integrate icat into a complex program like a file manager
|
If you are trying to integrate icat into a complex program like a file manager
|
||||||
or editor, there are a few things to keep in mind. icat works by communicating
|
or editor, there are a few things to keep in mind. icat works by communicating
|
||||||
over the TTY device, it both writes to and reads from the TTY. So it is
|
over the TTY device, it both writes to and reads from the TTY. So it is
|
||||||
imperative that while it is running the host program does not do any TTY I/O.
|
imperative that while it is running the host program does not do any TTY I/O.
|
||||||
Any key presses or other input from the user on the TTY device will be
|
Any key presses or other input from the user on the TTY device will be
|
||||||
discarded. At a minimum, you should use the :option:`--silent` and
|
discarded. At a minimum, you should use the :option:`--transfer-mode`
|
||||||
:option:`--transfer-mode` command line arguments. To be really robust you should
|
command line arguments. To be really robust you should
|
||||||
consider writing proper support for the :doc:`kitty graphics protocol
|
consider writing proper support for the :doc:`kitty graphics protocol
|
||||||
</graphics-protocol>` in the program instead. Nowadays there are many libraries
|
</graphics-protocol>` in the program instead. Nowadays there are many libraries
|
||||||
that have support for it.
|
that have support for it.
|
||||||
|
|||||||
@@ -143,6 +143,16 @@ transmitted instantly without any roundtrip delay.
|
|||||||
is something POSIX sh compliant, and use :code:`python` as the
|
is something POSIX sh compliant, and use :code:`python` as the
|
||||||
:opt:`interpreter <kitten-ssh.interpreter>` in :file:`ssh.conf`.
|
:opt:`interpreter <kitten-ssh.interpreter>` in :file:`ssh.conf`.
|
||||||
|
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This may or may not work when using terminal multiplexers, depending on
|
||||||
|
whether they passthrough the escape codes and if the values of the
|
||||||
|
environment variables :envvar:`KITTY_PID` and :envvar:`KITTY_WINDOW_ID` are
|
||||||
|
correct in the current session (they can be wrong when connecting to a tmux
|
||||||
|
session running in a different window) and the ssh kitten is run in the
|
||||||
|
currently active multiplexer window.
|
||||||
|
|
||||||
.. include:: /generated/conf-kitten-ssh.rst
|
.. include:: /generated/conf-kitten-ssh.rst
|
||||||
|
|
||||||
|
|
||||||
@@ -152,3 +162,42 @@ The copy command
|
|||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
.. include:: /generated/ssh-copy.rst
|
.. include:: /generated/ssh-copy.rst
|
||||||
|
|
||||||
|
|
||||||
|
.. _manual_terminfo_copy:
|
||||||
|
|
||||||
|
Copying terminfo files manually
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
Sometimes, the ssh kitten can fail, or maybe you dont like to use it. In such
|
||||||
|
cases, the terminfo files can be copied over manually to a server with the
|
||||||
|
following one liner::
|
||||||
|
|
||||||
|
infocmp -a xterm-kitty | ssh myserver tic -x -o \~/.terminfo /dev/stdin
|
||||||
|
|
||||||
|
If you are behind a proxy (like Balabit) that prevents this, or you are SSHing
|
||||||
|
into macOS where the :program:`tic` does not support reading from :file:`STDIN`,
|
||||||
|
you must redirect the first command to a file, copy that to the server and run :program:`tic`
|
||||||
|
manually. If you connect to a server, embedded, or Android system that doesn't
|
||||||
|
have :program:`tic`, copy over your local file terminfo to the other system as
|
||||||
|
:file:`~/.terminfo/x/xterm-kitty`.
|
||||||
|
|
||||||
|
If the server is running a relatively modern Linux distribution and you have
|
||||||
|
root access to it, you could simply install the ``kitty-terminfo`` package on
|
||||||
|
the server to make the terminfo files available.
|
||||||
|
|
||||||
|
Really, the correct solution for this is to convince the OpenSSH maintainers to
|
||||||
|
have :program:`ssh` do this automatically, if possible, when connecting to a
|
||||||
|
server, so that all terminals work transparently.
|
||||||
|
|
||||||
|
If the server is running FreeBSD, or another system that relies on termcap
|
||||||
|
rather than terminfo, you will need to convert the terminfo file on your local
|
||||||
|
machine by running (on local machine with |kitty|)::
|
||||||
|
|
||||||
|
infocmp -CrT0 xterm-kitty
|
||||||
|
|
||||||
|
The output of this command is the termcap description, which should be appended
|
||||||
|
to :file:`/usr/share/misc/termcap` on the remote server. Then run the following
|
||||||
|
command to apply your change (on the server)::
|
||||||
|
|
||||||
|
cap_mkdb /usr/share/misc/termcap
|
||||||
|
|||||||
@@ -68,11 +68,20 @@ for the options is::
|
|||||||
└──────────────┴───────────────┘
|
└──────────────┴───────────────┘
|
||||||
|
|
||||||
In addition, you can map keys to increase or decrease the number of full-height
|
In addition, you can map keys to increase or decrease the number of full-height
|
||||||
windows, for example::
|
windows, or toggle the mirrored setting, for example::
|
||||||
|
|
||||||
map ctrl+[ layout_action decrease_num_full_size_windows
|
map ctrl+[ layout_action decrease_num_full_size_windows
|
||||||
map ctrl+] layout_action increase_num_full_size_windows
|
map ctrl+] layout_action increase_num_full_size_windows
|
||||||
|
map ctrl+/ layout_action mirror toggle
|
||||||
|
map ctrl+y layout_action mirror true
|
||||||
|
map ctrl+n layout_action mirror false
|
||||||
|
|
||||||
|
You can also map a key to change the bias by providing a list of percentages
|
||||||
|
and it will rotate through the list as you press the key. If you only provide
|
||||||
|
one number it'll toggle between that percentage and 50, for example::
|
||||||
|
|
||||||
|
map ctrl+. layout_action bias 50 62 70
|
||||||
|
map ctrl+, layout_action bias 62
|
||||||
|
|
||||||
The Fat Layout
|
The Fat Layout
|
||||||
----------------
|
----------------
|
||||||
@@ -101,8 +110,7 @@ on the bottom of the screen instead of the top. The syntax for the options is::
|
|||||||
└─────────┴──────────┴─────────┘
|
└─────────┴──────────┴─────────┘
|
||||||
|
|
||||||
|
|
||||||
This layout also supports ``decrease_num_full_size_windows`` layout action like
|
This layout also supports the same layout actions as the *Tall* layout, shown above.
|
||||||
the *Tall* layout, shown above.
|
|
||||||
|
|
||||||
|
|
||||||
The Grid Layout
|
The Grid Layout
|
||||||
@@ -159,6 +167,12 @@ define a few extra key bindings in :file:`kitty.conf`::
|
|||||||
map shift+right move_window right
|
map shift+right move_window right
|
||||||
map shift+down move_window down
|
map shift+down move_window down
|
||||||
|
|
||||||
|
# Move the active window to the indicated screen edge
|
||||||
|
map ctrl+shift+up layout_action move_to_screen_edge top
|
||||||
|
map ctrl+shift+left layout_action move_to_screen_edge left
|
||||||
|
map ctrl+shift+right layout_action move_to_screen_edge right
|
||||||
|
map ctrl+shift+down layout_action move_to_screen_edge bottom
|
||||||
|
|
||||||
# Switch focus to the neighboring window in the indicated direction
|
# Switch focus to the neighboring window in the indicated direction
|
||||||
map ctrl+left neighboring_window left
|
map ctrl+left neighboring_window left
|
||||||
map ctrl+right neighboring_window right
|
map ctrl+right neighboring_window right
|
||||||
|
|||||||
@@ -64,6 +64,17 @@ some special variables, documented below:
|
|||||||
``FRAGMENT``
|
``FRAGMENT``
|
||||||
The fragment (unquoted), if any of the URL or the empty string.
|
The fragment (unquoted), if any of the URL or the empty string.
|
||||||
|
|
||||||
|
``URL_PATH``
|
||||||
|
The path, query and fragment portions of the URL, without any
|
||||||
|
unquoting.
|
||||||
|
|
||||||
|
``EDITOR``
|
||||||
|
The terminal based text editor. The configured :opt:`editor` in
|
||||||
|
:file:`kitty.conf` is preferred.
|
||||||
|
|
||||||
|
``SHELL``
|
||||||
|
The path to the shell. The configured :opt:`shell` in :file:`kitty.conf` is
|
||||||
|
preferred, without arguments.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
You can use the :opt:`action_alias` option just as in :file:`kitty.conf` to
|
You can use the :opt:`action_alias` option just as in :file:`kitty.conf` to
|
||||||
@@ -99,7 +110,8 @@ lines. The various available criteria are:
|
|||||||
Useful if your system MIME database does not have definitions you need. This
|
Useful if your system MIME database does not have definitions you need. This
|
||||||
file is in the standard format of one definition per line, like:
|
file is in the standard format of one definition per line, like:
|
||||||
``text/plain rst md``. Note that the MIME type for directories is
|
``text/plain rst md``. Note that the MIME type for directories is
|
||||||
``inode/directory``.
|
``inode/directory``. MIME types are detected based on file extension, not
|
||||||
|
file contents.
|
||||||
|
|
||||||
``ext``
|
``ext``
|
||||||
A comma separated list of file extensions, for example: ``jpeg, tar.gz``
|
A comma separated list of file extensions, for example: ``jpeg, tar.gz``
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ Extending kitty
|
|||||||
------------------
|
------------------
|
||||||
|
|
||||||
kitty has a powerful framework for scripting. You can create small terminal
|
kitty has a powerful framework for scripting. You can create small terminal
|
||||||
programs called :doc:`kittens <kittens_intro>`. These can used to add features
|
programs called :doc:`kittens <kittens_intro>`. These can be used to add features
|
||||||
to kitty, for example, :doc:`editing remote files <kittens/remote_file>` or
|
to kitty, for example, :doc:`editing remote files <kittens/remote_file>` or
|
||||||
:doc:`inputting Unicode characters <kittens/unicode_input>`. They can also be
|
:doc:`inputting Unicode characters <kittens/unicode_input>`. They can also be
|
||||||
used to create programs that leverage kitty's powerful features, for example,
|
used to create programs that leverage kitty's powerful features, for example,
|
||||||
@@ -127,7 +127,7 @@ Startup Sessions
|
|||||||
You can control the :term:`tabs <tab>`, :term:`kitty window <window>` layout,
|
You can control the :term:`tabs <tab>`, :term:`kitty window <window>` layout,
|
||||||
working directory, startup programs, etc. by creating a *session* file and using
|
working directory, startup programs, etc. by creating a *session* file and using
|
||||||
the :option:`kitty --session` command line flag or the :opt:`startup_session`
|
the :option:`kitty --session` command line flag or the :opt:`startup_session`
|
||||||
option in :file:`kitty.conf`. For example:
|
option in :file:`kitty.conf`. An example, showing all available commands:
|
||||||
|
|
||||||
.. code-block:: session
|
.. code-block:: session
|
||||||
|
|
||||||
@@ -154,21 +154,30 @@ option in :file:`kitty.conf`. For example:
|
|||||||
launch zsh
|
launch zsh
|
||||||
|
|
||||||
# Create a new OS window
|
# Create a new OS window
|
||||||
# Any definitions specifed before the first new_os_window will apply to first OS window.
|
# Any definitions specified before the first new_os_window will apply to first OS window.
|
||||||
new_os_window
|
new_os_window
|
||||||
# Set new window size to 80x24 cells
|
# Set new window size to 80x24 cells
|
||||||
os_window_size 80c 24c
|
os_window_size 80c 24c
|
||||||
# Set the --class for the new OS window
|
# Set the --class for the new OS window
|
||||||
os_window_class mywindow
|
os_window_class mywindow
|
||||||
launch sh
|
launch sh
|
||||||
# Make the current window the active (focused) window
|
# Resize the current window (see the resize_window action for details)
|
||||||
|
resize_window wider 2
|
||||||
|
# Make the current window the active (focused) window in its tab
|
||||||
focus
|
focus
|
||||||
|
# Make the current OS Window the globally active window (not supported on Wayland)
|
||||||
|
focus_os_window
|
||||||
launch emacs
|
launch emacs
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
The :doc:`launch <launch>` command when used in a session file cannot create
|
The :doc:`launch <launch>` command when used in a session file cannot create
|
||||||
new OS windows, or tabs.
|
new OS windows, or tabs.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Environment variables of the for :code:`${NAME}` or :code:`$NAME` are
|
||||||
|
expanded in the session file, except in the *arguments* (not options) to the
|
||||||
|
launch command.
|
||||||
|
|
||||||
|
|
||||||
Creating tabs/windows
|
Creating tabs/windows
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|||||||
@@ -32,3 +32,4 @@ please do so by opening issues in the `GitHub bug tracker
|
|||||||
unscroll
|
unscroll
|
||||||
color-stack
|
color-stack
|
||||||
deccara
|
deccara
|
||||||
|
clipboard
|
||||||
|
|||||||
@@ -7,13 +7,16 @@ form::
|
|||||||
|
|
||||||
<ESC>P@kitty-cmd<JSON object><ESC>\
|
<ESC>P@kitty-cmd<JSON object><ESC>\
|
||||||
|
|
||||||
Where ``<ESC>`` is the byte ``0x1b``. The JSON object has the form::
|
Where ``<ESC>`` is the byte ``0x1b``. The JSON object has the form:
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
{
|
{
|
||||||
"cmd": "command name",
|
"cmd": "command name",
|
||||||
"version": <kitty version>,
|
"version": "<kitty version>",
|
||||||
"no_response": <Optional Boolean>,
|
"no_response": "<Optional Boolean>",
|
||||||
"payload": <Optional JSON object>,
|
"kitty_window_id": "<Optional value of the KITTY_WINDOW_ID env var>",
|
||||||
|
"payload": "<Optional JSON object>"
|
||||||
}
|
}
|
||||||
|
|
||||||
The ``version`` above is an array of the form :code:`[0, 14, 2]`. If you are
|
The ``version`` above is an array of the form :code:`[0, 14, 2]`. If you are
|
||||||
@@ -38,4 +41,47 @@ with the following command line::
|
|||||||
|
|
||||||
echo -en '\eP@kitty-cmd{"cmd":"ls","version":[0,14,2]}\e\\' | socat - unix:/tmp/test | awk '{ print substr($0, 13, length($0) - 14) }' | jq -c '.data | fromjson' | jq .
|
echo -en '\eP@kitty-cmd{"cmd":"ls","version":[0,14,2]}\e\\' | socat - unix:/tmp/test | awk '{ print substr($0, 13, length($0) - 14) }' | jq -c '.data | fromjson' | jq .
|
||||||
|
|
||||||
|
There is also the statically compiled stand-alone executable ``kitten``
|
||||||
|
that can be used for this, available from the `kitty releases
|
||||||
|
<https://github.com/kovidgoyal/kitty/releases>`__ page::
|
||||||
|
|
||||||
|
kitten @ --help
|
||||||
|
|
||||||
|
.. _rc_crypto:
|
||||||
|
|
||||||
|
Encrypted communication
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
.. versionadded:: 0.26.0
|
||||||
|
|
||||||
|
When using the :opt:`remote_control_password` option communication to the
|
||||||
|
terminal is encrypted to keep the password secure. A public key is used from
|
||||||
|
the :envvar:`KITTY_PUBLIC_KEY` environment variable. Currently, only one
|
||||||
|
encryption protocol is supported. The protocol number is present in
|
||||||
|
:envvar:`KITTY_PUBLIC_KEY` as ``1``. The key data in this environment variable
|
||||||
|
is :rfc:`Base-85 <1924>` encoded. The algorithm used is `Elliptic Curve Diffie
|
||||||
|
Helman <https://en.wikipedia.org/wiki/Elliptic-curve_Diffie–Hellman>`__ with
|
||||||
|
the `X25519 curve <https://en.wikipedia.org/wiki/Curve25519>`__. A time based
|
||||||
|
nonce is used to minimise replay attacks. The original JSON command has the
|
||||||
|
fields: ``password`` and ``timestamp`` added. The timestamp is the number of
|
||||||
|
nanoseconds since the epoch, excluding leap seconds. Commands with a timestamp
|
||||||
|
more than 5 minutes from the current time are rejected. The command is then
|
||||||
|
encrypted using AES-256-GCM in authenticated encryption mode, with a symmetric
|
||||||
|
key that is derived from the ECDH key-pair by running the shared secret through
|
||||||
|
SHA-256 hashing, once. An IV of at least 96 bits of CSPRNG data is used. The
|
||||||
|
tag for authenticated encryption **must** be at least 128 bits long. The tag
|
||||||
|
**must** authenticate only the value of the ``encrypted`` field. A new command
|
||||||
|
is created and transmitted that contains the fields:
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"version": "<kitty version>",
|
||||||
|
"iv": "base85 encoded IV",
|
||||||
|
"tag": "base85 encoded AEAD tag",
|
||||||
|
"pubkey": "base85 encoded ECDH public key of sender",
|
||||||
|
"encrypted": "The original command encrypted and base85 encoded"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.. include:: generated/rc.rst
|
.. include:: generated/rc.rst
|
||||||
|
|||||||
@@ -17,8 +17,9 @@ Start by running |kitty| as::
|
|||||||
|
|
||||||
kitty -o allow_remote_control=yes -o enabled_layouts=tall
|
kitty -o allow_remote_control=yes -o enabled_layouts=tall
|
||||||
|
|
||||||
In order for control to work, :opt:`allow_remote_control` must be enabled in
|
In order for control to work, :opt:`allow_remote_control` or
|
||||||
:file:`kitty.conf`. Here we turn it on explicitly at the command line.
|
:opt:`remote_control_password` must be enabled in :file:`kitty.conf`. Here we
|
||||||
|
turn it on explicitly at the command line.
|
||||||
|
|
||||||
Now, in the new |kitty| window, enter the command::
|
Now, in the new |kitty| window, enter the command::
|
||||||
|
|
||||||
@@ -35,12 +36,12 @@ This will make ``Hello, World`` show up in the window running the :program:`cat`
|
|||||||
program. The :option:`kitty @ send-text --match` option is very powerful, it
|
program. The :option:`kitty @ send-text --match` option is very powerful, it
|
||||||
allows selecting windows by their titles, the command line of the program
|
allows selecting windows by their titles, the command line of the program
|
||||||
running in the window, the working directory of the program running in the
|
running in the window, the working directory of the program running in the
|
||||||
window, etc. See :ref:`kitty @ send-text --help <at_send-text>` for details.
|
window, etc. See :ref:`kitty @ send-text --help <at-send-text>` for details.
|
||||||
|
|
||||||
More usefully, you can pipe the output of a command running in one window to
|
More usefully, you can pipe the output of a command running in one window to
|
||||||
another window, for example::
|
another window, for example::
|
||||||
|
|
||||||
ls | kitty @ send-text --match title:^Output --stdin
|
ls | kitty @ send-text --match 'title:^Output' --stdin
|
||||||
|
|
||||||
This will show the output of :program:`ls` in the output window instead of the
|
This will show the output of :program:`ls` in the output window instead of the
|
||||||
current window. You can use this technique to, for example, show the output of
|
current window. You can use this technique to, for example, show the output of
|
||||||
@@ -49,7 +50,7 @@ are endless.
|
|||||||
|
|
||||||
You can even have things you type show up in a different window. Run::
|
You can even have things you type show up in a different window. Run::
|
||||||
|
|
||||||
kitty @ send-text --match title:^Output --stdin
|
kitty @ send-text --match 'title:^Output' --stdin
|
||||||
|
|
||||||
And type some text, it will show up in the output window, instead of the current
|
And type some text, it will show up in the output window, instead of the current
|
||||||
window. Type :kbd:`Ctrl+D` when you are ready to stop.
|
window. Type :kbd:`Ctrl+D` when you are ready to stop.
|
||||||
@@ -61,7 +62,7 @@ Now, let's open a new tab::
|
|||||||
This will open a new tab running the bash shell with the title "My Tab".
|
This will open a new tab running the bash shell with the title "My Tab".
|
||||||
We can change the title of the tab to "New Title" with::
|
We can change the title of the tab to "New Title" with::
|
||||||
|
|
||||||
kitty @ set-tab-title --match title:^My New Title
|
kitty @ set-tab-title --match 'title:^My' New Title
|
||||||
|
|
||||||
Let's change the title of the current tab::
|
Let's change the title of the current tab::
|
||||||
|
|
||||||
@@ -69,12 +70,12 @@ Let's change the title of the current tab::
|
|||||||
|
|
||||||
Now lets switch to the newly opened tab::
|
Now lets switch to the newly opened tab::
|
||||||
|
|
||||||
kitty @ focus-tab --match title:^New
|
kitty @ focus-tab --match 'title:^New'
|
||||||
|
|
||||||
Similarly, to focus the previously opened output window (which will also switch
|
Similarly, to focus the previously opened output window (which will also switch
|
||||||
back to the old tab, automatically)::
|
back to the old tab, automatically)::
|
||||||
|
|
||||||
kitty @ focus-window --match title:^Output
|
kitty @ focus-window --match 'title:^Output'
|
||||||
|
|
||||||
You can get a listing of available tabs and windows, by running::
|
You can get a listing of available tabs and windows, by running::
|
||||||
|
|
||||||
@@ -92,11 +93,23 @@ As you can see, it is very easy to control |kitty| using the ``kitty @``
|
|||||||
messaging system. This tutorial touches only the surface of what is possible.
|
messaging system. This tutorial touches only the surface of what is possible.
|
||||||
See ``kitty @ --help`` for more details.
|
See ``kitty @ --help`` for more details.
|
||||||
|
|
||||||
Note that in the example's above, ``kitty @`` messaging works only when run
|
In the example's above, ``kitty @`` messaging works only when run
|
||||||
inside a |kitty| window, not anywhere. But, within a |kitty| window it even
|
inside a |kitty| window, not anywhere. But, within a |kitty| window it even
|
||||||
works over SSH. If you want to control |kitty| from programs/scripts not running
|
works over SSH. If you want to control |kitty| from programs/scripts not running
|
||||||
inside a |kitty| window, you have to implement a couple of extra steps. First
|
inside a |kitty| window, see the section on :ref:`using a socket for remote control <rc_via_socket>`
|
||||||
start |kitty| as::
|
below.
|
||||||
|
|
||||||
|
|
||||||
|
Note that if all you want to do is run a single |kitty| "daemon" and have
|
||||||
|
subsequent |kitty| invocations appear as new top-level windows, you can use the
|
||||||
|
simpler :option:`kitty --single-instance` option, see ``kitty --help`` for that.
|
||||||
|
|
||||||
|
|
||||||
|
.. _rc_via_socket:
|
||||||
|
|
||||||
|
Remote control via a socket
|
||||||
|
--------------------------------
|
||||||
|
First, start |kitty| as::
|
||||||
|
|
||||||
kitty -o allow_remote_control=yes --listen-on unix:/tmp/mykitty
|
kitty -o allow_remote_control=yes --listen-on unix:/tmp/mykitty
|
||||||
|
|
||||||
@@ -108,11 +121,6 @@ command line argument to ``kitty @``. For example::
|
|||||||
kitty @ --to unix:/tmp/mykitty ls
|
kitty @ --to unix:/tmp/mykitty ls
|
||||||
|
|
||||||
|
|
||||||
Note that if all you want to do is run a single |kitty| "daemon" and have
|
|
||||||
subsequent |kitty| invocations appear as new top-level windows, you can use the
|
|
||||||
simpler :option:`kitty --single-instance` option, see ``kitty --help`` for that.
|
|
||||||
|
|
||||||
|
|
||||||
The builtin kitty shell
|
The builtin kitty shell
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
@@ -147,6 +155,112 @@ other computers (for example, over SSH) or as other users.
|
|||||||
what programs you run in such windows, since they can effectively control
|
what programs you run in such windows, since they can effectively control
|
||||||
kitty, as if you were running with :opt:`allow_remote_control` turned on.
|
kitty, as if you were running with :opt:`allow_remote_control` turned on.
|
||||||
|
|
||||||
|
You can further restrict what is allowed in these windows by using
|
||||||
|
:option:`kitty @ launch --remote-control-password`.
|
||||||
|
|
||||||
|
|
||||||
|
Fine grained permissions for remote control
|
||||||
|
----------------------------------------------
|
||||||
|
|
||||||
|
.. versionadded:: 0.26.0
|
||||||
|
|
||||||
|
The :opt:`allow_remote_control` option discussed so far is a blunt
|
||||||
|
instrument, granting the ability to any program running on your computer
|
||||||
|
or even on remote computers via SSH the ability to use remote control.
|
||||||
|
|
||||||
|
You can instead define remote control passwords that can be used to grant
|
||||||
|
different levels of control to different places. You can even write your
|
||||||
|
own script to decide which remote control requests are allowed. This is
|
||||||
|
done using the :opt:`remote_control_password` option in :file:`kitty.conf`.
|
||||||
|
Set :opt:`allow_remote_control` to :code:`password` to use this feature.
|
||||||
|
Let's see some examples:
|
||||||
|
|
||||||
|
.. code-block:: conf
|
||||||
|
|
||||||
|
remote_control_password "control colors" get-colors set-colors
|
||||||
|
|
||||||
|
Now, using this password, you can, in scripts run the command::
|
||||||
|
|
||||||
|
kitty @ --password="control colors" set-colors background=red
|
||||||
|
|
||||||
|
Any script with access to the password can now change colors in kitty using
|
||||||
|
remote control, but only that and nothing else. You can even supply the
|
||||||
|
password via the :envvar:`KITTY_RC_PASSWORD` environment variable, or the
|
||||||
|
file :file:`~/.config/kitty/rc-password` to avoid having to type it repeatedly.
|
||||||
|
See :option:`kitty @ --password-file` and :option:`kitty @ --password-env`.
|
||||||
|
|
||||||
|
The :opt:`remote_control_password` can be specified multiple times to create
|
||||||
|
different passwords with different capabilities. Run the following to get a
|
||||||
|
list of all action names::
|
||||||
|
|
||||||
|
kitty @ --help
|
||||||
|
|
||||||
|
You can even use glob patterns to match action names, for example:
|
||||||
|
|
||||||
|
.. code-block:: conf
|
||||||
|
|
||||||
|
remote_control_password "control colors" *-colors
|
||||||
|
|
||||||
|
If no action names are specified, all actions are allowed.
|
||||||
|
|
||||||
|
If ``kitty @`` is run with a password that is not present in
|
||||||
|
:file:`kitty.conf`, then kitty will interactively prompt the user to allow or
|
||||||
|
disallow the remote control request. The user can choose to allow or disallow
|
||||||
|
either just that request or all requests using that password. The user's
|
||||||
|
decision is remembered for the duration of that kitty instance.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
For password based authentication to work over SSH, you must pass the
|
||||||
|
:envvar:`KITTY_PUBLIC_KEY` environment variable to the remote host. The
|
||||||
|
:doc:`ssh kitten <kittens/ssh>` does this for you automatically. When
|
||||||
|
using a password, :ref:`rc_crypto` is used to ensure the password
|
||||||
|
is kept secure. This does mean that using password based authentication
|
||||||
|
is slower as the entire command is encrypted before transmission. This
|
||||||
|
can be noticeable when using a command like ``kitty @ set-background-image``
|
||||||
|
which transmits large amounts of image data. Also, the clock on the remote
|
||||||
|
system must match (within a few minutes) the clock on the local system.
|
||||||
|
kitty uses a time based nonce to minimise the potential for replay attacks.
|
||||||
|
|
||||||
|
.. _rc_custom_auth:
|
||||||
|
|
||||||
|
Customizing authorization with your own program
|
||||||
|
____________________________________________________________
|
||||||
|
|
||||||
|
If the ability to control access by action names is not fine grained enough,
|
||||||
|
you can define your own Python script to examine every remote control command
|
||||||
|
and allow/disallow it. To do so create a file in the kitty configuration
|
||||||
|
directory, :file:`~/.config/kitty/my_rc_auth.py` and add the following
|
||||||
|
to :file:`kitty.conf`:
|
||||||
|
|
||||||
|
.. code-block:: conf
|
||||||
|
|
||||||
|
remote_control_password "testing custom auth" my_rc_auth.py
|
||||||
|
|
||||||
|
:file:`my_rc_auth.py` should define a :code:`is_cmd_allowed` function
|
||||||
|
as shown below:
|
||||||
|
|
||||||
|
.. code-block:: py
|
||||||
|
|
||||||
|
def is_cmd_allowed(pcmd, window, from_socket, extra_data):
|
||||||
|
cmd_name = pcmd['cmd'] # the name of the command
|
||||||
|
cmd_payload = pcmd['payload'] # the arguments to the command
|
||||||
|
# examine the cmd_name and cmd_payload and return True to allow
|
||||||
|
# the command or False to disallow it. Return None to have no
|
||||||
|
# effect on the command.
|
||||||
|
|
||||||
|
# The command payload will vary from command to command, see
|
||||||
|
# the rc protocol docs for details. Below is an example of
|
||||||
|
# restricting the launch command to allow only running the
|
||||||
|
# default shell.
|
||||||
|
|
||||||
|
if cmd_name != 'launch':
|
||||||
|
return None
|
||||||
|
if cmd_payload.get('args') or cmd_payload.get('env') or cmd_payload.get('copy_cmdline') or cmd_payload.get('copy_env'):
|
||||||
|
return False
|
||||||
|
# prints in this function go to the parent kitty process STDOUT
|
||||||
|
print('Allowing launch command:', cmd_payload)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
.. _rc_mapping:
|
.. _rc_mapping:
|
||||||
|
|
||||||
@@ -162,6 +276,12 @@ Then pressing the :kbd:`F1` key will set the active window margins to
|
|||||||
:code:`30`. The syntax for what follows :ac:`remote_control` is exactly the same
|
:code:`30`. The syntax for what follows :ac:`remote_control` is exactly the same
|
||||||
as the syntax for what follows :code:`kitty @` above.
|
as the syntax for what follows :code:`kitty @` above.
|
||||||
|
|
||||||
|
If you wish to ignore errors from the command, prefix the command with an
|
||||||
|
``!``. For example, the following will not return an error when no windows
|
||||||
|
are matched::
|
||||||
|
|
||||||
|
map f1 remote_control !focus-window --match XXXXXX
|
||||||
|
|
||||||
.. note:: You do not need :opt:`allow_remote_control` to use these mappings,
|
.. note:: You do not need :opt:`allow_remote_control` to use these mappings,
|
||||||
as they are not actual remote programs, but are simply a way to resuse the
|
as they are not actual remote programs, but are simply a way to resuse the
|
||||||
remote control infrastructure via keybings.
|
remote control infrastructure via keybings.
|
||||||
@@ -184,7 +304,13 @@ The remote control protocol
|
|||||||
-----------------------------------------------
|
-----------------------------------------------
|
||||||
|
|
||||||
If you wish to develop your own client to talk to |kitty|, you can use the
|
If you wish to develop your own client to talk to |kitty|, you can use the
|
||||||
:doc:`remote control protocol specification <rc_protocol>`.
|
:doc:`remote control protocol specification <rc_protocol>`. Note that there
|
||||||
|
is a statically compiled, standalone executable, ``kitten`` available that
|
||||||
|
can be used as a remote control client on any UNIX like computer. This can be
|
||||||
|
downloaded and used directly from the `kitty releases
|
||||||
|
<https://github.com/kovidgoyal/kitty/releases>`__ page::
|
||||||
|
|
||||||
|
kitten @ --help
|
||||||
|
|
||||||
|
|
||||||
.. _search_syntax:
|
.. _search_syntax:
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ Features
|
|||||||
* Sophisticated completion for the :program:`kitty` command in the shell.
|
* Sophisticated completion for the :program:`kitty` command in the shell.
|
||||||
|
|
||||||
* When confirming a quit command if a window is sitting at a shell prompt,
|
* When confirming a quit command if a window is sitting at a shell prompt,
|
||||||
it is optionally, not counted (see :opt:`confirm_os_window_close`)
|
it is not counted (for details, see :opt:`confirm_os_window_close`)
|
||||||
|
|
||||||
|
|
||||||
Configuration
|
Configuration
|
||||||
@@ -274,8 +274,8 @@ Edit files in new kitty windows even over SSH
|
|||||||
|
|
||||||
edit-in-kitty myfile.txt
|
edit-in-kitty myfile.txt
|
||||||
edit-in-kitty --type tab --title "Editing My File" myfile.txt
|
edit-in-kitty --type tab --title "Editing My File" myfile.txt
|
||||||
# open file.txt at line 75 (works with vim, emacs, nano, micro)
|
# open myfile.txt at line 75 (works with vim, neovim, emacs, nano, micro)
|
||||||
edit-in-kitty +75 file.txt
|
edit-in-kitty +75 myfile.txt
|
||||||
|
|
||||||
The :command:`edit-in-kitty` command allows you to seamlessly edit files
|
The :command:`edit-in-kitty` command allows you to seamlessly edit files
|
||||||
in your default :opt:`editor` in new kitty windows. This works even over
|
in your default :opt:`editor` in new kitty windows. This works even over
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
#!/usr/bin/env python
|
#!./kitty/launcher/kitty +launch
|
||||||
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import subprocess
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from kitty.conf.generate import write_output
|
from kitty.conf.generate import write_output
|
||||||
@@ -11,14 +12,25 @@ from kitty.conf.generate import write_output
|
|||||||
def patch_color_list(path: str, colors: List[str], name: str, spc: str = ' ') -> None:
|
def patch_color_list(path: str, colors: List[str], name: str, spc: str = ' ') -> None:
|
||||||
with open(path, 'r+') as f:
|
with open(path, 'r+') as f:
|
||||||
raw = f.read()
|
raw = f.read()
|
||||||
nraw = re.sub(
|
colors = sorted(colors)
|
||||||
fr'(# {name}_COLORS_START).+?(\s+# {name}_COLORS_END)',
|
if path.endswith('.go'):
|
||||||
r'\1' + f'\n{spc}' + f'\n{spc}'.join(map(lambda x: f'{x!r},', sorted(colors))) + r'\2',
|
spc = '\t'
|
||||||
raw, flags=re.DOTALL | re.MULTILINE)
|
nraw = re.sub(
|
||||||
|
fr'(// {name}_COLORS_START).+?(\s+// {name}_COLORS_END)',
|
||||||
|
r'\1' + f'\n{spc}' + f'\n{spc}'.join(map(lambda x: f'"{x}":true,', colors)) + r'\2',
|
||||||
|
raw, flags=re.DOTALL | re.MULTILINE)
|
||||||
|
else:
|
||||||
|
nraw = re.sub(
|
||||||
|
fr'(# {name}_COLORS_START).+?(\s+# {name}_COLORS_END)',
|
||||||
|
r'\1' + f'\n{spc}' + f'\n{spc}'.join(map(lambda x: f'{x!r},', colors)) + r'\2',
|
||||||
|
raw, flags=re.DOTALL | re.MULTILINE)
|
||||||
if nraw != raw:
|
if nraw != raw:
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
f.truncate()
|
f.truncate()
|
||||||
f.write(nraw)
|
f.write(nraw)
|
||||||
|
f.flush()
|
||||||
|
if path.endswith('.go'):
|
||||||
|
subprocess.check_call(['gofmt', '-w', path])
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
@@ -34,6 +46,7 @@ def main() -> None:
|
|||||||
elif opt.parser_func.__name__ in ('to_color', 'titlebar_color', 'macos_titlebar_color'):
|
elif opt.parser_func.__name__ in ('to_color', 'titlebar_color', 'macos_titlebar_color'):
|
||||||
all_colors.append(opt.name)
|
all_colors.append(opt.name)
|
||||||
patch_color_list('kitty/rc/set_colors.py', nullable_colors, 'NULLABLE')
|
patch_color_list('kitty/rc/set_colors.py', nullable_colors, 'NULLABLE')
|
||||||
|
patch_color_list('tools/cmd/at/set_colors.go', nullable_colors, 'NULLABLE')
|
||||||
patch_color_list('kittens/themes/collection.py', all_colors, 'ALL', ' ' * 8)
|
patch_color_list('kittens/themes/collection.py', all_colors, 'ALL', ' ' * 8)
|
||||||
|
|
||||||
from kittens.diff.options.definition import definition as kd
|
from kittens.diff.options.definition import definition as kd
|
||||||
|
|||||||
607
gen-go-code.py
Executable file
607
gen-go-code.py
Executable file
@@ -0,0 +1,607 @@
|
|||||||
|
#!./kitty/launcher/kitty +launch
|
||||||
|
# License: GPLv3 Copyright: 2022, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from contextlib import contextmanager, suppress
|
||||||
|
from functools import lru_cache
|
||||||
|
from typing import Any, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Union
|
||||||
|
|
||||||
|
import kitty.constants as kc
|
||||||
|
from kittens.tui.operations import Mode
|
||||||
|
from kittens.tui.spinners import spinners
|
||||||
|
from kitty.cli import (
|
||||||
|
CompletionSpec,
|
||||||
|
GoOption,
|
||||||
|
go_options_for_seq,
|
||||||
|
parse_option_spec,
|
||||||
|
serialize_as_go_string,
|
||||||
|
)
|
||||||
|
from kitty.guess_mime_type import text_mimes
|
||||||
|
from kitty.key_encoding import config_mod_map
|
||||||
|
from kitty.key_names import character_key_name_aliases, functional_key_name_aliases
|
||||||
|
from kitty.options.types import Options
|
||||||
|
from kitty.rc.base import RemoteCommand, all_command_names, command_for_name
|
||||||
|
from kitty.remote_control import global_options_spec
|
||||||
|
from kitty.rgb import color_names
|
||||||
|
|
||||||
|
changed: List[str] = []
|
||||||
|
|
||||||
|
|
||||||
|
# Utils {{{
|
||||||
|
|
||||||
|
def serialize_go_dict(x: Union[Dict[str, int], Dict[int, str], Dict[int, int], Dict[str, str]]) -> str:
|
||||||
|
ans = []
|
||||||
|
|
||||||
|
def s(x: Union[int, str]) -> str:
|
||||||
|
if isinstance(x, int):
|
||||||
|
return str(x)
|
||||||
|
return f'"{serialize_as_go_string(x)}"'
|
||||||
|
|
||||||
|
for k, v in x.items():
|
||||||
|
ans.append(f'{s(k)}: {s(v)}')
|
||||||
|
return '{' + ', '.join(ans) + '}'
|
||||||
|
|
||||||
|
|
||||||
|
def replace(template: str, **kw: str) -> str:
|
||||||
|
for k, v in kw.items():
|
||||||
|
template = template.replace(k, v)
|
||||||
|
return template
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
|
# Completions {{{
|
||||||
|
|
||||||
|
@lru_cache
|
||||||
|
def kitten_cli_docs(kitten: str) -> Any:
|
||||||
|
from kittens.runner import get_kitten_cli_docs
|
||||||
|
return get_kitten_cli_docs(kitten)
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache
|
||||||
|
def go_options_for_kitten(kitten: str) -> Tuple[Sequence[GoOption], Optional[CompletionSpec]]:
|
||||||
|
kcd = kitten_cli_docs(kitten)
|
||||||
|
if kcd:
|
||||||
|
ospec = kcd['options']
|
||||||
|
return (tuple(go_options_for_seq(parse_option_spec(ospec())[0])), kcd.get('args_completion'))
|
||||||
|
return (), None
|
||||||
|
|
||||||
|
|
||||||
|
def generate_kittens_completion() -> None:
|
||||||
|
from kittens.runner import all_kitten_names, get_kitten_wrapper_of
|
||||||
|
for kitten in sorted(all_kitten_names()):
|
||||||
|
kn = 'kitten_' + kitten
|
||||||
|
print(f'{kn} := plus_kitten.AddSubCommand(&cli.Command{{Name:"{kitten}", Group: "Kittens"}})')
|
||||||
|
wof = get_kitten_wrapper_of(kitten)
|
||||||
|
if wof:
|
||||||
|
print(f'{kn}.ArgCompleter = cli.CompletionForWrapper("{serialize_as_go_string(wof)}")')
|
||||||
|
print(f'{kn}.OnlyArgsAllowed = true')
|
||||||
|
continue
|
||||||
|
gopts, ac = go_options_for_kitten(kitten)
|
||||||
|
if gopts or ac:
|
||||||
|
for opt in gopts:
|
||||||
|
print(opt.as_option(kn))
|
||||||
|
if ac is not None:
|
||||||
|
print(''.join(ac.as_go_code(kn + '.ArgCompleter', ' = ')))
|
||||||
|
else:
|
||||||
|
print(f'{kn}.HelpText = ""')
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache
|
||||||
|
def clone_safe_launch_opts() -> Sequence[GoOption]:
|
||||||
|
from kitty.launch import clone_safe_opts, options_spec
|
||||||
|
ans = []
|
||||||
|
allowed = clone_safe_opts()
|
||||||
|
for o in go_options_for_seq(parse_option_spec(options_spec())[0]):
|
||||||
|
if o.obj_dict['name'] in allowed:
|
||||||
|
ans.append(o)
|
||||||
|
return tuple(ans)
|
||||||
|
|
||||||
|
|
||||||
|
def completion_for_launch_wrappers(*names: str) -> None:
|
||||||
|
for o in clone_safe_launch_opts():
|
||||||
|
for name in names:
|
||||||
|
print(o.as_option(name))
|
||||||
|
|
||||||
|
|
||||||
|
def generate_completions_for_kitty() -> None:
|
||||||
|
from kitty.config import option_names_for_completion
|
||||||
|
print('package completion\n')
|
||||||
|
print('import "kitty/tools/cli"')
|
||||||
|
print('import "kitty/tools/cmd/tool"')
|
||||||
|
print('import "kitty/tools/cmd/at"')
|
||||||
|
conf_names = ', '.join((f'"{serialize_as_go_string(x)}"' for x in option_names_for_completion()))
|
||||||
|
print('var kitty_option_names_for_completion = []string{' + conf_names + '}')
|
||||||
|
|
||||||
|
print('func kitty(root *cli.Command) {')
|
||||||
|
|
||||||
|
# The kitty exe
|
||||||
|
print('k := root.AddSubCommand(&cli.Command{'
|
||||||
|
'Name:"kitty", SubCommandIsOptional: true, ArgCompleter: cli.CompleteExecutableFirstArg, SubCommandMustBeFirst: true })')
|
||||||
|
print('kt := root.AddSubCommand(&cli.Command{Name:"kitten", SubCommandMustBeFirst: true })')
|
||||||
|
print('tool.KittyToolEntryPoints(kt)')
|
||||||
|
for opt in go_options_for_seq(parse_option_spec()[0]):
|
||||||
|
print(opt.as_option('k'))
|
||||||
|
|
||||||
|
# kitty +
|
||||||
|
print('plus := k.AddSubCommand(&cli.Command{Name:"+", Group:"Entry points", ShortDescription: "Various special purpose tools and kittens"})')
|
||||||
|
|
||||||
|
# kitty +launch
|
||||||
|
print('plus_launch := plus.AddSubCommand(&cli.Command{'
|
||||||
|
'Name:"launch", Group:"Entry points", ShortDescription: "Launch Python scripts", ArgCompleter: complete_plus_launch})')
|
||||||
|
print('k.AddClone("", plus_launch).Name = "+launch"')
|
||||||
|
|
||||||
|
# kitty +list-fonts
|
||||||
|
print('plus_list_fonts := plus.AddSubCommand(&cli.Command{'
|
||||||
|
'Name:"list-fonts", Group:"Entry points", ShortDescription: "List all available monospaced fonts"})')
|
||||||
|
print('k.AddClone("", plus_list_fonts).Name = "+list-fonts"')
|
||||||
|
|
||||||
|
# kitty +runpy
|
||||||
|
print('plus_runpy := plus.AddSubCommand(&cli.Command{'
|
||||||
|
'Name: "runpy", Group:"Entry points", ArgCompleter: complete_plus_runpy, ShortDescription: "Run Python code"})')
|
||||||
|
print('k.AddClone("", plus_runpy).Name = "+runpy"')
|
||||||
|
|
||||||
|
# kitty +open
|
||||||
|
print('plus_open := plus.AddSubCommand(&cli.Command{'
|
||||||
|
'Name:"open", Group:"Entry points", ArgCompleter: complete_plus_open, ShortDescription: "Open files and URLs"})')
|
||||||
|
print('for _, og := range k.OptionGroups { plus_open.OptionGroups = append(plus_open.OptionGroups, og.Clone(plus_open)) }')
|
||||||
|
print('k.AddClone("", plus_open).Name = "+open"')
|
||||||
|
|
||||||
|
# kitty +kitten
|
||||||
|
print('plus_kitten := plus.AddSubCommand(&cli.Command{Name:"kitten", Group:"Kittens", SubCommandMustBeFirst: true})')
|
||||||
|
generate_kittens_completion()
|
||||||
|
print('k.AddClone("", plus_kitten).Name = "+kitten"')
|
||||||
|
|
||||||
|
# @
|
||||||
|
print('at.EntryPoint(k)')
|
||||||
|
|
||||||
|
# clone-in-kitty, edit-in-kitty
|
||||||
|
print('cik := root.AddSubCommand(&cli.Command{Name:"clone-in-kitty"})')
|
||||||
|
completion_for_launch_wrappers('cik')
|
||||||
|
|
||||||
|
print('}')
|
||||||
|
print('func init() {')
|
||||||
|
print('cli.RegisterExeForCompletion(kitty)')
|
||||||
|
print('}')
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
|
# rc command wrappers {{{
|
||||||
|
json_field_types: Dict[str, str] = {
|
||||||
|
'bool': 'bool', 'str': 'escaped_string', 'list.str': '[]escaped_string', 'dict.str': 'map[escaped_string]escaped_string', 'float': 'float64', 'int': 'int',
|
||||||
|
'scroll_amount': 'any', 'spacing': 'any', 'colors': 'any',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def go_field_type(json_field_type: str) -> str:
|
||||||
|
q = json_field_types.get(json_field_type)
|
||||||
|
if q:
|
||||||
|
return q
|
||||||
|
if json_field_type.startswith('choices.'):
|
||||||
|
return 'string'
|
||||||
|
if '.' in json_field_type:
|
||||||
|
p, r = json_field_type.split('.', 1)
|
||||||
|
p = {'list': '[]', 'dict': 'map[string]'}[p]
|
||||||
|
return p + go_field_type(r)
|
||||||
|
raise TypeError(f'Unknown JSON field type: {json_field_type}')
|
||||||
|
|
||||||
|
|
||||||
|
class JSONField:
|
||||||
|
|
||||||
|
def __init__(self, line: str) -> None:
|
||||||
|
field_def = line.split(':', 1)[0]
|
||||||
|
self.required = False
|
||||||
|
self.field, self.field_type = field_def.split('/', 1)
|
||||||
|
if self.field.endswith('+'):
|
||||||
|
self.required = True
|
||||||
|
self.field = self.field[:-1]
|
||||||
|
self.struct_field_name = self.field[0].upper() + self.field[1:]
|
||||||
|
|
||||||
|
def go_declaration(self) -> str:
|
||||||
|
return self.struct_field_name + ' ' + go_field_type(self.field_type) + f'`json:"{self.field},omitempty"`'
|
||||||
|
|
||||||
|
|
||||||
|
def go_code_for_remote_command(name: str, cmd: RemoteCommand, template: str) -> str:
|
||||||
|
template = '\n' + template[len('//go:build exclude'):]
|
||||||
|
NO_RESPONSE_BASE = 'false'
|
||||||
|
af: List[str] = []
|
||||||
|
a = af.append
|
||||||
|
af.extend(cmd.args.as_go_completion_code('ans'))
|
||||||
|
od: List[str] = []
|
||||||
|
option_map: Dict[str, GoOption] = {}
|
||||||
|
for o in rc_command_options(name):
|
||||||
|
option_map[o.go_var_name] = o
|
||||||
|
a(o.as_option('ans'))
|
||||||
|
if o.go_var_name in ('NoResponse', 'ResponseTimeout'):
|
||||||
|
continue
|
||||||
|
od.append(o.struct_declaration())
|
||||||
|
jd: List[str] = []
|
||||||
|
json_fields = []
|
||||||
|
field_types: Dict[str, str] = {}
|
||||||
|
for line in cmd.protocol_spec.splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if ':' not in line:
|
||||||
|
continue
|
||||||
|
f = JSONField(line)
|
||||||
|
json_fields.append(f)
|
||||||
|
field_types[f.field] = f.field_type
|
||||||
|
jd.append(f.go_declaration())
|
||||||
|
jc: List[str] = []
|
||||||
|
handled_fields: Set[str] = set()
|
||||||
|
jc.extend(cmd.args.as_go_code(name, field_types, handled_fields))
|
||||||
|
|
||||||
|
unhandled = {}
|
||||||
|
used_options = set()
|
||||||
|
for field in json_fields:
|
||||||
|
oq = (cmd.field_to_option_map or {}).get(field.field, field.field)
|
||||||
|
oq = ''.join(x.capitalize() for x in oq.split('_'))
|
||||||
|
if oq in option_map:
|
||||||
|
o = option_map[oq]
|
||||||
|
used_options.add(oq)
|
||||||
|
if field.field_type == 'str':
|
||||||
|
jc.append(f'payload.{field.struct_field_name} = escaped_string(options_{name}.{o.go_var_name})')
|
||||||
|
elif field.field_type == 'list.str':
|
||||||
|
jc.append(f'payload.{field.struct_field_name} = escape_list_of_strings(options_{name}.{o.go_var_name})')
|
||||||
|
elif field.field_type == 'dict.str':
|
||||||
|
jc.append(f'payload.{field.struct_field_name} = escape_dict_of_strings(options_{name}.{o.go_var_name})')
|
||||||
|
else:
|
||||||
|
jc.append(f'payload.{field.struct_field_name} = options_{name}.{o.go_var_name}')
|
||||||
|
elif field.field in handled_fields:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
unhandled[field.field] = field
|
||||||
|
for x in tuple(unhandled):
|
||||||
|
if x == 'match_window' and 'Match' in option_map and 'Match' not in used_options:
|
||||||
|
used_options.add('Match')
|
||||||
|
o = option_map['Match']
|
||||||
|
field = unhandled[x]
|
||||||
|
if field.field_type == 'str':
|
||||||
|
jc.append(f'payload.{field.struct_field_name} = escaped_string(options_{name}.{o.go_var_name})')
|
||||||
|
else:
|
||||||
|
jc.append(f'payload.{field.struct_field_name} = options_{name}.{o.go_var_name}')
|
||||||
|
del unhandled[x]
|
||||||
|
if unhandled:
|
||||||
|
raise SystemExit(f'Cant map fields: {", ".join(unhandled)} for cmd: {name}')
|
||||||
|
if name != 'send_text':
|
||||||
|
unused_options = set(option_map) - used_options - {'NoResponse', 'ResponseTimeout'}
|
||||||
|
if unused_options:
|
||||||
|
raise SystemExit(f'Unused options: {", ".join(unused_options)} for command: {name}')
|
||||||
|
|
||||||
|
argspec = cmd.args.spec
|
||||||
|
if argspec:
|
||||||
|
argspec = ' ' + argspec
|
||||||
|
ans = replace(
|
||||||
|
template,
|
||||||
|
CMD_NAME=name, __FILE__=__file__, CLI_NAME=name.replace('_', '-'),
|
||||||
|
SHORT_DESC=serialize_as_go_string(cmd.short_desc),
|
||||||
|
LONG_DESC=serialize_as_go_string(cmd.desc.strip()),
|
||||||
|
IS_ASYNC='true' if cmd.is_asynchronous else 'false',
|
||||||
|
NO_RESPONSE_BASE=NO_RESPONSE_BASE, ADD_FLAGS_CODE='\n'.join(af),
|
||||||
|
WAIT_TIMEOUT=str(cmd.response_timeout),
|
||||||
|
OPTIONS_DECLARATION_CODE='\n'.join(od),
|
||||||
|
JSON_DECLARATION_CODE='\n'.join(jd),
|
||||||
|
JSON_INIT_CODE='\n'.join(jc), ARGSPEC=argspec,
|
||||||
|
STRING_RESPONSE_IS_ERROR='true' if cmd.string_return_is_error else 'false',
|
||||||
|
STREAM_WANTED='true' if cmd.reads_streaming_data else 'false',
|
||||||
|
)
|
||||||
|
return ans
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
|
# kittens {{{
|
||||||
|
|
||||||
|
@lru_cache
|
||||||
|
def wrapped_kittens() -> Sequence[str]:
|
||||||
|
with open('shell-integration/ssh/kitty') as f:
|
||||||
|
for line in f:
|
||||||
|
if line.startswith(' wrapped_kittens="'):
|
||||||
|
val = line.strip().partition('"')[2][:-1]
|
||||||
|
return tuple(sorted(filter(None, val.split())))
|
||||||
|
raise Exception('Failed to read wrapped kittens from kitty wrapper script')
|
||||||
|
|
||||||
|
|
||||||
|
def kitten_clis() -> None:
|
||||||
|
for kitten in wrapped_kittens():
|
||||||
|
with replace_if_needed(f'tools/cmd/{kitten}/cli_generated.go'):
|
||||||
|
od = []
|
||||||
|
kcd = kitten_cli_docs(kitten)
|
||||||
|
has_underscore = '_' in kitten
|
||||||
|
print(f'package {kitten}')
|
||||||
|
print('import "kitty/tools/cli"')
|
||||||
|
print('func create_cmd(root *cli.Command, run_func func(*cli.Command, *Options, []string)(int, error)) {')
|
||||||
|
print('ans := root.AddSubCommand(&cli.Command{')
|
||||||
|
print(f'Name: "{kitten}",')
|
||||||
|
print(f'ShortDescription: "{serialize_as_go_string(kcd["short_desc"])}",')
|
||||||
|
if kcd['usage']:
|
||||||
|
print(f'Usage: "[options] {serialize_as_go_string(kcd["usage"])}",')
|
||||||
|
print(f'HelpText: "{serialize_as_go_string(kcd["help_text"])}",')
|
||||||
|
print('Run: func(cmd *cli.Command, args []string) (int, error) {')
|
||||||
|
print('opts := Options{}')
|
||||||
|
print('err := cmd.GetOptionValues(&opts)')
|
||||||
|
print('if err != nil { return 1, err }')
|
||||||
|
print('return run_func(cmd, &opts, args)},')
|
||||||
|
if has_underscore:
|
||||||
|
print('Hidden: true,')
|
||||||
|
print('})')
|
||||||
|
gopts, ac = go_options_for_kitten(kitten)
|
||||||
|
for opt in gopts:
|
||||||
|
print(opt.as_option('ans'))
|
||||||
|
od.append(opt.struct_declaration())
|
||||||
|
if ac is not None:
|
||||||
|
print(''.join(ac.as_go_code('ans.ArgCompleter', ' = ')))
|
||||||
|
if has_underscore:
|
||||||
|
print("clone := root.AddClone(ans.Group, ans)")
|
||||||
|
print('clone.Hidden = false')
|
||||||
|
print(f'clone.Name = "{serialize_as_go_string(kitten.replace("_", "-"))}"')
|
||||||
|
print('}')
|
||||||
|
print('type Options struct {')
|
||||||
|
print('\n'.join(od))
|
||||||
|
print('}')
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
|
# Constants {{{
|
||||||
|
|
||||||
|
def generate_spinners() -> str:
|
||||||
|
ans = ['package tui', 'import "time"', 'func NewSpinner(name string) *Spinner {', 'var ans *Spinner', 'switch name {']
|
||||||
|
a = ans.append
|
||||||
|
for name, spinner in spinners.items():
|
||||||
|
a(f'case "{serialize_as_go_string(name)}":')
|
||||||
|
a('ans = &Spinner{')
|
||||||
|
a(f'Name: "{serialize_as_go_string(name)}",')
|
||||||
|
a(f'interval: {spinner["interval"]},')
|
||||||
|
frames = ', '.join(f'"{serialize_as_go_string(x)}"' for x in spinner['frames'])
|
||||||
|
a(f'frames: []string{{{frames}}},')
|
||||||
|
a('}')
|
||||||
|
a('}')
|
||||||
|
a('if ans != nil {')
|
||||||
|
a('ans.interval *= time.Millisecond')
|
||||||
|
a('ans.current_frame = -1')
|
||||||
|
a('ans.last_change_at = time.Now().Add(-ans.interval)')
|
||||||
|
a('}')
|
||||||
|
a('return ans}')
|
||||||
|
return '\n'.join(ans)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_color_names() -> str:
|
||||||
|
return 'package style\n\nvar ColorNames = map[string]RGBA{' + '\n'.join(
|
||||||
|
f'\t"{name}": RGBA{{ Red:{val.red}, Green:{val.green}, Blue:{val.blue} }},'
|
||||||
|
for name, val in color_names.items()
|
||||||
|
) + '\n}' + '\n\nvar ColorTable = [256]uint32{' + ', '.join(
|
||||||
|
f'{x}' for x in Options.color_table) + '}\n'
|
||||||
|
|
||||||
|
|
||||||
|
def load_ref_map() -> Dict[str, Dict[str, str]]:
|
||||||
|
with open('kitty/docs_ref_map_generated.h') as f:
|
||||||
|
raw = f.read()
|
||||||
|
raw = raw.split('{', 1)[1].split('}', 1)[0]
|
||||||
|
data = json.loads(bytes(bytearray(json.loads(f'[{raw}]'))))
|
||||||
|
return data # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
def generate_constants() -> str:
|
||||||
|
ref_map = load_ref_map()
|
||||||
|
dp = ", ".join(map(lambda x: f'"{serialize_as_go_string(x)}"', kc.default_pager_for_help))
|
||||||
|
return f'''\
|
||||||
|
package kitty
|
||||||
|
|
||||||
|
type VersionType struct {{
|
||||||
|
Major, Minor, Patch int
|
||||||
|
}}
|
||||||
|
const VersionString string = "{kc.str_version}"
|
||||||
|
const WebsiteBaseURL string = "{kc.website_base_url}"
|
||||||
|
const VCSRevision string = ""
|
||||||
|
const RC_ENCRYPTION_PROTOCOL_VERSION string = "{kc.RC_ENCRYPTION_PROTOCOL_VERSION}"
|
||||||
|
const IsFrozenBuild bool = false
|
||||||
|
const IsStandaloneBuild bool = false
|
||||||
|
const HandleTermiosSignals = {Mode.HANDLE_TERMIOS_SIGNALS.value[0]}
|
||||||
|
var Version VersionType = VersionType{{Major: {kc.version.major}, Minor: {kc.version.minor}, Patch: {kc.version.patch},}}
|
||||||
|
var DefaultPager []string = []string{{ {dp} }}
|
||||||
|
var FunctionalKeyNameAliases = map[string]string{serialize_go_dict(functional_key_name_aliases)}
|
||||||
|
var CharacterKeyNameAliases = map[string]string{serialize_go_dict(character_key_name_aliases)}
|
||||||
|
var ConfigModMap = map[string]uint16{serialize_go_dict(config_mod_map)}
|
||||||
|
var RefMap = map[string]string{serialize_go_dict(ref_map['ref'])}
|
||||||
|
var DocTitleMap = map[string]string{serialize_go_dict(ref_map['doc'])}
|
||||||
|
''' # }}}
|
||||||
|
|
||||||
|
|
||||||
|
# Boilerplate {{{
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def replace_if_needed(path: str, show_diff: bool = False) -> Iterator[io.StringIO]:
|
||||||
|
buf = io.StringIO()
|
||||||
|
origb = sys.stdout
|
||||||
|
sys.stdout = buf
|
||||||
|
try:
|
||||||
|
yield buf
|
||||||
|
finally:
|
||||||
|
sys.stdout = origb
|
||||||
|
orig = ''
|
||||||
|
with suppress(FileNotFoundError), open(path, 'r') as f:
|
||||||
|
orig = f.read()
|
||||||
|
new = buf.getvalue()
|
||||||
|
new = f'// Code generated by {os.path.basename(__file__)}; DO NOT EDIT.\n\n' + new
|
||||||
|
if orig != new:
|
||||||
|
changed.append(path)
|
||||||
|
if show_diff:
|
||||||
|
with open(path + '.new', 'w') as f:
|
||||||
|
f.write(new)
|
||||||
|
subprocess.run(['diff', '-Naurp', path, f.name], stdout=open('/dev/tty', 'w'))
|
||||||
|
os.remove(f.name)
|
||||||
|
with open(path, 'w') as f:
|
||||||
|
f.write(new)
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=256)
|
||||||
|
def rc_command_options(name: str) -> Tuple[GoOption, ...]:
|
||||||
|
cmd = command_for_name(name)
|
||||||
|
return tuple(go_options_for_seq(parse_option_spec(cmd.options_spec or '\n\n')[0]))
|
||||||
|
|
||||||
|
|
||||||
|
def update_at_commands() -> None:
|
||||||
|
with open('tools/cmd/at/template.go') as f:
|
||||||
|
template = f.read()
|
||||||
|
for name in all_command_names():
|
||||||
|
cmd = command_for_name(name)
|
||||||
|
code = go_code_for_remote_command(name, cmd, template)
|
||||||
|
dest = f'tools/cmd/at/cmd_{name}_generated.go'
|
||||||
|
with replace_if_needed(dest) as f:
|
||||||
|
f.write(code)
|
||||||
|
struct_def = []
|
||||||
|
opt_def = []
|
||||||
|
for o in go_options_for_seq(parse_option_spec(global_options_spec())[0]):
|
||||||
|
struct_def.append(o.struct_declaration())
|
||||||
|
opt_def.append(o.as_option(depth=1, group="Global options"))
|
||||||
|
sdef = '\n'.join(struct_def)
|
||||||
|
odef = '\n'.join(opt_def)
|
||||||
|
code = f'''
|
||||||
|
package at
|
||||||
|
import "kitty/tools/cli"
|
||||||
|
type rc_global_options struct {{
|
||||||
|
{sdef}
|
||||||
|
}}
|
||||||
|
var rc_global_opts rc_global_options
|
||||||
|
|
||||||
|
func add_rc_global_opts(cmd *cli.Command) {{
|
||||||
|
{odef}
|
||||||
|
}}
|
||||||
|
'''
|
||||||
|
with replace_if_needed('tools/cmd/at/global_opts_generated.go') as f:
|
||||||
|
f.write(code)
|
||||||
|
|
||||||
|
|
||||||
|
def update_completion() -> None:
|
||||||
|
with replace_if_needed('tools/cmd/completion/kitty_generated.go'):
|
||||||
|
generate_completions_for_kitty()
|
||||||
|
with replace_if_needed('tools/cmd/edit_in_kitty/launch_generated.go'):
|
||||||
|
print('package edit_in_kitty')
|
||||||
|
print('import "kitty/tools/cli"')
|
||||||
|
print('func AddCloneSafeOpts(cmd *cli.Command) {')
|
||||||
|
completion_for_launch_wrappers('cmd')
|
||||||
|
print(''.join(CompletionSpec.from_string('type:file mime:text/* group:"Text files"').as_go_code('cmd.ArgCompleter', ' = ')))
|
||||||
|
print('}')
|
||||||
|
|
||||||
|
|
||||||
|
def define_enum(package_name: str, type_name: str, items: str, underlying_type: str = 'uint') -> str:
|
||||||
|
actions = []
|
||||||
|
for x in items.splitlines():
|
||||||
|
x = x.strip()
|
||||||
|
if x:
|
||||||
|
actions.append(x)
|
||||||
|
ans = [f'package {package_name}', 'import "strconv"', f'type {type_name} {underlying_type}', 'const (']
|
||||||
|
stringer = [f'func (ac {type_name}) String() string ''{', 'switch(ac) {']
|
||||||
|
for i, ac in enumerate(actions):
|
||||||
|
stringer.append(f'case {ac}: return "{ac}"')
|
||||||
|
if i == 0:
|
||||||
|
ac = ac + f' {type_name} = iota'
|
||||||
|
ans.append(ac)
|
||||||
|
ans.append(')')
|
||||||
|
stringer.append('}\nreturn strconv.Itoa(int(ac)) }')
|
||||||
|
return '\n'.join(ans + stringer)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_readline_actions() -> str:
|
||||||
|
return define_enum('readline', 'Action', '''\
|
||||||
|
ActionNil
|
||||||
|
|
||||||
|
ActionBackspace
|
||||||
|
ActionDelete
|
||||||
|
ActionMoveToStartOfLine
|
||||||
|
ActionMoveToEndOfLine
|
||||||
|
ActionMoveToStartOfDocument
|
||||||
|
ActionMoveToEndOfDocument
|
||||||
|
ActionMoveToEndOfWord
|
||||||
|
ActionMoveToStartOfWord
|
||||||
|
ActionCursorLeft
|
||||||
|
ActionCursorRight
|
||||||
|
ActionEndInput
|
||||||
|
ActionAcceptInput
|
||||||
|
ActionCursorUp
|
||||||
|
ActionHistoryPreviousOrCursorUp
|
||||||
|
ActionCursorDown
|
||||||
|
ActionHistoryNextOrCursorDown
|
||||||
|
ActionHistoryNext
|
||||||
|
ActionHistoryPrevious
|
||||||
|
ActionHistoryFirst
|
||||||
|
ActionHistoryLast
|
||||||
|
ActionHistoryIncrementalSearchBackwards
|
||||||
|
ActionHistoryIncrementalSearchForwards
|
||||||
|
ActionTerminateHistorySearchAndApply
|
||||||
|
ActionTerminateHistorySearchAndRestore
|
||||||
|
ActionClearScreen
|
||||||
|
ActionAddText
|
||||||
|
ActionAbortCurrentLine
|
||||||
|
|
||||||
|
ActionStartKillActions
|
||||||
|
ActionKillToEndOfLine
|
||||||
|
ActionKillToStartOfLine
|
||||||
|
ActionKillNextWord
|
||||||
|
ActionKillPreviousWord
|
||||||
|
ActionKillPreviousSpaceDelimitedWord
|
||||||
|
ActionEndKillActions
|
||||||
|
ActionYank
|
||||||
|
ActionPopYank
|
||||||
|
|
||||||
|
ActionNumericArgumentDigit0
|
||||||
|
ActionNumericArgumentDigit1
|
||||||
|
ActionNumericArgumentDigit2
|
||||||
|
ActionNumericArgumentDigit3
|
||||||
|
ActionNumericArgumentDigit4
|
||||||
|
ActionNumericArgumentDigit5
|
||||||
|
ActionNumericArgumentDigit6
|
||||||
|
ActionNumericArgumentDigit7
|
||||||
|
ActionNumericArgumentDigit8
|
||||||
|
ActionNumericArgumentDigit9
|
||||||
|
ActionNumericArgumentDigitMinus
|
||||||
|
|
||||||
|
ActionCompleteForward
|
||||||
|
ActionCompleteBackward
|
||||||
|
''')
|
||||||
|
|
||||||
|
|
||||||
|
def generate_mimetypes() -> str:
|
||||||
|
import mimetypes
|
||||||
|
if not mimetypes.inited:
|
||||||
|
mimetypes.init()
|
||||||
|
ans = ['package utils', 'import "sync"', 'var only_once sync.Once', 'var builtin_types_map map[string]string',
|
||||||
|
'func set_builtins() {', 'builtin_types_map = map[string]string{',]
|
||||||
|
for k, v in mimetypes.types_map.items():
|
||||||
|
ans.append(f' "{serialize_as_go_string(k)}": "{serialize_as_go_string(v)}",')
|
||||||
|
ans.append('}}')
|
||||||
|
return '\n'.join(ans)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_textual_mimetypes() -> str:
|
||||||
|
ans = ['package utils', 'var KnownTextualMimes = map[string]bool{',]
|
||||||
|
for k in text_mimes:
|
||||||
|
ans.append(f' "{serialize_as_go_string(k)}": true,')
|
||||||
|
ans.append('}')
|
||||||
|
return '\n'.join(ans)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
with replace_if_needed('constants_generated.go') as f:
|
||||||
|
f.write(generate_constants())
|
||||||
|
with replace_if_needed('tools/utils/style/color-names_generated.go') as f:
|
||||||
|
f.write(generate_color_names())
|
||||||
|
with replace_if_needed('tools/tui/readline/actions_generated.go') as f:
|
||||||
|
f.write(generate_readline_actions())
|
||||||
|
with replace_if_needed('tools/tui/spinners_generated.go') as f:
|
||||||
|
f.write(generate_spinners())
|
||||||
|
with replace_if_needed('tools/utils/mimetypes_generated.go') as f:
|
||||||
|
f.write(generate_mimetypes())
|
||||||
|
with replace_if_needed('tools/utils/mimetypes_textual_generated.go') as f:
|
||||||
|
f.write(generate_textual_mimetypes())
|
||||||
|
|
||||||
|
update_completion()
|
||||||
|
update_at_commands()
|
||||||
|
kitten_clis()
|
||||||
|
print(json.dumps(changed, indent=2))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main() # }}}
|
||||||
@@ -2,8 +2,8 @@
|
|||||||
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
import string
|
import string
|
||||||
from typing import Dict, List, Any
|
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
|
from typing import Any, Dict, List, Union
|
||||||
|
|
||||||
functional_key_defs = '''# {{{
|
functional_key_defs = '''# {{{
|
||||||
# kitty XKB macVK macU
|
# kitty XKB macVK macU
|
||||||
@@ -130,7 +130,7 @@ functional_encoding_overrides = {
|
|||||||
}
|
}
|
||||||
different_trailer_functionals = {
|
different_trailer_functionals = {
|
||||||
'up': 'A', 'down': 'B', 'right': 'C', 'left': 'D', 'kp_begin': 'E', 'end': 'F', 'home': 'H',
|
'up': 'A', 'down': 'B', 'right': 'C', 'left': 'D', 'kp_begin': 'E', 'end': 'F', 'home': 'H',
|
||||||
'f1': 'P', 'f2': 'Q', 'f3': 'R', 'f4': 'S', 'enter': 'u', 'tab': 'u',
|
'f1': 'P', 'f2': 'Q', 'f3': '~', 'f4': 'S', 'enter': 'u', 'tab': 'u',
|
||||||
'backspace': 'u', 'escape': 'u'
|
'backspace': 'u', 'escape': 'u'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,6 +248,19 @@ def serialize_dict(x: Dict[Any, Any]) -> str:
|
|||||||
return pformat(x, indent=4).replace('{', '{\n ', 1)
|
return pformat(x, indent=4).replace('{', '{\n ', 1)
|
||||||
|
|
||||||
|
|
||||||
|
def serialize_go_dict(x: Union[Dict[str, int], Dict[int, str], Dict[int, int]]) -> str:
|
||||||
|
ans = []
|
||||||
|
|
||||||
|
def s(x: Union[int, str]) -> str:
|
||||||
|
if isinstance(x, int):
|
||||||
|
return str(x)
|
||||||
|
return f'"{x}"'
|
||||||
|
|
||||||
|
for k, v in x.items():
|
||||||
|
ans.append(f'{s(k)}: {s(v)}')
|
||||||
|
return '{' + ', '.join(ans) + '}'
|
||||||
|
|
||||||
|
|
||||||
def generate_glfw_header() -> None:
|
def generate_glfw_header() -> None:
|
||||||
lines = [
|
lines = [
|
||||||
'typedef enum {',
|
'typedef enum {',
|
||||||
@@ -309,14 +322,20 @@ def generate_functional_table() -> None:
|
|||||||
patch_file('kitty/key_encoding.c', 'special numbers', '\n'.join(enc_lines))
|
patch_file('kitty/key_encoding.c', 'special numbers', '\n'.join(enc_lines))
|
||||||
code_to_name = {v: k.upper() for k, v in name_to_code.items()}
|
code_to_name = {v: k.upper() for k, v in name_to_code.items()}
|
||||||
csi_map = {v: name_to_code[k] for k, v in functional_encoding_overrides.items()}
|
csi_map = {v: name_to_code[k] for k, v in functional_encoding_overrides.items()}
|
||||||
letter_trailer_codes = {
|
letter_trailer_codes: Dict[str, int] = {
|
||||||
v: functional_encoding_overrides.get(k, name_to_code.get(k))
|
v: functional_encoding_overrides.get(k, name_to_code.get(k, 0))
|
||||||
for k, v in different_trailer_functionals.items() if v in 'ABCDEHFPQRSZ'}
|
for k, v in different_trailer_functionals.items() if v in 'ABCDEHFPQRSZ'}
|
||||||
text = f'functional_key_number_to_name_map = {serialize_dict(code_to_name)}'
|
text = f'functional_key_number_to_name_map = {serialize_dict(code_to_name)}'
|
||||||
text += f'\ncsi_number_to_functional_number_map = {serialize_dict(csi_map)}'
|
text += f'\ncsi_number_to_functional_number_map = {serialize_dict(csi_map)}'
|
||||||
text += f'\nletter_trailer_to_csi_number_map = {letter_trailer_codes!r}'
|
text += f'\nletter_trailer_to_csi_number_map = {letter_trailer_codes!r}'
|
||||||
text += f'\ntilde_trailers = {tilde_trailers!r}'
|
text += f'\ntilde_trailers = {tilde_trailers!r}'
|
||||||
patch_file('kitty/key_encoding.py', 'csi mapping', text, start_marker='# ', end_marker='')
|
patch_file('kitty/key_encoding.py', 'csi mapping', text, start_marker='# ', end_marker='')
|
||||||
|
text = f'var functional_key_number_to_name_map = map[int]string{serialize_go_dict(code_to_name)}\n'
|
||||||
|
text += f'\nvar csi_number_to_functional_number_map = map[int]int{serialize_go_dict(csi_map)}\n'
|
||||||
|
text += f'\nvar letter_trailer_to_csi_number_map = map[string]int{serialize_go_dict(letter_trailer_codes)}\n'
|
||||||
|
tt = ', '.join(f'{x}: true' for x in tilde_trailers)
|
||||||
|
text += '\nvar tilde_trailers = map[int]bool{' + f'{tt}' + '}\n'
|
||||||
|
patch_file('tools/tui/loop/key-encoding.go', 'csi mapping', text, start_marker='// ', end_marker='')
|
||||||
|
|
||||||
|
|
||||||
def generate_legacy_text_key_maps() -> None:
|
def generate_legacy_text_key_maps() -> None:
|
||||||
|
|||||||
104
gen-wcwidth.py
104
gen-wcwidth.py
@@ -3,17 +3,26 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from datetime import date
|
from functools import lru_cache, partial
|
||||||
from functools import partial
|
|
||||||
from html.entities import html5
|
from html.entities import html5
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
from typing import (
|
from typing import (
|
||||||
Callable, DefaultDict, Dict, FrozenSet, Generator, Iterable, List,
|
Callable,
|
||||||
Optional, Set, Tuple, Union
|
DefaultDict,
|
||||||
|
Dict,
|
||||||
|
FrozenSet,
|
||||||
|
Generator,
|
||||||
|
Iterable,
|
||||||
|
List,
|
||||||
|
Optional,
|
||||||
|
Set,
|
||||||
|
Tuple,
|
||||||
|
Union,
|
||||||
)
|
)
|
||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
|
|
||||||
@@ -44,6 +53,15 @@ def get_data(fname: str, folder: str = 'UCD') -> Iterable[str]:
|
|||||||
yield line
|
yield line
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=2)
|
||||||
|
def unicode_version() -> Tuple[int, int, int]:
|
||||||
|
for line in get_data("ReadMe.txt"):
|
||||||
|
m = re.search(r'Version\s+(\d+)\.(\d+)\.(\d+)', line)
|
||||||
|
if m is not None:
|
||||||
|
return int(m.group(1)), int(m.group(2)), int(m.group(3))
|
||||||
|
raise ValueError('Could not find Unicode Version')
|
||||||
|
|
||||||
|
|
||||||
# Map of class names to set of codepoints in class
|
# Map of class names to set of codepoints in class
|
||||||
class_maps: Dict[str, Set[int]] = {}
|
class_maps: Dict[str, Set[int]] = {}
|
||||||
all_symbols: Set[int] = set()
|
all_symbols: Set[int] = set()
|
||||||
@@ -254,9 +272,13 @@ def get_ranges(items: List[int]) -> Generator[Union[int, Tuple[int, int]], None,
|
|||||||
yield a, b
|
yield a, b
|
||||||
|
|
||||||
|
|
||||||
def write_case(spec: Union[Tuple[int, ...], int], p: Callable[..., None]) -> None:
|
def write_case(spec: Union[Tuple[int, ...], int], p: Callable[..., None], for_go: bool = False) -> None:
|
||||||
if isinstance(spec, tuple):
|
if isinstance(spec, tuple):
|
||||||
p('\t\tcase 0x{:x} ... 0x{:x}:'.format(*spec))
|
if for_go:
|
||||||
|
v = ', '.join(f'0x{x:x}' for x in range(spec[0], spec[1] + 1))
|
||||||
|
p(f'\t\tcase {v}:')
|
||||||
|
else:
|
||||||
|
p('\t\tcase 0x{:x} ... 0x{:x}:'.format(*spec))
|
||||||
else:
|
else:
|
||||||
p(f'\t\tcase 0x{spec:x}:')
|
p(f'\t\tcase 0x{spec:x}:')
|
||||||
|
|
||||||
@@ -265,8 +287,8 @@ def write_case(spec: Union[Tuple[int, ...], int], p: Callable[..., None]) -> Non
|
|||||||
def create_header(path: str, include_data_types: bool = True) -> Generator[Callable[..., None], None, None]:
|
def create_header(path: str, include_data_types: bool = True) -> Generator[Callable[..., None], None, None]:
|
||||||
with open(path, 'w') as f:
|
with open(path, 'w') as f:
|
||||||
p = partial(print, file=f)
|
p = partial(print, file=f)
|
||||||
p('// unicode data, built from the unicode standard on:', date.today())
|
p('// Unicode data, built from the Unicode Standard', '.'.join(map(str, unicode_version())))
|
||||||
p('// see gen-wcwidth.py')
|
p(f'// Code generated by {os.path.basename(__file__)}, DO NOT EDIT.', end='\n\n')
|
||||||
if path.endswith('.h'):
|
if path.endswith('.h'):
|
||||||
p('#pragma once')
|
p('#pragma once')
|
||||||
if include_data_types:
|
if include_data_types:
|
||||||
@@ -476,7 +498,7 @@ def gen_names() -> None:
|
|||||||
|
|
||||||
# The trie
|
# The trie
|
||||||
p('typedef struct { uint32_t children_offset; uint32_t match_offset; } word_trie;\n')
|
p('typedef struct { uint32_t children_offset; uint32_t match_offset; } word_trie;\n')
|
||||||
all_trie_nodes: List['TrieNode'] = [] # noqa
|
all_trie_nodes: List['TrieNode'] = []
|
||||||
|
|
||||||
class TrieNode:
|
class TrieNode:
|
||||||
|
|
||||||
@@ -524,45 +546,69 @@ def gen_names() -> None:
|
|||||||
|
|
||||||
def gen_wcwidth() -> None:
|
def gen_wcwidth() -> None:
|
||||||
seen: Set[int] = set()
|
seen: Set[int] = set()
|
||||||
|
non_printing = class_maps['Cc'] | class_maps['Cf'] | class_maps['Cs']
|
||||||
|
|
||||||
def add(p: Callable[..., None], comment: str, chars_: Union[Set[int], FrozenSet[int]], ret: int) -> None:
|
def add(p: Callable[..., None], comment: str, chars_: Union[Set[int], FrozenSet[int]], ret: int, for_go: bool = False) -> None:
|
||||||
chars = chars_ - seen
|
chars = chars_ - seen
|
||||||
seen.update(chars)
|
seen.update(chars)
|
||||||
p(f'\t\t// {comment} ({len(chars)} codepoints)' + ' {{' '{')
|
p(f'\t\t// {comment} ({len(chars)} codepoints)' + ' {{' '{')
|
||||||
for spec in get_ranges(list(chars)):
|
for spec in get_ranges(list(chars)):
|
||||||
write_case(spec, p)
|
write_case(spec, p, for_go)
|
||||||
p(f'\t\t\treturn {ret};')
|
p(f'\t\t\treturn {ret};')
|
||||||
p('\t\t// }}}\n')
|
p('\t\t// }}}\n')
|
||||||
|
|
||||||
with create_header('kitty/wcwidth-std.h') as p:
|
def add_all(p: Callable[..., None], for_go: bool = False) -> None:
|
||||||
p('static inline int\nwcwidth_std(int32_t code) {')
|
seen.clear()
|
||||||
p('\tif (LIKELY(0x20 <= code && code <= 0x7e)) return 1;')
|
add(p, 'Flags', flag_codepoints, 2, for_go)
|
||||||
p('\tswitch(code) {')
|
add(p, 'Marks', marks | {0}, 0, for_go)
|
||||||
|
add(p, 'Non-printing characters', non_printing, -1, for_go)
|
||||||
|
add(p, 'Private use', class_maps['Co'], -3, for_go)
|
||||||
|
add(p, 'Text Presentation', narrow_emoji, 1, for_go)
|
||||||
|
add(p, 'East Asian ambiguous width', ambiguous, -2, for_go)
|
||||||
|
add(p, 'East Asian double width', doublewidth, 2, for_go)
|
||||||
|
add(p, 'Emoji Presentation', wide_emoji, 2, for_go)
|
||||||
|
|
||||||
non_printing = class_maps['Cc'] | class_maps['Cf'] | class_maps['Cs']
|
add(p, 'Not assigned in the unicode character database', not_assigned, -4, for_go)
|
||||||
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', narrow_emoji, 1)
|
|
||||||
add(p, 'East Asian ambiguous width', ambiguous, -2)
|
|
||||||
add(p, 'East Asian double width', doublewidth, 2)
|
|
||||||
add(p, 'Emoji Presentation', wide_emoji, 2)
|
|
||||||
|
|
||||||
add(p, 'Not assigned in the unicode character database', not_assigned, -4)
|
p('\t\tdefault:\n\t\t\treturn 1;')
|
||||||
|
|
||||||
p('\t\tdefault: return 1;')
|
|
||||||
p('\t}')
|
p('\t}')
|
||||||
p('\treturn 1;\n}')
|
if for_go:
|
||||||
|
p('\t}')
|
||||||
|
else:
|
||||||
|
p('\treturn 1;\n}')
|
||||||
|
|
||||||
|
with create_header('kitty/wcwidth-std.h') as p, open('tools/wcswidth/std.go', 'w') as gof:
|
||||||
|
gop = partial(print, file=gof)
|
||||||
|
gop('package wcswidth\n\n')
|
||||||
|
gop('func Runewidth(code rune) int {')
|
||||||
|
p('static inline int\nwcwidth_std(int32_t code) {')
|
||||||
|
p('\tif (LIKELY(0x20 <= code && code <= 0x7e)) { return 1; }')
|
||||||
|
p('\tswitch(code) {')
|
||||||
|
gop('\tswitch(code) {')
|
||||||
|
add_all(p)
|
||||||
|
add_all(gop, True)
|
||||||
|
|
||||||
p('static inline bool\nis_emoji_presentation_base(uint32_t code) {')
|
p('static inline bool\nis_emoji_presentation_base(uint32_t code) {')
|
||||||
|
gop('func IsEmojiPresentationBase(code rune) bool {')
|
||||||
p('\tswitch(code) {')
|
p('\tswitch(code) {')
|
||||||
|
gop('\tswitch(code) {')
|
||||||
for spec in get_ranges(list(emoji_presentation_bases)):
|
for spec in get_ranges(list(emoji_presentation_bases)):
|
||||||
write_case(spec, p)
|
write_case(spec, p)
|
||||||
|
write_case(spec, gop, for_go=True)
|
||||||
p('\t\t\treturn true;')
|
p('\t\t\treturn true;')
|
||||||
|
gop('\t\t\treturn true;')
|
||||||
p('\t\tdefault: return false;')
|
p('\t\tdefault: return false;')
|
||||||
p('\t}')
|
p('\t}')
|
||||||
p('\treturn 1;\n}')
|
gop('\t\tdefault:\n\t\t\treturn false')
|
||||||
|
gop('\t}')
|
||||||
|
p('\treturn true;\n}')
|
||||||
|
gop('\n}')
|
||||||
|
uv = unicode_version()
|
||||||
|
p(f'#define UNICODE_MAJOR_VERSION {uv[0]}')
|
||||||
|
p(f'#define UNICODE_MINOR_VERSION {uv[1]}')
|
||||||
|
p(f'#define UNICODE_PATCH_VERSION {uv[2]}')
|
||||||
|
gop('var UnicodeDatabaseVersion [3]int = [3]int{' f'{uv[0]}, {uv[1]}, {uv[2]}' + '}')
|
||||||
|
subprocess.check_call(['gofmt', '-w', '-s', gof.name])
|
||||||
|
|
||||||
|
|
||||||
parse_ucd()
|
parse_ucd()
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import shlex
|
|||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
cmdline = (
|
cmdline = (
|
||||||
'glad --out-path {dest} --api gl:core=3.3 '
|
'glad --out-path {dest} --api gl:core=3.3 '
|
||||||
' --extensions GL_ARB_texture_storage,GL_ARB_copy_image,GL_ARB_multisample,GL_ARB_robustness,GL_KHR_debug '
|
' --extensions GL_ARB_texture_storage,GL_ARB_copy_image,GL_ARB_multisample,GL_ARB_robustness,GL_KHR_debug '
|
||||||
|
|||||||
@@ -770,6 +770,11 @@ is_useful_apple_global_shortcut(int sc) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
is_apple_jis_layout_function_key(NSEvent *event) {
|
||||||
|
return [event keyCode] == 0x66 /* kVK_JIS_Eisu */ || [event keyCode] == 0x68 /* kVK_JIS_Kana */;
|
||||||
|
}
|
||||||
|
|
||||||
GLFWAPI GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback) {
|
GLFWAPI GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback) {
|
||||||
GLFWapplicationshouldhandlereopenfun previous = handle_reopen_callback;
|
GLFWapplicationshouldhandlereopenfun previous = handle_reopen_callback;
|
||||||
handle_reopen_callback = callback;
|
handle_reopen_callback = callback;
|
||||||
@@ -837,6 +842,14 @@ int _glfwPlatformInit(void)
|
|||||||
last_keydown_shortcut_event.timestamp = [event timestamp];
|
last_keydown_shortcut_event.timestamp = [event timestamp];
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
|
// check for JIS keyboard layout function keys
|
||||||
|
if (is_apple_jis_layout_function_key(event)) {
|
||||||
|
debug_key("keyDown triggerred JIS layout function key ignoring\n");
|
||||||
|
last_keydown_shortcut_event.virtual_key_code = [event keyCode];
|
||||||
|
last_keydown_shortcut_event.input_source_switch_modifiers = 0;
|
||||||
|
last_keydown_shortcut_event.timestamp = [event timestamp];
|
||||||
|
return event;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
last_keydown_shortcut_event.virtual_key_code = 0xffff;
|
last_keydown_shortcut_event.virtual_key_code = 0xffff;
|
||||||
NSWindow *kw = [NSApp keyWindow];
|
NSWindow *kw = [NSApp keyWindow];
|
||||||
@@ -979,8 +992,6 @@ void _glfwPlatformTerminate(void)
|
|||||||
_glfw.ns.appleSettings = nil;
|
_glfw.ns.appleSettings = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
free(_glfw.ns.clipboardString);
|
|
||||||
|
|
||||||
_glfwTerminateNSGL();
|
_glfwTerminateNSGL();
|
||||||
if (global_shortcuts != nil) { [global_shortcuts release]; global_shortcuts = nil; }
|
if (global_shortcuts != nil) { [global_shortcuts release]; global_shortcuts = nil; }
|
||||||
|
|
||||||
|
|||||||
1
glfw/cocoa_platform.h
vendored
1
glfw/cocoa_platform.h
vendored
@@ -183,7 +183,6 @@ typedef struct _GLFWlibraryNS
|
|||||||
|
|
||||||
char keyName[64];
|
char keyName[64];
|
||||||
char text[256];
|
char text[256];
|
||||||
char* clipboardString;
|
|
||||||
CGPoint cascadePoint;
|
CGPoint cascadePoint;
|
||||||
// Where to place the cursor when re-enabled
|
// Where to place the cursor when re-enabled
|
||||||
double restoreCursorPosX, restoreCursorPosY;
|
double restoreCursorPosX, restoreCursorPosY;
|
||||||
|
|||||||
@@ -29,6 +29,9 @@
|
|||||||
#include "internal.h"
|
#include "internal.h"
|
||||||
#include "../kitty/monotonic.h"
|
#include "../kitty/monotonic.h"
|
||||||
|
|
||||||
|
#include <Availability.h>
|
||||||
|
#import <CoreServices/CoreServices.h>
|
||||||
|
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
|
||||||
#include <float.h>
|
#include <float.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
@@ -1540,6 +1543,102 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
|
|||||||
debug_key("\n\tdoCommandBySelector: (%s)\n", [NSStringFromSelector(selector) UTF8String]);
|
debug_key("\n\tdoCommandBySelector: (%s)\n", [NSStringFromSelector(selector) UTF8String]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (BOOL)isAccessibilityElement
|
||||||
|
{
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)isAccessibilitySelectorAllowed:(SEL)selector
|
||||||
|
{
|
||||||
|
if (selector == @selector(accessibilityRole) || selector == @selector(accessibilitySelectedText)) return YES;
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if (TARGET_OS_OSX && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101400)
|
||||||
|
|
||||||
|
- (NSAccessibilityRole)accessibilityRole
|
||||||
|
{
|
||||||
|
return NSAccessibilityTextAreaRole;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
- (NSString *)accessibilitySelectedText
|
||||||
|
{
|
||||||
|
NSString *text = nil;
|
||||||
|
if (_glfw.callbacks.get_current_selection) {
|
||||||
|
char *s = _glfw.callbacks.get_current_selection();
|
||||||
|
if (s) {
|
||||||
|
text = [NSString stringWithUTF8String:s];
|
||||||
|
free(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/SysServices/Articles/using.html>
|
||||||
|
|
||||||
|
// Support services receiving "public.utf8-plain-text" and "NSStringPboardType"
|
||||||
|
- (id)validRequestorForSendType:(NSString *)sendType returnType:(NSString *)returnType
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
(!sendType || [sendType isEqual:NSPasteboardTypeString] || [sendType isEqual:@"NSStringPboardType"]) &&
|
||||||
|
(!returnType || [returnType isEqual:NSPasteboardTypeString] || [returnType isEqual:@"NSStringPboardType"])
|
||||||
|
) {
|
||||||
|
if (_glfw.callbacks.has_current_selection && _glfw.callbacks.has_current_selection()) return self;
|
||||||
|
}
|
||||||
|
return [super validRequestorForSendType:sendType returnType:returnType];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selected text as input to be sent to Services
|
||||||
|
- (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard types:(NSArray *)types
|
||||||
|
{
|
||||||
|
if (!_glfw.callbacks.get_current_selection) return NO;
|
||||||
|
char *text = _glfw.callbacks.get_current_selection();
|
||||||
|
if (!text) return NO;
|
||||||
|
BOOL ans = NO;
|
||||||
|
if (text[0]) {
|
||||||
|
if ([types containsObject:NSPasteboardTypeString] == YES) {
|
||||||
|
[pboard declareTypes:@[NSPasteboardTypeString] owner:self];
|
||||||
|
ans = [pboard setString:@(text) forType:NSPasteboardTypeString];
|
||||||
|
} else if ([types containsObject:@"NSStringPboardType"] == YES) {
|
||||||
|
[pboard declareTypes:@[@"NSStringPboardType"] owner:self];
|
||||||
|
ans = [pboard setString:@(text) forType:@"NSStringPboardType"];
|
||||||
|
}
|
||||||
|
free(text);
|
||||||
|
}
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Service output to be handled
|
||||||
|
- (BOOL)readSelectionFromPasteboard:(NSPasteboard *)pboard
|
||||||
|
{
|
||||||
|
NSString* text = nil;
|
||||||
|
NSArray *types = [pboard types];
|
||||||
|
if ([types containsObject:NSPasteboardTypeString] == YES) {
|
||||||
|
text = [pboard stringForType:NSPasteboardTypeString]; // public.utf8-plain-text
|
||||||
|
} else if ([types containsObject:@"NSStringPboardType"] == YES) {
|
||||||
|
text = [pboard stringForType:@"NSStringPboardType"]; // for older services (need re-encode?)
|
||||||
|
} else {
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
if (text && [text length] > 0) {
|
||||||
|
// Terminal.app inserts the output, do the same
|
||||||
|
const char *utf8 = polymorphic_string_as_utf8(text);
|
||||||
|
if ([self hasMarkedText]) {
|
||||||
|
[self unmarkText];
|
||||||
|
debug_key("Clearing pre-edit because insertText called from readSelectionFromPasteboard\n");
|
||||||
|
GLFWkeyevent glfw_keyevent = {.ime_state = GLFW_IME_PREEDIT_CHANGED};
|
||||||
|
_glfwInputKeyboard(window, &glfw_keyevent);
|
||||||
|
}
|
||||||
|
debug_key("Sending text received in readSelectionFromPasteboard as key event\n");
|
||||||
|
GLFWkeyevent glfw_keyevent = {.text=utf8, .ime_state=GLFW_IME_COMMIT_TEXT};
|
||||||
|
_glfwInputKeyboard(window, &glfw_keyevent);
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
@@ -2472,48 +2571,150 @@ bool _glfwPlatformToggleFullscreen(_GLFWwindow* w, unsigned int flags) {
|
|||||||
return made_fullscreen;
|
return made_fullscreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformSetClipboardString(const char* string)
|
// Clipboard {{{
|
||||||
{
|
|
||||||
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
#define UTI_ROUNDTRIP_PREFIX @"uti-is-typical-apple-nih."
|
||||||
[pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil];
|
|
||||||
[pasteboard setString:@(string) forType:NSPasteboardTypeString];
|
static NSString*
|
||||||
|
mime_to_uti(const char *mime) {
|
||||||
|
if (strcmp(mime, "text/plain") == 0) return NSPasteboardTypeString;
|
||||||
|
if (@available(macOS 11.0, *)) {
|
||||||
|
UTType *t = [UTType typeWithMIMEType:@(mime)]; // auto-released
|
||||||
|
if (t != nil && !t.dynamic) return t.identifier;
|
||||||
|
}
|
||||||
|
return [NSString stringWithFormat:@"%@%s", UTI_ROUNDTRIP_PREFIX, mime]; // auto-released
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* _glfwPlatformGetClipboardString(void)
|
static const char*
|
||||||
{
|
uti_to_mime(NSString *uti) {
|
||||||
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
if ([uti isEqualToString:NSPasteboardTypeString]) return "text/plain";
|
||||||
free(_glfw.ns.clipboardString); _glfw.ns.clipboardString = NULL;
|
if ([uti hasPrefix:UTI_ROUNDTRIP_PREFIX]) return [[uti substringFromIndex:[UTI_ROUNDTRIP_PREFIX length]] UTF8String];
|
||||||
|
if (@available(macOS 11.0, *)) {
|
||||||
|
UTType *t = [UTType typeWithIdentifier:uti]; // auto-released
|
||||||
|
if (t.preferredMIMEType != nil) return [t.preferredMIMEType UTF8String];
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
list_clipboard_mimetypes(GLFWclipboardwritedatafun write_data, void *object) {
|
||||||
|
#define w(x) { if (ok) ok = write_data(object, x, strlen(x)); }
|
||||||
|
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
||||||
|
NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES};
|
||||||
|
BOOL has_file_urls = [pasteboard canReadObjectForClasses:@[[NSURL class]] options:options];
|
||||||
|
BOOL has_strings = [pasteboard canReadObjectForClasses:@[[NSString class]] options:nil];
|
||||||
|
/* NSLog(@"has_file_urls: %d has_strings: %d", has_file_urls, has_strings); */
|
||||||
|
bool ok = true;
|
||||||
|
if (has_strings) w("text/plain");
|
||||||
|
if (has_file_urls) w("text/local-path-list");
|
||||||
|
for (NSPasteboardItem * item in pasteboard.pasteboardItems) {
|
||||||
|
for (NSPasteboardType type in item.types) {
|
||||||
|
/* NSLog(@"%@", type); */
|
||||||
|
const char *mime = uti_to_mime(type);
|
||||||
|
if (mime && mime[0] && ![@(mime) hasPrefix:@"text/plain"]) {
|
||||||
|
/* NSLog(@"ut: %@ mt: %@ tags: %@", ut, ut.preferredMIMEType, ut.tags); */
|
||||||
|
w(mime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#undef w
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
get_text_plain(GLFWclipboardwritedatafun write_data, void *object) {
|
||||||
|
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
||||||
NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES};
|
NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES};
|
||||||
NSArray* objs = [pasteboard readObjectsForClasses:@[[NSURL class], [NSString class]] options:options];
|
NSArray* objs = [pasteboard readObjectsForClasses:@[[NSURL class], [NSString class]] options:options];
|
||||||
|
bool found = false;
|
||||||
if (objs) {
|
if (objs) {
|
||||||
const NSUInteger count = [objs count];
|
const NSUInteger count = [objs count];
|
||||||
if (count) {
|
if (count) {
|
||||||
NSMutableString *path_list = [NSMutableString stringWithCapacity:4096]; // auto-released
|
NSMutableData *path_list = [NSMutableData dataWithCapacity:4096]; // auto-released
|
||||||
NSMutableString *text_list = [NSMutableString stringWithCapacity:4096]; // auto-released
|
NSMutableData *text_list = [NSMutableData dataWithCapacity:4096]; // auto-released
|
||||||
for (NSUInteger i = 0; i < count; i++) {
|
for (NSUInteger i = 0; i < count; i++) {
|
||||||
id obj = objs[i];
|
id obj = objs[i];
|
||||||
if ([obj isKindOfClass:[NSURL class]]) {
|
if ([obj isKindOfClass:[NSURL class]]) {
|
||||||
NSURL *url = (NSURL*)obj;
|
NSURL *url = (NSURL*)obj;
|
||||||
if (url.fileURL && url.fileSystemRepresentation) {
|
if (url.fileURL && url.fileSystemRepresentation) {
|
||||||
if ([path_list length] > 0) [path_list appendString:@("\n")];
|
if ([path_list length] > 0) [path_list appendBytes:"\n" length:1];
|
||||||
[path_list appendString:@(url.fileSystemRepresentation)];
|
[path_list appendBytes:url.fileSystemRepresentation length:strlen(url.fileSystemRepresentation)];
|
||||||
}
|
}
|
||||||
} else if ([obj isKindOfClass:[NSString class]]) {
|
} else if ([obj isKindOfClass:[NSString class]]) {
|
||||||
if ([text_list length] > 0) [text_list appendString:@("\n")];
|
if ([text_list length] > 0) [text_list appendBytes:"\n" length:1];
|
||||||
[text_list appendString:obj];
|
[text_list appendData:[obj dataUsingEncoding:NSUTF8StringEncoding]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const char *text = NULL;
|
const NSMutableData *text = nil;
|
||||||
if (path_list.length > 0) text = [path_list UTF8String];
|
if (path_list.length > 0) text = path_list;
|
||||||
else if (text_list.length > 0) text = [text_list UTF8String];
|
else if (text_list.length > 0) text = text_list;
|
||||||
if (text) _glfw.ns.clipboardString = _glfw_strdup(text);
|
if (text) {
|
||||||
|
found = true;
|
||||||
|
write_data(object, text.mutableBytes, text.length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!_glfw.ns.clipboardString) _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to retrieve object from pasteboard");
|
if (!found) _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to retrieve text/plain from pasteboard");
|
||||||
return _glfw.ns.clipboardString;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
_glfwPlatformGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object) {
|
||||||
|
if (clipboard_type != GLFW_CLIPBOARD) return;
|
||||||
|
if (mime_type == NULL) {
|
||||||
|
list_clipboard_mimetypes(write_data, object);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (strcmp(mime_type, "text/plain") == 0) {
|
||||||
|
get_text_plain(write_data, object);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
||||||
|
/* NSLog(@"mime: %s uti: %@", mime_type, mime_to_uti(mime_type)); */
|
||||||
|
NSPasteboardType t = [pasteboard availableTypeFromArray:@[mime_to_uti(mime_type)]];
|
||||||
|
/* NSLog(@"available type: %@", t); */
|
||||||
|
if (t != nil) {
|
||||||
|
NSData *data = [pasteboard dataForType:t]; // auto-released
|
||||||
|
/* NSLog(@"data: %@", data); */
|
||||||
|
if (data != nil && data.length > 0) {
|
||||||
|
write_data(object, data.bytes, data.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static NSMutableData*
|
||||||
|
get_clipboard_data(const _GLFWClipboardData *cd, const char *mime) {
|
||||||
|
NSMutableData *ans = [NSMutableData dataWithCapacity:8192];
|
||||||
|
if (ans == nil) return nil;
|
||||||
|
GLFWDataChunk chunk = cd->get_data(mime, NULL, cd->ctype);
|
||||||
|
void *iter = chunk.iter;
|
||||||
|
if (!iter) return ans;
|
||||||
|
while (true) {
|
||||||
|
chunk = cd->get_data(mime, iter, cd->ctype);
|
||||||
|
if (!chunk.sz) break;
|
||||||
|
[ans appendBytes:chunk.data length:chunk.sz];
|
||||||
|
if (chunk.free) chunk.free((void*)chunk.free_data);
|
||||||
|
}
|
||||||
|
cd->get_data(NULL, iter, cd->ctype);
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
_glfwPlatformSetClipboard(GLFWClipboardType t) {
|
||||||
|
if (t != GLFW_CLIPBOARD) return;
|
||||||
|
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
||||||
|
NSMutableArray<NSPasteboardType> *ptypes = [NSMutableArray arrayWithCapacity:_glfw.clipboard.num_mime_types]; // auto-released
|
||||||
|
for (size_t i = 0; i < _glfw.clipboard.num_mime_types; i++) {
|
||||||
|
[ptypes addObject:mime_to_uti(_glfw.clipboard.mime_types[i])];
|
||||||
|
}
|
||||||
|
[pasteboard declareTypes:ptypes owner:nil];
|
||||||
|
for (size_t i = 0; i < _glfw.clipboard.num_mime_types; i++) {
|
||||||
|
NSMutableData *data = get_clipboard_data(&_glfw.clipboard, _glfw.clipboard.mime_types[i]); // auto-released
|
||||||
|
/* NSLog(@"putting data: %@ for: %s with UTI: %@", data, _glfw.clipboard.mime_types[i], ptypes[i]); */
|
||||||
|
if (data != nil) [pasteboard setData:data forType:ptypes[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs)
|
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs)
|
||||||
{
|
{
|
||||||
if (_glfw.egl.ANGLE_platform_angle)
|
if (_glfw.egl.ANGLE_platform_angle)
|
||||||
|
|||||||
12
glfw/context.c
vendored
12
glfw/context.c
vendored
@@ -396,15 +396,6 @@ bool _glfwRefreshContextAttribs(_GLFWwindow* window,
|
|||||||
window->context.release = GLFW_RELEASE_BEHAVIOR_FLUSH;
|
window->context.release = GLFW_RELEASE_BEHAVIOR_FLUSH;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clearing the front buffer to black to avoid garbage pixels left over from
|
|
||||||
// previous uses of our bit of VRAM
|
|
||||||
{
|
|
||||||
PFNGLCLEARPROC glClear = (PFNGLCLEARPROC)
|
|
||||||
window->context.getProcAddress("glClear");
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
|
||||||
window->context.swapBuffers(window);
|
|
||||||
}
|
|
||||||
|
|
||||||
glfwMakeContextCurrent((GLFWwindow*) previous);
|
glfwMakeContextCurrent((GLFWwindow*) previous);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -487,6 +478,9 @@ GLFWAPI void glfwSwapBuffers(GLFWwindow* handle)
|
|||||||
}
|
}
|
||||||
|
|
||||||
window->context.swapBuffers(window);
|
window->context.swapBuffers(window);
|
||||||
|
#ifdef _GLFW_WAYLAND
|
||||||
|
_glfwWaylandAfterBufferSwap(window);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
GLFWAPI void glfwSwapInterval(int interval)
|
GLFWAPI void glfwSwapInterval(int interval)
|
||||||
|
|||||||
8
glfw/egl_context.c
vendored
8
glfw/egl_context.c
vendored
@@ -326,6 +326,7 @@ bool _glfwInitEGL(void)
|
|||||||
glfw_dlsym(_glfw.egl.SwapBuffers, _glfw.egl.handle, "eglSwapBuffers");
|
glfw_dlsym(_glfw.egl.SwapBuffers, _glfw.egl.handle, "eglSwapBuffers");
|
||||||
glfw_dlsym(_glfw.egl.SwapInterval, _glfw.egl.handle, "eglSwapInterval");
|
glfw_dlsym(_glfw.egl.SwapInterval, _glfw.egl.handle, "eglSwapInterval");
|
||||||
glfw_dlsym(_glfw.egl.QueryString, _glfw.egl.handle, "eglQueryString");
|
glfw_dlsym(_glfw.egl.QueryString, _glfw.egl.handle, "eglQueryString");
|
||||||
|
glfw_dlsym(_glfw.egl.QuerySurface, _glfw.egl.handle, "eglQuerySurface");
|
||||||
glfw_dlsym(_glfw.egl.GetProcAddress, _glfw.egl.handle, "eglGetProcAddress");
|
glfw_dlsym(_glfw.egl.GetProcAddress, _glfw.egl.handle, "eglGetProcAddress");
|
||||||
|
|
||||||
if (!_glfw.egl.GetConfigAttrib ||
|
if (!_glfw.egl.GetConfigAttrib ||
|
||||||
@@ -428,6 +429,8 @@ bool _glfwInitEGL(void)
|
|||||||
extensionSupportedEGL("EGL_KHR_get_all_proc_addresses");
|
extensionSupportedEGL("EGL_KHR_get_all_proc_addresses");
|
||||||
_glfw.egl.KHR_context_flush_control =
|
_glfw.egl.KHR_context_flush_control =
|
||||||
extensionSupportedEGL("EGL_KHR_context_flush_control");
|
extensionSupportedEGL("EGL_KHR_context_flush_control");
|
||||||
|
_glfw.egl.EXT_present_opaque =
|
||||||
|
extensionSupportedEGL("EGL_EXT_present_opaque");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -598,6 +601,11 @@ bool _glfwCreateContextEGL(_GLFWwindow* window,
|
|||||||
if (_glfw.egl.KHR_gl_colorspace)
|
if (_glfw.egl.KHR_gl_colorspace)
|
||||||
setAttrib(EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR);
|
setAttrib(EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR);
|
||||||
}
|
}
|
||||||
|
// Disabled because it prevents transparency from working on NVIDIA drivers under Wayland
|
||||||
|
// https://github.com/kovidgoyal/kitty/issues/5479
|
||||||
|
// We anyway dont use the alpha bits for anything.
|
||||||
|
/* if (_glfw.egl.EXT_present_opaque) */
|
||||||
|
/* setAttrib(EGL_PRESENT_OPAQUE_EXT, !fbconfig->transparent); */
|
||||||
|
|
||||||
setAttrib(EGL_NONE, EGL_NONE);
|
setAttrib(EGL_NONE, EGL_NONE);
|
||||||
|
|
||||||
|
|||||||
12
glfw/egl_context.h
vendored
12
glfw/egl_context.h
vendored
@@ -132,6 +132,7 @@ typedef EGLBoolean (EGLAPIENTRY * PFN_eglMakeCurrent)(EGLDisplay,EGLSurface,EGLS
|
|||||||
typedef EGLBoolean (EGLAPIENTRY * PFN_eglSwapBuffers)(EGLDisplay,EGLSurface);
|
typedef EGLBoolean (EGLAPIENTRY * PFN_eglSwapBuffers)(EGLDisplay,EGLSurface);
|
||||||
typedef EGLBoolean (EGLAPIENTRY * PFN_eglSwapInterval)(EGLDisplay,EGLint);
|
typedef EGLBoolean (EGLAPIENTRY * PFN_eglSwapInterval)(EGLDisplay,EGLint);
|
||||||
typedef const char* (EGLAPIENTRY * PFN_eglQueryString)(EGLDisplay,EGLint);
|
typedef const char* (EGLAPIENTRY * PFN_eglQueryString)(EGLDisplay,EGLint);
|
||||||
|
typedef const char* (EGLAPIENTRY * PFN_eglQuerySurface)(EGLDisplay,EGLSurface,EGLint,EGLint*);
|
||||||
typedef GLFWglproc (EGLAPIENTRY * PFN_eglGetProcAddress)(const char*);
|
typedef GLFWglproc (EGLAPIENTRY * PFN_eglGetProcAddress)(const char*);
|
||||||
#define eglGetConfigAttrib _glfw.egl.GetConfigAttrib
|
#define eglGetConfigAttrib _glfw.egl.GetConfigAttrib
|
||||||
#define eglGetConfigs _glfw.egl.GetConfigs
|
#define eglGetConfigs _glfw.egl.GetConfigs
|
||||||
@@ -149,6 +150,7 @@ typedef GLFWglproc (EGLAPIENTRY * PFN_eglGetProcAddress)(const char*);
|
|||||||
#define eglSwapBuffers _glfw.egl.SwapBuffers
|
#define eglSwapBuffers _glfw.egl.SwapBuffers
|
||||||
#define eglSwapInterval _glfw.egl.SwapInterval
|
#define eglSwapInterval _glfw.egl.SwapInterval
|
||||||
#define eglQueryString _glfw.egl.QueryString
|
#define eglQueryString _glfw.egl.QueryString
|
||||||
|
#define eglQuerySurface _glfw.egl.QuerySurface
|
||||||
#define eglGetProcAddress _glfw.egl.GetProcAddress
|
#define eglGetProcAddress _glfw.egl.GetProcAddress
|
||||||
|
|
||||||
typedef EGLDisplay (EGLAPIENTRY * PFNEGLGETPLATFORMDISPLAYEXTPROC)(EGLenum,void*,const EGLint*);
|
typedef EGLDisplay (EGLAPIENTRY * PFNEGLGETPLATFORMDISPLAYEXTPROC)(EGLenum,void*,const EGLint*);
|
||||||
@@ -160,11 +162,11 @@ typedef EGLSurface (EGLAPIENTRY * PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)(EGLD
|
|||||||
//
|
//
|
||||||
typedef struct _GLFWcontextEGL
|
typedef struct _GLFWcontextEGL
|
||||||
{
|
{
|
||||||
EGLConfig config;
|
EGLConfig config;
|
||||||
EGLContext handle;
|
EGLContext handle;
|
||||||
EGLSurface surface;
|
EGLSurface surface;
|
||||||
|
|
||||||
void* client;
|
void* client;
|
||||||
|
|
||||||
} _GLFWcontextEGL;
|
} _GLFWcontextEGL;
|
||||||
|
|
||||||
@@ -186,6 +188,7 @@ typedef struct _GLFWlibraryEGL
|
|||||||
bool EXT_platform_base;
|
bool EXT_platform_base;
|
||||||
bool EXT_platform_x11;
|
bool EXT_platform_x11;
|
||||||
bool EXT_platform_wayland;
|
bool EXT_platform_wayland;
|
||||||
|
bool EXT_present_opaque;
|
||||||
bool ANGLE_platform_angle;
|
bool ANGLE_platform_angle;
|
||||||
bool ANGLE_platform_angle_opengl;
|
bool ANGLE_platform_angle_opengl;
|
||||||
bool ANGLE_platform_angle_d3d;
|
bool ANGLE_platform_angle_d3d;
|
||||||
@@ -210,6 +213,7 @@ typedef struct _GLFWlibraryEGL
|
|||||||
PFN_eglSwapBuffers SwapBuffers;
|
PFN_eglSwapBuffers SwapBuffers;
|
||||||
PFN_eglSwapInterval SwapInterval;
|
PFN_eglSwapInterval SwapInterval;
|
||||||
PFN_eglQueryString QueryString;
|
PFN_eglQueryString QueryString;
|
||||||
|
PFN_eglQuerySurface QuerySurface;
|
||||||
PFN_eglGetProcAddress GetProcAddress;
|
PFN_eglGetProcAddress GetProcAddress;
|
||||||
|
|
||||||
PFNEGLGETPLATFORMDISPLAYEXTPROC GetPlatformDisplayEXT;
|
PFNEGLGETPLATFORMDISPLAYEXTPROC GetPlatformDisplayEXT;
|
||||||
|
|||||||
14
glfw/glfw.py
14
glfw/glfw.py
@@ -14,6 +14,10 @@ is_openbsd = 'openbsd' in _plat
|
|||||||
base = os.path.dirname(os.path.abspath(__file__))
|
base = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
def null_func() -> None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class CompileKey(NamedTuple):
|
class CompileKey(NamedTuple):
|
||||||
src: str
|
src: str
|
||||||
dest: str
|
dest: str
|
||||||
@@ -23,7 +27,7 @@ class Command(NamedTuple):
|
|||||||
desc: str
|
desc: str
|
||||||
cmd: Sequence[str]
|
cmd: Sequence[str]
|
||||||
is_newer_func: Callable[[], bool]
|
is_newer_func: Callable[[], bool]
|
||||||
on_success: Callable[[], None] = lambda: None
|
on_success: Callable[[], None] = null_func
|
||||||
key: Optional[CompileKey] = None
|
key: Optional[CompileKey] = None
|
||||||
keyfile: Optional[str] = None
|
keyfile: Optional[str] = None
|
||||||
|
|
||||||
@@ -111,7 +115,7 @@ def init_env(
|
|||||||
|
|
||||||
elif module == 'cocoa':
|
elif module == 'cocoa':
|
||||||
ans.cppflags.append('-DGL_SILENCE_DEPRECATION')
|
ans.cppflags.append('-DGL_SILENCE_DEPRECATION')
|
||||||
for f_ in 'Cocoa IOKit CoreFoundation CoreVideo'.split():
|
for f_ in 'Cocoa IOKit CoreFoundation CoreVideo UniformTypeIdentifiers'.split():
|
||||||
ans.ldpaths.extend(('-framework', f_))
|
ans.ldpaths.extend(('-framework', f_))
|
||||||
|
|
||||||
elif module == 'wayland':
|
elif module == 'wayland':
|
||||||
@@ -172,6 +176,8 @@ class Arg:
|
|||||||
while self.name.startswith('*'):
|
while self.name.startswith('*'):
|
||||||
self.name = self.name[1:]
|
self.name = self.name[1:]
|
||||||
self.type = self.type + '*'
|
self.type = self.type + '*'
|
||||||
|
if '[' in self.name:
|
||||||
|
self.type += '[' + self.name.partition('[')[-1]
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f'Arg({self.type}, {self.name})'
|
return f'Arg({self.type}, {self.name})'
|
||||||
@@ -245,11 +251,15 @@ def generate_wrappers(glfw_header: str) -> None:
|
|||||||
const char* glfwGetPrimarySelectionString(GLFWwindow* window, void)
|
const char* glfwGetPrimarySelectionString(GLFWwindow* window, void)
|
||||||
int glfwGetNativeKeyForName(const char* key_name, int case_sensitive)
|
int glfwGetNativeKeyForName(const char* key_name, int case_sensitive)
|
||||||
void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long id, GLFWwaylandframecallbackfunc callback)
|
void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long id, GLFWwaylandframecallbackfunc callback)
|
||||||
|
void glfwWaylandActivateWindow(GLFWwindow *handle, const char *activation_token)
|
||||||
|
void glfwWaylandRunWithActivationToken(GLFWwindow *handle, GLFWactivationcallback cb, void *cb_data)
|
||||||
bool glfwWaylandSetTitlebarColor(GLFWwindow *handle, uint32_t color, bool use_system_color)
|
bool glfwWaylandSetTitlebarColor(GLFWwindow *handle, uint32_t color, bool use_system_color)
|
||||||
unsigned long long glfwDBusUserNotify(const char *app_name, const char* icon, const char *summary, const char *body, \
|
unsigned long long glfwDBusUserNotify(const char *app_name, const char* icon, const char *summary, const char *body, \
|
||||||
const char *action_text, int32_t timeout, GLFWDBusnotificationcreatedfun callback, void *data)
|
const char *action_text, int32_t timeout, GLFWDBusnotificationcreatedfun callback, void *data)
|
||||||
void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler)
|
void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler)
|
||||||
int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc)
|
int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc)
|
||||||
|
void glfwSetX11WindowAsDock(int32_t x11_window_id)
|
||||||
|
void glfwSetX11WindowStrut(int32_t x11_window_id, uint32_t dimensions[12])
|
||||||
'''.splitlines():
|
'''.splitlines():
|
||||||
if line:
|
if line:
|
||||||
functions.append(Function(line.strip(), check_fail=False))
|
functions.append(Function(line.strip(), check_fail=False))
|
||||||
|
|||||||
79
glfw/glfw3.h
vendored
79
glfw/glfw3.h
vendored
@@ -512,6 +512,7 @@ typedef enum {
|
|||||||
* GLFW_LOCK_KEY_MODS input mode is set.
|
* GLFW_LOCK_KEY_MODS input mode is set.
|
||||||
*/
|
*/
|
||||||
#define GLFW_MOD_NUM_LOCK 0x0080
|
#define GLFW_MOD_NUM_LOCK 0x0080
|
||||||
|
#define GLFW_MOD_LAST GLFW_MOD_NUM_LOCK
|
||||||
#define GLFW_LOCK_MASK (GLFW_MOD_NUM_LOCK | GLFW_MOD_CAPS_LOCK)
|
#define GLFW_LOCK_MASK (GLFW_MOD_NUM_LOCK | GLFW_MOD_CAPS_LOCK)
|
||||||
|
|
||||||
/*! @} */
|
/*! @} */
|
||||||
@@ -1715,7 +1716,22 @@ typedef void (* GLFWjoystickfun)(int,int);
|
|||||||
|
|
||||||
typedef void (* GLFWuserdatafun)(unsigned long long, void*);
|
typedef void (* GLFWuserdatafun)(unsigned long long, void*);
|
||||||
typedef void (* GLFWtickcallback)(void*);
|
typedef void (* GLFWtickcallback)(void*);
|
||||||
|
typedef void (* GLFWactivationcallback)(GLFWwindow *window, const char *token, void *data);
|
||||||
typedef bool (* GLFWdrawtextfun)(GLFWwindow *window, const char *text, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin);
|
typedef bool (* GLFWdrawtextfun)(GLFWwindow *window, const char *text, uint32_t fg, uint32_t bg, uint8_t *output_buf, size_t width, size_t height, float x_offset, float y_offset, size_t right_margin);
|
||||||
|
typedef char* (* GLFWcurrentselectionfun)(void);
|
||||||
|
typedef bool (* GLFWhascurrentselectionfun)(void);
|
||||||
|
typedef void (* GLFWclipboarddatafreefun)(void* data);
|
||||||
|
typedef struct GLFWDataChunk {
|
||||||
|
const char *data;
|
||||||
|
size_t sz;
|
||||||
|
GLFWclipboarddatafreefun free;
|
||||||
|
void *iter, *free_data;
|
||||||
|
} GLFWDataChunk;
|
||||||
|
typedef enum {
|
||||||
|
GLFW_CLIPBOARD, GLFW_PRIMARY_SELECTION
|
||||||
|
} GLFWClipboardType;
|
||||||
|
typedef GLFWDataChunk (* GLFWclipboarditerfun)(const char *mime_type, void *iter, GLFWClipboardType ctype);
|
||||||
|
typedef bool (* GLFWclipboardwritedatafun)(void *object, const char *data, size_t sz);
|
||||||
|
|
||||||
/*! @brief Video mode type.
|
/*! @brief Video mode type.
|
||||||
*
|
*
|
||||||
@@ -1873,6 +1889,8 @@ GLFWAPI unsigned long long glfwAddTimer(monotonic_t interval, bool repeats, GLFW
|
|||||||
GLFWAPI void glfwUpdateTimer(unsigned long long timer_id, monotonic_t interval, bool enabled);
|
GLFWAPI void glfwUpdateTimer(unsigned long long timer_id, monotonic_t interval, bool enabled);
|
||||||
GLFWAPI void glfwRemoveTimer(unsigned long long);
|
GLFWAPI void glfwRemoveTimer(unsigned long long);
|
||||||
GLFWAPI GLFWdrawtextfun glfwSetDrawTextFunction(GLFWdrawtextfun function);
|
GLFWAPI GLFWdrawtextfun glfwSetDrawTextFunction(GLFWdrawtextfun function);
|
||||||
|
GLFWAPI GLFWcurrentselectionfun glfwSetCurrentSelectionCallback(GLFWcurrentselectionfun callback);
|
||||||
|
GLFWAPI GLFWhascurrentselectionfun glfwSetHasCurrentSelectionCallback(GLFWhascurrentselectionfun callback);
|
||||||
|
|
||||||
/*! @brief Terminates the GLFW library.
|
/*! @brief Terminates the GLFW library.
|
||||||
*
|
*
|
||||||
@@ -2768,6 +2786,7 @@ GLFWAPI void glfwWindowHintString(int hint, const char* value);
|
|||||||
GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height, const char* title, GLFWmonitor* monitor, GLFWwindow* share);
|
GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height, const char* title, GLFWmonitor* monitor, GLFWwindow* share);
|
||||||
GLFWAPI bool glfwToggleFullscreen(GLFWwindow *window, unsigned int flags);
|
GLFWAPI bool glfwToggleFullscreen(GLFWwindow *window, unsigned int flags);
|
||||||
GLFWAPI bool glfwIsFullscreen(GLFWwindow *window, unsigned int flags);
|
GLFWAPI bool glfwIsFullscreen(GLFWwindow *window, unsigned int flags);
|
||||||
|
GLFWAPI bool glfwAreSwapsAllowed(const GLFWwindow* window);
|
||||||
|
|
||||||
/*! @brief Destroys the specified window and its context.
|
/*! @brief Destroys the specified window and its context.
|
||||||
*
|
*
|
||||||
@@ -5216,64 +5235,8 @@ GLFWAPI const char* glfwGetGamepadName(int jid);
|
|||||||
*/
|
*/
|
||||||
GLFWAPI int glfwGetGamepadState(int jid, GLFWgamepadstate* state);
|
GLFWAPI int glfwGetGamepadState(int jid, GLFWgamepadstate* state);
|
||||||
|
|
||||||
/*! @brief Sets the clipboard to the specified string.
|
GLFWAPI void glfwSetClipboardDataTypes(GLFWClipboardType clipboard_type, const char* const *mime_types, size_t num_mime_types, GLFWclipboarditerfun get_iter);
|
||||||
*
|
GLFWAPI void glfwGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object);
|
||||||
* This function sets the system clipboard to the specified, UTF-8 encoded
|
|
||||||
* string.
|
|
||||||
*
|
|
||||||
* @param[in] window Deprecated. Any valid window or `NULL`.
|
|
||||||
* @param[in] string A UTF-8 encoded string.
|
|
||||||
*
|
|
||||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
|
||||||
* GLFW_PLATFORM_ERROR.
|
|
||||||
*
|
|
||||||
* @remark @wayland Clipboard is currently unimplemented.
|
|
||||||
*
|
|
||||||
* @pointer_lifetime The specified string is copied before this function
|
|
||||||
* returns.
|
|
||||||
*
|
|
||||||
* @thread_safety This function must only be called from the main thread.
|
|
||||||
*
|
|
||||||
* @sa @ref clipboard
|
|
||||||
* @sa @ref glfwGetClipboardString
|
|
||||||
*
|
|
||||||
* @since Added in version 3.0.
|
|
||||||
*
|
|
||||||
* @ingroup input
|
|
||||||
*/
|
|
||||||
GLFWAPI void glfwSetClipboardString(GLFWwindow* window, const char* string);
|
|
||||||
|
|
||||||
/*! @brief Returns the contents of the clipboard as a string.
|
|
||||||
*
|
|
||||||
* This function returns the contents of the system clipboard, if it contains
|
|
||||||
* or is convertible to a UTF-8 encoded string. If the clipboard is empty or
|
|
||||||
* if its contents cannot be converted, `NULL` is returned and a @ref
|
|
||||||
* GLFW_FORMAT_UNAVAILABLE error is generated.
|
|
||||||
*
|
|
||||||
* @param[in] window Deprecated. Any valid window or `NULL`.
|
|
||||||
* @return The contents of the clipboard as a UTF-8 encoded string, or `NULL`
|
|
||||||
* if an [error](@ref error_handling) occurred.
|
|
||||||
*
|
|
||||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
|
||||||
* GLFW_PLATFORM_ERROR.
|
|
||||||
*
|
|
||||||
* @remark @wayland Clipboard is currently unimplemented.
|
|
||||||
*
|
|
||||||
* @pointer_lifetime The returned string is allocated and freed by GLFW. You
|
|
||||||
* should not free it yourself. It is valid until the next call to @ref
|
|
||||||
* glfwGetClipboardString or @ref glfwSetClipboardString, or until the library
|
|
||||||
* is terminated.
|
|
||||||
*
|
|
||||||
* @thread_safety This function must only be called from the main thread.
|
|
||||||
*
|
|
||||||
* @sa @ref clipboard
|
|
||||||
* @sa @ref glfwSetClipboardString
|
|
||||||
*
|
|
||||||
* @since Added in version 3.0.
|
|
||||||
*
|
|
||||||
* @ingroup input
|
|
||||||
*/
|
|
||||||
GLFWAPI const char* glfwGetClipboardString(GLFWwindow* window);
|
|
||||||
|
|
||||||
/*! @brief Returns the GLFW time.
|
/*! @brief Returns the GLFW time.
|
||||||
*
|
*
|
||||||
|
|||||||
44
glfw/ibus_glfw.c
vendored
44
glfw/ibus_glfw.c
vendored
@@ -283,29 +283,35 @@ static const char*
|
|||||||
get_ibus_address_file_name(void) {
|
get_ibus_address_file_name(void) {
|
||||||
const char *addr;
|
const char *addr;
|
||||||
static char ans[PATH_MAX];
|
static char ans[PATH_MAX];
|
||||||
|
static char display[64] = {0};
|
||||||
addr = getenv("IBUS_ADDRESS");
|
addr = getenv("IBUS_ADDRESS");
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
if (addr && addr[0]) {
|
if (addr && addr[0]) {
|
||||||
memcpy(ans, addr, GLFW_MIN(strlen(addr), sizeof(ans)));
|
memcpy(ans, addr, GLFW_MIN(strlen(addr), sizeof(ans)));
|
||||||
return ans;
|
return ans;
|
||||||
}
|
}
|
||||||
|
const char* disp_num = NULL;
|
||||||
const char *de = getenv("DISPLAY");
|
const char *host = "unix";
|
||||||
if (!de || !de[0]) de = ":0.0";
|
// See https://github.com/ibus/ibus/commit/8ce25208c3f4adfd290a032c6aa739d2b7580eb1 for why we need this dance.
|
||||||
char *display = _glfw_strdup(de);
|
const char *de = getenv("WAYLAND_DISPLAY");
|
||||||
const char *host = display;
|
if (de) {
|
||||||
char *disp_num = strrchr(display, ':');
|
disp_num = de;
|
||||||
char *screen_num = strrchr(display, '.');
|
} else {
|
||||||
|
const char *de = getenv("DISPLAY");
|
||||||
if (!disp_num) {
|
if (!de || !de[0]) de = ":0.0";
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Could not get IBUS address file name as DISPLAY env var has no colon");
|
strncpy(display, de, sizeof(display) - 1);
|
||||||
free(display);
|
char *dnum = strrchr(display, ':');
|
||||||
return NULL;
|
if (!dnum) {
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR, "Could not get IBUS address file name as DISPLAY env var has no colon");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
char *screen_num = strrchr(display, '.');
|
||||||
|
*dnum = 0;
|
||||||
|
dnum++;
|
||||||
|
if (screen_num) *screen_num = 0;
|
||||||
|
if (*display) host = display;
|
||||||
|
disp_num = dnum;
|
||||||
}
|
}
|
||||||
*disp_num = 0;
|
|
||||||
disp_num++;
|
|
||||||
if (screen_num) *screen_num = 0;
|
|
||||||
if (!*host) host = "unix";
|
|
||||||
|
|
||||||
memset(ans, 0, sizeof(ans));
|
memset(ans, 0, sizeof(ans));
|
||||||
const char *conf_env = getenv("XDG_CONFIG_HOME");
|
const char *conf_env = getenv("XDG_CONFIG_HOME");
|
||||||
@@ -315,7 +321,6 @@ get_ibus_address_file_name(void) {
|
|||||||
conf_env = getenv("HOME");
|
conf_env = getenv("HOME");
|
||||||
if (!conf_env || !conf_env[0]) {
|
if (!conf_env || !conf_env[0]) {
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Could not get IBUS address file name as no HOME env var is set");
|
_glfwInputError(GLFW_PLATFORM_ERROR, "Could not get IBUS address file name as no HOME env var is set");
|
||||||
free(display);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
offset = snprintf(ans, sizeof(ans), "%s/.config", conf_env);
|
offset = snprintf(ans, sizeof(ans), "%s/.config", conf_env);
|
||||||
@@ -323,7 +328,6 @@ get_ibus_address_file_name(void) {
|
|||||||
char *key = dbus_get_local_machine_id();
|
char *key = dbus_get_local_machine_id();
|
||||||
snprintf(ans + offset, sizeof(ans) - offset, "/ibus/bus/%s-%s-%s", key, host, disp_num);
|
snprintf(ans + offset, sizeof(ans) - offset, "/ibus/bus/%s-%s-%s", key, host, disp_num);
|
||||||
dbus_free(key);
|
dbus_free(key);
|
||||||
free(display);
|
|
||||||
return ans;
|
return ans;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,12 +387,12 @@ input_context_created(DBusMessage *msg, const char* errmsg, void *data) {
|
|||||||
enum Capabilities caps = IBUS_CAP_FOCUS | IBUS_CAP_PREEDIT_TEXT;
|
enum Capabilities caps = IBUS_CAP_FOCUS | IBUS_CAP_PREEDIT_TEXT;
|
||||||
if (!glfw_dbus_call_method_no_reply(ibus->conn, IBUS_SERVICE, ibus->input_ctx_path, IBUS_INPUT_INTERFACE, "SetCapabilities", DBUS_TYPE_UINT32, &caps, DBUS_TYPE_INVALID)) return;
|
if (!glfw_dbus_call_method_no_reply(ibus->conn, IBUS_SERVICE, ibus->input_ctx_path, IBUS_INPUT_INTERFACE, "SetCapabilities", DBUS_TYPE_UINT32, &caps, DBUS_TYPE_INVALID)) return;
|
||||||
ibus->ok = true;
|
ibus->ok = true;
|
||||||
glfw_ibus_set_focused(ibus, false);
|
glfw_ibus_set_focused(ibus, _glfwFocusedWindow() != NULL);
|
||||||
glfw_ibus_set_cursor_geometry(ibus, 0, 0, 0, 0);
|
glfw_ibus_set_cursor_geometry(ibus, 0, 0, 0, 0);
|
||||||
debug("Connected to IBUS daemon for IME input management\n");
|
debug("Connected to IBUS daemon for IME input management\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
static bool
|
||||||
setup_connection(_GLFWIBUSData *ibus) {
|
setup_connection(_GLFWIBUSData *ibus) {
|
||||||
const char *client_name = "GLFW_Application";
|
const char *client_name = "GLFW_Application";
|
||||||
const char *address_file_name = get_ibus_address_file_name();
|
const char *address_file_name = get_ibus_address_file_name();
|
||||||
|
|||||||
16
glfw/init.c
vendored
16
glfw/init.c
vendored
@@ -68,6 +68,8 @@ static void terminate(void)
|
|||||||
int i;
|
int i;
|
||||||
|
|
||||||
memset(&_glfw.callbacks, 0, sizeof(_glfw.callbacks));
|
memset(&_glfw.callbacks, 0, sizeof(_glfw.callbacks));
|
||||||
|
_glfw_free_clipboard_data(&_glfw.clipboard);
|
||||||
|
_glfw_free_clipboard_data(&_glfw.primary);
|
||||||
|
|
||||||
while (_glfw.windowListHead)
|
while (_glfw.windowListHead)
|
||||||
glfwDestroyWindow((GLFWwindow*) _glfw.windowListHead);
|
glfwDestroyWindow((GLFWwindow*) _glfw.windowListHead);
|
||||||
@@ -386,3 +388,17 @@ GLFWAPI GLFWdrawtextfun glfwSetDrawTextFunction(GLFWdrawtextfun cbfun)
|
|||||||
_GLFW_SWAP_POINTERS(_glfw.callbacks.draw_text, cbfun);
|
_GLFW_SWAP_POINTERS(_glfw.callbacks.draw_text, cbfun);
|
||||||
return cbfun;
|
return cbfun;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GLFWAPI GLFWcurrentselectionfun glfwSetCurrentSelectionCallback(GLFWcurrentselectionfun cbfun)
|
||||||
|
{
|
||||||
|
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||||
|
_GLFW_SWAP_POINTERS(_glfw.callbacks.get_current_selection, cbfun);
|
||||||
|
return cbfun;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLFWAPI GLFWhascurrentselectionfun glfwSetHasCurrentSelectionCallback(GLFWhascurrentselectionfun cbfun)
|
||||||
|
{
|
||||||
|
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||||
|
_GLFW_SWAP_POINTERS(_glfw.callbacks.has_current_selection, cbfun);
|
||||||
|
return cbfun;
|
||||||
|
}
|
||||||
|
|||||||
51
glfw/input.c
vendored
51
glfw/input.c
vendored
@@ -1526,36 +1526,41 @@ GLFWAPI int glfwGetGamepadState(int jid, GLFWgamepadstate* state)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
GLFWAPI void glfwSetClipboardString(GLFWwindow* handle UNUSED, const char* string)
|
void _glfw_free_clipboard_data(_GLFWClipboardData *cd) {
|
||||||
{
|
if (cd->mime_types) {
|
||||||
assert(string != NULL);
|
for (size_t i = 0; i < cd->num_mime_types; i++) free((void*)cd->mime_types[i]);
|
||||||
|
free((void*)cd->mime_types);
|
||||||
|
}
|
||||||
|
memset(cd, 0, sizeof(cd[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
GLFWAPI void glfwGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object) {
|
||||||
_GLFW_REQUIRE_INIT();
|
_GLFW_REQUIRE_INIT();
|
||||||
_glfwPlatformSetClipboardString(string);
|
_glfwPlatformGetClipboard(clipboard_type, mime_type, write_data, object);
|
||||||
}
|
}
|
||||||
|
|
||||||
GLFWAPI const char* glfwGetClipboardString(GLFWwindow* handle UNUSED)
|
GLFWAPI void glfwSetClipboardDataTypes(GLFWClipboardType clipboard_type, const char* const *mime_types, size_t num_mime_types, GLFWclipboarditerfun get_data) {
|
||||||
{
|
assert(mime_types != NULL);
|
||||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
assert(get_data != NULL);
|
||||||
return _glfwPlatformGetClipboardString();
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND)
|
|
||||||
GLFWAPI void glfwSetPrimarySelectionString(GLFWwindow* handle UNUSED, const char* string)
|
|
||||||
{
|
|
||||||
assert(string != NULL);
|
|
||||||
|
|
||||||
_GLFW_REQUIRE_INIT();
|
_GLFW_REQUIRE_INIT();
|
||||||
_glfwPlatformSetPrimarySelectionString(string);
|
_GLFWClipboardData *cd = NULL;
|
||||||
|
switch(clipboard_type) {
|
||||||
|
case GLFW_CLIPBOARD: cd = &_glfw.clipboard; break;
|
||||||
|
case GLFW_PRIMARY_SELECTION: cd = &_glfw.primary; break;
|
||||||
|
}
|
||||||
|
_glfw_free_clipboard_data(cd);
|
||||||
|
cd->get_data = get_data;
|
||||||
|
cd->mime_types = calloc(num_mime_types, sizeof(char*));
|
||||||
|
cd->num_mime_types = 0;
|
||||||
|
cd->ctype = clipboard_type;
|
||||||
|
for (size_t i = 0; i < num_mime_types; i++) {
|
||||||
|
if (mime_types[i]) {
|
||||||
|
cd->mime_types[cd->num_mime_types++] = _glfw_strdup(mime_types[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_glfwPlatformSetClipboard(clipboard_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
GLFWAPI const char* glfwGetPrimarySelectionString(GLFWwindow* handle UNUSED)
|
|
||||||
{
|
|
||||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
|
||||||
return _glfwPlatformGetPrimarySelectionString();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
GLFWAPI monotonic_t glfwGetTime(void)
|
GLFWAPI monotonic_t glfwGetTime(void)
|
||||||
{
|
{
|
||||||
_GLFW_REQUIRE_INIT_OR_RETURN(0);
|
_GLFW_REQUIRE_INIT_OR_RETURN(0);
|
||||||
|
|||||||
31
glfw/internal.h
vendored
31
glfw/internal.h
vendored
@@ -34,6 +34,9 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define arraysz(x) (sizeof(x)/sizeof(x[0]))
|
#define arraysz(x) (sizeof(x)/sizeof(x[0]))
|
||||||
|
#define MAX(x, y) __extension__ ({ \
|
||||||
|
__typeof__ (x) a = (x); __typeof__ (y) b = (y); \
|
||||||
|
a > b ? a : b;})
|
||||||
|
|
||||||
#if defined(GLFW_INCLUDE_GLCOREARB) || \
|
#if defined(GLFW_INCLUDE_GLCOREARB) || \
|
||||||
defined(GLFW_INCLUDE_ES1) || \
|
defined(GLFW_INCLUDE_ES1) || \
|
||||||
@@ -52,6 +55,8 @@
|
|||||||
#define GLFW_INCLUDE_NONE
|
#define GLFW_INCLUDE_NONE
|
||||||
#include "glfw3.h"
|
#include "glfw3.h"
|
||||||
|
|
||||||
|
#define EGL_PRESENT_OPAQUE_EXT 0x31df
|
||||||
|
|
||||||
#define _GLFW_INSERT_FIRST 0
|
#define _GLFW_INSERT_FIRST 0
|
||||||
#define _GLFW_INSERT_LAST 1
|
#define _GLFW_INSERT_LAST 1
|
||||||
|
|
||||||
@@ -439,6 +444,11 @@ struct _GLFWwindow
|
|||||||
bool rawMouseMotion;
|
bool rawMouseMotion;
|
||||||
|
|
||||||
_GLFWcontext context;
|
_GLFWcontext context;
|
||||||
|
#ifdef _GLFW_WAYLAND
|
||||||
|
bool swaps_disallowed;
|
||||||
|
#else
|
||||||
|
const bool swaps_disallowed;
|
||||||
|
#endif
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
GLFWwindowposfun pos;
|
GLFWwindowposfun pos;
|
||||||
@@ -554,6 +564,13 @@ struct _GLFWmutex
|
|||||||
_GLFW_PLATFORM_MUTEX_STATE;
|
_GLFW_PLATFORM_MUTEX_STATE;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef struct _GLFWClipboardData {
|
||||||
|
const char** mime_types;
|
||||||
|
size_t num_mime_types;
|
||||||
|
GLFWclipboarditerfun get_data;
|
||||||
|
GLFWClipboardType ctype;
|
||||||
|
} _GLFWClipboardData;
|
||||||
|
|
||||||
// Library global data
|
// Library global data
|
||||||
//
|
//
|
||||||
struct _GLFWlibrary
|
struct _GLFWlibrary
|
||||||
@@ -568,6 +585,8 @@ struct _GLFWlibrary
|
|||||||
int refreshRate;
|
int refreshRate;
|
||||||
} hints;
|
} hints;
|
||||||
|
|
||||||
|
_GLFWClipboardData primary, clipboard;
|
||||||
|
|
||||||
_GLFWerror* errorListHead;
|
_GLFWerror* errorListHead;
|
||||||
_GLFWcursor* cursorListHead;
|
_GLFWcursor* cursorListHead;
|
||||||
_GLFWwindow* windowListHead;
|
_GLFWwindow* windowListHead;
|
||||||
@@ -614,6 +633,8 @@ struct _GLFWlibrary
|
|||||||
GLFWjoystickfun joystick;
|
GLFWjoystickfun joystick;
|
||||||
GLFWapplicationclosefun application_close;
|
GLFWapplicationclosefun application_close;
|
||||||
GLFWdrawtextfun draw_text;
|
GLFWdrawtextfun draw_text;
|
||||||
|
GLFWcurrentselectionfun get_current_selection;
|
||||||
|
GLFWhascurrentselectionfun has_current_selection;
|
||||||
} callbacks;
|
} callbacks;
|
||||||
|
|
||||||
|
|
||||||
@@ -666,12 +687,8 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode);
|
|||||||
bool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp);
|
bool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp);
|
||||||
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp);
|
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp);
|
||||||
|
|
||||||
void _glfwPlatformSetClipboardString(const char* string);
|
void _glfwPlatformSetClipboard(GLFWClipboardType t);
|
||||||
const char* _glfwPlatformGetClipboardString(void);
|
void _glfwPlatformGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object);
|
||||||
#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND)
|
|
||||||
void _glfwPlatformSetPrimarySelectionString(const char* string);
|
|
||||||
const char* _glfwPlatformGetPrimarySelectionString(void);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool _glfwPlatformInitJoysticks(void);
|
bool _glfwPlatformInitJoysticks(void);
|
||||||
void _glfwPlatformTerminateJoysticks(void);
|
void _glfwPlatformTerminateJoysticks(void);
|
||||||
@@ -847,3 +864,5 @@ void _glfwPlatformUpdateTimer(unsigned long long timer_id, monotonic_t interval,
|
|||||||
void _glfwPlatformRemoveTimer(unsigned long long timer_id);
|
void _glfwPlatformRemoveTimer(unsigned long long timer_id);
|
||||||
|
|
||||||
char* _glfw_strdup(const char* source);
|
char* _glfw_strdup(const char* source);
|
||||||
|
|
||||||
|
void _glfw_free_clipboard_data(_GLFWClipboardData *cd);
|
||||||
|
|||||||
4
glfw/linux_desktop_settings.c
vendored
4
glfw/linux_desktop_settings.c
vendored
@@ -83,8 +83,8 @@ process_settings_dict(DBusMessageIter *array_iter, void(process_setting)(const c
|
|||||||
|
|
||||||
HANDLER(process_desktop_settings)
|
HANDLER(process_desktop_settings)
|
||||||
cursor_theme_changed = false;
|
cursor_theme_changed = false;
|
||||||
DBusMessageIter root, array, item, settings;
|
DBusMessageIter root, array, item, settings;
|
||||||
dbus_message_iter_init(msg, &root);
|
dbus_message_iter_init(msg, &root);
|
||||||
#define die(...) { _glfwInputError(GLFW_PLATFORM_ERROR, __VA_ARGS__); return; }
|
#define die(...) { _glfwInputError(GLFW_PLATFORM_ERROR, __VA_ARGS__); return; }
|
||||||
if (dbus_message_iter_get_arg_type(&root) != DBUS_TYPE_ARRAY) die("Reply to request for desktop settings is not an array");
|
if (dbus_message_iter_get_arg_type(&root) != DBUS_TYPE_ARRAY) die("Reply to request for desktop settings is not an array");
|
||||||
dbus_message_iter_recurse(&root, &array);
|
dbus_message_iter_recurse(&root, &array);
|
||||||
|
|||||||
@@ -77,7 +77,8 @@
|
|||||||
"unstable/idle-inhibit/idle-inhibit-unstable-v1.xml",
|
"unstable/idle-inhibit/idle-inhibit-unstable-v1.xml",
|
||||||
"unstable/xdg-decoration/xdg-decoration-unstable-v1.xml",
|
"unstable/xdg-decoration/xdg-decoration-unstable-v1.xml",
|
||||||
"unstable/primary-selection/primary-selection-unstable-v1.xml",
|
"unstable/primary-selection/primary-selection-unstable-v1.xml",
|
||||||
"unstable/text-input/text-input-unstable-v3.xml"
|
"unstable/text-input/text-input-unstable-v3.xml",
|
||||||
|
"staging/xdg-activation/xdg-activation-v1.xml"
|
||||||
],
|
],
|
||||||
"sources": [
|
"sources": [
|
||||||
"wl_init.c",
|
"wl_init.c",
|
||||||
|
|||||||
6
glfw/window.c
vendored
6
glfw/window.c
vendored
@@ -71,7 +71,7 @@ void _glfwInputWindowFocus(_GLFWwindow* window, bool focused)
|
|||||||
_glfw.focusedWindowId = window->id;
|
_glfw.focusedWindowId = window->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
_GLFWwindow* _glfwFocusedWindow() {
|
_GLFWwindow* _glfwFocusedWindow(void) {
|
||||||
if (_glfw.focusedWindowId) {
|
if (_glfw.focusedWindowId) {
|
||||||
_GLFWwindow *w = _glfw.windowListHead;
|
_GLFWwindow *w = _glfw.windowListHead;
|
||||||
while (w) {
|
while (w) {
|
||||||
@@ -1067,6 +1067,10 @@ GLFWAPI bool glfwIsFullscreen(GLFWwindow* wh, unsigned int flags) {
|
|||||||
return _glfwPlatformIsFullscreen((_GLFWwindow*)wh, flags);
|
return _glfwPlatformIsFullscreen((_GLFWwindow*)wh, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GLFWAPI bool glfwAreSwapsAllowed(const GLFWwindow* wh) {
|
||||||
|
return !(((_GLFWwindow*)wh)->swaps_disallowed);
|
||||||
|
}
|
||||||
|
|
||||||
GLFWAPI void glfwSetWindowUserPointer(GLFWwindow* handle, void* pointer)
|
GLFWAPI void glfwSetWindowUserPointer(GLFWwindow* handle, void* pointer)
|
||||||
{
|
{
|
||||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||||
|
|||||||
33
glfw/wl_client_side_decorations.c
vendored
33
glfw/wl_client_side_decorations.c
vendored
@@ -31,13 +31,13 @@ static void
|
|||||||
build_blur_kernel(kernel_type *blur_kernel, const size_t size, kernel_type sigma) {
|
build_blur_kernel(kernel_type *blur_kernel, const size_t size, kernel_type sigma) {
|
||||||
// 1D Normalized Gaussian
|
// 1D Normalized Gaussian
|
||||||
const kernel_type half = size / (kernel_type)2;
|
const kernel_type half = size / (kernel_type)2;
|
||||||
kernel_type sum = 0;
|
kernel_type sum = 0;
|
||||||
for (size_t i = 0; i < size; i++) {
|
for (size_t i = 0; i < size; i++) {
|
||||||
kernel_type f = (i - half);
|
kernel_type f = (i - half);
|
||||||
blur_kernel[i] = (kernel_type)exp(- f * f / sigma);
|
blur_kernel[i] = (kernel_type)exp(- f * f / sigma);
|
||||||
sum += blur_kernel[i];
|
sum += blur_kernel[i];
|
||||||
}
|
}
|
||||||
for (size_t i = 0; i < size; i++) blur_kernel[i] /= sum;
|
for (size_t i = 0; i < size; i++) blur_kernel[i] /= sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -104,9 +104,10 @@ init_buffer_pair(_GLFWWaylandBufferPair *pair, size_t width, size_t height, unsi
|
|||||||
|
|
||||||
static bool
|
static bool
|
||||||
window_has_buffer(_GLFWwindow *window, struct wl_buffer *q) {
|
window_has_buffer(_GLFWwindow *window, struct wl_buffer *q) {
|
||||||
#define Q(which) decs.which.buffer.a == q || decs.which.buffer.b == q
|
#define Q(which) if (decs.which.buffer.a == q) { decs.which.buffer.a_needs_to_be_destroyed = false; return true; } if (decs.which.buffer.b == q) { decs.which.buffer.b_needs_to_be_destroyed = false; return true; }
|
||||||
return Q(left) || Q(top) || Q(right) || Q(bottom);
|
Q(left); Q(top); Q(right); Q(bottom);
|
||||||
#undef Q
|
#undef Q
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -122,10 +123,12 @@ static void
|
|||||||
alloc_buffer_pair(uintptr_t window_id, _GLFWWaylandBufferPair *pair, struct wl_shm_pool *pool, uint8_t *data, size_t *offset) {
|
alloc_buffer_pair(uintptr_t window_id, _GLFWWaylandBufferPair *pair, struct wl_shm_pool *pool, uint8_t *data, size_t *offset) {
|
||||||
pair->data.a = data + *offset;
|
pair->data.a = data + *offset;
|
||||||
pair->a = wl_shm_pool_create_buffer(pool, *offset, pair->width, pair->height, pair->stride, WL_SHM_FORMAT_ARGB8888);
|
pair->a = wl_shm_pool_create_buffer(pool, *offset, pair->width, pair->height, pair->stride, WL_SHM_FORMAT_ARGB8888);
|
||||||
|
pair->a_needs_to_be_destroyed = true;
|
||||||
wl_buffer_add_listener(pair->a, &handle_buffer_events, (void*)window_id);
|
wl_buffer_add_listener(pair->a, &handle_buffer_events, (void*)window_id);
|
||||||
*offset += pair->size_in_bytes;
|
*offset += pair->size_in_bytes;
|
||||||
pair->data.b = data + *offset;
|
pair->data.b = data + *offset;
|
||||||
pair->b = wl_shm_pool_create_buffer(pool, *offset, pair->width, pair->height, pair->stride, WL_SHM_FORMAT_ARGB8888);
|
pair->b = wl_shm_pool_create_buffer(pool, *offset, pair->width, pair->height, pair->stride, WL_SHM_FORMAT_ARGB8888);
|
||||||
|
pair->b_needs_to_be_destroyed = true;
|
||||||
wl_buffer_add_listener(pair->b, &handle_buffer_events, (void*)window_id);
|
wl_buffer_add_listener(pair->b, &handle_buffer_events, (void*)window_id);
|
||||||
*offset += pair->size_in_bytes;
|
*offset += pair->size_in_bytes;
|
||||||
pair->front = pair->a; pair->back = pair->b;
|
pair->front = pair->a; pair->back = pair->b;
|
||||||
@@ -331,6 +334,8 @@ free_csd_surfaces(_GLFWwindow *window) {
|
|||||||
static void
|
static void
|
||||||
free_csd_buffers(_GLFWwindow *window) {
|
free_csd_buffers(_GLFWwindow *window) {
|
||||||
#define d(which) { \
|
#define d(which) { \
|
||||||
|
if (decs.which.buffer.a_needs_to_be_destroyed && decs.which.buffer.a) wl_buffer_destroy(decs.which.buffer.a); \
|
||||||
|
if (decs.which.buffer.b_needs_to_be_destroyed && decs.which.buffer.b) wl_buffer_destroy(decs.which.buffer.b); \
|
||||||
memset(&decs.which.buffer, 0, sizeof(_GLFWWaylandBufferPair)); \
|
memset(&decs.which.buffer, 0, sizeof(_GLFWWaylandBufferPair)); \
|
||||||
}
|
}
|
||||||
d(left); d(top); d(right); d(bottom);
|
d(left); d(top); d(right); d(bottom);
|
||||||
@@ -353,9 +358,10 @@ create_csd_surfaces(_GLFWwindow *window, _GLFWWaylandCSDEdge *s) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#define damage_csd(which, xbuffer) \
|
#define damage_csd(which, xbuffer) \
|
||||||
wl_surface_attach(decs.which.surface, xbuffer, 0, 0); \
|
wl_surface_attach(decs.which.surface, (xbuffer), 0, 0); \
|
||||||
wl_surface_damage(decs.which.surface, 0, 0, decs.which.buffer.width, decs.which.buffer.height); \
|
wl_surface_damage(decs.which.surface, 0, 0, decs.which.buffer.width, decs.which.buffer.height); \
|
||||||
wl_surface_commit(decs.which.surface)
|
wl_surface_commit(decs.which.surface); \
|
||||||
|
if (decs.which.buffer.a == (xbuffer)) { decs.which.buffer.a_needs_to_be_destroyed = false; } else { decs.which.buffer.b_needs_to_be_destroyed = false; }
|
||||||
|
|
||||||
bool
|
bool
|
||||||
ensure_csd_resources(_GLFWwindow *window) {
|
ensure_csd_resources(_GLFWwindow *window) {
|
||||||
@@ -450,8 +456,5 @@ set_titlebar_color(_GLFWwindow *window, uint32_t color, bool use_system_color) {
|
|||||||
decs.use_custom_titlebar_color = use_custom_color;
|
decs.use_custom_titlebar_color = use_custom_color;
|
||||||
decs.titlebar_color = color;
|
decs.titlebar_color = color;
|
||||||
}
|
}
|
||||||
if (window->decorated && decs.top.surface) {
|
change_csd_title(window);
|
||||||
update_title_bar(window);
|
|
||||||
damage_csd(top, decs.top.buffer.front);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
47
glfw/wl_init.c
vendored
47
glfw/wl_init.c
vendored
@@ -99,7 +99,7 @@ static void pointerHandleEnter(void* data UNUSED,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
window->wl.decorations.focus = focus;
|
window->wl.decorations.focus = focus;
|
||||||
_glfw.wl.serial = serial;
|
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial;
|
||||||
_glfw.wl.pointerFocus = window;
|
_glfw.wl.pointerFocus = window;
|
||||||
|
|
||||||
window->wl.hovered = true;
|
window->wl.hovered = true;
|
||||||
@@ -144,8 +144,12 @@ static void setCursor(GLFWCursorShape shape, _GLFWwindow* window)
|
|||||||
if (!image)
|
if (!image)
|
||||||
return;
|
return;
|
||||||
if (image->width % scale || image->height % scale) {
|
if (image->width % scale || image->height % scale) {
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR, "WARNING: Cursor image size: %dx%d is not a multiple of window scale: %d. This will"
|
static uint32_t warned_width = 0, warned_height = 0;
|
||||||
" cause some compositors such as GNOME to crash. See https://github.com/kovidgoyal/kitty/issues/4878", image->width, image->height, scale);
|
if (warned_width != image->width || warned_height != image->height) {
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR, "WARNING: Cursor image size: %dx%d is not a multiple of window scale: %d. This will"
|
||||||
|
" cause some compositors such as GNOME to crash. See https://github.com/kovidgoyal/kitty/issues/4878", image->width, image->height, scale);
|
||||||
|
warned_width = image->width; warned_height = image->height;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer = wl_cursor_image_get_buffer(image);
|
buffer = wl_cursor_image_get_buffer(image);
|
||||||
@@ -307,7 +311,7 @@ static void pointerHandleButton(void* data UNUSED,
|
|||||||
if (window->wl.decorations.focus != CENTRAL_WINDOW)
|
if (window->wl.decorations.focus != CENTRAL_WINDOW)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_glfw.wl.serial = serial;
|
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial;
|
||||||
|
|
||||||
/* Makes left, right and middle 0, 1 and 2. Overall order follows evdev
|
/* Makes left, right and middle 0, 1 and 2. Overall order follows evdev
|
||||||
* codes. */
|
* codes. */
|
||||||
@@ -459,7 +463,7 @@ static void keyboardHandleEnter(void* data UNUSED,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_glfw.wl.serial = serial;
|
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial;
|
||||||
_glfw.wl.keyboardFocusId = window->id;
|
_glfw.wl.keyboardFocusId = window->id;
|
||||||
_glfwInputWindowFocus(window, true);
|
_glfwInputWindowFocus(window, true);
|
||||||
uint32_t* key;
|
uint32_t* key;
|
||||||
@@ -512,7 +516,7 @@ static void keyboardHandleKey(void* data UNUSED,
|
|||||||
return;
|
return;
|
||||||
int action = state == WL_KEYBOARD_KEY_STATE_PRESSED ? GLFW_PRESS : GLFW_RELEASE;
|
int action = state == WL_KEYBOARD_KEY_STATE_PRESSED ? GLFW_PRESS : GLFW_RELEASE;
|
||||||
|
|
||||||
_glfw.wl.serial = serial;
|
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial;
|
||||||
glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, key, action);
|
glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, key, action);
|
||||||
|
|
||||||
if (action == GLFW_PRESS && _glfw.wl.keyboardRepeatRate > 0 && glfw_xkb_should_repeat(&_glfw.wl.xkb, key))
|
if (action == GLFW_PRESS && _glfw.wl.keyboardRepeatRate > 0 && glfw_xkb_should_repeat(&_glfw.wl.xkb, key))
|
||||||
@@ -535,7 +539,7 @@ static void keyboardHandleModifiers(void* data UNUSED,
|
|||||||
uint32_t modsLocked,
|
uint32_t modsLocked,
|
||||||
uint32_t group)
|
uint32_t group)
|
||||||
{
|
{
|
||||||
_glfw.wl.serial = serial;
|
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial;
|
||||||
glfw_xkb_update_modifiers(&_glfw.wl.xkb, modsDepressed, modsLatched, modsLocked, 0, 0, group);
|
glfw_xkb_update_modifiers(&_glfw.wl.xkb, modsDepressed, modsLatched, modsLocked, 0, 0, group);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,10 +577,7 @@ static void seatHandleCapabilities(void* data UNUSED,
|
|||||||
{
|
{
|
||||||
wl_pointer_destroy(_glfw.wl.pointer);
|
wl_pointer_destroy(_glfw.wl.pointer);
|
||||||
_glfw.wl.pointer = NULL;
|
_glfw.wl.pointer = NULL;
|
||||||
if (_glfw.wl.cursorAnimationTimer) {
|
if (_glfw.wl.cursorAnimationTimer) toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0);
|
||||||
removeTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer);
|
|
||||||
_glfw.wl.cursorAnimationTimer = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !_glfw.wl.keyboard)
|
if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !_glfw.wl.keyboard)
|
||||||
@@ -589,10 +590,7 @@ static void seatHandleCapabilities(void* data UNUSED,
|
|||||||
wl_keyboard_destroy(_glfw.wl.keyboard);
|
wl_keyboard_destroy(_glfw.wl.keyboard);
|
||||||
_glfw.wl.keyboard = NULL;
|
_glfw.wl.keyboard = NULL;
|
||||||
_glfw.wl.keyboardFocusId = 0;
|
_glfw.wl.keyboardFocusId = 0;
|
||||||
if (_glfw.wl.keyRepeatInfo.keyRepeatTimer) {
|
if (_glfw.wl.keyRepeatInfo.keyRepeatTimer) toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 0);
|
||||||
removeTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer);
|
|
||||||
_glfw.wl.keyRepeatInfo.keyRepeatTimer = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -721,6 +719,9 @@ static void registryHandleGlobal(void* data UNUSED,
|
|||||||
_glfwSetupWaylandPrimarySelectionDevice();
|
_glfwSetupWaylandPrimarySelectionDevice();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (strstr(interface, "xdg_activation_v1") != 0) {
|
||||||
|
_glfw.wl.xdg_activation_v1 = wl_registry_bind(registry, name, &xdg_activation_v1_interface, 1);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -884,6 +885,14 @@ int _glfwPlatformInit(void)
|
|||||||
|
|
||||||
void _glfwPlatformTerminate(void)
|
void _glfwPlatformTerminate(void)
|
||||||
{
|
{
|
||||||
|
if (_glfw.wl.activation_requests.array) {
|
||||||
|
for (size_t i=0; i < _glfw.wl.activation_requests.sz; i++) {
|
||||||
|
glfw_wl_xdg_activation_request *r = _glfw.wl.activation_requests.array + i;
|
||||||
|
if (r->callback) r->callback(NULL, NULL, r->callback_data);
|
||||||
|
xdg_activation_token_v1_destroy(r->token);
|
||||||
|
}
|
||||||
|
free(_glfw.wl.activation_requests.array);
|
||||||
|
}
|
||||||
_glfwTerminateEGL();
|
_glfwTerminateEGL();
|
||||||
if (_glfw.wl.egl.handle)
|
if (_glfw.wl.egl.handle)
|
||||||
{
|
{
|
||||||
@@ -928,6 +937,8 @@ void _glfwPlatformTerminate(void)
|
|||||||
zwp_idle_inhibit_manager_v1_destroy(_glfw.wl.idleInhibitManager);
|
zwp_idle_inhibit_manager_v1_destroy(_glfw.wl.idleInhibitManager);
|
||||||
if (_glfw.wl.dataSourceForClipboard)
|
if (_glfw.wl.dataSourceForClipboard)
|
||||||
wl_data_source_destroy(_glfw.wl.dataSourceForClipboard);
|
wl_data_source_destroy(_glfw.wl.dataSourceForClipboard);
|
||||||
|
if (_glfw.wl.dataSourceForPrimarySelection)
|
||||||
|
zwp_primary_selection_source_v1_destroy(_glfw.wl.dataSourceForPrimarySelection);
|
||||||
for (size_t doi=0; doi < arraysz(_glfw.wl.dataOffers); doi++) {
|
for (size_t doi=0; doi < arraysz(_glfw.wl.dataOffers); doi++) {
|
||||||
if (_glfw.wl.dataOffers[doi].id) {
|
if (_glfw.wl.dataOffers[doi].id) {
|
||||||
destroy_data_offer(&_glfw.wl.dataOffers[doi]);
|
destroy_data_offer(&_glfw.wl.dataOffers[doi]);
|
||||||
@@ -941,6 +952,8 @@ void _glfwPlatformTerminate(void)
|
|||||||
zwp_primary_selection_device_v1_destroy(_glfw.wl.primarySelectionDevice);
|
zwp_primary_selection_device_v1_destroy(_glfw.wl.primarySelectionDevice);
|
||||||
if (_glfw.wl.primarySelectionDeviceManager)
|
if (_glfw.wl.primarySelectionDeviceManager)
|
||||||
zwp_primary_selection_device_manager_v1_destroy(_glfw.wl.primarySelectionDeviceManager);
|
zwp_primary_selection_device_manager_v1_destroy(_glfw.wl.primarySelectionDeviceManager);
|
||||||
|
if (_glfw.wl.xdg_activation_v1)
|
||||||
|
xdg_activation_v1_destroy(_glfw.wl.xdg_activation_v1);
|
||||||
if (_glfw.wl.registry)
|
if (_glfw.wl.registry)
|
||||||
wl_registry_destroy(_glfw.wl.registry);
|
wl_registry_destroy(_glfw.wl.registry);
|
||||||
if (_glfw.wl.display)
|
if (_glfw.wl.display)
|
||||||
@@ -949,10 +962,6 @@ void _glfwPlatformTerminate(void)
|
|||||||
wl_display_disconnect(_glfw.wl.display);
|
wl_display_disconnect(_glfw.wl.display);
|
||||||
}
|
}
|
||||||
finalizePollData(&_glfw.wl.eventLoopData);
|
finalizePollData(&_glfw.wl.eventLoopData);
|
||||||
free(_glfw.wl.clipboardString); _glfw.wl.clipboardString = NULL;
|
|
||||||
free(_glfw.wl.primarySelectionString); _glfw.wl.primarySelectionString = NULL;
|
|
||||||
free(_glfw.wl.pasteString); _glfw.wl.pasteString = NULL;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* _glfwPlatformGetVersionString(void)
|
const char* _glfwPlatformGetVersionString(void)
|
||||||
|
|||||||
36
glfw/wl_platform.h
vendored
36
glfw/wl_platform.h
vendored
@@ -58,7 +58,9 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
|
|||||||
#include "wayland-pointer-constraints-unstable-v1-client-protocol.h"
|
#include "wayland-pointer-constraints-unstable-v1-client-protocol.h"
|
||||||
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
|
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
|
||||||
#include "wayland-primary-selection-unstable-v1-client-protocol.h"
|
#include "wayland-primary-selection-unstable-v1-client-protocol.h"
|
||||||
|
#include "wayland-primary-selection-unstable-v1-client-protocol.h"
|
||||||
#include "wl_text_input.h"
|
#include "wl_text_input.h"
|
||||||
|
#include "wayland-xdg-activation-v1-client-protocol.h"
|
||||||
|
|
||||||
#define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL)
|
#define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL)
|
||||||
#define _glfw_dlclose(handle) dlclose(handle)
|
#define _glfw_dlclose(handle) dlclose(handle)
|
||||||
@@ -102,6 +104,7 @@ typedef struct _GLFWWaylandBufferPair {
|
|||||||
struct { uint8_t *a, *b, *front, *back; } data;
|
struct { uint8_t *a, *b, *front, *back; } data;
|
||||||
bool has_pending_update;
|
bool has_pending_update;
|
||||||
size_t size_in_bytes, width, height, stride;
|
size_t size_in_bytes, width, height, stride;
|
||||||
|
bool a_needs_to_be_destroyed, b_needs_to_be_destroyed;
|
||||||
} _GLFWWaylandBufferPair;
|
} _GLFWWaylandBufferPair;
|
||||||
|
|
||||||
typedef struct _GLFWWaylandCSDEdge {
|
typedef struct _GLFWWaylandCSDEdge {
|
||||||
@@ -116,14 +119,22 @@ typedef enum WaylandWindowState {
|
|||||||
TOPLEVEL_STATE_NONE = 0,
|
TOPLEVEL_STATE_NONE = 0,
|
||||||
TOPLEVEL_STATE_MAXIMIZED = 1,
|
TOPLEVEL_STATE_MAXIMIZED = 1,
|
||||||
TOPLEVEL_STATE_FULLSCREEN = 2,
|
TOPLEVEL_STATE_FULLSCREEN = 2,
|
||||||
TOPLEVEL_STATE_RESIZING = 4,
|
TOPLEVEL_STATE_RESIZING = 4,
|
||||||
TOPLEVEL_STATE_ACTIVATED = 8,
|
TOPLEVEL_STATE_ACTIVATED = 8,
|
||||||
TOPLEVEL_STATE_TILED_LEFT = 16,
|
TOPLEVEL_STATE_TILED_LEFT = 16,
|
||||||
TOPLEVEL_STATE_TILED_RIGHT = 32,
|
TOPLEVEL_STATE_TILED_RIGHT = 32,
|
||||||
TOPLEVEL_STATE_TILED_TOP = 64,
|
TOPLEVEL_STATE_TILED_TOP = 64,
|
||||||
TOPLEVEL_STATE_TILED_BOTTOM = 128,
|
TOPLEVEL_STATE_TILED_BOTTOM = 128,
|
||||||
} WaylandWindowState;
|
} WaylandWindowState;
|
||||||
|
|
||||||
|
typedef struct glfw_wl_xdg_activation_request {
|
||||||
|
GLFWid window_id;
|
||||||
|
GLFWactivationcallback callback;
|
||||||
|
void *callback_data;
|
||||||
|
uintptr_t request_id;
|
||||||
|
void *token;
|
||||||
|
} glfw_wl_xdg_activation_request;
|
||||||
|
|
||||||
|
|
||||||
static const WaylandWindowState TOPLEVEL_STATE_DOCKED = TOPLEVEL_STATE_MAXIMIZED | TOPLEVEL_STATE_FULLSCREEN | TOPLEVEL_STATE_TILED_TOP | TOPLEVEL_STATE_TILED_LEFT | TOPLEVEL_STATE_TILED_RIGHT | TOPLEVEL_STATE_TILED_BOTTOM;
|
static const WaylandWindowState TOPLEVEL_STATE_DOCKED = TOPLEVEL_STATE_MAXIMIZED | TOPLEVEL_STATE_FULLSCREEN | TOPLEVEL_STATE_TILED_TOP | TOPLEVEL_STATE_TILED_LEFT | TOPLEVEL_STATE_TILED_RIGHT | TOPLEVEL_STATE_TILED_BOTTOM;
|
||||||
|
|
||||||
@@ -141,6 +152,7 @@ typedef struct _GLFWwindowWayland
|
|||||||
bool hovered;
|
bool hovered;
|
||||||
bool transparent;
|
bool transparent;
|
||||||
struct wl_surface* surface;
|
struct wl_surface* surface;
|
||||||
|
bool waiting_for_swap_to_commit;
|
||||||
struct wl_egl_window* native;
|
struct wl_egl_window* native;
|
||||||
struct wl_callback* callback;
|
struct wl_callback* callback;
|
||||||
|
|
||||||
@@ -276,13 +288,14 @@ typedef struct _GLFWlibraryWayland
|
|||||||
struct zwp_primary_selection_device_manager_v1* primarySelectionDeviceManager;
|
struct zwp_primary_selection_device_manager_v1* primarySelectionDeviceManager;
|
||||||
struct zwp_primary_selection_device_v1* primarySelectionDevice;
|
struct zwp_primary_selection_device_v1* primarySelectionDevice;
|
||||||
struct zwp_primary_selection_source_v1* dataSourceForPrimarySelection;
|
struct zwp_primary_selection_source_v1* dataSourceForPrimarySelection;
|
||||||
|
struct xdg_activation_v1* xdg_activation_v1;
|
||||||
|
|
||||||
int compositorVersion;
|
int compositorVersion;
|
||||||
int seatVersion;
|
int seatVersion;
|
||||||
|
|
||||||
struct wl_surface* cursorSurface;
|
struct wl_surface* cursorSurface;
|
||||||
GLFWCursorShape cursorPreviousShape;
|
GLFWCursorShape cursorPreviousShape;
|
||||||
uint32_t serial;
|
uint32_t serial, input_serial;
|
||||||
|
|
||||||
int32_t keyboardRepeatRate;
|
int32_t keyboardRepeatRate;
|
||||||
monotonic_t keyboardRepeatDelay;
|
monotonic_t keyboardRepeatDelay;
|
||||||
@@ -316,12 +329,14 @@ typedef struct _GLFWlibraryWayland
|
|||||||
PFN_wl_egl_window_resize window_resize;
|
PFN_wl_egl_window_resize window_resize;
|
||||||
} egl;
|
} egl;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
glfw_wl_xdg_activation_request *array;
|
||||||
|
size_t capacity, sz;
|
||||||
|
} activation_requests;
|
||||||
|
|
||||||
EventLoopData eventLoopData;
|
EventLoopData eventLoopData;
|
||||||
char* pasteString;
|
|
||||||
char* clipboardString;
|
|
||||||
size_t dataOffersCounter;
|
size_t dataOffersCounter;
|
||||||
_GLFWWaylandDataOffer dataOffers[8];
|
_GLFWWaylandDataOffer dataOffers[8];
|
||||||
char* primarySelectionString;
|
|
||||||
} _GLFWlibraryWayland;
|
} _GLFWlibraryWayland;
|
||||||
|
|
||||||
// Wayland-specific per-monitor data
|
// Wayland-specific per-monitor data
|
||||||
@@ -355,6 +370,7 @@ typedef struct _GLFWcursorWayland
|
|||||||
|
|
||||||
|
|
||||||
void _glfwAddOutputWayland(uint32_t name, uint32_t version);
|
void _glfwAddOutputWayland(uint32_t name, uint32_t version);
|
||||||
|
void _glfwWaylandAfterBufferSwap(_GLFWwindow *window);
|
||||||
void _glfwSetupWaylandDataDevice(void);
|
void _glfwSetupWaylandDataDevice(void);
|
||||||
void _glfwSetupWaylandPrimarySelectionDevice(void);
|
void _glfwSetupWaylandPrimarySelectionDevice(void);
|
||||||
void animateCursorImage(id_type timer_id, void *data);
|
void animateCursorImage(id_type timer_id, void *data);
|
||||||
|
|||||||
39
glfw/wl_text_input.c
vendored
39
glfw/wl_text_input.c
vendored
@@ -9,12 +9,15 @@
|
|||||||
#include "internal.h"
|
#include "internal.h"
|
||||||
#include "wayland-text-input-unstable-v3-client-protocol.h"
|
#include "wayland-text-input-unstable-v3-client-protocol.h"
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
#define debug(...) if (_glfw.hints.init.debugKeyboard) printf(__VA_ARGS__);
|
#define debug(...) if (_glfw.hints.init.debugKeyboard) printf(__VA_ARGS__);
|
||||||
|
|
||||||
static struct zwp_text_input_v3* text_input;
|
static struct zwp_text_input_v3* text_input;
|
||||||
static struct zwp_text_input_manager_v3* text_input_manager;
|
static struct zwp_text_input_manager_v3* text_input_manager;
|
||||||
static char *pending_pre_edit = NULL;
|
static char *pending_pre_edit = NULL;
|
||||||
|
static char *current_pre_edit = NULL;
|
||||||
static char *pending_commit = NULL;
|
static char *pending_commit = NULL;
|
||||||
|
static int last_cursor_left = 0, last_cursor_top = 0, last_cursor_width = 0, last_cursor_height = 0;
|
||||||
uint32_t commit_serial = 0;
|
uint32_t commit_serial = 0;
|
||||||
|
|
||||||
static void commit(void) {
|
static void commit(void) {
|
||||||
@@ -87,15 +90,23 @@ static void
|
|||||||
text_input_done(void *data UNUSED, struct zwp_text_input_v3 *txt_input UNUSED, uint32_t serial) {
|
text_input_done(void *data UNUSED, struct zwp_text_input_v3 *txt_input UNUSED, uint32_t serial) {
|
||||||
debug("text-input: done event: serial: %u current_commit_serial: %u\n", serial, commit_serial);
|
debug("text-input: done event: serial: %u current_commit_serial: %u\n", serial, commit_serial);
|
||||||
if (serial != commit_serial) {
|
if (serial != commit_serial) {
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: text_input_done serial mismatch, expected=%u got=%u\n", commit_serial, serial);
|
if (serial > commit_serial) _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: text_input_done serial mismatch, expected=%u got=%u\n", commit_serial, serial);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (pending_pre_edit) {
|
|
||||||
send_text(pending_pre_edit, GLFW_IME_PREEDIT_CHANGED);
|
if ((pending_pre_edit == NULL && current_pre_edit == NULL) ||
|
||||||
|
(pending_pre_edit && current_pre_edit && strcmp(pending_pre_edit, current_pre_edit) == 0)) {
|
||||||
free(pending_pre_edit); pending_pre_edit = NULL;
|
free(pending_pre_edit); pending_pre_edit = NULL;
|
||||||
} else {
|
} else {
|
||||||
// Clear pre-edit text
|
free(current_pre_edit);
|
||||||
send_text(NULL, GLFW_IME_WAYLAND_DONE_EVENT);
|
current_pre_edit = pending_pre_edit;
|
||||||
|
pending_pre_edit = NULL;
|
||||||
|
if (current_pre_edit) {
|
||||||
|
send_text(current_pre_edit, GLFW_IME_PREEDIT_CHANGED);
|
||||||
|
} else {
|
||||||
|
// Clear pre-edit text
|
||||||
|
send_text(NULL, GLFW_IME_WAYLAND_DONE_EVENT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (pending_commit) {
|
if (pending_commit) {
|
||||||
send_text(pending_commit, GLFW_IME_COMMIT_TEXT);
|
send_text(pending_commit, GLFW_IME_COMMIT_TEXT);
|
||||||
@@ -133,6 +144,7 @@ _glfwWaylandDestroyTextInput(void) {
|
|||||||
if (text_input_manager) zwp_text_input_manager_v3_destroy(text_input_manager);
|
if (text_input_manager) zwp_text_input_manager_v3_destroy(text_input_manager);
|
||||||
text_input = NULL; text_input_manager = NULL;
|
text_input = NULL; text_input_manager = NULL;
|
||||||
free(pending_pre_edit); pending_pre_edit = NULL;
|
free(pending_pre_edit); pending_pre_edit = NULL;
|
||||||
|
free(current_pre_edit); current_pre_edit = NULL;
|
||||||
free(pending_commit); pending_commit = NULL;
|
free(pending_commit); pending_commit = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,10 +158,11 @@ _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
|
|||||||
zwp_text_input_v3_enable(text_input);
|
zwp_text_input_v3_enable(text_input);
|
||||||
zwp_text_input_v3_set_content_type(text_input, ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE, ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL);
|
zwp_text_input_v3_set_content_type(text_input, ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE, ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL);
|
||||||
} else {
|
} else {
|
||||||
if (pending_pre_edit) {
|
free(pending_pre_edit); pending_pre_edit = NULL;
|
||||||
|
if (current_pre_edit) {
|
||||||
// Clear pre-edit text
|
// Clear pre-edit text
|
||||||
send_text(NULL, GLFW_IME_PREEDIT_CHANGED);
|
send_text(NULL, GLFW_IME_PREEDIT_CHANGED);
|
||||||
free(pending_pre_edit); pending_pre_edit = NULL;
|
free(current_pre_edit); current_pre_edit = NULL;
|
||||||
}
|
}
|
||||||
if (pending_commit) {
|
if (pending_commit) {
|
||||||
free(pending_commit); pending_commit = NULL;
|
free(pending_commit); pending_commit = NULL;
|
||||||
@@ -161,9 +174,15 @@ _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
|
|||||||
case GLFW_IME_UPDATE_CURSOR_POSITION: {
|
case GLFW_IME_UPDATE_CURSOR_POSITION: {
|
||||||
const int scale = w->wl.scale;
|
const int scale = w->wl.scale;
|
||||||
const int left = ev->cursor.left / scale, top = ev->cursor.top / scale, width = ev->cursor.width / scale, height = ev->cursor.height / scale;
|
const int left = ev->cursor.left / scale, top = ev->cursor.top / scale, width = ev->cursor.width / scale, height = ev->cursor.height / scale;
|
||||||
debug("\ntext-input: updating cursor position: left=%d top=%d width=%d height=%d\n", left, top, width, height);
|
if (left != last_cursor_left || top != last_cursor_top || width != last_cursor_width || height != last_cursor_height) {
|
||||||
zwp_text_input_v3_set_cursor_rectangle(text_input, left, top, width, height);
|
last_cursor_left = left;
|
||||||
commit();
|
last_cursor_top = top;
|
||||||
|
last_cursor_width = width;
|
||||||
|
last_cursor_height = height;
|
||||||
|
debug("\ntext-input: updating cursor position: left=%d top=%d width=%d height=%d\n", left, top, width, height);
|
||||||
|
zwp_text_input_v3_set_cursor_rectangle(text_input, left, top, width, height);
|
||||||
|
commit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
549
glfw/wl_window.c
vendored
549
glfw/wl_window.c
vendored
@@ -44,6 +44,59 @@
|
|||||||
|
|
||||||
#define debug(...) if (_glfw.hints.init.debugRendering) fprintf(stderr, __VA_ARGS__);
|
#define debug(...) if (_glfw.hints.init.debugRendering) fprintf(stderr, __VA_ARGS__);
|
||||||
|
|
||||||
|
static void
|
||||||
|
activation_token_done(void *data, struct xdg_activation_token_v1 *xdg_token, const char *token) {
|
||||||
|
for (size_t i = 0; i < _glfw.wl.activation_requests.sz; i++) {
|
||||||
|
glfw_wl_xdg_activation_request *r = _glfw.wl.activation_requests.array + i;
|
||||||
|
if (r->request_id == (uintptr_t)data) {
|
||||||
|
_GLFWwindow *window = _glfwWindowForId(r->window_id);
|
||||||
|
if (r->callback) r->callback((GLFWwindow*)window, token, r->callback_data);
|
||||||
|
remove_i_from_array(_glfw.wl.activation_requests.array, i, _glfw.wl.activation_requests.sz);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xdg_activation_token_v1_destroy(xdg_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static const struct
|
||||||
|
xdg_activation_token_v1_listener activation_token_listener = {
|
||||||
|
.done = &activation_token_done,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static bool
|
||||||
|
get_activation_token(
|
||||||
|
_GLFWwindow *window, uint32_t serial, GLFWactivationcallback cb, void *cb_data
|
||||||
|
) {
|
||||||
|
#define fail(msg) { _glfwInputError(GLFW_PLATFORM_ERROR, msg); if (cb) cb((GLFWwindow*)window, NULL, cb_data); return false; }
|
||||||
|
if (_glfw.wl.xdg_activation_v1 == NULL) fail("Wayland: activation requests not supported by this Wayland compositor");
|
||||||
|
struct xdg_activation_token_v1 *token = xdg_activation_v1_get_activation_token(_glfw.wl.xdg_activation_v1);
|
||||||
|
if (token == NULL) fail("Wayland: failed to create activation request token");
|
||||||
|
if (_glfw.wl.activation_requests.capacity < _glfw.wl.activation_requests.sz + 1) {
|
||||||
|
_glfw.wl.activation_requests.capacity = MAX(64u, _glfw.wl.activation_requests.capacity * 2);
|
||||||
|
_glfw.wl.activation_requests.array = realloc(_glfw.wl.activation_requests.array, _glfw.wl.activation_requests.capacity * sizeof(_glfw.wl.activation_requests.array[0]));
|
||||||
|
if (!_glfw.wl.activation_requests.array) {
|
||||||
|
_glfw.wl.activation_requests.capacity = 0;
|
||||||
|
fail("Wayland: Out of memory while allocation activation request");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
glfw_wl_xdg_activation_request *r = _glfw.wl.activation_requests.array + _glfw.wl.activation_requests.sz++;
|
||||||
|
memset(r, 0, sizeof(*r));
|
||||||
|
static uintptr_t rq = 0;
|
||||||
|
r->window_id = window->id;
|
||||||
|
r->callback = cb; r->callback_data = cb_data;
|
||||||
|
r->request_id = ++rq; r->token = token;
|
||||||
|
if (serial != 0)
|
||||||
|
xdg_activation_token_v1_set_serial(token, serial, _glfw.wl.seat);
|
||||||
|
|
||||||
|
xdg_activation_token_v1_set_surface(token, window->wl.surface);
|
||||||
|
xdg_activation_token_v1_add_listener(token, &activation_token_listener, (void*)r->request_id);
|
||||||
|
xdg_activation_token_v1_commit(token);
|
||||||
|
return true;
|
||||||
|
#undef fail
|
||||||
|
}
|
||||||
|
|
||||||
static struct wl_buffer* createShmBuffer(const GLFWimage* image, bool is_opaque, bool init_data)
|
static struct wl_buffer* createShmBuffer(const GLFWimage* image, bool is_opaque, bool init_data)
|
||||||
{
|
{
|
||||||
struct wl_shm_pool* pool;
|
struct wl_shm_pool* pool;
|
||||||
@@ -198,8 +251,17 @@ static bool checkScaleChange(_GLFWwindow* window)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
commit_window_surface_if_safe(_GLFWwindow *window) {
|
||||||
|
// we only commit if the buffer attached to the surface is the correct size,
|
||||||
|
// which means that at least one frame is drawn after resizeFramebuffer()
|
||||||
|
if (!window->wl.waiting_for_swap_to_commit) {
|
||||||
|
wl_surface_commit(window->wl.surface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Makes the surface considered as XRGB instead of ARGB.
|
// Makes the surface considered as XRGB instead of ARGB.
|
||||||
static void setOpaqueRegion(_GLFWwindow* window)
|
static void setOpaqueRegion(_GLFWwindow* window, bool commit_surface)
|
||||||
{
|
{
|
||||||
struct wl_region* region;
|
struct wl_region* region;
|
||||||
|
|
||||||
@@ -209,7 +271,7 @@ static void setOpaqueRegion(_GLFWwindow* window)
|
|||||||
|
|
||||||
wl_region_add(region, 0, 0, window->wl.width, window->wl.height);
|
wl_region_add(region, 0, 0, window->wl.width, window->wl.height);
|
||||||
wl_surface_set_opaque_region(window->wl.surface, region);
|
wl_surface_set_opaque_region(window->wl.surface, region);
|
||||||
wl_surface_commit(window->wl.surface);
|
if (commit_surface) commit_window_surface_if_safe(window);
|
||||||
wl_region_destroy(region);
|
wl_region_destroy(region);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,10 +283,21 @@ resizeFramebuffer(_GLFWwindow* window) {
|
|||||||
int scaledHeight = window->wl.height * scale;
|
int scaledHeight = window->wl.height * scale;
|
||||||
debug("Resizing framebuffer to: %dx%d at scale: %d\n", window->wl.width, window->wl.height, scale);
|
debug("Resizing framebuffer to: %dx%d at scale: %d\n", window->wl.width, window->wl.height, scale);
|
||||||
wl_egl_window_resize(window->wl.native, scaledWidth, scaledHeight, 0, 0);
|
wl_egl_window_resize(window->wl.native, scaledWidth, scaledHeight, 0, 0);
|
||||||
if (!window->wl.transparent) setOpaqueRegion(window);
|
if (!window->wl.transparent) setOpaqueRegion(window, false);
|
||||||
|
window->wl.waiting_for_swap_to_commit = true;
|
||||||
_glfwInputFramebufferSize(window, scaledWidth, scaledHeight);
|
_glfwInputFramebufferSize(window, scaledWidth, scaledHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
_glfwWaylandAfterBufferSwap(_GLFWwindow* window) {
|
||||||
|
if (window->wl.waiting_for_swap_to_commit) {
|
||||||
|
debug("Waiting for swap to commit: swap has happened\n");
|
||||||
|
window->wl.waiting_for_swap_to_commit = false;
|
||||||
|
// this is not really needed, since I think eglSwapBuffers() calls wl_surface_commit()
|
||||||
|
// but lets be safe. See https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/egl/drivers/dri2/platform_wayland.c#L1510
|
||||||
|
wl_surface_commit(window->wl.surface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static const char*
|
static const char*
|
||||||
clipboard_mime(void) {
|
clipboard_mime(void) {
|
||||||
@@ -368,9 +441,22 @@ static bool createSurface(_GLFWwindow* window,
|
|||||||
|
|
||||||
wl_surface_set_user_data(window->wl.surface, window);
|
wl_surface_set_user_data(window->wl.surface, window);
|
||||||
|
|
||||||
window->wl.native = wl_egl_window_create(window->wl.surface,
|
// If we already have been notified of the primary monitor scale, assume
|
||||||
wndconfig->width,
|
// the window will be created on it and so avoid a rescale roundtrip in the common
|
||||||
wndconfig->height);
|
// case of the window being shown on the primary monitor or all monitors having the same scale.
|
||||||
|
// If you change this also change get_window_content_scale() in the kitty code.
|
||||||
|
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
|
||||||
|
float xscale = 1.0, yscale = 1.0;
|
||||||
|
int scale = 1;
|
||||||
|
if (monitor) {
|
||||||
|
glfwGetMonitorContentScale(monitor, &xscale, &yscale);
|
||||||
|
// see wl_monitor.c xscale is always == yscale
|
||||||
|
if (xscale <= 0.0001 || xscale != xscale || xscale >= 24) xscale = 1.0;
|
||||||
|
if (xscale > 1) scale = (int)xscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug("Creating window at size: %dx%d and scale %d\n", wndconfig->width, wndconfig->height, scale);
|
||||||
|
window->wl.native = wl_egl_window_create(window->wl.surface, wndconfig->width * scale, wndconfig->height * scale);
|
||||||
if (!window->wl.native)
|
if (!window->wl.native)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -378,11 +464,13 @@ static bool createSurface(_GLFWwindow* window,
|
|||||||
window->wl.height = wndconfig->height;
|
window->wl.height = wndconfig->height;
|
||||||
window->wl.user_requested_content_size.width = wndconfig->width;
|
window->wl.user_requested_content_size.width = wndconfig->width;
|
||||||
window->wl.user_requested_content_size.height = wndconfig->height;
|
window->wl.user_requested_content_size.height = wndconfig->height;
|
||||||
window->wl.scale = 1;
|
|
||||||
|
window->wl.scale = scale;
|
||||||
|
|
||||||
if (!window->wl.transparent)
|
if (!window->wl.transparent)
|
||||||
setOpaqueRegion(window);
|
setOpaqueRegion(window, false);
|
||||||
|
|
||||||
|
wl_surface_set_buffer_scale(window->wl.surface, scale);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,6 +576,15 @@ static void xdgSurfaceHandleConfigure(void* data,
|
|||||||
struct xdg_surface* surface,
|
struct xdg_surface* surface,
|
||||||
uint32_t serial)
|
uint32_t serial)
|
||||||
{
|
{
|
||||||
|
// The poorly documented pattern Wayland requires is:
|
||||||
|
// 1) ack the configure,
|
||||||
|
// 2) set the window geometry
|
||||||
|
// 3) attach a new buffer of the correct size to the surface
|
||||||
|
// 4) only then commit the surface.
|
||||||
|
// buffer is attached only by eglSwapBuffers,
|
||||||
|
// so we set a flag to not commit the surface till the next swapbuffers. Note that
|
||||||
|
// wl_egl_window_resize() does not actually resize the buffer until the next draw call
|
||||||
|
// or buffer state query.
|
||||||
_GLFWwindow* window = data;
|
_GLFWwindow* window = data;
|
||||||
xdg_surface_ack_configure(surface, serial);
|
xdg_surface_ack_configure(surface, serial);
|
||||||
if (window->wl.pending_state & PENDING_STATE_TOPLEVEL) {
|
if (window->wl.pending_state & PENDING_STATE_TOPLEVEL) {
|
||||||
@@ -495,12 +592,9 @@ static void xdgSurfaceHandleConfigure(void* data,
|
|||||||
int width = window->wl.pending.width;
|
int width = window->wl.pending.width;
|
||||||
int height = window->wl.pending.height;
|
int height = window->wl.pending.height;
|
||||||
if (!window->wl.surface_configured_once) {
|
if (!window->wl.surface_configured_once) {
|
||||||
|
window->swaps_disallowed = false;
|
||||||
|
window->wl.waiting_for_swap_to_commit = true;
|
||||||
window->wl.surface_configured_once = true;
|
window->wl.surface_configured_once = true;
|
||||||
if (!width && !height && !new_states && !window->wl.decorations.serverSide && getenv("XAUTHORITY") && strstr(getenv("XAUTHORITY"), "mutter")) {
|
|
||||||
// https://github.com/kovidgoyal/kitty/issues/4802
|
|
||||||
debug("Ignoring first empty surface configure event on mutter.\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (new_states != window->wl.current.toplevel_states ||
|
if (new_states != window->wl.current.toplevel_states ||
|
||||||
@@ -523,12 +617,11 @@ static void xdgSurfaceHandleConfigure(void* data,
|
|||||||
window->wl.current.decoration_mode = mode;
|
window->wl.current.decoration_mode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool resized = false;
|
|
||||||
if (window->wl.pending_state) {
|
if (window->wl.pending_state) {
|
||||||
int width = window->wl.pending.width, height = window->wl.pending.height;
|
int width = window->wl.pending.width, height = window->wl.pending.height;
|
||||||
set_csd_window_geometry(window, &width, &height);
|
set_csd_window_geometry(window, &width, &height);
|
||||||
resized = dispatchChangesAfterConfigure(window, width, height);
|
bool resized = dispatchChangesAfterConfigure(window, width, height);
|
||||||
if (window->wl.decorations.serverSide) {
|
if (window->wl.decorations.serverSide || window->monitor || window->wl.current.toplevel_states & TOPLEVEL_STATE_FULLSCREEN) {
|
||||||
free_csd_surfaces(window);
|
free_csd_surfaces(window);
|
||||||
} else {
|
} else {
|
||||||
ensure_csd_resources(window);
|
ensure_csd_resources(window);
|
||||||
@@ -537,10 +630,7 @@ static void xdgSurfaceHandleConfigure(void* data,
|
|||||||
}
|
}
|
||||||
|
|
||||||
inform_compositor_of_window_geometry(window, "configure");
|
inform_compositor_of_window_geometry(window, "configure");
|
||||||
|
commit_window_surface_if_safe(window);
|
||||||
// if a resize happened there will be a commit at the next render frame so
|
|
||||||
// dont commit here, GNOME doesnt like it and its not really needed anyway
|
|
||||||
if (!resized) wl_surface_commit(window->wl.surface);
|
|
||||||
window->wl.pending_state = 0;
|
window->wl.pending_state = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -784,6 +874,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
|||||||
initialize_csd_metrics(window);
|
initialize_csd_metrics(window);
|
||||||
window->wl.transparent = fbconfig->transparent;
|
window->wl.transparent = fbconfig->transparent;
|
||||||
strncpy(window->wl.appId, wndconfig->wl.appId, sizeof(window->wl.appId));
|
strncpy(window->wl.appId, wndconfig->wl.appId, sizeof(window->wl.appId));
|
||||||
|
window->swaps_disallowed = true;
|
||||||
|
|
||||||
if (!createSurface(window, wndconfig))
|
if (!createSurface(window, wndconfig))
|
||||||
return false;
|
return false;
|
||||||
@@ -939,7 +1030,7 @@ void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
|
|||||||
window->wl.width = w; window->wl.height = h;
|
window->wl.width = w; window->wl.height = h;
|
||||||
resizeFramebuffer(window);
|
resizeFramebuffer(window);
|
||||||
ensure_csd_resources(window);
|
ensure_csd_resources(window);
|
||||||
wl_surface_commit(window->wl.surface);
|
commit_window_surface_if_safe(window);
|
||||||
inform_compositor_of_window_geometry(window, "SetWindowSize");
|
inform_compositor_of_window_geometry(window, "SetWindowSize");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -956,7 +1047,7 @@ void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
|
|||||||
maxwidth = maxheight = 0;
|
maxwidth = maxheight = 0;
|
||||||
xdg_toplevel_set_min_size(window->wl.xdg.toplevel, minwidth, minheight);
|
xdg_toplevel_set_min_size(window->wl.xdg.toplevel, minwidth, minheight);
|
||||||
xdg_toplevel_set_max_size(window->wl.xdg.toplevel, maxwidth, maxheight);
|
xdg_toplevel_set_max_size(window->wl.xdg.toplevel, maxwidth, maxheight);
|
||||||
wl_surface_commit(window->wl.surface);
|
commit_window_surface_if_safe(window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -993,7 +1084,7 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
|||||||
if (window->decorated && !window->monitor && !window->wl.decorations.serverSide)
|
if (window->decorated && !window->monitor && !window->wl.decorations.serverSide)
|
||||||
{
|
{
|
||||||
if (top)
|
if (top)
|
||||||
*top = window->wl.decorations.metrics.top;
|
*top = window->wl.decorations.metrics.top - window->wl.decorations.metrics.visible_titlebar_height;
|
||||||
if (left)
|
if (left)
|
||||||
*left = window->wl.decorations.metrics.width;
|
*left = window->wl.decorations.metrics.width;
|
||||||
if (right)
|
if (right)
|
||||||
@@ -1062,38 +1153,51 @@ void _glfwPlatformHideWindow(_GLFWwindow* window)
|
|||||||
xdg_surface_destroy(window->wl.xdg.surface);
|
xdg_surface_destroy(window->wl.xdg.surface);
|
||||||
window->wl.xdg.toplevel = NULL;
|
window->wl.xdg.toplevel = NULL;
|
||||||
window->wl.xdg.surface = NULL;
|
window->wl.xdg.surface = NULL;
|
||||||
|
window->wl.surface_configured_once = false;
|
||||||
|
window->swaps_disallowed = true;
|
||||||
}
|
}
|
||||||
window->wl.visible = false;
|
window->wl.visible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformRequestWindowAttention(_GLFWwindow* window UNUSED)
|
static void
|
||||||
{
|
request_attention(GLFWwindow *window, const char *token, void *data UNUSED) {
|
||||||
// TODO
|
if (window && token && token[0]) xdg_activation_v1_activate(_glfw.wl.xdg_activation_v1, token, ((_GLFWwindow*)window)->wl.surface);
|
||||||
static bool notified = false;
|
}
|
||||||
if (!notified) {
|
|
||||||
_glfwInputError(GLFW_FEATURE_UNIMPLEMENTED,
|
static bool
|
||||||
"Wayland: Window attention request not implemented yet");
|
has_activation_in_flight(_GLFWwindow* window, GLFWactivationcallback callback) {
|
||||||
notified = true;
|
for (size_t i = 0; i < _glfw.wl.activation_requests.sz; i++) {
|
||||||
|
glfw_wl_xdg_activation_request *r = _glfw.wl.activation_requests.array + i;
|
||||||
|
if (r->window_id == window->id && r->callback == callback) return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _glfwPlatformRequestWindowAttention(_GLFWwindow* window) {
|
||||||
|
if (!has_activation_in_flight(window, request_attention)) get_activation_token(window, 0, request_attention, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
int _glfwPlatformWindowBell(_GLFWwindow* window UNUSED)
|
int _glfwPlatformWindowBell(_GLFWwindow* window UNUSED)
|
||||||
{
|
{
|
||||||
// TODO: Use an actual Wayland API to implement this when one becomes available
|
// TODO: Use an actual Wayland API to implement this when one becomes available
|
||||||
static char tty[L_ctermid + 1];
|
|
||||||
int fd = open(ctermid(tty), O_WRONLY | O_CLOEXEC);
|
|
||||||
if (fd > -1) {
|
|
||||||
int ret = write(fd, "\x07", 1) == 1 ? true : false;
|
|
||||||
close(fd);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
focus_window(GLFWwindow *window, const char *token, void *data UNUSED) {
|
||||||
|
if (!window) return;
|
||||||
|
if (token && token[0]) xdg_activation_v1_activate(_glfw.wl.xdg_activation_v1, token, ((_GLFWwindow*)window)->wl.surface);
|
||||||
|
else {
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: Window focus request via xdg-activation protocol was denied by the compositor. Use a better compositor.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _glfwPlatformFocusWindow(_GLFWwindow* window UNUSED)
|
void _glfwPlatformFocusWindow(_GLFWwindow* window UNUSED)
|
||||||
{
|
{
|
||||||
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
|
// Attempt to focus the window by using the activation protocol, whether it works
|
||||||
"Wayland: The platform does not support setting the input focus");
|
// is entirely compositor dependent and as we all know Wayland and its ecosystem is
|
||||||
|
// the product of morons.
|
||||||
|
if (_glfw.wl.input_serial && !has_activation_in_flight(window, focus_window)) get_activation_token(window, _glfw.wl.input_serial, focus_window, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
|
void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
|
||||||
@@ -1178,7 +1282,7 @@ void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
wl_surface_set_input_region(window->wl.surface, 0);
|
wl_surface_set_input_region(window->wl.surface, 0);
|
||||||
wl_surface_commit(window->wl.surface);
|
commit_window_surface_if_safe(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window UNUSED)
|
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window UNUSED)
|
||||||
@@ -1242,7 +1346,7 @@ void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y)
|
|||||||
zwp_locked_pointer_v1_set_cursor_position_hint(
|
zwp_locked_pointer_v1_set_cursor_position_hint(
|
||||||
window->wl.pointerLock.lockedPointer,
|
window->wl.pointerLock.lockedPointer,
|
||||||
wl_fixed_from_double(x), wl_fixed_from_double(y));
|
wl_fixed_from_double(x), wl_fixed_from_double(y));
|
||||||
wl_surface_commit(window->wl.surface);
|
commit_window_surface_if_safe(window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1440,53 +1544,68 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void send_text(char *text, int fd)
|
static bool
|
||||||
{
|
write_all(int fd, const char *data, size_t sz) {
|
||||||
if (text) {
|
monotonic_t start = glfwGetTime();
|
||||||
size_t len = strlen(text), pos = 0;
|
size_t pos = 0;
|
||||||
monotonic_t start = glfwGetTime();
|
while (pos < sz && glfwGetTime() - start < s_to_monotonic_t(2ll)) {
|
||||||
while (pos < len && glfwGetTime() - start < s_to_monotonic_t(2ll)) {
|
ssize_t ret = write(fd, data + pos, sz - pos);
|
||||||
ssize_t ret = write(fd, text + pos, len - pos);
|
if (ret < 0) {
|
||||||
if (ret < 0) {
|
if (errno == EAGAIN || errno == EINTR) continue;
|
||||||
if (errno == EAGAIN || errno == EINTR) continue;
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
"Wayland: Could not copy writing to destination fd failed with error: %s", strerror(errno));
|
||||||
"Wayland: Could not copy writing to destination fd failed with error: %s", strerror(errno));
|
return false;
|
||||||
break;
|
}
|
||||||
}
|
if (ret > 0) {
|
||||||
if (ret > 0) {
|
start = glfwGetTime();
|
||||||
start = glfwGetTime();
|
pos += ret;
|
||||||
pos += ret;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return pos >= sz;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
send_clipboard_data(const _GLFWClipboardData *cd, const char *mime, int fd) {
|
||||||
|
if (strcmp(mime, "text/plain;charset=utf-8") == 0 || strcmp(mime, "UTF8_STRING") == 0 || strcmp(mime, "TEXT") == 0 || strcmp(mime, "STRING") == 0) mime = "text/plain";
|
||||||
|
GLFWDataChunk chunk = cd->get_data(mime, NULL, cd->ctype);
|
||||||
|
void *iter = chunk.iter;
|
||||||
|
if (!iter) return;
|
||||||
|
bool keep_going = true;
|
||||||
|
while (keep_going) {
|
||||||
|
chunk = cd->get_data(mime, iter, cd->ctype);
|
||||||
|
if (!chunk.sz) break;
|
||||||
|
if (!write_all(fd, chunk.data, chunk.sz)) keep_going = false;
|
||||||
|
if (chunk.free) chunk.free((void*)chunk.free_data);
|
||||||
|
}
|
||||||
|
cd->get_data(NULL, iter, cd->ctype);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _glfwSendClipboardText(void *data UNUSED, struct wl_data_source *data_source UNUSED, const char *mime_type, int fd) {
|
||||||
|
send_clipboard_data(&_glfw.clipboard, mime_type, fd);
|
||||||
close(fd);
|
close(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _glfwSendClipboardText(void *data UNUSED, struct wl_data_source *data_source UNUSED, const char *mime_type UNUSED, int fd) {
|
|
||||||
send_text(_glfw.wl.clipboardString, fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _glfwSendPrimarySelectionText(void *data UNUSED, struct zwp_primary_selection_source_v1 *primary_selection_source UNUSED,
|
static void _glfwSendPrimarySelectionText(void *data UNUSED, struct zwp_primary_selection_source_v1 *primary_selection_source UNUSED,
|
||||||
const char *mime_type UNUSED, int fd) {
|
const char *mime_type, int fd) {
|
||||||
send_text(_glfw.wl.primarySelectionString, fd);
|
send_clipboard_data(&_glfw.primary, mime_type, fd);
|
||||||
|
close(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
static char* read_offer_string(int data_pipe, size_t *data_sz) {
|
static void
|
||||||
|
read_offer(int data_pipe, GLFWclipboardwritedatafun write_data, void *object) {
|
||||||
wl_display_flush(_glfw.wl.display);
|
wl_display_flush(_glfw.wl.display);
|
||||||
size_t capacity = 0;
|
|
||||||
char *buf = NULL;
|
|
||||||
*data_sz = 0;
|
|
||||||
struct pollfd fds;
|
struct pollfd fds;
|
||||||
fds.fd = data_pipe;
|
fds.fd = data_pipe;
|
||||||
fds.events = POLLIN;
|
fds.events = POLLIN;
|
||||||
monotonic_t start = glfwGetTime();
|
monotonic_t start = glfwGetTime();
|
||||||
#define bail(...) { \
|
#define bail(...) { \
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR, __VA_ARGS__); \
|
_glfwInputError(GLFW_PLATFORM_ERROR, __VA_ARGS__); \
|
||||||
free(buf); buf = NULL; \
|
|
||||||
close(data_pipe); \
|
close(data_pipe); \
|
||||||
return NULL; \
|
return; \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char buf[8192];
|
||||||
|
|
||||||
while (glfwGetTime() - start < s_to_monotonic_t(2ll)) {
|
while (glfwGetTime() - start < s_to_monotonic_t(2ll)) {
|
||||||
int ret = poll(&fds, 1, 2000);
|
int ret = poll(&fds, 1, 2000);
|
||||||
if (ret == -1) {
|
if (ret == -1) {
|
||||||
@@ -1496,34 +1615,65 @@ static char* read_offer_string(int data_pipe, size_t *data_sz) {
|
|||||||
if (!ret) {
|
if (!ret) {
|
||||||
bail("Wayland: Failed to read clipboard data from pipe (timed out)");
|
bail("Wayland: Failed to read clipboard data from pipe (timed out)");
|
||||||
}
|
}
|
||||||
if (capacity <= *data_sz || capacity - *data_sz <= 64) {
|
ret = read(data_pipe, buf, sizeof(buf));
|
||||||
capacity += 4096;
|
|
||||||
buf = realloc(buf, capacity);
|
|
||||||
if (!buf) {
|
|
||||||
bail("Wayland: Failed to allocate memory to read clipboard data");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ret = read(data_pipe, buf + *data_sz, capacity - *data_sz - 1);
|
|
||||||
if (ret == -1) {
|
if (ret == -1) {
|
||||||
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue;
|
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue;
|
||||||
bail("Wayland: Failed to read clipboard data from pipe with error: %s", strerror(errno));
|
bail("Wayland: Failed to read clipboard data from pipe with error: %s", strerror(errno));
|
||||||
}
|
}
|
||||||
if (ret == 0) { close(data_pipe); buf[*data_sz] = 0; return buf; }
|
if (ret == 0) { close(data_pipe); return; }
|
||||||
*data_sz += ret;
|
if (!write_data(object, buf, ret)) bail("Wayland: call to write_data() failed with data from data offer");
|
||||||
start = glfwGetTime();
|
start = glfwGetTime();
|
||||||
}
|
}
|
||||||
bail("Wayland: Failed to read clipboard data from pipe (timed out)");
|
bail("Wayland: Failed to read clipboard data from pipe (timed out)");
|
||||||
#undef bail
|
#undef bail
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static char* read_primary_selection_offer(struct zwp_primary_selection_offer_v1 *primary_selection_offer, const char *mime) {
|
|
||||||
|
typedef struct chunked_writer {
|
||||||
|
char *buf; size_t sz, cap;
|
||||||
|
} chunked_writer;
|
||||||
|
|
||||||
|
static bool
|
||||||
|
write_chunk(void *object, const char *data, size_t sz) {
|
||||||
|
chunked_writer *cw = object;
|
||||||
|
if (cw->cap < cw->sz + sz) {
|
||||||
|
cw->cap = MAX(cw->cap * 2, cw->sz + 8*sz);
|
||||||
|
cw->buf = realloc(cw->buf, cw->cap * sizeof(cw->buf[0]));
|
||||||
|
}
|
||||||
|
memcpy(cw->buf + cw->sz, data, sz);
|
||||||
|
cw->sz += sz;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static char*
|
||||||
|
read_offer_string(int data_pipe, size_t *sz) {
|
||||||
|
chunked_writer cw = {0};
|
||||||
|
read_offer(data_pipe, write_chunk, &cw);
|
||||||
|
if (cw.buf) {
|
||||||
|
*sz = cw.sz;
|
||||||
|
return cw.buf;
|
||||||
|
}
|
||||||
|
*sz = 0;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
read_clipboard_data_offer(struct wl_data_offer *data_offer, const char *mime, GLFWclipboardwritedatafun write_data, void *object) {
|
||||||
int pipefd[2];
|
int pipefd[2];
|
||||||
if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL;
|
if (pipe2(pipefd, O_CLOEXEC) != 0) return;
|
||||||
|
wl_data_offer_receive(data_offer, mime, pipefd[1]);
|
||||||
|
close(pipefd[1]);
|
||||||
|
read_offer(pipefd[0], write_data, object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
read_primary_selection_offer(struct zwp_primary_selection_offer_v1 *primary_selection_offer, const char *mime, GLFWclipboardwritedatafun write_data, void *object) {
|
||||||
|
int pipefd[2];
|
||||||
|
if (pipe2(pipefd, O_CLOEXEC) != 0) return;
|
||||||
zwp_primary_selection_offer_v1_receive(primary_selection_offer, mime, pipefd[1]);
|
zwp_primary_selection_offer_v1_receive(primary_selection_offer, mime, pipefd[1]);
|
||||||
close(pipefd[1]);
|
close(pipefd[1]);
|
||||||
size_t sz = 0;
|
read_offer(pipefd[0], write_data, object);
|
||||||
return read_offer_string(pipefd[0], &sz);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime, size_t *sz) {
|
static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime, size_t *sz) {
|
||||||
@@ -1535,24 +1685,33 @@ static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void data_source_canceled(void *data UNUSED, struct wl_data_source *wl_data_source) {
|
static void data_source_canceled(void *data UNUSED, struct wl_data_source *wl_data_source) {
|
||||||
if (_glfw.wl.dataSourceForClipboard == wl_data_source)
|
if (_glfw.wl.dataSourceForClipboard == wl_data_source) {
|
||||||
_glfw.wl.dataSourceForClipboard = NULL;
|
_glfw.wl.dataSourceForClipboard = NULL;
|
||||||
|
_glfw_free_clipboard_data(&_glfw.clipboard);
|
||||||
|
}
|
||||||
wl_data_source_destroy(wl_data_source);
|
wl_data_source_destroy(wl_data_source);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void primary_selection_source_canceled(void *data UNUSED, struct zwp_primary_selection_source_v1 *primary_selection_source) {
|
static void primary_selection_source_canceled(void *data UNUSED, struct zwp_primary_selection_source_v1 *primary_selection_source) {
|
||||||
if (_glfw.wl.dataSourceForPrimarySelection == primary_selection_source)
|
if (_glfw.wl.dataSourceForPrimarySelection == primary_selection_source) {
|
||||||
_glfw.wl.dataSourceForPrimarySelection = NULL;
|
_glfw.wl.dataSourceForPrimarySelection = NULL;
|
||||||
|
_glfw_free_clipboard_data(&_glfw.primary);
|
||||||
|
}
|
||||||
zwp_primary_selection_source_v1_destroy(primary_selection_source);
|
zwp_primary_selection_source_v1_destroy(primary_selection_source);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void data_source_target(void *data UNUSED, struct wl_data_source *wl_data_source UNUSED, const char* mime UNUSED) {
|
// KWin aborts if we don't define these even though they are not used for copy/paste
|
||||||
|
static void dummy_data_source_target(void* data UNUSED, struct wl_data_source* wl_data_source UNUSED, const char* mime_type UNUSED) {
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dummy_data_source_action(void* data UNUSED, struct wl_data_source* wl_data_source UNUSED, uint dnd_action UNUSED) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct wl_data_source_listener data_source_listener = {
|
static const struct wl_data_source_listener data_source_listener = {
|
||||||
.send = _glfwSendClipboardText,
|
.send = _glfwSendClipboardText,
|
||||||
.cancelled = data_source_canceled,
|
.cancelled = data_source_canceled,
|
||||||
.target = data_source_target,
|
.target = dummy_data_source_target,
|
||||||
|
.action = dummy_data_source_action,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct zwp_primary_selection_source_v1_listener primary_selection_source_listener = {
|
static const struct zwp_primary_selection_source_v1_listener primary_selection_source_listener = {
|
||||||
@@ -1829,30 +1988,69 @@ static bool _glfwEnsureDataDevice(void) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformSetClipboardString(const char* string)
|
typedef void(*add_offer_func)(void*, const char *mime);
|
||||||
{
|
|
||||||
if (!_glfwEnsureDataDevice()) return;
|
|
||||||
free(_glfw.wl.clipboardString);
|
void
|
||||||
_glfw.wl.clipboardString = _glfw_strdup(string);
|
_glfwPlatformSetClipboard(GLFWClipboardType t) {
|
||||||
if (_glfw.wl.dataSourceForClipboard)
|
_GLFWClipboardData *cd = NULL;
|
||||||
wl_data_source_destroy(_glfw.wl.dataSourceForClipboard);
|
void *data_source;
|
||||||
_glfw.wl.dataSourceForClipboard = wl_data_device_manager_create_data_source(_glfw.wl.dataDeviceManager);
|
add_offer_func f;
|
||||||
if (!_glfw.wl.dataSourceForClipboard)
|
if (t == GLFW_CLIPBOARD) {
|
||||||
{
|
if (!_glfwEnsureDataDevice()) return;
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
cd = &_glfw.clipboard;
|
||||||
"Wayland: Cannot copy failed to create data source");
|
f = (add_offer_func)wl_data_source_offer;
|
||||||
return;
|
if (_glfw.wl.dataSourceForClipboard) wl_data_source_destroy(_glfw.wl.dataSourceForClipboard);
|
||||||
|
_glfw.wl.dataSourceForClipboard = wl_data_device_manager_create_data_source(_glfw.wl.dataDeviceManager);
|
||||||
|
if (!_glfw.wl.dataSourceForClipboard)
|
||||||
|
{
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||||
|
"Wayland: Cannot copy failed to create data source");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
wl_data_source_add_listener(_glfw.wl.dataSourceForClipboard, &data_source_listener, NULL);
|
||||||
|
data_source = _glfw.wl.dataSourceForClipboard;
|
||||||
|
} else {
|
||||||
|
if (!_glfw.wl.primarySelectionDevice) {
|
||||||
|
static bool warned_about_primary_selection_device = false;
|
||||||
|
if (!warned_about_primary_selection_device) {
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||||
|
"Wayland: Cannot copy no primary selection device available");
|
||||||
|
warned_about_primary_selection_device = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cd = &_glfw.primary;
|
||||||
|
f = (add_offer_func)zwp_primary_selection_source_v1_offer;
|
||||||
|
if (_glfw.wl.dataSourceForPrimarySelection) zwp_primary_selection_source_v1_destroy(_glfw.wl.dataSourceForPrimarySelection);
|
||||||
|
_glfw.wl.dataSourceForPrimarySelection = zwp_primary_selection_device_manager_v1_create_source(_glfw.wl.primarySelectionDeviceManager);
|
||||||
|
if (!_glfw.wl.dataSourceForPrimarySelection)
|
||||||
|
{
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||||
|
"Wayland: Cannot copy failed to create primary selection source");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
zwp_primary_selection_source_v1_add_listener(_glfw.wl.dataSourceForPrimarySelection, &primary_selection_source_listener, NULL);
|
||||||
|
data_source = _glfw.wl.dataSourceForPrimarySelection;
|
||||||
|
}
|
||||||
|
f(data_source, clipboard_mime());
|
||||||
|
for (size_t i = 0; i < cd->num_mime_types; i++) {
|
||||||
|
if (strcmp(cd->mime_types[i], "text/plain") == 0) {
|
||||||
|
f(data_source, "TEXT");
|
||||||
|
f(data_source, "STRING");
|
||||||
|
f(data_source, "UTF8_STRING");
|
||||||
|
f(data_source, "text/plain;charset=utf-8");
|
||||||
|
}
|
||||||
|
f(data_source, cd->mime_types[i]);
|
||||||
}
|
}
|
||||||
wl_data_source_add_listener(_glfw.wl.dataSourceForClipboard, &data_source_listener, NULL);
|
|
||||||
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, clipboard_mime());
|
|
||||||
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "text/plain");
|
|
||||||
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "text/plain;charset=utf-8");
|
|
||||||
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "TEXT");
|
|
||||||
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "STRING");
|
|
||||||
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "UTF8_STRING");
|
|
||||||
struct wl_callback *callback = wl_display_sync(_glfw.wl.display);
|
struct wl_callback *callback = wl_display_sync(_glfw.wl.display);
|
||||||
static const struct wl_callback_listener clipboard_copy_callback_listener = {.done = clipboard_copy_callback_done};
|
if (t == GLFW_CLIPBOARD) {
|
||||||
wl_callback_add_listener(callback, &clipboard_copy_callback_listener, _glfw.wl.dataSourceForClipboard);
|
static const struct wl_callback_listener clipboard_copy_callback_listener = {.done = clipboard_copy_callback_done};
|
||||||
|
wl_callback_add_listener(callback, &clipboard_copy_callback_listener, _glfw.wl.dataSourceForClipboard);
|
||||||
|
} else {
|
||||||
|
static const struct wl_callback_listener primary_selection_copy_callback_listener = {.done = primary_selection_copy_callback_done};
|
||||||
|
wl_callback_add_listener(callback, &primary_selection_copy_callback_listener, _glfw.wl.dataSourceForPrimarySelection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
@@ -1870,81 +2068,47 @@ plain_text_mime_for_offer(const _GLFWWaylandDataOffer *d) {
|
|||||||
A("text/plain");
|
A("text/plain");
|
||||||
A("UTF8_STRING");
|
A("UTF8_STRING");
|
||||||
A("STRING");
|
A("STRING");
|
||||||
|
A("TEXT");
|
||||||
#undef A
|
#undef A
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* _glfwPlatformGetClipboardString(void)
|
void
|
||||||
{
|
_glfwPlatformGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object) {
|
||||||
|
_GLFWWaylandOfferType offer_type = clipboard_type == GLFW_PRIMARY_SELECTION ? PRIMARY_SELECTION : CLIPBOARD;
|
||||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||||
_GLFWWaylandDataOffer *d = _glfw.wl.dataOffers + i;
|
_GLFWWaylandDataOffer *d = _glfw.wl.dataOffers + i;
|
||||||
if (d->id && d->offer_type == CLIPBOARD) {
|
if (d->id && d->offer_type == offer_type) {
|
||||||
if (d->is_self_offer) return _glfw.wl.clipboardString;
|
if (d->is_self_offer) {
|
||||||
const char *mime = plain_text_mime_for_offer(d);
|
write_data(object, NULL, 1);
|
||||||
if (mime) {
|
return;
|
||||||
free(_glfw.wl.pasteString);
|
|
||||||
size_t sz = 0;
|
|
||||||
_glfw.wl.pasteString = read_data_offer(d->id, mime, &sz);
|
|
||||||
return _glfw.wl.pasteString;
|
|
||||||
}
|
}
|
||||||
}
|
if (mime_type == NULL) {
|
||||||
}
|
bool ok = true;
|
||||||
return NULL;
|
for (size_t o = 0; o < d->mimes_count; o++) {
|
||||||
}
|
const char *q = d->mimes[o];
|
||||||
|
if (strchr(d->mimes[0], '/')) {
|
||||||
void _glfwPlatformSetPrimarySelectionString(const char* string)
|
if (strcmp(q, clipboard_mime()) == 0) continue;
|
||||||
{
|
if (strcmp(q, "text/plain;charset=utf-8") == 0) q = "text/plain";
|
||||||
if (!_glfw.wl.primarySelectionDevice) {
|
} else {
|
||||||
static bool warned_about_primary_selection_device = false;
|
if (strcmp(q, "UTF8_STRING") == 0 || strcmp(q, "STRING") == 0 || strcmp(q, "TEXT") == 0) q = "text/plain";
|
||||||
if (!warned_about_primary_selection_device) {
|
}
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
if (ok) ok = write_data(object, q, strlen(q));
|
||||||
"Wayland: Cannot copy no primary selection device available");
|
}
|
||||||
warned_about_primary_selection_device = true;
|
return;
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (_glfw.wl.primarySelectionString == string) return;
|
|
||||||
free(_glfw.wl.primarySelectionString);
|
|
||||||
_glfw.wl.primarySelectionString = _glfw_strdup(string);
|
|
||||||
|
|
||||||
if (_glfw.wl.dataSourceForPrimarySelection)
|
|
||||||
zwp_primary_selection_source_v1_destroy(_glfw.wl.dataSourceForPrimarySelection);
|
|
||||||
_glfw.wl.dataSourceForPrimarySelection = zwp_primary_selection_device_manager_v1_create_source(_glfw.wl.primarySelectionDeviceManager);
|
|
||||||
if (!_glfw.wl.dataSourceForPrimarySelection)
|
|
||||||
{
|
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
|
||||||
"Wayland: Cannot copy failed to create primary selection source");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
zwp_primary_selection_source_v1_add_listener(_glfw.wl.dataSourceForPrimarySelection, &primary_selection_source_listener, NULL);
|
|
||||||
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, clipboard_mime());
|
|
||||||
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "text/plain");
|
|
||||||
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "text/plain;charset=utf-8");
|
|
||||||
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "TEXT");
|
|
||||||
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "STRING");
|
|
||||||
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "UTF8_STRING");
|
|
||||||
struct wl_callback *callback = wl_display_sync(_glfw.wl.display);
|
|
||||||
static const struct wl_callback_listener primary_selection_copy_callback_listener = {.done = primary_selection_copy_callback_done};
|
|
||||||
wl_callback_add_listener(callback, &primary_selection_copy_callback_listener, _glfw.wl.dataSourceForPrimarySelection);
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* _glfwPlatformGetPrimarySelectionString(void)
|
|
||||||
{
|
|
||||||
if (_glfw.wl.dataSourceForPrimarySelection != NULL) return _glfw.wl.primarySelectionString;
|
|
||||||
|
|
||||||
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) {
|
|
||||||
if (d->is_self_offer) return _glfw.wl.primarySelectionString;
|
|
||||||
const char *mime = plain_text_mime_for_offer(d);
|
|
||||||
if (mime) {
|
|
||||||
free(_glfw.wl.pasteString);
|
|
||||||
_glfw.wl.pasteString = read_primary_selection_offer(d->id, mime);
|
|
||||||
return _glfw.wl.pasteString;
|
|
||||||
}
|
}
|
||||||
|
if (strcmp(mime_type, "text/plain") == 0) {
|
||||||
|
mime_type = plain_text_mime_for_offer(d);
|
||||||
|
if (!mime_type) return;
|
||||||
|
}
|
||||||
|
if (d->is_primary) {
|
||||||
|
read_primary_selection_offer(d->id, mime_type, write_data, object);
|
||||||
|
} else {
|
||||||
|
read_clipboard_data_offer(d->id, mime_type, write_data, object);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs UNUSED)
|
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs UNUSED)
|
||||||
@@ -2066,6 +2230,18 @@ GLFWAPI struct wl_surface* glfwGetWaylandWindow(GLFWwindow* handle)
|
|||||||
return window->wl.surface;
|
return window->wl.surface;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GLFWAPI void glfwWaylandActivateWindow(GLFWwindow* handle, const char *activation_token) {
|
||||||
|
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||||
|
_GLFW_REQUIRE_INIT();
|
||||||
|
if (activation_token && activation_token[0]) xdg_activation_v1_activate(_glfw.wl.xdg_activation_v1, activation_token, window->wl.surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
GLFWAPI void glfwWaylandRunWithActivationToken(GLFWwindow *handle, GLFWactivationcallback cb, void *cb_data) {
|
||||||
|
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||||
|
_GLFW_REQUIRE_INIT();
|
||||||
|
get_activation_token(window, _glfw.wl.input_serial, cb, cb_data);
|
||||||
|
}
|
||||||
|
|
||||||
GLFWAPI int glfwGetNativeKeyForName(const char* keyName, bool caseSensitive) {
|
GLFWAPI int glfwGetNativeKeyForName(const char* keyName, bool caseSensitive) {
|
||||||
return glfw_xkb_keysym_from_name(keyName, caseSensitive);
|
return glfw_xkb_keysym_from_name(keyName, caseSensitive);
|
||||||
}
|
}
|
||||||
@@ -2074,12 +2250,19 @@ GLFWAPI void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long
|
|||||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||||
static const struct wl_callback_listener frame_listener = { .done = frame_handle_redraw };
|
static const struct wl_callback_listener frame_listener = { .done = frame_handle_redraw };
|
||||||
if (window->wl.frameCallbackData.current_wl_callback) wl_callback_destroy(window->wl.frameCallbackData.current_wl_callback);
|
if (window->wl.frameCallbackData.current_wl_callback) wl_callback_destroy(window->wl.frameCallbackData.current_wl_callback);
|
||||||
window->wl.frameCallbackData.id = id;
|
if (window->wl.waiting_for_swap_to_commit) {
|
||||||
window->wl.frameCallbackData.callback = callback;
|
callback(id);
|
||||||
window->wl.frameCallbackData.current_wl_callback = wl_surface_frame(window->wl.surface);
|
window->wl.frameCallbackData.id = 0;
|
||||||
if (window->wl.frameCallbackData.current_wl_callback) {
|
window->wl.frameCallbackData.callback = NULL;
|
||||||
wl_callback_add_listener(window->wl.frameCallbackData.current_wl_callback, &frame_listener, window);
|
window->wl.frameCallbackData.current_wl_callback = NULL;
|
||||||
wl_surface_commit(window->wl.surface);
|
} else {
|
||||||
|
window->wl.frameCallbackData.id = id;
|
||||||
|
window->wl.frameCallbackData.callback = callback;
|
||||||
|
window->wl.frameCallbackData.current_wl_callback = wl_surface_frame(window->wl.surface);
|
||||||
|
if (window->wl.frameCallbackData.current_wl_callback) {
|
||||||
|
wl_callback_add_listener(window->wl.frameCallbackData.current_wl_callback, &frame_listener, window);
|
||||||
|
commit_window_surface_if_safe(window);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
14
glfw/x11_init.c
vendored
14
glfw/x11_init.c
vendored
@@ -137,6 +137,8 @@ static void detectEWMH(void)
|
|||||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE");
|
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE");
|
||||||
_glfw.x11.NET_WM_WINDOW_TYPE_NORMAL =
|
_glfw.x11.NET_WM_WINDOW_TYPE_NORMAL =
|
||||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE_NORMAL");
|
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE_NORMAL");
|
||||||
|
_glfw.x11.NET_WM_WINDOW_TYPE_DOCK =
|
||||||
|
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE_DOCK");
|
||||||
_glfw.x11.NET_WORKAREA =
|
_glfw.x11.NET_WORKAREA =
|
||||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WORKAREA");
|
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WORKAREA");
|
||||||
_glfw.x11.NET_CURRENT_DESKTOP =
|
_glfw.x11.NET_CURRENT_DESKTOP =
|
||||||
@@ -147,6 +149,8 @@ static void detectEWMH(void)
|
|||||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_FRAME_EXTENTS");
|
getAtomIfSupported(supportedAtoms, atomCount, "_NET_FRAME_EXTENTS");
|
||||||
_glfw.x11.NET_REQUEST_FRAME_EXTENTS =
|
_glfw.x11.NET_REQUEST_FRAME_EXTENTS =
|
||||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_REQUEST_FRAME_EXTENTS");
|
getAtomIfSupported(supportedAtoms, atomCount, "_NET_REQUEST_FRAME_EXTENTS");
|
||||||
|
_glfw.x11.NET_WM_STRUT_PARTIAL =
|
||||||
|
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STRUT_PARTIAL");
|
||||||
|
|
||||||
XFree(supportedAtoms);
|
XFree(supportedAtoms);
|
||||||
}
|
}
|
||||||
@@ -680,8 +684,14 @@ void _glfwPlatformTerminate(void)
|
|||||||
|
|
||||||
glfw_xkb_release(&_glfw.x11.xkb);
|
glfw_xkb_release(&_glfw.x11.xkb);
|
||||||
glfw_dbus_terminate(&_glfw.x11.dbus);
|
glfw_dbus_terminate(&_glfw.x11.dbus);
|
||||||
free(_glfw.x11.primarySelectionString);
|
if (_glfw.x11.mime_atoms.array) {
|
||||||
free(_glfw.x11.clipboardString);
|
for (size_t i = 0; i < _glfw.x11.mime_atoms.sz; i++) {
|
||||||
|
free((void*)_glfw.x11.mime_atoms.array[i].mime);
|
||||||
|
}
|
||||||
|
free(_glfw.x11.mime_atoms.array);
|
||||||
|
}
|
||||||
|
if (_glfw.x11.clipboard_atoms.array) { free(_glfw.x11.clipboard_atoms.array); }
|
||||||
|
if (_glfw.x11.primary_atoms.array) { free(_glfw.x11.primary_atoms.array); }
|
||||||
|
|
||||||
if (_glfw.x11.display)
|
if (_glfw.x11.display)
|
||||||
{
|
{
|
||||||
|
|||||||
18
glfw/x11_platform.h
vendored
18
glfw/x11_platform.h
vendored
@@ -207,6 +207,16 @@ typedef struct _GLFWwindowX11
|
|||||||
|
|
||||||
} _GLFWwindowX11;
|
} _GLFWwindowX11;
|
||||||
|
|
||||||
|
typedef struct MimeAtom {
|
||||||
|
Atom atom;
|
||||||
|
const char* mime;
|
||||||
|
} MimeAtom;
|
||||||
|
|
||||||
|
typedef struct AtomArray {
|
||||||
|
MimeAtom *array;
|
||||||
|
size_t sz, capacity;
|
||||||
|
} AtomArray;
|
||||||
|
|
||||||
// X11-specific global data
|
// X11-specific global data
|
||||||
//
|
//
|
||||||
typedef struct _GLFWlibraryX11
|
typedef struct _GLFWlibraryX11
|
||||||
@@ -225,10 +235,6 @@ typedef struct _GLFWlibraryX11
|
|||||||
XContext context;
|
XContext context;
|
||||||
// Most recent error code received by X error handler
|
// Most recent error code received by X error handler
|
||||||
int errorCode;
|
int errorCode;
|
||||||
// Primary selection string (while the primary selection is owned)
|
|
||||||
char* primarySelectionString;
|
|
||||||
// Clipboard string (while the selection is owned)
|
|
||||||
char* clipboardString;
|
|
||||||
// Where to place the cursor when re-enabled
|
// Where to place the cursor when re-enabled
|
||||||
double restoreCursorPosX, restoreCursorPosY;
|
double restoreCursorPosX, restoreCursorPosY;
|
||||||
// The window whose disabled cursor mode is active
|
// The window whose disabled cursor mode is active
|
||||||
@@ -247,6 +253,7 @@ typedef struct _GLFWlibraryX11
|
|||||||
Atom NET_WM_PING;
|
Atom NET_WM_PING;
|
||||||
Atom NET_WM_WINDOW_TYPE;
|
Atom NET_WM_WINDOW_TYPE;
|
||||||
Atom NET_WM_WINDOW_TYPE_NORMAL;
|
Atom NET_WM_WINDOW_TYPE_NORMAL;
|
||||||
|
Atom NET_WM_WINDOW_TYPE_DOCK;
|
||||||
Atom NET_WM_STATE;
|
Atom NET_WM_STATE;
|
||||||
Atom NET_WM_STATE_ABOVE;
|
Atom NET_WM_STATE_ABOVE;
|
||||||
Atom NET_WM_STATE_FULLSCREEN;
|
Atom NET_WM_STATE_FULLSCREEN;
|
||||||
@@ -262,6 +269,7 @@ typedef struct _GLFWlibraryX11
|
|||||||
Atom NET_ACTIVE_WINDOW;
|
Atom NET_ACTIVE_WINDOW;
|
||||||
Atom NET_FRAME_EXTENTS;
|
Atom NET_FRAME_EXTENTS;
|
||||||
Atom NET_REQUEST_FRAME_EXTENTS;
|
Atom NET_REQUEST_FRAME_EXTENTS;
|
||||||
|
Atom NET_WM_STRUT_PARTIAL;
|
||||||
Atom MOTIF_WM_HINTS;
|
Atom MOTIF_WM_HINTS;
|
||||||
|
|
||||||
// Xdnd (drag and drop) atoms
|
// Xdnd (drag and drop) atoms
|
||||||
@@ -291,6 +299,8 @@ typedef struct _GLFWlibraryX11
|
|||||||
|
|
||||||
// XRM database atom
|
// XRM database atom
|
||||||
Atom RESOURCE_MANAGER;
|
Atom RESOURCE_MANAGER;
|
||||||
|
// Atoms for MIME types
|
||||||
|
AtomArray mime_atoms, clipboard_atoms, primary_atoms;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
bool available;
|
bool available;
|
||||||
|
|||||||
374
glfw/x11_window.c
vendored
374
glfw/x11_window.c
vendored
@@ -716,19 +716,60 @@ static bool createNativeWindow(_GLFWwindow* window,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
get_clipboard_data(const _GLFWClipboardData *cd, const char *mime, char **data) {
|
||||||
|
*data = NULL;
|
||||||
|
GLFWDataChunk chunk = cd->get_data(mime, NULL, cd->ctype);
|
||||||
|
char *buf = NULL;
|
||||||
|
size_t sz = 0, cap = 0;
|
||||||
|
void *iter = chunk.iter;
|
||||||
|
if (!iter) return 0;
|
||||||
|
while (true) {
|
||||||
|
chunk = cd->get_data(mime, iter, cd->ctype);
|
||||||
|
if (!chunk.sz) break;
|
||||||
|
if (cap < sz + chunk.sz) {
|
||||||
|
cap = MAX(cap * 2, sz + 4 * chunk.sz);
|
||||||
|
buf = realloc(buf, cap * sizeof(buf[0]));
|
||||||
|
}
|
||||||
|
memcpy(buf + sz, chunk.data, chunk.sz);
|
||||||
|
sz += chunk.sz;
|
||||||
|
if (chunk.free) chunk.free((void*)chunk.free_data);
|
||||||
|
}
|
||||||
|
*data = buf;
|
||||||
|
cd->get_data(NULL, iter, cd->ctype);
|
||||||
|
return sz;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
get_atom_names(const Atom *atoms, int count, char **atom_names) {
|
||||||
|
_glfwGrabErrorHandlerX11();
|
||||||
|
XGetAtomNames(_glfw.x11.display, (Atom*)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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Set the specified property to the selection converted to the requested target
|
// Set the specified property to the selection converted to the requested target
|
||||||
//
|
//
|
||||||
static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
|
static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
|
||||||
{
|
{
|
||||||
int i;
|
const AtomArray *aa;
|
||||||
char* selectionString = NULL;
|
const _GLFWClipboardData *cd;
|
||||||
const Atom formats[] = { _glfw.x11.UTF8_STRING, XA_STRING };
|
|
||||||
const int formatCount = sizeof(formats) / sizeof(formats[0]);
|
|
||||||
|
|
||||||
if (request->selection == _glfw.x11.PRIMARY)
|
if (request->selection == _glfw.x11.PRIMARY) {
|
||||||
selectionString = _glfw.x11.primarySelectionString;
|
aa = &_glfw.x11.primary_atoms;
|
||||||
else
|
cd = &_glfw.primary;
|
||||||
selectionString = _glfw.x11.clipboardString;
|
} else {
|
||||||
|
aa = &_glfw.x11.clipboard_atoms;
|
||||||
|
cd = &_glfw.clipboard;
|
||||||
|
}
|
||||||
|
|
||||||
if (request->property == None)
|
if (request->property == None)
|
||||||
{
|
{
|
||||||
@@ -741,11 +782,10 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
|
|||||||
{
|
{
|
||||||
// The list of supported targets was requested
|
// The list of supported targets was requested
|
||||||
|
|
||||||
const Atom targets[] = { _glfw.x11.TARGETS,
|
Atom *targets = calloc(aa->sz + 2, sizeof(Atom));
|
||||||
_glfw.x11.MULTIPLE,
|
targets[0] = _glfw.x11.TARGETS;
|
||||||
_glfw.x11.UTF8_STRING,
|
targets[1] = _glfw.x11.MULTIPLE;
|
||||||
XA_STRING };
|
for (size_t i = 0; i < aa->sz; i++) targets[i+2] = aa->array[i].atom;
|
||||||
|
|
||||||
XChangeProperty(_glfw.x11.display,
|
XChangeProperty(_glfw.x11.display,
|
||||||
request->requestor,
|
request->requestor,
|
||||||
request->property,
|
request->property,
|
||||||
@@ -753,8 +793,8 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
|
|||||||
32,
|
32,
|
||||||
PropModeReplace,
|
PropModeReplace,
|
||||||
(unsigned char*) targets,
|
(unsigned char*) targets,
|
||||||
sizeof(targets) / sizeof(targets[0]));
|
aa->sz + 2);
|
||||||
|
free(targets);
|
||||||
return request->property;
|
return request->property;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -763,7 +803,7 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
|
|||||||
// Multiple conversions were requested
|
// Multiple conversions were requested
|
||||||
|
|
||||||
Atom* targets;
|
Atom* targets;
|
||||||
unsigned long i, count;
|
size_t i, j, count;
|
||||||
|
|
||||||
count = _glfwGetWindowPropertyX11(request->requestor,
|
count = _glfwGetWindowPropertyX11(request->requestor,
|
||||||
request->property,
|
request->property,
|
||||||
@@ -772,24 +812,25 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
|
|||||||
|
|
||||||
for (i = 0; i < count; i += 2)
|
for (i = 0; i < count; i += 2)
|
||||||
{
|
{
|
||||||
int j;
|
for (j = 0; j < aa->sz; j++)
|
||||||
|
|
||||||
for (j = 0; j < formatCount; j++)
|
|
||||||
{
|
{
|
||||||
if (targets[i] == formats[j])
|
if (targets[i] == aa->array[j].atom)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (j < formatCount)
|
if (j < aa->sz)
|
||||||
{
|
{
|
||||||
if (selectionString) XChangeProperty(_glfw.x11.display,
|
char *data = NULL; size_t sz = get_clipboard_data(cd, aa->array[j].mime, &data);
|
||||||
|
|
||||||
|
if (data) XChangeProperty(_glfw.x11.display,
|
||||||
request->requestor,
|
request->requestor,
|
||||||
targets[i + 1],
|
targets[i + 1],
|
||||||
targets[i],
|
targets[i],
|
||||||
8,
|
8,
|
||||||
PropModeReplace,
|
PropModeReplace,
|
||||||
(unsigned char *) selectionString,
|
(unsigned char *) data,
|
||||||
strlen(selectionString));
|
sz);
|
||||||
|
free(data);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
targets[i + 1] = None;
|
targets[i + 1] = None;
|
||||||
@@ -828,20 +869,22 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
|
|||||||
|
|
||||||
// Conversion to a data target was requested
|
// Conversion to a data target was requested
|
||||||
|
|
||||||
for (i = 0; i < formatCount; i++)
|
for (size_t i = 0; i < aa->sz; i++)
|
||||||
{
|
{
|
||||||
if (request->target == formats[i])
|
if (request->target == aa->array[i].atom)
|
||||||
{
|
{
|
||||||
// The requested target is one we support
|
// The requested target is one we support
|
||||||
|
|
||||||
if (selectionString) XChangeProperty(_glfw.x11.display,
|
char *data = NULL; size_t sz = get_clipboard_data(cd, aa->array[i].mime, &data);
|
||||||
|
if (data) XChangeProperty(_glfw.x11.display,
|
||||||
request->requestor,
|
request->requestor,
|
||||||
request->property,
|
request->property,
|
||||||
request->target,
|
request->target,
|
||||||
8,
|
8,
|
||||||
PropModeReplace,
|
PropModeReplace,
|
||||||
(unsigned char *) selectionString,
|
(unsigned char *) data,
|
||||||
strlen(selectionString));
|
sz);
|
||||||
|
free(data);
|
||||||
|
|
||||||
return request->property;
|
return request->property;
|
||||||
}
|
}
|
||||||
@@ -856,13 +899,11 @@ static void handleSelectionClear(XEvent* event)
|
|||||||
{
|
{
|
||||||
if (event->xselectionclear.selection == _glfw.x11.PRIMARY)
|
if (event->xselectionclear.selection == _glfw.x11.PRIMARY)
|
||||||
{
|
{
|
||||||
free(_glfw.x11.primarySelectionString);
|
_glfw_free_clipboard_data(&_glfw.primary);
|
||||||
_glfw.x11.primarySelectionString = NULL;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
free(_glfw.x11.clipboardString);
|
_glfw_free_clipboard_data(&_glfw.clipboard);
|
||||||
_glfw.x11.clipboardString = NULL;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -881,34 +922,21 @@ static void handleSelectionRequest(XEvent* event)
|
|||||||
XSendEvent(_glfw.x11.display, request->requestor, False, 0, &reply);
|
XSendEvent(_glfw.x11.display, request->requestor, False, 0, &reply);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char* getSelectionString(Atom selection)
|
static void
|
||||||
|
getSelectionString(Atom selection, Atom *targets, size_t num_targets, GLFWclipboardwritedatafun write_data, void *object, bool report_not_found)
|
||||||
{
|
{
|
||||||
char** selectionString = NULL;
|
#define XFREE(x) { if (x) XFree(x); x = NULL; }
|
||||||
const Atom targets[] = { _glfw.x11.UTF8_STRING, XA_STRING };
|
if (XGetSelectionOwner(_glfw.x11.display, selection) == _glfw.x11.helperWindowHandle) {
|
||||||
const size_t targetCount = sizeof(targets) / sizeof(targets[0]);
|
write_data(object, NULL, 1);
|
||||||
|
return;
|
||||||
if (selection == _glfw.x11.PRIMARY)
|
|
||||||
selectionString = &_glfw.x11.primarySelectionString;
|
|
||||||
else
|
|
||||||
selectionString = &_glfw.x11.clipboardString;
|
|
||||||
|
|
||||||
if (XGetSelectionOwner(_glfw.x11.display, selection) ==
|
|
||||||
_glfw.x11.helperWindowHandle)
|
|
||||||
{
|
|
||||||
// Instead of doing a large number of X round-trips just to put this
|
|
||||||
// string into a window property and then read it back, just return it
|
|
||||||
return *selectionString;
|
|
||||||
}
|
}
|
||||||
|
bool found = false;
|
||||||
free(*selectionString);
|
for (size_t i = 0; !found && i < num_targets; i++)
|
||||||
*selectionString = NULL;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < targetCount; i++)
|
|
||||||
{
|
{
|
||||||
char* data;
|
char* data = NULL;
|
||||||
Atom actualType;
|
Atom actualType = None;
|
||||||
int actualFormat;
|
int actualFormat = 0;
|
||||||
unsigned long itemCount, bytesAfter;
|
unsigned long itemCount = 0, bytesAfter = 0;
|
||||||
monotonic_t start = glfwGetTime();
|
monotonic_t start = glfwGetTime();
|
||||||
XEvent notification, dummy;
|
XEvent notification, dummy;
|
||||||
|
|
||||||
@@ -925,8 +953,7 @@ static const char* getSelectionString(Atom selection)
|
|||||||
¬ification))
|
¬ification))
|
||||||
{
|
{
|
||||||
monotonic_t time = glfwGetTime();
|
monotonic_t time = glfwGetTime();
|
||||||
if (time - start > s_to_monotonic_t(2ll))
|
if (time - start > s_to_monotonic_t(2ll)) return;
|
||||||
return "";
|
|
||||||
waitForX11Event(s_to_monotonic_t(2ll) - (time - start));
|
waitForX11Event(s_to_monotonic_t(2ll) - (time - start));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -953,9 +980,6 @@ static const char* getSelectionString(Atom selection)
|
|||||||
|
|
||||||
if (actualType == _glfw.x11.INCR)
|
if (actualType == _glfw.x11.INCR)
|
||||||
{
|
{
|
||||||
size_t size = 1;
|
|
||||||
char* string = NULL;
|
|
||||||
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
start = glfwGetTime();
|
start = glfwGetTime();
|
||||||
@@ -966,13 +990,12 @@ static const char* getSelectionString(Atom selection)
|
|||||||
{
|
{
|
||||||
monotonic_t time = glfwGetTime();
|
monotonic_t time = glfwGetTime();
|
||||||
if (time - start > s_to_monotonic_t(2ll)) {
|
if (time - start > s_to_monotonic_t(2ll)) {
|
||||||
free(string);
|
return;
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
waitForX11Event(s_to_monotonic_t(2ll) - (time - start));
|
waitForX11Event(s_to_monotonic_t(2ll) - (time - start));
|
||||||
}
|
}
|
||||||
|
|
||||||
XFree(data);
|
XFREE(data);
|
||||||
XGetWindowProperty(_glfw.x11.display,
|
XGetWindowProperty(_glfw.x11.display,
|
||||||
notification.xselection.requestor,
|
notification.xselection.requestor,
|
||||||
notification.xselection.property,
|
notification.xselection.property,
|
||||||
@@ -988,47 +1011,41 @@ static const char* getSelectionString(Atom selection)
|
|||||||
|
|
||||||
if (itemCount)
|
if (itemCount)
|
||||||
{
|
{
|
||||||
size += itemCount;
|
const char *string = data;
|
||||||
string = realloc(string, size);
|
if (targets[i] == XA_STRING) {
|
||||||
string[size - itemCount - 1] = '\0';
|
string = convertLatin1toUTF8(data);
|
||||||
strcat(string, data);
|
itemCount = strlen(string);
|
||||||
}
|
|
||||||
|
|
||||||
if (!itemCount)
|
|
||||||
{
|
|
||||||
if (targets[i] == XA_STRING)
|
|
||||||
{
|
|
||||||
*selectionString = convertLatin1toUTF8(string);
|
|
||||||
free(string);
|
|
||||||
}
|
}
|
||||||
else
|
bool ok = write_data(object, string, itemCount);
|
||||||
*selectionString = string;
|
if (string != data) free((void*)string);
|
||||||
|
if (!ok) { XFREE(data); break; }
|
||||||
|
} else { found = true; break; }
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (actualType == targets[i])
|
else if (actualType == targets[i])
|
||||||
{
|
{
|
||||||
if (targets[i] == XA_STRING)
|
if (targets[i] == XA_STRING) {
|
||||||
*selectionString = convertLatin1toUTF8(data);
|
const char *string = convertLatin1toUTF8(data);
|
||||||
else
|
write_data(object, string, strlen(string)); free((void*)string);
|
||||||
*selectionString = _glfw_strdup(data);
|
} else write_data(object, data, itemCount);
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
else if (actualType == XA_ATOM && targets[i] == _glfw.x11.TARGETS) {
|
||||||
|
found = true;
|
||||||
|
write_data(object, data, sizeof(Atom) * itemCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
XFree(data);
|
XFREE(data);
|
||||||
|
|
||||||
if (*selectionString)
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!*selectionString)
|
if (!found && report_not_found)
|
||||||
{
|
{
|
||||||
_glfwInputError(GLFW_FORMAT_UNAVAILABLE,
|
_glfwInputError(GLFW_FORMAT_UNAVAILABLE,
|
||||||
"X11: Failed to convert selection to string");
|
"X11: Failed to convert selection to data from clipboard");
|
||||||
}
|
}
|
||||||
|
#undef XFREE
|
||||||
return *selectionString;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make the specified window and its video mode active on its monitor
|
// Make the specified window and its video mode active on its monitor
|
||||||
@@ -1098,21 +1115,6 @@ 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
|
// Process the specified X event
|
||||||
//
|
//
|
||||||
static void processEvent(XEvent *event)
|
static void processEvent(XEvent *event)
|
||||||
@@ -2849,51 +2851,120 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor UNUSED)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformSetClipboardString(const char* string)
|
static MimeAtom atom_for_mime(const char *mime) {
|
||||||
{
|
for (size_t i = 0; i < _glfw.x11.mime_atoms.sz; i++) {
|
||||||
char* copy = _glfw_strdup(string);
|
MimeAtom ma = _glfw.x11.mime_atoms.array[i];
|
||||||
free(_glfw.x11.clipboardString);
|
if (strcmp(ma.mime, mime) == 0) {
|
||||||
_glfw.x11.clipboardString = copy;
|
return ma;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MimeAtom ma = {.mime=_glfw_strdup(mime), .atom=XInternAtom(_glfw.x11.display, mime, 0)};
|
||||||
|
if (_glfw.x11.mime_atoms.capacity < _glfw.x11.mime_atoms.sz + 1) {
|
||||||
|
_glfw.x11.mime_atoms.capacity += 32;
|
||||||
|
_glfw.x11.mime_atoms.array = realloc(_glfw.x11.mime_atoms.array, _glfw.x11.mime_atoms.capacity * sizeof(_glfw.x11.mime_atoms.array[0]));
|
||||||
|
}
|
||||||
|
_glfw.x11.mime_atoms.array[_glfw.x11.mime_atoms.sz++] = ma;
|
||||||
|
return ma;
|
||||||
|
}
|
||||||
|
|
||||||
XSetSelectionOwner(_glfw.x11.display,
|
void _glfwPlatformSetClipboard(GLFWClipboardType t) {
|
||||||
_glfw.x11.CLIPBOARD,
|
Atom which = None;
|
||||||
_glfw.x11.helperWindowHandle,
|
_GLFWClipboardData *cd = NULL;
|
||||||
CurrentTime);
|
AtomArray *aa = NULL;
|
||||||
|
switch (t) {
|
||||||
if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.CLIPBOARD) !=
|
case GLFW_CLIPBOARD: which = _glfw.x11.CLIPBOARD; cd = &_glfw.clipboard; aa = &_glfw.x11.clipboard_atoms; break;
|
||||||
_glfw.x11.helperWindowHandle)
|
case GLFW_PRIMARY_SELECTION: which = _glfw.x11.PRIMARY; cd = &_glfw.primary; aa = &_glfw.x11.primary_atoms; break;
|
||||||
{
|
}
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
XSetSelectionOwner(_glfw.x11.display, which, _glfw.x11.helperWindowHandle, CurrentTime);
|
||||||
"X11: Failed to become owner of clipboard selection");
|
if (XGetSelectionOwner(_glfw.x11.display, which) != _glfw.x11.helperWindowHandle) {
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to become owner of clipboard selection");
|
||||||
|
}
|
||||||
|
if (aa->capacity < cd->num_mime_types + 32) {
|
||||||
|
aa->capacity = cd->num_mime_types + 32;
|
||||||
|
aa->array = reallocarray(aa->array, aa->capacity, sizeof(aa->array[0]));
|
||||||
|
}
|
||||||
|
aa->sz = 0;
|
||||||
|
for (size_t i = 0; i < cd->num_mime_types; i++) {
|
||||||
|
MimeAtom *a = aa->array + aa->sz++;
|
||||||
|
*a = atom_for_mime(cd->mime_types[i]);
|
||||||
|
if (strcmp(cd->mime_types[i], "text/plain") == 0) {
|
||||||
|
a = aa->array + aa->sz++;
|
||||||
|
a->atom = _glfw.x11.UTF8_STRING;
|
||||||
|
a->mime = "text/plain";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* _glfwPlatformGetClipboardString(void)
|
typedef struct chunked_writer {
|
||||||
{
|
char *buf; size_t sz, cap;
|
||||||
return getSelectionString(_glfw.x11.CLIPBOARD);
|
bool is_self_offer;
|
||||||
|
} chunked_writer;
|
||||||
|
|
||||||
|
static bool
|
||||||
|
write_chunk(void *object, const char *data, size_t sz) {
|
||||||
|
chunked_writer *cw = object;
|
||||||
|
if (data) {
|
||||||
|
if (cw->cap < cw->sz + sz) {
|
||||||
|
cw->cap = MAX(cw->cap * 2, cw->sz + 8*sz);
|
||||||
|
cw->buf = realloc(cw->buf, cw->cap * sizeof(cw->buf[0]));
|
||||||
|
}
|
||||||
|
memcpy(cw->buf + cw->sz, data, sz);
|
||||||
|
cw->sz += sz;
|
||||||
|
} else if (sz == 1) cw->is_self_offer = true;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformSetPrimarySelectionString(const char* string)
|
static void
|
||||||
{
|
get_available_mime_types(Atom which_clipboard, GLFWclipboardwritedatafun write_data, void *object) {
|
||||||
free(_glfw.x11.primarySelectionString);
|
chunked_writer cw = {0};
|
||||||
_glfw.x11.primarySelectionString = _glfw_strdup(string);
|
getSelectionString(which_clipboard, &_glfw.x11.TARGETS, 1, write_chunk, &cw, false);
|
||||||
|
if (cw.is_self_offer) {
|
||||||
XSetSelectionOwner(_glfw.x11.display,
|
write_data(object, NULL, 1);
|
||||||
_glfw.x11.PRIMARY,
|
return;
|
||||||
_glfw.x11.helperWindowHandle,
|
}
|
||||||
CurrentTime);
|
size_t count = 0;
|
||||||
|
bool ok = true;
|
||||||
if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.PRIMARY) !=
|
if (cw.buf) {
|
||||||
_glfw.x11.helperWindowHandle)
|
Atom *atoms = (Atom*)cw.buf;
|
||||||
{
|
count = cw.sz / sizeof(Atom);
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
char **names = calloc(count, sizeof(char*));
|
||||||
"X11: Failed to become owner of primary selection");
|
get_atom_names(atoms, count, names);
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
if (strchr(names[i], '/')) {
|
||||||
|
if (ok) ok = write_data(object, names[i], strlen(names[i]));
|
||||||
|
} else {
|
||||||
|
if (atoms[i] == _glfw.x11.UTF8_STRING || atoms[i] == XA_STRING) {
|
||||||
|
if (ok) ok = write_data(object, "text/plain", strlen("text/plain"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
XFree(names[i]);
|
||||||
|
}
|
||||||
|
free(cw.buf);
|
||||||
|
free(names);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* _glfwPlatformGetPrimarySelectionString(void)
|
void
|
||||||
{
|
_glfwPlatformGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object) {
|
||||||
return getSelectionString(_glfw.x11.PRIMARY);
|
Atom atoms[4], which = clipboard_type == GLFW_PRIMARY_SELECTION ? _glfw.x11.PRIMARY : _glfw.x11.CLIPBOARD;
|
||||||
|
if (mime_type == NULL) {
|
||||||
|
get_available_mime_types(which, write_data, object);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
size_t count = 0;
|
||||||
|
if (strcmp(mime_type, "text/plain") == 0) {
|
||||||
|
// UTF8_STRING is what xclip uses by default, and there are people out there that expect to be able to paste from it with a single read operation. See https://github.com/kovidgoyal/kitty/issues/5842
|
||||||
|
// Also ancient versions of GNOME use DOS line endings even for text/plain;charset=utf-8. See https://github.com/kovidgoyal/kitty/issues/5528#issuecomment-1325348218
|
||||||
|
atoms[count++] = _glfw.x11.UTF8_STRING;
|
||||||
|
// we need to do this because GTK/GNOME is moronic they convert text/plain to DOS line endings, see
|
||||||
|
// https://gitlab.gnome.org/GNOME/gtk/-/issues/2307
|
||||||
|
atoms[count++] = atom_for_mime("text/plain;charset=utf-8").atom;
|
||||||
|
atoms[count++] = atom_for_mime("text/plain").atom;
|
||||||
|
atoms[count++] = XA_STRING;
|
||||||
|
} else {
|
||||||
|
atoms[count++] = atom_for_mime(mime_type).atom;
|
||||||
|
}
|
||||||
|
getSelectionString(which, atoms, count, write_data, object, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs)
|
EGLenum _glfwPlatformGetEGLPlatform(EGLint** attribs)
|
||||||
@@ -3134,3 +3205,18 @@ GLFWAPI int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc)
|
|||||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||||
return XSetCommand(_glfw.x11.display, window->x11.handle, argv, argc);
|
return XSetCommand(_glfw.x11.display, window->x11.handle, argv, argc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GLFWAPI void glfwSetX11WindowAsDock(int32_t x11_window_id) {
|
||||||
|
_GLFW_REQUIRE_INIT();
|
||||||
|
Atom type = _glfw.x11.NET_WM_WINDOW_TYPE_DOCK;
|
||||||
|
XChangeProperty(_glfw.x11.display, x11_window_id,
|
||||||
|
_glfw.x11.NET_WM_WINDOW_TYPE, XA_ATOM, 32,
|
||||||
|
PropModeReplace, (unsigned char*) &type, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
GLFWAPI void glfwSetX11WindowStrut(int32_t x11_window_id, uint32_t dimensions[12]) {
|
||||||
|
_GLFW_REQUIRE_INIT();
|
||||||
|
XChangeProperty(_glfw.x11.display, x11_window_id,
|
||||||
|
_glfw.x11.NET_WM_STRUT_PARTIAL, XA_CARDINAL, 32,
|
||||||
|
PropModeReplace, (unsigned char*) dimensions, 12);
|
||||||
|
}
|
||||||
|
|||||||
17
go.mod
Normal file
17
go.mod
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
module kitty
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/ALTree/bigfloat v0.0.0-20220102081255-38c8b72a9924
|
||||||
|
github.com/disintegration/imaging v1.6.2
|
||||||
|
github.com/google/go-cmp v0.5.8
|
||||||
|
github.com/google/uuid v1.3.0
|
||||||
|
github.com/jamesruan/go-rfc1924 v0.0.0-20170108144916-2767ca7c638f
|
||||||
|
github.com/seancfoley/ipaddress-go v1.5.3
|
||||||
|
golang.org/x/exp v0.0.0-20230202163644-54bba9f4231b
|
||||||
|
golang.org/x/image v0.3.0
|
||||||
|
golang.org/x/sys v0.4.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require github.com/seancfoley/bintree v1.2.1 // indirect
|
||||||
45
go.sum
Normal file
45
go.sum
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
github.com/ALTree/bigfloat v0.0.0-20220102081255-38c8b72a9924 h1:DG4UyTVIujioxwJc8Zj8Nabz1L1wTgQ/xNBSQDfdP3I=
|
||||||
|
github.com/ALTree/bigfloat v0.0.0-20220102081255-38c8b72a9924/go.mod h1:+NaH2gLeY6RPBPPQf4aRotPPStg+eXc8f9ZaE4vRfD4=
|
||||||
|
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||||
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
|
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||||
|
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/jamesruan/go-rfc1924 v0.0.0-20170108144916-2767ca7c638f h1:Ko4+g6K16vSyUrtd/pPXuQnWsiHe5BYptEtTxfwYwCc=
|
||||||
|
github.com/jamesruan/go-rfc1924 v0.0.0-20170108144916-2767ca7c638f/go.mod h1:eHzfhOKbTGJEGPSdMHzU6jft192tHHt2Bu2vIZArvC0=
|
||||||
|
github.com/seancfoley/bintree v1.2.1 h1:Z/iNjRKkXnn0CTW7jDQYtjW5fz2GH1yWvOTJ4MrMvdo=
|
||||||
|
github.com/seancfoley/bintree v1.2.1/go.mod h1:hIUabL8OFYyFVTQ6azeajbopogQc2l5C/hiXMcemWNU=
|
||||||
|
github.com/seancfoley/ipaddress-go v1.5.3 h1:fLnn4nsatd2rp3IJsVWriXv5gXn2Qiy8uxjxe4iZtTg=
|
||||||
|
github.com/seancfoley/ipaddress-go v1.5.3/go.mod h1:fpvVPC+Jso+YEhNcNiww8HQmBgKP8T4T6BTp1SLxxIo=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/exp v0.0.0-20230202163644-54bba9f4231b h1:EqBVA+nNsObCwQoBEHy4wLU0pi7i8a4AL3pbItPdPkE=
|
||||||
|
golang.org/x/exp v0.0.0-20230202163644-54bba9f4231b/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
||||||
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/image v0.3.0 h1:HTDXbdK9bjfSWkPzDJIw89W8CAtfFGduujWs33NLLsg=
|
||||||
|
golang.org/x/image v0.3.0/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||||
|
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@@ -2,25 +2,35 @@
|
|||||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING, Callable, Dict, List, NamedTuple, Optional, Tuple
|
TYPE_CHECKING,
|
||||||
|
Callable,
|
||||||
|
Dict,
|
||||||
|
Iterator,
|
||||||
|
List,
|
||||||
|
NamedTuple,
|
||||||
|
Optional,
|
||||||
|
Tuple,
|
||||||
)
|
)
|
||||||
|
|
||||||
from kitty.cli import parse_args
|
from kitty.cli import parse_args
|
||||||
from kitty.cli_stub import AskCLIOptions
|
from kitty.cli_stub import AskCLIOptions
|
||||||
from kitty.constants import cache_dir
|
from kitty.constants import cache_dir
|
||||||
from kitty.fast_data_types import truncate_point_for_length, wcswidth
|
from kitty.fast_data_types import truncate_point_for_length, wcswidth
|
||||||
|
from kitty.types import run_once
|
||||||
from kitty.typing import BossType, KeyEventType, TypedDict
|
from kitty.typing import BossType, KeyEventType, TypedDict
|
||||||
from kitty.utils import ScreenSize
|
from kitty.utils import ScreenSize
|
||||||
|
|
||||||
from ..tui.handler import Handler, result_handler
|
from ..tui.handler import Handler, result_handler
|
||||||
from ..tui.loop import Loop, MouseEvent
|
from ..tui.loop import Loop, MouseEvent, debug
|
||||||
from ..tui.operations import MouseTracking, alternate_screen, styled
|
from ..tui.operations import MouseTracking, alternate_screen, styled
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import readline
|
import readline
|
||||||
|
debug
|
||||||
else:
|
else:
|
||||||
readline = None
|
readline = None
|
||||||
|
|
||||||
@@ -108,6 +118,15 @@ The default choice is selected when the user presses the :kbd:`Enter` key.
|
|||||||
--prompt -p
|
--prompt -p
|
||||||
default="> "
|
default="> "
|
||||||
The prompt to use when inputting a line of text or a password.
|
The prompt to use when inputting a line of text or a password.
|
||||||
|
|
||||||
|
|
||||||
|
--unhide-key
|
||||||
|
default=u
|
||||||
|
The key to be pressed to unhide hidden text
|
||||||
|
|
||||||
|
|
||||||
|
--hidden-text-placeholder
|
||||||
|
The text in the message to be replaced by hidden text. The hidden text is read via STDIN.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
@@ -147,14 +166,17 @@ def extra_for(width: int, screen_width: int) -> int:
|
|||||||
|
|
||||||
class Password(Handler):
|
class Password(Handler):
|
||||||
|
|
||||||
def __init__(self, cli_opts: AskCLIOptions, prompt: str) -> None:
|
def __init__(self, cli_opts: AskCLIOptions, prompt: str, is_password: bool = True, initial_text: str = '') -> None:
|
||||||
self.cli_opts = cli_opts
|
self.cli_opts = cli_opts
|
||||||
self.prompt = prompt
|
self.prompt = prompt
|
||||||
|
self.initial_text = initial_text
|
||||||
from kittens.tui.line_edit import LineEdit
|
from kittens.tui.line_edit import LineEdit
|
||||||
self.line_edit = LineEdit(is_password=True)
|
self.line_edit = LineEdit(is_password=is_password)
|
||||||
|
|
||||||
def initialize(self) -> None:
|
def initialize(self) -> None:
|
||||||
self.cmd.set_cursor_shape('beam')
|
self.cmd.set_cursor_shape('beam')
|
||||||
|
if self.initial_text:
|
||||||
|
self.line_edit.on_text(self.initial_text, True)
|
||||||
self.draw_screen()
|
self.draw_screen()
|
||||||
|
|
||||||
@Handler.atomic_update
|
@Handler.atomic_update
|
||||||
@@ -194,10 +216,11 @@ class Password(Handler):
|
|||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
|
||||||
class Choose(Handler):
|
class Choose(Handler): # {{{
|
||||||
mouse_tracking = MouseTracking.buttons_only
|
mouse_tracking = MouseTracking.buttons_only
|
||||||
|
|
||||||
def __init__(self, cli_opts: AskCLIOptions) -> None:
|
def __init__(self, cli_opts: AskCLIOptions) -> None:
|
||||||
|
self.prefix_style_pat = re.compile(r'(?:\x1b\[[^m]*?m)+')
|
||||||
self.cli_opts = cli_opts
|
self.cli_opts = cli_opts
|
||||||
self.choices: Dict[str, Choice] = {}
|
self.choices: Dict[str, Choice] = {}
|
||||||
self.clickable_ranges: Dict[str, List[Range]] = {}
|
self.clickable_ranges: Dict[str, List[Range]] = {}
|
||||||
@@ -219,6 +242,18 @@ class Choose(Handler):
|
|||||||
self.response_on_accept = cli_opts.default or ''
|
self.response_on_accept = cli_opts.default or ''
|
||||||
if cli_opts.type in ('yesno', 'choices') and self.response_on_accept not in self.allowed:
|
if cli_opts.type in ('yesno', 'choices') and self.response_on_accept not in self.allowed:
|
||||||
self.response_on_accept = 'y' if cli_opts.type == 'yesno' else tuple(self.choices.keys())[0]
|
self.response_on_accept = 'y' if cli_opts.type == 'yesno' else tuple(self.choices.keys())[0]
|
||||||
|
self.message = cli_opts.message
|
||||||
|
self.hidden_text_start_pos = self.hidden_text_end_pos = -1
|
||||||
|
self.hidden_text = ''
|
||||||
|
self.replacement_text = t = f'Press {styled(self.cli_opts.unhide_key, fg="green")} or click to show'
|
||||||
|
self.replacement_range = Range(-1, -1, -1)
|
||||||
|
if self.message and self.cli_opts.hidden_text_placeholder:
|
||||||
|
self.hidden_text_start_pos = self.message.find(self.cli_opts.hidden_text_placeholder)
|
||||||
|
if self.hidden_text_start_pos > -1:
|
||||||
|
self.hidden_text = sys.stdin.read().rstrip()
|
||||||
|
self.hidden_text_end_pos = self.hidden_text_start_pos + len(t)
|
||||||
|
suffix = self.message[self.hidden_text_start_pos + len(self.cli_opts.hidden_text_placeholder):]
|
||||||
|
self.message = self.message[:self.hidden_text_start_pos] + t + suffix
|
||||||
|
|
||||||
def initialize(self) -> None:
|
def initialize(self) -> None:
|
||||||
self.cmd.set_cursor_visible(False)
|
self.cmd.set_cursor_visible(False)
|
||||||
@@ -227,24 +262,35 @@ class Choose(Handler):
|
|||||||
def finalize(self) -> None:
|
def finalize(self) -> None:
|
||||||
self.cmd.set_cursor_visible(True)
|
self.cmd.set_cursor_visible(True)
|
||||||
|
|
||||||
def draw_long_text(self, text: str) -> int:
|
def draw_long_text(self, text: str) -> Iterator[str]:
|
||||||
y = 0
|
if not text:
|
||||||
|
yield ''
|
||||||
|
return
|
||||||
width = self.screen_size.cols - 2
|
width = self.screen_size.cols - 2
|
||||||
|
m = self.prefix_style_pat.match(text)
|
||||||
|
prefix = m.group() if m else ''
|
||||||
while text:
|
while text:
|
||||||
t, text = truncate_at_space(text, width)
|
t, text = truncate_at_space(text, width)
|
||||||
t = t.strip()
|
t = t.strip()
|
||||||
self.print(' ' * extra_for(wcswidth(t), width), styled(t, bold=True), sep='')
|
yield ' ' * extra_for(wcswidth(t), width) + styled(prefix + t, bold=True)
|
||||||
y += 1
|
|
||||||
return y
|
|
||||||
|
|
||||||
@Handler.atomic_update
|
@Handler.atomic_update
|
||||||
def draw_screen(self) -> None:
|
def draw_screen(self) -> None:
|
||||||
self.cmd.clear_screen()
|
self.cmd.clear_screen()
|
||||||
y = max(0, self.screen_size.rows // 2 - 2)
|
msg_lines: List[str] = []
|
||||||
|
if self.message:
|
||||||
|
for line in self.message.splitlines():
|
||||||
|
msg_lines.extend(self.draw_long_text(line))
|
||||||
|
y = self.screen_size.rows - len(msg_lines)
|
||||||
|
y = max(0, (y // 2) - 2)
|
||||||
self.print(end='\r\n'*y)
|
self.print(end='\r\n'*y)
|
||||||
if self.cli_opts.message:
|
for line in msg_lines:
|
||||||
for line in self.cli_opts.message.splitlines():
|
if self.replacement_text in line:
|
||||||
y += self.draw_long_text(line)
|
idx = line.find(self.replacement_text)
|
||||||
|
x = wcswidth(line[:idx])
|
||||||
|
self.replacement_range = Range(x, x + wcswidth(self.replacement_text), y)
|
||||||
|
self.print(line)
|
||||||
|
y += 1
|
||||||
if self.screen_size.rows > 2:
|
if self.screen_size.rows > 2:
|
||||||
self.print()
|
self.print()
|
||||||
y += 1
|
y += 1
|
||||||
@@ -259,7 +305,8 @@ class Choose(Handler):
|
|||||||
current_line_length = 0
|
current_line_length = 0
|
||||||
current_line: List[Tuple[str, str]] = []
|
current_line: List[Tuple[str, str]] = []
|
||||||
lines: List[List[Tuple[str, str]]] = []
|
lines: List[List[Tuple[str, str]]] = []
|
||||||
sep, sep_sz = ' ', 2
|
sep = ' '
|
||||||
|
sep_sz = len(sep) + 2 # for the borders
|
||||||
|
|
||||||
for choice in choices:
|
for choice in choices:
|
||||||
self.clickable_ranges[choice.letter] = []
|
self.clickable_ranges[choice.letter] = []
|
||||||
@@ -272,7 +319,7 @@ class Choose(Handler):
|
|||||||
current_line = []
|
current_line = []
|
||||||
current_line_length = 0
|
current_line_length = 0
|
||||||
current_line.append((choice.letter, text))
|
current_line.append((choice.letter, text))
|
||||||
current_line_length += sz
|
current_line_length += sz + sep_sz
|
||||||
if current_line:
|
if current_line:
|
||||||
lines.append(current_line)
|
lines.append(current_line)
|
||||||
|
|
||||||
@@ -311,10 +358,12 @@ class Choose(Handler):
|
|||||||
self.print(' ' * offset, line, sep='', end='' if is_last else '\r\n')
|
self.print(' ' * offset, line, sep='', end='' if is_last else '\r\n')
|
||||||
y += 1
|
y += 1
|
||||||
|
|
||||||
|
self.cmd.set_line_wrapping(False)
|
||||||
for boxed_line in lines:
|
for boxed_line in lines:
|
||||||
print_line(top, *boxed_line)
|
print_line(top, *boxed_line)
|
||||||
print_line(middle, *boxed_line)
|
print_line(middle, *boxed_line)
|
||||||
print_line(bottom, *boxed_line, is_last=boxed_line is lines[-1])
|
print_line(bottom, *boxed_line, is_last=boxed_line is lines[-1])
|
||||||
|
self.cmd.set_line_wrapping(True)
|
||||||
|
|
||||||
def draw_choice(self, y: int) -> None:
|
def draw_choice(self, y: int) -> None:
|
||||||
if y + 3 <= self.screen_size.rows:
|
if y + 3 <= self.screen_size.rows:
|
||||||
@@ -370,6 +419,14 @@ class Choose(Handler):
|
|||||||
self.quit_loop(0)
|
self.quit_loop(0)
|
||||||
elif self.cli_opts.type == 'yesno':
|
elif self.cli_opts.type == 'yesno':
|
||||||
self.on_interrupt()
|
self.on_interrupt()
|
||||||
|
elif self.hidden_text and text == self.cli_opts.unhide_key:
|
||||||
|
self.unhide()
|
||||||
|
|
||||||
|
def unhide(self) -> None:
|
||||||
|
if self.hidden_text and self.message:
|
||||||
|
self.message = self.message[:self.hidden_text_start_pos] + self.hidden_text + self.message[self.hidden_text_end_pos:]
|
||||||
|
self.hidden_text = ''
|
||||||
|
self.draw_screen()
|
||||||
|
|
||||||
def on_key(self, key_event: KeyEventType) -> None:
|
def on_key(self, key_event: KeyEventType) -> None:
|
||||||
if key_event.matches('esc'):
|
if key_event.matches('esc'):
|
||||||
@@ -385,6 +442,8 @@ class Choose(Handler):
|
|||||||
self.response = letter
|
self.response = letter
|
||||||
self.quit_loop(0)
|
self.quit_loop(0)
|
||||||
return
|
return
|
||||||
|
if self.hidden_text and self.replacement_range.has_point(ev.cell_x, ev.cell_y):
|
||||||
|
self.unhide()
|
||||||
|
|
||||||
def on_resize(self, screen_size: ScreenSize) -> None:
|
def on_resize(self, screen_size: ScreenSize) -> None:
|
||||||
self.screen_size = screen_size
|
self.screen_size = screen_size
|
||||||
@@ -393,6 +452,18 @@ class Choose(Handler):
|
|||||||
def on_interrupt(self) -> None:
|
def on_interrupt(self) -> None:
|
||||||
self.quit_loop(1)
|
self.quit_loop(1)
|
||||||
on_eot = on_interrupt
|
on_eot = on_interrupt
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
|
@run_once
|
||||||
|
def init_readline() -> None:
|
||||||
|
import readline
|
||||||
|
with suppress(OSError):
|
||||||
|
readline.read_init_file()
|
||||||
|
if 'libedit' in readline.__doc__:
|
||||||
|
readline.parse_and_bind("bind ^I rl_complete")
|
||||||
|
else:
|
||||||
|
readline.parse_and_bind('tab: complete')
|
||||||
|
|
||||||
|
|
||||||
def main(args: List[str]) -> Response:
|
def main(args: List[str]) -> Response:
|
||||||
@@ -402,7 +473,7 @@ def main(args: List[str]) -> Response:
|
|||||||
global readline
|
global readline
|
||||||
msg = 'Ask the user for input'
|
msg = 'Ask the user for input'
|
||||||
try:
|
try:
|
||||||
cli_opts, items = parse_args(args[1:], option_text, '', msg, 'kitty ask', result_class=AskCLIOptions)
|
cli_opts, items = parse_args(args[1:], option_text, '', msg, 'kitty +kitten ask', result_class=AskCLIOptions)
|
||||||
except SystemExit as e:
|
except SystemExit as e:
|
||||||
if e.code != 0:
|
if e.code != 0:
|
||||||
print(e.args[0])
|
print(e.args[0])
|
||||||
@@ -424,17 +495,22 @@ def main(args: List[str]) -> Response:
|
|||||||
loop.loop(phandler)
|
loop.loop(phandler)
|
||||||
return {'items': items, 'response': phandler.response}
|
return {'items': items, 'response': phandler.response}
|
||||||
|
|
||||||
import readline as rl
|
# we do this file descriptor dance to get readline to work even when STDOUT
|
||||||
readline = rl
|
# is redirected
|
||||||
from kitty.shell import init_readline
|
orig_stdout = os.dup(sys.stdout.fileno())
|
||||||
init_readline()
|
try:
|
||||||
response = None
|
with open(os.ctermid(), 'r') as tty:
|
||||||
|
os.dup2(tty.fileno(), sys.stdin.fileno())
|
||||||
|
with open(os.ctermid(), 'w') as tty:
|
||||||
|
os.dup2(tty.fileno(), sys.stdout.fileno())
|
||||||
|
import readline as rl
|
||||||
|
readline = rl
|
||||||
|
init_readline()
|
||||||
|
response = None
|
||||||
|
|
||||||
with alternate_screen(), HistoryCompleter(cli_opts.name):
|
with alternate_screen(), HistoryCompleter(cli_opts.name), suppress(KeyboardInterrupt, EOFError):
|
||||||
if cli_opts.message:
|
if cli_opts.message:
|
||||||
print(styled(cli_opts.message, bold=True))
|
print(styled(cli_opts.message, bold=True))
|
||||||
|
|
||||||
with suppress(KeyboardInterrupt, EOFError):
|
|
||||||
if cli_opts.default:
|
if cli_opts.default:
|
||||||
def prefill_text() -> None:
|
def prefill_text() -> None:
|
||||||
readline.insert_text(cli_opts.default or '')
|
readline.insert_text(cli_opts.default or '')
|
||||||
@@ -444,6 +520,10 @@ def main(args: List[str]) -> Response:
|
|||||||
readline.set_pre_input_hook()
|
readline.set_pre_input_hook()
|
||||||
else:
|
else:
|
||||||
response = input(prompt)
|
response = input(prompt)
|
||||||
|
sys.stdout.flush()
|
||||||
|
os.dup2(orig_stdout, sys.stdout.fileno())
|
||||||
|
finally:
|
||||||
|
os.close(orig_stdout)
|
||||||
return {'items': items, 'response': response}
|
return {'items': items, 'response': response}
|
||||||
|
|
||||||
|
|
||||||
@@ -457,4 +537,5 @@ def handle_result(args: List[str], data: Response, target_window_id: int, boss:
|
|||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
ans = main(sys.argv)
|
ans = main(sys.argv)
|
||||||
if ans:
|
if ans:
|
||||||
print(ans)
|
import json
|
||||||
|
print(json.dumps(ans))
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class Broadcast(Handler):
|
|||||||
|
|
||||||
def __init__(self, opts: BroadcastCLIOptions, initial_strings: List[str]) -> None:
|
def __init__(self, opts: BroadcastCLIOptions, initial_strings: List[str]) -> None:
|
||||||
self.opts = opts
|
self.opts = opts
|
||||||
|
self.hide_input = False
|
||||||
self.initial_strings = initial_strings
|
self.initial_strings = initial_strings
|
||||||
self.payload = {'exclude_active': True, 'data': '', 'match': opts.match, 'match_tab': opts.match_tab, 'session_id': uuid4()}
|
self.payload = {'exclude_active': True, 'data': '', 'match': opts.match, 'match_tab': opts.match_tab, 'session_id': uuid4()}
|
||||||
self.line_edit = LineEdit()
|
self.line_edit = LineEdit()
|
||||||
@@ -40,7 +41,7 @@ class Broadcast(Handler):
|
|||||||
|
|
||||||
def initialize(self) -> None:
|
def initialize(self) -> None:
|
||||||
self.write_broadcast_session()
|
self.write_broadcast_session()
|
||||||
self.print('Type the text to broadcast below, press', styled('Ctrl+Esc', fg='yellow'), 'to quit:')
|
self.print('Type the text to broadcast below, press', styled(self.opts.end_session, fg='yellow'), 'to quit:')
|
||||||
for x in self.initial_strings:
|
for x in self.initial_strings:
|
||||||
self.write_broadcast_text(x)
|
self.write_broadcast_text(x)
|
||||||
self.write(SAVE_CURSOR)
|
self.write(SAVE_CURSOR)
|
||||||
@@ -56,7 +57,8 @@ class Broadcast(Handler):
|
|||||||
|
|
||||||
def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
|
def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
|
||||||
self.write_broadcast_text(text)
|
self.write_broadcast_text(text)
|
||||||
self.line_edit.on_text(text, in_bracketed_paste)
|
if not self.hide_input:
|
||||||
|
self.line_edit.on_text(text, in_bracketed_paste)
|
||||||
self.commit_line()
|
self.commit_line()
|
||||||
|
|
||||||
def on_interrupt(self) -> None:
|
def on_interrupt(self) -> None:
|
||||||
@@ -68,22 +70,33 @@ class Broadcast(Handler):
|
|||||||
self.write_broadcast_text('\x04')
|
self.write_broadcast_text('\x04')
|
||||||
|
|
||||||
def on_key(self, key_event: KeyEventType) -> None:
|
def on_key(self, key_event: KeyEventType) -> None:
|
||||||
if self.line_edit.on_key(key_event):
|
if key_event.matches(self.opts.hide_input_toggle):
|
||||||
|
self.hide_input ^= True
|
||||||
|
self.cmd.set_cursor_visible(not self.hide_input)
|
||||||
|
if self.hide_input:
|
||||||
|
self.end_line()
|
||||||
|
self.print('Input hidden, press', styled(self.opts.hide_input_toggle, fg='yellow'), 'to unhide:')
|
||||||
|
self.end_line()
|
||||||
|
return
|
||||||
|
if key_event.matches(self.opts.end_session):
|
||||||
|
self.quit_loop(0)
|
||||||
|
return
|
||||||
|
if not self.hide_input and self.line_edit.on_key(key_event):
|
||||||
self.commit_line()
|
self.commit_line()
|
||||||
if key_event.matches('enter'):
|
if key_event.matches('enter'):
|
||||||
self.write_broadcast_text('\r')
|
self.write_broadcast_text('\r')
|
||||||
self.print('')
|
self.end_line()
|
||||||
self.line_edit.clear()
|
|
||||||
self.write(SAVE_CURSOR)
|
|
||||||
return
|
|
||||||
if key_event.matches('ctrl+esc'):
|
|
||||||
self.quit_loop(0)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
ek = encode_key_event(key_event)
|
ek = encode_key_event(key_event)
|
||||||
ek = standard_b64encode(ek.encode('utf-8')).decode('ascii')
|
ek = standard_b64encode(ek.encode('utf-8')).decode('ascii')
|
||||||
self.write_broadcast_data('kitty-key:' + ek)
|
self.write_broadcast_data('kitty-key:' + ek)
|
||||||
|
|
||||||
|
def end_line(self) -> None:
|
||||||
|
self.print('')
|
||||||
|
self.line_edit.clear()
|
||||||
|
self.write(SAVE_CURSOR)
|
||||||
|
|
||||||
def write_broadcast_text(self, text: str) -> None:
|
def write_broadcast_text(self, text: str) -> None:
|
||||||
self.write_broadcast_data('base64:' + standard_b64encode(text.encode('utf-8')).decode('ascii'))
|
self.write_broadcast_data('base64:' + standard_b64encode(text.encode('utf-8')).decode('ascii'))
|
||||||
|
|
||||||
@@ -98,7 +111,19 @@ class Broadcast(Handler):
|
|||||||
self.write(session_command(self.payload, start))
|
self.write(session_command(self.payload, start))
|
||||||
|
|
||||||
|
|
||||||
OPTIONS = (MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t')).format
|
OPTIONS = ('''
|
||||||
|
--hide-input-toggle
|
||||||
|
default=Ctrl+Alt+Esc
|
||||||
|
Key to press that will toggle hiding of the input in the broadcast window itself.
|
||||||
|
Useful while typing a password, prevents the password from being visible on the screen.
|
||||||
|
|
||||||
|
|
||||||
|
--end-session
|
||||||
|
default=Ctrl+Esc
|
||||||
|
Key to press to end the broadcast session.
|
||||||
|
|
||||||
|
|
||||||
|
''' + MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t')).format
|
||||||
help_text = 'Broadcast typed text to kitty windows. By default text is sent to all windows, unless one of the matching options is specified'
|
help_text = 'Broadcast typed text to kitty windows. By default text is sent to all windows, unless one of the matching options is specified'
|
||||||
usage = '[initial text to send ...]'
|
usage = '[initial text to send ...]'
|
||||||
|
|
||||||
|
|||||||
@@ -231,11 +231,11 @@ static PyMethodDef module_methods[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static struct PyModuleDef module = {
|
static struct PyModuleDef module = {
|
||||||
.m_base = PyModuleDef_HEAD_INIT,
|
.m_base = PyModuleDef_HEAD_INIT,
|
||||||
.m_name = "subseq_matcher", /* name of module */
|
.m_name = "subseq_matcher", /* name of module */
|
||||||
.m_doc = NULL,
|
.m_doc = NULL,
|
||||||
.m_size = -1,
|
.m_size = -1,
|
||||||
.m_methods = module_methods
|
.m_methods = module_methods
|
||||||
};
|
};
|
||||||
|
|
||||||
EXPORTED PyMODINIT_FUNC
|
EXPORTED PyMODINIT_FUNC
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
from typing import List, Optional, Tuple
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
|
|
||||||
def match(
|
def match(
|
||||||
lines: List[bytes], levels: Tuple[str, str, str], needle: str,
|
lines: List[bytes], levels: Tuple[str, str, str], needle: str,
|
||||||
output_positions: bool, limit: int, num_threads: int, mark_before: str,
|
output_positions: bool, limit: int, num_threads: int, mark_before: str,
|
||||||
|
|||||||
@@ -1,115 +1,91 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
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
|
|
||||||
|
|
||||||
|
|
||||||
class Clipboard(Handler):
|
|
||||||
|
|
||||||
def __init__(self, data_to_send: Optional[bytes], args: ClipboardCLIOptions):
|
|
||||||
self.args = args
|
|
||||||
self.clipboard_contents: Optional[str] = None
|
|
||||||
self.data_to_send = data_to_send
|
|
||||||
self.quit_on_write = False
|
|
||||||
|
|
||||||
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:
|
|
||||||
if self.args.wait_for_completion:
|
|
||||||
# ask kitty for the TN terminfo capability and
|
|
||||||
# only quit after a response is received
|
|
||||||
self.print('\x1bP+q544e\x1b\\', end='')
|
|
||||||
self.print('Waiting for completion...')
|
|
||||||
return
|
|
||||||
self.quit_on_write = True
|
|
||||||
return
|
|
||||||
self.cmd.request_from_clipboard(self.args.use_primary)
|
|
||||||
|
|
||||||
def on_writing_finished(self) -> None:
|
|
||||||
if self.quit_on_write:
|
|
||||||
self.quit_loop(0)
|
|
||||||
|
|
||||||
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: str, val: str) -> None:
|
|
||||||
self.quit_loop(0)
|
|
||||||
|
|
||||||
def on_interrupt(self) -> None:
|
|
||||||
self.quit_loop(1)
|
|
||||||
|
|
||||||
def on_eot(self) -> None:
|
|
||||||
self.quit_loop(1)
|
|
||||||
|
|
||||||
|
|
||||||
OPTIONS = r'''
|
OPTIONS = r'''
|
||||||
--get-clipboard
|
--get-clipboard -g
|
||||||
default=False
|
|
||||||
type=bool-set
|
type=bool-set
|
||||||
Output the current contents of the clipboard to STDOUT. Note that by default
|
Output the current contents of the clipboard to STDOUT. Note that by default
|
||||||
kitty will prompt for permission to access the clipboard. Can be controlled
|
kitty will prompt for permission to access the clipboard. Can be controlled
|
||||||
by :opt:`clipboard_control`.
|
by :opt:`clipboard_control`.
|
||||||
|
|
||||||
|
|
||||||
--use-primary
|
--use-primary -p
|
||||||
default=False
|
|
||||||
type=bool-set
|
type=bool-set
|
||||||
Use the primary selection rather than the clipboard on systems that support it,
|
Use the primary selection rather than the clipboard on systems that support it,
|
||||||
such as X11.
|
such as Linux.
|
||||||
|
|
||||||
|
|
||||||
|
--mime -m
|
||||||
|
type=list
|
||||||
|
The mimetype of the specified file. Useful when the auto-detected mimetype is
|
||||||
|
likely to be incorrect or the filename has no extension and therefore no mimetype
|
||||||
|
can be detected. If more than one file is specified, this option should be specified multiple
|
||||||
|
times, once for each specified file. When copying data from the clipboard, you can use wildcards
|
||||||
|
to match MIME types. For example: :code:`--mime 'text/*'` will match any textual MIME type
|
||||||
|
available on the clipboard, usually the first matching MIME type is copied. The special MIME
|
||||||
|
type :code:`.` will return the list of available MIME types currently on the system clipboard.
|
||||||
|
|
||||||
|
|
||||||
|
--alias -a
|
||||||
|
type=list
|
||||||
|
Specify aliases for MIME types. Aliased MIME types are considered equivalent.
|
||||||
|
When copying to clipboard both the original and alias are made available on the
|
||||||
|
clipboard. When copying from clipboard if the original is not found, the alias
|
||||||
|
is used, as a fallback. Can be specified multiple times to create multiple
|
||||||
|
aliases. For example: :code:`--alias text/plain=text/x-rst` makes :code:`text/plain` an alias
|
||||||
|
of :code:`text/rst`. Aliases are not used in filter mode. An alias for
|
||||||
|
:code:`text/plain` is automatically created if :code:`text/plain` is not present in the input data, but some
|
||||||
|
other :code:`text/*` MIME is present.
|
||||||
|
|
||||||
|
|
||||||
--wait-for-completion
|
--wait-for-completion
|
||||||
default=False
|
|
||||||
type=bool-set
|
type=bool-set
|
||||||
Wait till the copy to clipboard is complete before exiting. Useful if running
|
Wait till the copy to clipboard is complete before exiting. Useful if running
|
||||||
the kitten in a dedicated, ephemeral window.
|
the kitten in a dedicated, ephemeral window. Only needed in filter mode.
|
||||||
'''.format
|
'''.format
|
||||||
help_text = '''\
|
help_text = '''\
|
||||||
Read or write to the system clipboard.
|
Read or write to the system clipboard.
|
||||||
|
|
||||||
To set the clipboard text, pipe in the new text on STDIN. Use the
|
This kitten operates most simply in :italic:`filter mode`.
|
||||||
:option:`--get-clipboard` option to output the current clipboard contents to
|
To set the clipboard text, pipe in the new text on :file:`STDIN`. Use the
|
||||||
:file:`stdout`. Note that reading the clipboard will cause a permission
|
:option:`--get-clipboard` option to output the current clipboard text content to
|
||||||
|
:file:`STDOUT`. Note that copying from the clipboard will cause a permission
|
||||||
popup, see :opt:`clipboard_control` for details.
|
popup, see :opt:`clipboard_control` for details.
|
||||||
|
|
||||||
|
For more control, specify filename arguments. Then, different MIME types can be copied to/from
|
||||||
|
the clipboard. Some examples:
|
||||||
|
|
||||||
|
.. code:: sh
|
||||||
|
|
||||||
|
# Copy an image to the clipboard:
|
||||||
|
kitty +kitten clipboard picture.png
|
||||||
|
|
||||||
|
# Copy an image and some text to the clipboard:
|
||||||
|
kitty +kitten clipboard picture.jpg text.txt
|
||||||
|
|
||||||
|
# Copy text from STDIN and an image to the clipboard:
|
||||||
|
echo hello | kitty +kitten clipboard picture.png /dev/stdin
|
||||||
|
|
||||||
|
# Copy any raster image available on the clipboard to a PNG file:
|
||||||
|
kitty +kitten clipboard -g picture.png
|
||||||
|
|
||||||
|
# Copy an image to a file and text to STDOUT:
|
||||||
|
kitty +kitten clipboard -g picture.png /dev/stdout
|
||||||
|
|
||||||
|
# List the formats available on the system clipboard
|
||||||
|
kitty +kitten clipboard -g -m . /dev/stdout
|
||||||
'''
|
'''
|
||||||
|
|
||||||
usage = ''
|
usage = '[files to copy to/from]'
|
||||||
|
|
||||||
|
|
||||||
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: Optional[bytes] = None
|
|
||||||
if not sys.stdin.isatty():
|
|
||||||
data = sys.stdin.buffer.read()
|
|
||||||
try:
|
|
||||||
sys.stdin = open(os.ctermid())
|
|
||||||
except FileNotFoundError:
|
|
||||||
raise SystemExit('Not connected to a controlling terminal device, no /dev/tty')
|
|
||||||
loop = Loop()
|
|
||||||
handler = Clipboard(data, cli_opts)
|
|
||||||
loop.loop(handler)
|
|
||||||
if loop.return_code == 0 and handler.clipboard_contents:
|
|
||||||
sys.stdout.write(handler.clipboard_contents)
|
|
||||||
sys.stdout.flush()
|
|
||||||
raise SystemExit(loop.return_code)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main(sys.argv)
|
raise SystemExit('This should be run as kitten clipboard')
|
||||||
elif __name__ == '__doc__':
|
elif __name__ == '__doc__':
|
||||||
|
from kitty.cli import CompletionSpec
|
||||||
cd = sys.cli_docs # type: ignore
|
cd = sys.cli_docs # type: ignore
|
||||||
cd['usage'] = usage
|
cd['usage'] = usage
|
||||||
cd['options'] = OPTIONS
|
cd['options'] = OPTIONS
|
||||||
cd['help_text'] = help_text
|
cd['help_text'] = help_text
|
||||||
|
cd['short_desc'] = 'Copy/paste with the system clipboard, even over SSH'
|
||||||
|
cd['args_completion'] = CompletionSpec.from_string('type:file mime:* group:Files')
|
||||||
|
|||||||
13
kittens/diff/collect.py
Executable file → Normal file
13
kittens/diff/collect.py
Executable file → Normal file
@@ -2,19 +2,17 @@
|
|||||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from fnmatch import fnmatch
|
from fnmatch import fnmatch
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
from typing import (
|
from typing import TYPE_CHECKING, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Union
|
||||||
TYPE_CHECKING, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Union
|
|
||||||
)
|
|
||||||
|
|
||||||
from kitty.guess_mime_type import guess_type
|
from kitty.guess_mime_type import guess_type
|
||||||
|
from kitty.utils import control_codes_pat
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .highlight import DiffHighlight # noqa
|
from .highlight import DiffHighlight
|
||||||
|
|
||||||
|
|
||||||
path_name_map: Dict[str, str] = {}
|
path_name_map: Dict[str, str] = {}
|
||||||
@@ -161,12 +159,9 @@ def collect_files(collection: Collection, left: str, right: str) -> None:
|
|||||||
collection.add_add(right_path_map[name])
|
collection.add_add(right_path_map[name])
|
||||||
|
|
||||||
|
|
||||||
sanitize_pat = re.compile('[\x00-\x09\x0b-\x1f\x7f\x80-\x9f]')
|
|
||||||
|
|
||||||
|
|
||||||
def sanitize(text: str) -> str:
|
def sanitize(text: str) -> str:
|
||||||
ntext = text.replace('\r\n', '⏎\n')
|
ntext = text.replace('\r\n', '⏎\n')
|
||||||
return sanitize_pat.sub('░', ntext)
|
return control_codes_pat().sub('░', ntext)
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=1024)
|
@lru_cache(maxsize=1024)
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ import os
|
|||||||
from typing import Any, Dict, Iterable, Optional
|
from typing import Any, Dict, Iterable, Optional
|
||||||
|
|
||||||
from kitty.cli_stub import DiffCLIOptions
|
from kitty.cli_stub import DiffCLIOptions
|
||||||
from kitty.conf.utils import (
|
from kitty.conf.utils import load_config as _load_config
|
||||||
load_config as _load_config, parse_config_base, resolve_config
|
from kitty.conf.utils import parse_config_base, resolve_config
|
||||||
)
|
|
||||||
from kitty.constants import config_dir
|
from kitty.constants import config_dir
|
||||||
from kitty.rgb import color_as_sgr
|
from kitty.rgb import color_as_sgr
|
||||||
|
|
||||||
from .options.types import Options as DiffOptions, defaults
|
from .options.types import Options as DiffOptions
|
||||||
|
from .options.types import defaults
|
||||||
|
|
||||||
formats: Dict[str, str] = {
|
formats: Dict[str, str] = {
|
||||||
'title': '',
|
'title': '',
|
||||||
@@ -41,9 +41,7 @@ defconf = os.path.join(config_dir, 'diff.conf')
|
|||||||
|
|
||||||
|
|
||||||
def load_config(*paths: str, overrides: Optional[Iterable[str]] = None) -> DiffOptions:
|
def load_config(*paths: str, overrides: Optional[Iterable[str]] = None) -> DiffOptions:
|
||||||
from .options.parse import (
|
from .options.parse import create_result_dict, merge_result_dicts, parse_conf_item
|
||||||
create_result_dict, merge_result_dicts, parse_conf_item
|
|
||||||
)
|
|
||||||
|
|
||||||
def parse_config(lines: Iterable[str]) -> Dict[str, Any]:
|
def parse_config(lines: Iterable[str]) -> Dict[str, Any]:
|
||||||
ans: Dict[str, Any] = create_result_dict()
|
ans: Dict[str, Any] = create_result_dict()
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from typing import List, Optional, Tuple
|
|||||||
|
|
||||||
from .collect import Segment
|
from .collect import Segment
|
||||||
|
|
||||||
|
|
||||||
def split_with_highlights(
|
def split_with_highlights(
|
||||||
line: str, truncate_points: List[int], fg_highlights: List[Segment],
|
line: str, truncate_points: List[int], fg_highlights: List[Segment],
|
||||||
bg_highlight: Optional[Segment]
|
bg_highlight: Optional[Segment]
|
||||||
|
|||||||
@@ -5,9 +5,7 @@ import concurrent
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from concurrent.futures import ProcessPoolExecutor
|
from concurrent.futures import ProcessPoolExecutor
|
||||||
from typing import (
|
from typing import IO, Dict, Iterable, Iterator, List, Optional, Tuple, Union, cast
|
||||||
IO, Dict, Iterable, Iterator, List, Optional, Tuple, Union, cast
|
|
||||||
)
|
|
||||||
|
|
||||||
from pygments import highlight # type: ignore
|
from pygments import highlight # type: ignore
|
||||||
from pygments.formatter import Formatter # type: ignore
|
from pygments.formatter import Formatter # type: ignore
|
||||||
|
|||||||
46
kittens/diff/main.py
Executable file → Normal file
46
kittens/diff/main.py
Executable file → Normal file
@@ -14,16 +14,24 @@ from enum import Enum, auto
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
from typing import (
|
from typing import (
|
||||||
Any, DefaultDict, Dict, Iterable, Iterator, List, Optional, Tuple, Union
|
Any,
|
||||||
|
DefaultDict,
|
||||||
|
Dict,
|
||||||
|
Iterable,
|
||||||
|
Iterator,
|
||||||
|
List,
|
||||||
|
Optional,
|
||||||
|
Tuple,
|
||||||
|
Union,
|
||||||
)
|
)
|
||||||
|
|
||||||
from kitty.cli import CONFIG_HELP, parse_args
|
from kitty.cli import CONFIG_HELP, CompletionSpec, parse_args
|
||||||
from kitty.cli_stub import DiffCLIOptions
|
from kitty.cli_stub import DiffCLIOptions
|
||||||
from kitty.conf.utils import KeyAction
|
from kitty.conf.utils import KeyAction
|
||||||
from kitty.constants import appname
|
from kitty.constants import appname
|
||||||
from kitty.fast_data_types import wcswidth
|
from kitty.fast_data_types import wcswidth
|
||||||
from kitty.key_encoding import EventType, KeyEvent
|
from kitty.key_encoding import EventType, KeyEvent
|
||||||
from kitty.utils import ScreenSize
|
from kitty.utils import ScreenSize, extract_all_from_tarfile_safely
|
||||||
|
|
||||||
from ..tui.handler import Handler
|
from ..tui.handler import Handler
|
||||||
from ..tui.images import ImageManager, Placement
|
from ..tui.images import ImageManager, Placement
|
||||||
@@ -32,21 +40,33 @@ from ..tui.loop import Loop
|
|||||||
from ..tui.operations import styled
|
from ..tui.operations import styled
|
||||||
from . import global_data
|
from . import global_data
|
||||||
from .collect import (
|
from .collect import (
|
||||||
Collection, add_remote_dir, create_collection, data_for_path,
|
Collection,
|
||||||
lines_for_path, sanitize, set_highlight_data
|
add_remote_dir,
|
||||||
|
create_collection,
|
||||||
|
data_for_path,
|
||||||
|
lines_for_path,
|
||||||
|
sanitize,
|
||||||
|
set_highlight_data,
|
||||||
)
|
)
|
||||||
from .config import init_config
|
from .config import init_config
|
||||||
from .options.types import Options as DiffOptions
|
from .options.types import Options as DiffOptions
|
||||||
from .patch import Differ, Patch, set_diff_command, worker_processes
|
from .patch import Differ, Patch, set_diff_command, worker_processes
|
||||||
from .render import (
|
from .render import (
|
||||||
ImagePlacement, ImageSupportWarning, Line, LineRef, Reference, render_diff
|
ImagePlacement,
|
||||||
|
ImageSupportWarning,
|
||||||
|
Line,
|
||||||
|
LineRef,
|
||||||
|
Reference,
|
||||||
|
render_diff,
|
||||||
)
|
)
|
||||||
from .search import BadRegex, Search
|
from .search import BadRegex, Search
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .highlight import (
|
from .highlight import (
|
||||||
DiffHighlight, get_highlight_processes, highlight_collection,
|
DiffHighlight,
|
||||||
initialize_highlighter
|
get_highlight_processes,
|
||||||
|
highlight_collection,
|
||||||
|
initialize_highlighter,
|
||||||
)
|
)
|
||||||
has_highlighter = True
|
has_highlighter = True
|
||||||
DiffHighlight
|
DiffHighlight
|
||||||
@@ -229,8 +249,8 @@ class DiffHandler(Handler):
|
|||||||
self.diff_lines: Tuple[Line, ...] = tuple(render_diff(self.collection, self.diff_map, self.args, self.screen_size.cols, self.image_manager))
|
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.margin_size = render_diff.margin_size
|
||||||
self.ref_path_map: DefaultDict[str, List[Tuple[int, Reference]]] = defaultdict(list)
|
self.ref_path_map: DefaultDict[str, List[Tuple[int, Reference]]] = defaultdict(list)
|
||||||
for i, l in enumerate(self.diff_lines):
|
for i, dl in enumerate(self.diff_lines):
|
||||||
self.ref_path_map[l.ref.path].append((i, l.ref))
|
self.ref_path_map[dl.ref.path].append((i, dl.ref))
|
||||||
self.max_scroll_pos = len(self.diff_lines) - self.num_lines
|
self.max_scroll_pos = len(self.diff_lines) - self.num_lines
|
||||||
if self.current_search is not None:
|
if self.current_search is not None:
|
||||||
self.current_search(self.diff_lines, self.margin_size, self.screen_size.cols)
|
self.current_search(self.diff_lines, self.margin_size, self.screen_size.cols)
|
||||||
@@ -319,7 +339,7 @@ class DiffHandler(Handler):
|
|||||||
fg=self.opts.foreground, bg=self.opts.background,
|
fg=self.opts.foreground, bg=self.opts.background,
|
||||||
cursor=self.opts.foreground, select_fg=self.opts.select_fg,
|
cursor=self.opts.foreground, select_fg=self.opts.select_fg,
|
||||||
select_bg=self.opts.select_bg)
|
select_bg=self.opts.select_bg)
|
||||||
self.cmd.set_cursor_shape('bar')
|
self.cmd.set_cursor_shape('beam')
|
||||||
|
|
||||||
def finalize(self) -> None:
|
def finalize(self) -> None:
|
||||||
self.cmd.set_default_colors()
|
self.cmd.set_default_colors()
|
||||||
@@ -577,6 +597,7 @@ number set in :file:`diff.conf`.
|
|||||||
|
|
||||||
--config
|
--config
|
||||||
type=list
|
type=list
|
||||||
|
completion=type:file ext:conf group:"Config files" kwds:none,NONE
|
||||||
{config_help}
|
{config_help}
|
||||||
|
|
||||||
|
|
||||||
@@ -629,7 +650,7 @@ def get_ssh_file(hostname: str, rpath: str) -> str:
|
|||||||
raise SystemExit(p.returncode)
|
raise SystemExit(p.returncode)
|
||||||
with tarfile.open(fileobj=io.BytesIO(raw), mode='r:') as tf:
|
with tarfile.open(fileobj=io.BytesIO(raw), mode='r:') as tf:
|
||||||
members = tf.getmembers()
|
members = tf.getmembers()
|
||||||
tf.extractall(tdir)
|
extract_all_from_tarfile_safely(tf, tdir)
|
||||||
if len(members) == 1:
|
if len(members) == 1:
|
||||||
for root, dirs, files in os.walk(tdir):
|
for root, dirs, files in os.walk(tdir):
|
||||||
if files:
|
if files:
|
||||||
@@ -687,6 +708,7 @@ elif __name__ == '__doc__':
|
|||||||
cd['usage'] = usage
|
cd['usage'] = usage
|
||||||
cd['options'] = OPTIONS
|
cd['options'] = OPTIONS
|
||||||
cd['help_text'] = help_text
|
cd['help_text'] = help_text
|
||||||
|
cd['args_completion'] = CompletionSpec.from_string('type:file mime:text/* mime:image/* group:"Text and image files"')
|
||||||
elif __name__ == '__conf__':
|
elif __name__ == '__conf__':
|
||||||
from .options.definition import definition
|
from .options.definition import definition
|
||||||
sys.options_definition = definition # type: ignore
|
sys.options_definition = definition # type: ignore
|
||||||
|
|||||||
7
kittens/diff/options/definition.py
Executable file → Normal file
7
kittens/diff/options/definition.py
Executable file → Normal file
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
from kitty.conf.types import Action, Definition
|
from kitty.conf.types import Action, Definition
|
||||||
|
|
||||||
|
|
||||||
definition = Definition(
|
definition = Definition(
|
||||||
'kittens.diff',
|
'kittens.diff',
|
||||||
Action('map', 'parse_map', {'key_definitions': 'kitty.conf.utils.KittensKeyMap'}, ['kitty.types.ParsedShortcut', 'kitty.conf.utils.KeyAction']),
|
Action('map', 'parse_map', {'key_definitions': 'kitty.conf.utils.KittensKeyMap'}, ['kitty.types.ParsedShortcut', 'kitty.conf.utils.KeyAction']),
|
||||||
@@ -56,9 +55,9 @@ A glob pattern that is matched against only the filename of files and directorie
|
|||||||
files and directories are ignored when scanning the filesystem to look for files to diff.
|
files and directories are ignored when scanning the filesystem to look for files to diff.
|
||||||
Can be specified multiple times to use multiple patterns. For example::
|
Can be specified multiple times to use multiple patterns. For example::
|
||||||
|
|
||||||
ignore_name .git
|
ignore_name .git
|
||||||
ignore_name *~
|
ignore_name *~
|
||||||
ignore_name *.pyc
|
ignore_name *.pyc
|
||||||
''',
|
''',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
1
kittens/diff/options/parse.py
generated
1
kittens/diff/options/parse.py
generated
@@ -1,5 +1,6 @@
|
|||||||
# generated by gen-config.py DO NOT edit
|
# generated by gen-config.py DO NOT edit
|
||||||
|
|
||||||
|
# isort: skip_file
|
||||||
import typing
|
import typing
|
||||||
from kittens.diff.options.utils import parse_map, store_multiple, syntax_aliases
|
from kittens.diff.options.utils import parse_map, store_multiple, syntax_aliases
|
||||||
from kitty.conf.utils import merge_dicts, positive_int, python_string, to_color, to_color_or_none
|
from kitty.conf.utils import merge_dicts, positive_int, python_string, to_color, to_color_or_none
|
||||||
|
|||||||
51
kittens/diff/options/types.py
generated
51
kittens/diff/options/types.py
generated
@@ -1,5 +1,6 @@
|
|||||||
# generated by gen-config.py DO NOT edit
|
# generated by gen-config.py DO NOT edit
|
||||||
|
|
||||||
|
# isort: skip_file
|
||||||
import typing
|
import typing
|
||||||
from kitty.conf.utils import KeyAction, KittensKeyMap
|
from kitty.conf.utils import KeyAction, KittensKeyMap
|
||||||
import kitty.conf.utils
|
import kitty.conf.utils
|
||||||
@@ -121,53 +122,53 @@ defaults = Options()
|
|||||||
defaults.ignore_name = {}
|
defaults.ignore_name = {}
|
||||||
defaults.map = [
|
defaults.map = [
|
||||||
# quit
|
# quit
|
||||||
(ParsedShortcut(mods=0, key_name='q'), KeyAction('quit')), # noqa
|
(ParsedShortcut(mods=0, key_name='q'), KeyAction('quit')),
|
||||||
# quit
|
# quit
|
||||||
(ParsedShortcut(mods=0, key_name='ESCAPE'), KeyAction('quit')), # noqa
|
(ParsedShortcut(mods=0, key_name='ESCAPE'), KeyAction('quit')),
|
||||||
# scroll_down
|
# scroll_down
|
||||||
(ParsedShortcut(mods=0, key_name='j'), KeyAction('scroll_by', (1,))), # noqa
|
(ParsedShortcut(mods=0, key_name='j'), KeyAction('scroll_by', (1,))),
|
||||||
# scroll_down
|
# scroll_down
|
||||||
(ParsedShortcut(mods=0, key_name='DOWN'), KeyAction('scroll_by', (1,))), # noqa
|
(ParsedShortcut(mods=0, key_name='DOWN'), KeyAction('scroll_by', (1,))),
|
||||||
# scroll_up
|
# scroll_up
|
||||||
(ParsedShortcut(mods=0, key_name='k'), KeyAction('scroll_by', (-1,))), # noqa
|
(ParsedShortcut(mods=0, key_name='k'), KeyAction('scroll_by', (-1,))),
|
||||||
# scroll_up
|
# scroll_up
|
||||||
(ParsedShortcut(mods=0, key_name='UP'), KeyAction('scroll_by', (-1,))), # noqa
|
(ParsedShortcut(mods=0, key_name='UP'), KeyAction('scroll_by', (-1,))),
|
||||||
# scroll_top
|
# scroll_top
|
||||||
(ParsedShortcut(mods=0, key_name='HOME'), KeyAction('scroll_to', ('start',))), # noqa
|
(ParsedShortcut(mods=0, key_name='HOME'), KeyAction('scroll_to', ('start',))),
|
||||||
# scroll_bottom
|
# scroll_bottom
|
||||||
(ParsedShortcut(mods=0, key_name='END'), KeyAction('scroll_to', ('end',))), # noqa
|
(ParsedShortcut(mods=0, key_name='END'), KeyAction('scroll_to', ('end',))),
|
||||||
# scroll_page_down
|
# scroll_page_down
|
||||||
(ParsedShortcut(mods=0, key_name='PAGE_DOWN'), KeyAction('scroll_to', ('next-page',))), # noqa
|
(ParsedShortcut(mods=0, key_name='PAGE_DOWN'), KeyAction('scroll_to', ('next-page',))),
|
||||||
# scroll_page_down
|
# scroll_page_down
|
||||||
(ParsedShortcut(mods=0, key_name=' '), KeyAction('scroll_to', ('next-page',))), # noqa
|
(ParsedShortcut(mods=0, key_name=' '), KeyAction('scroll_to', ('next-page',))),
|
||||||
# scroll_page_up
|
# scroll_page_up
|
||||||
(ParsedShortcut(mods=0, key_name='PAGE_UP'), KeyAction('scroll_to', ('prev-page',))), # noqa
|
(ParsedShortcut(mods=0, key_name='PAGE_UP'), KeyAction('scroll_to', ('prev-page',))),
|
||||||
# next_change
|
# next_change
|
||||||
(ParsedShortcut(mods=0, key_name='n'), KeyAction('scroll_to', ('next-change',))), # noqa
|
(ParsedShortcut(mods=0, key_name='n'), KeyAction('scroll_to', ('next-change',))),
|
||||||
# prev_change
|
# prev_change
|
||||||
(ParsedShortcut(mods=0, key_name='p'), KeyAction('scroll_to', ('prev-change',))), # noqa
|
(ParsedShortcut(mods=0, key_name='p'), KeyAction('scroll_to', ('prev-change',))),
|
||||||
# all_context
|
# all_context
|
||||||
(ParsedShortcut(mods=0, key_name='a'), KeyAction('change_context', ('all',))), # noqa
|
(ParsedShortcut(mods=0, key_name='a'), KeyAction('change_context', ('all',))),
|
||||||
# default_context
|
# default_context
|
||||||
(ParsedShortcut(mods=0, key_name='='), KeyAction('change_context', ('default',))), # noqa
|
(ParsedShortcut(mods=0, key_name='='), KeyAction('change_context', ('default',))),
|
||||||
# increase_context
|
# increase_context
|
||||||
(ParsedShortcut(mods=0, key_name='+'), KeyAction('change_context', (5,))), # noqa
|
(ParsedShortcut(mods=0, key_name='+'), KeyAction('change_context', (5,))),
|
||||||
# decrease_context
|
# decrease_context
|
||||||
(ParsedShortcut(mods=0, key_name='-'), KeyAction('change_context', (-5,))), # noqa
|
(ParsedShortcut(mods=0, key_name='-'), KeyAction('change_context', (-5,))),
|
||||||
# search_forward
|
# search_forward
|
||||||
(ParsedShortcut(mods=0, key_name='/'), KeyAction('start_search', (True, False))), # noqa
|
(ParsedShortcut(mods=0, key_name='/'), KeyAction('start_search', (True, False))),
|
||||||
# search_backward
|
# search_backward
|
||||||
(ParsedShortcut(mods=0, key_name='?'), KeyAction('start_search', (True, True))), # noqa
|
(ParsedShortcut(mods=0, key_name='?'), KeyAction('start_search', (True, True))),
|
||||||
# next_match
|
# next_match
|
||||||
(ParsedShortcut(mods=0, key_name='.'), KeyAction('scroll_to', ('next-match',))), # noqa
|
(ParsedShortcut(mods=0, key_name='.'), KeyAction('scroll_to', ('next-match',))),
|
||||||
# next_match
|
# next_match
|
||||||
(ParsedShortcut(mods=0, key_name='>'), KeyAction('scroll_to', ('next-match',))), # noqa
|
(ParsedShortcut(mods=0, key_name='>'), KeyAction('scroll_to', ('next-match',))),
|
||||||
# prev_match
|
# prev_match
|
||||||
(ParsedShortcut(mods=0, key_name=','), KeyAction('scroll_to', ('prev-match',))), # noqa
|
(ParsedShortcut(mods=0, key_name=','), KeyAction('scroll_to', ('prev-match',))),
|
||||||
# prev_match
|
# prev_match
|
||||||
(ParsedShortcut(mods=0, key_name='<'), KeyAction('scroll_to', ('prev-match',))), # noqa
|
(ParsedShortcut(mods=0, key_name='<'), KeyAction('scroll_to', ('prev-match',))),
|
||||||
# search_forward_simple
|
# search_forward_simple
|
||||||
(ParsedShortcut(mods=0, key_name='f'), KeyAction('start_search', (False, False))), # noqa
|
(ParsedShortcut(mods=0, key_name='f'), KeyAction('start_search', (False, False))),
|
||||||
# search_backward_simple
|
# search_backward_simple
|
||||||
(ParsedShortcut(mods=0, key_name='b'), KeyAction('start_search', (False, True))), # noqa
|
(ParsedShortcut(mods=0, key_name='b'), KeyAction('start_search', (False, True))),
|
||||||
]
|
]
|
||||||
|
|||||||
4
kittens/diff/options/utils.py
Executable file → Normal file
4
kittens/diff/options/utils.py
Executable file → Normal file
@@ -5,9 +5,7 @@
|
|||||||
|
|
||||||
from typing import Any, Container, Dict, Iterable, Tuple, Union
|
from typing import Any, Container, Dict, Iterable, Tuple, Union
|
||||||
|
|
||||||
from kitty.conf.utils import (
|
from kitty.conf.utils import KeyFuncWrapper, KittensKeyDefinition, parse_kittens_key
|
||||||
KeyFuncWrapper, KittensKeyDefinition, parse_kittens_key
|
|
||||||
)
|
|
||||||
|
|
||||||
ReturnType = Tuple[str, Any]
|
ReturnType = Tuple[str, Any]
|
||||||
func_with_args = KeyFuncWrapper[ReturnType]()
|
func_with_args = KeyFuncWrapper[ReturnType]()
|
||||||
|
|||||||
@@ -13,10 +13,7 @@ from kitty.types import run_once
|
|||||||
from kitty.utils import ScreenSize
|
from kitty.utils import ScreenSize
|
||||||
|
|
||||||
from ..tui.images import ImageManager, can_display_images
|
from ..tui.images import ImageManager, can_display_images
|
||||||
from .collect import (
|
from .collect import Collection, 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 .config import formats
|
||||||
from .diff_speedup import split_with_highlights as _split_with_highlights
|
from .diff_speedup import split_with_highlights as _split_with_highlights
|
||||||
from .patch import Chunk, Hunk, Patch
|
from .patch import Chunk, Hunk, Patch
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import re
|
|||||||
from typing import TYPE_CHECKING, Callable, Dict, Iterable, List, Tuple
|
from typing import TYPE_CHECKING, Callable, Dict, Iterable, List, Tuple
|
||||||
|
|
||||||
from kitty.fast_data_types import wcswidth
|
from kitty.fast_data_types import wcswidth
|
||||||
from .options.types import Options as DiffOptions
|
|
||||||
|
|
||||||
from ..tui.operations import styled
|
from ..tui.operations import styled
|
||||||
|
from .options.types import Options as DiffOptions
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .render import Line
|
from .render import Line
|
||||||
|
|||||||
@@ -187,11 +187,11 @@ static PyMethodDef module_methods[] = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static struct PyModuleDef module = {
|
static struct PyModuleDef module = {
|
||||||
.m_base = PyModuleDef_HEAD_INIT,
|
.m_base = PyModuleDef_HEAD_INIT,
|
||||||
.m_name = "diff_speedup", /* name of module */
|
.m_name = "diff_speedup", /* name of module */
|
||||||
.m_doc = NULL,
|
.m_doc = NULL,
|
||||||
.m_size = -1,
|
.m_size = -1,
|
||||||
.m_methods = module_methods
|
.m_methods = module_methods
|
||||||
};
|
};
|
||||||
|
|
||||||
EXPORTED PyMODINIT_FUNC
|
EXPORTED PyMODINIT_FUNC
|
||||||
|
|||||||
@@ -8,21 +8,16 @@ import sys
|
|||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
from itertools import repeat
|
from itertools import repeat
|
||||||
from typing import (
|
from typing import Any, Callable, Dict, Generator, Iterable, Iterator, List, Optional, Pattern, Sequence, Set, Tuple, Type, cast
|
||||||
Any, Callable, Dict, Generator, Iterable, Iterator, List, Optional,
|
|
||||||
Pattern, Sequence, Set, Tuple, Type, cast
|
|
||||||
)
|
|
||||||
|
|
||||||
from kitty.cli import parse_args
|
from kitty.cli import parse_args
|
||||||
from kitty.cli_stub import HintsCLIOptions
|
from kitty.cli_stub import HintsCLIOptions
|
||||||
|
from kitty.clipboard import set_clipboard_string, set_primary_selection
|
||||||
from kitty.constants import website_url
|
from kitty.constants import website_url
|
||||||
from kitty.fast_data_types import get_options, set_clipboard_string
|
from kitty.fast_data_types import get_options, wcswidth
|
||||||
from kitty.key_encoding import KeyEvent
|
from kitty.key_encoding import KeyEvent
|
||||||
from kitty.typing import BossType, KittyCommonOpts
|
from kitty.typing import BossType, KittyCommonOpts
|
||||||
from kitty.utils import (
|
from kitty.utils import ScreenSize, kitty_ansi_sanitizer_pat, resolve_custom_file, screen_size_function
|
||||||
ScreenSize, kitty_ansi_sanitizer_pat, resolve_custom_file,
|
|
||||||
screen_size_function, set_primary_selection
|
|
||||||
)
|
|
||||||
|
|
||||||
from ..tui.handler import Handler, result_handler
|
from ..tui.handler import Handler, result_handler
|
||||||
from ..tui.loop import Loop
|
from ..tui.loop import Loop
|
||||||
@@ -411,7 +406,10 @@ def convert_text(text: str, cols: int) -> str:
|
|||||||
appended = False
|
appended = False
|
||||||
for line in full_line.split('\r'):
|
for line in full_line.split('\r'):
|
||||||
if line:
|
if line:
|
||||||
lines.append(line.ljust(cols, '\0'))
|
line_sz = wcswidth(line)
|
||||||
|
if line_sz < cols:
|
||||||
|
line += '\0' * (cols - line_sz)
|
||||||
|
lines.append(line)
|
||||||
lines.append('\r')
|
lines.append('\r')
|
||||||
appended = True
|
appended = True
|
||||||
if appended:
|
if appended:
|
||||||
@@ -536,12 +534,27 @@ OPTIONS = r'''
|
|||||||
--program
|
--program
|
||||||
type=list
|
type=list
|
||||||
What program to use to open matched text. Defaults to the default open program
|
What program to use to open matched text. Defaults to the default open program
|
||||||
for the operating system. Use a value of :code:`-` to paste the match into the
|
for the operating system. Various special values are supported:
|
||||||
terminal window instead. A value of :code:`@` will copy the match to the
|
|
||||||
clipboard. A value of :code:`*` will copy the match to the primary selection
|
:code:`-`
|
||||||
(on systems that support primary selections). A value of :code:`default` will
|
paste the match into the terminal window.
|
||||||
run the default open program. Can be specified multiple times to run multiple
|
|
||||||
programs.
|
:code:`@`
|
||||||
|
copy the match to the clipboard
|
||||||
|
|
||||||
|
:code:`*`
|
||||||
|
copy the match to the primary selection (on systems that support primary selections)
|
||||||
|
|
||||||
|
:code:`default`
|
||||||
|
run the default open program.
|
||||||
|
|
||||||
|
:code:`launch`
|
||||||
|
run :doc:`/launch` to open the program in a new kitty tab, window, overlay, etc.
|
||||||
|
For example::
|
||||||
|
|
||||||
|
--program "launch --type=tab vim"
|
||||||
|
|
||||||
|
Can be specified multiple times to run multiple programs.
|
||||||
|
|
||||||
|
|
||||||
--type
|
--type
|
||||||
@@ -803,6 +816,7 @@ def handle_result(args: List[str], data: Dict[str, Any], target_window_id: int,
|
|||||||
elif program == '*':
|
elif program == '*':
|
||||||
set_primary_selection(joined_text())
|
set_primary_selection(joined_text())
|
||||||
else:
|
else:
|
||||||
|
from kitty.conf.utils import to_cmdline
|
||||||
cwd = data['cwd']
|
cwd = data['cwd']
|
||||||
program = get_options().open_url_with if program == 'default' else program
|
program = get_options().open_url_with if program == 'default' else program
|
||||||
if text_type == 'hyperlink':
|
if text_type == 'hyperlink':
|
||||||
@@ -811,12 +825,20 @@ def handle_result(args: List[str], data: Dict[str, Any], target_window_id: int,
|
|||||||
if w is not None:
|
if w is not None:
|
||||||
w.open_url(m, hyperlink_id=1, cwd=cwd)
|
w.open_url(m, hyperlink_id=1, cwd=cwd)
|
||||||
else:
|
else:
|
||||||
|
launch_args = []
|
||||||
|
if isinstance(program, str) and program.startswith('launch '):
|
||||||
|
launch_args = to_cmdline(program)
|
||||||
|
launch_args.insert(1, '--cwd=' + cwd)
|
||||||
for m, groupdict in zip(matches, groupdicts):
|
for m, groupdict in zip(matches, groupdicts):
|
||||||
if groupdict:
|
if groupdict:
|
||||||
m = []
|
m = []
|
||||||
for k, v in groupdict.items():
|
for k, v in groupdict.items():
|
||||||
m.append('{}={}'.format(k, v or ''))
|
m.append('{}={}'.format(k, v or ''))
|
||||||
boss.open_url(m, program, cwd=cwd)
|
if launch_args:
|
||||||
|
w = boss.window_id_map.get(target_window_id)
|
||||||
|
boss.call_remote_control(active_window=w, args=tuple(launch_args + ([m] if isinstance(m, str) else m)))
|
||||||
|
else:
|
||||||
|
boss.open_url(m, program, cwd=cwd)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
# generated by gen-wcwidth.py, do not edit
|
# generated by gen-wcwidth.py, do not edit
|
||||||
|
|
||||||
url_delimiters = '\x00-\x09\x0b-\x0c\x0e-\x20\x7f-\xa0\xad\u0600-\u0605\u061c\u06dd\u070f\u0890-\u0891\u08e2\u1680\u180e\u2000-\u200f\u2028-\u202f\u205f-\u2064\u2066-\u206f\u3000\ud800-\uf8ff\ufeff\ufff9-\ufffb\U000110bd\U000110cd\U00013430-\U00013438\U0001bca0-\U0001bca3\U0001d173-\U0001d17a\U000e0001\U000e0020-\U000e007f\U000f0000-\U000ffffd\U00100000-\U0010fffd' # noqa
|
url_delimiters = '\x00-\x09\x0b-\x0c\x0e-\x20\x7f-\xa0\xad\u0600-\u0605\u061c\u06dd\u070f\u0890-\u0891\u08e2\u1680\u180e\u2000-\u200f\u2028-\u202f\u205f-\u2064\u2066-\u206f\u3000\ud800-\uf8ff\ufeff\ufff9-\ufffb\U000110bd\U000110cd\U00013430-\U0001343f\U0001bca0-\U0001bca3\U0001d173-\U0001d17a\U000e0001\U000e0020-\U000e007f\U000f0000-\U000ffffd\U00100000-\U0010fffd' # noqa
|
||||||
156
kittens/hyperlinked_grep/main.py
Normal file → Executable file
156
kittens/hyperlinked_grep/main.py
Normal file → Executable file
@@ -1,15 +1,17 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import argparse
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import signal
|
import signal
|
||||||
import socket
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from typing import Callable, cast
|
from typing import Callable, List, cast
|
||||||
from urllib.parse import quote_from_bytes
|
from urllib.parse import quote_from_bytes
|
||||||
|
|
||||||
|
from kitty.utils import get_hostname
|
||||||
|
|
||||||
|
|
||||||
def write_hyperlink(write: Callable[[bytes], None], url: bytes, line: bytes, frag: bytes = b'') -> None:
|
def write_hyperlink(write: Callable[[bytes], None], url: bytes, line: bytes, frag: bytes = b'') -> None:
|
||||||
text = b'\033]8;;' + url
|
text = b'\033]8;;' + url
|
||||||
@@ -19,8 +21,93 @@ def write_hyperlink(write: Callable[[bytes], None], url: bytes, line: bytes, fra
|
|||||||
write(text)
|
write(text)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_options(argv: List[str]) -> argparse.Namespace:
|
||||||
|
p = argparse.ArgumentParser(add_help=False)
|
||||||
|
p.add_argument('--context-separator', default='--')
|
||||||
|
p.add_argument('-c', '--count', action='store_true')
|
||||||
|
p.add_argument('--count-matches', action='store_true')
|
||||||
|
p.add_argument('--field-context-separator', default='-')
|
||||||
|
p.add_argument('--field-match-separator', default='-')
|
||||||
|
p.add_argument('--files', action='store_true')
|
||||||
|
p.add_argument('-l', '--files-with-matches', action='store_true')
|
||||||
|
p.add_argument('--files-without-match', action='store_true')
|
||||||
|
p.add_argument('-h', '--help', action='store_true')
|
||||||
|
p.add_argument('--json', action='store_true')
|
||||||
|
p.add_argument('-I', '--no-filename', action='store_true')
|
||||||
|
p.add_argument('--no-heading', action='store_true')
|
||||||
|
p.add_argument('-N', '--no-line-number', action='store_true')
|
||||||
|
p.add_argument('-0', '--null', action='store_true')
|
||||||
|
p.add_argument('--null-data', action='store_true')
|
||||||
|
p.add_argument('--path-separator', default=os.path.sep)
|
||||||
|
p.add_argument('--stats', action='store_true')
|
||||||
|
p.add_argument('--type-list', action='store_true')
|
||||||
|
p.add_argument('-V', '--version', action='store_true')
|
||||||
|
p.add_argument('--vimgrep', action='store_true')
|
||||||
|
p.add_argument(
|
||||||
|
'-p', '--pretty',
|
||||||
|
default=sys.stdout.isatty(),
|
||||||
|
action='store_true',
|
||||||
|
)
|
||||||
|
p.add_argument('--kitten', action='append', default=[])
|
||||||
|
args, _ = p.parse_known_args(argv)
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
if not sys.stdout.isatty() and '--pretty' not in sys.argv and '-p' not in sys.argv:
|
i = 1
|
||||||
|
args = parse_options(sys.argv[1:])
|
||||||
|
all_link_options = {'matching_lines', 'context_lines', 'file_headers'}
|
||||||
|
link_options = set()
|
||||||
|
delegate_to_rg = False
|
||||||
|
|
||||||
|
for raw in args.kitten:
|
||||||
|
p, _, s = raw.partition('=')
|
||||||
|
if p != 'hyperlink':
|
||||||
|
raise SystemExit(f'Unknown argument for --kitten: {raw}')
|
||||||
|
for option in s.split(','):
|
||||||
|
if option == 'all':
|
||||||
|
link_options.update(all_link_options)
|
||||||
|
delegate_to_rg = False
|
||||||
|
elif option == 'none':
|
||||||
|
delegate_to_rg = True
|
||||||
|
link_options.clear()
|
||||||
|
elif option not in all_link_options:
|
||||||
|
a = ', '.join(sorted(all_link_options))
|
||||||
|
raise SystemExit(f"hyperlink option must be one of all, none, {a}, not '{option}'")
|
||||||
|
else:
|
||||||
|
link_options.add(option)
|
||||||
|
delegate_to_rg = False
|
||||||
|
|
||||||
|
while i < len(sys.argv):
|
||||||
|
if sys.argv[i] == '--kitten':
|
||||||
|
del sys.argv[i:i+2]
|
||||||
|
elif sys.argv[i].startswith('--kitten='):
|
||||||
|
del sys.argv[i]
|
||||||
|
else:
|
||||||
|
i += 1
|
||||||
|
if not link_options: # Default to linking everything if no options given
|
||||||
|
link_options.update(all_link_options)
|
||||||
|
link_file_headers = 'file_headers' in link_options
|
||||||
|
link_context_lines = 'context_lines' in link_options
|
||||||
|
link_matching_lines = 'matching_lines' in link_options
|
||||||
|
|
||||||
|
if any((
|
||||||
|
args.context_separator != '--',
|
||||||
|
args.field_context_separator != '-',
|
||||||
|
args.field_match_separator != '-',
|
||||||
|
args.help,
|
||||||
|
args.json,
|
||||||
|
args.no_filename,
|
||||||
|
args.null,
|
||||||
|
args.null_data,
|
||||||
|
args.path_separator != os.path.sep,
|
||||||
|
args.type_list,
|
||||||
|
args.version,
|
||||||
|
not args.pretty,
|
||||||
|
)):
|
||||||
|
delegate_to_rg = True
|
||||||
|
|
||||||
|
if delegate_to_rg:
|
||||||
os.execlp('rg', 'rg', *sys.argv[1:])
|
os.execlp('rg', 'rg', *sys.argv[1:])
|
||||||
cmdline = ['rg', '--pretty', '--with-filename'] + sys.argv[1:]
|
cmdline = ['rg', '--pretty', '--with-filename'] + sys.argv[1:]
|
||||||
try:
|
try:
|
||||||
@@ -28,13 +115,22 @@ def main() -> None:
|
|||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
raise SystemExit('Could not find the rg executable in your PATH. Is ripgrep installed?')
|
raise SystemExit('Could not find the rg executable in your PATH. Is ripgrep installed?')
|
||||||
assert p.stdout is not None
|
assert p.stdout is not None
|
||||||
|
|
||||||
write: Callable[[bytes], None] = cast(Callable[[bytes], None], sys.stdout.buffer.write)
|
write: Callable[[bytes], None] = cast(Callable[[bytes], None], sys.stdout.buffer.write)
|
||||||
sgr_pat = re.compile(br'\x1b\[.*?m')
|
sgr_pat = re.compile(br'\x1b\[.*?m')
|
||||||
osc_pat = re.compile(b'\x1b\\].*?\x1b\\\\')
|
osc_pat = re.compile(b'\x1b\\].*?\x1b\\\\')
|
||||||
num_pat = re.compile(br'^(\d+)[:-]')
|
num_pat = re.compile(br'^(\d+)([:-])')
|
||||||
|
path_with_count_pat = re.compile(br'(.*?)(:\d+)')
|
||||||
|
path_with_linenum_pat = re.compile(br'^(.*?):(\d+):')
|
||||||
|
stats_pat = re.compile(br'^\d+ matches$')
|
||||||
|
vimgrep_pat = re.compile(br'^(.*?):(\d+):(\d+):')
|
||||||
|
|
||||||
|
in_stats = False
|
||||||
in_result: bytes = b''
|
in_result: bytes = b''
|
||||||
hostname = socket.gethostname().encode('utf-8')
|
hostname = get_hostname().encode('utf-8')
|
||||||
|
|
||||||
|
def get_quoted_url(file_path: bytes) -> bytes:
|
||||||
|
return b'file://' + hostname + quote_from_bytes(os.path.abspath(file_path)).encode('utf-8')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for line in p.stdout:
|
for line in p.stdout:
|
||||||
@@ -43,20 +139,43 @@ def main() -> None:
|
|||||||
if not clean_line:
|
if not clean_line:
|
||||||
in_result = b''
|
in_result = b''
|
||||||
write(b'\n')
|
write(b'\n')
|
||||||
continue
|
elif in_stats:
|
||||||
if in_result:
|
write(line)
|
||||||
m = num_pat.match(clean_line)
|
elif in_result:
|
||||||
if m is not None:
|
if not args.no_line_number:
|
||||||
write_hyperlink(write, in_result, line, frag=m.group(1))
|
m = num_pat.match(clean_line)
|
||||||
else:
|
if m is not None:
|
||||||
write(line)
|
is_match_line = m.group(2) == b':'
|
||||||
|
if (is_match_line and link_matching_lines) or (not is_match_line and link_context_lines):
|
||||||
|
write_hyperlink(write, in_result, line, frag=m.group(1))
|
||||||
|
continue
|
||||||
|
write(line)
|
||||||
else:
|
else:
|
||||||
if line.strip():
|
if line.strip():
|
||||||
path = quote_from_bytes(os.path.abspath(clean_line)).encode('utf-8')
|
# The option priority should be consistent with ripgrep here.
|
||||||
in_result = b'file://' + hostname + path
|
if args.stats and not in_stats and stats_pat.match(clean_line):
|
||||||
write_hyperlink(write, in_result, line)
|
in_stats = True
|
||||||
else:
|
elif args.count or args.count_matches:
|
||||||
write(line)
|
m = path_with_count_pat.match(clean_line)
|
||||||
|
if m is not None and link_file_headers:
|
||||||
|
write_hyperlink(write, get_quoted_url(m.group(1)), line)
|
||||||
|
continue
|
||||||
|
elif args.files or args.files_with_matches or args.files_without_match:
|
||||||
|
if link_file_headers:
|
||||||
|
write_hyperlink(write, get_quoted_url(clean_line), line)
|
||||||
|
continue
|
||||||
|
elif args.vimgrep or args.no_heading:
|
||||||
|
# When the vimgrep option is present, it will take precedence.
|
||||||
|
m = vimgrep_pat.match(clean_line) if args.vimgrep else path_with_linenum_pat.match(clean_line)
|
||||||
|
if m is not None and (link_file_headers or link_matching_lines):
|
||||||
|
write_hyperlink(write, get_quoted_url(m.group(1)), line, frag=m.group(2))
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
in_result = get_quoted_url(clean_line)
|
||||||
|
if link_file_headers:
|
||||||
|
write_hyperlink(write, in_result, line)
|
||||||
|
continue
|
||||||
|
write(line)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
p.send_signal(signal.SIGINT)
|
p.send_signal(signal.SIGINT)
|
||||||
except (EOFError, BrokenPipeError):
|
except (EOFError, BrokenPipeError):
|
||||||
@@ -68,3 +187,6 @@ def main() -> None:
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
elif __name__ == '__wrapper_of__':
|
||||||
|
cd = sys.cli_docs # type: ignore
|
||||||
|
cd['wrapper_of'] = 'rg'
|
||||||
|
|||||||
533
kittens/icat/main.py
Executable file → Normal file
533
kittens/icat/main.py
Executable file → Normal file
@@ -1,38 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
import contextlib
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import signal
|
|
||||||
import socket
|
|
||||||
import sys
|
|
||||||
import zlib
|
|
||||||
from base64 import standard_b64encode
|
|
||||||
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.guess_mime_type import guess_type
|
|
||||||
from kitty.rgb import to_color
|
|
||||||
from kitty.types import run_once
|
|
||||||
from kitty.typing import GRT_f, GRT_t
|
|
||||||
from kitty.utils import (
|
|
||||||
TTYIO, ScreenSize, ScreenSizeGetter, screen_size_function
|
|
||||||
)
|
|
||||||
|
|
||||||
from ..tui.images import (
|
|
||||||
ConvertFailed, Dispose, GraphicsCommand, NoImageMagick, OpenFailed,
|
|
||||||
OutdatedImageMagick, RenderedImage, fsenc, identify,
|
|
||||||
render_as_single_image, render_image
|
|
||||||
)
|
|
||||||
from ..tui.operations import clear_images_on_screen, raw_mode
|
|
||||||
|
|
||||||
OPTIONS = '''\
|
OPTIONS = '''\
|
||||||
--align
|
--align
|
||||||
type=choices
|
type=choices
|
||||||
@@ -46,7 +14,10 @@ Choose where on the screen to display the image. The image will be scaled to fit
|
|||||||
into the specified rectangle. The syntax for specifying rectangles is
|
into the specified rectangle. The syntax for specifying rectangles is
|
||||||
<:italic:`width`>x<:italic:`height`>@<:italic:`left`>x<:italic:`top`>.
|
<:italic:`width`>x<:italic:`height`>@<:italic:`left`>x<:italic:`top`>.
|
||||||
All measurements are in cells (i.e. cursor positions) with the origin
|
All measurements are in cells (i.e. cursor positions) with the origin
|
||||||
:italic:`(0, 0)` at the top-left corner of the screen.
|
:italic:`(0, 0)` at the top-left corner of the screen. Note that the :option:`--align`
|
||||||
|
option will horizontally align the image within this rectangle. By default, the image
|
||||||
|
is horizontally centered within the rectangle. Using place will cause the cursor to
|
||||||
|
be positioned at the top left corner of the image, instead of on the line after the image.
|
||||||
|
|
||||||
|
|
||||||
--scale-up
|
--scale-up
|
||||||
@@ -76,13 +47,14 @@ Remove all images currently displayed on the screen.
|
|||||||
|
|
||||||
--transfer-mode
|
--transfer-mode
|
||||||
type=choices
|
type=choices
|
||||||
choices=detect,file,stream
|
choices=detect,file,stream,memory
|
||||||
default=detect
|
default=detect
|
||||||
Which mechanism to use to transfer images to the terminal. The default is to
|
Which mechanism to use to transfer images to the terminal. The default is to
|
||||||
auto-detect. :italic:`file` means to use a temporary file and :italic:`stream`
|
auto-detect. :italic:`file` means to use a temporary file, :italic:`memory` means
|
||||||
means to send the data via terminal escape codes. Note that if you use the
|
to use shared memory, :italic:`stream` means to send the data via terminal
|
||||||
:italic:`file` transfer mode and you are connecting over a remote session then
|
escape codes. Note that if you use the :italic:`file` or :italic:`memory` transfer
|
||||||
image display will not work.
|
modes and you are connecting over a remote session then image display will not
|
||||||
|
work.
|
||||||
|
|
||||||
|
|
||||||
--detect-support
|
--detect-support
|
||||||
@@ -117,7 +89,17 @@ not a terminal, but you can turn it off or on explicitly, if needed.
|
|||||||
|
|
||||||
--silent
|
--silent
|
||||||
type=bool-set
|
type=bool-set
|
||||||
Do not print out anything to STDOUT during operation.
|
Not used, present for legacy compatibility.
|
||||||
|
|
||||||
|
|
||||||
|
--engine
|
||||||
|
type=choices
|
||||||
|
choices=auto,builtin,magick
|
||||||
|
default=auto
|
||||||
|
The engine used for decoding and processing of images. The default is to use
|
||||||
|
the most appropriate engine. The :code:`builtin` engine uses Go's native
|
||||||
|
imaging libraries. The :code:`magick` engine uses ImageMagick which requires
|
||||||
|
it to be installed on the system.
|
||||||
|
|
||||||
|
|
||||||
--z-index -z
|
--z-index -z
|
||||||
@@ -140,306 +122,6 @@ type=bool-set
|
|||||||
Wait for a key press before exiting after displaying the images.
|
Wait for a key press before exiting after displaying the images.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
screen_size: Optional[ScreenSizeGetter] = None
|
|
||||||
can_transfer_with_files = False
|
|
||||||
|
|
||||||
|
|
||||||
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() -> ScreenSize:
|
|
||||||
screen_size = get_screen_size_function()
|
|
||||||
return screen_size()
|
|
||||||
|
|
||||||
|
|
||||||
@run_once
|
|
||||||
def options_spec() -> str:
|
|
||||||
return OPTIONS.format(appname=f'{appname}-icat')
|
|
||||||
|
|
||||||
|
|
||||||
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: int, cell_width: int, align: str) -> int:
|
|
||||||
if align == 'left':
|
|
||||||
return 0
|
|
||||||
extra_pixels = width % cell_width
|
|
||||||
if not extra_pixels:
|
|
||||||
return 0
|
|
||||||
if align == 'right':
|
|
||||||
return cell_width - extra_pixels
|
|
||||||
return (cell_width - extra_pixels) // 2
|
|
||||||
|
|
||||||
|
|
||||||
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))
|
|
||||||
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
|
|
||||||
elif align == 'right':
|
|
||||||
extra_cells = (ss.cols - num_of_cells_needed)
|
|
||||||
if extra_cells:
|
|
||||||
sys.stdout.buffer.write(b' ' * extra_cells)
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
extra_cells = 0
|
|
||||||
if align == 'center':
|
|
||||||
extra_cells = (place.width - num_of_cells_needed) // 2
|
|
||||||
elif align == 'right':
|
|
||||||
extra_cells = place.width - num_of_cells_needed
|
|
||||||
sys.stdout.buffer.write(f'\033[{place.top + 1};{x + extra_cells}H'.encode('ascii'))
|
|
||||||
|
|
||||||
|
|
||||||
def write_chunked(cmd: GraphicsCommand, data: bytes) -> None:
|
|
||||||
cmd = cmd.clone()
|
|
||||||
if cmd.f != 100:
|
|
||||||
data = zlib.compress(data)
|
|
||||||
cmd.o = 'z'
|
|
||||||
data = standard_b64encode(data)
|
|
||||||
ac = cmd.a
|
|
||||||
quiet = cmd.q
|
|
||||||
while data:
|
|
||||||
chunk, data = data[:4096], data[4096:]
|
|
||||||
cmd.m = 1 if data else 0
|
|
||||||
write_gr_cmd(cmd, chunk)
|
|
||||||
cmd.clear()
|
|
||||||
cmd.a = ac
|
|
||||||
cmd.q = quiet
|
|
||||||
|
|
||||||
|
|
||||||
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,
|
|
||||||
use_number: int = 0
|
|
||||||
) -> None:
|
|
||||||
cmd = GraphicsCommand()
|
|
||||||
cmd.a = 'T'
|
|
||||||
cmd.f = fmt
|
|
||||||
cmd.s = width
|
|
||||||
cmd.v = height
|
|
||||||
cmd.z = zindex
|
|
||||||
if use_number:
|
|
||||||
cmd.I = use_number # noqa
|
|
||||||
cmd.q = 2
|
|
||||||
if place:
|
|
||||||
set_cursor_for_place(place, cmd, width, height, align)
|
|
||||||
else:
|
|
||||||
set_cursor(cmd, width, height, align)
|
|
||||||
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:
|
|
||||||
data = f.read()
|
|
||||||
if transmit_mode == 't':
|
|
||||||
os.unlink(outfile)
|
|
||||||
if fmt == 100:
|
|
||||||
cmd.S = len(data)
|
|
||||||
write_chunked(cmd, data)
|
|
||||||
|
|
||||||
|
|
||||||
def show_frames(frame_data: RenderedImage, use_number: int, loops: int) -> None:
|
|
||||||
transmit_cmd = GraphicsCommand()
|
|
||||||
transmit_cmd.a = 'f'
|
|
||||||
transmit_cmd.I = use_number # noqa
|
|
||||||
transmit_cmd.q = 2
|
|
||||||
if can_transfer_with_files:
|
|
||||||
transmit_cmd.t = 't'
|
|
||||||
transmit_cmd.f = 24 if frame_data.mode == 'rgb' else 32
|
|
||||||
|
|
||||||
def control(frame_number: int = 0, loops: Optional[int] = None, gap: Optional[int] = 0, animation_control: int = 0) -> None:
|
|
||||||
cmd = GraphicsCommand()
|
|
||||||
cmd.a = 'a'
|
|
||||||
cmd.I = use_number # noqa
|
|
||||||
cmd.r = frame_number
|
|
||||||
if loops is not None:
|
|
||||||
cmd.v = loops + 1
|
|
||||||
if gap is not None:
|
|
||||||
cmd.z = gap if gap > 0 else -1
|
|
||||||
if animation_control:
|
|
||||||
cmd.s = animation_control
|
|
||||||
write_gr_cmd(cmd)
|
|
||||||
|
|
||||||
anchor_frame = 0
|
|
||||||
|
|
||||||
for frame in frame_data.frames:
|
|
||||||
frame_number = frame.index + 1
|
|
||||||
if frame.dispose < Dispose.previous:
|
|
||||||
anchor_frame = frame_number
|
|
||||||
if frame_number == 1:
|
|
||||||
control(frame_number, gap=frame.gap, loops=None if loops < 1 else loops)
|
|
||||||
continue
|
|
||||||
if frame.dispose is Dispose.previous:
|
|
||||||
if anchor_frame != frame_number:
|
|
||||||
transmit_cmd.c = anchor_frame
|
|
||||||
else:
|
|
||||||
transmit_cmd.c = (frame_number - 1) if frame.needs_blend else 0
|
|
||||||
transmit_cmd.s = frame.width
|
|
||||||
transmit_cmd.v = frame.height
|
|
||||||
transmit_cmd.x = frame.canvas_x
|
|
||||||
transmit_cmd.y = frame.canvas_y
|
|
||||||
transmit_cmd.z = frame.gap if frame.gap > 0 else -1
|
|
||||||
if can_transfer_with_files:
|
|
||||||
write_gr_cmd(transmit_cmd, standard_b64encode(os.path.abspath(frame.path).encode(fsenc)))
|
|
||||||
else:
|
|
||||||
with open(frame.path, 'rb') as f:
|
|
||||||
data = f.read()
|
|
||||||
write_chunked(transmit_cmd, data)
|
|
||||||
if frame_number == 2:
|
|
||||||
control(animation_control=2)
|
|
||||||
control(animation_control=3)
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
remove_alpha: str = ''
|
|
||||||
flip: bool = False
|
|
||||||
flop: bool = False
|
|
||||||
|
|
||||||
|
|
||||||
def process(path: str, args: IcatCLIOptions, parsed_opts: ParsedOpts, is_tempfile: bool) -> bool:
|
|
||||||
m = identify(path)
|
|
||||||
ss = get_screen_size()
|
|
||||||
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
|
|
||||||
needs_conversion = needs_scaling or bool(parsed_opts.remove_alpha) or parsed_opts.flip or parsed_opts.flop
|
|
||||||
file_removed = False
|
|
||||||
use_number = 0
|
|
||||||
if m.fmt == 'png' and not needs_conversion:
|
|
||||||
outfile = path
|
|
||||||
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'
|
|
||||||
if len(m) == 1 or args.loop == 0:
|
|
||||||
outfile, width, height = render_as_single_image(
|
|
||||||
path, m, available_width, available_height, args.scale_up,
|
|
||||||
remove_alpha=parsed_opts.remove_alpha, flip=parsed_opts.flip, flop=parsed_opts.flop)
|
|
||||||
else:
|
|
||||||
import struct
|
|
||||||
use_number = max(1, struct.unpack('@I', os.urandom(4))[0])
|
|
||||||
with NamedTemporaryFile() as f:
|
|
||||||
prefix = f.name
|
|
||||||
frame_data = render_image(
|
|
||||||
path, prefix, m, available_width, available_height, args.scale_up,
|
|
||||||
remove_alpha=parsed_opts.remove_alpha, flip=parsed_opts.flip, flop=parsed_opts.flop)
|
|
||||||
outfile, width, height = frame_data.frames[0].path, frame_data.width, frame_data.height
|
|
||||||
show(
|
|
||||||
outfile, width, height, parsed_opts.z_index, fmt, transmit_mode,
|
|
||||||
align=args.align, place=parsed_opts.place, use_number=use_number
|
|
||||||
)
|
|
||||||
if use_number:
|
|
||||||
show_frames(frame_data, use_number, args.loop)
|
|
||||||
if not can_transfer_with_files:
|
|
||||||
for fr in frame_data.frames:
|
|
||||||
with contextlib.suppress(FileNotFoundError):
|
|
||||||
os.unlink(fr.path)
|
|
||||||
if not args.place:
|
|
||||||
print() # ensure cursor is on a new line
|
|
||||||
return file_removed
|
|
||||||
|
|
||||||
|
|
||||||
def scan(d: str) -> Generator[Tuple[str, str], None, None]:
|
|
||||||
for dirpath, dirnames, filenames in os.walk(d):
|
|
||||||
for f in filenames:
|
|
||||||
mt = guess_type(f)
|
|
||||||
if mt and mt.startswith('image/'):
|
|
||||||
yield os.path.join(dirpath, f), mt
|
|
||||||
|
|
||||||
|
|
||||||
def detect_support(wait_for: float = 10, silent: bool = False) -> bool:
|
|
||||||
global can_transfer_with_files
|
|
||||||
if not silent:
|
|
||||||
print(f'Checking for graphics ({wait_for}s max. wait)...', end='\r')
|
|
||||||
sys.stdout.flush()
|
|
||||||
try:
|
|
||||||
received = b''
|
|
||||||
responses: Dict[int, bool] = {}
|
|
||||||
|
|
||||||
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'
|
|
||||||
|
|
||||||
def more_needed(data: bytes) -> bool:
|
|
||||||
nonlocal received
|
|
||||||
received += data
|
|
||||||
parse_responses()
|
|
||||||
return 1 not in responses or 2 not in responses
|
|
||||||
|
|
||||||
with NamedTemporaryFile() as f:
|
|
||||||
f.write(b'abcd')
|
|
||||||
f.flush()
|
|
||||||
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=wait_for)
|
|
||||||
finally:
|
|
||||||
if not silent:
|
|
||||||
sys.stdout.buffer.write(b'\033[J'), sys.stdout.flush()
|
|
||||||
can_transfer_with_files = bool(responses.get(2))
|
|
||||||
return responses.get(1, False)
|
|
||||||
|
|
||||||
|
|
||||||
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 Place(w, h, l, t)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
help_text = (
|
help_text = (
|
||||||
'A cat like utility to display images in the terminal.'
|
'A cat like utility to display images in the terminal.'
|
||||||
' You can specify multiple image files and/or directories.'
|
' You can specify multiple image files and/or directories.'
|
||||||
@@ -451,174 +133,15 @@ help_text = (
|
|||||||
usage = 'image-file-or-url-or-directory ...'
|
usage = 'image-file-or-url-or-directory ...'
|
||||||
|
|
||||||
|
|
||||||
@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[str]'] = None,
|
|
||||||
maybe_dir: bool = True
|
|
||||||
) -> None:
|
|
||||||
is_tempfile = False
|
|
||||||
file_removed = False
|
|
||||||
try:
|
|
||||||
if isinstance(item, bytes):
|
|
||||||
with NamedTemporaryFile(prefix='stdin-image-data-', delete=False) as tf:
|
|
||||||
tf.write(item)
|
|
||||||
item = tf.name
|
|
||||||
is_tempfile = True
|
|
||||||
if url_pat is not None and url_pat.match(item) is not None:
|
|
||||||
from urllib.request import urlretrieve
|
|
||||||
with NamedTemporaryFile(prefix='url-image-data-', delete=False) as tf:
|
|
||||||
try:
|
|
||||||
with socket_timeout(30):
|
|
||||||
urlretrieve(item, filename=tf.name)
|
|
||||||
except Exception as e:
|
|
||||||
raise SystemExit(f'Failed to download image at URL: {item} with error: {e}')
|
|
||||||
item = tf.name
|
|
||||||
is_tempfile = True
|
|
||||||
file_removed = process(item, args, parsed_opts, is_tempfile)
|
|
||||||
elif item.lower().startswith('file://'):
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
from urllib.request import url2pathname
|
|
||||||
pitem = urlparse(item)
|
|
||||||
if os.sep == '\\':
|
|
||||||
item = pitem.netloc + pitem.path
|
|
||||||
else:
|
|
||||||
item = pitem.path
|
|
||||||
item = url2pathname(item)
|
|
||||||
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, parsed_opts, url_pat=None, maybe_dir=False)
|
|
||||||
else:
|
|
||||||
file_removed = process(item, args, parsed_opts, is_tempfile)
|
|
||||||
finally:
|
|
||||||
if is_tempfile and not file_removed:
|
|
||||||
os.remove(item)
|
|
||||||
|
|
||||||
|
|
||||||
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, f'{appname} +kitten icat', result_class=IcatCLIOptions)
|
|
||||||
items: List[Union[str, bytes]] = list(items_)
|
|
||||||
|
|
||||||
if cli_opts.print_window_size:
|
|
||||||
screen_size_function.cache_clear()
|
|
||||||
with open(os.ctermid()) as tty:
|
|
||||||
try:
|
|
||||||
fd = tty.fileno()
|
|
||||||
except AttributeError:
|
|
||||||
# use default value for fd if ctermid is not available
|
|
||||||
fd = None
|
|
||||||
ss = screen_size_function(fd)()
|
|
||||||
print(f'{ss.width}x{ss.height}', end='')
|
|
||||||
raise SystemExit(0)
|
|
||||||
|
|
||||||
if not sys.stdout.isatty():
|
|
||||||
sys.stdout = open(os.ctermid(), 'w')
|
|
||||||
stdin_data = None
|
|
||||||
if cli_opts.stdin == 'yes' or (cli_opts.stdin == 'detect' and sys.stdin is not None and not sys.stdin.isatty()):
|
|
||||||
stdin_data = sys.stdin.buffer.read()
|
|
||||||
if stdin_data:
|
|
||||||
items.insert(0, stdin_data)
|
|
||||||
sys.stdin.close()
|
|
||||||
sys.stdin = open(os.ctermid())
|
|
||||||
|
|
||||||
screen_size = get_screen_size_function()
|
|
||||||
signal.signal(signal.SIGWINCH, lambda signum, frame: setattr(screen_size, 'changed', True))
|
|
||||||
if screen_size().width == 0:
|
|
||||||
if cli_opts.detect_support:
|
|
||||||
raise SystemExit(1)
|
|
||||||
raise SystemExit(
|
|
||||||
'Terminal does not support reporting screen sizes via the TIOCGWINSZ ioctl'
|
|
||||||
)
|
|
||||||
parsed_opts = ParsedOpts()
|
|
||||||
if cli_opts.place:
|
|
||||||
try:
|
|
||||||
parsed_opts.place = parse_place(cli_opts.place)
|
|
||||||
except Exception:
|
|
||||||
raise SystemExit(f'Not a valid place specification: {cli_opts.place}')
|
|
||||||
|
|
||||||
try:
|
|
||||||
parsed_opts.z_index = parse_z_index(cli_opts.z_index)
|
|
||||||
except Exception:
|
|
||||||
raise SystemExit(f'Not a valid z-index specification: {cli_opts.z_index}')
|
|
||||||
if cli_opts.background != 'none':
|
|
||||||
ra = to_color(cli_opts.background)
|
|
||||||
if ra is None:
|
|
||||||
raise SystemExit(f'Not a valid color specification: {cli_opts.background}')
|
|
||||||
parsed_opts.remove_alpha = ra.as_sharp
|
|
||||||
parsed_opts.flip = cli_opts.mirror in ('both', 'vertical')
|
|
||||||
parsed_opts.flop = cli_opts.mirror in ('both', 'horizontal')
|
|
||||||
|
|
||||||
if cli_opts.detect_support:
|
|
||||||
if not detect_support(wait_for=cli_opts.detection_timeout, silent=True):
|
|
||||||
raise SystemExit(1)
|
|
||||||
print('file' if can_transfer_with_files else 'stream', end='', file=sys.stderr)
|
|
||||||
return
|
|
||||||
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:
|
|
||||||
can_transfer_with_files = cli_opts.transfer_mode == 'file'
|
|
||||||
errors = []
|
|
||||||
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 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)
|
|
||||||
|
|
||||||
def hold_if_needed(exit_code_or_msg: Union[int, str]) -> None:
|
|
||||||
if cli_opts.hold:
|
|
||||||
if isinstance(exit_code_or_msg, str):
|
|
||||||
print(exit_code_or_msg, file=sys.stderr, flush=True)
|
|
||||||
exit_code_or_msg = 1
|
|
||||||
with open(os.ctermid()) as tty, raw_mode(tty.fileno()):
|
|
||||||
tty.buffer.read(1)
|
|
||||||
raise SystemExit(exit_code_or_msg)
|
|
||||||
|
|
||||||
for item in items:
|
|
||||||
try:
|
|
||||||
process_single_item(item, cli_opts, parsed_opts, url_pat)
|
|
||||||
except NoImageMagick as e:
|
|
||||||
hold_if_needed(str(e))
|
|
||||||
except OutdatedImageMagick as e:
|
|
||||||
print(e.detailed_error, file=sys.stderr)
|
|
||||||
hold_if_needed(str(e))
|
|
||||||
except ConvertFailed as e:
|
|
||||||
hold_if_needed(str(e))
|
|
||||||
except OpenFailed as e:
|
|
||||||
errors.append(e)
|
|
||||||
if parsed_opts.place:
|
|
||||||
sys.stdout.buffer.write(b'\0338') # restore cursor
|
|
||||||
if errors:
|
|
||||||
for err in errors:
|
|
||||||
print(err, file=sys.stderr)
|
|
||||||
hold_if_needed(1 if errors else 0)
|
|
||||||
raise SystemExit()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
raise SystemExit('This should be run as kitten icat')
|
||||||
elif __name__ == '__doc__':
|
elif __name__ == '__doc__':
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from kitty.cli import CompletionSpec
|
||||||
cd = sys.cli_docs # type: ignore
|
cd = sys.cli_docs # type: ignore
|
||||||
cd['usage'] = usage
|
cd['usage'] = usage
|
||||||
cd['options'] = options_spec
|
cd['options'] = lambda: OPTIONS.format()
|
||||||
cd['help_text'] = help_text
|
cd['help_text'] = help_text
|
||||||
|
cd['short_desc'] = 'Display images in the terminal'
|
||||||
|
cd['args_completion'] = CompletionSpec.from_string('type:file mime:image/* group:Images')
|
||||||
|
|||||||
@@ -2,14 +2,13 @@
|
|||||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, Callable, Dict, List, Tuple
|
from typing import Any, Callable, Dict, List, Tuple
|
||||||
|
|
||||||
from kitty.cli import parse_args
|
from kitty.cli import parse_args
|
||||||
from kitty.cli_stub import PanelCLIOptions
|
from kitty.cli_stub import PanelCLIOptions
|
||||||
from kitty.constants import appname, is_macos
|
from kitty.constants import appname, is_macos
|
||||||
|
from kitty.fast_data_types import make_x11_window_a_dock_window
|
||||||
from kitty.os_window_size import WindowSizeData
|
from kitty.os_window_size import WindowSizeData
|
||||||
|
|
||||||
OPTIONS = r'''
|
OPTIONS = r'''
|
||||||
@@ -65,14 +64,7 @@ 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)
|
return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten panel', result_class=PanelCLIOptions)
|
||||||
|
|
||||||
|
|
||||||
def call_xprop(*cmd: str, silent: bool = False) -> None:
|
Strut = Tuple[int, int, int, int, int, int, int, int, int, int, int, int]
|
||||||
cmd_ = ['xprop'] + list(cmd)
|
|
||||||
try:
|
|
||||||
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:
|
|
||||||
raise SystemExit(cp.returncode)
|
|
||||||
|
|
||||||
|
|
||||||
def create_strut(
|
def create_strut(
|
||||||
@@ -80,43 +72,33 @@ def create_strut(
|
|||||||
left: int = 0, right: int = 0, top: int = 0, bottom: int = 0, left_start_y: int = 0, left_end_y: int = 0,
|
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,
|
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
|
bottom_start_x: int = 0, bottom_end_x: int = 0
|
||||||
) -> None:
|
) -> Strut:
|
||||||
call_xprop(
|
return left, right, top, bottom, left_start_y, left_end_y, right_start_y, right_end_y, top_start_x, top_end_x, bottom_start_x, bottom_end_x
|
||||||
'-id',
|
|
||||||
str(int(win_id)), '-format', '_NET_WM_STRUT_PARTIAL', '32cccccccccccc',
|
|
||||||
'-set', '_NET_WM_STRUT_PARTIAL',
|
|
||||||
f'{left},{right},{top},{bottom},'
|
|
||||||
f'{left_start_y},{left_end_y},{right_start_y},{right_end_y},'
|
|
||||||
f'{top_start_x},{top_end_x},{bottom_start_x},{bottom_end_x}'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def create_top_strut(win_id: int, width: int, height: int) -> None:
|
def create_top_strut(win_id: int, width: int, height: int) -> Strut:
|
||||||
create_strut(win_id, top=height, top_end_x=width)
|
return create_strut(win_id, top=height, top_end_x=width)
|
||||||
|
|
||||||
|
|
||||||
def create_bottom_strut(win_id: int, width: int, height: int) -> None:
|
def create_bottom_strut(win_id: int, width: int, height: int) -> Strut:
|
||||||
create_strut(win_id, bottom=height, bottom_end_x=width)
|
return create_strut(win_id, bottom=height, bottom_end_x=width)
|
||||||
|
|
||||||
|
|
||||||
def create_left_strut(win_id: int, width: int, height: int) -> None:
|
def create_left_strut(win_id: int, width: int, height: int) -> Strut:
|
||||||
create_strut(win_id, left=width, left_end_y=height)
|
return create_strut(win_id, left=width, left_end_y=height)
|
||||||
|
|
||||||
|
|
||||||
def create_right_strut(win_id: int, width: int, height: int) -> None:
|
def create_right_strut(win_id: int, width: int, height: int) -> Strut:
|
||||||
create_strut(win_id, right=width, right_end_y=height)
|
return create_strut(win_id, right=width, right_end_y=height)
|
||||||
|
|
||||||
|
|
||||||
window_width = window_height = 0
|
window_width = window_height = 0
|
||||||
|
|
||||||
|
|
||||||
def setup_x11_window(win_id: int) -> None:
|
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()[f'create_{args.edge}_strut']
|
func = globals()[f'create_{args.edge}_strut']
|
||||||
func(win_id, window_width, window_height)
|
strut = func(win_id, window_width, window_height)
|
||||||
|
make_x11_window_a_dock_window(win_id, strut)
|
||||||
|
|
||||||
|
|
||||||
def initial_window_size_func(opts: WindowSizeData, cached_values: Dict[str, Any]) -> Callable[[int, int, float, float, float, float], Tuple[int, int]]:
|
def initial_window_size_func(opts: WindowSizeData, cached_values: Dict[str, Any]) -> Callable[[int, int, float, float, float, float], Tuple[int, int]]:
|
||||||
@@ -140,8 +122,6 @@ def main(sys_args: List[str]) -> None:
|
|||||||
global args
|
global args
|
||||||
if is_macos or not os.environ.get('DISPLAY'):
|
if is_macos or not os.environ.get('DISPLAY'):
|
||||||
raise SystemExit('Currently the panel kitten is supported only on X11 desktops')
|
raise SystemExit('Currently the panel kitten is supported only on X11 desktops')
|
||||||
if not shutil.which('xprop'):
|
|
||||||
raise SystemExit('The xprop program is required for the panel kitten')
|
|
||||||
args, items = parse_panel_args(sys_args[1:])
|
args, items = parse_panel_args(sys_args[1:])
|
||||||
if not items:
|
if not items:
|
||||||
raise SystemExit('You must specify the program to run')
|
raise SystemExit('You must specify the program to run')
|
||||||
@@ -154,7 +134,8 @@ def main(sys_args: List[str]) -> None:
|
|||||||
for override in args.override:
|
for override in args.override:
|
||||||
sys.argv.extend(('--override', override))
|
sys.argv.extend(('--override', override))
|
||||||
sys.argv.extend(items)
|
sys.argv.extend(items)
|
||||||
from kitty.main import main as real_main, run_app
|
from kitty.main import main as real_main
|
||||||
|
from kitty.main import run_app
|
||||||
run_app.cached_values_name = 'panel'
|
run_app.cached_values_name = 'panel'
|
||||||
run_app.first_window_callback = setup_x11_window
|
run_app.first_window_callback = setup_x11_window
|
||||||
run_app.initial_window_size_func = initial_window_size_func
|
run_app.initial_window_size_func = initial_window_size_func
|
||||||
|
|||||||
@@ -1,453 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# License: GPLv3 Copyright: 2022, Kovid Goyal <kovid at kovidgoyal.net>
|
|
||||||
|
|
||||||
import io
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import select
|
|
||||||
import signal
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
from contextlib import suppress
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from importlib import import_module
|
|
||||||
from typing import (
|
|
||||||
IO, TYPE_CHECKING, Any, Dict, List, NoReturn, Optional, Union, cast
|
|
||||||
)
|
|
||||||
|
|
||||||
from kitty.child import remove_cloexec
|
|
||||||
from kitty.constants import kitty_exe
|
|
||||||
from kitty.entry_points import main as main_entry_point
|
|
||||||
from kitty.fast_data_types import establish_controlling_tty, safe_pipe
|
|
||||||
from kitty.shm import SharedMemory
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from _typeshed import ReadableBuffer, WriteableBuffer
|
|
||||||
|
|
||||||
|
|
||||||
hangup_events = select.POLLHUP
|
|
||||||
error_events = select.POLLERR | select.POLLNVAL
|
|
||||||
basic_events = hangup_events | error_events
|
|
||||||
|
|
||||||
|
|
||||||
class PrewarmProcessFailed(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Child:
|
|
||||||
child_id: int
|
|
||||||
child_process_pid: int
|
|
||||||
|
|
||||||
|
|
||||||
class PrewarmProcess:
|
|
||||||
|
|
||||||
def __init__(self, create_file_to_read_from_worker: bool = False) -> None:
|
|
||||||
self.from_worker_fd, self.in_worker_fd = safe_pipe()
|
|
||||||
self.children: Dict[int, Child] = {}
|
|
||||||
if create_file_to_read_from_worker:
|
|
||||||
os.set_blocking(self.from_worker_fd, True)
|
|
||||||
self.from_worker = open(self.from_worker_fd, mode='r', closefd=True)
|
|
||||||
self.from_worker_fd = -1
|
|
||||||
|
|
||||||
def take_from_worker_fd(self) -> int:
|
|
||||||
ans, self.from_worker_fd = self.from_worker_fd, -1
|
|
||||||
return ans
|
|
||||||
|
|
||||||
def __del__(self) -> None:
|
|
||||||
if self.from_worker_fd > -1:
|
|
||||||
os.close(self.from_worker_fd)
|
|
||||||
self.from_worker_fd = -1
|
|
||||||
if hasattr(self, 'from_worker'):
|
|
||||||
self.from_worker.close()
|
|
||||||
del self.from_worker
|
|
||||||
if self.worker_started:
|
|
||||||
import subprocess
|
|
||||||
self.process.stdin and self.process.stdin.close()
|
|
||||||
self.process.stdout and self.process.stdout.close()
|
|
||||||
try:
|
|
||||||
self.process.wait(timeout=1.0)
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
self.process.kill()
|
|
||||||
del self.process
|
|
||||||
|
|
||||||
@property
|
|
||||||
def worker_started(self) -> bool:
|
|
||||||
return self.in_worker_fd == -1
|
|
||||||
|
|
||||||
def ensure_worker(self) -> None:
|
|
||||||
if not self.worker_started:
|
|
||||||
import subprocess
|
|
||||||
self.process = subprocess.Popen(
|
|
||||||
[kitty_exe(), '+kitten', 'prewarm', str(self.in_worker_fd)], stdin=subprocess.PIPE, stdout=subprocess.PIPE, pass_fds=(self.in_worker_fd,))
|
|
||||||
os.close(self.in_worker_fd)
|
|
||||||
self.in_worker_fd = -1
|
|
||||||
assert self.process.stdin is not None and self.process.stdout is not None
|
|
||||||
self.write_to_process_fd = self.process.stdin.fileno()
|
|
||||||
self.read_from_process_fd = self.process.stdout.fileno()
|
|
||||||
os.set_blocking(self.write_to_process_fd, False)
|
|
||||||
os.set_blocking(self.read_from_process_fd, False)
|
|
||||||
self.poll = select.poll()
|
|
||||||
self.poll.register(self.process.stdout.fileno(), select.POLLIN | basic_events)
|
|
||||||
|
|
||||||
def poll_to_send(self, yes: bool = True) -> None:
|
|
||||||
if yes:
|
|
||||||
self.poll.register(self.write_to_process_fd, select.POLLOUT | basic_events)
|
|
||||||
else:
|
|
||||||
self.poll.unregister(self.write_to_process_fd)
|
|
||||||
|
|
||||||
def reload_kitty_config(self) -> None:
|
|
||||||
if self.worker_started:
|
|
||||||
self.send_to_prewarm_process('reload_kitty_config:\n')
|
|
||||||
|
|
||||||
def __call__(
|
|
||||||
self,
|
|
||||||
tty_fd: int,
|
|
||||||
argv: List[str],
|
|
||||||
cwd: str = '',
|
|
||||||
env: Optional[Dict[str, str]] = None,
|
|
||||||
stdin_data: Optional[Union[str, bytes]] = None
|
|
||||||
) -> Child:
|
|
||||||
self.ensure_worker()
|
|
||||||
tty_name = os.ttyname(tty_fd)
|
|
||||||
if isinstance(stdin_data, str):
|
|
||||||
stdin_data = stdin_data.encode()
|
|
||||||
if env is None:
|
|
||||||
env = dict(os.environ)
|
|
||||||
cmd: Dict[str, Union[int, List[str], str, Dict[str, str]]] = {
|
|
||||||
'tty_name': tty_name, 'cwd': cwd or os.getcwd(), 'argv': argv, 'env': env,
|
|
||||||
}
|
|
||||||
total_size = 0
|
|
||||||
if stdin_data is not None:
|
|
||||||
cmd['stdin_size'] = len(stdin_data)
|
|
||||||
total_size += len(stdin_data)
|
|
||||||
data = json.dumps(cmd).encode()
|
|
||||||
total_size += len(data) + SharedMemory.num_bytes_for_size
|
|
||||||
with SharedMemory(size=total_size, unlink_on_exit=True) as shm:
|
|
||||||
shm.write_data_with_size(data)
|
|
||||||
if stdin_data:
|
|
||||||
shm.write(stdin_data)
|
|
||||||
shm.flush()
|
|
||||||
self.send_to_prewarm_process(f'fork:{shm.name}\n')
|
|
||||||
input_buf = b''
|
|
||||||
st = time.monotonic()
|
|
||||||
while time.monotonic() - st < 2:
|
|
||||||
for (fd, event) in self.poll.poll(0.2):
|
|
||||||
if event & basic_events:
|
|
||||||
raise PrewarmProcessFailed('Failed doing I/O with prewarm process')
|
|
||||||
if fd == self.read_from_process_fd and event & select.POLLIN:
|
|
||||||
d = os.read(self.read_from_process_fd, io.DEFAULT_BUFFER_SIZE)
|
|
||||||
input_buf += d
|
|
||||||
while (idx := input_buf.find(b'\n')) > -1:
|
|
||||||
line = input_buf[:idx].decode()
|
|
||||||
input_buf = input_buf[idx+1:]
|
|
||||||
if line.startswith('CHILD:'):
|
|
||||||
_, cid, pid = line.split(':')
|
|
||||||
child = self.add_child(int(cid), int(pid))
|
|
||||||
shm.unlink_on_exit = False
|
|
||||||
return child
|
|
||||||
if line.startswith('ERR:'):
|
|
||||||
raise PrewarmProcessFailed(line.split(':', 1)[-1])
|
|
||||||
raise PrewarmProcessFailed('Timed out waiting for I/O with prewarm process')
|
|
||||||
|
|
||||||
def add_child(self, child_id: int, pid: int) -> Child:
|
|
||||||
self.children[child_id] = c = Child(child_id, pid)
|
|
||||||
return c
|
|
||||||
|
|
||||||
def send_to_prewarm_process(self, output_buf: Union[str, bytes] = b'', timeout: float = 2) -> None:
|
|
||||||
if isinstance(output_buf, str):
|
|
||||||
output_buf = output_buf.encode()
|
|
||||||
st = time.monotonic()
|
|
||||||
while time.monotonic() - st < timeout and output_buf:
|
|
||||||
self.poll_to_send(bool(output_buf))
|
|
||||||
for (fd, event) in self.poll.poll(0.2):
|
|
||||||
if event & basic_events:
|
|
||||||
raise PrewarmProcessFailed('Failed doing I/O with prewarm process')
|
|
||||||
if fd == self.write_to_process_fd and event & select.POLLOUT:
|
|
||||||
n = os.write(self.write_to_process_fd, output_buf)
|
|
||||||
output_buf = output_buf[n:]
|
|
||||||
self.poll_to_send(False)
|
|
||||||
if output_buf:
|
|
||||||
raise PrewarmProcessFailed('Timed out waiting to write to prewarm process')
|
|
||||||
|
|
||||||
def mark_child_as_ready(self, child_id: int) -> bool:
|
|
||||||
c = self.children.pop(child_id, None)
|
|
||||||
if c is None:
|
|
||||||
return False
|
|
||||||
self.send_to_prewarm_process(f'ready:{child_id}\n')
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def reload_kitty_config() -> None:
|
|
||||||
from kittens.tui.utils import kitty_opts
|
|
||||||
kitty_opts.clear_cached()
|
|
||||||
kitty_opts()
|
|
||||||
|
|
||||||
|
|
||||||
def prewarm() -> None:
|
|
||||||
reload_kitty_config()
|
|
||||||
for kitten in ('hints', 'ssh', 'unicode_input', 'ask', 'show_error'):
|
|
||||||
import_module(f'kittens.{kitten}.main')
|
|
||||||
|
|
||||||
|
|
||||||
class MemoryViewReadWrapperBytes(io.BufferedIOBase):
|
|
||||||
|
|
||||||
def __init__(self, mw: memoryview):
|
|
||||||
self.mw = mw
|
|
||||||
self.pos = 0
|
|
||||||
|
|
||||||
def detach(self) -> io.RawIOBase:
|
|
||||||
raise io.UnsupportedOperation('detach() not supported')
|
|
||||||
|
|
||||||
def read(self, size: Optional[int] = -1) -> bytes:
|
|
||||||
if size is None or size < 0:
|
|
||||||
size = max(0, len(self.mw) - self.pos)
|
|
||||||
oldpos = self.pos
|
|
||||||
self.pos = min(len(self.mw), self.pos + size)
|
|
||||||
if self.pos <= oldpos:
|
|
||||||
return b''
|
|
||||||
return bytes(self.mw[oldpos:self.pos])
|
|
||||||
|
|
||||||
def readinto(self, b: 'WriteableBuffer') -> int:
|
|
||||||
if not isinstance(b, memoryview):
|
|
||||||
b = memoryview(b)
|
|
||||||
b = b.cast('B')
|
|
||||||
data = self.read(len(b))
|
|
||||||
n = len(data)
|
|
||||||
b[:n] = data
|
|
||||||
return n
|
|
||||||
readinto1 = readinto
|
|
||||||
|
|
||||||
def readall(self) -> bytes:
|
|
||||||
return self.read()
|
|
||||||
|
|
||||||
def write(self, b: 'ReadableBuffer') -> int:
|
|
||||||
raise io.UnsupportedOperation('readonly stream')
|
|
||||||
|
|
||||||
def readable(self) -> bool:
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class MemoryViewReadWrapper(io.TextIOWrapper):
|
|
||||||
|
|
||||||
def __init__(self, mw: memoryview):
|
|
||||||
super().__init__(cast(IO[bytes], MemoryViewReadWrapperBytes(mw)), encoding='utf-8', errors='replace')
|
|
||||||
|
|
||||||
|
|
||||||
def child_main(cmd: Dict[str, Any], ready_fd: int) -> NoReturn:
|
|
||||||
cwd = cmd.get('cwd')
|
|
||||||
if cwd:
|
|
||||||
try:
|
|
||||||
os.chdir(cwd)
|
|
||||||
except OSError:
|
|
||||||
with suppress(OSError):
|
|
||||||
os.chdir('/')
|
|
||||||
os.setsid()
|
|
||||||
env = cmd.get('env')
|
|
||||||
if env is not None:
|
|
||||||
os.environ.clear()
|
|
||||||
os.environ.update(env)
|
|
||||||
argv = cmd.get('argv')
|
|
||||||
if argv:
|
|
||||||
sys.argv = list(argv)
|
|
||||||
poll = select.poll()
|
|
||||||
poll.register(ready_fd, select.POLLIN | select.POLLERR | select.POLLHUP)
|
|
||||||
poll.poll()
|
|
||||||
os.close(ready_fd)
|
|
||||||
tty_name = cmd.get('tty_name')
|
|
||||||
if tty_name:
|
|
||||||
sys.__stdout__.flush()
|
|
||||||
sys.__stderr__.flush()
|
|
||||||
establish_controlling_tty(tty_name, sys.__stdin__.fileno(), sys.__stdout__.fileno(), sys.__stderr__.fileno())
|
|
||||||
main_entry_point()
|
|
||||||
raise SystemExit(0)
|
|
||||||
|
|
||||||
|
|
||||||
def fork(shm_address: str, ready_fd: int) -> int:
|
|
||||||
sz = pos = 0
|
|
||||||
with SharedMemory(name=shm_address, unlink_on_exit=True) as shm:
|
|
||||||
data = shm.read_data_with_size()
|
|
||||||
cmd = json.loads(data)
|
|
||||||
sz = cmd.get('stdin_size', 0)
|
|
||||||
if sz:
|
|
||||||
pos = shm.tell()
|
|
||||||
shm.unlink_on_exit = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
child_pid = os.fork()
|
|
||||||
except OSError:
|
|
||||||
if sz:
|
|
||||||
with SharedMemory(shm_address, unlink_on_exit=True):
|
|
||||||
pass
|
|
||||||
if child_pid:
|
|
||||||
# master process
|
|
||||||
return child_pid
|
|
||||||
# child process
|
|
||||||
if shm.unlink_on_exit:
|
|
||||||
child_main(cmd, ready_fd)
|
|
||||||
else:
|
|
||||||
with SharedMemory(shm_address, unlink_on_exit=True) as shm:
|
|
||||||
stdin_data = memoryview(shm.mmap)[pos:pos + sz]
|
|
||||||
if stdin_data:
|
|
||||||
sys.stdin = MemoryViewReadWrapper(stdin_data)
|
|
||||||
try:
|
|
||||||
child_main(cmd, ready_fd)
|
|
||||||
finally:
|
|
||||||
stdin_data.release()
|
|
||||||
sys.stdin = sys.__stdin__
|
|
||||||
|
|
||||||
|
|
||||||
def main(args: List[str] = sys.argv) -> None:
|
|
||||||
read_signal_fd, write_signal_fd = safe_pipe()
|
|
||||||
notify_child_death_fd = int(sys.argv[-1])
|
|
||||||
os.set_blocking(notify_child_death_fd, False)
|
|
||||||
signal.set_wakeup_fd(write_signal_fd)
|
|
||||||
signal.signal(signal.SIGCHLD, lambda *a: None)
|
|
||||||
signal.siginterrupt(signal.SIGCHLD, False)
|
|
||||||
prewarm()
|
|
||||||
stdin_fd = sys.__stdin__.fileno()
|
|
||||||
os.set_blocking(stdin_fd, False)
|
|
||||||
stdout_fd = sys.__stdout__.fileno()
|
|
||||||
os.set_blocking(stdout_fd, False)
|
|
||||||
poll = select.poll()
|
|
||||||
poll.register(stdin_fd, select.POLLIN | basic_events)
|
|
||||||
poll.register(read_signal_fd, select.POLLIN | basic_events)
|
|
||||||
input_buf = output_buf = child_death_buf = b''
|
|
||||||
child_ready_fds: Dict[int, int] = {}
|
|
||||||
child_id_map: Dict[int, int] = {}
|
|
||||||
self_pid = os.getpid()
|
|
||||||
|
|
||||||
def check_event(event: int, err_msg: str) -> None:
|
|
||||||
if event & hangup_events:
|
|
||||||
raise SystemExit(0)
|
|
||||||
if event & error_events:
|
|
||||||
raise SystemExit(err_msg)
|
|
||||||
|
|
||||||
def handle_input(event: int) -> None:
|
|
||||||
nonlocal input_buf, output_buf
|
|
||||||
check_event(event, 'Polling of STDIN failed')
|
|
||||||
if not (event & select.POLLIN):
|
|
||||||
return
|
|
||||||
d = os.read(stdin_fd, io.DEFAULT_BUFFER_SIZE)
|
|
||||||
if not d:
|
|
||||||
raise SystemExit(0)
|
|
||||||
input_buf += d
|
|
||||||
while (idx := input_buf.find(b'\n')) > -1:
|
|
||||||
line = input_buf[:idx].decode()
|
|
||||||
input_buf = input_buf[idx+1:]
|
|
||||||
cmd, _, payload = line.partition(':')
|
|
||||||
if cmd == 'reload_kitty_config':
|
|
||||||
reload_kitty_config()
|
|
||||||
elif cmd == 'ready':
|
|
||||||
child_id = int(payload)
|
|
||||||
cfd = child_ready_fds.pop(child_id)
|
|
||||||
if cfd is not None:
|
|
||||||
os.write(cfd, b'1')
|
|
||||||
os.close(cfd)
|
|
||||||
elif cmd == 'fork':
|
|
||||||
read_fd, write_fd = safe_pipe(False)
|
|
||||||
remove_cloexec(read_fd)
|
|
||||||
try:
|
|
||||||
child_pid = fork(payload, read_fd)
|
|
||||||
except Exception as e:
|
|
||||||
es = str(e).replace('\n', ' ')
|
|
||||||
output_buf += f'ERR:{es}\n'.encode()
|
|
||||||
else:
|
|
||||||
if os.getpid() == self_pid:
|
|
||||||
child_id = len(child_id_map) + 1
|
|
||||||
child_id_map[child_id] = child_pid
|
|
||||||
child_ready_fds[child_id] = write_fd
|
|
||||||
output_buf += f'CHILD:{child_id}:{child_pid}\n'.encode()
|
|
||||||
finally:
|
|
||||||
if os.getpid() == self_pid:
|
|
||||||
os.close(read_fd)
|
|
||||||
elif cmd == 'echo':
|
|
||||||
output_buf += f'{payload}\n'.encode()
|
|
||||||
|
|
||||||
def handle_output(event: int) -> None:
|
|
||||||
nonlocal output_buf
|
|
||||||
check_event(event, 'Polling of STDOUT failed')
|
|
||||||
if not (event & select.POLLOUT):
|
|
||||||
return
|
|
||||||
if output_buf:
|
|
||||||
n = os.write(stdout_fd, output_buf)
|
|
||||||
if not n:
|
|
||||||
raise SystemExit(0)
|
|
||||||
output_buf = output_buf[n:]
|
|
||||||
if not output_buf:
|
|
||||||
poll.unregister(stdout_fd)
|
|
||||||
|
|
||||||
def handle_notify_child_death(event: int) -> None:
|
|
||||||
nonlocal child_death_buf
|
|
||||||
check_event(event, 'Polling of notify child death fd failed')
|
|
||||||
if not (event & select.POLLOUT):
|
|
||||||
return
|
|
||||||
if child_death_buf:
|
|
||||||
n = os.write(notify_child_death_fd, child_death_buf)
|
|
||||||
if not n:
|
|
||||||
raise SystemExit(0)
|
|
||||||
child_death_buf = child_death_buf[n:]
|
|
||||||
if not child_death_buf:
|
|
||||||
poll.unregister(notify_child_death_fd)
|
|
||||||
|
|
||||||
def handle_signal(event: int) -> None:
|
|
||||||
nonlocal child_death_buf
|
|
||||||
check_event(event, 'Polling of signal fd failed')
|
|
||||||
if not (event & select.POLLIN):
|
|
||||||
return
|
|
||||||
d = os.read(read_signal_fd, io.DEFAULT_BUFFER_SIZE)
|
|
||||||
if not d:
|
|
||||||
raise SystemExit(0)
|
|
||||||
signals = set(bytearray(d))
|
|
||||||
if signal.SIGCHLD in signals:
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
pid, exit_status = os.waitpid(-1, os.WNOHANG)
|
|
||||||
except ChildProcessError:
|
|
||||||
break
|
|
||||||
# a zero return means there is at least one child process
|
|
||||||
# existing and no exit status is available
|
|
||||||
if pid == 0:
|
|
||||||
break
|
|
||||||
matched_child_id = -1
|
|
||||||
for child_id, child_pid in child_id_map.items():
|
|
||||||
if child_pid == pid:
|
|
||||||
matched_child_id = child_id
|
|
||||||
break
|
|
||||||
if matched_child_id > -1:
|
|
||||||
del child_id_map[matched_child_id]
|
|
||||||
child_ready_fds.pop(matched_child_id, None)
|
|
||||||
child_death_buf += f'{pid}\n'.encode()
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
if output_buf:
|
|
||||||
poll.register(stdout_fd, select.POLLOUT | basic_events)
|
|
||||||
if child_death_buf:
|
|
||||||
poll.register(notify_child_death_fd, select.POLLOUT | basic_events)
|
|
||||||
for (q, event) in poll.poll():
|
|
||||||
if q == stdin_fd:
|
|
||||||
handle_input(event)
|
|
||||||
elif q == stdout_fd:
|
|
||||||
handle_output(event)
|
|
||||||
elif q == read_signal_fd:
|
|
||||||
handle_signal(event)
|
|
||||||
elif q == notify_child_death_fd:
|
|
||||||
handle_notify_child_death(event)
|
|
||||||
except (KeyboardInterrupt, EOFError, BrokenPipeError):
|
|
||||||
if os.getpid() == self_pid:
|
|
||||||
raise SystemExit(1)
|
|
||||||
raise
|
|
||||||
except Exception:
|
|
||||||
if os.getpid() == self_pid:
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
if os.getpid() == self_pid:
|
|
||||||
for fmd in child_ready_fds.values():
|
|
||||||
with suppress(OSError):
|
|
||||||
os.close(fmd)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
@@ -5,7 +5,7 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
from binascii import hexlify, unhexlify
|
from binascii import hexlify, unhexlify
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from typing import Dict, Iterable, List, Type, Optional
|
from typing import Dict, Iterable, List, Optional, Type
|
||||||
|
|
||||||
from kitty.cli import parse_args
|
from kitty.cli import parse_args
|
||||||
from kitty.cli_stub import QueryTerminalCLIOptions
|
from kitty.cli_stub import QueryTerminalCLIOptions
|
||||||
|
|||||||
@@ -16,17 +16,12 @@ from kitty.cli import parse_args
|
|||||||
from kitty.cli_stub import RemoteFileCLIOptions
|
from kitty.cli_stub import RemoteFileCLIOptions
|
||||||
from kitty.constants import cache_dir
|
from kitty.constants import cache_dir
|
||||||
from kitty.typing import BossType
|
from kitty.typing import BossType
|
||||||
from kitty.utils import (
|
from kitty.utils import SSHConnectionData, command_for_open, get_editor, open_cmd
|
||||||
SSHConnectionData, command_for_open, get_editor, open_cmd
|
|
||||||
)
|
|
||||||
|
|
||||||
from ..tui.handler import result_handler
|
from ..tui.handler import result_handler
|
||||||
from ..tui.operations import (
|
from ..tui.operations import faint, raw_mode, reset_terminal, styled
|
||||||
faint, raw_mode, reset_terminal, styled
|
|
||||||
)
|
|
||||||
from ..tui.utils import get_key_press
|
from ..tui.utils import get_key_press
|
||||||
|
|
||||||
|
|
||||||
is_ssh_kitten_sentinel = '!#*&$#($ssh-kitten)(##$'
|
is_ssh_kitten_sentinel = '!#*&$#($ssh-kitten)(##$'
|
||||||
|
|
||||||
|
|
||||||
@@ -206,7 +201,7 @@ class ControlMaster:
|
|||||||
show_error(msg)
|
show_error(msg)
|
||||||
|
|
||||||
def download(self) -> bool:
|
def download(self) -> bool:
|
||||||
cmdline = self.batch_cmd_prefix + [self.conn_data.hostname, 'cat', self.remote_path]
|
cmdline = self.batch_cmd_prefix + [self.conn_data.hostname, 'cat', shlex.quote(self.remote_path)]
|
||||||
with open(self.dest, 'wb') as f:
|
with open(self.dest, 'wb') as f:
|
||||||
cp = subprocess.run(cmdline, stdout=f, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL)
|
cp = subprocess.run(cmdline, stdout=f, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL)
|
||||||
if cp.returncode != 0:
|
if cp.returncode != 0:
|
||||||
@@ -216,7 +211,7 @@ class ControlMaster:
|
|||||||
|
|
||||||
def upload(self, suppress_output: bool = True) -> bool:
|
def upload(self, suppress_output: bool = True) -> bool:
|
||||||
cmd_prefix = self.cmd_prefix if suppress_output else self.batch_cmd_prefix
|
cmd_prefix = self.cmd_prefix if suppress_output else self.batch_cmd_prefix
|
||||||
cmd = cmd_prefix + [self.conn_data.hostname, 'cat', '>', self.remote_path]
|
cmd = cmd_prefix + [self.conn_data.hostname, 'cat', '>', shlex.quote(self.remote_path)]
|
||||||
if not suppress_output:
|
if not suppress_output:
|
||||||
print(shlex.join(cmd))
|
print(shlex.join(cmd))
|
||||||
with open(self.dest, 'rb') as f:
|
with open(self.dest, 'rb') as f:
|
||||||
@@ -234,9 +229,9 @@ Result = Optional[str]
|
|||||||
|
|
||||||
|
|
||||||
def main(args: List[str]) -> Result:
|
def main(args: List[str]) -> Result:
|
||||||
msg = 'Ask the user what to do with the remote file'
|
msg = 'Ask the user what to do with the remote file. For internal use by kitty, do not run it directly.'
|
||||||
try:
|
try:
|
||||||
cli_opts, items = parse_args(args[1:], option_text, '', msg, 'kitty remote_file', result_class=RemoteFileCLIOptions)
|
cli_opts, items = parse_args(args[1:], option_text, '', msg, 'kitty +kitten remote_file', result_class=RemoteFileCLIOptions)
|
||||||
except SystemExit as e:
|
except SystemExit as e:
|
||||||
if e.code != 0:
|
if e.code != 0:
|
||||||
print(e.args[0])
|
print(e.args[0])
|
||||||
@@ -273,9 +268,9 @@ def save_as(conn_data: SSHConnectionData, remote_path: str, cli_opts: RemoteFile
|
|||||||
)
|
)
|
||||||
print('Relative paths will be resolved from:', styled(os.getcwd(), fg_intense=True, bold=True))
|
print('Relative paths will be resolved from:', styled(os.getcwd(), fg_intense=True, bold=True))
|
||||||
print()
|
print()
|
||||||
from ..tui.path_completer import PathCompleter
|
from ..tui.path_completer import get_path
|
||||||
try:
|
try:
|
||||||
dest = PathCompleter().input()
|
dest = get_path()
|
||||||
except (KeyboardInterrupt, EOFError):
|
except (KeyboardInterrupt, EOFError):
|
||||||
return
|
return
|
||||||
if dest:
|
if dest:
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user