Compare commits
1761 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
ad128a3c29 | ||
|
|
b71d2e04ca | ||
|
|
cc3c7b0286 | ||
|
|
bf6c90a69a | ||
|
|
4c9a75c1ba | ||
|
|
98f46f8bd7 | ||
|
|
dec62b1929 | ||
|
|
79f7954048 | ||
|
|
fc217dafba | ||
|
|
a6aff817cf | ||
|
|
38cb18fe92 | ||
|
|
20b6a97159 | ||
|
|
98f6e24106 | ||
|
|
4ed6364b61 | ||
|
|
cd7751681d | ||
|
|
76531d2f7a | ||
|
|
eea652f1d0 | ||
|
|
f20f4762a8 | ||
|
|
c004fd1f4d | ||
|
|
f29039db29 | ||
|
|
fbf1ec43c7 | ||
|
|
a42200a430 | ||
|
|
ad576c333c | ||
|
|
5a0a980648 | ||
|
|
b3578a4fa5 | ||
|
|
3c952b01d3 | ||
|
|
b1453a51f6 | ||
|
|
2c3a7b5f41 | ||
|
|
0df5a6c6c8 | ||
|
|
bf4dc6365a | ||
|
|
ce3322bf91 | ||
|
|
835cb31501 | ||
|
|
762dd065f3 | ||
|
|
7b91447d89 | ||
|
|
1146c4ea3f | ||
|
|
729f7b8e03 | ||
|
|
d3ec632b9f | ||
|
|
cb01b5ee44 | ||
|
|
0828096e9e | ||
|
|
ebcbed290f | ||
|
|
d6afe6f2cb | ||
|
|
4daf70f636 | ||
|
|
2f3dd72c84 | ||
|
|
dca8c65376 | ||
|
|
12d8175d9a | ||
|
|
609bcd68dd | ||
|
|
313136803c | ||
|
|
b937033411 | ||
|
|
55eeb9c11e | ||
|
|
8fbec2dfd5 | ||
|
|
c4521290b8 | ||
|
|
e12a9f3caf | ||
|
|
56f8a06362 | ||
|
|
5c4abe749b | ||
|
|
62c27b3db7 | ||
|
|
1283a2b7e5 | ||
|
|
bec620a8d3 | ||
|
|
6b0bb2e845 | ||
|
|
135818f34b | ||
|
|
2a361d1de1 | ||
|
|
761a941d43 | ||
|
|
c03630a487 | ||
|
|
0b79cbdb3b | ||
|
|
1dc6b49e02 | ||
|
|
1962f8e5a0 | ||
|
|
cabfcbcb69 | ||
|
|
48bb43a2c4 | ||
|
|
af5e3e0c18 | ||
|
|
fb8398c2eb | ||
|
|
eefd4bb3e3 | ||
|
|
581c388838 | ||
|
|
7a63ea6480 | ||
|
|
65f1329635 | ||
|
|
0df9a5d5c5 | ||
|
|
6405849a1b | ||
|
|
e9340dfaaf | ||
|
|
0509855930 | ||
|
|
4c74462763 | ||
|
|
7f69381221 | ||
|
|
75942fb47e | ||
|
|
0893d7164a | ||
|
|
27906ea853 | ||
|
|
8540ca3990 | ||
|
|
e82c5a63b5 | ||
|
|
83602d7e53 | ||
|
|
64e30e5299 | ||
|
|
83ce23ff9f | ||
|
|
c5e5ec618d | ||
|
|
bd84ec03ce | ||
|
|
d71f18566e | ||
|
|
76e75d9f89 | ||
|
|
f5695ac4f4 | ||
|
|
b748cd21cf | ||
|
|
3a8bdf3b48 | ||
|
|
196c6b07aa | ||
|
|
1978cc5b55 | ||
|
|
ea47cee23f | ||
|
|
e6844ad6f2 | ||
|
|
02d5b3eafa | ||
|
|
151dacf17f | ||
|
|
117d1b02be | ||
|
|
cb55878efd | ||
|
|
ea4751eae5 | ||
|
|
cc4f1c0a24 | ||
|
|
3eb5320e29 | ||
|
|
4f63cff1a4 | ||
|
|
8381171c8d | ||
|
|
1b2d54d97a | ||
|
|
e7da874b82 | ||
|
|
ea8bed2a71 | ||
|
|
c747e681a2 | ||
|
|
a9a9ec80b5 | ||
|
|
eb84990f5a | ||
|
|
1938ba3108 | ||
|
|
0eee2db199 | ||
|
|
bba1455e28 | ||
|
|
8c41cc8d3e | ||
|
|
5efcb35cfb | ||
|
|
1b4cf1fea7 | ||
|
|
d3656bf7e9 | ||
|
|
aaffec1cbc | ||
|
|
ed9391d4d6 | ||
|
|
58333f260b | ||
|
|
cf8ccabad9 | ||
|
|
600c595fdf | ||
|
|
2427d2d9da | ||
|
|
4ca70bfa26 | ||
|
|
4e8eb2f7f0 | ||
|
|
b6cf4bc78f | ||
|
|
622cd5531a | ||
|
|
ec35b0cc7c | ||
|
|
0719c7f8bb | ||
|
|
bb7e4039e8 | ||
|
|
a0559e506b | ||
|
|
4ce4176bbe | ||
|
|
99409f0a8b | ||
|
|
13303416b8 | ||
|
|
9811d677e5 | ||
|
|
b9fd039668 | ||
|
|
6c1f20bb27 | ||
|
|
2b58316c64 | ||
|
|
f2df629634 | ||
|
|
8f46505a50 | ||
|
|
c48bf4fd85 | ||
|
|
8c67f5aea8 | ||
|
|
76f84e32c4 | ||
|
|
4a7125ec92 | ||
|
|
92c3af6a92 | ||
|
|
443f36ebc7 | ||
|
|
17632dea3c | ||
|
|
bb78dc5ccb | ||
|
|
2737cb7dd0 | ||
|
|
08b2ce444f | ||
|
|
289028b468 | ||
|
|
2b3be147e6 | ||
|
|
1a2d9c6fba | ||
|
|
af15b0313a | ||
|
|
b080296326 | ||
|
|
516e0e8bb3 | ||
|
|
57e23bd4e3 | ||
|
|
59534d620e | ||
|
|
068b7e8d63 | ||
|
|
2c1edb9611 | ||
|
|
510022c3c1 | ||
|
|
627c79ffbb | ||
|
|
e80cd36237 | ||
|
|
92ebada9a6 | ||
|
|
462ae3bb58 | ||
|
|
6d6d9cc26b | ||
|
|
a36d5dcde1 | ||
|
|
f9f6f98527 | ||
|
|
ee94114eb2 | ||
|
|
8e98fcf2f6 | ||
|
|
c4710bf9cb | ||
|
|
97a568a405 | ||
|
|
7bace19aed | ||
|
|
7282f1f684 | ||
|
|
9edb772a81 | ||
|
|
d123b6c377 | ||
|
|
c9ba3695d3 | ||
|
|
0ee970b418 | ||
|
|
8239cb1b5a | ||
|
|
97caa073b0 | ||
|
|
45bbe17559 | ||
|
|
288d16f7be | ||
|
|
ecb60b313e | ||
|
|
cf2a20e4ea | ||
|
|
bc4f40fea7 | ||
|
|
102097da5a | ||
|
|
0e3528df14 | ||
|
|
214631c2dd | ||
|
|
e919857dfc | ||
|
|
a5bd1dcb08 | ||
|
|
37cdaea9ed | ||
|
|
6bbaf5f1cf | ||
|
|
8808a252ae | ||
|
|
5f1376b9a0 | ||
|
|
efa873bf50 | ||
|
|
714461de76 | ||
|
|
b753cf6879 | ||
|
|
27010d0446 | ||
|
|
bdc039fbf5 | ||
|
|
72f92f3174 | ||
|
|
8fcd5e40d4 | ||
|
|
b76319b7e8 | ||
|
|
feeb8f28c4 | ||
|
|
55b21b741e | ||
|
|
6941884221 | ||
|
|
6174c2008f | ||
|
|
1a32e79470 | ||
|
|
dd032db89c | ||
|
|
f70e0e216c | ||
|
|
51362706d7 | ||
|
|
b2c317ebc6 | ||
|
|
90acbd0dd5 | ||
|
|
402fac3edf | ||
|
|
c4c62c1505 | ||
|
|
b569c01b49 | ||
|
|
c0d9b6e979 | ||
|
|
b94afbba13 | ||
|
|
1994c17c75 | ||
|
|
f88a4fe986 | ||
|
|
25616aaa7b | ||
|
|
19fdcec358 | ||
|
|
45eb4a0760 | ||
|
|
9e026983e6 | ||
|
|
be0a524e23 | ||
|
|
339af1b4b2 | ||
|
|
31fda568e4 | ||
|
|
3efbccc850 | ||
|
|
93c23f99cb | ||
|
|
6590d0690e | ||
|
|
230a9f4678 | ||
|
|
f2189b3e70 | ||
|
|
0ee5712e00 | ||
|
|
f9cad2c4ea | ||
|
|
4372cf2893 | ||
|
|
34c18bacd8 | ||
|
|
3af11e92d6 | ||
|
|
74d5f2c259 | ||
|
|
291f9e9a5e | ||
|
|
38e1d32065 | ||
|
|
b45fedd794 | ||
|
|
df9b13fb74 | ||
|
|
53788c4c89 | ||
|
|
831043b773 | ||
|
|
1daf745d74 | ||
|
|
d6c5b40ead | ||
|
|
57ea524464 | ||
|
|
e19ce6cce6 | ||
|
|
8d4f6681e8 | ||
|
|
9c855a8377 | ||
|
|
716813e38a | ||
|
|
e5322cdc52 | ||
|
|
b5701691c6 | ||
|
|
aab9482e31 | ||
|
|
775584b5a5 | ||
|
|
25f022cc14 | ||
|
|
981ad88309 | ||
|
|
e71b9091a3 | ||
|
|
bde468594e | ||
|
|
0fcfe104e3 | ||
|
|
4cf54d2dfb | ||
|
|
b866c3e783 | ||
|
|
c15a31e725 | ||
|
|
afaf866b15 | ||
|
|
af6b1837cb | ||
|
|
aab6f3e450 | ||
|
|
829ed0ec0d | ||
|
|
d9899cb489 | ||
|
|
1a370ea9b6 | ||
|
|
cc07b1f79d | ||
|
|
3ddea42660 | ||
|
|
236dbd95c9 | ||
|
|
6b078c4267 | ||
|
|
1dec5f2e08 | ||
|
|
e5f70b7306 | ||
|
|
e2bb856e41 | ||
|
|
88d597f6b2 | ||
|
|
d0c0b01724 | ||
|
|
4b7c50518e | ||
|
|
a1bfcd9fc5 | ||
|
|
eb024fa40a | ||
|
|
122475ad4d | ||
|
|
f7114bc1c5 | ||
|
|
feea6998f8 | ||
|
|
e187110611 | ||
|
|
c19e69855a | ||
|
|
7788f48dd5 | ||
|
|
073eecb6bb | ||
|
|
030e7e2db3 | ||
|
|
aead3c1c35 | ||
|
|
587f44ad4e | ||
|
|
c9ef4aa8c8 | ||
|
|
aabadd8517 | ||
|
|
b8866371a3 | ||
|
|
0f3883af02 | ||
|
|
5876ce0845 | ||
|
|
2c72c56e22 | ||
|
|
f15ce21da1 | ||
|
|
b3fa7310cb | ||
|
|
ade38870a0 | ||
|
|
11bc1b100c | ||
|
|
93a7b220c9 | ||
|
|
8f92c594f2 | ||
|
|
afebea8635 | ||
|
|
5d76cfb578 | ||
|
|
f085f853bd | ||
|
|
4ac2312b2a | ||
|
|
c6dcbe6521 | ||
|
|
49efbf0c6e | ||
|
|
1d8d669a43 | ||
|
|
f4d44f30b4 | ||
|
|
4c77b0c562 | ||
|
|
2f367fa90c | ||
|
|
970ec9c839 | ||
|
|
be52f29792 | ||
|
|
8ae2f06828 | ||
|
|
601b2a05ac | ||
|
|
b78fbb4521 | ||
|
|
c40ef01445 | ||
|
|
ab8a4c6b9f | ||
|
|
dd331ca12e | ||
|
|
c3b23679f3 | ||
|
|
d0398dca28 | ||
|
|
e66c732b41 | ||
|
|
e7b216ba68 | ||
|
|
838a85ee8b | ||
|
|
3ec93e0b4e | ||
|
|
e8a9bbb51d | ||
|
|
25c2a0e241 | ||
|
|
de12dddbef | ||
|
|
24eeeedb1f | ||
|
|
c2bc24de35 | ||
|
|
21446e36c1 | ||
|
|
0f734719e7 | ||
|
|
f3c96f308d | ||
|
|
8ff841f1ac | ||
|
|
709c4abb53 | ||
|
|
c1f06941f5 | ||
|
|
ecb74aed29 | ||
|
|
c72154782b | ||
|
|
9520006466 | ||
|
|
40f04284d4 | ||
|
|
832534aac9 | ||
|
|
6cc89416ce | ||
|
|
b3fc2cb118 | ||
|
|
69efd9801b | ||
|
|
c07f164154 | ||
|
|
a2d1140229 | ||
|
|
bae9b095b4 | ||
|
|
efe6ff6188 | ||
|
|
3257fdf24f | ||
|
|
3284d71300 | ||
|
|
cdb3e2c1cd | ||
|
|
57cffc71b7 | ||
|
|
a4f1caeb4e | ||
|
|
45de091ee6 | ||
|
|
00c1802319 | ||
|
|
02c2d24360 | ||
|
|
499c255e81 | ||
|
|
30cad2e0a6 | ||
|
|
6e838b83d8 | ||
|
|
5297223ee3 | ||
|
|
2340d5be8d | ||
|
|
7de1a145aa | ||
|
|
32426d61c3 | ||
|
|
3ce47660a0 | ||
|
|
0dff455ffa | ||
|
|
81739288e8 | ||
|
|
5641668bc1 | ||
|
|
5c03a52a0b | ||
|
|
087b52e3e0 | ||
|
|
733b8e7c1c | ||
|
|
aa83f42f2d | ||
|
|
4ed2854791 | ||
|
|
73cdd87d91 | ||
|
|
51c8e3b2c6 | ||
|
|
e522095fae | ||
|
|
6fc1226028 | ||
|
|
fb40280d3c | ||
|
|
746cb3684a | ||
|
|
16c6545e93 | ||
|
|
0c4b20aa4e | ||
|
|
15e3e8d8b1 | ||
|
|
6123de52c2 | ||
|
|
3db0aab24b | ||
|
|
ae77f696ce | ||
|
|
98369db7f5 | ||
|
|
bc9d6892d4 | ||
|
|
5c02c370d4 | ||
|
|
b944cdddeb | ||
|
|
a1d203d34a | ||
|
|
e151b8e604 | ||
|
|
3c67e991c2 | ||
|
|
cb7aed3234 | ||
|
|
ff80b906d0 | ||
|
|
8300436481 | ||
|
|
6b13454091 | ||
|
|
9eae4ad913 | ||
|
|
bfb8532c52 | ||
|
|
c41405fd57 | ||
|
|
276ba7754a | ||
|
|
8569be81ea | ||
|
|
a765d551e4 | ||
|
|
4309aa1ace | ||
|
|
6ef83a09d3 | ||
|
|
adcc616c92 | ||
|
|
6dc1617429 | ||
|
|
1837168b0b | ||
|
|
1be1864657 | ||
|
|
49c335972f | ||
|
|
aabc99c7bf | ||
|
|
7c91dc6183 | ||
|
|
ff8a99211d | ||
|
|
efda0ea455 | ||
|
|
906be21b8d | ||
|
|
5a36fbfe7b | ||
|
|
f9f69a0577 | ||
|
|
8ccbb96b8d | ||
|
|
8ae7256fd6 | ||
|
|
ba401c19c2 | ||
|
|
11343d42c3 | ||
|
|
6c628bc594 | ||
|
|
f6cbca1aeb | ||
|
|
51e7c8c136 | ||
|
|
f952694ffd | ||
|
|
59afdfc4e9 | ||
|
|
27ec1e4b3c | ||
|
|
e791a8a7bb | ||
|
|
66fe53ceb1 | ||
|
|
de3cc11423 | ||
|
|
901eae9604 | ||
|
|
1962519666 | ||
|
|
1e84cbe2ab | ||
|
|
06da2b88ff | ||
|
|
91b9110dcc | ||
|
|
d57abb56ce | ||
|
|
f6edb774fc | ||
|
|
ec0f3e9128 | ||
|
|
6f1404d17b | ||
|
|
3d9e47c295 | ||
|
|
afa6128155 | ||
|
|
72b1996423 | ||
|
|
6b1a9d635e | ||
|
|
dea79f16d0 | ||
|
|
2d772d7243 | ||
|
|
09fb246c8f | ||
|
|
0a5c16363c | ||
|
|
b444f4636e | ||
|
|
2b8acebd6e | ||
|
|
766010c292 | ||
|
|
0a2768e496 | ||
|
|
0fd7f3f7b5 | ||
|
|
d3472966d3 | ||
|
|
576ab24609 | ||
|
|
621453b068 | ||
|
|
6638546247 | ||
|
|
f99edbae3c | ||
|
|
150bf1a5b0 | ||
|
|
91a17e3f0c | ||
|
|
7160027c14 | ||
|
|
736d6cf7e6 | ||
|
|
82de6a1c56 | ||
|
|
1fbb4f763e | ||
|
|
20582431d6 | ||
|
|
7d9cf0b064 | ||
|
|
a834f221f1 | ||
|
|
c95dca4023 | ||
|
|
c05c0353fd | ||
|
|
e944e2ecae | ||
|
|
47b3c37bf0 | ||
|
|
190666dc8a | ||
|
|
8c6e7ce61a | ||
|
|
04ead526b9 | ||
|
|
135fb7e6e4 | ||
|
|
ce1e22ac95 | ||
|
|
a216f6bd46 | ||
|
|
c47e5372b5 | ||
|
|
daa673eff1 | ||
|
|
5099dd6aa3 | ||
|
|
f982e754e4 | ||
|
|
4a1ad7755a | ||
|
|
946589d1f1 | ||
|
|
7168ceab94 | ||
|
|
868d57b818 | ||
|
|
6057c57ea4 | ||
|
|
e8437fd435 | ||
|
|
9e317971b4 | ||
|
|
bdb55a02fa | ||
|
|
02e062af65 | ||
|
|
54c5faa12d | ||
|
|
9b0bd81661 | ||
|
|
52da68876d | ||
|
|
2780630a18 | ||
|
|
8f77cc65e2 | ||
|
|
7224363639 | ||
|
|
43851fc1c4 | ||
|
|
e33bf11012 | ||
|
|
0e21376538 | ||
|
|
7a463c1240 | ||
|
|
8e03da855f | ||
|
|
942881d1b7 | ||
|
|
2a50203fcd | ||
|
|
08ce8ffa54 | ||
|
|
90561682cf | ||
|
|
71027e74e0 | ||
|
|
be8bfbe370 | ||
|
|
f7e4447b66 | ||
|
|
df4e58bc39 | ||
|
|
441ea7d696 | ||
|
|
2b06ca5e1a | ||
|
|
434ef97952 | ||
|
|
e1504c4775 | ||
|
|
74f0057ec8 | ||
|
|
7cd74cb852 | ||
|
|
b2e74e4830 | ||
|
|
f54a3e8036 | ||
|
|
f3088c5646 | ||
|
|
db00adaf69 | ||
|
|
ea74add814 | ||
|
|
ba1b3c3c2d | ||
|
|
d5c48ddb94 | ||
|
|
9687318b22 | ||
|
|
f6fb36c58a | ||
|
|
c8a258f36b | ||
|
|
14e0b01b40 | ||
|
|
76de99a5a8 | ||
|
|
8613c6e1cd | ||
|
|
5bb6b29ca3 | ||
|
|
61558d518e | ||
|
|
3b724c8415 | ||
|
|
910565aa7c | ||
|
|
c7a2e060e3 | ||
|
|
740e787f54 | ||
|
|
bd87d50948 | ||
|
|
d3c0c422a5 | ||
|
|
72718cbab7 | ||
|
|
ea28951e0e | ||
|
|
39a78f6be3 | ||
|
|
fe8aaca320 | ||
|
|
f5cc58ea9d | ||
|
|
44992452cf | ||
|
|
f080871911 | ||
|
|
6789eb88e2 | ||
|
|
1a97be4a25 | ||
|
|
c9ef5c0715 | ||
|
|
0a9f8d4f6a | ||
|
|
cac2c153c2 | ||
|
|
f2d6ba8775 | ||
|
|
92abaad22c | ||
|
|
3df0194f6e | ||
|
|
f774841ee0 | ||
|
|
a9de91087a | ||
|
|
76a536ece0 | ||
|
|
719339e116 | ||
|
|
1c3910de5c | ||
|
|
39d3ef6fe8 | ||
|
|
064d0fa6f1 | ||
|
|
99a5843595 | ||
|
|
39e75e39e8 | ||
|
|
322a80e76e | ||
|
|
d4b048735d | ||
|
|
c23e04fd03 | ||
|
|
f67009f554 | ||
|
|
4013544efb | ||
|
|
31d9db7e74 | ||
|
|
384c56f834 | ||
|
|
3282c8200d | ||
|
|
ffbc9174f8 | ||
|
|
dceb00f669 | ||
|
|
920086ae88 | ||
|
|
a1311a2332 | ||
|
|
4c392426f6 | ||
|
|
4528173ff5 | ||
|
|
d3b63a9c45 | ||
|
|
c30a249cf6 | ||
|
|
20962d989f | ||
|
|
a1e4b19486 | ||
|
|
fd0262413e | ||
|
|
aa4d36cc57 | ||
|
|
8d119f06b3 | ||
|
|
708c5126b9 | ||
|
|
577de9f746 | ||
|
|
38a70f5b51 | ||
|
|
118306a9ff | ||
|
|
704717ff1d | ||
|
|
405c472d13 | ||
|
|
17c994db57 | ||
|
|
e227264bad | ||
|
|
8751ea79e7 | ||
|
|
3b3ffa7455 | ||
|
|
4322825ac7 | ||
|
|
d29fa7b382 | ||
|
|
08bb63fa92 | ||
|
|
5dde31f80c | ||
|
|
3bb869f725 | ||
|
|
11686b90f7 | ||
|
|
6df78fa67c | ||
|
|
53b1607c4d | ||
|
|
2341a27f63 | ||
|
|
0661caf9da | ||
|
|
5b5bd77c53 | ||
|
|
d36f6b39c7 | ||
|
|
b2a5b07f92 | ||
|
|
46889a3a32 | ||
|
|
958ebb72a6 | ||
|
|
febc5c53a6 | ||
|
|
6e921300e2 | ||
|
|
e167dec8a9 | ||
|
|
0019f07cc0 | ||
|
|
20fc1e6b0c | ||
|
|
908946a067 | ||
|
|
f1df7b1c1f | ||
|
|
65b9c69bd8 | ||
|
|
b91e47c3b6 | ||
|
|
0e3a11c53b | ||
|
|
53d8d2aaad | ||
|
|
84303cbf2e | ||
|
|
5e457da30b | ||
|
|
855718b179 | ||
|
|
d037c0b0fc | ||
|
|
90f5937413 | ||
|
|
cbbea37b0c | ||
|
|
4d74b23f20 | ||
|
|
79dd26a43a | ||
|
|
259e3076fa | ||
|
|
f2cb2422f0 | ||
|
|
4aea64261e | ||
|
|
cf0cd9acd9 | ||
|
|
b4b0bdc853 | ||
|
|
0b2837fcfa | ||
|
|
f20ad7df01 | ||
|
|
c9071a66ca | ||
|
|
b0ea960159 | ||
|
|
0e7f1d60d6 | ||
|
|
03c79502f9 | ||
|
|
d4d4e00f9d | ||
|
|
b52e5e795e | ||
|
|
68df13d3fe | ||
|
|
5f3d90e411 | ||
|
|
1b68e41db4 | ||
|
|
817ac82968 | ||
|
|
43fd8cb13c | ||
|
|
397fbe7ad3 | ||
|
|
3095e7a256 | ||
|
|
df9e893cbe | ||
|
|
6ff69c88df | ||
|
|
6e4d3c98da | ||
|
|
2404eba11f | ||
|
|
e5c57a679d | ||
|
|
33de55540b | ||
|
|
99e1605bba | ||
|
|
95c4e26b24 | ||
|
|
795953a341 | ||
|
|
75d5e386d3 | ||
|
|
00d2a8527f | ||
|
|
a1ade8fc42 | ||
|
|
7f9fec061a | ||
|
|
ec782d3296 | ||
|
|
2444864508 | ||
|
|
1ccc50b21b | ||
|
|
4552a474b7 | ||
|
|
85c6d8f16e | ||
|
|
14d391cc2e | ||
|
|
8fe87a0df5 | ||
|
|
ff01df3b16 | ||
|
|
c713dc0ca8 | ||
|
|
31ea5d74a7 | ||
|
|
31b01d42c0 | ||
|
|
482b853908 | ||
|
|
675411df85 | ||
|
|
f94d33fa6a | ||
|
|
7fe110dff1 | ||
|
|
591f711886 | ||
|
|
1dc7fc8ac7 | ||
|
|
ffa79d731c | ||
|
|
01dd0416ac | ||
|
|
0bd1676978 | ||
|
|
f3407959a6 | ||
|
|
30e635a934 | ||
|
|
f6706a55ec | ||
|
|
f0b79f88b2 | ||
|
|
262ba0197d | ||
|
|
31c2447fb8 | ||
|
|
54a4ebfe48 | ||
|
|
106c7a1706 | ||
|
|
e469b46ce5 | ||
|
|
0c79561977 | ||
|
|
9e82397de9 | ||
|
|
7c166e2194 | ||
|
|
0a2b09da40 | ||
|
|
c02d578468 | ||
|
|
e990d233e5 | ||
|
|
a7cdcfcc16 | ||
|
|
75224e1661 | ||
|
|
d236b34fd4 | ||
|
|
c8313409ff | ||
|
|
6cba8e0166 | ||
|
|
0067726bbe | ||
|
|
ce620cec0a | ||
|
|
e797ba4800 | ||
|
|
fe27ee2d79 | ||
|
|
95efeee7de | ||
|
|
da30536709 | ||
|
|
108ccffcd8 | ||
|
|
c3ce0c26e7 | ||
|
|
e0c984046c | ||
|
|
64d6337612 | ||
|
|
603684211f | ||
|
|
499b30d175 | ||
|
|
3f3efab221 | ||
|
|
ef9adc92c8 | ||
|
|
ad7c251f56 | ||
|
|
1c0d254ec6 | ||
|
|
f1ce8c0e8a | ||
|
|
1c558be524 | ||
|
|
7d653cb7bf | ||
|
|
b4cc38a1d9 | ||
|
|
8867317b6a | ||
|
|
4b6bfaffba | ||
|
|
fadae42715 | ||
|
|
77c9affc00 | ||
|
|
e5ba15949b | ||
|
|
a3e59697a1 | ||
|
|
0e2125cda3 | ||
|
|
95da414511 | ||
|
|
c0d5ace640 | ||
|
|
ae48407b20 | ||
|
|
e06bd68379 | ||
|
|
ae6665493a | ||
|
|
c6f37afeff | ||
|
|
59f656e3ca | ||
|
|
12658c4756 | ||
|
|
37c185462a | ||
|
|
53c8485a7a | ||
|
|
846021296f | ||
|
|
5c8651c7cd | ||
|
|
ad91f5af53 | ||
|
|
02a68e7541 | ||
|
|
6e5dbc5285 | ||
|
|
fda9415873 | ||
|
|
4054163447 | ||
|
|
e1f5ef59c8 | ||
|
|
44baeb6924 | ||
|
|
c00e8b1709 | ||
|
|
3fb7ce7100 | ||
|
|
86b15ad693 | ||
|
|
53c00ac631 | ||
|
|
99d0c2d846 | ||
|
|
55ede897b9 | ||
|
|
a71e7d7eb1 | ||
|
|
3c28a4f723 | ||
|
|
6b681df473 | ||
|
|
22c1ee7dc8 | ||
|
|
c24e16e8cd | ||
|
|
d452a5cdce | ||
|
|
198dd52700 | ||
|
|
2dfea0f213 | ||
|
|
5064b5c2b1 | ||
|
|
78b553e55e | ||
|
|
8ca3a511cc | ||
|
|
bf26a3f569 | ||
|
|
ddb8753548 | ||
|
|
e73525d0a2 | ||
|
|
f37d947dd5 | ||
|
|
4279f20daf | ||
|
|
2dd7c3b939 | ||
|
|
cf01480ec8 | ||
|
|
d8ed42ae8e | ||
|
|
297592242c | ||
|
|
1c0a8a49fc | ||
|
|
16298e49c7 | ||
|
|
abb0b95006 | ||
|
|
32f5ea7b63 | ||
|
|
90ed5959de | ||
|
|
d999cc4143 | ||
|
|
6c1d8d5c63 | ||
|
|
a316242a4b | ||
|
|
a4ae090c37 | ||
|
|
abdcc64053 | ||
|
|
ac407d42de | ||
|
|
19c4a3f0a8 | ||
|
|
3afd96d421 | ||
|
|
f421666a27 | ||
|
|
af6baa33be | ||
|
|
1096cbe236 | ||
|
|
998be9b90c | ||
|
|
854cb8f27e | ||
|
|
e359094cff | ||
|
|
db57230987 | ||
|
|
4de3cecbbe | ||
|
|
c37a8bd3b1 | ||
|
|
e103b280fd | ||
|
|
2235ea67e2 | ||
|
|
1f6a4f7bd4 | ||
|
|
8c7ffc90f3 | ||
|
|
29d85833f1 | ||
|
|
74c56d69ac | ||
|
|
cf520646a9 | ||
|
|
da5213346a | ||
|
|
bdcb98eafc | ||
|
|
ef637cd7d3 | ||
|
|
dbbabd73c5 | ||
|
|
436ef0074a | ||
|
|
0904dec810 | ||
|
|
0aa07ead7e | ||
|
|
d87cac87ff | ||
|
|
7e8c96896f | ||
|
|
2619db0e58 | ||
|
|
ee3632b85d | ||
|
|
1c88c13fc2 | ||
|
|
7f476eb924 | ||
|
|
2d4f7e3446 | ||
|
|
165c1240a9 | ||
|
|
ef5c66ee17 | ||
|
|
33abd31d1a | ||
|
|
f91463a494 | ||
|
|
30146007d3 | ||
|
|
d53f8f24c4 | ||
|
|
f9621b1e11 | ||
|
|
e2f16ff624 | ||
|
|
121778e5c6 | ||
|
|
ef32488890 | ||
|
|
9d349d618a | ||
|
|
9f0a4f43b8 | ||
|
|
a8826f0d02 | ||
|
|
6689d312a3 | ||
|
|
2d7cb25b20 | ||
|
|
da10eaca00 | ||
|
|
6d3995d4ac | ||
|
|
e3adf8c6bf | ||
|
|
b8e522484d | ||
|
|
ef4240e196 | ||
|
|
8fa4a48ae5 | ||
|
|
4f3da2bc00 | ||
|
|
7b8c2c846f | ||
|
|
b212fd5bcd | ||
|
|
0a297f4656 | ||
|
|
fa397a1d24 | ||
|
|
0610daaec7 | ||
|
|
8278e2b88d | ||
|
|
cb4a9d89cf | ||
|
|
7800c598f6 | ||
|
|
fc651b72ab | ||
|
|
a9a7912372 | ||
|
|
43f435c334 | ||
|
|
693fc0f0c7 | ||
|
|
12e011c481 | ||
|
|
668783ba1c | ||
|
|
c18ebef702 | ||
|
|
03f9f29ce4 | ||
|
|
f62acab715 | ||
|
|
28ab9bfa2a | ||
|
|
9c05481f14 | ||
|
|
8ca92e0536 | ||
|
|
c0f6201ac3 | ||
|
|
27f3a5e16a | ||
|
|
de10dfe91b | ||
|
|
444a690a86 | ||
|
|
3d7b3f7d72 | ||
|
|
741ef7f115 | ||
|
|
e0c84c7176 | ||
|
|
393425e3d1 | ||
|
|
fc9645832d | ||
|
|
c47f41cfc0 | ||
|
|
08221489fd | ||
|
|
3c6766ff06 | ||
|
|
e28aae620a | ||
|
|
c240e7deaf | ||
|
|
31a5965b01 | ||
|
|
7a2a849a97 | ||
|
|
2c96e49566 | ||
|
|
e7846f916a | ||
|
|
d4f4d56f94 | ||
|
|
0108037076 | ||
|
|
88091b4ab3 | ||
|
|
4487462b0d | ||
|
|
dac9b07f16 | ||
|
|
b59212696a | ||
|
|
7fe1376e34 | ||
|
|
e25b90c1b6 | ||
|
|
cb32a0b8fc | ||
|
|
05617f7dca | ||
|
|
2d2f4b9ba9 | ||
|
|
65c7ecbc30 | ||
|
|
da5e37620e | ||
|
|
261057396c | ||
|
|
a43f610555 | ||
|
|
45ae52e5d0 | ||
|
|
a565443d4a | ||
|
|
081d6a3f16 | ||
|
|
c9cc832875 | ||
|
|
595698d8e9 | ||
|
|
b28d94ac97 | ||
|
|
b9684879e7 | ||
|
|
63f974531b | ||
|
|
a559210923 | ||
|
|
8d36fb9edc | ||
|
|
f652b23169 | ||
|
|
d50a2ea288 | ||
|
|
80fc3a1faa | ||
|
|
9a04405ad2 | ||
|
|
fdc9835587 | ||
|
|
702bb2cd06 | ||
|
|
58e8609c1a | ||
|
|
69c5c49094 | ||
|
|
ff8de7607a | ||
|
|
cfd0872cea | ||
|
|
7007f2e7fc | ||
|
|
6d0721341b | ||
|
|
7a156d5ef3 | ||
|
|
185c3320a4 | ||
|
|
1c9cf32735 | ||
|
|
6bfb6da0ad | ||
|
|
56a5738018 | ||
|
|
919667129f | ||
|
|
e9f49a3292 | ||
|
|
711f8b024e | ||
|
|
53716c084b | ||
|
|
2b495bcf9d | ||
|
|
d10812c6b0 | ||
|
|
80202d2679 | ||
|
|
5d120a2f36 | ||
|
|
cc11326baa | ||
|
|
f63dbc0ebd | ||
|
|
88630731dc | ||
|
|
3e2a8d80d7 | ||
|
|
55626e5a41 | ||
|
|
7ff07f7620 | ||
|
|
d2f522277c | ||
|
|
0248edbdb9 | ||
|
|
d24248e93b | ||
|
|
dcd7890a0c | ||
|
|
f219b10c30 | ||
|
|
cc039afc1e | ||
|
|
d2288d8f83 | ||
|
|
4b4f904aac | ||
|
|
e06b774174 | ||
|
|
8d4772f804 | ||
|
|
3be00bd712 | ||
|
|
a0e4449fb2 | ||
|
|
ebfc19def5 | ||
|
|
a4abe26d32 | ||
|
|
5f8cb22d02 | ||
|
|
cafc3973ec | ||
|
|
436a57a61e | ||
|
|
da5111a267 | ||
|
|
ee5cf90684 | ||
|
|
108974f4f7 | ||
|
|
79fa9f1c95 | ||
|
|
40bf12af7a | ||
|
|
263d121f3e | ||
|
|
9222d002f7 | ||
|
|
9a6aab034d | ||
|
|
b931b06941 | ||
|
|
90dc9b6fe6 | ||
|
|
275ede6f0a | ||
|
|
9fd4f8e5c2 | ||
|
|
01b4654461 | ||
|
|
d40e6a9ece | ||
|
|
b2317e0f12 | ||
|
|
444d9bd341 | ||
|
|
8fbe96744b | ||
|
|
1c48ec7196 | ||
|
|
c4b3bbd057 | ||
|
|
502ed94f31 | ||
|
|
18ce091bfa | ||
|
|
60f675758f | ||
|
|
9bfa4553a8 | ||
|
|
358f30ca7a | ||
|
|
e32785831b | ||
|
|
728eceb620 | ||
|
|
57f591d1ce | ||
|
|
e4397a1c73 | ||
|
|
1170cf474f | ||
|
|
ce8b0cf748 | ||
|
|
696f371aa4 | ||
|
|
8ebd514251 | ||
|
|
d3bc6001d8 | ||
|
|
f5415ca824 | ||
|
|
0dbe27438d | ||
|
|
7448789951 | ||
|
|
dc6138d286 | ||
|
|
7457f637a2 | ||
|
|
c9da734c0e | ||
|
|
141c814d72 | ||
|
|
bdb98cf210 | ||
|
|
7cc3d5907f | ||
|
|
3c709a28f7 | ||
|
|
e9ea7b13b6 | ||
|
|
ac16880eec | ||
|
|
c54bdd921a | ||
|
|
fc17528337 | ||
|
|
9b2db6ec53 | ||
|
|
0fcfaa2a98 | ||
|
|
ba97a728f2 | ||
|
|
a3b046fdcd | ||
|
|
ddfda3efde | ||
|
|
a2269cb66e | ||
|
|
1358f00969 | ||
|
|
eedc849c45 | ||
|
|
89679d07ae | ||
|
|
01d866f482 | ||
|
|
73b0312dcb | ||
|
|
f9d9fe6db4 | ||
|
|
e31ca68875 | ||
|
|
8ae273ee3a | ||
|
|
73a197fcde | ||
|
|
648bff02b5 | ||
|
|
7740bc138b | ||
|
|
f5337096d5 | ||
|
|
dddff91fad | ||
|
|
f047678711 | ||
|
|
aa8b23395f | ||
|
|
7c36c19de0 | ||
|
|
49183ced3b | ||
|
|
007e9697c4 | ||
|
|
9989edbe42 | ||
|
|
9742e2ec48 | ||
|
|
b47711e23a | ||
|
|
3dfdb3ac89 | ||
|
|
4d3e2a07d1 | ||
|
|
a42eb3e643 | ||
|
|
39c77a9486 | ||
|
|
e22546c56a | ||
|
|
ced61096df | ||
|
|
c0ea127810 | ||
|
|
29b1e3fa46 | ||
|
|
b2faa0d9f7 | ||
|
|
ddb9b67de3 | ||
|
|
ffbc533565 | ||
|
|
206e490491 | ||
|
|
135066f38c | ||
|
|
51d591e177 | ||
|
|
80a62c8d71 | ||
|
|
90b54c5f7f | ||
|
|
99c81e6858 | ||
|
|
8fcb6278d7 | ||
|
|
89e0abd41d | ||
|
|
a1a0c9ab80 | ||
|
|
9ab2a38d5c | ||
|
|
b5676a53ee | ||
|
|
94898a8758 | ||
|
|
97a6a11066 | ||
|
|
9fe22a5c27 | ||
|
|
f9b35c71d7 | ||
|
|
6b47f6f769 | ||
|
|
37d8483728 | ||
|
|
4776a9e785 | ||
|
|
50bc5b0302 | ||
|
|
7d496f20a1 | ||
|
|
42fbd0a1af | ||
|
|
8960bfebc0 | ||
|
|
b1209c1e7a | ||
|
|
365583efd7 | ||
|
|
926c2d71ee | ||
|
|
ce57c747cc | ||
|
|
3e9129655a | ||
|
|
12f41f30b3 | ||
|
|
1e7edd0218 | ||
|
|
2fd8ef389e | ||
|
|
53589c3954 | ||
|
|
34a0218f35 |
18
.github/workflows/ci.py
vendored
18
.github/workflows/ci.py
vendored
@@ -30,13 +30,21 @@ def install_deps():
|
||||
print('Installing kitty dependencies...')
|
||||
sys.stdout.flush()
|
||||
if is_macos:
|
||||
items = (x.strip() for x in open('Brewfile').readlines() if not x.startswith('#'))
|
||||
run('brew', 'install', *items)
|
||||
items = (x.split()[1].strip('"') for x in open('Brewfile').readlines() if x.strip().startswith('brew '))
|
||||
openssl = 'openssl'
|
||||
import ssl
|
||||
if ssl.OPENSSL_VERSION_INFO[0] == 1:
|
||||
openssl += '@1.1'
|
||||
run('brew', 'install', 'fish', openssl, *items)
|
||||
else:
|
||||
run('sudo apt-get update')
|
||||
run('sudo apt-get install -y libgl1-mesa-dev libxi-dev libxrandr-dev libxinerama-dev ca-certificates'
|
||||
' libxcursor-dev libxcb-xkb-dev libdbus-1-dev libxkbcommon-dev libharfbuzz-dev libx11-xcb-dev'
|
||||
' libpng-dev liblcms2-dev libfontconfig-dev libxkbcommon-x11-dev libcanberra-dev librsync-dev uuid-dev')
|
||||
' libxcursor-dev libxcb-xkb-dev libdbus-1-dev libxkbcommon-dev libharfbuzz-dev libx11-xcb-dev zsh'
|
||||
' libpng-dev liblcms2-dev libfontconfig-dev libxkbcommon-x11-dev libcanberra-dev librsync-dev uuid-dev'
|
||||
' zsh bash dash')
|
||||
# for some reason these directories are world writable which causes zsh
|
||||
# compinit to break
|
||||
run('sudo chmod -R og-w /usr/share/zsh')
|
||||
if is_bundle:
|
||||
install_bundle()
|
||||
else:
|
||||
@@ -55,7 +63,7 @@ def build_kitty():
|
||||
|
||||
|
||||
def test_kitty():
|
||||
run('./kitty/launcher/kitty +launch test.py')
|
||||
run('./test.py')
|
||||
|
||||
|
||||
def package_kitty():
|
||||
|
||||
39
.github/workflows/ci.yml
vendored
39
.github/workflows/ci.yml
vendored
@@ -6,6 +6,9 @@ env:
|
||||
LC_ALL: en_US.UTF-8
|
||||
LANG: en_US.UTF-8
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
name: Linux (python=${{ matrix.pyver }} cc=${{ matrix.cc }} sanitize=${{ matrix.sanitize }})
|
||||
@@ -20,15 +23,15 @@ jobs:
|
||||
cc: [gcc, clang]
|
||||
include:
|
||||
- python: a
|
||||
pyver: 3.7
|
||||
pyver: "3.8"
|
||||
sanitize: 0
|
||||
|
||||
- python: b
|
||||
pyver: 3.8
|
||||
pyver: "3.11"
|
||||
sanitize: 1
|
||||
|
||||
- python: c
|
||||
pyver: 3.9
|
||||
pyver: "3.9"
|
||||
sanitize: 1
|
||||
|
||||
|
||||
@@ -42,11 +45,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@master
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 10
|
||||
- name: Set up Python ${{ matrix.pyver }}
|
||||
uses: actions/setup-python@master
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: ${{ matrix.pyver }}
|
||||
|
||||
@@ -63,17 +66,17 @@ jobs:
|
||||
CFLAGS: -funsigned-char
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@master
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 10
|
||||
fetch-depth: 0 # needed for :commit: docs role
|
||||
|
||||
- name: Test for trailing whitespace
|
||||
run: if grep -Inr '\s$' kitty kitty_tests kittens docs *.py *.asciidoc *.rst .gitattributes .gitignore; then echo Trailing whitespace found, aborting.; exit 1; fi
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@master
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: 3.8
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Install build-only deps
|
||||
run: pip install -r docs/requirements.txt flake8 mypy types-requests types-docutils
|
||||
@@ -84,8 +87,11 @@ jobs:
|
||||
- name: Build kitty package
|
||||
run: python .github/workflows/ci.py package
|
||||
|
||||
- name: Build kitty
|
||||
run: python setup.py build --debug
|
||||
|
||||
- name: Run mypy
|
||||
run: python test.py mypy
|
||||
run: ./test.py mypy
|
||||
|
||||
- name: Build man page
|
||||
run: make FAIL_WARN=1 man
|
||||
@@ -120,7 +126,12 @@ jobs:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@master
|
||||
with:
|
||||
fetch-depth: 10
|
||||
fetch-depth: 0 # needed for :commit: docs role
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Build kitty
|
||||
run: python3 .github/workflows/ci.py build
|
||||
@@ -128,5 +139,11 @@ jobs:
|
||||
- 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
|
||||
|
||||
10
.github/workflows/codeql-analysis.yml
vendored
10
.github/workflows/codeql-analysis.yml
vendored
@@ -9,14 +9,21 @@ on:
|
||||
schedule:
|
||||
- cron: '0 22 * * 5'
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
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
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
@@ -27,6 +34,7 @@ jobs:
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: python, c
|
||||
setup-python-dependencies: false
|
||||
|
||||
- name: Build kitty
|
||||
run: python3 .github/workflows/ci.py build
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -2,6 +2,8 @@
|
||||
*.pyc
|
||||
*.pyo
|
||||
*_stub.pyi
|
||||
*_generated.go
|
||||
*_generated.h
|
||||
/.dmypy.json
|
||||
/tags
|
||||
/build/
|
||||
@@ -10,7 +12,7 @@
|
||||
/compile_commands.json
|
||||
/link_commands.json
|
||||
/glad/out/
|
||||
/kitty/launcher/
|
||||
/kitty/launcher/kitty*
|
||||
/*.dSYM/
|
||||
__pycache__/
|
||||
/glfw/wayland-*-client-protocol.[ch]
|
||||
|
||||
14
Brewfile
14
Brewfile
@@ -1,7 +1,7 @@
|
||||
pkg-config
|
||||
zlib
|
||||
librsync
|
||||
python
|
||||
imagemagick
|
||||
harfbuzz
|
||||
sphinx-doc
|
||||
brew "pkg-config"
|
||||
brew "zlib"
|
||||
brew "librsync"
|
||||
brew "python"
|
||||
brew "imagemagick"
|
||||
brew "harfbuzz"
|
||||
brew "sphinx-doc"
|
||||
|
||||
6
Makefile
6
Makefile
@@ -35,6 +35,12 @@ profile:
|
||||
app:
|
||||
python3 setup.py kitty.app $(VVAL)
|
||||
|
||||
linux-package: FORCE
|
||||
rm -rf linux-package
|
||||
python3 setup.py linux-package
|
||||
|
||||
FORCE:
|
||||
|
||||
man:
|
||||
$(MAKE) -C docs man
|
||||
|
||||
|
||||
@@ -11,4 +11,4 @@ https://www.reddit.com/r/KittyTerminal[Reddit community]
|
||||
|
||||
Packaging status in various repositories:
|
||||
|
||||
image:https://repology.org/badge/vertical-allrepos/kitty.svg[https://repology.org/project/kitty/versions]
|
||||
image:https://repology.org/badge/vertical-allrepos/kitty.svg["Packaging status", link="https://repology.org/project/kitty/versions"]
|
||||
|
||||
164
__main__.py
164
__main__.py
@@ -1,169 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
# License: GPL v3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
|
||||
def icat(args: List[str]) -> None:
|
||||
from kittens.runner import run_kitten as rk
|
||||
sys.argv = args
|
||||
rk('icat')
|
||||
|
||||
|
||||
def list_fonts(args: List[str]) -> None:
|
||||
from kitty.fonts.list import main as list_main
|
||||
list_main(args)
|
||||
|
||||
|
||||
def remote_control(args: List[str]) -> None:
|
||||
from kitty.remote_control import main as rc_main
|
||||
rc_main(args)
|
||||
|
||||
|
||||
def runpy(args: List[str]) -> None:
|
||||
if len(args) < 2:
|
||||
raise SystemExit('Usage: kitty +runpy "some python code"')
|
||||
sys.argv = ['kitty'] + args[2:]
|
||||
exec(args[1])
|
||||
|
||||
|
||||
def hold(args: List[str]) -> None:
|
||||
import subprocess
|
||||
ret = subprocess.Popen(args[1:]).wait()
|
||||
from kitty.utils import hold_till_enter
|
||||
hold_till_enter()
|
||||
raise SystemExit(ret)
|
||||
|
||||
|
||||
def complete(args: List[str]) -> None:
|
||||
from kitty.complete import main as complete_main
|
||||
complete_main(args[1:], entry_points, namespaced_entry_points)
|
||||
|
||||
|
||||
def launch(args: List[str]) -> None:
|
||||
import runpy
|
||||
sys.argv = args[1:]
|
||||
try:
|
||||
exe = args[1]
|
||||
except IndexError:
|
||||
raise SystemExit(
|
||||
'usage: kitty +launch script.py [arguments to be passed to script.py ...]\n\n'
|
||||
'script.py will be run with full access to kitty code. If script.py is '
|
||||
'prefixed with a : it will be searched for in PATH'
|
||||
)
|
||||
if exe.startswith(':'):
|
||||
import shutil
|
||||
q = shutil.which(exe[1:])
|
||||
if not q:
|
||||
raise SystemExit(f'{exe[1:]} not found in PATH')
|
||||
exe = q
|
||||
if not os.path.exists(exe):
|
||||
raise SystemExit(f'{exe} does not exist')
|
||||
runpy.run_path(exe, run_name='__main__')
|
||||
|
||||
|
||||
def shebang(args: List[str]) -> None:
|
||||
script_path = args[1]
|
||||
cmd = args[2:]
|
||||
if cmd == ['__ext__']:
|
||||
cmd = [os.path.splitext(script_path)[1][1:].lower()]
|
||||
try:
|
||||
f = open(script_path)
|
||||
except FileNotFoundError:
|
||||
raise SystemExit(f'The file {script_path} does not exist')
|
||||
with f:
|
||||
if f.read(2) == '#!':
|
||||
line = f.readline().strip()
|
||||
_plat = sys.platform.lower()
|
||||
is_macos: bool = 'darwin' in _plat
|
||||
if is_macos:
|
||||
cmd = line.split(' ')
|
||||
else:
|
||||
cmd = line.split(' ', maxsplit=1)
|
||||
os.execvp(cmd[0], cmd + [script_path])
|
||||
|
||||
|
||||
def run_kitten(args: List[str]) -> None:
|
||||
try:
|
||||
kitten = args[1]
|
||||
except IndexError:
|
||||
from kittens.runner import list_kittens
|
||||
list_kittens()
|
||||
raise SystemExit(1)
|
||||
sys.argv = args[1:]
|
||||
from kittens.runner import run_kitten as rk
|
||||
rk(kitten)
|
||||
|
||||
|
||||
def edit_config_file(args: List[str]) -> None:
|
||||
from kitty.cli import create_default_opts
|
||||
from kitty.fast_data_types import set_options
|
||||
from kitty.utils import edit_config_file as f
|
||||
set_options(create_default_opts())
|
||||
f()
|
||||
|
||||
|
||||
def namespaced(args: List[str]) -> None:
|
||||
try:
|
||||
func = namespaced_entry_points[args[1]]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
func(args[1:])
|
||||
return
|
||||
raise SystemExit(f'{args[1]} is not a known entry point. Choices are: ' + ', '.join(namespaced_entry_points))
|
||||
|
||||
|
||||
entry_points = {
|
||||
# These two are here for backwards compat
|
||||
'icat': icat,
|
||||
'list-fonts': list_fonts,
|
||||
|
||||
'@': remote_control,
|
||||
'+': namespaced,
|
||||
}
|
||||
namespaced_entry_points = {k: v for k, v in entry_points.items() if k[0] not in '+@'}
|
||||
namespaced_entry_points['hold'] = hold
|
||||
namespaced_entry_points['complete'] = complete
|
||||
namespaced_entry_points['runpy'] = runpy
|
||||
namespaced_entry_points['launch'] = launch
|
||||
namespaced_entry_points['kitten'] = run_kitten
|
||||
namespaced_entry_points['edit-config'] = edit_config_file
|
||||
namespaced_entry_points['shebang'] = shebang
|
||||
|
||||
|
||||
def setup_openssl_environment() -> None:
|
||||
# Use our bundled CA certificates instead of the system ones, since
|
||||
# many systems come with no certificates in a useable form or have various
|
||||
# locations for the certificates.
|
||||
d = os.path.dirname
|
||||
ext_dir: str = getattr(sys, 'kitty_extensions_dir')
|
||||
if 'darwin' in sys.platform.lower():
|
||||
cert_file = os.path.join(d(d(d(ext_dir))), 'cacert.pem')
|
||||
else:
|
||||
cert_file = os.path.join(d(ext_dir), 'cacert.pem')
|
||||
os.environ['SSL_CERT_FILE'] = cert_file
|
||||
setattr(sys, 'kitty_ssl_env_var', 'SSL_CERT_FILE')
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if getattr(sys, 'frozen', False) and getattr(sys, 'kitty_extensions_dir', ''):
|
||||
setup_openssl_environment()
|
||||
first_arg = '' if len(sys.argv) < 2 else sys.argv[1]
|
||||
func = entry_points.get(first_arg)
|
||||
if func is None:
|
||||
if first_arg.startswith('@'):
|
||||
remote_control(['@', first_arg[1:]] + sys.argv[2:])
|
||||
elif first_arg.startswith('+'):
|
||||
namespaced(['+', first_arg[1:]] + sys.argv[2:])
|
||||
else:
|
||||
from kitty.main import main as kitty_main
|
||||
kitty_main()
|
||||
else:
|
||||
func(sys.argv[1:])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from kitty.entry_points import main
|
||||
main()
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -13,11 +12,10 @@ import tempfile
|
||||
|
||||
def compile_terminfo(base):
|
||||
with tempfile.TemporaryDirectory() as tdir:
|
||||
proc = subprocess.run(['tic', '-x', f'-o{tdir}', 'terminfo/kitty.terminfo'], check=True, stderr=subprocess.PIPE)
|
||||
regex = '^"terminfo/kitty.terminfo", line [0-9]+, col [0-9]+, terminal \'xterm-kitty\': older tic versions may treat the description field as an alias$'
|
||||
for error in proc.stderr.decode('utf-8').splitlines():
|
||||
if not re.match(regex, error):
|
||||
print(error, file=sys.stderr)
|
||||
proc = subprocess.run(['tic', '-x', f'-o{tdir}', 'terminfo/kitty.terminfo'], capture_output=True)
|
||||
if proc.returncode != 0:
|
||||
sys.stderr.buffer.write(proc.stderr)
|
||||
raise SystemExit(proc.returncode)
|
||||
tfiles = glob.glob(os.path.join(tdir, '*', 'xterm-kitty'))
|
||||
if not tfiles:
|
||||
raise SystemExit('tic failed to output the compiled kitty terminfo file')
|
||||
@@ -40,6 +38,13 @@ def generate_terminfo():
|
||||
|
||||
with open('terminfo/kitty.terminfo', 'w') as f:
|
||||
f.write(generate_terminfo())
|
||||
proc = subprocess.run(['tic', '-CrT0', 'terminfo/kitty.terminfo'], capture_output=True)
|
||||
if proc.returncode != 0:
|
||||
sys.stderr.buffer.write(proc.stderr)
|
||||
raise SystemExit(proc.returncode)
|
||||
tcap = proc.stdout.decode('utf-8').splitlines()[-1]
|
||||
with open('terminfo/kitty.termcap', 'w') as f:
|
||||
f.write(tcap)
|
||||
|
||||
compile_terminfo(os.path.join(base, 'terminfo'))
|
||||
|
||||
|
||||
@@ -35,7 +35,11 @@ def binary_includes():
|
||||
'expat', 'sqlite3', 'ffi', 'z', 'lzma', 'png16', 'lcms2', 'crypt',
|
||||
'iconv', 'pcre', 'graphite2', 'glib-2.0', 'freetype', 'rsync',
|
||||
'harfbuzz', 'xkbcommon', 'xkbcommon-x11',
|
||||
'ncursesw', 'readline', 'brotlicommon', 'brotlienc', 'brotlidec'
|
||||
# fontconfig is not bundled because in typical brain dead Linux
|
||||
# distro fashion, different distros use different default config
|
||||
# paths for fontconfig.
|
||||
'ncursesw', 'readline', 'brotlicommon', 'brotlienc', 'brotlidec',
|
||||
'wayland-client', 'wayland-cursor',
|
||||
))) + (
|
||||
get_dll_path('bz2', 2), get_dll_path('ssl', 2), get_dll_path('crypto', 2),
|
||||
get_dll_path(f'python{py_ver}', 2),
|
||||
@@ -139,6 +143,7 @@ def copy_python(env):
|
||||
iv['sanitize_source_folder'](os.path.join(env.py_dir, x))
|
||||
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)
|
||||
shutil.rmtree(env.py_dir)
|
||||
|
||||
|
||||
def build_launcher(env):
|
||||
@@ -215,7 +220,7 @@ def create_tarfile(env, compression_level='9'):
|
||||
print('Compressing archive...')
|
||||
ans = f'{dist.rpartition(".")[0]}.txz'
|
||||
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
|
||||
print('Compressed in {} minutes {} seconds'.format(secs // 60, secs % 60))
|
||||
os.rename(f'{dist}.xz', ans)
|
||||
|
||||
@@ -185,7 +185,16 @@ class Freeze(object):
|
||||
def add_ca_certs(self):
|
||||
print('\nDownloading CA certs...')
|
||||
from urllib.request import urlopen
|
||||
cdata = urlopen(kitty_constants['cacerts_url']).read()
|
||||
cdata = None
|
||||
for i in range(5):
|
||||
try:
|
||||
cdata = urlopen(kitty_constants['cacerts_url']).read()
|
||||
break
|
||||
except Exception as e:
|
||||
print(f'Downloading CA certs failed with error: {e}, retrying...')
|
||||
|
||||
if cdata is None:
|
||||
raise SystemExit('Downloading C certs failed, giving up')
|
||||
dest = join(self.contents_dir, 'Resources', 'cacert.pem')
|
||||
with open(dest, 'wb') as f:
|
||||
f.write(cdata)
|
||||
@@ -360,6 +369,7 @@ class Freeze(object):
|
||||
iv['sanitize_source_folder'](join(self.python_stdlib, x))
|
||||
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)
|
||||
shutil.rmtree(self.python_stdlib)
|
||||
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'))
|
||||
shutil.rmtree(join(dirname(self.contents_dir), 'bin'))
|
||||
|
||||
@@ -166,8 +166,8 @@
|
||||
{
|
||||
"name": "pygments",
|
||||
"unix": {
|
||||
"filename": "Pygments-2.7.4.tar.gz",
|
||||
"hash": "sha256:df49d09b498e83c1a73128295860250b0b7edd4c723a32e9bc0d295c7c2ec337",
|
||||
"filename": "Pygments-2.11.2.tar.gz",
|
||||
"hash": "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a",
|
||||
"urls": ["pypi"]
|
||||
}
|
||||
},
|
||||
@@ -273,8 +273,8 @@
|
||||
"name": "wayland",
|
||||
"os": "linux",
|
||||
"unix": {
|
||||
"filename": "wayland-1.19.0.tar.xz",
|
||||
"hash": "sha256:baccd902300d354581cd5ad3cc49daa4921d55fb416a5883e218750fef166d15",
|
||||
"filename": "wayland-1.20.0.tar.xz",
|
||||
"hash": "sha256:b8a034154c7059772e0fdbd27dbfcda6c732df29cae56a82274f6ec5d7cd8725",
|
||||
"urls": ["https://wayland.freedesktop.org/releases/{filename}"]
|
||||
}
|
||||
},
|
||||
@@ -283,8 +283,8 @@
|
||||
"name": "wayland-protocols",
|
||||
"os": "linux",
|
||||
"unix": {
|
||||
"filename": "wayland-protocols-1.20.tar.xz",
|
||||
"hash": "sha256:9782b7a1a863d82d7c92478497d13c758f52e7da4f197aa16443f73de77e4de7",
|
||||
"filename": "wayland-protocols-1.25.tar.xz",
|
||||
"hash": "sha256:f1ff0f7199d0a0da337217dd8c99979967808dc37731a1e759e822b75b571460",
|
||||
"urls": ["https://wayland.freedesktop.org/releases/{filename}"]
|
||||
}
|
||||
}
|
||||
|
||||
2
docs/_static/custom.css
vendored
2
docs/_static/custom.css
vendored
@@ -10,7 +10,7 @@
|
||||
}
|
||||
|
||||
.sidebar-logo {
|
||||
max-height: 128px;
|
||||
height: 128px;
|
||||
}
|
||||
|
||||
.major-features li {
|
||||
|
||||
18
docs/_templates/base.html
vendored
18
docs/_templates/base.html
vendored
@@ -4,18 +4,14 @@
|
||||
{{ super() }}
|
||||
|
||||
{%- if analytics_id %}
|
||||
<script type="text/javascript">
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', '{{ analytics_id }}']);
|
||||
_gaq.push(['_setDomainName', 'none']);
|
||||
_gaq.push(['_setAllowLinker', true]);
|
||||
_gaq.push(['_trackPageview']);
|
||||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id={{ analytics_id }}"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
|
||||
(function() {
|
||||
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);
|
||||
})();
|
||||
gtag('config', '{{ analytics_id }}');
|
||||
</script>
|
||||
{% endif -%}
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
Tabs and Windows
|
||||
-------------------
|
||||
|
||||
|kitty| is capable of running multiple programs organized into tabs and
|
||||
windows. The top level of organization is the *Tab*. Each tab consists
|
||||
of one or more *windows*. The windows can be arranged in multiple
|
||||
different layouts, like windows are organized in a tiling window
|
||||
manager. The keyboard controls (which are all customizable) for tabs and
|
||||
windows are:
|
||||
|kitty| is capable of running multiple programs organized into tabs and windows.
|
||||
The top level of organization is the :term:`OS window <os_window>`. Each OS
|
||||
window consists of one or more :term:`tabs <tab>`. Each tab consists of one or more
|
||||
:term:`kitty windows <window>`. The kitty windows can be arranged in multiple
|
||||
different :term:`layouts <layout>`, like windows are organized in a tiling
|
||||
window manager. The keyboard controls (which are :ref:`all customizable
|
||||
<conf-kitty-shortcuts>`) for tabs and windows are:
|
||||
|
||||
Scrolling
|
||||
~~~~~~~~~~~~~~
|
||||
@@ -64,8 +65,9 @@ Focus specific window :sc:`first_window`, :sc:`second_window` ... :sc:`ten
|
||||
(clockwise from the top-left)
|
||||
======================== =======================
|
||||
|
||||
Additionally, you can define shortcuts in :file:`kitty.conf` to focus neighboring
|
||||
windows and move windows around (similar to window movement in vim)::
|
||||
Additionally, you can define shortcuts in :file:`kitty.conf` to focus
|
||||
neighboring windows and move windows around (similar to window movement in
|
||||
:program:`vim`)::
|
||||
|
||||
map ctrl+left neighboring_window left
|
||||
map shift+left move_window right
|
||||
@@ -77,20 +79,20 @@ You can also define a shortcut to switch to the previously active window::
|
||||
|
||||
map ctrl+p nth_window -1
|
||||
|
||||
``nth_window`` will focus the nth window for positive numbers (starting from
|
||||
:ac:`nth_window` will focus the nth window for positive numbers (starting from
|
||||
zero) and the previously active windows for negative numbers.
|
||||
|
||||
To switch to the nth OS window, you can define ``nth_os_window``. Only positive
|
||||
numbers are accepted, starting from one.
|
||||
To switch to the nth OS window, you can define :ac:`nth_os_window`. Only
|
||||
positive numbers are accepted, starting from one.
|
||||
|
||||
.. _detach_window:
|
||||
|
||||
You can define shortcuts to detach the current window and
|
||||
move it to another tab or another OS window::
|
||||
You can define shortcuts to detach the current window and move it to another tab
|
||||
or another OS window::
|
||||
|
||||
# moves the window into a new OS window
|
||||
map ctrl+f2 detach_window
|
||||
# moves the window into a new Tab
|
||||
# moves the window into a new tab
|
||||
map ctrl+f3 detach_window new-tab
|
||||
# moves the window into the previously active tab
|
||||
map ctrl+f3 detach_window tab-prev
|
||||
@@ -106,8 +108,8 @@ Similarly, you can detach the current tab, with::
|
||||
# asks which OS Window to move the tab into
|
||||
map ctrl+f4 detach_tab ask
|
||||
|
||||
Finally, you can define a shortcut to close all windows in a tab other than
|
||||
the currently active window::
|
||||
Finally, you can define a shortcut to close all windows in a tab other than the
|
||||
currently active window::
|
||||
|
||||
map f9 close_other_windows_in_tab
|
||||
|
||||
@@ -121,21 +123,22 @@ The full list of actions that can be mapped to key presses is available
|
||||
================================== =======================
|
||||
Action Shortcut
|
||||
================================== =======================
|
||||
Show this help :sc:`show_kitty_doc`
|
||||
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 selection :sc:`paste_from_selection`
|
||||
Pass selection to program :sc:`pass_selection_to_program`
|
||||
Increase font size :sc:`increase_font_size` (also :kbd:`⌘++` on macOS)
|
||||
Decrease font size :sc:`decrease_font_size` (also :kbd:`⌘+-` on macOS)
|
||||
Restore font size :sc:`reset_font_size` (also :kbd:`⌘+0` on macOS)
|
||||
Toggle fullscreen :sc:`toggle_fullscreen` (also :kbd:`⌃+⌘+f` on macOS)
|
||||
Toggle maximized :sc:`toggle_maximized`
|
||||
Input unicode character :sc:`input_unicode_character` (also :kbd:`⌃+⌘+space` on macOS)
|
||||
Input Unicode character :sc:`input_unicode_character` (also :kbd:`⌃+⌘+space` on macOS)
|
||||
Open URL in web browser :sc:`open_url`
|
||||
Reset the terminal :sc:`reset_terminal` (also :kbd:`⌥+⌘+r` on macOS)
|
||||
Edit :file:`kitty.conf` :sc:`edit_config_file` (also :kbd:`⌘+,` on macOS)
|
||||
Reload :file:`kitty.conf` :sc:`reload_config_file` (also :kbd:`⌃+⌘+,` on macOS)
|
||||
Debug :file:`kitty.conf` :sc:`debug_config` (also :kbd:`⌥+⌘+,` on macOS)
|
||||
Pass current selection to program :sc:`pass_selection_to_program`
|
||||
Edit |kitty| config file :sc:`edit_config_file` (also :kbd:`⌘+,` on macOS)
|
||||
Open a |kitty| shell :sc:`kitty_shell`
|
||||
Increase background opacity :sc:`increase_background_opacity`
|
||||
Decrease background opacity :sc:`decrease_background_opacity`
|
||||
|
||||
@@ -4,17 +4,14 @@ Install kitty
|
||||
Binary install
|
||||
----------------
|
||||
|
||||
.. |ins| replace:: curl -L :literal:`https://sw.kovidgoyal.net/kitty/installer.sh` | sh /dev/stdin
|
||||
|
||||
.. highlight:: sh
|
||||
|
||||
You can install pre-built binaries of |kitty| if you are on macOS or Linux using
|
||||
the following simple command:
|
||||
|
||||
.. parsed-literal::
|
||||
:class: pre
|
||||
.. code-block:: sh
|
||||
|
||||
|ins|
|
||||
_kitty_install_cmd
|
||||
|
||||
|
||||
The binaries will be installed in the standard location for your OS,
|
||||
@@ -24,7 +21,7 @@ simply re-run the command.
|
||||
|
||||
.. warning::
|
||||
**Do not** copy the kitty binary out of the installation folder. If you want
|
||||
to add it to your ``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.
|
||||
|
||||
|
||||
@@ -33,11 +30,12 @@ Manually installing
|
||||
|
||||
If something goes wrong or you simply do not want to run the installer, you can
|
||||
manually download and install |kitty| from the `GitHub releases page
|
||||
<https://github.com/kovidgoyal/kitty/releases>`_. If you are on macOS, download
|
||||
the :file:`.dmg` and install as normal. If you are on Linux, download the tarball
|
||||
and extract it into a directory. The |kitty| executable will be in the
|
||||
<https://github.com/kovidgoyal/kitty/releases>`__. If you are on macOS, download
|
||||
the :file:`.dmg` and install as normal. If you are on Linux, download the
|
||||
tarball and extract it into a directory. The |kitty| executable will be in the
|
||||
:file:`bin` sub-directory.
|
||||
|
||||
|
||||
Desktop integration on Linux
|
||||
--------------------------------
|
||||
|
||||
@@ -49,17 +47,30 @@ particular desktop, but it should work for most major desktop environments.
|
||||
.. code-block:: sh
|
||||
|
||||
# Create a symbolic link to add kitty to PATH (assuming ~/.local/bin is in
|
||||
# your PATH)
|
||||
# your system-wide PATH)
|
||||
ln -s ~/.local/kitty.app/bin/kitty ~/.local/bin/
|
||||
# Place the kitty.desktop file somewhere it can be found by the OS
|
||||
cp ~/.local/kitty.app/share/applications/kitty.desktop ~/.local/share/applications/
|
||||
# Update the path to the kitty icon in the kitty.desktop file
|
||||
sed -i "s|Icon=kitty|Icon=/home/$USER/.local/kitty.app/share/icons/hicolor/256x256/apps/kitty.png|g" ~/.local/share/applications/kitty.desktop
|
||||
# If you want to open text files and images in kitty via your file manager also add the kitty-open.desktop file
|
||||
cp ~/.local/kitty.app/share/applications/kitty-open.desktop ~/.local/share/applications/
|
||||
# Update the paths to the kitty and its icon in the kitty.desktop file(s)
|
||||
sed -i "s|Icon=kitty|Icon=/home/$USER/.local/kitty.app/share/icons/hicolor/256x256/apps/kitty.png|g" ~/.local/share/applications/kitty*.desktop
|
||||
sed -i "s|Exec=kitty|Exec=/home/$USER/.local/kitty.app/bin/kitty|g" ~/.local/share/applications/kitty*.desktop
|
||||
|
||||
.. note::
|
||||
If you use the venerable `stow <https://www.gnu.org/software/stow/>`_
|
||||
In :file:`kitty-open.desktop`, kitty is registered to handle some supported
|
||||
MIME types. This will cause kitty to take precedence on some systems where
|
||||
the default apps are not explicitly set. For example, you expect to use
|
||||
other GUI file managers to open dir paths when using commands such as
|
||||
:program:`xdg-open`, you should configure the default opener for the MIME
|
||||
type ``inode/directory``::
|
||||
|
||||
xdg-mime default org.kde.dolphin.desktop inode/directory
|
||||
|
||||
.. note::
|
||||
If you use the venerable `stow <https://www.gnu.org/software/stow/>`__
|
||||
command to manage your manual installations, the following takes care of the
|
||||
above for you (use with :file:`dest=~/.local/stow`)::
|
||||
above for you (use with :code:`dest=~/.local/stow`)::
|
||||
|
||||
cd ~/.local/stow
|
||||
stow -v kitty.app
|
||||
@@ -72,44 +83,39 @@ Customizing the installation
|
||||
|
||||
* You can install the latest nightly kitty build with ``installer``:
|
||||
|
||||
.. parsed-literal::
|
||||
:class: pre
|
||||
.. code-block:: sh
|
||||
|
||||
|ins| \\
|
||||
_kitty_install_cmd \
|
||||
installer=nightly
|
||||
|
||||
If you want to install it in parallel to the released kitty specify a
|
||||
different install locations with ``dest``:
|
||||
|
||||
.. parsed-literal::
|
||||
:class: pre
|
||||
.. code-block:: sh
|
||||
|
||||
|ins| \\
|
||||
_kitty_install_cmd \
|
||||
installer=nightly dest=/some/other/location
|
||||
|
||||
* You can specify a different install location, with ``dest``:
|
||||
|
||||
.. parsed-literal::
|
||||
:class: pre
|
||||
.. code-block:: sh
|
||||
|
||||
|ins| \\
|
||||
_kitty_install_cmd \
|
||||
dest=/some/other/location
|
||||
|
||||
* You can tell the installer not to launch |kitty| after installing it with
|
||||
``launch=n``:
|
||||
|
||||
.. parsed-literal::
|
||||
:class: pre
|
||||
.. code-block:: sh
|
||||
|
||||
|ins| \\
|
||||
_kitty_install_cmd \
|
||||
launch=n
|
||||
|
||||
* You can use a previously downloaded dmg/tarball, with ``installer``:
|
||||
|
||||
.. parsed-literal::
|
||||
:class: pre
|
||||
.. code-block:: sh
|
||||
|
||||
|ins| \\
|
||||
_kitty_install_cmd \
|
||||
installer=/path/to/dmg or tarball
|
||||
|
||||
|
||||
|
||||
153
docs/build.rst
153
docs/build.rst
@@ -7,21 +7,21 @@ Build from source
|
||||
|
||||
.. highlight:: sh
|
||||
|
||||
|kitty| is designed to run from source, for easy hack-ability. Make sure
|
||||
the following dependencies are installed first.
|
||||
|kitty| is designed to run from source, for easy hack-ability. Make sure the
|
||||
following dependencies are installed first.
|
||||
|
||||
.. note::
|
||||
If you just want to test the latest changes to kitty you dont need to build
|
||||
If you just want to test the latest changes to kitty, you don't need to build
|
||||
from source. Instead install the :ref:`latest nightly build <nightly>`.
|
||||
|
||||
.. note::
|
||||
If you are making small changes only to the python parts of kitty, there is no need to
|
||||
build kitty at all, instead, assuming you have installed the official kitty
|
||||
binaries, you can simply set the KITTY_DEVELOP_FROM enviroment variable to
|
||||
point to the directory into which you have checked out the kitty source
|
||||
code. kitty will then load its python code from there. You should use a
|
||||
version of the source that matches the binary version as closely as
|
||||
possible, since the two are tightly coupled.
|
||||
If you are making small changes only to the Python parts of kitty, there is
|
||||
no need to build kitty at all, instead, assuming you have installed the
|
||||
official kitty binaries, you can simply set the :envvar:`KITTY_DEVELOP_FROM`
|
||||
enviroment variable to point to the directory into which you have checked out
|
||||
the kitty source code. kitty will then load its Python code from there. You
|
||||
should use a version of the source that matches the binary version as closely
|
||||
as possible, since the two are tightly coupled.
|
||||
|
||||
|
||||
Dependencies
|
||||
@@ -29,12 +29,13 @@ Dependencies
|
||||
|
||||
Run-time dependencies:
|
||||
|
||||
* ``python`` >= 3.6
|
||||
* ``python`` >= 3.8
|
||||
* ``harfbuzz`` >= 2.2.0
|
||||
* ``zlib``
|
||||
* ``libpng``
|
||||
* ``liblcms2``
|
||||
* ``librsync``
|
||||
* ``openssl``
|
||||
* ``freetype`` (not needed on macOS)
|
||||
* ``fontconfig`` (not needed on macOS)
|
||||
* ``libcanberra`` (not needed on macOS)
|
||||
@@ -46,7 +47,9 @@ Build-time dependencies:
|
||||
|
||||
* ``gcc`` or ``clang``
|
||||
* ``pkg-config``
|
||||
* 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 your distro:
|
||||
* 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
|
||||
your distro:
|
||||
|
||||
- ``libdbus-1-dev``
|
||||
- ``libxcursor-dev``
|
||||
@@ -77,9 +80,12 @@ You can run |kitty|, as::
|
||||
|
||||
./kitty/launcher/kitty
|
||||
|
||||
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``.
|
||||
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``.
|
||||
|
||||
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
|
||||
@@ -87,28 +93,30 @@ Building kitty.app on macOS from source
|
||||
|
||||
Run::
|
||||
|
||||
python3 -m pip install -r docs/requirements.txt && make docs
|
||||
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
|
||||
python and the other dependencies. So if you ever un-install/upgrade those dependencies
|
||||
you might have to rebuild the app.
|
||||
Python and the other dependencies. So if you ever un-install/upgrade those
|
||||
dependencies you might have to rebuild the app.
|
||||
|
||||
.. note::
|
||||
The released :file:`kitty.dmg` includes all dependencies, unlike the
|
||||
:file:`kitty.app` built above and is built automatically by using the
|
||||
`bypy framework <https://github.com/kovidgoyal/bypy>`_ however, that is
|
||||
`bypy framework <https://github.com/kovidgoyal/bypy>`__ however, that is
|
||||
designed to run on Linux and is not for the faint of heart.
|
||||
|
||||
.. note::
|
||||
Apple disallows certain functionality, such as notifications for unsigned applications.
|
||||
If you need this functionality, you can try signing the built kitty.app with
|
||||
a self signed certificate, see for example, `here
|
||||
<https://stackoverflow.com/questions/27474751/how-can-i-codesign-an-app-without-being-in-the-mac-developer-program/27474942>`_.
|
||||
Apple disallows certain functionality, such as notifications for unsigned
|
||||
applications. If you need this functionality, you can try signing the built
|
||||
:file:`kitty.app` with a self signed certificate, see for example, `here
|
||||
<https://stackoverflow.com/questions/27474751/how-can-i-codesign-an-app-without-being-in-the-mac-developer-program/27474942>`__.
|
||||
|
||||
.. note::
|
||||
If you are facing issues with ``linker`` while building,
|
||||
try with a ``brew`` installed python instead, see :iss:`289`
|
||||
for more discussion.
|
||||
If you are facing issues with ``linker`` while building, try with a ``brew``
|
||||
installed Python instead, see :iss:`289` for more discussion.
|
||||
|
||||
|
||||
Build and run from source with Nix
|
||||
@@ -116,89 +124,78 @@ Build and run from source with Nix
|
||||
|
||||
On NixOS or any other Linux or macOS system with the Nix package manager
|
||||
installed, execute `nix-shell
|
||||
<https://nixos.org/guides/nix-pills/developing-with-nix-shell.html>`_ to create
|
||||
<https://nixos.org/guides/nix-pills/developing-with-nix-shell.html>`__ to create
|
||||
the correct environment to build kitty or use ``nix-shell --pure`` instead to
|
||||
eliminate most of the influence of the outside system, e.g. globally installed
|
||||
packages. ``nix-shell`` will automatically fetch all required dependencies and
|
||||
make them available in the newly spawned shell.
|
||||
|
||||
Then proceed with ``make`` or ``make app`` according to the platform specific instructions above.
|
||||
Then proceed with ``make`` or ``make app`` according to the platform specific
|
||||
instructions above.
|
||||
|
||||
|
||||
Debug builds
|
||||
--------------
|
||||
|
||||
A basic debug build can be done with::
|
||||
|
||||
make debug
|
||||
|
||||
This includes debug info in the binary for better traces. To build with address
|
||||
sanitizer, use::
|
||||
|
||||
make asan
|
||||
|
||||
Which will result in a debug binary that uses the address sanitizer as well.
|
||||
|
||||
.. _packagers:
|
||||
|
||||
Notes for Linux/macOS packagers
|
||||
----------------------------------
|
||||
|
||||
The released |kitty| source code is available as a `tarball`_ from
|
||||
`the GitHub releases page <https://github.com/kovidgoyal/kitty/releases>`_.
|
||||
`the GitHub releases page <https://github.com/kovidgoyal/kitty/releases>`__.
|
||||
|
||||
While |kitty| does use python, it is not a traditional python package, so please
|
||||
While |kitty| does use Python, it is not a traditional Python package, so please
|
||||
do not install it in site-packages.
|
||||
Instead run::
|
||||
|
||||
python3 setup.py linux-package
|
||||
make linux-package
|
||||
|
||||
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 will be in
|
||||
:file:`linux-package/lib/kitty`. The terminfo file will be installed into
|
||||
:file:`linux-package/share/terminfo`. Simply copy these files into :file:`/usr` to install
|
||||
|kitty|. In other words, :file:`linux-package` is the staging area into which |kitty| is
|
||||
installed. You can choose a different staging area, by passing the ``--prefix``
|
||||
argument to :file:`setup.py`.
|
||||
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
|
||||
will be in :file:`linux-package/lib/kitty`. The terminfo file will be installed
|
||||
into :file:`linux-package/share/terminfo`. Simply copy these files into
|
||||
:file:`/usr` to install |kitty|. In other words, :file:`linux-package` is the
|
||||
staging area into which |kitty| is installed. You can choose a different staging
|
||||
area, by passing the ``--prefix`` argument to :file:`setup.py`.
|
||||
|
||||
You should probably split |kitty| into three packages:
|
||||
|
||||
:code:`kitty-terminfo`
|
||||
installs the terminfo file
|
||||
Installs the terminfo file
|
||||
|
||||
:code:`kitty-shell-integration`
|
||||
installs the shell integration scripts (the contents of the
|
||||
shell-integration directory in the kitty source code, probably to
|
||||
Installs the shell integration scripts (the contents of the
|
||||
shell-integration directory in the kitty source code), probably to
|
||||
:file:`/usr/share/kitty/shell-integration`
|
||||
|
||||
:code:`kitty`
|
||||
installs the main program
|
||||
Installs the main program
|
||||
|
||||
This allows users to install the terminfo and shell integration files on
|
||||
servers 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::
|
||||
You need a couple of extra dependencies to build linux-package.
|
||||
:file:`tic` to compile terminfo files, usually found in the
|
||||
development package of :file:`ncurses`. Also, if you are building from
|
||||
a git checkout instead of the released source code tarball, you will
|
||||
need to install the dependencies from ``docs/requirements.txt`` to
|
||||
build the kitty documentation. They can be installed most easily with
|
||||
``python -m pip -r docs/requirements.txt``.
|
||||
You need a couple of extra dependencies to build linux-package. :file:`tic`
|
||||
to compile terminfo files, usually found in the development package of
|
||||
:file:`ncurses`. Also, if you are building from a git checkout instead of the
|
||||
released source code tarball, you will need to install the dependencies from
|
||||
:file:`docs/requirements.txt` to build the kitty documentation. They can be
|
||||
installed most easily with ``python -m pip -r docs/requirements.txt``.
|
||||
|
||||
This applies to creating packages for |kitty| for macOS package managers such as
|
||||
brew or MacPorts as well.
|
||||
|
||||
|
||||
Changing defaults for packages
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|kitty| has its defaults chosen to be suitable for a standalone distributable.
|
||||
If you are packaging it a few of these might need to be changed.
|
||||
|
||||
update-checking
|
||||
|kitty| has its own update check mechanism, if you would like to turn
|
||||
it off for your package, use::
|
||||
|
||||
./setup.py linux-package --update-check-interval=0
|
||||
|
||||
shell-integration
|
||||
|kitty| by default injects its :ref:`shell_integration` code into the user's
|
||||
shell using environment variables or (for bash only) modifying
|
||||
the user's :file:`~/.bashrc` file.
|
||||
For a package, it might make more sense to distribute the shell
|
||||
integration scripts into the system-wide shell vendor locations. The
|
||||
shell integration files are found in the :file:`shell-integration`
|
||||
directory. Copy them to the system wide shell vendor locations for each
|
||||
shell, and use::
|
||||
|
||||
./setup.py linux-package --shell-integration=enabled\ no-rc
|
||||
|
||||
This will prevent kitty from modifying the user's shell environment to load
|
||||
the integration scripts.
|
||||
Homebrew or MacPorts as well.
|
||||
|
||||
@@ -9,23 +9,15 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
|
||||
Recent major new features
|
||||
---------------------------
|
||||
|
||||
.. only:: dirhtml
|
||||
Truly convenient SSH
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A demo video
|
||||
~~~~~~~~~~~~~~~~
|
||||
The :doc:`ssh kitten <kittens/ssh>` is redesigned with powerful new features:
|
||||
|
||||
A new video showcasing some of kitty's many productivity enhancing features.
|
||||
|
||||
.. raw:: html
|
||||
|
||||
<video controls width="640" height="360" poster="../_static/poster.png">
|
||||
<source src="https://download.calibre-ebook.com/videos/kitty.mp4" type="video/mp4">
|
||||
<source src="https://download.calibre-ebook.com/videos/kitty.webm" type="video/webm">
|
||||
</video>
|
||||
|
||||
.. rst-class:: caption caption-text
|
||||
|
||||
Watch kitty in action!
|
||||
* Automatic :ref:`shell_integration` on remote machines
|
||||
* Easily :ref:`clone local shell/editor config <real_world_ssh_kitten_config>` on remote machines
|
||||
* Easily :ref:`edit files in your local editor <edit_file>` on remote machines
|
||||
* Automatic :opt:`re-use of existing connections <kitten-ssh.share_connections>` to avoid connection setup latency
|
||||
|
||||
|
||||
Shell integration
|
||||
@@ -38,43 +30,427 @@ scrollback by pressing :sc:`scroll_to_previous_prompt` and clicking with the
|
||||
mouse anywhere in the current command to move the cursor there. See
|
||||
:doc:`shell-integration` for details.
|
||||
|
||||
Logos for windows
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
kitty has the ability to display arbitrary ``PNG`` images at the corner
|
||||
of any kitty window to serve as a *logo* for quick window identification.
|
||||
Can be controlled via :opt:`window_logo_path` and also and changed via
|
||||
the :ref:`at_set-window-logo` remote control command.
|
||||
Useful to quickly identify special windows or just for some *bling*.
|
||||
|
||||
.. figure:: screenshots/window-logo.png
|
||||
:alt: A screenshot of the kitty shell window showing the kitty logo
|
||||
:align: center
|
||||
:width: 100%
|
||||
|
||||
A screenshot of the kitty shell window showing the kitty logo
|
||||
|
||||
|
||||
Visual keyboard based window select
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Select a kitty window visually using the keyboard, like the
|
||||
:doc:`kittens/hints`, but for kitty windows. Can also be used from shell
|
||||
scripts/third party integrations via the :ref:`at_select-window` remote control
|
||||
command.
|
||||
|
||||
.. figure:: screenshots/select-window.png
|
||||
:alt: A screenshot of the kitty select window function
|
||||
:align: center
|
||||
:width: 100%
|
||||
|
||||
Hints for selecting kitty windows visually
|
||||
|
||||
.. }}}
|
||||
|
||||
Detailed list of changes
|
||||
-------------------------------------
|
||||
|
||||
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]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- A new command :command:`edit-in-kitty` to :ref:`edit_file`
|
||||
|
||||
- Allow getting the last non-empty command output easily via an action or
|
||||
remote control (:pull:`4973`)
|
||||
|
||||
- Fix a bug that caused :opt:`macos_colorspace` to always be ``default`` regardless of its actual value (:iss:`5129`)
|
||||
|
||||
- diff kitten: A new option :opt:`kitten-diff.ignore_name` to exclude files and directories from being scanned (:pull:`5171`)
|
||||
|
||||
- ssh kitten: Fix bash not being executed as a login shell since kitty 0.25.0 (:iss:`5130`)
|
||||
|
||||
- macOS: When pasting text and the clipboard has a filesystem path, paste the
|
||||
full path instead of the text, which is sometimes just the file name (:pull:`5142`)
|
||||
|
||||
- macOS: Allow opening executables without a file extension with kitty as well
|
||||
(:iss:`5160`)
|
||||
|
||||
- Themes kitten: Add a tab to show user defined custom color themes separately
|
||||
(:pull:`5150`)
|
||||
|
||||
- Iosevka: Fix incorrect rendering when there is a combining char that does not
|
||||
group with its neighbors (:iss:`5153`)
|
||||
|
||||
- Weston: Fix client side decorations flickering on slow computers during
|
||||
window resize (:iss:`5162`)
|
||||
|
||||
- Remote control: Fix commands with large or asynchronous payloads like
|
||||
:command:`kitty @ set-backround-image`, :command:`kitty @ set-window-logo`
|
||||
and :command:`kitty @ select-window` not working correctly
|
||||
when using a socket (:iss:`5165`)
|
||||
|
||||
- hints kitten: Fix surrounding quotes/brackets and embedded carriage returns
|
||||
not being removed when using line number processing (:iss:`5170`)
|
||||
|
||||
|
||||
0.25.1 [2022-05-26]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Shell integration: Add a command to :ref:`clone_shell`
|
||||
|
||||
- Remote control: Allow using :ref:`Boolean operators <search_syntax>` when constructing queries to match windows or tabs
|
||||
|
||||
- Sessions: Fix :code:`os_window_size` and :code:`os_window_class` not applying to the first OS Window (:iss:`4957`)
|
||||
|
||||
- Allow using the cwd of the oldest as well as the newest foreground process for :option:`launch --cwd` (:disc:`4869`)
|
||||
|
||||
- Bash integration: Fix the value of :opt:`shell_integration` not taking effect if the integration script is sourced in bashrc (:pull:`4964`)
|
||||
|
||||
- Fix a regression in the previous release that caused mouse move events to be incorrectly reported as drag events even when a button is not pressed (:iss:`4992`)
|
||||
|
||||
- remote file kitten: Integrate with the ssh kitten for improved performance
|
||||
and robustness. Re-uses the control master connection of the ssh kitten to
|
||||
avoid round-trip latency.
|
||||
|
||||
- Fix tab selection when closing a new tab not correct in some scenarios (:iss:`4987`)
|
||||
|
||||
- A new action :ac:`open_url` to open the specified URL (:pull:`5004`)
|
||||
|
||||
- A new option :opt:`select_by_word_characters_forward` that allows changing
|
||||
which characters are considered part of a word to the right when double clicking to select
|
||||
words (:pull:`5103`)
|
||||
|
||||
- macOS: Make the global menu shortcut to open kitty website configurable (:pull:`5004`)
|
||||
|
||||
- macOS: Add the :opt:`macos_colorspace` option to control what color space colors are rendered in (:iss:`4686`)
|
||||
|
||||
- Fix reloading of config not working when :file:`kitty.conf` does not exist when kitty is launched (:iss:`5071`)
|
||||
|
||||
- Fix deleting images by row not calculating image bounds correctly (:iss:`5081`)
|
||||
|
||||
- Increase the max number of combining chars per cell from two to three, without increasing memory usage.
|
||||
|
||||
- Linux: Load libfontconfig at runtime to allow the binaries to work for
|
||||
running kittens on servers without FontConfig
|
||||
|
||||
- GNOME: Fix for high CPU usage caused by GNOME's text input subsystem going
|
||||
into an infinite loop when IME cursor position is updated after a done event
|
||||
(:iss:`5105`)
|
||||
|
||||
|
||||
0.25.0 [2022-04-11]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- :doc:`kittens/ssh`: automatic shell integration when using SSH. Easily
|
||||
clone local shell and editor configuration on remote machines, and automatic
|
||||
re-use of existing connections to avoid connection setup latency.
|
||||
|
||||
- When pasting URLs at shell prompts automatically quote them. Also allow filtering pasted text and confirm pastes. See :opt:`paste_actions` for details. (:iss:`4873`)
|
||||
|
||||
- Change the default value of :opt:`confirm_os_window_close` to ask for confirmation when closing windows that are not sitting at shell prompts
|
||||
|
||||
- A new value :code:`last_reported` for :option:`launch --cwd` to use the current working directory last reported by the program running in the terminal
|
||||
|
||||
- macOS: When using Apple's less as the pager for viewing scrollback strip out OSC codes as it cant parse them (:iss:`4788`)
|
||||
|
||||
- diff kitten: Fix incorrect rendering in rare circumstances when scrolling after changing the context size (:iss:`4831`)
|
||||
|
||||
- icat kitten: Fix a regression that broke :option:`kitty +kitten icat --print-window-size` (:pull:`4818`)
|
||||
|
||||
- Wayland: Fix :opt:`hide_window_decorations` causing docked windows to be resized on blur (:iss:`4797`)
|
||||
|
||||
- Bash integration: Prevent shell integration code from running twice if user enables both automatic and manual integration
|
||||
|
||||
- Bash integration: Handle existing PROMPT_COMMAND ending with a literal newline
|
||||
|
||||
- Fix continued lines not having their continued status reset on line feed (:iss:`4837`)
|
||||
|
||||
- macOS: Allow the New kitty Tab/Window Here services to open multiple selected folders. (:pull:`4848`)
|
||||
|
||||
- Wayland: Fix a regression that broke IME when changing windows/tabs (:iss:`4853`)
|
||||
|
||||
- macOS: Fix Unicode paths not decoded correctly when dropping files (:pull:`4879`)
|
||||
|
||||
- Avoid flicker when starting kittens such as the hints kitten (:iss:`4674`)
|
||||
|
||||
- A new action :ac:`scroll_prompt_to_top` to move the current prompt to the top (:pull:`4891`)
|
||||
|
||||
- :ac:`select_tab`: Use stable numbers when selecting the tab (:iss:`4792`)
|
||||
|
||||
- Only check for updates in the official binary builds. Distro packages or source builds will no longer check for updates, regardless of the
|
||||
value of :opt:`update_check_interval`.
|
||||
|
||||
- Fix :opt:`inactive_text_alpha` still being applied to the cursor hidden window after focus (:iss:`4928`)
|
||||
|
||||
- Fix resizing window that is extra tall/wide because of left-over cells not
|
||||
working reliably (:iss:`4913`)
|
||||
|
||||
- A new action :ac:`close_other_tabs_in_os_window` to close other tabs in the active OS window (:pull:`4944`)
|
||||
|
||||
|
||||
0.24.4 [2022-03-03]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Shell integration: Fix the default Bash :code:`$HISTFILE` changing to :file:`~/.sh_history` instead of :file:`~/.bash_history` (:iss:`4765`)
|
||||
|
||||
- Linux binaries: Fix binaries not working on systems with older Wayland client libraries (:iss:`4760`)
|
||||
|
||||
- Fix a regression in the previous release that broke kittens launched with :code:`STDIN` not connected to a terminal (:iss:`4763`)
|
||||
|
||||
- Wayland: Fix surface configure events not being acknowledged before commit
|
||||
the resized buffer (:pull:`4768`)
|
||||
|
||||
|
||||
0.24.3 [2022-02-28]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Bash integration: No longer modify :file:`~/.bashrc` to load :ref:`shell integration <shell_integration>`.
|
||||
It is recommended to remove the lines used to load the shell integration from :file:`~/.bashrc` as they are no-ops.
|
||||
|
||||
- macOS: Allow kitty to handle various URL types. Can be configured via
|
||||
:ref:`launch_actions` (:pull:`4618`)
|
||||
|
||||
- macOS: Add a new service ``Open with kitty`` to open file types that are not
|
||||
recognized by the system (:pull:`4641`)
|
||||
|
||||
- Splits layout: A new value for :option:`launch --location` to auto-select the split axis when splitting existing windows.
|
||||
Wide windows are split side-by-side and tall windows are split one-above-the-other
|
||||
|
||||
- hints kitten: Fix a regression that broke recognition of path:linenumber:colnumber (:iss:`4675`)
|
||||
|
||||
- Fix a regression in the previous release that broke :opt:`active_tab_foreground` (:iss:`4620`)
|
||||
|
||||
- Fix :ac:`show_last_command_output` not working when the output is stored
|
||||
partially in the scrollback pager history buffer (:iss:`4435`)
|
||||
|
||||
- When dropping URLs/files onto kitty at a shell prompt insert them appropriately quoted and space
|
||||
separated (:iss:`4734`)
|
||||
|
||||
- Improve CWD detection when there are multiple foreground processes in the TTY process group
|
||||
|
||||
- A new option :opt:`narrow_symbols` to turn off opportunistic wide rendering of private use codepoints
|
||||
|
||||
- ssh kitten: Fix location of generated terminfo files on NetBSD (:iss:`4622`)
|
||||
|
||||
- A new action to clear the screen up to the line containing the cursor, see
|
||||
:ac:`clear_terminal`
|
||||
|
||||
- A new action :ac:`copy_ansi_to_clipboard` to copy the current selection with ANSI formatting codes
|
||||
(:iss:`4665`)
|
||||
|
||||
- Linux: Do not rescale fallback fonts to match the main font cell height, instead just
|
||||
set the font size and let FreeType take care of it. This matches
|
||||
rendering on macOS (:iss:`4707`)
|
||||
|
||||
- macOS: Fix a regression in the previous release that broke switching input
|
||||
sources by keyboard (:iss:`4621`)
|
||||
|
||||
- macOS: Add the default shortcut :kbd:`cmd+k` to clear the terminal screen and
|
||||
scrollback up to the cursor (:iss:`4625`)
|
||||
|
||||
- Fix a regression in the previous release that broke strikethrough (:disc:`4632`)
|
||||
|
||||
- A new action :ac:`scroll_prompt_to_bottom` to move the current prompt
|
||||
to the bottom, filling in the window from the scrollback (:pull:`4634`)
|
||||
|
||||
- Add two special arguments ``@first-line-on-screen`` and ``@last-line-on-screen``
|
||||
for the :doc:`launch <launch>` command to be used for pager positioning.
|
||||
(:iss:`4462`)
|
||||
|
||||
- Linux: Fix rendering of emoji when using scalable fonts such as Segoe UI Emoji
|
||||
|
||||
- Shell integration: bash: Dont fail if an existing PROMPT_COMMAND ends with a semi-colon (:iss:`4645`)
|
||||
|
||||
- Shell integration: bash: Fix rendering of multiline prompts with more than two lines (:iss:`4681`)
|
||||
|
||||
- Shell integration: fish: Check fish version 3.3.0+ and exit on outdated versions (:pull:`4745`)
|
||||
|
||||
- Shell integration: fish: Fix pipestatus being overwritten (:pull:`4756`)
|
||||
|
||||
- Linux: Fix fontconfig alias not being used if the aliased font is dual spaced instead of monospaced (:iss:`4649`)
|
||||
|
||||
- macOS: Add an option :opt:`macos_menubar_title_max_length` to control the max length of the window title displayed in the global menubar (:iss:`2132`)
|
||||
|
||||
- Fix :opt:`touch_scroll_multiplier` also taking effect in terminal programs such as vim that handle mouse events themselves (:iss:`4680`)
|
||||
|
||||
- Fix symbol/PUA glyphs loaded via :opt:`symbol_map` instead of as fallbacks not using following spaces to render larger versions (:iss:`4670`)
|
||||
|
||||
- macOS: Fix regression in previous release that caused Apple's global shortcuts to not work if they had never been configured on a particular machine (:iss:`4657`)
|
||||
|
||||
- Fix a fast *click, move mouse, click* sequence causing the first click event to be discarded (:iss:`4603`)
|
||||
|
||||
- Wayland: Fix wheel mice with line based scrolling being incorrectly handled as high precision devices (:iss:`4694`)
|
||||
|
||||
- Wayland: Fix touchpads and high resolution wheels not scrolling at the same speed on monitors with different scales (:iss:`4703`)
|
||||
|
||||
- Add an option :opt:`wheel_scroll_min_lines` to set the minimum number of lines for mouse wheel scrolling when using a mouse with a wheel that generates very small offsets when slow scrolling (:pull:`4710`)
|
||||
|
||||
- macOS: Make the shortcut to toggle full screen configurable (:pull:`4714`)
|
||||
|
||||
- macOS: Fix the mouse cursor being set to arrow after switching desktops or toggling full screen (:pull:`4716`)
|
||||
|
||||
- Fix copying of selection after selection has been scrolled off history buffer raising an error (:iss:`4713`)
|
||||
|
||||
|
||||
0.24.2 [2022-02-03]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -420,7 +796,7 @@ Detailed list of changes
|
||||
0.22.0 [2021-07-26]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Add a new :ref:`action-toggle_layout` action to easily zoom/unzoom a window
|
||||
- Add a new :ac:`toggle_layout` action to easily zoom/unzoom a window
|
||||
|
||||
- When right clicking to extend a selection, move the nearest selection
|
||||
boundary rather than the end of the selection. To restore previous behavior
|
||||
@@ -490,7 +866,7 @@ Detailed list of changes
|
||||
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`)
|
||||
|
||||
- A new :opt:`url_excluded_characters` option to exclude additional characters
|
||||
@@ -664,7 +1040,7 @@ Detailed list of changes
|
||||
scrollback buffer onto the screen. Useful, for example, to restore
|
||||
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`)
|
||||
|
||||
- Linux: Fix binary kitty builds not able to load fonts in WOFF2 format
|
||||
@@ -689,7 +1065,7 @@ Detailed list of changes
|
||||
kitty window title bar
|
||||
|
||||
- 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
|
||||
(:iss:`3535`)
|
||||
@@ -1832,7 +2208,7 @@ Detailed list of changes
|
||||
- Allow the user to control the resize debounce time via
|
||||
: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
|
||||
active top-level window, by default (:iss:`1581`)
|
||||
|
||||
@@ -2141,7 +2517,7 @@ Detailed list of changes
|
||||
|
||||
- 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`)
|
||||
|
||||
- Add completion of the kitty command for the fish shell (:pull:`829`)
|
||||
@@ -2184,7 +2560,7 @@ Detailed list of changes
|
||||
window is not the active window (:iss:`729`)
|
||||
|
||||
- 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
|
||||
child process as well
|
||||
|
||||
@@ -2,25 +2,25 @@ Saving and restoring colors
|
||||
==============================
|
||||
|
||||
It is often useful for a full screen application with its own color themes to
|
||||
set the default foreground, background, selection and cursor colors and the
|
||||
ANSI color table. This allows for various performance optimizations when
|
||||
drawing the screen. The problem is that if the user previously used the escape
|
||||
codes to change these colors herself, then running the full screen application
|
||||
will lose her changes even after it exits. To avoid this, kitty introduces a
|
||||
new pair of *OSC* escape codes to push and pop the current color values from a
|
||||
set the default foreground, background, selection and cursor colors and the ANSI
|
||||
color table. This allows for various performance optimizations when drawing the
|
||||
screen. The problem is that if the user previously used the escape codes to
|
||||
change these colors themselves, then running the full screen application will
|
||||
lose those changes even after it exits. To avoid this, kitty introduces a new
|
||||
pair of *OSC* escape codes to push and pop the current color values from a
|
||||
stack::
|
||||
|
||||
<ESC>]30001<ESC>\ # push onto stack
|
||||
<ESC>]30101<ESC>\ # pop from stack
|
||||
|
||||
These escape codes save/restore the colors, default
|
||||
background, default foreground, selection background, selection foreground and
|
||||
cursor color and the 256 colors of the ANSI color table.
|
||||
These escape codes save/restore the colors, default background, default
|
||||
foreground, selection background, selection foreground and cursor color and the
|
||||
256 colors of the ANSI color table.
|
||||
|
||||
.. note:: In July 2020, after several years, XTerm copied this protocol
|
||||
.. note:: In July 2020, after several years, xterm copied this protocol
|
||||
extension, without acknowledgement, and using incompatible escape codes
|
||||
(XTPUSHCOLORS, XTPOPCOLORS, XTREPORTCOLORS). And they decided to save not
|
||||
just the dynamic colors but the entire ANSI color table. In the interests of
|
||||
promoting interoperability, kitty added support for XTerm's escape codes as
|
||||
promoting interoperability, kitty added support for xterm's escape codes as
|
||||
well, and changed this extension to also save/restore the entire ANSI color
|
||||
table.
|
||||
|
||||
65
docs/conf.py
65
docs/conf.py
@@ -13,7 +13,7 @@ import subprocess
|
||||
import sys
|
||||
import time
|
||||
from functools import partial
|
||||
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple
|
||||
from typing import Any, Callable, Dict, Iterable, List, Tuple
|
||||
|
||||
from docutils import nodes
|
||||
from docutils.parsers.rst.roles import set_classes
|
||||
@@ -85,7 +85,7 @@ master_doc = 'index'
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language: Optional[str] = None
|
||||
language: str = 'en'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
@@ -104,6 +104,10 @@ rst_prolog = '''
|
||||
'''.replace('VERSION', str_version)
|
||||
smartquotes_action = 'qe' # educate quotes and ellipses but not dashes
|
||||
|
||||
string_replacements = {
|
||||
'_kitty_install_cmd': 'curl -L https://sw.kovidgoyal.net/kitty/installer.sh | sh /dev/stdin',
|
||||
}
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
|
||||
@@ -116,10 +120,23 @@ html_title = 'kitty'
|
||||
# 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
|
||||
# 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] = {
|
||||
'sidebar_hide_name': 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": "",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -200,6 +217,13 @@ def commit_role(
|
||||
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.options.definition import copy_message
|
||||
with open('generated/ssh-copy.rst', 'w') as f:
|
||||
f.write(option_spec_as_rst(
|
||||
appname='copy', ospec=option_text, heading_char='^',
|
||||
usage='file-or-dir-to-copy ...', message=copy_message
|
||||
))
|
||||
with open('generated/launch.rst', 'w') as f:
|
||||
f.write(option_spec_as_rst(
|
||||
appname='launch', ospec=launch_options_spec, heading_char='_',
|
||||
@@ -225,7 +249,7 @@ if you specify a program-to-run you can use the special placeholder
|
||||
from kitty.rc.base import cli_params_for
|
||||
for cmd_name in sorted(all_command_names()):
|
||||
func = command_for_name(cmd_name)
|
||||
p(f'.. _at_{func.name}:\n')
|
||||
p(f'.. _at-{func.name}:\n')
|
||||
p('kitty @', func.name)
|
||||
p('-' * 120)
|
||||
p('.. program::', 'kitty @', func.name)
|
||||
@@ -253,7 +277,7 @@ def write_remote_control_protocol_docs() -> None: # {{{
|
||||
from kitty.rc.base import (
|
||||
RemoteCommand, all_command_names, command_for_name
|
||||
)
|
||||
field_pat = re.compile(r'\s*([a-zA-Z0-9_+]+)\s*:\s*(.+)')
|
||||
field_pat = re.compile(r'\s*([^:]+?)\s*:\s*(.+)')
|
||||
|
||||
def format_cmd(p: Callable[..., None], name: str, cmd: RemoteCommand) -> None:
|
||||
p(name)
|
||||
@@ -265,7 +289,7 @@ def write_remote_control_protocol_docs() -> None: # {{{
|
||||
if m is None:
|
||||
p(line)
|
||||
else:
|
||||
fields.append((m.group(1), m.group(2)))
|
||||
fields.append((m.group(1).split('/')[0], m.group(2)))
|
||||
if fields:
|
||||
p('\nFields are:\n')
|
||||
for (name, desc) in fields:
|
||||
@@ -293,8 +317,16 @@ def write_remote_control_protocol_docs() -> None: # {{{
|
||||
# }}}
|
||||
|
||||
|
||||
def replace_string(app: Any, docname: str, source: List[str]) -> None: # {{{
|
||||
src = source[0]
|
||||
for k, v in app.config.string_replacements.items():
|
||||
src = src.replace(k, v)
|
||||
source[0] = src
|
||||
# }}}
|
||||
|
||||
# config file docs {{{
|
||||
|
||||
|
||||
class ConfLexer(RegexLexer): # type: ignore
|
||||
name = 'Conf'
|
||||
aliases = ['conf']
|
||||
@@ -399,6 +431,12 @@ def parse_shortcut_node(env: Any, sig: str, signode: Any) -> str:
|
||||
return sig
|
||||
|
||||
|
||||
def parse_action_node(env: Any, sig: str, signode: Any) -> str:
|
||||
"""Transform an action description into RST nodes."""
|
||||
signode += addnodes.desc_name(sig, sig)
|
||||
return sig
|
||||
|
||||
|
||||
def process_opt_link(env: Any, refnode: Any, has_explicit_title: bool, title: str, target: str) -> Tuple[str, str]:
|
||||
conf_name, opt = target.partition('.')[::2]
|
||||
if not opt:
|
||||
@@ -407,6 +445,10 @@ def process_opt_link(env: Any, refnode: Any, has_explicit_title: bool, title: st
|
||||
return title, opt_aliases.get(full_name, full_name)
|
||||
|
||||
|
||||
def process_action_link(env: Any, refnode: Any, has_explicit_title: bool, title: str, target: str) -> Tuple[str, str]:
|
||||
return title, target
|
||||
|
||||
|
||||
def process_shortcut_link(env: Any, refnode: Any, has_explicit_title: bool, title: str, target: str) -> Tuple[str, str]:
|
||||
conf_name, slug = target.partition('.')[::2]
|
||||
if not slug:
|
||||
@@ -444,6 +486,15 @@ def write_conf_docs(app: Any, all_kitten_names: Iterable[str]) -> None:
|
||||
sc_role.process_link = process_shortcut_link
|
||||
shortcut_slugs.clear()
|
||||
|
||||
app.add_object_type(
|
||||
'action', 'ac',
|
||||
indextemplate="pair: %s; Action",
|
||||
parse_node=parse_action_node,
|
||||
)
|
||||
ac_role = app.registry.domain_roles['std']['ac']
|
||||
ac_role.warn_dangling = True
|
||||
ac_role.process_link = process_action_link
|
||||
|
||||
def generate_default_config(definition: Definition, name: str) -> None:
|
||||
with open(f'generated/conf-{name}.rst', 'w', encoding='utf-8') as f:
|
||||
print('.. highlight:: conf\n', file=f)
|
||||
@@ -490,6 +541,8 @@ def setup(app: Any) -> None:
|
||||
write_cli_docs(kn)
|
||||
write_remote_control_protocol_docs()
|
||||
write_conf_docs(app, kn)
|
||||
app.add_config_value('string_replacements', {}, True)
|
||||
app.connect('source-read', replace_string)
|
||||
app.add_config_value('analytics_id', '', 'env')
|
||||
app.connect('html-page-context', add_html_context)
|
||||
app.add_lexer('session', SessionLexer() if version_info[0] < 3 else SessionLexer)
|
||||
|
||||
@@ -3,13 +3,14 @@ kitty.conf
|
||||
|
||||
.. highlight:: conf
|
||||
|
||||
|kitty| is highly customizable, everything from keyboard shortcuts, to
|
||||
rendering frames-per-second. See below for an overview of all customization
|
||||
possibilities.
|
||||
|kitty| is highly customizable, everything from keyboard shortcuts, to rendering
|
||||
frames-per-second. See below for an overview of all customization possibilities.
|
||||
|
||||
You can open the config file within kitty by pressing :sc:`edit_config_file` (:kbd:`⌘+,` on macOS).
|
||||
You can reload the config file within kitty by pressing
|
||||
:sc:`reload_config_file` (:kbd:`⌃+⌘+,` on macOS) or sending kitty the ``SIGUSR1`` signal.
|
||||
You can open the config file within kitty by pressing :sc:`edit_config_file`
|
||||
(:kbd:`⌘+,` on macOS). A :file:`kitty.conf` with commented default
|
||||
configurations and descriptions will be created if the file does not exist.
|
||||
You can reload the config file within kitty by pressing :sc:`reload_config_file`
|
||||
(:kbd:`⌃+⌘+,` on macOS) or sending kitty the ``SIGUSR1`` signal.
|
||||
You can also display the current configuration by pressing :sc:`debug_config`
|
||||
(:kbd:`⌥+⌘+,` on macOS).
|
||||
|
||||
@@ -17,26 +18,32 @@ You can also display the current configuration by pressing :sc:`debug_config`
|
||||
|
||||
|kitty| looks for a config file in the OS config directories (usually
|
||||
:file:`~/.config/kitty/kitty.conf`) but you can pass a specific path via the
|
||||
:option:`kitty --config` option or use the ``KITTY_CONFIG_DIRECTORY``
|
||||
environment variable. See the :option:`kitty --config` option for full details.
|
||||
:option:`kitty --config` option or use the :envvar:`KITTY_CONFIG_DIRECTORY`
|
||||
environment variable. See :option:`kitty --config` for full details.
|
||||
|
||||
Comments can be added to the config file as lines starting with the ``#``
|
||||
character. This works only if the ``#`` character is the first character
|
||||
in the line.
|
||||
character. This works only if the ``#`` character is the first character in the
|
||||
line.
|
||||
|
||||
.. _include:
|
||||
|
||||
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
|
||||
location of the current config file. Note that environment variables are
|
||||
expanded, so :code:`${USER}.conf` becomes :file:`name.conf` if
|
||||
:code:`USER=name`. Also, you can use :code:`globinclude` to include files
|
||||
matching a shell glob pattern. For example::
|
||||
matching a shell glob pattern and :code:`envinclude` to include configuration
|
||||
from environment variables. For example::
|
||||
|
||||
include other.conf
|
||||
# Include *.conf files from all subdirs of kitty.d inside the kitty config dir
|
||||
globinclude kitty.d/**/*.conf
|
||||
# Include the *contents* of all env vars starting with KITTY_CONF_
|
||||
envinclude KITTY_CONF_*
|
||||
|
||||
|
||||
.. note:: Syntax highlighting for :file:`kitty.conf` in vim is available via
|
||||
`vim-kitty <https://github.com/fladson/vim-kitty>`_.
|
||||
`vim-kitty <https://github.com/fladson/vim-kitty>`__.
|
||||
|
||||
|
||||
.. include:: /generated/conf-kitty.rst
|
||||
@@ -47,17 +54,17 @@ Sample kitty.conf
|
||||
|
||||
.. only:: html
|
||||
|
||||
You can download a sample :file:`kitty.conf` file with all default settings and
|
||||
comments describing each setting by clicking: :download:`sample kitty.conf
|
||||
</generated/conf/kitty.conf>`.
|
||||
You can download a sample :file:`kitty.conf` file with all default settings
|
||||
and comments describing each setting by clicking: :download:`sample
|
||||
kitty.conf </generated/conf/kitty.conf>`.
|
||||
|
||||
.. only:: man
|
||||
|
||||
You can edit a fully commented sample kitty.conf by pressing the
|
||||
:sc:`edit_config_file` shortcut in kitty. This will generate a config
|
||||
file with full documentation and all settings commented out. If you
|
||||
have a pre-existing kitty.conf, then that will be used instead, delete
|
||||
it to see the sample file.
|
||||
:sc:`edit_config_file` shortcut in kitty. This will generate a config file
|
||||
with full documentation and all settings commented out. If you have a
|
||||
pre-existing :file:`kitty.conf`, then that will be used instead, delete it to
|
||||
see the sample file.
|
||||
|
||||
|
||||
All mappable actions
|
||||
|
||||
@@ -3,19 +3,18 @@ Setting text styles/colors in arbitrary regions of the screen
|
||||
|
||||
There already exists an escape code to set *some* text attributes in arbitrary
|
||||
regions of the screen, `DECCARA
|
||||
<https://vt100.net/docs/vt510-rm/DECCARA.html>`_. However, it is limited to
|
||||
<https://vt100.net/docs/vt510-rm/DECCARA.html>`__. However, it is limited to
|
||||
only a few attributes. |kitty| extends this to work with *all* SGR attributes.
|
||||
So, for example, this can be used to set the background color in an arbitrary
|
||||
region of the screen.
|
||||
|
||||
The motivation for this extension is the various problems with the existing
|
||||
solution for erasing to background color, namely the *background color erase
|
||||
(bce)* capability. See
|
||||
:iss:`this discussion <160#issuecomment-346470545>`
|
||||
and `this FAQ <https://invisible-island.net/ncurses/ncurses.faq.html#bce_mismatches>`_
|
||||
(bce)* capability. See :iss:`this discussion <160#issuecomment-346470545>`
|
||||
and `this FAQ <https://invisible-island.net/ncurses/ncurses.faq.html#bce_mismatches>`__
|
||||
for a summary of problems with *bce*.
|
||||
|
||||
For example, to set the background color to blue in a
|
||||
rectangular region of the screen from (3, 4) to (10, 11), you use::
|
||||
For example, to set the background color to blue in a rectangular region of the
|
||||
screen from (3, 4) to (10, 11), you use::
|
||||
|
||||
<ESC>[2*x<ESC>[4;3;11;10;44$r<ESC>[*x
|
||||
|
||||
@@ -5,21 +5,20 @@ Desktop notifications
|
||||
=======================
|
||||
|
||||
|kitty| implements an extensible escape code (OSC 99) to show desktop
|
||||
notifications. It is easy to use from shell scripts and fully extensible to
|
||||
show title and body. Clicking on the notification can optionally focus the
|
||||
window it came from, and/or send an escape code back to the application running
|
||||
in that window.
|
||||
notifications. It is easy to use from shell scripts and fully extensible to show
|
||||
title and body. Clicking on the notification can optionally focus the window it
|
||||
came from, and/or send an escape code back to the application running in that
|
||||
window.
|
||||
|
||||
The design of the escape code is partially based on the discussion in
|
||||
the defunct
|
||||
`terminal-wg <https://gitlab.freedesktop.org/terminal-wg/specifications/-/issues/13>`_
|
||||
The design of the escape code is partially based on the discussion in the
|
||||
defunct `terminal-wg <https://gitlab.freedesktop.org/terminal-wg/specifications/-/issues/13>`__
|
||||
|
||||
The escape code has the form::
|
||||
|
||||
<OSC> 99 ; metadata ; payload <terminator>
|
||||
|
||||
Here ``<OSC>`` is :code:`<ESC>]` and ``<terminator>`` is
|
||||
:code:`<ESC><backslash>`. The metadata is a section of colon separated
|
||||
:code:`<ESC><backslash>`. The ``metadata`` is a section of colon separated
|
||||
:code:`key=value` pairs. Every key must be a single character from the set
|
||||
:code:`a-zA-Z` and every value must be a word consisting of characters from
|
||||
the set :code:`a-zA-Z0-9-_/\+.,(){}[]*&^%$#@!`~`. The payload must be
|
||||
@@ -45,28 +44,27 @@ The design of the escape code is fundamentally chunked, this is because
|
||||
different terminal emulators have different limits on how large a single escape
|
||||
code can be. Chunking is accomplished by the ``i`` and ``d`` keys. The ``i``
|
||||
key is the *notification id* which can be any string containing the characters
|
||||
``[a-zA-Z0-9_-+.]``. The ``d`` key stands for *done* and
|
||||
can only take the values ``0`` and ``1``. A value of ``0`` means the
|
||||
notification is not yet done 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 body multiple times and the terminal
|
||||
emulator will concatenate them, thereby allowing arbitrarily long text
|
||||
(terminal emulators are free to impose a sensible limit to avoid
|
||||
Denial-of-Service attacks).
|
||||
``[a-zA-Z0-9_-+.]``. The ``d`` key stands for *done* and can only take the
|
||||
values ``0`` and ``1``. A value of ``0`` means the notification is not yet done
|
||||
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
|
||||
body multiple times and the terminal emulator will concatenate them, thereby
|
||||
allowing arbitrarily long text (terminal emulators are free to impose a sensible
|
||||
limit to avoid Denial-of-Service attacks).
|
||||
|
||||
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
|
||||
which case there must be an ``e=1`` key in the metadata to indicate the payload
|
||||
is base64 encoded.
|
||||
is Base64 encoded.
|
||||
|
||||
When the user clicks the notification, a couple of things can happen, the
|
||||
terminal emulator can focus the window from which the notification came, and/or
|
||||
it can send back an escape code to the application indicating the notification
|
||||
was activated. This is controlled by the ``a`` key which takes a comma
|
||||
separated set of values, ``report`` and ``focus``. The value ``focus`` means
|
||||
focus the window from which the notification was issued and is the default.
|
||||
``report`` means send an escape code back to the application. The format of the
|
||||
returned escape code is::
|
||||
was activated. This is controlled by the ``a`` key which takes a comma separated
|
||||
set of values, ``report`` and ``focus``. The value ``focus`` means focus the
|
||||
window from which the notification was issued and is the default. ``report``
|
||||
means send an escape code back to the application. The format of the returned
|
||||
escape code is::
|
||||
|
||||
<OSC> 99 ; i=identifier ; <terminator>
|
||||
|
||||
@@ -87,9 +85,9 @@ to display it based on what it does understand.
|
||||
|
||||
.. note::
|
||||
It is possible to extend this escape code to allow specifying an icon for
|
||||
the notification, however, given that some platforms, such as macOS, dont
|
||||
allow displaying custom icons on a notification, at all, it was decided to
|
||||
leave it out of the spec for the time being.
|
||||
the notification, however, given that some platforms, such as legacy versions
|
||||
of macOS, don't allow displaying custom images on a notification, it was
|
||||
decided to leave it out of the spec for the time being.
|
||||
|
||||
Similarly, features such as scheduled notifications could be added in future
|
||||
revisions.
|
||||
@@ -107,7 +105,7 @@ Key Value Default Description
|
||||
``d`` ``0`` or ``1`` ``1`` Indicates if the notification is
|
||||
complete or not.
|
||||
|
||||
``e`` ``0`` or ``1`` ``0`` If set to ``1`` means the payload is base64 encoded UTF-8,
|
||||
``e`` ``0`` or ``1`` ``0`` If set to ``1`` means the payload is Base64 encoded UTF-8,
|
||||
otherwise it is plain UTF-8 text with no C0 control codes in it
|
||||
|
||||
``i`` ``[a-zA-Z0-9-_+.]`` ``0`` Identifier for the notification
|
||||
@@ -118,5 +116,6 @@ Key Value Default Description
|
||||
|
||||
|
||||
.. note::
|
||||
|kitty| also supports the legacy OSC 9 protocol developed by iTerm2 for
|
||||
desktop notifications.
|
||||
|kitty| also supports the `legacy OSC 9 protocol developed by iTerm2
|
||||
<https://iterm2.com/documentation-escape-codes.html>`__ for desktop
|
||||
notifications.
|
||||
|
||||
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))
|
||||
280
docs/faq.rst
280
docs/faq.rst
@@ -6,34 +6,36 @@ Frequently Asked Questions
|
||||
Some special symbols are rendered small/truncated in kitty?
|
||||
-----------------------------------------------------------
|
||||
|
||||
The number of cells a unicode character takes up are controlled by the unicode
|
||||
standard. All characters are rendered in a single cell unless the unicode
|
||||
The number of cells a Unicode character takes up are controlled by the Unicode
|
||||
standard. All characters are rendered in a single cell unless the Unicode
|
||||
standard says they should be rendered in two cells. When a symbol does not fit,
|
||||
it will either be rescaled to be smaller or truncated (depending on how much
|
||||
extra space it needs). This is often different from other terminals which just
|
||||
let the character overflow into neighboring cells, which is fine if the
|
||||
neighboring cell is empty, but looks terrible if it is not.
|
||||
|
||||
Some programs, like powerline, vim with fancy gutter symbols/status-bar, etc.
|
||||
misuse unicode characters from the private use area to represent symbols. Often
|
||||
these symbols are square and should be rendered in two cells. However, since
|
||||
private use area symbols all have their width set to one in the unicode
|
||||
Some programs, like Powerline, vim with fancy gutter symbols/status-bar, etc.
|
||||
use Unicode characters from the private use area to represent symbols. Often
|
||||
these symbols are wide and should be rendered in two cells. However, since
|
||||
private use area symbols all have their width set to one in the Unicode
|
||||
standard, |kitty| renders them either smaller or truncated. The exception is if
|
||||
these characters are followed by a space or empty cell in which case kitty
|
||||
makes use of the extra cell to render them in two cells.
|
||||
makes use of the extra cell to render them in two cells. This behavior can be
|
||||
turned off for specific symbols using :opt:`narrow_symbols`.
|
||||
|
||||
|
||||
Using a color theme with a background color does not work well in vim?
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
First make sure you have not changed the TERM environment variable, it should
|
||||
be ``xterm-kitty``. vim uses *background color erase* even if the terminfo file
|
||||
does not contain the ``bce`` capability. This is a bug in vim. You can work around
|
||||
it by adding the following to your vimrc::
|
||||
First make sure you have not changed the :envvar:`TERM` environment variable, it
|
||||
should be ``xterm-kitty``. vim uses *background color erase* even if the
|
||||
terminfo file does not contain the ``bce`` capability. This is a bug in vim. You
|
||||
can work around it by adding the following to your vimrc::
|
||||
|
||||
let &t_ut=''
|
||||
|
||||
See :doc:`here <deccara>` for why |kitty| does not support background color erase.
|
||||
See :doc:`here <deccara>` for why |kitty| does not support background color
|
||||
erase.
|
||||
|
||||
|
||||
I get errors about the terminal being unknown or opening the terminal failing when SSHing into a different computer?
|
||||
@@ -45,42 +47,33 @@ terminfo files to the server::
|
||||
|
||||
kitty +kitten ssh myserver
|
||||
|
||||
This ssh kitten takes all the same command line arguments
|
||||
as ssh, you can alias it to ssh in your shell's rc files to avoid having to
|
||||
type it each time::
|
||||
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
|
||||
to avoid having to type it each time::
|
||||
|
||||
alias ssh="kitty +kitten ssh"
|
||||
alias s="kitty +kitten ssh"
|
||||
|
||||
Remember to also setup :ref:`shell_integration` for completion and other
|
||||
niceties.
|
||||
|
||||
If for some reason that does not work (typically because the server is using a
|
||||
non POSIX compliant shell as ``/bin/sh``), you can try using it with ``python``
|
||||
instead::
|
||||
|
||||
kitty +kitten ssh use-python myserver
|
||||
|
||||
If that also fails, perhaps because python is not installed on the remote
|
||||
server, use the following one-liner instead (it
|
||||
is slower as it needs to ssh into the server twice, but will work with most
|
||||
servers)::
|
||||
If the ssh kitten fails, use the following one-liner instead (it is slower as it
|
||||
needs to ssh into the server twice, but will work with most servers)::
|
||||
|
||||
infocmp -a xterm-kitty | ssh myserver tic -x -o \~/.terminfo /dev/stdin
|
||||
|
||||
If you are behind a proxy (like Balabit) that prevents this, you must redirect the
|
||||
1st command to a file, copy that to the server and run ``tic`` manually. If you
|
||||
connect to a server, embedded or Android system that doesn't have ``tic``, copy over
|
||||
your local file terminfo to the other system as :file:`~/.terminfo/x/xterm-kitty`.
|
||||
If you are behind a proxy (like Balabit) that prevents this, or :program:`tic`
|
||||
comes with macOS that does not support reading from 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`.
|
||||
|
||||
Really, the correct solution for this is to convince the OpenSSH maintainers to
|
||||
have ssh do this automatically, if possible, when connecting to a server, so that
|
||||
all terminals work transparently.
|
||||
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 -C xterm-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
|
||||
@@ -92,22 +85,29 @@ command to apply your change (on the server)::
|
||||
Keys such as arrow keys, backspace, delete, home/end, etc. do not work when using su or sudo?
|
||||
-------------------------------------------------------------------------------------------------
|
||||
|
||||
Make sure the TERM environment variable, is ``xterm-kitty``. And either the
|
||||
TERMINFO environment variable points to a directory containing :file:`x/xterm-kitty`
|
||||
or that file is under :file:`~/.terminfo/x/`.
|
||||
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/`.
|
||||
|
||||
Note that ``sudo`` might remove 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 ``sudo`` to set it or if that is not supported, insert an ``env``
|
||||
command before starting the shell, or, if not possible, after sudo start another
|
||||
Shell providing the right terminfo path::
|
||||
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 sudo to preserve TERMINFO by running ``sudo
|
||||
visudo`` and adding the following line::
|
||||
You can configure :program:`sudo` to preserve :envvar:`TERMINFO` by running
|
||||
``sudo visudo`` and adding the following line::
|
||||
|
||||
Defaults env_keep += "TERM TERMINFO"
|
||||
|
||||
@@ -131,12 +131,15 @@ You can also define keyboard shortcuts to set colors, for example::
|
||||
|
||||
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 :ref:`at_set-colors`.
|
||||
The shortcut mapping technique has the same syntax as the remote control
|
||||
command, for details, see :ref:`at_set-colors`.
|
||||
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
|
||||
remote control command, for details, see :ref:`at-set-colors`.
|
||||
|
||||
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>`.
|
||||
|
||||
Additionally, You can use the
|
||||
`OSC terminal escape codes <https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands>`_
|
||||
`OSC terminal escape codes <https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands>`__
|
||||
to set colors. Examples of using OSC escape codes to set colors::
|
||||
|
||||
Change the default foreground color:
|
||||
@@ -153,7 +156,7 @@ to set colors. Examples of using OSC escape codes to set colors::
|
||||
printf '\x1b]4;n;green\x1b\\'
|
||||
|
||||
You can use various syntaxes/names for color specifications in the above
|
||||
examples. See `XParseColor <https://linux.die.net/man/3/xparsecolor>`_
|
||||
examples. See `XParseColor <https://linux.die.net/man/3/xparsecolor>`__
|
||||
for full details.
|
||||
|
||||
If a ``?`` is given rather than a color specification, kitty will respond
|
||||
@@ -166,14 +169,15 @@ How do I specify command line options for kitty on macOS?
|
||||
Apple does not want you to use command line options with GUI applications. To
|
||||
workaround that limitation, |kitty| will read command line options from the file
|
||||
:file:`<kitty config dir>/macos-launch-services-cmdline` when it is launched
|
||||
from the GUI, i.e. by clicking the |kitty| application icon or using ``open -a kitty``.
|
||||
Note that this file is *only read* when running via the GUI.
|
||||
from the GUI, i.e. by clicking the |kitty| application icon or using
|
||||
``open -a kitty``. Note that this file is *only read* when running via the GUI.
|
||||
|
||||
You can, of course, also run |kitty| from a terminal with command line options, using:
|
||||
:file:`/Applications/kitty.app/Contents/MacOS/kitty`.
|
||||
You can, of course, also run |kitty| from a terminal with command line options,
|
||||
using: :file:`/Applications/kitty.app/Contents/MacOS/kitty`.
|
||||
|
||||
And within |kitty| itself, you can always run |kitty| using just ``kitty`` as it
|
||||
cleverly adds itself to the :envvar:`PATH`.
|
||||
|
||||
And within |kitty| itself, you can always run |kitty| using just `kitty` as it
|
||||
cleverly adds itself to the ``PATH``.
|
||||
|
||||
I catted a binary file and now kitty is hung?
|
||||
-----------------------------------------------
|
||||
@@ -181,10 +185,10 @@ I catted a binary file and now kitty is hung?
|
||||
**Never** output unknown binary data directly into a terminal.
|
||||
|
||||
Terminals have a single channel for both data and control. Certain bytes
|
||||
are control codes. Some of these control codes are of arbitrary length, so
|
||||
if the binary data you output into the terminal happens to contain the starting
|
||||
sequence for one of these control codes, the terminal will hang waiting for
|
||||
the closing sequence. Press :kbd:`ctrl+shift+delete` to reset the terminal.
|
||||
are control codes. Some of these control codes are of arbitrary length, so if
|
||||
the binary data you output into the terminal happens to contain the starting
|
||||
sequence for one of these control codes, the terminal will hang waiting for the
|
||||
closing sequence. Press :sc:`reset_terminal` to reset the terminal.
|
||||
|
||||
If you do want to cat unknown data, use ``cat -v``.
|
||||
|
||||
@@ -192,29 +196,36 @@ If you do want to cat unknown data, use ``cat -v``.
|
||||
kitty is not able to use my favorite font?
|
||||
---------------------------------------------
|
||||
|
||||
|kitty| achieves its stellar performance by caching alpha masks of each
|
||||
rendered character on the GPU, and rendering them all in parallel. This means
|
||||
it is a strictly character cell based display. As such it can use only
|
||||
monospace fonts, since every cell in the grid has to be the same size.
|
||||
Furthermore, it needs fonts to be freely resizable, so it does not support
|
||||
bitmapped fonts.
|
||||
|kitty| achieves its stellar performance by caching alpha masks of each rendered
|
||||
character on the GPU, and rendering them all in parallel. This means it is a
|
||||
strictly character cell based display. As such it can use only monospace fonts,
|
||||
since every cell in the grid has to be the same size. Furthermore, it needs
|
||||
fonts to be freely resizable, so it does not support bitmapped fonts.
|
||||
|
||||
.. note::
|
||||
If you are trying to use a font patched with NERD font symbols, dont do that
|
||||
as patching destroys fonts. There is no need, simply install the standalone
|
||||
NERD font, kitty should pick up symbols from it automatically, and you can
|
||||
tell it to do so explicitly in case it doesnt with the :opt:`symbol_map`
|
||||
directive::
|
||||
If you are trying to use a font patched with `Nerd Fonts
|
||||
<https://nerdfonts.com/>`__ symbols, don't do that as patching destroys
|
||||
fonts. There is no need, simply install the standalone ``Symbols Nerd Font Mono``
|
||||
(the file :file:`NerdFontsSymbolsOnly.zip` from the `Nerd Fonts releases page
|
||||
<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
|
||||
case it doesn't with the :opt:`symbol_map` directive::
|
||||
|
||||
symbol_map U+23FB-U+23FE,U+2B58,U+E200-U+E2A9,U+E0A0-U+E0A3,U+E0B0-U+E0BF,U+E0C0-U+E0C8,U+E0CC-U+E0CF,U+E0D0-U+E0D2,U+E0D4,U+E700-U+E7C5,U+F000-U+F2E0,U+2665,U+26A1,U+F400-U+F4A8,U+F67C,U+E000-U+E00A,U+F300-U+F313,U+E5FA-U+E62B Symbols Nerd Font
|
||||
# Nerd Fonts v2.2.2
|
||||
|
||||
If your font is not listed in ``kitty list-fonts`` it means that it is not
|
||||
monospace or is a bitmapped font. On Linux you can list all monospace fonts with::
|
||||
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
|
||||
not included.
|
||||
|
||||
If your font is not listed in ``kitty +list-fonts`` it means that it is not
|
||||
monospace or is a bitmapped font. On Linux you can list all monospace fonts
|
||||
with::
|
||||
|
||||
fc-list : family spacing outline scalable | grep -e spacing=100 -e spacing=90 | grep -e outline=True | grep -e scalable=True
|
||||
|
||||
Note that the spacing property is calculated by fontconfig based on actual
|
||||
glyph widths in the font. If for some reason fontconfig concludes your favorite
|
||||
Note that the spacing property is calculated by fontconfig based on actual glyph
|
||||
widths in the font. If for some reason fontconfig concludes your favorite
|
||||
monospace font does not have ``spacing=100`` you can override it by using the
|
||||
following :file:`~/.config/fontconfig/fonts.conf`::
|
||||
|
||||
@@ -236,7 +247,7 @@ command to rebuild your fontconfig cache::
|
||||
|
||||
fc-cache -r
|
||||
|
||||
Then, the font will be available in ``kitty list-fonts``.
|
||||
Then, the font will be available in ``kitty +list-fonts``.
|
||||
|
||||
|
||||
How can I assign a single global shortcut to bring up the kitty terminal?
|
||||
@@ -270,13 +281,44 @@ homepage:
|
||||
:target: https://github.com/hristost/kitty-alternative-icon
|
||||
:width: 256
|
||||
|
||||
On macOS you can change the icon by following the steps:
|
||||
.. image:: https://github.com/igrmk/whiskers/raw/main/whiskers.svg
|
||||
:target: https://github.com/igrmk/whiskers
|
||||
:width: 256
|
||||
|
||||
#. Find :file:`kitty.app` in the Applications folder, select it and press :kbd:`⌘+i`
|
||||
.. 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`
|
||||
#. 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?
|
||||
@@ -292,7 +334,14 @@ the :sc:`send_text <send_text>` you can use the ``show_key`` kitten. Run::
|
||||
|
||||
kitty +kitten show_key
|
||||
|
||||
Then press the key you want to emulate.
|
||||
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
|
||||
actions in kitty will not be shown. To check those first map them to
|
||||
: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?
|
||||
--------------------------------------------------------------------------------------------
|
||||
@@ -319,8 +368,8 @@ variables which kitty will now inherit.
|
||||
You need to make sure that the environment variables you define in your shell's
|
||||
rc files are either also defined system wide or via the :opt:`env` directive in
|
||||
:file:`kitty.conf`. Common environment variables that cause issues are those
|
||||
related to localization, such as ``LANG, LC_*`` and loading of configuration
|
||||
files such as ``XDG_*, KITTY_CONFIG_DIRECTORY``.
|
||||
related to localization, such as :envvar:`LANG`, ``LC_*`` and loading of
|
||||
configuration files such as ``XDG_*``, :envvar:`KITTY_CONFIG_DIRECTORY`.
|
||||
|
||||
To see the environment variables that kitty sees, you can add the following
|
||||
mapping to :file:`kitty.conf`::
|
||||
@@ -337,40 +386,42 @@ sorts of places where they may or may not work.
|
||||
I am using tmux and have a problem
|
||||
--------------------------------------
|
||||
|
||||
First, terminal multiplexers are :iss:`a bad idea <391#issuecomment-638320745>`, do
|
||||
not use them, if at all possible. kitty contains features that do all of what
|
||||
First, terminal multiplexers are :iss:`a bad idea <391#issuecomment-638320745>`,
|
||||
do not use them, if at all possible. kitty contains features that do all of what
|
||||
tmux does, but better, with the exception of remote persistence (:iss:`391`).
|
||||
If you still want to use tmux, read on.
|
||||
|
||||
Image display will not work, see `tmux issue
|
||||
<https://github.com/tmux/tmux/issues/1391>`_.
|
||||
<https://github.com/tmux/tmux/issues/1391>`__.
|
||||
|
||||
Using ancient versions of tmux such as 1.8 will
|
||||
cause gibberish on screen when pressing keys (:iss:`3541`).
|
||||
Using ancient versions of tmux such as 1.8 will cause gibberish on screen when
|
||||
pressing keys (:iss:`3541`).
|
||||
|
||||
If you are using tmux with multiple terminals or you start it under one
|
||||
terminal and then switch to another and these terminals have different TERM
|
||||
variables, tmux will break. You will need to restart it as tmux does not
|
||||
support multiple terminfo definitions.
|
||||
If you are using tmux with multiple terminals or you start it under one terminal
|
||||
and then switch to another and these terminals have different :envvar:`TERM`
|
||||
variables, tmux will break. You will need to restart it as tmux does not support
|
||||
multiple terminfo definitions.
|
||||
|
||||
If you use any of the advanced features that kitty has innovated, such as
|
||||
styled underlines, desktop notifications, extended keyboard support, etc.
|
||||
they may or may not work, depending on the whims of tmux's maintainer, your
|
||||
version of tmux, etc.
|
||||
:doc:`styled underlines </underlines>`, :doc:`desktop notifications
|
||||
</desktop-notifications>`, :doc:`extended keyboard support
|
||||
</keyboard-protocol>`, etc. they may or may not work, depending on the whims of
|
||||
tmux's maintainer, your version of tmux, etc.
|
||||
|
||||
|
||||
I opened and closed a lot of windows/tabs and top shows kitty's memory usage is very high?
|
||||
-------------------------------------------------------------------------------------------
|
||||
|
||||
``top`` is not a good way to measure process memory usage. That is because on
|
||||
modern systems, when allocating memory to a process, the C library functions
|
||||
will typically allocate memory in large blocks, and give the process chunks of
|
||||
these blocks. When the process frees a chunk, the C library will not
|
||||
:program:`top` is not a good way to measure process memory usage. That is
|
||||
because on modern systems, when allocating memory to a process, the C library
|
||||
functions will typically allocate memory in large blocks, and give the process
|
||||
chunks of these blocks. When the process frees a chunk, the C library will not
|
||||
necessarily release the underlying block back to the OS. So even though the
|
||||
application has released the memory, ``top`` will still claim the process is
|
||||
using it.
|
||||
application has released the memory, :program:`top` will still claim the process
|
||||
is using it.
|
||||
|
||||
To check for memory leaks, instead use a tool like ``valgrind``. Run::
|
||||
To check for memory leaks, instead use a tool like `Valgrind
|
||||
<https://valgrind.org/>`__. Run::
|
||||
|
||||
PYTHONMALLOC=malloc valgrind --tool=massif kitty
|
||||
|
||||
@@ -383,18 +434,19 @@ that window, maybe run yes or find again. Then quit kitty and run::
|
||||
You will see the allocations graph goes up when you opened the windows, then
|
||||
goes back down when you closed them, indicating there were no memory leaks.
|
||||
|
||||
For those interested, you can get a similar profile out of ``valgrind`` as you get
|
||||
with ``top`` by adding ``--pages-as-heap=yes`` then you will see that memory
|
||||
allocated in malloc is not freed in free. This can be further refined if you
|
||||
use `glibc`` as your C library by setting the environment variable
|
||||
``MALLOC_MMAP_THRESHOLD_=64``. This will cause free to actually free memory
|
||||
allocated in sizes of more than 64 bytes. With this set, memory usage will
|
||||
climb high, then fall when closing windows, but not fall all the way back. The
|
||||
remaining used memory can be investigated using valgrind again, and it will
|
||||
For those interested, you can get a similar profile out of :program:`valgrind`
|
||||
as you get with :program:`top` by adding ``--pages-as-heap=yes`` then you will
|
||||
see that memory allocated in malloc is not freed in free. This can be further
|
||||
refined if you use ``glibc`` as your C library by setting the environment
|
||||
variable ``MALLOC_MMAP_THRESHOLD_=64``. This will cause free to actually free
|
||||
memory allocated in sizes of more than 64 bytes. With this set, memory usage
|
||||
will climb high, then fall when closing windows, but not fall all the way back.
|
||||
The remaining used memory can be investigated using valgrind again, and it will
|
||||
come from arenas in the GPU drivers and the per thread arenas glibc's malloc
|
||||
maintains. These too allocate memory in large blocks and dont release it back
|
||||
maintains. These too allocate memory in large blocks and don't release it back
|
||||
to the OS immediately.
|
||||
|
||||
|
||||
Why does kitty sometimes start slowly on my Linux system?
|
||||
-------------------------------------------------------------------------------------------
|
||||
|
||||
@@ -418,4 +470,4 @@ The correct command will depend on your situation and hardware.
|
||||
:file:`libEGL_mesa.so` and ignore :file:`libEGL_nvidia.so` also available on the
|
||||
system, which will wake the NVIDIA card during device enumeration.
|
||||
``MESA_LOADER_DRIVER_OVERRIDE`` also assures that Mesa won't offer any NVIDIA
|
||||
card during enumeration, and will instead just use `/lib/dri/radeonsi_dri.so`.
|
||||
card during enumeration, and will instead just use :file:`radeonsi_dri.so`.
|
||||
|
||||
@@ -30,14 +30,20 @@ Glossary
|
||||
overlay
|
||||
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
|
||||
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
|
||||
</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
|
||||
Terminals can have hyperlinks, just like the internet. In kitty you can
|
||||
:doc:`control exactly what happens <open_actions>` when clicking on a
|
||||
hyperlink, based on the type of link and its URL.
|
||||
hyperlink, based on the type of link and its URL. See also `Hyperlinks in terminal
|
||||
emulators <https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda>`__.
|
||||
|
||||
.. _env_vars:
|
||||
|
||||
@@ -45,6 +51,7 @@ Environment variables
|
||||
------------------------
|
||||
|
||||
Variables that influence kitty behavior
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. envvar:: KITTY_CONFIG_DIRECTORY
|
||||
|
||||
@@ -57,11 +64,17 @@ Variables that influence kitty behavior
|
||||
Controls where kitty stores cache files. Defaults to :file:`~/.cache/kitty`
|
||||
or :file:`~/Library/Caches/kitty` on macOS.
|
||||
|
||||
.. envvar:: KITTY_RUNTIME_DIRECTORY
|
||||
|
||||
Controls where kitty stores runtime files like sockets. Defaults to
|
||||
the :code:`XDG_RUNTIME_DIR` environment variable if that is defined
|
||||
otherwise the run directory inside the kitty cache directory is used.
|
||||
|
||||
.. envvar:: VISUAL
|
||||
|
||||
The terminal editor (such as ``vi`` or ``nano``) kitty uses, when, for
|
||||
instance, opening :file:`kitty.conf` in response to :sc:`edit_config_file`.
|
||||
|
||||
The terminal based text editor (such as :program:`vi` or :program:`nano`)
|
||||
kitty uses, when, for instance, opening :file:`kitty.conf` in response to
|
||||
:sc:`edit_config_file`.
|
||||
|
||||
.. envvar:: EDITOR
|
||||
|
||||
@@ -78,84 +91,124 @@ Variables that influence kitty behavior
|
||||
is possible for the autodiscovery to fail; the default Wayland XKB mappings
|
||||
are used in this case. See :pull:`3943` for details.
|
||||
|
||||
.. envvar:: SSH_ASKPASS
|
||||
|
||||
Specify the program for SSH to ask for passwords. When this is set, :doc:`ssh
|
||||
kitten </kittens/ssh>` will use this environment variable by default. See
|
||||
:opt:`askpass <kitten-ssh.askpass>` for details.
|
||||
|
||||
.. envvar:: KITTY_CLONE_SOURCE_CODE
|
||||
|
||||
Set this to some shell code that will be executed in the cloned window with
|
||||
:code:`eval` when :ref:`clone-in-kitty <clone_shell>` is used.
|
||||
|
||||
.. envvar:: KITTY_CLONE_SOURCE_PATH
|
||||
|
||||
Set this to the path of a file that will be sourced in the cloned window when
|
||||
:ref:`clone-in-kitty <clone_shell>` is used.
|
||||
|
||||
.. envvar:: KITTY_DEVELOP_FROM
|
||||
|
||||
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.
|
||||
|
||||
.. 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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. envvar:: LANG
|
||||
|
||||
This is set only on macOS, and only if the country and language from the
|
||||
macOS user settings form a valid locale.
|
||||
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`.
|
||||
|
||||
.. envvar:: PATH
|
||||
|
||||
kitty prepends itself to the PATH of its own environment to ensure the
|
||||
functions calling :program:`kitty` will work properly.
|
||||
|
||||
.. envvar:: KITTY_WINDOW_ID
|
||||
|
||||
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>`.
|
||||
|
||||
|
||||
.. envvar:: KITTY_PID
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
|
||||
.. envvar:: TERM
|
||||
|
||||
The name of the terminal, defaults to ``xterm-kitty``. See :opt:`term`.
|
||||
|
||||
|
||||
.. envvar:: TERMINFO
|
||||
|
||||
Path to a directory containing the kitty terminfo database.
|
||||
|
||||
|
||||
.. envvar:: KITTY_INSTALLATION_DIR
|
||||
|
||||
Path to the kitty installation directory.
|
||||
|
||||
|
||||
.. envvar:: COLORTERM
|
||||
|
||||
Set to the value ``truecolor`` to indicate that kitty supports 16 million
|
||||
colors.
|
||||
|
||||
|
||||
.. envvar:: KITTY_LISTEN_ON
|
||||
|
||||
Set when the :doc:`remote control <remote-control>` facility is enabled and
|
||||
the a socket is used for control via :option:`kitty --listen-on` or :opt:`listen_on`.
|
||||
Contains the path to the socket. Avoids needs 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.
|
||||
|
||||
|
||||
.. envvar:: KITTY_PIPE_DATA
|
||||
|
||||
Set to data describing the layout of the screen when running child
|
||||
programs using :option:`launch --stdin-source` with the contents of the
|
||||
screen/scrollback piped to them.
|
||||
|
||||
|
||||
.. envvar:: KITTY_CHILD_CMDLINE
|
||||
|
||||
Set to the command line of the child process running in the kitty
|
||||
window when calling the notification callback program on terminal bell, see
|
||||
:opt:`command_on_bell`.
|
||||
|
||||
|
||||
.. envvar:: KITTY_COMMON_OPTS
|
||||
|
||||
Set with the values of some common kitty options when running
|
||||
kittens, so kittens can use them without needing to load kitty.conf.
|
||||
|
||||
kittens, so kittens can use them without needing to load :file:`kitty.conf`.
|
||||
|
||||
.. envvar:: KITTY_SHELL_INTEGRATION
|
||||
|
||||
Set when enabling :ref:`shell_integration`. It is automatically removed by
|
||||
the shell integration scripts.
|
||||
|
||||
.. envvar:: ZDOTDIR
|
||||
|
||||
Set when enabling :ref:`shell_integration` with :program:`zsh`, allowing
|
||||
:program:`zsh` to automatically load the integration script.
|
||||
|
||||
.. envvar:: XDG_DATA_DIRS
|
||||
|
||||
Set when enabling :ref:`shell_integration` with :program:`fish`, allowing
|
||||
:program:`fish` to automatically load the integration script.
|
||||
|
||||
.. envvar:: ENV
|
||||
|
||||
Set when enabling :ref:`shell_integration` with :program:`bash`, allowing
|
||||
:program:`bash` to automatically load the integration script.
|
||||
|
||||
@@ -31,6 +31,7 @@ Some programs and libraries that use the kitty graphics protocol:
|
||||
* `ranger <https://github.com/ranger/ranger>`_ - a terminal file manager, with
|
||||
image previews, see this `PR <https://github.com/ranger/ranger/pull/1077>`_
|
||||
* :doc:`kitty-diff <kittens/diff>` - a side-by-side terminal diff program with support for images
|
||||
* `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
|
||||
* `pixcat <https://github.com/mirukana/pixcat>`_ - a third party CLI and python library that wraps the graphics protocol
|
||||
* `neofetch <https://github.com/dylanaraps/neofetch>`_ - A command line system
|
||||
information tool
|
||||
@@ -42,10 +43,13 @@ Some programs and libraries that use the kitty graphics protocol:
|
||||
* `rasterm <https://github.com/BourgeoisBear/rasterm>`_ - Go library to display images in the terminal
|
||||
* `chafa <https://github.com/hpjansson/chafa>`_ - a terminal image viewer
|
||||
* `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
|
||||
|
||||
Other terminals that have implemented the graphics protocol:
|
||||
|
||||
* `WezTerm <https://github.com/wez/wezterm/issues/986>`_
|
||||
* `Konsole <https://invent.kde.org/utilities/konsole/-/merge_requests/594>`_
|
||||
* `wayst <https://github.com/91861/wayst>`_
|
||||
|
||||
|
||||
Getting the window size
|
||||
@@ -233,7 +237,8 @@ Value of `t` Meaning
|
||||
the terminal emulator should only delete the file if it
|
||||
is in a known temporary directory, such as :file:`/tmp`,
|
||||
: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
|
||||
`POSIX shared memory object <https://pubs.opengroup.org/onlinepubs/9699919799/functions/shm_open.html>`_
|
||||
and on Windows is a
|
||||
@@ -325,14 +330,14 @@ use the *query action*, set ``a=q``. Then the terminal emulator will try to load
|
||||
the image and respond with either OK or an error, as above, but it will not
|
||||
replace an existing image with the same id, nor will it store the image.
|
||||
|
||||
As of September 2021, kitty and WezTerm are the only terminal emulators to support this
|
||||
graphics protocol. We intend that any terminal emulator that wishes to support
|
||||
it can do so. To check if a terminal emulator supports the graphics protocol the best way
|
||||
is to send the above *query action* followed by a request for the
|
||||
`primary device attributes <https://vt100.net/docs/vt510-rm/DA1.html>`_. If you
|
||||
get back an answer for the device attributes without getting back an answer for
|
||||
the *query action* the terminal emulator does not support the graphics
|
||||
protocol.
|
||||
As of April 2022, kitty and WezTerm are the only terminal emulators to
|
||||
support this graphics protocol completely, with Konsole and wayst having partial support.
|
||||
We intend that any terminal emulator that wishes to support it can do so. To
|
||||
check if a terminal emulator supports the graphics protocol the best way is to
|
||||
send the above *query action* followed by a request for the `primary device
|
||||
attributes <https://vt100.net/docs/vt510-rm/DA1.html>`_. If you get back an
|
||||
answer for the device attributes without getting back an answer for the *query
|
||||
action* the terminal emulator does not support the graphics protocol.
|
||||
|
||||
This means that terminal emulators that support the graphics protocol, **must**
|
||||
reply to *query actions* immediately without processing other input. Most
|
||||
@@ -375,7 +380,9 @@ id. To do so add the ``p`` key with a number between ``1`` and ``4294967295``.
|
||||
When you specify a placement id, it will be added to the acknowledgement code
|
||||
above. Every placement is uniquely identified by the pair of the ``image id``
|
||||
and the ``placement id``. If you specify a placement id for an image that does
|
||||
not have an id, it will be ignored. An example response::
|
||||
not have an id (i.e. has id=0), it will be ignored. In particular this means
|
||||
there can exist multiple images with ``image id=0, placement id=0``.
|
||||
An example response::
|
||||
|
||||
<ESC>_Gi=<image id>,p=<placement id>;OK<ESC>\
|
||||
|
||||
@@ -412,7 +419,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
|
||||
allows rendering of text on top of images. Negative z-index values below
|
||||
INT32_MIN/2 (-1,073,741,824) will be drawn under cells with non-default background
|
||||
colors.
|
||||
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
|
||||
right by the number of cols in the image placement rectangle and down by the
|
||||
|
||||
@@ -1,261 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from __future__ import (
|
||||
absolute_import, division, print_function, unicode_literals
|
||||
)
|
||||
|
||||
import atexit
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
py3 = sys.version_info[0] > 2
|
||||
is64bit = platform.architecture()[0] == '64bit'
|
||||
is_macos = 'darwin' in sys.platform.lower()
|
||||
is_linux_arm = is_linux_arm64 = False
|
||||
if is_macos:
|
||||
mac_ver = tuple(map(int, platform.mac_ver()[0].split('.')))
|
||||
if mac_ver[:2] < (10, 12):
|
||||
raise SystemExit('Your version of macOS is too old, at least 10.12 is required')
|
||||
else:
|
||||
machine = (os.uname()[4] or '').lower()
|
||||
if machine.startswith('arm') or machine.startswith('aarch64'):
|
||||
is_linux_arm = True
|
||||
is_linux_arm64 = machine.startswith('arm64') or machine.startswith('aarch64')
|
||||
|
||||
try:
|
||||
__file__
|
||||
from_file = True
|
||||
except NameError:
|
||||
from_file = False
|
||||
|
||||
if py3:
|
||||
unicode = str
|
||||
raw_input = input
|
||||
import urllib.request as urllib
|
||||
|
||||
def encode_for_subprocess(x):
|
||||
return x
|
||||
else:
|
||||
from future_builtins import map
|
||||
import urllib2 as urllib
|
||||
|
||||
def encode_for_subprocess(x):
|
||||
if isinstance(x, unicode):
|
||||
x = x.encode('utf-8')
|
||||
return x
|
||||
|
||||
|
||||
def run(*args):
|
||||
if len(args) == 1:
|
||||
args = shlex.split(args[0])
|
||||
args = list(map(encode_for_subprocess, args))
|
||||
ret = subprocess.Popen(args).wait()
|
||||
if ret != 0:
|
||||
raise SystemExit(ret)
|
||||
|
||||
|
||||
class Reporter: # {{{
|
||||
|
||||
def __init__(self, fname):
|
||||
self.fname = fname
|
||||
self.last_percent = 0
|
||||
|
||||
def __call__(self, blocks, block_size, total_size):
|
||||
percent = (blocks*block_size)/float(total_size)
|
||||
report = '\rDownloaded {:.1%} '.format(percent)
|
||||
if percent - self.last_percent > 0.05:
|
||||
self.last_percent = percent
|
||||
print(report, end='')
|
||||
sys.stdout.flush()
|
||||
# }}}
|
||||
|
||||
|
||||
def get_release_data(relname='latest'):
|
||||
print('Checking for latest release on GitHub...')
|
||||
req = urllib.Request(
|
||||
'https://api.github.com/repos/kovidgoyal/kitty/releases/' + relname,
|
||||
headers={'Accept': 'application/vnd.github.v3+json'})
|
||||
try:
|
||||
res = urllib.urlopen(req).read().decode('utf-8')
|
||||
except Exception as err:
|
||||
raise SystemExit('Failed to contact {} with error: {}'.format(req.get_full_url(), err))
|
||||
data = json.loads(res)
|
||||
html_url = data['html_url'].replace('/tag/', '/download/').rstrip('/')
|
||||
for asset in data.get('assets', ()):
|
||||
name = asset['name']
|
||||
if is_macos:
|
||||
if name.endswith('.dmg'):
|
||||
return html_url + '/' + name, asset['size']
|
||||
else:
|
||||
if name.endswith('.txz'):
|
||||
if is64bit:
|
||||
q = '-arm64.txz' if is_linux_arm64 else '-x86_64.txz'
|
||||
if name.endswith(q):
|
||||
return html_url + '/' + name, asset['size']
|
||||
else:
|
||||
if name.endswith('-i686.txz'):
|
||||
return html_url + '/' + name, asset['size']
|
||||
raise SystemExit('Failed to find the installer package on github')
|
||||
|
||||
|
||||
def do_download(url, size, dest):
|
||||
print('Will download and install', os.path.basename(dest))
|
||||
reporter = Reporter(os.path.basename(dest))
|
||||
|
||||
# Get content length and check if range is supported
|
||||
rq = urllib.urlopen(url)
|
||||
headers = rq.info()
|
||||
sent_size = int(headers['content-length'])
|
||||
if sent_size != size:
|
||||
raise SystemExit('Failed to download from {} Content-Length ({}) != {}'.format(url, sent_size, size))
|
||||
with open(dest, 'wb') as f:
|
||||
while f.tell() < size:
|
||||
raw = rq.read(8192)
|
||||
if not raw:
|
||||
break
|
||||
f.write(raw)
|
||||
reporter(f.tell(), 1, size)
|
||||
rq.close()
|
||||
if os.path.getsize(dest) < size:
|
||||
raise SystemExit('Download failed, try again later')
|
||||
print('\rDownloaded {} bytes'.format(os.path.getsize(dest)))
|
||||
|
||||
|
||||
def clean_cache(cache, fname):
|
||||
for x in os.listdir(cache):
|
||||
if fname not in x:
|
||||
os.remove(os.path.join(cache, x))
|
||||
|
||||
|
||||
def download_installer(url, size):
|
||||
fname = url.rpartition('/')[-1]
|
||||
tdir = tempfile.gettempdir()
|
||||
cache = os.path.join(tdir, 'kitty-installer-cache')
|
||||
if not os.path.exists(cache):
|
||||
os.makedirs(cache)
|
||||
clean_cache(cache, fname)
|
||||
dest = os.path.join(cache, fname)
|
||||
if os.path.exists(dest) and os.path.getsize(dest) == size:
|
||||
print('Using previously downloaded', fname)
|
||||
return dest
|
||||
if os.path.exists(dest):
|
||||
os.remove(dest)
|
||||
do_download(url, size, dest)
|
||||
return dest
|
||||
|
||||
|
||||
def macos_install(dmg, dest='/Applications', launch=True):
|
||||
mp = tempfile.mkdtemp()
|
||||
atexit.register(shutil.rmtree, mp)
|
||||
run('hdiutil', 'attach', dmg, '-mountpoint', mp)
|
||||
try:
|
||||
os.chdir(mp)
|
||||
app = 'kitty.app'
|
||||
d = os.path.join(dest, app)
|
||||
if os.path.exists(d):
|
||||
shutil.rmtree(d)
|
||||
dest = os.path.join(dest, app)
|
||||
run('ditto', '-v', app, dest)
|
||||
print('Successfully installed kitty into', dest)
|
||||
if launch:
|
||||
run('open', dest)
|
||||
finally:
|
||||
os.chdir('/')
|
||||
run('hdiutil', 'detach', mp)
|
||||
|
||||
|
||||
def linux_install(installer, dest=os.path.expanduser('~/.local'), launch=True):
|
||||
dest = os.path.join(dest, 'kitty.app')
|
||||
if os.path.exists(dest):
|
||||
shutil.rmtree(dest)
|
||||
os.makedirs(dest)
|
||||
print('Extracting tarball...')
|
||||
run('tar', '-C', dest, '-xJof', installer)
|
||||
print('kitty successfully installed to', dest)
|
||||
kitty = os.path.join(dest, 'bin', 'kitty')
|
||||
print('Use', kitty, 'to run kitty')
|
||||
if launch:
|
||||
run(kitty, '--detach')
|
||||
|
||||
|
||||
def main(dest=None, launch=True, installer=None):
|
||||
if not dest:
|
||||
if is_macos:
|
||||
dest = '/Applications'
|
||||
else:
|
||||
dest = os.path.expanduser('~/.local')
|
||||
if is_linux_arm and not is_linux_arm64:
|
||||
raise SystemExit(
|
||||
'You are running on a 32-bit ARM system. The kitty binaries are only'
|
||||
' available for 64 bit ARM systems. You will have to build from'
|
||||
' source.')
|
||||
if not installer:
|
||||
url, size = get_release_data()
|
||||
installer = download_installer(url, size)
|
||||
else:
|
||||
if installer == 'nightly':
|
||||
url, size = get_release_data('tags/nightly')
|
||||
installer = download_installer(url, size)
|
||||
else:
|
||||
installer = os.path.abspath(installer)
|
||||
if not os.access(installer, os.R_OK):
|
||||
raise SystemExit('Could not read from: {}'.format(installer))
|
||||
if is_macos:
|
||||
macos_install(installer, dest=dest, launch=launch)
|
||||
else:
|
||||
linux_install(installer, dest=dest, launch=launch)
|
||||
|
||||
|
||||
def script_launch():
|
||||
# To test: python3 -c "import runpy; runpy.run_path('installer.py', run_name='script_launch')"
|
||||
def path(x):
|
||||
return os.path.expandvars(os.path.expanduser(x))
|
||||
|
||||
def to_bool(x):
|
||||
return x.lower() in {'y', 'yes', '1', 'true'}
|
||||
|
||||
type_map = {x: path for x in 'dest installer'.split()}
|
||||
type_map['launch'] = to_bool
|
||||
kwargs = {}
|
||||
|
||||
for arg in sys.argv[1:]:
|
||||
if arg:
|
||||
m = re.match('([a-z_]+)=(.+)', arg)
|
||||
if m is None:
|
||||
raise SystemExit('Unrecognized command line argument: ' + arg)
|
||||
k = m.group(1)
|
||||
if k not in type_map:
|
||||
raise SystemExit('Unrecognized command line argument: ' + arg)
|
||||
kwargs[k] = type_map[k](m.group(2))
|
||||
main(**kwargs)
|
||||
|
||||
|
||||
def update_installer_wrapper():
|
||||
# To run: python3 -c "import runpy; runpy.run_path('installer.py', run_name='update_wrapper')" installer.sh
|
||||
with open(__file__, 'rb') as f:
|
||||
src = f.read().decode('utf-8')
|
||||
wrapper = sys.argv[-1]
|
||||
with open(wrapper, 'r+b') as f:
|
||||
raw = f.read().decode('utf-8')
|
||||
nraw = re.sub(r'^# HEREDOC_START.+^# HEREDOC_END', lambda m: '# HEREDOC_START\n{}\n# HEREDOC_END'.format(src), raw, flags=re.MULTILINE | re.DOTALL)
|
||||
if 'update_intaller_wrapper()' not in nraw:
|
||||
raise SystemExit('regex substitute of HEREDOC failed')
|
||||
f.seek(0), f.truncate()
|
||||
f.write(nraw.encode('utf-8'))
|
||||
|
||||
|
||||
if __name__ == '__main__' and from_file:
|
||||
main()
|
||||
elif __name__ == 'update_wrapper':
|
||||
update_installer_wrapper()
|
||||
elif __name__ == 'script_launch':
|
||||
script_launch()
|
||||
@@ -1,292 +1,173 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# installer.sh
|
||||
# Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
#
|
||||
# Distributed under terms of the GPLv3 license.
|
||||
#
|
||||
|
||||
python=$(command -v python3)
|
||||
if [ -z "$python" ]; then
|
||||
python=$(command -v python2)
|
||||
fi
|
||||
if [ -z "$python" ]; then
|
||||
python=$(command -v python2.7)
|
||||
fi
|
||||
if [ -z "$python" ]; then
|
||||
python=$(command -v python)
|
||||
fi
|
||||
if [ -z "$python" ]; then
|
||||
python=python
|
||||
fi
|
||||
{ \unalias command; \unset -f command; } >/dev/null 2>&1
|
||||
tdir=''
|
||||
cleanup() {
|
||||
[ -n "$tdir" ] && {
|
||||
command rm -rf "$tdir"
|
||||
tdir=''
|
||||
}
|
||||
}
|
||||
|
||||
echo Using python executable: $python
|
||||
die() {
|
||||
cleanup
|
||||
printf "\033[31m%s\033[m\n\r" "$*" > /dev/stderr;
|
||||
exit 1;
|
||||
}
|
||||
|
||||
$python -c "import sys; script_launch=lambda:sys.exit('Download of installer failed!'); exec(sys.stdin.read()); script_launch()" "$@" <<'INSTALLER_HEREDOC'
|
||||
# {{{
|
||||
# HEREDOC_START
|
||||
#!/usr/bin/env python3
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from __future__ import (
|
||||
absolute_import, division, print_function, unicode_literals
|
||||
)
|
||||
|
||||
import atexit
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
py3 = sys.version_info[0] > 2
|
||||
is64bit = platform.architecture()[0] == '64bit'
|
||||
is_macos = 'darwin' in sys.platform.lower()
|
||||
is_linux_arm = is_linux_arm64 = False
|
||||
if is_macos:
|
||||
mac_ver = tuple(map(int, platform.mac_ver()[0].split('.')))
|
||||
if mac_ver[:2] < (10, 12):
|
||||
raise SystemExit('Your version of macOS is too old, at least 10.12 is required')
|
||||
else:
|
||||
machine = (os.uname()[4] or '').lower()
|
||||
if machine.startswith('arm') or machine.startswith('aarch64'):
|
||||
is_linux_arm = True
|
||||
is_linux_arm64 = machine.startswith('arm64') or machine.startswith('aarch64')
|
||||
|
||||
try:
|
||||
__file__
|
||||
from_file = True
|
||||
except NameError:
|
||||
from_file = False
|
||||
|
||||
if py3:
|
||||
unicode = str
|
||||
raw_input = input
|
||||
import urllib.request as urllib
|
||||
|
||||
def encode_for_subprocess(x):
|
||||
return x
|
||||
else:
|
||||
from future_builtins import map
|
||||
import urllib2 as urllib
|
||||
|
||||
def encode_for_subprocess(x):
|
||||
if isinstance(x, unicode):
|
||||
x = x.encode('utf-8')
|
||||
return x
|
||||
detect_network_tool() {
|
||||
if command -v curl 2> /dev/null > /dev/null; then
|
||||
fetch() {
|
||||
command curl -fL "$1"
|
||||
}
|
||||
fetch_quiet() {
|
||||
command curl -fsSL "$1"
|
||||
}
|
||||
elif command -v wget 2> /dev/null > /dev/null; then
|
||||
fetch() {
|
||||
command wget -O- "$1"
|
||||
}
|
||||
fetch_quiet() {
|
||||
command wget --quiet -O- "$1"
|
||||
}
|
||||
else
|
||||
die "Neither curl nor wget available, cannot download kitty"
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
def run(*args):
|
||||
if len(args) == 1:
|
||||
args = shlex.split(args[0])
|
||||
args = list(map(encode_for_subprocess, args))
|
||||
ret = subprocess.Popen(args).wait()
|
||||
if ret != 0:
|
||||
raise SystemExit(ret)
|
||||
detect_os() {
|
||||
arch=""
|
||||
case "$(command uname)" in
|
||||
'Darwin') OS="macos";;
|
||||
'Linux')
|
||||
OS="linux"
|
||||
case "$(command uname -m)" in
|
||||
x86_64) arch="x86_64";;
|
||||
aarch64*) arch="arm64";;
|
||||
armv8*) arch="arm64";;
|
||||
i386) arch="i686";;
|
||||
i686) arch="i686";;
|
||||
*) die "Unknown CPU architecture $(command uname -m)";;
|
||||
esac
|
||||
;;
|
||||
*) die "kitty binaries are not available for $(command uname)"
|
||||
esac
|
||||
}
|
||||
|
||||
expand_tilde() {
|
||||
tilde_less="${1#\~/}"
|
||||
[ "$1" != "$tilde_less" ] && tilde_less="$HOME/$tilde_less"
|
||||
printf '%s' "$tilde_less"
|
||||
}
|
||||
|
||||
parse_args() {
|
||||
dest='~/.local'
|
||||
[ "$OS" = "macos" ] && dest="/Applications"
|
||||
launch='y'
|
||||
installer=''
|
||||
while :; do
|
||||
case "$1" in
|
||||
dest=*) dest="${1#*=}";;
|
||||
launch=*) launch="${1#*=}";;
|
||||
installer=*) installer="${1#*=}";;
|
||||
"") break;;
|
||||
*) die "Unrecognized command line option: $1";;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
dest=$(expand_tilde "${dest}")
|
||||
[ "$launch" != "y" -a "$launch" != "n" ] && die "Unrecognized command line option: launch=$launch"
|
||||
dest="$dest/kitty.app"
|
||||
}
|
||||
|
||||
|
||||
class Reporter: # {{{
|
||||
get_file_url() {
|
||||
url="https://github.com/kovidgoyal/kitty/releases/download/$1/kitty-$2"
|
||||
if [ "$OS" = "macos" ]; then
|
||||
url="$url.dmg"
|
||||
else
|
||||
url="$url-$arch.txz"
|
||||
fi
|
||||
}
|
||||
|
||||
def __init__(self, fname):
|
||||
self.fname = fname
|
||||
self.last_percent = 0
|
||||
|
||||
def __call__(self, blocks, block_size, total_size):
|
||||
percent = (blocks*block_size)/float(total_size)
|
||||
report = '\rDownloaded {:.1%} '.format(percent)
|
||||
if percent - self.last_percent > 0.05:
|
||||
self.last_percent = percent
|
||||
print(report, end='')
|
||||
sys.stdout.flush()
|
||||
# }}}
|
||||
get_release_url() {
|
||||
release_version=$(fetch_quiet "https://sw.kovidgoyal.net/kitty/current-version.txt")
|
||||
[ $? -ne 0 -o -z "$release_version" ] && die "Could not get kitty latest release version"
|
||||
get_file_url "v$release_version" "$release_version"
|
||||
}
|
||||
|
||||
|
||||
def get_release_data(relname='latest'):
|
||||
print('Checking for latest release on GitHub...')
|
||||
req = urllib.Request(
|
||||
'https://api.github.com/repos/kovidgoyal/kitty/releases/' + relname,
|
||||
headers={'Accept': 'application/vnd.github.v3+json'})
|
||||
try:
|
||||
res = urllib.urlopen(req).read().decode('utf-8')
|
||||
except Exception as err:
|
||||
raise SystemExit('Failed to contact {} with error: {}'.format(req.get_full_url(), err))
|
||||
data = json.loads(res)
|
||||
html_url = data['html_url'].replace('/tag/', '/download/').rstrip('/')
|
||||
for asset in data.get('assets', ()):
|
||||
name = asset['name']
|
||||
if is_macos:
|
||||
if name.endswith('.dmg'):
|
||||
return html_url + '/' + name, asset['size']
|
||||
else:
|
||||
if name.endswith('.txz'):
|
||||
if is64bit:
|
||||
q = '-arm64.txz' if is_linux_arm64 else '-x86_64.txz'
|
||||
if name.endswith(q):
|
||||
return html_url + '/' + name, asset['size']
|
||||
else:
|
||||
if name.endswith('-i686.txz'):
|
||||
return html_url + '/' + name, asset['size']
|
||||
raise SystemExit('Failed to find the installer package on github')
|
||||
get_nightly_url() {
|
||||
get_file_url "nightly" "nightly"
|
||||
}
|
||||
|
||||
get_download_url() {
|
||||
installer_is_file="n"
|
||||
case "$installer" in
|
||||
"nightly") get_nightly_url ;;
|
||||
"") get_release_url ;;
|
||||
*) installer_is_file="y" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
def do_download(url, size, dest):
|
||||
print('Will download and install', os.path.basename(dest))
|
||||
reporter = Reporter(os.path.basename(dest))
|
||||
linux_install() {
|
||||
if [ "$installer_is_file" = "y" ]; then
|
||||
command tar -C "$dest" "-xJof" "$installer"
|
||||
else
|
||||
printf '%s\n\n' "Downloading from: $url"
|
||||
fetch "$url" | command tar -C "$dest" "-xJof" "-"
|
||||
fi
|
||||
}
|
||||
|
||||
# Get content length and check if range is supported
|
||||
rq = urllib.urlopen(url)
|
||||
headers = rq.info()
|
||||
sent_size = int(headers['content-length'])
|
||||
if sent_size != size:
|
||||
raise SystemExit('Failed to download from {} Content-Length ({}) != {}'.format(url, sent_size, size))
|
||||
with open(dest, 'wb') as f:
|
||||
while f.tell() < size:
|
||||
raw = rq.read(8192)
|
||||
if not raw:
|
||||
break
|
||||
f.write(raw)
|
||||
reporter(f.tell(), 1, size)
|
||||
rq.close()
|
||||
if os.path.getsize(dest) < size:
|
||||
raise SystemExit('Download failed, try again later')
|
||||
print('\rDownloaded {} bytes'.format(os.path.getsize(dest)))
|
||||
macos_install() {
|
||||
tdir=$(command mktemp -d "/tmp/kitty-install-XXXXXXXXXXXX")
|
||||
[ "$installer_is_file" != "y" ] && {
|
||||
installer="$tdir/kitty.dmg"
|
||||
printf '%s\n\n' "Downloading from: $url"
|
||||
fetch "$url" > "$installer" || die "Failed to download: $url"
|
||||
}
|
||||
command mkdir "$tdir/mp"
|
||||
command hdiutil attach "$installer" "-mountpoint" "$tdir/mp" || die "Failed to mount kitty.dmg"
|
||||
command ditto -v "$tdir/mp/kitty.app" "$dest"
|
||||
rc="$?"
|
||||
command hdiutil detach "$tdir/mp"
|
||||
command rm -rf "$tdir"
|
||||
tdir=''
|
||||
[ "$rc" != "0" ] && die "Failed to copy kitty.app from mounted dmg"
|
||||
}
|
||||
|
||||
prepare_install_dest() {
|
||||
printf "%s\n" "Installing to $dest"
|
||||
command rm -rf "$dest"
|
||||
command mkdir -p "$dest" || die "Failed to create the directory: $dest"
|
||||
}
|
||||
|
||||
def clean_cache(cache, fname):
|
||||
for x in os.listdir(cache):
|
||||
if fname not in x:
|
||||
os.remove(os.path.join(cache, x))
|
||||
exec_kitty() {
|
||||
if [ "$OS" = "macos" ]; then
|
||||
exec "open" "$dest"
|
||||
else
|
||||
exec "$dest/bin/kitty" "--detach"
|
||||
fi
|
||||
die "Failed to launch kitty"
|
||||
}
|
||||
|
||||
main() {
|
||||
detect_os
|
||||
parse_args "$@"
|
||||
detect_network_tool
|
||||
get_download_url
|
||||
prepare_install_dest
|
||||
if [ "$OS" = "macos" ]; then
|
||||
macos_install
|
||||
else
|
||||
linux_install
|
||||
fi
|
||||
[ "$launch" = "y" ] && exec_kitty
|
||||
exit 0
|
||||
}
|
||||
|
||||
def download_installer(url, size):
|
||||
fname = url.rpartition('/')[-1]
|
||||
tdir = tempfile.gettempdir()
|
||||
cache = os.path.join(tdir, 'kitty-installer-cache')
|
||||
if not os.path.exists(cache):
|
||||
os.makedirs(cache)
|
||||
clean_cache(cache, fname)
|
||||
dest = os.path.join(cache, fname)
|
||||
if os.path.exists(dest) and os.path.getsize(dest) == size:
|
||||
print('Using previously downloaded', fname)
|
||||
return dest
|
||||
if os.path.exists(dest):
|
||||
os.remove(dest)
|
||||
do_download(url, size, dest)
|
||||
return dest
|
||||
|
||||
|
||||
def macos_install(dmg, dest='/Applications', launch=True):
|
||||
mp = tempfile.mkdtemp()
|
||||
atexit.register(shutil.rmtree, mp)
|
||||
run('hdiutil', 'attach', dmg, '-mountpoint', mp)
|
||||
try:
|
||||
os.chdir(mp)
|
||||
app = 'kitty.app'
|
||||
d = os.path.join(dest, app)
|
||||
if os.path.exists(d):
|
||||
shutil.rmtree(d)
|
||||
dest = os.path.join(dest, app)
|
||||
run('ditto', '-v', app, dest)
|
||||
print('Successfully installed kitty into', dest)
|
||||
if launch:
|
||||
run('open', dest)
|
||||
finally:
|
||||
os.chdir('/')
|
||||
run('hdiutil', 'detach', mp)
|
||||
|
||||
|
||||
def linux_install(installer, dest=os.path.expanduser('~/.local'), launch=True):
|
||||
dest = os.path.join(dest, 'kitty.app')
|
||||
if os.path.exists(dest):
|
||||
shutil.rmtree(dest)
|
||||
os.makedirs(dest)
|
||||
print('Extracting tarball...')
|
||||
run('tar', '-C', dest, '-xJof', installer)
|
||||
print('kitty successfully installed to', dest)
|
||||
kitty = os.path.join(dest, 'bin', 'kitty')
|
||||
print('Use', kitty, 'to run kitty')
|
||||
if launch:
|
||||
run(kitty, '--detach')
|
||||
|
||||
|
||||
def main(dest=None, launch=True, installer=None):
|
||||
if not dest:
|
||||
if is_macos:
|
||||
dest = '/Applications'
|
||||
else:
|
||||
dest = os.path.expanduser('~/.local')
|
||||
if is_linux_arm and not is_linux_arm64:
|
||||
raise SystemExit(
|
||||
'You are running on a 32-bit ARM system. The kitty binaries are only'
|
||||
' available for 64 bit ARM systems. You will have to build from'
|
||||
' source.')
|
||||
if not installer:
|
||||
url, size = get_release_data()
|
||||
installer = download_installer(url, size)
|
||||
else:
|
||||
if installer == 'nightly':
|
||||
url, size = get_release_data('tags/nightly')
|
||||
installer = download_installer(url, size)
|
||||
else:
|
||||
installer = os.path.abspath(installer)
|
||||
if not os.access(installer, os.R_OK):
|
||||
raise SystemExit('Could not read from: {}'.format(installer))
|
||||
if is_macos:
|
||||
macos_install(installer, dest=dest, launch=launch)
|
||||
else:
|
||||
linux_install(installer, dest=dest, launch=launch)
|
||||
|
||||
|
||||
def script_launch():
|
||||
# To test: python3 -c "import runpy; runpy.run_path('installer.py', run_name='script_launch')"
|
||||
def path(x):
|
||||
return os.path.expandvars(os.path.expanduser(x))
|
||||
|
||||
def to_bool(x):
|
||||
return x.lower() in {'y', 'yes', '1', 'true'}
|
||||
|
||||
type_map = {x: path for x in 'dest installer'.split()}
|
||||
type_map['launch'] = to_bool
|
||||
kwargs = {}
|
||||
|
||||
for arg in sys.argv[1:]:
|
||||
if arg:
|
||||
m = re.match('([a-z_]+)=(.+)', arg)
|
||||
if m is None:
|
||||
raise SystemExit('Unrecognized command line argument: ' + arg)
|
||||
k = m.group(1)
|
||||
if k not in type_map:
|
||||
raise SystemExit('Unrecognized command line argument: ' + arg)
|
||||
kwargs[k] = type_map[k](m.group(2))
|
||||
main(**kwargs)
|
||||
|
||||
|
||||
def update_installer_wrapper():
|
||||
# To run: python3 -c "import runpy; runpy.run_path('installer.py', run_name='update_wrapper')" installer.sh
|
||||
with open(__file__, 'rb') as f:
|
||||
src = f.read().decode('utf-8')
|
||||
wrapper = sys.argv[-1]
|
||||
with open(wrapper, 'r+b') as f:
|
||||
raw = f.read().decode('utf-8')
|
||||
nraw = re.sub(r'^# HEREDOC_START.+^# HEREDOC_END', lambda m: '# HEREDOC_START\n{}\n# HEREDOC_END'.format(src), raw, flags=re.MULTILINE | re.DOTALL)
|
||||
if 'update_intaller_wrapper()' not in nraw:
|
||||
raise SystemExit('regex substitute of HEREDOC failed')
|
||||
f.seek(0), f.truncate()
|
||||
f.write(nraw.encode('utf-8'))
|
||||
|
||||
|
||||
if __name__ == '__main__' and from_file:
|
||||
main()
|
||||
elif __name__ == 'update_wrapper':
|
||||
update_installer_wrapper()
|
||||
elif __name__ == 'script_launch':
|
||||
script_launch()
|
||||
|
||||
# HEREDOC_END
|
||||
# }}}
|
||||
INSTALLER_HEREDOC
|
||||
main "$@"
|
||||
|
||||
@@ -4,8 +4,8 @@ Integrations with other tools
|
||||
================================
|
||||
|
||||
kitty provides extremely powerful interfaces such as :doc:`remote-control` and
|
||||
:doc:`kittens/custom` and :doc:`kittens/icat`
|
||||
that allow it to be integrated with other tools seamlessly.
|
||||
:doc:`kittens/custom` and :doc:`kittens/icat` that allow it to be integrated
|
||||
with other tools seamlessly.
|
||||
|
||||
|
||||
Image and document viewers
|
||||
@@ -30,19 +30,28 @@ Display markdown files nicely formatted with images in the terminal
|
||||
|
||||
`ranger <https://github.com/ranger/ranger>`_
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
A terminal file manager, with previews of file contents powered by kitty's graphics protocol.
|
||||
A terminal file manager, with previews of file contents powered by kitty's
|
||||
graphics protocol.
|
||||
|
||||
.. _tool_nnn:
|
||||
|
||||
`nnn <https://github.com/jarun/nnn/>`_
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Another terminal file manager, with previews of file contents powered by kitty's graphics protocol.
|
||||
Another terminal file manager, with previews of file contents powered by kitty's
|
||||
graphics protocol.
|
||||
|
||||
.. _tool_hunter:
|
||||
|
||||
`hunter <https://github.com/rabite0/hunter>`_
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Another terminal file manager, with previews of file contents powered by kitty's graphics protocol.
|
||||
Another terminal file manager, with previews of file contents powered by kitty's
|
||||
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:
|
||||
|
||||
@@ -61,8 +70,8 @@ View images in the terminal, similar to kitty's icat.
|
||||
|
||||
`nb <https://github.com/xwmx/nb>`_
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
command line and local web note-taking, bookmarking, archiving, and
|
||||
knowledge base application that uses kitty's graphics protocol for images.
|
||||
Command line and local web note-taking, bookmarking, archiving, and knowledge
|
||||
base application that uses kitty's graphics protocol for images.
|
||||
|
||||
.. _tool_w3m:
|
||||
|
||||
@@ -75,9 +84,9 @@ images.
|
||||
|
||||
`timg <https://github.com/hzeller/timg>`_
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
A terminal image and video viewer, that displays static and animated
|
||||
images or plays videos. Fast multi-threaded loading, JPEG exif rotation,
|
||||
grid view and connecting to the webcam make it a versatile terminal utility.
|
||||
A terminal image and video viewer, that displays static and animated images or
|
||||
plays videos. Fast multi-threaded loading, JPEG exif rotation, grid view and
|
||||
connecting to the webcam make it a versatile terminal utility.
|
||||
|
||||
|
||||
System and data visualisation tools
|
||||
@@ -87,7 +96,8 @@ System and data visualisation tools
|
||||
|
||||
`neofetch <https://github.com/dylanaraps/neofetch>`_
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
A command line system information tool that shows images using kitty's graphics protocol
|
||||
A command line system information tool that shows images using kitty's graphics
|
||||
protocol
|
||||
|
||||
.. _tool_matplotlib:
|
||||
|
||||
@@ -105,16 +115,16 @@ Show images from Julia directly in kitty
|
||||
|
||||
`euporie <https://github.com/joouha/euporie>`_
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
A text-based user interface for running and editing Jupyter notebooks,
|
||||
powered by kitty's graphics protocol for displaying plots
|
||||
A text-based user interface for running and editing Jupyter notebooks, powered
|
||||
by kitty's graphics protocol for displaying plots
|
||||
|
||||
.. _tool_gnuplot:
|
||||
|
||||
`gnuplot <http://www.gnuplot.info/>`_
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
A graphing and data visualization tool that can be made to display its
|
||||
output in kitty with the following bash snippet:
|
||||
A graphing and data visualization tool that can be made to display its output in
|
||||
kitty with the following bash snippet:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
@@ -171,26 +181,28 @@ Run applications on your system with fuzzy find inside a kitty window
|
||||
Editor integration
|
||||
-----------------------
|
||||
|
||||
|kitty| can be integrated into many different terminal editors to add features
|
||||
such a split windows, previews, REPLs etc.
|
||||
|kitty| can be integrated into many different terminal based text editors to add
|
||||
features such a split windows, previews, REPLs etc.
|
||||
|
||||
.. tool_kakoune:
|
||||
|
||||
`kakoune <https://kakoune.org/>`_
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
integrates with kitty to use native kitty windows for its windows/panels and REPLs.
|
||||
Integrates with kitty to use native kitty windows for its windows/panels and
|
||||
REPLs.
|
||||
|
||||
.. tool_vim_slime:
|
||||
|
||||
`vim-slime <https://github.com/jpalardy/vim-slime#kitty>`_
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
uses kitty remote control for a Lisp REPL.
|
||||
Uses kitty remote control for a Lisp REPL.
|
||||
|
||||
.. tool_vim_kitty_navigator:
|
||||
|
||||
`vim-kitty-navigator <https://github.com/knubie/vim-kitty-navigator>`_
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
allows you to navigate seamlessly between vim and kitty splits using a consistent set of hotkeys.
|
||||
Allows you to navigate seamlessly between vim and kitty splits using a
|
||||
consistent set of hotkeys.
|
||||
|
||||
.. tool_vim_test:
|
||||
|
||||
@@ -202,7 +214,7 @@ Allows easily running tests in a terminal window
|
||||
|
||||
`hologram.nvim <https://github.com/edluffy/hologram.nvim>`_
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Terminal image viewer for nvim
|
||||
Terminal image viewer for Neovim
|
||||
|
||||
|
||||
Scrollback manipulation
|
||||
@@ -228,21 +240,21 @@ Miscellaneous
|
||||
|
||||
`kitty-smart-tab <https://github.com/yurikhan/kitty-smart-tab>`_
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
use keys to either control tabs or pass them onto running applications if
|
||||
no tabs are present
|
||||
Use keys to either control tabs or pass them onto running applications if no
|
||||
tabs are present
|
||||
|
||||
.. tool_kitty_smart_scroll:
|
||||
|
||||
`kitty-smart-scroll <https://github.com/yurikhan/kitty-smart-scroll>`_
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
use keys to either scroll or pass them onto running applications if
|
||||
no scrollback buffer is present
|
||||
Use keys to either scroll or pass them onto running applications if no
|
||||
scrollback buffer is present
|
||||
|
||||
.. tool_kitti3:
|
||||
|
||||
`kitti3 <https://github.com/LandingEllipse/kitti3>`_
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
allow using kitty as a drop-down terminal under the i3 window manager
|
||||
Allow using kitty as a drop-down terminal under the i3 window manager
|
||||
|
||||
.. tool_weechat_hints:
|
||||
|
||||
|
||||
@@ -35,9 +35,14 @@ inside the kitty terminal to report key events.
|
||||
In addition to kitty, this protocol is also implemented in:
|
||||
|
||||
* 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
|
||||
<https://github.com/dankamongmen/notcurses/issues/2131>`__
|
||||
* The `crossterm library
|
||||
<https://github.com/crossterm-rs/crossterm/pull/688>`__
|
||||
* The `neovim text editor <https://github.com/neovim/neovim/pull/18181>`__
|
||||
* The `kakoune text editor <https://github.com/mawww/kakoune/issues/4103>`__
|
||||
* The `dte text editor <https://gitlab.com/craigbarnes/dte/-/issues/138>`__
|
||||
|
||||
.. versionadded:: 0.20.0
|
||||
|
||||
@@ -376,7 +381,7 @@ An application can query the terminal for support of this protocol by sending
|
||||
the escape code querying for the :ref:`current progressive enhancement
|
||||
<progressive_enhancement>` status
|
||||
followed by request for the `primary device attributes
|
||||
<https://vt100.net/docs/vt510-rm/DA1.html>`. If an answer for the device
|
||||
<https://vt100.net/docs/vt510-rm/DA1.html>`__. If an answer for the device
|
||||
attributes is received without getting back an answer for the progressive
|
||||
enhancement the terminal does not support this protocol.
|
||||
|
||||
|
||||
@@ -3,18 +3,21 @@ broadcast
|
||||
|
||||
*Type text in all kitty windows simultaneously*
|
||||
|
||||
The ``broadcast`` kitten can be used to type text simultaneously in
|
||||
all kitty windows (or a subset as desired).
|
||||
The ``broadcast`` kitten can be used to type text simultaneously in all
|
||||
:term:`kitty windows <window>` (or a subset as desired).
|
||||
|
||||
To use it, simply create a mapping in :file:`kitty.conf` such as::
|
||||
|
||||
map F1 launch --allow-remote-control kitty +kitten broadcast
|
||||
map f1 launch --allow-remote-control kitty +kitten broadcast
|
||||
|
||||
Then press the :kbd:`F1` key and whatever you type in the newly created widow
|
||||
Then press the :kbd:`F1` key and whatever you type in the newly created window
|
||||
will be sent to all kitty windows.
|
||||
|
||||
You can use the options described below to control which windows
|
||||
are selected.
|
||||
You can use the options described below to control which windows are selected.
|
||||
|
||||
For example, only broadcast to other windows in the current tab::
|
||||
|
||||
map f1 launch --allow-remote-control kitty +kitten broadcast --match-tab state:focused
|
||||
|
||||
.. program:: kitty +kitten broadcast
|
||||
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
Custom kittens
|
||||
=================
|
||||
|
||||
You can easily create your own kittens to extend kitty. They are just
|
||||
terminal programs written in Python. When launching a kitten, kitty will
|
||||
open an overlay window over the current window and optionally pass the
|
||||
contents of the current window/scrollback to the kitten over its :file:`STDIN`.
|
||||
The kitten can then perform whatever actions it likes, just as a normal
|
||||
terminal program. After execution of the kitten is complete, it has access
|
||||
to the running kitty instance so it can perform arbitrary actions
|
||||
such as closing windows, pasting text, etc.
|
||||
You can easily create your own kittens to extend kitty. They are just terminal
|
||||
programs written in Python. When launching a kitten, kitty will open an overlay
|
||||
window over the current window and optionally pass the contents of the current
|
||||
window/scrollback to the kitten over its :file:`STDIN`. The kitten can then
|
||||
perform whatever actions it likes, just as a normal terminal program. After
|
||||
execution of the kitten is complete, it has access to the running kitty instance
|
||||
so it can perform arbitrary actions such as closing windows, pasting text, etc.
|
||||
|
||||
Let's see a simple example of creating a kitten. It will ask the user for some
|
||||
input and paste it into the terminal window.
|
||||
|
||||
Create a file in the kitty config folder, :file:`~/.config/kitty/mykitten.py`
|
||||
(you might need to adjust the path to wherever the kitty config folder is on
|
||||
your machine).
|
||||
Create a file in the kitty config directory, :file:`~/.config/kitty/mykitten.py`
|
||||
(you might need to adjust the path to wherever the :ref:`kitty config directory
|
||||
<confloc>` is on your machine).
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
@@ -35,7 +34,7 @@ your machine).
|
||||
# get the kitty window into which to paste answer
|
||||
w = boss.window_id_map.get(target_window_id)
|
||||
if w is not None:
|
||||
w.paste(answer)
|
||||
w.paste_text(answer)
|
||||
|
||||
|
||||
Now in :file:`kitty.conf` add the lines::
|
||||
@@ -43,11 +42,30 @@ Now in :file:`kitty.conf` add the lines::
|
||||
map ctrl+k kitten mykitten.py
|
||||
|
||||
|
||||
Start kitty and press :kbd:`ctrl+k` and you should see the kitten running.
|
||||
The best way to develop your own kittens is to modify one of the built in
|
||||
kittens. Look in the kittens sub-directory of the kitty source code for those.
|
||||
Or see below for a list of :ref:`third-party kittens <external_kittens>`,
|
||||
that other kitty users have created.
|
||||
Start kitty and press :kbd:`Ctrl+K` and you should see the kitten running.
|
||||
The best way to develop your own kittens is to modify one of the built-in
|
||||
kittens. Look in the `kittens sub-directory
|
||||
<https://github.com/kovidgoyal/kitty/tree/master/kittens>`__ of the kitty source
|
||||
code for those. Or see below for a list of :ref:`third-party kittens
|
||||
<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
|
||||
@@ -60,36 +78,40 @@ You can pass arguments to kittens by defining them in the map directive in
|
||||
|
||||
These will be available as the ``args`` parameter in the ``main()`` and
|
||||
``handle_result()`` functions. Note also that the current working directory
|
||||
of the kitten is set to the working directory of whatever program is
|
||||
running in the active kitty window. The special argument ``@selection``
|
||||
is replaced by the currently selected text in the active kitty window.
|
||||
of the kitten is set to the working directory of whatever program is running in
|
||||
the active kitty window. The special argument ``@selection`` is replaced by the
|
||||
currently selected text in the active kitty window.
|
||||
|
||||
|
||||
Passing the contents of the screen to the kitten
|
||||
---------------------------------------------------
|
||||
|
||||
If you would like your kitten to have access to the contents of the screen
|
||||
and/or the scrollback buffer, you just need to add an annotation to the ``handle_result()``
|
||||
function, telling kitty what kind of input your kitten would like. For example:
|
||||
and/or the scrollback buffer, you just need to add an annotation to the
|
||||
``handle_result()`` function, telling kitty what kind of input your kitten would
|
||||
like. For example:
|
||||
|
||||
.. code-block:: py
|
||||
|
||||
from typing import List
|
||||
from kitty.boss import Boss
|
||||
|
||||
# in main, STDIN is for the kitten process and will contain
|
||||
# the contents of the screen
|
||||
def main(args):
|
||||
def main(args: List[str]) -> str:
|
||||
return sys.stdin.read()
|
||||
|
||||
# in handle_result, STDIN is for the kitty process itself, rather
|
||||
# than the kitten process and should not be read from.
|
||||
from kittens.tui.handler import result_handler
|
||||
@result_handler(type_of_input='text')
|
||||
def handle_result(args, stdin_data, target_window_id, boss):
|
||||
def handle_result(args: List[str], stdin_data: str, target_window_id: int, boss: Boss) -> None:
|
||||
pass
|
||||
|
||||
|
||||
This will send the plain text of the active window to the kitten's
|
||||
:file:`STDIN`. There are many other types of input you can ask for,
|
||||
described in the table below:
|
||||
:file:`STDIN`. There are many other types of input you can ask for, described in
|
||||
the table below:
|
||||
|
||||
.. table:: Types of input to kittens
|
||||
:align: left
|
||||
@@ -121,31 +143,35 @@ and ``first_output`` gives the output of the first command currently on screen.
|
||||
These can also be combined with ``screen`` and ``ansi`` for formatting.
|
||||
|
||||
.. note::
|
||||
For the types based on the output of a command,
|
||||
:ref:`shell_integration` is required.
|
||||
For the types based on the output of a command, :ref:`shell_integration` is
|
||||
required.
|
||||
|
||||
|
||||
Using kittens to script kitty, without any terminal UI
|
||||
-----------------------------------------------------------
|
||||
|
||||
If you would like your kitten to script kitty, without bothering to write a
|
||||
terminal program, you can tell the kittens system to run the
|
||||
``handle_result()`` function without first running the ``main()`` function.
|
||||
terminal program, you can tell the kittens system to run the ``handle_result()``
|
||||
function without first running the ``main()`` function.
|
||||
|
||||
For example, here is a kitten that "zooms/unzooms" the current terminal window
|
||||
by switching to the stack layout or back to the previous layout. This is
|
||||
equivalent to the builtin :ref:`action-toggle_layout` action.
|
||||
For example, here is a kitten that "zooms in/zooms out" the current terminal
|
||||
window by switching to the stack layout or back to the previous layout. This is
|
||||
equivalent to the builtin :ac:`toggle_layout` action.
|
||||
|
||||
Create a file in the kitty config folder, :file:`~/.config/kitty/zoom_toggle.py`
|
||||
Create a Python file in the :ref:`kitty config directory <confloc>`,
|
||||
:file:`~/.config/kitty/zoom_toggle.py`
|
||||
|
||||
.. code-block:: py
|
||||
|
||||
def main(args):
|
||||
from typing import List
|
||||
from kitty.boss import Boss
|
||||
|
||||
def main(args: List[str]) -> str:
|
||||
pass
|
||||
|
||||
from kittens.tui.handler import result_handler
|
||||
@result_handler(no_ui=True)
|
||||
def handle_result(args, answer, target_window_id, boss):
|
||||
def handle_result(args: List[str], answer: str, target_window_id: int, boss: Boss) -> None:
|
||||
tab = boss.active_tab
|
||||
if tab is not None:
|
||||
if tab.current_layout.name == 'stack':
|
||||
@@ -154,7 +180,7 @@ Create a file in the kitty config folder, :file:`~/.config/kitty/zoom_toggle.py`
|
||||
tab.goto_layout('stack')
|
||||
|
||||
|
||||
Now in kitty.conf add::
|
||||
Now in :file:`kitty.conf` add::
|
||||
|
||||
map f11 kitten zoom_toggle.py
|
||||
|
||||
@@ -165,7 +191,7 @@ layout, by simply adding the line::
|
||||
boss.toggle_fullscreen()
|
||||
|
||||
|
||||
To the ``handle_result()`` function, above.
|
||||
to the ``handle_result()`` function, above.
|
||||
|
||||
|
||||
.. _send_mouse_event:
|
||||
@@ -173,7 +199,7 @@ To the ``handle_result()`` function, above.
|
||||
Sending mouse events
|
||||
--------------------
|
||||
|
||||
If the program running in a window is receiving mouse events you can simulate
|
||||
If the program running in a window is receiving mouse events, you can simulate
|
||||
those using::
|
||||
|
||||
from kitty.fast_data_types import send_mouse_event
|
||||
@@ -200,15 +226,15 @@ that type, and will return ``True`` if it sent the event, and ``False`` if not.
|
||||
Debugging kittens
|
||||
--------------------
|
||||
|
||||
The part of the kitten that runs in ``main()`` is just a normal program and
|
||||
the output of print statements will be visible in the kitten window. Or
|
||||
alternately, you can use::
|
||||
The part of the kitten that runs in ``main()`` is just a normal program and the
|
||||
output of print statements will be visible in the kitten window. Or alternately,
|
||||
you can use::
|
||||
|
||||
from kittens.tui.loop import debug
|
||||
debug('whatever')
|
||||
|
||||
The ``debug()`` function is just like ``print()`` except that the output
|
||||
will appear in the ``STDOUT`` of the kitty process inside which the kitten is
|
||||
The ``debug()`` function is just like ``print()`` except that the output will
|
||||
appear in the ``STDOUT`` of the kitty process inside which the kitten is
|
||||
running.
|
||||
|
||||
The ``handle_result()`` part of the kitten runs inside the kitty process.
|
||||
@@ -216,12 +242,13 @@ The output of print statements will go to the ``STDOUT`` of the kitty process.
|
||||
So if you run kitty from another kitty instance, the output will be visible
|
||||
in the first kitty instance.
|
||||
|
||||
|
||||
Adding options to kittens
|
||||
----------------------------
|
||||
|
||||
If you would like to use kitty's config framework to make your kittens
|
||||
configurable, you will need some boilerplate. In the directory
|
||||
of your kitten make the following files.
|
||||
configurable, you will need some boilerplate. Put the following files in the
|
||||
directory of your kitten.
|
||||
|
||||
:file:`kitten_options_definition.py`
|
||||
|
||||
@@ -311,8 +338,9 @@ You can parse and read the options in your kitten using the following code:
|
||||
opts.config_overrides = overrides
|
||||
return opts
|
||||
|
||||
See the code for the builtin diff kitten for examples of creating more options
|
||||
and keyboard shortcuts.
|
||||
See `the code <https://github.com/kovidgoyal/kitty/tree/master/kittens/diff>`__
|
||||
for the builtin :doc:`diff kitten </kittens/diff>` for examples of creating more
|
||||
options and keyboard shortcuts.
|
||||
|
||||
.. _external_kittens:
|
||||
|
||||
@@ -320,7 +348,8 @@ Kittens created by kitty users
|
||||
---------------------------------------------
|
||||
|
||||
`vim-kitty-navigator <https://github.com/knubie/vim-kitty-navigator>`_
|
||||
Allows you to navigate seamlessly between vim and kitty splits using a consistent set of hotkeys.
|
||||
Allows you to navigate seamlessly between vim and kitty splits using a
|
||||
consistent set of hotkeys.
|
||||
|
||||
`smart-scroll <https://github.com/yurikhan/kitty-smart-scroll>`_
|
||||
Makes the kitty scroll bindings work in full screen applications
|
||||
|
||||
@@ -12,8 +12,8 @@ Major Features
|
||||
|
||||
* Displays diffs side-by-side in the kitty terminal
|
||||
|
||||
* Does syntax highlighting of the displayed diffs, asynchronously, for maximum
|
||||
speed
|
||||
* Does syntax highlighting of the displayed diffs, asynchronously, for
|
||||
maximum speed
|
||||
|
||||
* Displays images as well as text diffs, even over SSH
|
||||
|
||||
@@ -31,11 +31,11 @@ Major Features
|
||||
Installation
|
||||
---------------
|
||||
|
||||
Simply :ref:`install kitty <quickstart>`. You also need
|
||||
to have either the `git <https://git-scm.com/>`_ program or the ``diff`` program
|
||||
installed. Additionally, for syntax highlighting to work,
|
||||
`pygments <https://pygments.org/>`_ must be installed (note that pygments is
|
||||
included in the official kitty binary builds).
|
||||
Simply :ref:`install kitty <quickstart>`. You also need to have either the `git
|
||||
<https://git-scm.com/>`__ program or the :program:`diff` program installed.
|
||||
Additionally, for syntax highlighting to work, `pygments
|
||||
<https://pygments.org/>`__ must be installed (note that pygments is included in
|
||||
the official kitty binary builds).
|
||||
|
||||
|
||||
Usage
|
||||
@@ -45,9 +45,10 @@ In the kitty terminal, run::
|
||||
|
||||
kitty +kitten diff file1 file2
|
||||
|
||||
to see the diff between file1 and file2.
|
||||
to see the diff between :file:`file1` and :file:`file2`.
|
||||
|
||||
Create an alias in your shell's startup file to shorten the command, for example:
|
||||
Create an alias in your shell's startup file to shorten the command, for
|
||||
example:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
@@ -67,20 +68,20 @@ Keyboard controls
|
||||
========================= ===========================
|
||||
Action Shortcut
|
||||
========================= ===========================
|
||||
Quit :kbd:`q`, :kbd:`ctrl+c`, :kbd:`Esc`
|
||||
Scroll line up :kbd:`k`, :kbd:`Up`
|
||||
Scroll line down :kbd:`j`, :kbd:`Down`
|
||||
Quit :kbd:`Q`, :kbd:`Ctrl+C`, :kbd:`Esc`
|
||||
Scroll line up :kbd:`K`, :kbd:`Up`
|
||||
Scroll line down :kbd:`J`, :kbd:`Down`
|
||||
Scroll page up :kbd:`PgUp`
|
||||
Scroll page down :kbd:`PgDn`
|
||||
Scroll to top :kbd:`Home`
|
||||
Scroll to bottom :kbd:`End`
|
||||
Scroll to next page :kbd:`Space`, :kbd:`PgDn`
|
||||
Scroll to previous page :kbd:`PgUp`
|
||||
Scroll to next change :kbd:`n`
|
||||
Scroll to previous change :kbd:`p`
|
||||
Scroll to next change :kbd:`N`
|
||||
Scroll to previous change :kbd:`P`
|
||||
Increase lines of context :kbd:`+`
|
||||
Decrease lines of context :kbd:`-`
|
||||
All lines of context :kbd:`a`
|
||||
All lines of context :kbd:`A`
|
||||
Restore default context :kbd:`=`
|
||||
Search forwards :kbd:`/`
|
||||
Search backwards :kbd:`?`
|
||||
@@ -93,7 +94,7 @@ Scroll to previous match :kbd:`<`, :kbd:`,`
|
||||
Integrating with git
|
||||
-----------------------
|
||||
|
||||
Add the following to `~/.gitconfig`:
|
||||
Add the following to :file:`~/.gitconfig`:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
@@ -119,24 +120,23 @@ Why does this work only in kitty?
|
||||
----------------------------------------
|
||||
|
||||
The diff kitten makes use of various features that are :doc:`kitty only
|
||||
</protocol-extensions>`, such as the :doc:`kitty graphics protocol
|
||||
</protocol-extensions>`, such as the :doc:`kitty graphics protocol
|
||||
</graphics-protocol>`, the :doc:`extended keyboard protocol
|
||||
</keyboard-protocol>`, etc. It also leverages terminal program
|
||||
infrastructure I created for all of kitty's other kittens to reduce the amount
|
||||
of code needed (the entire implementation is under 2000 lines of code).
|
||||
</keyboard-protocol>`, etc. It also leverages terminal program infrastructure
|
||||
I created for all of kitty's other kittens to reduce the amount of code needed
|
||||
(the entire implementation is under 2000 lines of code).
|
||||
|
||||
And fundamentally, it's kitty only because I wrote it for myself, and I am
|
||||
highly unlikely to use any other terminals :)
|
||||
|
||||
|
||||
|
||||
Configuration
|
||||
------------------------
|
||||
|
||||
You can configure the colors used, keyboard shortcuts, the diff implementation,
|
||||
the default lines of context, etc. by creating a :file:`diff.conf` file in
|
||||
your :ref:`kitty config folder <confloc>`. See below for the supported
|
||||
configuration directives.
|
||||
the default lines of context, etc. by creating a :file:`diff.conf` file in your
|
||||
:ref:`kitty config folder <confloc>`. See below for the supported configuration
|
||||
directives.
|
||||
|
||||
|
||||
.. include:: /generated/conf-kitten-diff.rst
|
||||
@@ -145,7 +145,6 @@ configuration directives.
|
||||
.. include:: /generated/cli-kitten-diff.rst
|
||||
|
||||
|
||||
|
||||
Sample diff.conf
|
||||
-----------------
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
Hints
|
||||
==========
|
||||
|
||||
|kitty| has a *hints mode* to select and act on arbitrary text snippets currently
|
||||
visible on the screen. For example, you can press :sc:`open_url`
|
||||
to choose any URL visible on the screen and then open it using your system
|
||||
|kitty| has a *hints mode* to select and act on arbitrary text snippets
|
||||
currently visible on the screen. For example, you can press :sc:`open_url`
|
||||
to choose any URL visible on the screen and then open it using your default web
|
||||
browser.
|
||||
|
||||
.. figure:: ../screenshots/hints_mode.png
|
||||
@@ -13,25 +13,29 @@ browser.
|
||||
|
||||
URL hints mode
|
||||
|
||||
Similarly, you can press :sc:`insert_selected_path` to
|
||||
select anything that looks like a path or filename and then insert it into the
|
||||
terminal, very useful for picking files from the output of a ``git`` or ``ls`` command and
|
||||
adding them to the command line for the next command.
|
||||
Similarly, you can press :sc:`insert_selected_path` to select anything that
|
||||
looks like a path or filename and then insert it into the terminal, very useful
|
||||
for picking files from the output of a :program:`git` or :program:`ls` command
|
||||
and adding them to the command line for the next command.
|
||||
|
||||
You can also press :sc:`goto_file_line` to select anything that looks
|
||||
like a path or filename followed by a colon and a line number and open
|
||||
the file in vim at the specified line number. The patterns and editor
|
||||
to be used can be modified using options passed to the kitten. For example::
|
||||
You can also press :sc:`goto_file_line` to select anything that looks like a
|
||||
path or filename followed by a colon and a line number and open the file in
|
||||
:program:`vim` at the specified line number. The patterns and editor to be used
|
||||
can be modified using options passed to the kitten. For example::
|
||||
|
||||
map ctrl+g kitten hints --type=linenum --linenum-action=tab nvim +{line} {path}
|
||||
|
||||
will open the selected file in a new tab inside neovim when you press
|
||||
:kbd:`ctrl+g`.
|
||||
will open the selected file in a new tab inside `Neovim <https://neovim.io/>`__
|
||||
when you press :kbd:`Ctrl+G`.
|
||||
|
||||
Pressing :sc:`open_selected_hyperlink` will open hyperlinks, i.e. a URL
|
||||
Pressing :sc:`open_selected_hyperlink` will open :term:`hyperlinks`, i.e. a URL
|
||||
that has been marked as such by the program running in the terminal,
|
||||
for example, by ``ls --hyperlink=auto``. You can also :doc:`customize what actions are
|
||||
taken for different types of URLs <../open_actions>`.
|
||||
for example, by ``ls --hyperlink=auto``. If :program:`ls` comes with your OS
|
||||
does not support hyperlink, you may need to install `GNU Coreutils
|
||||
<https://www.gnu.org/software/coreutils/>`__.
|
||||
|
||||
You can also :doc:`customize what actions are taken for different types of URLs
|
||||
<../open_actions>`.
|
||||
|
||||
.. note:: If there are more hints than letters, hints will use multiple
|
||||
letters. In this case, when you press the first letter, only hints
|
||||
@@ -48,13 +52,12 @@ snippets. See :sc:`insert_selected_path <insert_selected_path>` for examples.
|
||||
Completely customizing the matching and actions of the kitten
|
||||
---------------------------------------------------------------
|
||||
|
||||
The hints kitten supports writing simple python scripts that can be used to
|
||||
The hints kitten supports writing simple Python scripts that can be used to
|
||||
completely customize how it finds matches and what happens when a match is
|
||||
selected. This allows the hints kitten to provide the user interface, while
|
||||
you can provide the logic for finding matches and performing actions on them.
|
||||
This is best illustrated with an example. Create the file
|
||||
:file:`custom-hints.py` in the kitty config directory with the following
|
||||
contents:
|
||||
selected. This allows the hints kitten to provide the user interface, while you
|
||||
can provide the logic for finding matches and performing actions on them. This
|
||||
is best illustrated with an example. Create the file :file:`custom-hints.py` in
|
||||
the :ref:`kitty config directory <confloc>` with the following contents:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@@ -98,9 +101,10 @@ look it up in the Google dictionary.
|
||||
|
||||
.. note::
|
||||
|
||||
To avoid having to specify the same command line options on every invocation,
|
||||
you can use the :opt:`action_alias` option in :file:`kitty.conf`, creating aliases
|
||||
that have common sets of options. For example::
|
||||
To avoid having to specify the same command line options on every
|
||||
invocation, you can use the :opt:`action_alias` option in
|
||||
:file:`kitty.conf`, creating aliases that have common sets of options.
|
||||
For example::
|
||||
|
||||
action_alias myhints kitten hints --alphabet qfjdkslaureitywovmcxzpq1234567890
|
||||
map f1 myhints --customize-processing custom-hints.py
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
Hyperlinked grep
|
||||
=================
|
||||
|
||||
|
||||
This kitten allows you to search your files using `ripgrep
|
||||
<https://github.com/BurntSushi/ripgrep>`_ and open the results
|
||||
directly in your favorite editor in the terminal, at the line containing
|
||||
the search result, simply by clicking on the result you want.
|
||||
<https://github.com/BurntSushi/ripgrep>`__ and open the results directly in your
|
||||
favorite editor in the terminal, at the line containing the search result,
|
||||
simply by clicking on the result you want.
|
||||
|
||||
.. versionadded:: 0.19.0
|
||||
|
||||
@@ -18,26 +17,26 @@ following contents:
|
||||
# by the hyperlink_grep kitten and nothing else so far.
|
||||
protocol file
|
||||
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
|
||||
protocol file
|
||||
mime text/*
|
||||
action launch --type=overlay ${EDITOR} ${FILE_PATH}
|
||||
|
||||
action launch --type=overlay --cwd=current ${EDITOR} ${FILE_PATH}
|
||||
|
||||
Now, run a search with::
|
||||
|
||||
kitty +kitten hyperlinked_grep something
|
||||
|
||||
Hold down the :kbd:`ctrl+shift` keys and click on any of the
|
||||
result lines, to open the file in vim at the matching line. If
|
||||
you use some editor other than vim, you should adjust the
|
||||
:file:`open-actions.conf` file accordingly.
|
||||
Hold down the :kbd:`Ctrl+Shift` keys and click on any of the result lines, to
|
||||
open the file in :program:`vim` at the matching line. If you use some editor
|
||||
other than :program:`vim`, you should adjust the :file:`open-actions.conf` file
|
||||
accordingly.
|
||||
|
||||
Finally, add an alias to your shell's rc files to invoke the kitten as ``hg``::
|
||||
Finally, add an alias to your shell's rc files to invoke the kitten as
|
||||
:command:`hg`::
|
||||
|
||||
alias hg='kitty +kitten hyperlinked_grep'
|
||||
alias hg="kitty +kitten hyperlinked_grep"
|
||||
|
||||
|
||||
You can now run searches with::
|
||||
@@ -45,13 +44,13 @@ You can now run searches with::
|
||||
hg some-search-term
|
||||
|
||||
If you want to enable completion, for the kitten, you can delegate completion
|
||||
to rg. How to do that varies based on the shell:
|
||||
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
|
||||
:file:`hg` somewhere in your ``PATH``:
|
||||
Instead of using an alias, create a simple wrapper script named
|
||||
:program:`hg` somewhere in your :envvar:`PATH`:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
@@ -64,22 +63,33 @@ to rg. How to do that varies based on the shell:
|
||||
|
||||
.. tab:: fish
|
||||
|
||||
You can combine both the aliasing/wrapping and pointing fish
|
||||
to rg's autocompletion with a fish "wrapper" function in your :file:`config.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:: sh
|
||||
.. 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
|
||||
actions, :doc:`see 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 :command:`--kitten hyperlink` flag. For example,
|
||||
:command:`--kitten hyperlink=matching_lines` will only add hyperlinks to the
|
||||
match lines. :command:`--kitten hyperlink=file_headers,context_lines` will link
|
||||
file headers and context lines but not match lines. :command:`--kitten
|
||||
hyperlink=none` will cause the command line to be passed to directly to
|
||||
:command:`rg` so no hyperlinking will be performed. :command:`--kitten
|
||||
hyperlink` may be specified multiple times.
|
||||
|
||||
Hopefully, someday this functionality will make it into some `upstream grep
|
||||
<https://github.com/BurntSushi/ripgrep/issues/665>`_
|
||||
program directly removing the need for this kitten.
|
||||
<https://github.com/BurntSushi/ripgrep/issues/665>`__ program directly removing
|
||||
the need for this kitten.
|
||||
|
||||
|
||||
.. note::
|
||||
While you can pass any of ripgrep's comand line options to the kitten and
|
||||
they will be forwarded to rg, do not use options that change the output
|
||||
formatting as the kitten works by parsing the output from ripgrep.
|
||||
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.
|
||||
|
||||
@@ -9,8 +9,8 @@ terminal. Using it is as simple as::
|
||||
kitty +kitten icat image.jpeg
|
||||
|
||||
It supports all image types supported by `ImageMagick
|
||||
<https://www.imagemagick.org>`_. It even works over SSH. For details, see
|
||||
the :doc:`kitty graphics protocol </graphics-protocol>`.
|
||||
<https://www.imagemagick.org>`__. It even works over SSH. For details, see the
|
||||
:doc:`kitty graphics protocol </graphics-protocol>`.
|
||||
|
||||
You might want to create an alias in your shell's configuration files::
|
||||
|
||||
@@ -20,14 +20,14 @@ Then you can simply use ``icat image.png`` to view images.
|
||||
|
||||
.. note::
|
||||
|
||||
`ImageMagick <https://www.imagemagick.org>`_ must be installed for ``icat`` to
|
||||
work.
|
||||
`ImageMagick <https://www.imagemagick.org>`__ must be installed for icat
|
||||
kitten to work.
|
||||
|
||||
.. note::
|
||||
|
||||
kitty's image display protocol may not work when used within a terminal
|
||||
multiplexer such as ``screen`` or ``tmux``, depending on whether the
|
||||
multiplexer has added support for it or not.
|
||||
multiplexer such as :program:`screen` or :program:`tmux`, depending on
|
||||
whether the multiplexer has added support for it or not.
|
||||
|
||||
|
||||
.. program:: kitty +kitten icat
|
||||
@@ -35,18 +35,19 @@ 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
|
||||
from inside other programs to display images. In particular, :option:`--place`,
|
||||
:option:`--detect-support`, :option:`--silent` and :option:`--print-window-size`.
|
||||
:option:`--detect-support`, :option:`--silent` and
|
||||
:option:`--print-window-size`.
|
||||
|
||||
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 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. 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
|
||||
:option:`--transfer-mode` command line arguments. To be
|
||||
really robust you should consider writing proper support for the
|
||||
:doc:`../graphics-protocol` in the program instead. Nowadays there are many
|
||||
libraries that have support for it.
|
||||
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
|
||||
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.
|
||||
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
|
||||
:option:`--transfer-mode` command line arguments. To be really robust you should
|
||||
consider writing proper support for the :doc:`kitty graphics protocol
|
||||
</graphics-protocol>` in the program instead. Nowadays there are many libraries
|
||||
that have support for it.
|
||||
|
||||
|
||||
.. include:: /generated/cli-kitten-icat.rst
|
||||
|
||||
@@ -4,8 +4,8 @@ Draw a GPU accelerated dock panel on your desktop
|
||||
.. highlight:: sh
|
||||
|
||||
|
||||
You can use this kitten to draw a GPU accelerated panel on the edge
|
||||
of your screen, that shows the output from an arbitrary terminal program.
|
||||
You can use this kitten to draw a GPU accelerated panel on the edge of your
|
||||
screen, that shows the output from an arbitrary terminal program.
|
||||
|
||||
It is useful for showing status information or notifications on your desktop
|
||||
using terminal programs instead of GUI toolkits.
|
||||
@@ -32,8 +32,8 @@ Using this kitten is simple, for example::
|
||||
kitty +kitten panel sh -c 'printf "\n\n\nHello, world."; sleep 5s'
|
||||
|
||||
This will show ``Hello, world.`` at the top edge of your screen for five
|
||||
seconds. Here the terminal program we are running is ``sh`` with a script to
|
||||
print out ``Hello, world!``. You can make the terminal program as complex as
|
||||
seconds. Here the terminal program we are running is :program:`sh` with a script
|
||||
to print out ``Hello, world!``. You can make the terminal program as complex as
|
||||
you like, as demonstrated in the screenshot above.
|
||||
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
Query terminal
|
||||
=================
|
||||
|
||||
Used to query kitty from terminal programs about version, values of various
|
||||
runtime options controlling its features, etc.
|
||||
This kitten is used to query |kitty| from terminal programs about version, values
|
||||
of various runtime options controlling its features, etc.
|
||||
|
||||
The querying is done using the (*semi*) standard XTGETTCAP escape sequence
|
||||
pioneered by XTerm, so it works over SSH as well. The downside is that it
|
||||
is slow, since it requires a roundtrip to the terminal emulator and back.
|
||||
pioneered by xterm, so it works over SSH as well. The downside is that it is
|
||||
slow, since it requires a roundtrip to the terminal emulator and back.
|
||||
|
||||
If you want to do some of the same querying in your terminal program without
|
||||
depending on the kitten, you can do so, by processing the same escape codes.
|
||||
Search `this page <https://invisible-island.net/xterm/ctlseqs/ctlseqs.html>`_
|
||||
for *XTGETTCAP* to see the syntax for the escape code and read the source
|
||||
of this kitten to find the values of the keys for the various queries.
|
||||
Search `this page <https://invisible-island.net/xterm/ctlseqs/ctlseqs.html>`__
|
||||
for *XTGETTCAP* to see the syntax for the escape code and read the source of
|
||||
this kitten to find the values of the keys for the various queries.
|
||||
|
||||
|
||||
.. include:: ../generated/cli-kitten-query_terminal.rst
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
Remote files
|
||||
==============
|
||||
|
||||
|kitty| has the ability to easily *Edit*, *Open* or *Download* files
|
||||
from a computer into which you are SSHed. In your SSH session run::
|
||||
|kitty| has the ability to easily *Edit*, *Open* or *Download* files from a
|
||||
computer into which you are SSHed. In your SSH session run::
|
||||
|
||||
ls --hyperlink=auto
|
||||
|
||||
Then hold down :kbd:`ctrl+shift` and click the name of the file.
|
||||
Then hold down :kbd:`Ctrl+Shift` and click the name of the file.
|
||||
|
||||
.. figure:: ../screenshots/remote_file.png
|
||||
:alt: Remote file actions
|
||||
@@ -19,23 +19,28 @@ Then hold down :kbd:`ctrl+shift` and click the name of the file.
|
||||
to *Edit* it in which case kitty will download it and open it locally in your
|
||||
:envvar:`EDITOR`. As you make changes to the file, they are automatically
|
||||
transferred to the remote computer. Note that this happens without needing
|
||||
to install *any* special software on the server, beyond ``ls`` that supports
|
||||
hyperlinks.
|
||||
to install *any* special software on the server, beyond :program:`ls` that
|
||||
supports hyperlinks.
|
||||
|
||||
.. seealso:: See the :ref:`edit-in-kitty <edit_file>` command
|
||||
|
||||
.. seealso:: See the :doc:`transfer` kitten
|
||||
|
||||
.. versionadded:: 0.19.0
|
||||
|
||||
.. note::
|
||||
Nested SSH sessions are not supported. The kitten will always try to copy
|
||||
remote files from the first SSH host. This is because there is no way for
|
||||
For best results, use this kitten with the :doc:`ssh kitten <./ssh>`.
|
||||
Otherwise, nested SSH sessions are not supported. The kitten will always try to copy
|
||||
remote files from the first SSH host. This is because, without the ssh
|
||||
kitten, there is no way for
|
||||
|kitty| to detect and follow a nested SSH session robustly. Use the
|
||||
:doc:`transfer` kitten for such situations.
|
||||
|
||||
.. note::
|
||||
If you have not setup automatic password-less SSH access, then, when
|
||||
editing starts you will be asked to enter your password just once,
|
||||
thereafter the SSH connection will be re-used.
|
||||
If you have not setup automatic password-less SSH access, and are not using
|
||||
the ssh kitten, then, when editing
|
||||
starts you will be asked to enter your password just once, thereafter the SSH
|
||||
connection will be re-used.
|
||||
|
||||
Similarly, you can choose to save the file to the local computer or download
|
||||
and open it in its default file handler.
|
||||
|
||||
164
docs/kittens/ssh.rst
Normal file
164
docs/kittens/ssh.rst
Normal file
@@ -0,0 +1,164 @@
|
||||
Truly convenient SSH
|
||||
=========================================
|
||||
|
||||
* Automatic :ref:`shell_integration` on remote hosts
|
||||
|
||||
* Easily :ref:`clone local shell/editor config <real_world_ssh_kitten_config>` on remote hosts
|
||||
|
||||
* Automatic :opt:`re-use of existing connections <kitten-ssh.share_connections>` to avoid connection setup latency
|
||||
|
||||
* Make kitty itself available in the remote host :opt:`on demand <kitten-ssh.remote_kitty>`
|
||||
|
||||
* Easily :opt:`change terminal colors <kitten-ssh.color_scheme>` when connecting to remote hosts
|
||||
|
||||
.. versionadded:: 0.25.0
|
||||
Automatic shell integration, file transfer and reuse of connections
|
||||
|
||||
The ssh kitten allows you to login easily to remote hosts, and automatically
|
||||
setup the environment there to be as comfortable as your local shell. You can
|
||||
specify environment variables to set on the remote host and files to copy there,
|
||||
making your remote experience just like your local shell. Additionally, it
|
||||
automatically sets up :ref:`shell_integration` on the remote host and copies the
|
||||
kitty terminfo database there.
|
||||
|
||||
The ssh kitten is a thin wrapper around the traditional `ssh <https://man.openbsd.org/ssh>`__
|
||||
command line program and supports all the same options and arguments and configuration.
|
||||
In interactive usage scenarios it is a drop in replacement for :program:`ssh`.
|
||||
To try it out, simply run:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
kitty +kitten ssh some-hostname-to-connect-to
|
||||
|
||||
You should end up at a shell prompt on the remote host, with shell integration
|
||||
enabled. If you like it you can add an alias to it in your shell's rc files:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
alias s="kitty +kitten ssh"
|
||||
|
||||
So now you can just type ``s hostname`` to connect.
|
||||
|
||||
If you define a mapping in :file:`kitty.conf` such as::
|
||||
|
||||
map f1 new_window_with_cwd
|
||||
|
||||
Then, pressing :kbd:`F1` will open a new window automatically logged into the
|
||||
same host using the ssh kitten, at the same directory.
|
||||
|
||||
The ssh kitten can be configured using the :file:`~/.config/kitty/ssh.conf` file
|
||||
where you can specify environment variables to set on the remote host and files
|
||||
to copy from the local to the remote host. Let's see a quick example:
|
||||
|
||||
.. code-block:: conf
|
||||
|
||||
# Copy the files and directories needed to setup some common tools
|
||||
copy .zshrc .vimrc .vim
|
||||
# Setup some environment variables
|
||||
env SOME_VAR=x
|
||||
# COPIED_VAR will have the same value on the remote host as it does locally
|
||||
env COPIED_VAR=_kitty_copy_env_var_
|
||||
|
||||
# Create some per hostname settings
|
||||
hostname someserver-*
|
||||
copy env-files
|
||||
env SOMETHING=else
|
||||
|
||||
hostname someuser@somehost
|
||||
copy --dest=foo/bar some-file
|
||||
copy --glob some/files.*
|
||||
|
||||
|
||||
See below for full details on the syntax and options of :file:`ssh.conf`.
|
||||
Additionally, you can pass config options on the command line:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
kitty +kitten ssh --kitten interpreter=python servername
|
||||
|
||||
The :code:`--kitten` argument can be specified multiple times, with directives
|
||||
from :file:`ssh.conf`. These are merged with :file:`ssh.conf` as if they were
|
||||
appended to the end of that file. They apply only to the host being SSHed to by
|
||||
this invocation, so any :opt:`hostname <kitten-ssh.hostname>` directives are
|
||||
ignored.
|
||||
|
||||
.. warning::
|
||||
|
||||
Due to limitations in the design of SSH, any typing you do before the
|
||||
shell prompt appears may be lost. So ideally don't start typing till you see
|
||||
the shell prompt. 😇
|
||||
|
||||
|
||||
.. _real_world_ssh_kitten_config:
|
||||
|
||||
A real world example
|
||||
----------------------
|
||||
|
||||
Suppose you often SSH into a production server, and you would like to setup
|
||||
your shell and editor there using your custom settings. However, other people
|
||||
could SSH in as well and you don't want to clobber their settings. Here is how
|
||||
this could be achieved using the ssh kitten with :program:`zsh` and
|
||||
:program:`vim` as the shell and editor, respectively:
|
||||
|
||||
.. code-block:: conf
|
||||
|
||||
# Have these settings apply to servers in my organization
|
||||
hostname myserver-*
|
||||
|
||||
# Setup zsh to read its files from my-conf/zsh
|
||||
env ZDOTDIR $HOME/my-conf/zsh
|
||||
copy --dest my-conf/zsh/.zshrc .zshrc
|
||||
copy --dest my-conf/zsh/.zshenv .zshenv
|
||||
# If you use other zsh init files add them in a similar manner
|
||||
|
||||
# Setup vim to read its config from my-conf/vim
|
||||
env VIMINIT $HOME/my-conf/vim/vimrc
|
||||
env VIMRUNTIME $HOME/my-conf/vim
|
||||
copy --dest my-conf/vim .vim
|
||||
copy --dest my-conf/vim/vimrc .vimrc
|
||||
|
||||
|
||||
How it works
|
||||
----------------
|
||||
|
||||
The ssh kitten works by having SSH transmit and execute a POSIX sh (or
|
||||
:opt:`optionally <kitten-ssh.interpreter>` Python) bootstrap script on the
|
||||
remote host using an :opt:`interpreter <kitten-ssh.interpreter>`. This script
|
||||
reads setup data over the TTY device, which kitty sends as a Base64 encoded
|
||||
compressed tarball. The script extracts it and places the :opt:`files <kitten-ssh.copy>`
|
||||
and sets the :opt:`environment variables <kitten-ssh.env>` before finally
|
||||
launching the :opt:`login shell <kitten-ssh.login_shell>` with :opt:`shell
|
||||
integration <kitten-ssh.shell_integration>` enabled. The data is requested by
|
||||
the kitten over the TTY with a random one time password. kitty reads the request
|
||||
and if the password matches a password pre-stored in shared memory on the
|
||||
localhost by the kitten, the transmission is allowed. If your local
|
||||
`OpenSSH <https://www.openssh.com/>`__ version is >= 8.4 then the data is
|
||||
transmitted instantly without any roundtrip delay.
|
||||
|
||||
.. note::
|
||||
|
||||
When connecting to BSD hosts, it is possible the bootstrap script will fail
|
||||
or run slowly, because the default shells are crippled in various ways.
|
||||
Your best bet is to install Python on the remote, make sure the login shell
|
||||
is something POSIX sh compliant, and use :code:`python` as the
|
||||
: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
|
||||
|
||||
|
||||
.. _ssh_copy_command:
|
||||
|
||||
The copy command
|
||||
--------------------
|
||||
|
||||
.. include:: /generated/ssh-copy.rst
|
||||
@@ -1,8 +1,8 @@
|
||||
Changing kitty colors
|
||||
========================
|
||||
|
||||
The themes kitten allows you to easily change color themes, from a collection
|
||||
of almost two hundred pre-built themes available at `kitty-themes
|
||||
The themes kitten allows you to easily change color themes, from a collection of
|
||||
over two hundred pre-built themes available at `kitty-themes
|
||||
<https://github.com/kovidgoyal/kitty-themes>`_. To use it, simply run::
|
||||
|
||||
kitty +kitten themes
|
||||
@@ -12,9 +12,9 @@ of almost two hundred pre-built themes available at `kitty-themes
|
||||
:alt: The themes kitten in action
|
||||
:width: 600
|
||||
|
||||
The kitten allows you to pick a theme, with live previews of the colors. You
|
||||
can choose between light and dark themes and search by theme name by just
|
||||
typing a few characters from the name.
|
||||
The kitten allows you to pick a theme, with live previews of the colors. You can
|
||||
choose between light and dark themes and search by theme name by just typing a
|
||||
few characters from the name.
|
||||
|
||||
The kitten maintains a list of recently used themes to allow quick switching.
|
||||
|
||||
@@ -24,14 +24,15 @@ If you want to restore the colors to default, you can do so by choosing the
|
||||
.. versionadded:: 0.23.0
|
||||
The themes kitten
|
||||
|
||||
|
||||
How it works
|
||||
----------------
|
||||
|
||||
A theme in kitty is just a :file:`.conf` file containing kitty settings.
|
||||
When you select a theme, the kitten simply copies the :file:`.conf` file
|
||||
to :file:`~/.config/kitty/current-theme.conf` and adds an include for
|
||||
:file:`current-theme.conf` to :file:`kitty.conf`. It also comments out
|
||||
any existing color settings in :file:`kitty.conf` so they do not interfere.
|
||||
:file:`current-theme.conf` to :file:`kitty.conf`. It also comments out any
|
||||
existing color settings in :file:`kitty.conf` so they do not interfere.
|
||||
|
||||
Once that's done, the kitten sends kitty a signal to make it reload its config.
|
||||
|
||||
@@ -39,9 +40,9 @@ Using your own themes
|
||||
-----------------------
|
||||
|
||||
You can also create your own themes as :file:`.conf` files. Put them in the
|
||||
:file:`themes` sub-directory of the kitty config directory, usually,
|
||||
:file:`~/.config/kitty/themes` and the kitten will automatically add them to
|
||||
the list of themes. You can use this to modify the builtin themes, by giving
|
||||
:file:`themes` sub-directory of the :ref:`kitty config directory <confloc>`,
|
||||
usually, :file:`~/.config/kitty/themes`. The kitten will automatically add them
|
||||
to the list of themes. You can use this to modify the builtin themes, by giving
|
||||
the conf file the name :file:`Some theme name.conf` to override the builtin
|
||||
theme of that name. Note that after doing so you have to run the kitten and
|
||||
choose that theme once for your changes to be applied.
|
||||
@@ -52,13 +53,13 @@ Contributing new themes
|
||||
|
||||
If you wish to contribute a new theme to the kitty theme repository, start by
|
||||
going to the `kitty-themes <https://github.com/kovidgoyal/kitty-themes>`__
|
||||
repository. `Fork it
|
||||
<https://docs.github.com/en/get-started/quickstart/fork-a-repo>`_, and use the
|
||||
repository. `Fork it
|
||||
<https://docs.github.com/en/get-started/quickstart/fork-a-repo>`__, and use the
|
||||
file :download:`template.conf
|
||||
<https://github.com/kovidgoyal/kitty-themes/raw/master/template.conf>` as a
|
||||
template when creating your theme. Once you are satisfied with how it looks,
|
||||
`submit a pull request
|
||||
<https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request>`_
|
||||
<https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request>`__
|
||||
to have your theme merged into the `kitty-themes
|
||||
<https://github.com/kovidgoyal/kitty-themes>`__ repository, which will make it
|
||||
available in this kitten automatically.
|
||||
@@ -67,12 +68,12 @@ available in this kitten automatically.
|
||||
Changing the theme non-interactively
|
||||
---------------------------------------
|
||||
|
||||
You can specify the theme name as an argument when invoking the kitten
|
||||
to have it change to that theme instantly. For example::
|
||||
You can specify the theme name as an argument when invoking the kitten to have
|
||||
it change to that theme instantly. For example::
|
||||
|
||||
kitty +kitten themes --reload-in=all Dimmed Monokai
|
||||
|
||||
Will change the theme to ``Dimmed Monokai`` in all running kitty
|
||||
instances. See below for more details on non-interactive operation.
|
||||
Will change the theme to ``Dimmed Monokai`` in all running kitty instances. See
|
||||
below for more details on non-interactive operation.
|
||||
|
||||
.. include:: ../generated/cli-kitten-themes.rst
|
||||
|
||||
@@ -14,16 +14,16 @@ etc. Anywhere you have a terminal device, you can transfer files.
|
||||
:alt: The transfer kitten at work
|
||||
|
||||
This kitten supports transferring entire directory trees, preserving soft and
|
||||
hard links, file permissions, times, etc. It even supports the rsync_
|
||||
protocol to transfer only changes to large files.
|
||||
hard links, file permissions, times, etc. It even supports the rsync_ protocol
|
||||
to transfer only changes to large files.
|
||||
|
||||
.. seealso:: See the :doc:`remote_file` kitten
|
||||
|
||||
.. note::
|
||||
This kitten (which practically means kitty) must be installed on the other
|
||||
machine as well. If that is not possible you can use the :doc:`remote_file`
|
||||
kitten instead. Or write your own script to use the underlying :doc:`file transfer
|
||||
protocol </file-transfer-protocol>`.
|
||||
kitten instead. Or write your own script to use the underlying
|
||||
:doc:`file transfer protocol </file-transfer-protocol>`.
|
||||
|
||||
.. versionadded:: 0.24.0
|
||||
|
||||
@@ -32,7 +32,8 @@ Basic usage
|
||||
---------------
|
||||
|
||||
In what follows, the *local computer* is the computer running this kitten and
|
||||
the *remote computer* is the computer connected to the other end of the TTY pipe.
|
||||
the *remote computer* is the computer connected to the other end of the TTY
|
||||
pipe.
|
||||
|
||||
To send a file from the local computer to the remote computer, simply run::
|
||||
|
||||
@@ -42,7 +43,7 @@ You will be prompted by kitty for confirmation on allowing the transfer, and if
|
||||
you grant permission, the file will be copied.
|
||||
|
||||
Similarly, to get a file from the remote computer to the local computer, use
|
||||
the :option:`kitty +kitten transfer --direction` option::
|
||||
the :option:`--direction <kitty +kitten transfer --direction>` option::
|
||||
|
||||
kitty +kitten transfer --direction=receive /path/to/remote/file /path/to/destination/on/local/computer
|
||||
|
||||
@@ -58,25 +59,25 @@ the fact that you are copying multiple things) it is good practice to always
|
||||
use a trailing slash when the destination is supposed to be a directory.
|
||||
|
||||
Also, when transferring multiple files/directories it is a good idea to
|
||||
use the :option:`kitty +kitten transfer --confirm-paths` option which will give
|
||||
you an opportunity to review and confirm the files that will be touched.
|
||||
use the :option:`--confirm-paths <kitty +kitten transfer --confirm-paths>`
|
||||
option which will give you an opportunity to review and confirm the files that
|
||||
will be touched.
|
||||
|
||||
|
||||
Avoiding the confirmation prompt
|
||||
------------------------------------
|
||||
|
||||
Normally, when you start a file transfer kitty will prompt you for
|
||||
confirmation. This is to ensure that hostile programs running on a remote
|
||||
machine cannot read/write files on your computer without your permission.
|
||||
If the remote machine is trusted and the connection between your computer
|
||||
and the remote machine is secure, then you can disable the confirmation prompt
|
||||
by:
|
||||
Normally, when you start a file transfer kitty will prompt you for confirmation.
|
||||
This is to ensure that hostile programs running on a remote machine cannot
|
||||
read/write files on your computer without your permission. If the remote machine
|
||||
is trusted and the connection between your computer and the remote machine is
|
||||
secure, then you can disable the confirmation prompt by:
|
||||
|
||||
#. Setting the :opt:`file_transfer_confirmation_bypass` option to some
|
||||
password.
|
||||
#. Setting the :opt:`file_transfer_confirmation_bypass` option to some password.
|
||||
|
||||
#. When invoking the kitten use the :option:`kitty +kitten transfer --permissions-bypass`
|
||||
to supply the password you set in step one.
|
||||
#. When invoking the kitten use the :option:`--permissions-bypass
|
||||
<kitty +kitten transfer --permissions-bypass>` to supply the password you set
|
||||
in step one.
|
||||
|
||||
.. warning:: Using a password to bypass confirmation means any software running
|
||||
on the remote machine could potentially learn that password and use it to
|
||||
@@ -89,9 +90,10 @@ Delta transfers
|
||||
-----------------------------------
|
||||
|
||||
This kitten has the ability to use the rsync_ protocol to only transfer the
|
||||
differences between files. To turn it on use the :option:`kitty +kitten
|
||||
transfer --transmit-deltas` option. Note that this will actually be slower when
|
||||
transferring small files because of round trip overhead, so use with care.
|
||||
differences between files. To turn it on use the :option:`--transmit-deltas
|
||||
<kitty +kitten transfer --transmit-deltas>` option. Note that this will actually
|
||||
be slower when transferring small files because of round trip overhead, so use
|
||||
with care.
|
||||
|
||||
|
||||
.. include:: ../generated/cli-kitten-transfer.rst
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
Unicode input
|
||||
================
|
||||
|
||||
You can input unicode characters by name, hex code, recently used and even an editable favorites list.
|
||||
Press :sc:`input_unicode_character` to start the unicode input widget, shown below.
|
||||
You can input Unicode characters by name, hex code, recently used and even an
|
||||
editable favorites list. Press :sc:`input_unicode_character` to start the
|
||||
unicode input kitten, shown below.
|
||||
|
||||
.. figure:: ../screenshots/unicode.png
|
||||
:alt: A screenshot of the unicode input widget
|
||||
:alt: A screenshot of the unicode input kitten
|
||||
:align: center
|
||||
:width: 100%
|
||||
|
||||
A screenshot of the unicode input widget
|
||||
A screenshot of the unicode input kitten
|
||||
|
||||
In :guilabel:`Code` mode, you enter a unicode character by typing in the hex code for the
|
||||
character and pressing enter, for example, type in ``2716`` and press enter to get
|
||||
✖. You can also choose a character from the list of recently used characters by
|
||||
typing a leading period and then the two character index and pressing Enter.
|
||||
The up and down arrow keys can be used to choose the previous and next unicode
|
||||
symbol respectively.
|
||||
In :guilabel:`Code` mode, you enter a Unicode character by typing in the hex
|
||||
code for the character and pressing :kbd:`Enter`. For example, type in ``2716``
|
||||
and press :kbd:`Enter` to get ``✖``. You can also choose a character from the
|
||||
list of recently used characters by typing a leading period ``.`` and then the
|
||||
two character index and pressing :kbd:`Enter`.
|
||||
The :kbd:`Up` and :kbd:`Down` arrow keys can be used to choose the previous and
|
||||
next Unicode symbol respectively.
|
||||
|
||||
In :guilabel:`Name` mode you instead type words from the character name and use
|
||||
the arrow keys/tab to select the character from the displayed matches. You can
|
||||
also type a space followed by a period and the index for the match if you don't
|
||||
like to use arrow keys.
|
||||
the :kbd:`ArrowKeys` / :kbd:`Tab` to select the character from the displayed
|
||||
matches. You can also type a space followed by a period and the index for the
|
||||
match if you don't like to use arrow keys.
|
||||
|
||||
You can switch between modes using either the keys :kbd:`F1` ... :kbd:`F4` or
|
||||
:kbd:`Ctrl+1` ... :kbd:`Ctrl+4` or by pressing :kbd:`Ctrl+[` and :kbd:`Ctrl+]`
|
||||
|
||||
@@ -15,12 +15,13 @@ Extend with kittens
|
||||
kittens/remote_file
|
||||
kittens/hyperlinked_grep
|
||||
kittens/transfer
|
||||
kittens/ssh
|
||||
kittens/custom
|
||||
kittens/*
|
||||
|
||||
|kitty| has a framework for easily creating terminal programs that make use of
|
||||
its advanced features. These programs are called kittens. They are used both
|
||||
to add features to |kitty| itself and to create useful standalone programs.
|
||||
its advanced features. These programs are called kittens. They are used both to
|
||||
add features to |kitty| itself and to create useful standalone programs.
|
||||
Some prominent kittens:
|
||||
|
||||
:doc:`icat <kittens/icat>`
|
||||
@@ -32,8 +33,8 @@ Some prominent kittens:
|
||||
images
|
||||
|
||||
|
||||
:doc:`Unicode Input <kittens/unicode_input>`
|
||||
Easily input arbitrary unicode characters in |kitty| by name or hex code.
|
||||
:doc:`Unicode input <kittens/unicode_input>`
|
||||
Easily input arbitrary Unicode characters in |kitty| by name or hex code.
|
||||
|
||||
|
||||
:doc:`Hints <kittens/hints>`
|
||||
@@ -47,24 +48,31 @@ Some prominent kittens:
|
||||
|
||||
|
||||
:doc:`Transfer files <kittens/transfer>`
|
||||
Transfer files and directories seamlessly and easily from remote machines over your existing
|
||||
SSH sessions with a simple command.
|
||||
Transfer files and directories seamlessly and easily from remote machines
|
||||
over your existing SSH sessions with a simple command.
|
||||
|
||||
|
||||
:doc:`Hyperlinked grep <kittens/hyperlinked_grep>`
|
||||
Search your files using `ripgrep <https://github.com/BurntSushi/ripgrep>`_
|
||||
Search your files using `ripgrep <https://github.com/BurntSushi/ripgrep>`__
|
||||
and open the results directly in your favorite editor in the terminal,
|
||||
at the line containing the search result, simply by clicking on the result you want.
|
||||
at the line containing the search result, simply by clicking on the result
|
||||
you want.
|
||||
|
||||
|
||||
:doc:`Broadcast <kittens/broadcast>`
|
||||
Type in one :term:`kitty window <window>` and have it broadcast to all (or a subset) of
|
||||
other :term:`kitty windows <window>`.
|
||||
Type in one :term:`kitty window <window>` and have it broadcast to all (or a
|
||||
subset) of other :term:`kitty windows <window>`.
|
||||
|
||||
|
||||
:doc:`SSH <kittens/ssh>`
|
||||
SSH with automatic :ref:`shell integration <shell_integration>`, connection
|
||||
re-use for low latency and easy cloning of local shell and editor
|
||||
configuration to the remote host.
|
||||
|
||||
|
||||
:doc:`Panel <kittens/panel>`
|
||||
Draw a GPU accelerated dock panel on your desktop showing the output
|
||||
from an arbitrary terminal program.
|
||||
Draw a GPU accelerated dock panel on your desktop showing the output from an
|
||||
arbitrary terminal program.
|
||||
|
||||
|
||||
:doc:`Clipboard <kittens/clipboard>`
|
||||
|
||||
295
docs/kitty_at_template.py
Normal file
295
docs/kitty_at_template.py
Normal file
@@ -0,0 +1,295 @@
|
||||
#!/usr/bin/env python
|
||||
# License: GPLv3 Copyright: 2022, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
import argparse
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import termios
|
||||
import time
|
||||
import tty
|
||||
from contextlib import contextmanager
|
||||
from ctypes import (
|
||||
CDLL, POINTER, byref, c_char_p, c_int, c_size_t, c_void_p,
|
||||
create_string_buffer
|
||||
)
|
||||
from ctypes.util import find_library
|
||||
|
||||
_plat = sys.platform.lower()
|
||||
is_macos: bool = 'darwin' in _plat
|
||||
|
||||
|
||||
def build_crypto_tools(): # {{{
|
||||
class EVP_PKEY_POINTER(c_void_p):
|
||||
|
||||
algorithm = 0
|
||||
|
||||
def __del__(self):
|
||||
EVP_PKEY_free(self)
|
||||
|
||||
@property
|
||||
def public(self):
|
||||
sz = c_size_t(0)
|
||||
EVP_PKEY_get_raw_public_key(self, None, byref(sz))
|
||||
buf = create_string_buffer(sz.value)
|
||||
EVP_PKEY_get_raw_public_key(self, buf, byref(sz))
|
||||
return buf.raw
|
||||
|
||||
def derive_secret(self, pubkey):
|
||||
pubkey = EVP_PKEY_new_raw_public_key(self.algorithm, None, pubkey, len(pubkey))
|
||||
ctx = EVP_PKEY_CTX_new(self, None)
|
||||
EVP_PKEY_derive_init(ctx)
|
||||
EVP_PKEY_derive_set_peer(ctx, pubkey)
|
||||
sz = c_size_t(0)
|
||||
EVP_PKEY_derive(ctx, None, byref(sz))
|
||||
buf = create_string_buffer(sz.value)
|
||||
EVP_PKEY_derive(ctx, buf, byref(sz))
|
||||
return hashlib.sha256(buf.raw).digest()
|
||||
|
||||
class EVP_PKEY_CTX_POINTER(c_void_p):
|
||||
|
||||
def __del__(self):
|
||||
EVP_PKEY_CTX_free(self)
|
||||
|
||||
class EVP_CIPHER_CTX_POINTER(c_void_p):
|
||||
|
||||
def __del__(self):
|
||||
EVP_CIPHER_CTX_free(self)
|
||||
|
||||
class EVP_CIPHER_POINTER(c_void_p):
|
||||
pass
|
||||
|
||||
cl = find_library('crypto')
|
||||
if not cl:
|
||||
raise SystemExit('Failed to find libcrypto on your system, make sure OpenSSL is installed')
|
||||
crypto = CDLL(cl)
|
||||
libc = CDLL(None)
|
||||
|
||||
def create_crypto_func(name, *argtypes, restype=c_int, int_return_ok=lambda x: x == 1):
|
||||
|
||||
impl = getattr(crypto, name)
|
||||
impl.restype = restype
|
||||
impl.argtypes = argtypes
|
||||
|
||||
def func(*a):
|
||||
res = impl(*a)
|
||||
if restype is c_int:
|
||||
if not int_return_ok(res):
|
||||
print('Call to', name, 'failed with return code:', res, file=sys.stderr)
|
||||
abort_on_openssl_error()
|
||||
elif restype is not None and issubclass(restype, c_void_p):
|
||||
if res.value is None:
|
||||
print('Call to', name, 'failed with NULL return', file=sys.stderr)
|
||||
abort_on_openssl_error()
|
||||
return res
|
||||
return func
|
||||
|
||||
OBJ_txt2nid = create_crypto_func('OBJ_txt2nid', c_char_p, int_return_ok=bool)
|
||||
EVP_PKEY_CTX_new_id = create_crypto_func('EVP_PKEY_CTX_new_id', c_int, c_void_p, restype=EVP_PKEY_CTX_POINTER)
|
||||
EVP_PKEY_CTX_new = create_crypto_func('EVP_PKEY_CTX_new', EVP_PKEY_POINTER, c_void_p, restype=EVP_PKEY_CTX_POINTER)
|
||||
EVP_PKEY_keygen_init = create_crypto_func('EVP_PKEY_keygen_init', EVP_PKEY_CTX_POINTER)
|
||||
EVP_PKEY_keygen = create_crypto_func('EVP_PKEY_keygen', EVP_PKEY_CTX_POINTER, POINTER(EVP_PKEY_POINTER))
|
||||
ERR_print_errors_fp = create_crypto_func('ERR_print_errors_fp', c_void_p, restype=None)
|
||||
EVP_PKEY_free = create_crypto_func('EVP_PKEY_free', EVP_PKEY_POINTER, restype=None)
|
||||
EVP_PKEY_CTX_free = create_crypto_func('EVP_PKEY_CTX_free', EVP_PKEY_CTX_POINTER, restype=None)
|
||||
EVP_PKEY_get_raw_public_key = create_crypto_func('EVP_PKEY_get_raw_public_key', EVP_PKEY_POINTER, c_char_p, POINTER(c_size_t))
|
||||
EVP_PKEY_new_raw_public_key = create_crypto_func('EVP_PKEY_new_raw_public_key', c_int, c_void_p, c_char_p, c_size_t, restype=EVP_PKEY_POINTER)
|
||||
EVP_PKEY_derive_init = create_crypto_func('EVP_PKEY_derive_init', EVP_PKEY_CTX_POINTER)
|
||||
EVP_PKEY_derive_set_peer = create_crypto_func('EVP_PKEY_derive_set_peer', EVP_PKEY_CTX_POINTER, EVP_PKEY_POINTER)
|
||||
EVP_PKEY_derive = create_crypto_func('EVP_PKEY_derive', EVP_PKEY_CTX_POINTER, c_char_p, POINTER(c_size_t))
|
||||
EVP_CIPHER_CTX_free = create_crypto_func('EVP_CIPHER_CTX_free', EVP_CIPHER_CTX_POINTER, restype=None)
|
||||
EVP_get_cipherbyname = create_crypto_func('EVP_get_cipherbyname', c_char_p, restype=EVP_CIPHER_POINTER)
|
||||
EVP_CIPHER_key_length = create_crypto_func('EVP_CIPHER_key_length', EVP_CIPHER_POINTER, int_return_ok=bool)
|
||||
EVP_CIPHER_iv_length = create_crypto_func('EVP_CIPHER_iv_length', EVP_CIPHER_POINTER, int_return_ok=bool)
|
||||
EVP_CIPHER_CTX_block_size = create_crypto_func('EVP_CIPHER_CTX_block_size', EVP_CIPHER_CTX_POINTER, int_return_ok=bool)
|
||||
EVP_CIPHER_CTX_new = create_crypto_func('EVP_CIPHER_CTX_new', restype=EVP_CIPHER_CTX_POINTER)
|
||||
EVP_EncryptInit_ex = create_crypto_func('EVP_EncryptInit_ex', EVP_CIPHER_CTX_POINTER, EVP_CIPHER_POINTER, c_void_p, c_char_p, c_char_p)
|
||||
EVP_EncryptUpdate = create_crypto_func('EVP_EncryptUpdate', EVP_CIPHER_CTX_POINTER, c_char_p, POINTER(c_int), c_char_p, c_int)
|
||||
EVP_EncryptFinal_ex = create_crypto_func('EVP_EncryptFinal_ex', EVP_CIPHER_CTX_POINTER, c_char_p, POINTER(c_int))
|
||||
EVP_CIPHER_CTX_ctrl = create_crypto_func('EVP_CIPHER_CTX_ctrl', EVP_CIPHER_CTX_POINTER, c_int, c_int, c_char_p)
|
||||
try:
|
||||
EVP_CIPHER_CTX_tag_length = create_crypto_func('EVP_CIPHER_CTX_tag_length', EVP_CIPHER_CTX_POINTER, int_return_ok=bool)
|
||||
except AttributeError: # need openssl >= 3
|
||||
def EVP_CIPHER_CTX_tag_length(cipher):
|
||||
return 16
|
||||
EVP_CTRL_AEAD_GET_TAG, EVP_CTRL_AEAD_SET_TAG = 0x10, 0x11 # these are defines in the header dont know how to get them programmatically
|
||||
EVP_CTRL_AEAD_SET_TAG
|
||||
|
||||
def abort_on_openssl_error():
|
||||
stderr = c_void_p.in_dll(libc, 'stderr')
|
||||
ERR_print_errors_fp(stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
def elliptic_curve_keypair(algorithm='X25519'):
|
||||
nid = OBJ_txt2nid(algorithm.encode())
|
||||
pctx = EVP_PKEY_CTX_new_id(nid, None)
|
||||
EVP_PKEY_keygen_init(pctx)
|
||||
key = EVP_PKEY_POINTER()
|
||||
EVP_PKEY_keygen(pctx, byref(key))
|
||||
key.algorithm = nid
|
||||
return key
|
||||
|
||||
def encrypt(plaintext, symmetric_key, algorithm='aes-256-gcm'):
|
||||
cipher = EVP_get_cipherbyname(algorithm.encode())
|
||||
if len(symmetric_key) != EVP_CIPHER_key_length(cipher):
|
||||
raise KeyError(f'The symmetric key has length {len(symmetric_key)} != {EVP_CIPHER_key_length(cipher)} needed for {algorithm}')
|
||||
ctx = EVP_CIPHER_CTX_new()
|
||||
iv = os.urandom(EVP_CIPHER_iv_length(cipher))
|
||||
EVP_EncryptInit_ex(ctx, cipher, None, symmetric_key, iv)
|
||||
bs = EVP_CIPHER_CTX_block_size(ctx)
|
||||
ciphertext = create_string_buffer(len(plaintext) + 2 * bs)
|
||||
outlen = c_int(len(ciphertext))
|
||||
EVP_EncryptUpdate(ctx, ciphertext, byref(outlen), plaintext, len(plaintext))
|
||||
ans = ciphertext[:outlen.value]
|
||||
outlen = c_int(len(ciphertext))
|
||||
EVP_EncryptFinal_ex(ctx, ciphertext, byref(outlen))
|
||||
if outlen.value:
|
||||
ans += ciphertext[:outlen.value]
|
||||
tag = create_string_buffer(EVP_CIPHER_CTX_tag_length(cipher))
|
||||
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, len(tag), tag)
|
||||
return iv, ans, tag.raw
|
||||
return elliptic_curve_keypair, encrypt
|
||||
# }}}
|
||||
|
||||
|
||||
# utils {{{
|
||||
def encrypt_cmd(cmd, password, pubkey=None):
|
||||
elliptic_curve_keypair, encrypt = build_crypto_tools()
|
||||
if pubkey is None:
|
||||
pubkey = os.environ['KITTY_PUBLIC_KEY']
|
||||
v, d = pubkey.split(':', 1)
|
||||
if v != '1':
|
||||
raise SystemExit(f'Unsupported encryption protocol: {v}')
|
||||
pubkey = base64.b85decode(d)
|
||||
k = elliptic_curve_keypair()
|
||||
sk = k.derive_secret(pubkey)
|
||||
cmd['timestamp'] = time.time_ns()
|
||||
cmd['password'] = password
|
||||
data = json.dumps(cmd).encode()
|
||||
iv, encrypted, tag = encrypt(data, sk)
|
||||
|
||||
def e(x):
|
||||
return base64.b85encode(x).decode('ascii')
|
||||
|
||||
return {
|
||||
'encrypted': e(encrypted), 'iv': e(iv), 'tag': e(tag), 'pubkey': e(k.public), 'version': cmd['version']
|
||||
}
|
||||
|
||||
|
||||
@contextmanager
|
||||
def raw_mode(fd):
|
||||
old = termios.tcgetattr(fd)
|
||||
try:
|
||||
tty.setraw(fd)
|
||||
yield
|
||||
finally:
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
||||
|
||||
|
||||
def config_dir():
|
||||
if 'KITTY_CONFIG_DIRECTORY' in os.environ:
|
||||
return os.path.abspath(os.path.expanduser(os.environ['KITTY_CONFIG_DIRECTORY']))
|
||||
locations = []
|
||||
if 'XDG_CONFIG_HOME' in os.environ:
|
||||
locations.append(os.path.abspath(os.path.expanduser(os.environ['XDG_CONFIG_HOME'])))
|
||||
locations.append(os.path.expanduser('~/.config'))
|
||||
if is_macos:
|
||||
locations.append(os.path.expanduser('~/Library/Preferences'))
|
||||
for loc in filter(None, os.environ.get('XDG_CONFIG_DIRS', '').split(os.pathsep)):
|
||||
locations.append(os.path.abspath(os.path.expanduser(loc)))
|
||||
for loc in locations:
|
||||
if loc:
|
||||
q = os.path.join(loc, 'kitty')
|
||||
if os.access(q, os.W_OK) and os.path.exists(os.path.join(q, 'kitty.conf')):
|
||||
return q
|
||||
for loc in locations:
|
||||
if loc:
|
||||
q = os.path.join(loc, 'kitty')
|
||||
if os.path.isdir(q) and os.access(q, os.W_OK):
|
||||
return q
|
||||
return ''
|
||||
|
||||
|
||||
def resolve_custom_file(path):
|
||||
path = os.path.expanduser(path)
|
||||
path = os.path.expandvars(path)
|
||||
if not os.path.isabs(path):
|
||||
cdir = config_dir()
|
||||
if cdir:
|
||||
path = os.path.join(cdir, path)
|
||||
return path
|
||||
|
||||
|
||||
def get_password(opts):
|
||||
if opts.use_password == 'never':
|
||||
return ''
|
||||
ans = ''
|
||||
if opts.password:
|
||||
ans = opts.password
|
||||
if not ans and opts.password_file:
|
||||
if opts.password_file == '-':
|
||||
if sys.stdin.isatty():
|
||||
from getpass import getpass
|
||||
ans = getpass()
|
||||
else:
|
||||
ans = sys.stdin.read().rstrip()
|
||||
try:
|
||||
tty_fd = os.open(os.ctermid(), os.O_RDONLY | os.O_CLOEXEC)
|
||||
except OSError:
|
||||
pass
|
||||
else:
|
||||
with open(tty_fd, closefd=True):
|
||||
os.dup2(tty_fd, sys.stdin.fileno())
|
||||
else:
|
||||
try:
|
||||
with open(resolve_custom_file(opts.password_file)) as f:
|
||||
ans = f.read().rstrip()
|
||||
except OSError:
|
||||
pass
|
||||
if not ans and opts.password_env:
|
||||
ans = os.environ.get(opts.password_env, '')
|
||||
if not ans and opts.use_password == 'always':
|
||||
raise SystemExit('No password was found')
|
||||
if ans and len(ans) > 1024:
|
||||
raise SystemExit('Specified password is too long')
|
||||
return ans
|
||||
# }}}
|
||||
|
||||
|
||||
arg_parser = argparse.ArgumentParser(prog='kitty@', description='Control kitty remotely.')
|
||||
arg_parser.add_argument('--password', default='', help='''\
|
||||
A password to use when contacting kitty. This will cause kitty to ask the user
|
||||
for permission to perform the specified action, unless the password has been
|
||||
accepted before or is pre-configured in kitty.conf''')
|
||||
arg_parser.add_argument('--password-file', default='rc-pass', help='''\
|
||||
A file from which to read the password. Trailing whitespace is ignored. Relative
|
||||
paths are resolved from the kitty configuration directory. Use - to read from STDIN.
|
||||
Used if no --password is supplied. Defaults to checking for the
|
||||
rc-pass file in the kitty configuration directory.''')
|
||||
arg_parser.add_argument('--password-env', default='KITTY_RC_PASSWORD', help='''\
|
||||
The name of an environment variable to read the password from.
|
||||
Used if no --password-file is supplied. Defaults to checking the KITTY_RC_PASSWORD.''')
|
||||
arg_parser.add_argument('--use-password', default='if-available', choices=('if-available', 'always', 'never'), help='''\
|
||||
If no password is available, kitty will usually just send the remote control command
|
||||
without a password. This option can be used to force it to always or never use
|
||||
the supplied password.''')
|
||||
|
||||
args = arg_parser.parse_args()
|
||||
|
||||
|
||||
def populate_cmd(cmd):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
password = get_password(args)
|
||||
cmd = {'version': [0, 20, 0]} # use a random version that's fairly old
|
||||
populate_cmd(cmd)
|
||||
if password:
|
||||
encrypt_cmd(cmd, password)
|
||||
|
||||
# cmd = {'version': [0, 14, 2], 'cmd': 'ls'}
|
||||
# cmd = encrypt_cmd(cmd, 'test')
|
||||
# with open(os.open(os.ctermid(), os.O_RDWR | os.O_CLOEXEC), 'w') as tty_file, raw_mode(tty_file.fileno()):
|
||||
# print(end=f'\x1bP@kitty-cmd{json.dumps(cmd)}\x1b\\', flush=True, file=tty_file)
|
||||
# os.read(tty_file.fileno(), 4096)
|
||||
@@ -5,22 +5,21 @@ The :command:`launch` command
|
||||
|
||||
|
||||
|kitty| has a :code:`launch` action that can be used to run arbitrary programs
|
||||
in new windows/tabs. It can be mapped to user defined shortcuts in kitty.conf.
|
||||
It is very powerful and allows sending the contents of
|
||||
the current window to the launched program, as well as many other options.
|
||||
in new windows/tabs. It can be mapped to user defined shortcuts in
|
||||
:file:`kitty.conf`. It is very powerful and allows sending the contents of the
|
||||
current window to the launched program, as well as many other options.
|
||||
|
||||
In the simplest form, you can use it to open a new kitty window running the
|
||||
shell, as shown below::
|
||||
|
||||
map f1 launch
|
||||
|
||||
To run a different program simply pass the command line as arguments to
|
||||
launch::
|
||||
To run a different program simply pass the command line as arguments to launch::
|
||||
|
||||
map f1 launch vim path/to/some/file
|
||||
|
||||
To open a new window with the same working directory as the currently
|
||||
active window::
|
||||
To open a new window with the same working directory as the currently active
|
||||
window::
|
||||
|
||||
map f1 launch --cwd=current
|
||||
|
||||
@@ -30,9 +29,10 @@ To open the new window in a new tab::
|
||||
|
||||
To run multiple commands in a shell, use::
|
||||
|
||||
map f1 launch sh -c "ls && zsh"
|
||||
map f1 launch sh -c "ls && exec zsh"
|
||||
|
||||
To pass the contents of the current screen and scrollback to the started process::
|
||||
To pass the contents of the current screen and scrollback to the started
|
||||
process::
|
||||
|
||||
map f1 launch --stdin-source=@screen_scrollback less
|
||||
|
||||
@@ -46,16 +46,16 @@ There are many more powerful options, refer to the complete list below.
|
||||
map f1 launch_tab vim
|
||||
map f2 launch_tab emacs
|
||||
|
||||
The :kbd:`F1` key will now open vim in a new tab with the current windows
|
||||
working directory
|
||||
The :kbd:`F1` key will now open :program:`vim` in a new tab with the current
|
||||
windows working directory.
|
||||
|
||||
|
||||
The piping environment
|
||||
--------------------------
|
||||
|
||||
When using :option:`launch --stdin-source`, the program to which the data is
|
||||
piped has a special environment variable declared, :envvar:`KITTY_PIPE_DATA` whose
|
||||
contents are::
|
||||
piped has a special environment variable declared, :envvar:`KITTY_PIPE_DATA`
|
||||
whose contents are::
|
||||
|
||||
KITTY_PIPE_DATA={scrolled_by}:{cursor_x},{cursor_y}:{lines},{columns}
|
||||
|
||||
@@ -73,27 +73,33 @@ the command line:
|
||||
|
||||
|
||||
``@selection``
|
||||
replaced by the currently selected text
|
||||
Replaced by the currently selected text.
|
||||
|
||||
``@active-kitty-window-id``
|
||||
replaced by the id of the currently active kitty window
|
||||
Replaced by the id of the currently active kitty window.
|
||||
|
||||
``@line-count``
|
||||
replaced by the number of lines in STDIN. Only present when passing some
|
||||
data to STDIN
|
||||
Replaced by the number of lines in STDIN. Only present when passing some
|
||||
data to STDIN.
|
||||
|
||||
``@input-line-number``
|
||||
replaced the number of lines a pager should scroll to match the current
|
||||
scroll position in kitty. See :opt:`scrollback_pager` for details
|
||||
Replaced the number of lines a pager should scroll to match the current
|
||||
scroll position in kitty. See :opt:`scrollback_pager` for details.
|
||||
|
||||
``@scrolled-by``
|
||||
replaced by the number of lines kitty is currently scrolled by
|
||||
Replaced by the number of lines kitty is currently scrolled by.
|
||||
|
||||
``@cursor-x``
|
||||
replaced by the current cursor x position with 1 being the leftmost cell
|
||||
Replaced by the current cursor x position with 1 being the leftmost cell.
|
||||
|
||||
``@cursor-y``
|
||||
replaced by the current cursor y position with 1 being the topmost cell
|
||||
Replaced by the current cursor y position with 1 being the topmost cell.
|
||||
|
||||
``@first-line-on-screen``
|
||||
Replaced by the first line on screen. Can be used for pager positioning.
|
||||
|
||||
``@last-line-on-screen``
|
||||
Replaced by the last line on screen. Can be used for pager positioning.
|
||||
|
||||
|
||||
For example::
|
||||
@@ -106,44 +112,51 @@ For example::
|
||||
Watching launched windows
|
||||
---------------------------
|
||||
|
||||
The :option:`launch --watcher` option allows you to specify python functions
|
||||
The :option:`launch --watcher` option allows you to specify Python functions
|
||||
that will be called at specific events, such as when the window is resized or
|
||||
closed. Simply specify the path to a python module that specifies callback
|
||||
closed. Simply specify the path to a Python module that specifies callback
|
||||
functions for the events you are interested in, for example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def on_resize(boss, window, data):
|
||||
from typing import Any, Dict
|
||||
|
||||
from kitty.boss import Boss
|
||||
from kitty.window import Window
|
||||
|
||||
|
||||
def on_resize(boss: Boss, window: Window, data: Dict[str, Any]) -> None:
|
||||
# Here data will contain old_geometry and new_geometry
|
||||
|
||||
def on_focus_change(boss, window, data):
|
||||
def on_focus_change(boss: Boss, window: Window, data: Dict[str, Any])-> None:
|
||||
# Here data will contain focused
|
||||
|
||||
def on_close(boss, window, data):
|
||||
def on_close(boss: Boss, window: Window, data: Dict[str, Any])-> None:
|
||||
# called when window is closed, typically when the program running in
|
||||
# it exits.
|
||||
|
||||
|
||||
Every callback is passed a reference to the global ``Boss`` object as well as
|
||||
the ``Window`` object the action is occurring on. The ``data`` object is
|
||||
a dict that contains event dependent data. Some useful methods and attributes
|
||||
for the ``Window`` object are: ``as_text(as_ans=False, add_history=False,
|
||||
the ``Window`` object the action is occurring on. The ``data`` object is a dict
|
||||
that contains event dependent data. Some useful methods and attributes for the
|
||||
``Window`` object are: ``as_text(as_ans=False, add_history=False,
|
||||
add_wrap_markers=False, alternate_screen=False)`` with which you can get the
|
||||
contents of the window and its scrollback buffer. Similarly,
|
||||
``window.child.pid`` is the PID of the processes that was launched
|
||||
in the window and ``window.id`` is the internal kitty ``id`` of the
|
||||
window.
|
||||
in the window and ``window.id`` is the internal kitty ``id`` of the window.
|
||||
|
||||
|
||||
Finding executables
|
||||
-----------------------
|
||||
|
||||
When you specify a command to run as just a name rather than an absolute path,
|
||||
it is searched for in the system-wide ``PATH`` environment variable. Note that
|
||||
this **may not** be the value of ``PATH`` inside a shell, as shell startup scripts
|
||||
often change the value of this variable. If it is not found there, then a
|
||||
system specific list of default paths is searched. If it is still not found,
|
||||
then your shell is run and the value of ``PATH`` inside the shell is used.
|
||||
it is searched for in the system-wide :envvar:`PATH` environment variable. Note
|
||||
that this **may not** be the value of :envvar:`PATH` inside a shell, as shell
|
||||
startup scripts often change the value of this variable. If it is not found
|
||||
there, then a system specific list of default paths is searched. If it is still
|
||||
not found, then your shell is run and the value of :envvar:`PATH` inside the
|
||||
shell is used.
|
||||
|
||||
See :opt:`exe_search_path` for details and how to control this.
|
||||
|
||||
Syntax reference
|
||||
|
||||
116
docs/layouts.rst
116
docs/layouts.rst
@@ -10,7 +10,8 @@ other in arbitrary arrangements, based on *Layouts*, see below for examples:
|
||||
:align: center
|
||||
:width: 100%
|
||||
|
||||
Screenshot, showing vim, tig and git running in |kitty| with the 'Tall' layout
|
||||
Screenshot, showing :program:`vim`, :program:`tig` and :program:`git`
|
||||
running in |kitty| with the *Tall* layout
|
||||
|
||||
|
||||
.. figure:: screenshots/splits.png
|
||||
@@ -18,21 +19,22 @@ other in arbitrary arrangements, based on *Layouts*, see below for examples:
|
||||
:align: center
|
||||
:width: 100%
|
||||
|
||||
Screenshot, showing windows with arbitrary arrangement in the 'Splits'
|
||||
Screenshot, showing windows with arbitrary arrangement in the *Splits*
|
||||
layout
|
||||
|
||||
|
||||
There are many different layouts available. They are all enabled by default,
|
||||
you can switch layouts using :sc:`next_layout`. To control which layouts
|
||||
are available use :opt:`enabled_layouts`, the first listed layout becomes
|
||||
the default. Individual layouts and how to use them are described below.
|
||||
There are many different layouts available. They are all enabled by default, you
|
||||
can switch layouts using :ac:`next_layout` (:sc:`next_layout` by default). To
|
||||
control which layouts are available use :opt:`enabled_layouts`, the first listed
|
||||
layout becomes the default. Individual layouts and how to use them are described
|
||||
below.
|
||||
|
||||
|
||||
The Stack Layout
|
||||
------------------
|
||||
|
||||
This is the simplest layout it displays a single window using all available
|
||||
space, other windows are hidden behind it. It has no options::
|
||||
This is the simplest layout. It displays a single window using all available
|
||||
space, other windows are hidden behind it. This layout has no options::
|
||||
|
||||
enabled_layouts stack
|
||||
|
||||
@@ -40,14 +42,14 @@ space, other windows are hidden behind it. It has no options::
|
||||
The Tall Layout
|
||||
------------------
|
||||
|
||||
Displays one (or optionally more) full height windows on the left half of the
|
||||
Displays one (or optionally more) full-height windows on the left half of the
|
||||
screen. Remaining windows are tiled vertically on the right half of the screen.
|
||||
There are options to control how the screen is split horizontally ``bias``
|
||||
(an integer between ``10`` and ``90``) and options to control how many
|
||||
full-height windows there are ``full_size`` (a positive integer). The
|
||||
``mirrored`` option when set to ``true`` will cause the short windows to be
|
||||
on the left side of the screen instead of the right. The syntax
|
||||
for the options is shown below::
|
||||
``mirrored`` option when set to ``true`` will cause the full-height windows to
|
||||
be on the right side of the screen instead of the left. The syntax
|
||||
for the options is::
|
||||
|
||||
enabled_layouts tall:bias=50;full_size=1;mirrored=false
|
||||
|
||||
@@ -65,24 +67,32 @@ for the options is shown below::
|
||||
│ │ │
|
||||
└──────────────┴───────────────┘
|
||||
|
||||
In addition, you can map keys to increase or decrease the number of full size
|
||||
windows, for example::
|
||||
In addition, you can map keys to increase or decrease the number of full-height
|
||||
windows, or toggle the mirrored setting, for example::
|
||||
|
||||
map ctrl+[ layout_action decrease_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
|
||||
----------------
|
||||
|
||||
Displays one (or optionally more) full width windows on the top half of the
|
||||
screen. Remaining windows are tiled horizontally on the bottom half of the screen.
|
||||
There are options to control how the screen is split vertically ``bias``
|
||||
Displays one (or optionally more) full-width windows on the top half of the
|
||||
screen. Remaining windows are tiled horizontally on the bottom half of the
|
||||
screen. There are options to control how the screen is split vertically ``bias``
|
||||
(an integer between ``10`` and ``90``) and options to control how many
|
||||
full-height windows there are ``full_size`` (a positive integer). The
|
||||
``mirrored`` option when set to ``true`` will cause the narrow windows to be
|
||||
on the top of the screen instead of the bottom. The syntax for the options is
|
||||
shown below::
|
||||
full-width windows there are ``full_size`` (a positive integer). The
|
||||
``mirrored`` option when set to ``true`` will cause the full-width windows to be
|
||||
on the bottom of the screen instead of the top. The syntax for the options is::
|
||||
|
||||
enabled_layouts fat:bias=50;full_size=1;mirrored=false
|
||||
|
||||
@@ -100,11 +110,15 @@ shown below::
|
||||
└─────────┴──────────┴─────────┘
|
||||
|
||||
|
||||
This layout also supports the same layout actions as the *Tall* layout, shown above.
|
||||
|
||||
|
||||
The Grid Layout
|
||||
--------------------
|
||||
|
||||
Display windows in a balanced grid with all windows the same size except the
|
||||
last column if there are not enough windows to fill the grid. Has no options::
|
||||
last column if there are not enough windows to fill the grid. This layout has no
|
||||
options::
|
||||
|
||||
enabled_layouts grid
|
||||
|
||||
@@ -132,15 +146,20 @@ define a few extra key bindings in :file:`kitty.conf`::
|
||||
|
||||
# Create a new window splitting the space used by the existing one so that
|
||||
# the two windows are placed one above the other
|
||||
map F5 launch --location=hsplit
|
||||
map f5 launch --location=hsplit
|
||||
|
||||
# Create a new window splitting the space used by the existing one so that
|
||||
# the two windows are placed side by side
|
||||
map F6 launch --location=vsplit
|
||||
map f6 launch --location=vsplit
|
||||
|
||||
# Create a new window splitting the space used by the existing one so that
|
||||
# the two windows are placed side by side if the existing window is wide or
|
||||
# one above the other if the existing window is tall.
|
||||
map f4 launch --location=split
|
||||
|
||||
# Rotate the current split, chaging its split axis from vertical to
|
||||
# horizontal or vice versa
|
||||
map F7 layout_action rotate
|
||||
map f7 layout_action rotate
|
||||
|
||||
# Move the active window in the indicated direction
|
||||
map shift+up move_window up
|
||||
@@ -148,22 +167,28 @@ define a few extra key bindings in :file:`kitty.conf`::
|
||||
map shift+right move_window right
|
||||
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
|
||||
map ctrl+left neighboring_window left
|
||||
map ctrl+right neighboring_window right
|
||||
map ctrl+up neighboring_window up
|
||||
map ctrl+down neighboring_window down
|
||||
|
||||
Windows can be resized using :ref:`window_resizing`. You can swap the windows
|
||||
Windows can be resized using :ref:`window_resizing`. You can swap the windows
|
||||
in a split using the ``rotate`` action with an argument of ``180`` and rotate
|
||||
and swap with an argument of ``270``.
|
||||
|
||||
This layout takes one option, ``split_axis`` that controls whether new windows
|
||||
are placed into vertical or horizontal splits when a ``--location`` is not
|
||||
specified. A value of ``horizontal`` (same as ``--location=vsplit``)
|
||||
means when a new split is created the two windows will be placed side by side
|
||||
and a value of ``vertical`` (same as ``--location=hsplit``) means the two
|
||||
windows will be placed one on top of the other. By default::
|
||||
are placed into vertical or horizontal splits when a :option:`--location <launch
|
||||
--location>` is not specified. A value of ``horizontal`` (same as
|
||||
``--location=vsplit``) means when a new split is created the two windows will be
|
||||
placed side by side and a value of ``vertical`` (same as ``--location=hsplit``)
|
||||
means the two windows will be placed one on top of the other. By default::
|
||||
|
||||
enabled_layouts splits:split_axis=horizontal
|
||||
|
||||
@@ -188,7 +213,7 @@ windows will be placed one on top of the other. By default::
|
||||
The Horizontal Layout
|
||||
------------------------
|
||||
|
||||
All windows are shown side by side. Has no options::
|
||||
All windows are shown side by side. This layout has no options::
|
||||
|
||||
enabled_layouts horizontal
|
||||
|
||||
@@ -208,7 +233,7 @@ All windows are shown side by side. Has no options::
|
||||
The Vertical Layout
|
||||
-----------------------
|
||||
|
||||
All windows are shown one below the other. Has no options::
|
||||
All windows are shown one below the other. This layout has no options::
|
||||
|
||||
enabled_layouts vertical
|
||||
|
||||
@@ -234,37 +259,38 @@ Resizing windows
|
||||
|
||||
You can resize windows inside layouts. Press :sc:`start_resizing_window` (also
|
||||
:kbd:`⌘+r` on macOS) to enter resizing mode and follow the on-screen
|
||||
instructions. In a given window layout only some operations may be possible
|
||||
for a particular window. For example, in the Tall layout you can make the first
|
||||
instructions. In a given window layout only some operations may be possible for
|
||||
a particular window. For example, in the *Tall* layout you can make the first
|
||||
window wider/narrower, but not taller/shorter. Note that what you are resizing
|
||||
is actually not a window, but a row/column in the layout, all windows in that
|
||||
row/column will be resized.
|
||||
|
||||
You can also define shortcuts in :file:`kitty.conf` to make the active window
|
||||
wider, narrower, taller, or shorter by mapping to the ``resize_window``
|
||||
wider, narrower, taller, or shorter by mapping to the :ac:`resize_window`
|
||||
action, for example::
|
||||
|
||||
map ctrl+left resize_window narrower
|
||||
map ctrl+right resize_window wider
|
||||
map ctrl+up resize_window taller
|
||||
map ctrl+down resize_window shorter 3
|
||||
# reset all windows in the tab to default sizes
|
||||
map ctrl+home resize_window reset
|
||||
|
||||
The ``resize_window`` action has a second, optional argument to control
|
||||
The :ac:`resize_window` action has a second optional argument to control
|
||||
the resizing increment (a positive integer that defaults to 1).
|
||||
|
||||
|
||||
Some layouts take options to control their behavior. For example, the ``fat``
|
||||
and ``tall`` layouts accept the ``bias`` and ``full_size`` options to control
|
||||
how the available space is split up.
|
||||
To specify the option, in :opt:`kitty.conf <enabled_layouts>` use::
|
||||
Some layouts take options to control their behavior. For example, the *Fat*
|
||||
and *Tall* layouts accept the ``bias`` and ``full_size`` options to control
|
||||
how the available space is split up. To specify the option, in :opt:`kitty.conf
|
||||
<enabled_layouts>` use::
|
||||
|
||||
enabled_layouts tall:bias=70;full_size=2
|
||||
|
||||
This will have ``2`` instead of a single tall window, that occupy ``70%``
|
||||
instead of ``50%`` of available width. ``bias`` can be any number between 10
|
||||
and 90.
|
||||
instead of ``50%`` of available width. ``bias`` can be any number between ``10``
|
||||
and ``90``.
|
||||
|
||||
Writing a new layout only requires about two hundred lines of code, so if there
|
||||
is some layout you want, take a look at one of the existing layouts in the
|
||||
`layout <https://github.com/kovidgoyal/kitty/tree/master/kitty/layout>`_
|
||||
`layout <https://github.com/kovidgoyal/kitty/tree/master/kitty/layout>`__
|
||||
package and submit a pull request!
|
||||
|
||||
@@ -9,12 +9,12 @@ running programs or similar. Lets start with a few examples:
|
||||
Examples
|
||||
----------
|
||||
|
||||
Suppose we want to be able to highlight the word ERROR in the current window.
|
||||
Add the following to :file:`kitty.conf`::
|
||||
Suppose we want to be able to highlight the word :code:`ERROR` in the current
|
||||
window. Add the following to :file:`kitty.conf`::
|
||||
|
||||
map f1 toggle_marker text 1 ERROR
|
||||
|
||||
Now when you press :kbd:`F1` all instances of the word :code:`ERROR` will be
|
||||
Now when you press :kbd:`F1`, all instances of the word :code:`ERROR` will be
|
||||
highlighted. To turn off the highlighting, press :kbd:`F1` again.
|
||||
If you want to make it case-insensitive, use::
|
||||
|
||||
@@ -39,41 +39,40 @@ can control the colors used for these groups in :file:`kitty.conf` with::
|
||||
|
||||
|
||||
.. note::
|
||||
For performance reasons, matching is done per line only, and only when that line is
|
||||
altered in any way. So you cannot match text that stretches across multiple
|
||||
lines.
|
||||
For performance reasons, matching is done per line only, and only when that
|
||||
line is altered in any way. So you cannot match text that stretches across
|
||||
multiple lines.
|
||||
|
||||
|
||||
Creating markers dynamically
|
||||
---------------------------------
|
||||
|
||||
If you want to create markers dynamically rather than pre-defining them in
|
||||
:file:`kitty.conf` you can do so as follows::
|
||||
:file:`kitty.conf`, you can do so as follows::
|
||||
|
||||
map f1 create_marker
|
||||
map f2 remove_marker
|
||||
|
||||
Then pressing :kbd:`F1` will allow you to enter the marker definition and set
|
||||
it and pressing :kbd:`F2` will remove the marker. ``create_marker`` accepts
|
||||
the same syntax as ``toggle_marker`` above. Note that while creating
|
||||
markers, the prompt has history so you can easily re-use previous marker
|
||||
expressions.
|
||||
Then pressing :kbd:`F1` will allow you to enter the marker definition and set it
|
||||
and pressing :kbd:`F2` will remove the marker. :ac:`create_marker` accepts the
|
||||
same syntax as :ac:`toggle_marker` above. Note that while creating markers, the
|
||||
prompt has history so you can easily re-use previous marker expressions.
|
||||
|
||||
You can also use the facilities for :doc:`remote-control` to dynamically
|
||||
add/remove markers.
|
||||
You can also use the facilities for :doc:`remote-control` to dynamically add or
|
||||
remove markers.
|
||||
|
||||
|
||||
Scrolling to marks
|
||||
--------------------
|
||||
|
||||
kitty has an action to scroll to the next line that contains a mark. You can
|
||||
use it by mapping it to some shortcut in :file:`kitty.conf`::
|
||||
kitty has a :ac:`scroll_to_mark` action to scroll to the next line that contains
|
||||
a mark. You can use it by mapping it to some shortcut in :file:`kitty.conf`::
|
||||
|
||||
map ctrl+p scroll_to_mark prev
|
||||
map ctrl+n scroll_to_mark next
|
||||
|
||||
Then pressing :kbd:`ctrl+p` will scroll to the first line in the scrollback
|
||||
buffer above the current top line that contains a mark. Pressing :kbd:`ctrl+n`
|
||||
Then pressing :kbd:`Ctrl+P` will scroll to the first line in the scrollback
|
||||
buffer above the current top line that contains a mark. Pressing :kbd:`Ctrl+N`
|
||||
will scroll to show the first line below the current last line that contains
|
||||
a mark. If you wish to jump to a mark of a specific type, you can add that to
|
||||
the mapping::
|
||||
@@ -86,26 +85,26 @@ Which will scroll only to marks of type 1.
|
||||
The full syntax for creating marks
|
||||
-------------------------------------
|
||||
|
||||
The syntax of the :code:`toggle_marker` command is::
|
||||
The syntax of the :ac:`toggle_marker` action is::
|
||||
|
||||
toggle_marker <marker-type> <specification>
|
||||
|
||||
Here :code:`marker-type` is one of:
|
||||
|
||||
* :code:`text` - simple substring matching
|
||||
* :code:`itext` - case-insensitive substring matching
|
||||
* :code:`regex` - A python regular expression
|
||||
* :code:`iregex` - A case-insensitive python regular expression
|
||||
* :code:`function` - An arbitrary function defined in a python file, see :ref:`marker_funcs`.
|
||||
* :code:`text` - Simple substring matching
|
||||
* :code:`itext` - Case-insensitive substring matching
|
||||
* :code:`regex` - A Python regular expression
|
||||
* :code:`iregex` - A case-insensitive Python regular expression
|
||||
* :code:`function` - An arbitrary function defined in a Python file, see :ref:`marker_funcs`.
|
||||
|
||||
.. _marker_funcs:
|
||||
|
||||
Arbitrary marker functions
|
||||
-----------------------------
|
||||
|
||||
You can create your own marker functions. Create a python file named
|
||||
:file:`mymarker.py` and in it create a :code:`marker` function. This
|
||||
function receives the text of the line as input and must yield three numbers,
|
||||
You can create your own marker functions. Create a Python file named
|
||||
:file:`mymarker.py` and in it create a :code:`marker` function. This function
|
||||
receives the text of the line as input and must yield three numbers,
|
||||
the starting character position, the ending character position and the mark
|
||||
group (1-3). For example:
|
||||
|
||||
@@ -122,6 +121,7 @@ Save this file somewhere and in :file:`kitty.conf`, use::
|
||||
|
||||
map f1 toggle_marker function /path/to/mymarker.py
|
||||
|
||||
If you save the file in the kitty config directory, you can use::
|
||||
If you save the file in the :ref:`kitty config directory <confloc>`, you can
|
||||
use::
|
||||
|
||||
map f1 toggle_marker function mymarker.py
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
Scripting the mouse click
|
||||
======================================================
|
||||
|
||||
|kitty| has support for `terminal hyperlinks
|
||||
<https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda>`_. These
|
||||
are generated by many terminal programs, such as ``ls``, ``gcc``, ``systemd``,
|
||||
:ref:`tool_mdcat`, etc. You can customize exactly what happens when clicking on these
|
||||
hyperlinks in |kitty|.
|
||||
|kitty| has support for :term:`terminal hyperlinks <hyperlinks>`. These are
|
||||
generated by many terminal programs, such as ``ls``, ``gcc``, ``systemd``,
|
||||
:ref:`tool_mdcat`, etc. You can customize exactly what happens when clicking on
|
||||
these hyperlinks in |kitty|.
|
||||
|
||||
You can tell kitty to take arbitrarily many, complex actions
|
||||
when a link is clicked. Let us illustrate with some examples, first. Create
|
||||
the file :file:`~/.config/kitty/open-actions.conf` with the following:
|
||||
You can tell kitty to take arbitrarily many, complex actions when a link is
|
||||
clicked. Let us illustrate with some examples, first. Create the file
|
||||
:file:`~/.config/kitty/open-actions.conf` with the following:
|
||||
|
||||
.. code:: conf
|
||||
|
||||
@@ -22,6 +21,13 @@ Now, run ``ls --hyperlink=auto`` in kitty and click on the filename of an
|
||||
image, holding down :kbd:`ctrl+shift`. It will be opened over the current
|
||||
window. Press any key to close it.
|
||||
|
||||
.. note::
|
||||
|
||||
The :program:`ls` comes with macOS does not support hyperlink, you need to
|
||||
install `GNU Coreutils <https://www.gnu.org/software/coreutils/>`__. If you
|
||||
install it via `Homebrew <https://formulae.brew.sh/formula/coreutils>`__, it
|
||||
will be :program:`gls`.
|
||||
|
||||
Each entry in :file:`open-actions.conf` consists of one or more
|
||||
:ref:`matching_criteria`, such as ``protocol`` and ``mime`` and one or more
|
||||
``action`` entries. In the example above kitty uses the :doc:`launch <launch>`
|
||||
@@ -29,8 +35,8 @@ action which can be used to run external programs. Entries are separated by
|
||||
blank lines.
|
||||
|
||||
Actions are very powerful, anything that you can map to a key combination in
|
||||
`kitty.conf` can be used as an action. You can specify more than one action per
|
||||
entry if you like, for example:
|
||||
:file:`kitty.conf` can be used as an action. You can specify more than one
|
||||
action per entry if you like, for example:
|
||||
|
||||
|
||||
.. code:: conf
|
||||
@@ -58,9 +64,13 @@ some special variables, documented below:
|
||||
``FRAGMENT``
|
||||
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.
|
||||
|
||||
|
||||
.. note::
|
||||
You can use the :opt:`action_alias` option just as in kitty.conf to
|
||||
You can use the :opt:`action_alias` option just as in :file:`kitty.conf` to
|
||||
define aliases for frequently used actions.
|
||||
|
||||
|
||||
@@ -77,7 +87,7 @@ lines. The various available criteria are:
|
||||
|
||||
``protocol``
|
||||
A comma separated list of protocols, for example: ``http, https``. If
|
||||
absent, there is no constraint on protocol
|
||||
absent, there is no constraint on protocol.
|
||||
|
||||
``url``
|
||||
A regular expression that must match against the entire (unquoted) URL
|
||||
@@ -88,11 +98,12 @@ lines. The various available criteria are:
|
||||
|
||||
``mime``
|
||||
A comma separated list of MIME types, for example: ``text/*, image/*,
|
||||
application/pdf``. You can add MIME types to kitty by creating the
|
||||
:file:`mime.types` in the kitty configuration directory. 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: ``text/plain rst
|
||||
md``. Note that the MIME type for directories is ``inode/directory``.
|
||||
application/pdf``. You can add MIME types to kitty by creating a file named
|
||||
:file:`mime.types` in the :ref:`kitty configuration directory <confloc>`.
|
||||
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:
|
||||
``text/plain rst md``. Note that the MIME type for directories is
|
||||
``inode/directory``.
|
||||
|
||||
``ext``
|
||||
A comma separated list of file extensions, for example: ``jpeg, tar.gz``
|
||||
@@ -102,39 +113,42 @@ lines. The various available criteria are:
|
||||
``image-??.png``
|
||||
|
||||
|
||||
.. _launch_actions:
|
||||
|
||||
Scripting the opening of files with kitty on macOS
|
||||
-------------------------------------------------------
|
||||
|
||||
On macOS you can use :guilabel:`Open With` in Finder or drag and drop files
|
||||
onto the kitty dock icon to open them with kitty. The default action
|
||||
is to open text files in your editor and images using the icat kitten.
|
||||
Shell scripts are run in a shell. You can customize these actions by creating
|
||||
a :file:`launch-actions.conf` file in the kitty config directory, just like
|
||||
the :file:`open-actions.conf` file above. For example:
|
||||
On macOS you can use :guilabel:`Open With` in Finder or drag and drop files and
|
||||
URLs onto the kitty dock icon to open them with kitty. The default actions are:
|
||||
|
||||
.. code:: conf
|
||||
* Open text files in your editor and images using the icat kitten.
|
||||
* Run shell scripts in a shell
|
||||
* Open SSH urls using the ssh command
|
||||
|
||||
# Open script files based on their shebangs
|
||||
protocol file
|
||||
ext sh,command,tool
|
||||
action launch --hold --type=os-window kitty +shebang $FILE_PATH {SHELL}
|
||||
These actions can also be executed from the command line by running::
|
||||
|
||||
# Open shell specific script files
|
||||
protocol file
|
||||
ext fish,bash,zsh
|
||||
action launch --hold --type=os-window kitty +shebang $FILE_PATH __ext__
|
||||
kitty +open file_or_url another_url ...
|
||||
|
||||
# Open directories
|
||||
protocol file
|
||||
mime inode/directory
|
||||
action launch --type=os-window --cwd $FILE_PATH
|
||||
# macOS only
|
||||
open -a kitty.app file_or_url another_url ...
|
||||
|
||||
# Open text files without fragments in the editor
|
||||
protocol file
|
||||
mime text/*
|
||||
action launch --type=os-window $EDITOR $FILE_PATH
|
||||
Since macOS lacks an official interface to set default URL scheme handlers,
|
||||
kitty has a command you can use for it. The first argument for is the URL
|
||||
scheme, and the second optional argument is the bundle id of the app, which
|
||||
defaults to kitty, if not specified. For example:
|
||||
|
||||
# Open image files with icat
|
||||
protocol file
|
||||
mime image/*
|
||||
action launch --type=os-window kitty +kitten icat --hold $FILE_PATH
|
||||
.. code-block:: sh
|
||||
|
||||
# Set kitty as the handler for ssh:// URLs
|
||||
kitty +runpy 'from kitty.fast_data_types import cocoa_set_url_handler; import sys; cocoa_set_url_handler(*sys.argv[1:]); print("OK")' ssh
|
||||
# Set someapp as the handler for xyz:// URLs
|
||||
kitty +runpy 'from kitty.fast_data_types import cocoa_set_url_handler; import sys; cocoa_set_url_handler(*sys.argv[1:]); print("OK")' xyz someapp.bundle.identifier
|
||||
|
||||
You can customize these actions by creating a :file:`launch-actions.conf` file
|
||||
in the :ref:`kitty config directory <confloc>`, just like the
|
||||
:file:`open-actions.conf` file above. For example:
|
||||
|
||||
.. literalinclude:: ../kitty/open_actions.py
|
||||
:language: conf
|
||||
:start-at: # Open script files
|
||||
:end-before: '''.splitlines()))
|
||||
|
||||
@@ -4,23 +4,22 @@ Overview
|
||||
Design philosophy
|
||||
-------------------
|
||||
|
||||
|kitty| is designed for power keyboard users. To that end all its controls
|
||||
work with the keyboard (although it fully supports mouse interactions as
|
||||
well). Its configuration is a simple, human editable, single file for
|
||||
easy reproducibility (I like to store configuration in source control).
|
||||
|kitty| is designed for power keyboard users. To that end all its controls work
|
||||
with the keyboard (although it fully supports mouse interactions as well). Its
|
||||
configuration is a simple, human editable, single file for easy reproducibility
|
||||
(I like to store configuration in source control).
|
||||
|
||||
The code in |kitty| is designed to be simple, modular and hackable. It is
|
||||
written in a mix of C (for performance sensitive parts) and Python (for
|
||||
easy hackability of the UI). It does not depend on any large and complex
|
||||
UI toolkit, using only OpenGL for rendering everything.
|
||||
written in a mix of C (for performance sensitive parts) and Python (for easy
|
||||
hackability of the UI). It does not depend on any large and complex UI toolkit,
|
||||
using only OpenGL for rendering everything.
|
||||
|
||||
Finally, |kitty| is designed from the ground up to support all modern
|
||||
terminal features, such as unicode, true color, bold/italic fonts, text
|
||||
formatting, etc. It even extends existing text formatting escape codes,
|
||||
to add support for features not available elsewhere, such as colored and
|
||||
styled (curly) underlines. One of the design goals of |kitty| is to be
|
||||
easily extensible so that new features can be added in the future with
|
||||
relatively little effort.
|
||||
Finally, |kitty| is designed from the ground up to support all modern terminal
|
||||
features, such as Unicode, true color, bold/italic fonts, text formatting, etc.
|
||||
It even extends existing text formatting escape codes, to add support for
|
||||
features not available elsewhere, such as colored and styled (curly) underlines.
|
||||
One of the design goals of |kitty| is to be easily extensible so that new
|
||||
features can be added in the future with relatively little effort.
|
||||
|
||||
.. include:: basic.rst
|
||||
|
||||
@@ -28,10 +27,10 @@ relatively little effort.
|
||||
Configuring kitty
|
||||
-------------------
|
||||
|
||||
|kitty| is highly configurable, everything from keyboard shortcuts to
|
||||
painting frames-per-second. Press :sc:`edit_config_file` in kitty
|
||||
to open its fully commented sample config file in your text editor.
|
||||
For details see the :doc:`configuration docs <conf>`.
|
||||
|kitty| is highly configurable, everything from keyboard shortcuts to painting
|
||||
frames-per-second. Press :sc:`edit_config_file` in kitty to open its fully
|
||||
commented sample config file in your text editor. For details see the
|
||||
:doc:`configuration docs <conf>`.
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
@@ -51,17 +50,26 @@ windows automatically, resizing and moving them as needed. You can create a new
|
||||
|
||||
Currently, there are seven layouts available:
|
||||
|
||||
* **Fat** -- One (or optionally more) windows are shown full width on the top, the rest of the windows are shown side-by-side on the bottom
|
||||
* **Fat** -- One (or optionally more) windows are shown full width on the top,
|
||||
the rest of the windows are shown side-by-side on the bottom
|
||||
|
||||
* **Grid** -- All windows are shown in a grid
|
||||
|
||||
* **Horizontal** -- All windows are shown side-by-side
|
||||
* **Splits** -- Windows arranged in arbitrary patterns created using horizontal and vertical splits
|
||||
|
||||
* **Splits** -- Windows arranged in arbitrary patterns created using horizontal
|
||||
and vertical splits
|
||||
|
||||
* **Stack** -- Only a single maximized window is shown at a time
|
||||
* **Tall** -- One (or optionally more) windows are shown full height on the left, the rest of the windows are shown one below the other on the right
|
||||
|
||||
* **Tall** -- One (or optionally more) windows are shown full height on the
|
||||
left, the rest of the windows are shown one below the other on the right
|
||||
|
||||
* **Vertical** -- All windows are shown one below the other
|
||||
|
||||
By default, all layouts are enabled and you can switch between layouts using
|
||||
the :sc:`next_layout` key combination. You can also create shortcuts to select
|
||||
particular layouts, and choose which layouts you want to enable/disable, see
|
||||
particular layouts, and choose which layouts you want to enable, see
|
||||
:ref:`conf-kitty-shortcuts.layout` for examples. The first layout listed in
|
||||
:opt:`enabled_layouts` becomes the default layout.
|
||||
|
||||
@@ -79,9 +87,9 @@ Extending kitty
|
||||
kitty has a powerful framework for scripting. You can create small terminal
|
||||
programs called :doc:`kittens <kittens_intro>`. These can used to add features
|
||||
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,
|
||||
:doc:`viewing images <kittens/icat>` or :doc:`diffing files with images
|
||||
:doc:`viewing images <kittens/icat>` or :doc:`diffing files with image support
|
||||
<kittens/diff>`.
|
||||
|
||||
You can :doc:`create your own kittens to scratch your own itches
|
||||
@@ -101,9 +109,9 @@ Remote control
|
||||
|kitty| has a very powerful system that allows you to control it from the
|
||||
:doc:`shell prompt, even over SSH <remote-control>`. You can change colors,
|
||||
fonts, open new :term:`windows <window>`, :term:`tabs <tab>`, set their titles,
|
||||
change window layout, get text
|
||||
from one window and send text to another, etc, etc. The possibilities are
|
||||
endless. See the :doc:`tutorial <remote-control>` to get started.
|
||||
change window layout, get text from one window and send text to another, etc.
|
||||
The possibilities are endless. See the :doc:`tutorial <remote-control>` to get
|
||||
started.
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
@@ -117,10 +125,9 @@ Startup Sessions
|
||||
------------------
|
||||
|
||||
You can control the :term:`tabs <tab>`, :term:`kitty window <window>` layout,
|
||||
working directory, startup programs,
|
||||
etc. by creating a "session" file and using the :option:`kitty --session`
|
||||
command line flag or the :opt:`startup_session` option in :file:`kitty.conf`.
|
||||
For example:
|
||||
working directory, startup programs, etc. by creating a *session* file and using
|
||||
the :option:`kitty --session` command line flag or the :opt:`startup_session`
|
||||
option in :file:`kitty.conf`. For example:
|
||||
|
||||
.. code-block:: session
|
||||
|
||||
@@ -130,37 +137,41 @@ For example:
|
||||
cd ~
|
||||
# Create a window and run the specified command in it
|
||||
launch zsh
|
||||
# Create a window with some environment variables set and run
|
||||
# vim in it
|
||||
# Create a window with some environment variables set and run vim in it
|
||||
launch --env FOO=BAR vim
|
||||
# Set the title for the next window
|
||||
launch --title "Chat with x" irssi --profile x
|
||||
|
||||
# Create a new tab (the part after new_tab is the optional tab
|
||||
# name which will be displayed in the tab bar, if omitted, the
|
||||
# title of the active window will be used instead)
|
||||
# Create a new tab
|
||||
# The part after new_tab is the optional tab title which will be displayed in
|
||||
# the tab bar, if omitted, the title of the active window will be used instead.
|
||||
new_tab my tab
|
||||
cd ~/somewhere
|
||||
# Set the layouts allowed in this tab
|
||||
enabled_layouts tall, stack
|
||||
enabled_layouts tall,stack
|
||||
# Set the current layout
|
||||
layout stack
|
||||
launch zsh
|
||||
|
||||
# Create a new OS window
|
||||
# Any definitions specifed before the first new_os_window will apply to first OS window.
|
||||
new_os_window
|
||||
# set new window size to 80x25 cells
|
||||
os_window_size 80c 25c
|
||||
# set the --class for the new OS window
|
||||
# Set new window size to 80x24 cells
|
||||
os_window_size 80c 24c
|
||||
# Set the --class for the new OS window
|
||||
os_window_class mywindow
|
||||
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
|
||||
# Make the current OS Window the globally active window (not supported on Wayland)
|
||||
focus_os_window
|
||||
launch emacs
|
||||
|
||||
.. note::
|
||||
The :doc:`launch <launch>` command when used in a session file
|
||||
cannot create new OS windows, or tabs.
|
||||
The :doc:`launch <launch>` command when used in a session file cannot create
|
||||
new OS windows, or tabs.
|
||||
|
||||
|
||||
Creating tabs/windows
|
||||
@@ -182,25 +193,25 @@ Mouse features
|
||||
* You can click on a URL to open it in a browser.
|
||||
* You can double click to select a word and then drag to select more words.
|
||||
* You can triple click to select a line and then drag to select more lines.
|
||||
* You can triple click while holding :kbd:`ctrl+alt` to select from clicked
|
||||
* You can triple click while holding :kbd:`Ctrl+Alt` to select from clicked
|
||||
point to end of line.
|
||||
* You can right click to extend a previous selection.
|
||||
* You can hold down :kbd:`ctrl+alt` and drag with the mouse to select in
|
||||
* You can hold down :kbd:`Ctrl+Alt` and drag with the mouse to select in
|
||||
columns.
|
||||
* Selecting text automatically copies it to the primary clipboard (on
|
||||
platforms with a primary clipboard).
|
||||
* You can middle click to paste from the primary clipboard (on platforms
|
||||
* Selecting text automatically copies it to the primary clipboard (on platforms
|
||||
with a primary clipboard).
|
||||
* You can right click while holding :kbd:`ctrl+shift` to open the output
|
||||
of the clicked on command in a pager (requires :ref:`shell_integration`)
|
||||
* You can select text with kitty even when a terminal program has grabbed
|
||||
the mouse by holding down the :kbd:`shift` key
|
||||
* You can middle click to paste from the primary clipboard (on platforms with a
|
||||
primary clipboard).
|
||||
* You can right click while holding :kbd:`Ctrl+Shift` to open the output of the
|
||||
clicked on command in a pager (requires :ref:`shell_integration`)
|
||||
* You can select text with kitty even when a terminal program has grabbed the
|
||||
mouse by holding down the :kbd:`Shift` key
|
||||
|
||||
All these actions can be customized in :file:`kitty.conf` as described
|
||||
:ref:`here <conf-kitty-mouse.mousemap>`.
|
||||
|
||||
You can also customize what happens when clicking on :term:`hyperlinks` in kitty,
|
||||
having it open files in your editor, download remote files, open things
|
||||
You can also customize what happens when clicking on :term:`hyperlinks` in
|
||||
kitty, having it open files in your editor, download remote files, open things
|
||||
in your browser, etc.
|
||||
|
||||
For details, see :doc:`here <open_actions>`.
|
||||
@@ -214,12 +225,11 @@ Font control
|
||||
-----------------
|
||||
|
||||
|kitty| has extremely flexible and powerful font selection features. You can
|
||||
specify individual families for the regular, bold, italic and bold+italic
|
||||
fonts. You can even specify specific font families for specific ranges of
|
||||
unicode characters. This allows precise control over text rendering. It can
|
||||
come in handy for applications like powerline, without the need to use patched
|
||||
fonts. See the various font related configuration directives in
|
||||
:ref:`conf-kitty-fonts`.
|
||||
specify individual families for the regular, bold, italic and bold+italic fonts.
|
||||
You can even specify specific font families for specific ranges of Unicode
|
||||
characters. This allows precise control over text rendering. It can comein handy
|
||||
for applications like powerline, without the need to use patched fonts. See the
|
||||
various font related configuration directives in :ref:`conf-kitty-fonts`.
|
||||
|
||||
|
||||
.. _scrollback:
|
||||
@@ -228,29 +238,29 @@ The scrollback buffer
|
||||
-----------------------
|
||||
|
||||
|kitty| supports scrolling back to view history, just like most terminals. You
|
||||
can use either keyboard shortcuts or the mouse scroll wheel to do so. However,
|
||||
|kitty| has an extra, neat feature. Sometimes you need to explore the
|
||||
scrollback buffer in more detail, maybe search for some text or refer to it
|
||||
side-by-side while typing in a follow-up command. |kitty| allows you to do this
|
||||
by pressing the :sc:`show_scrollback` key-combination, which will open the
|
||||
scrollback buffer in your favorite pager program (which is ``less`` by default).
|
||||
Colors and text formatting are preserved. You can explore the scrollback buffer
|
||||
comfortably within the pager.
|
||||
can use either keyboard shortcuts or the mouse scroll wheel to do so. However,
|
||||
|kitty| has an extra, neat feature. Sometimes you need to explore the scrollback
|
||||
buffer in more detail, maybe search for some text or refer to it side-by-side
|
||||
while typing in a follow-up command. |kitty| allows you to do this by pressing
|
||||
the :sc:`show_scrollback` shortcut, which will open the scrollback buffer in
|
||||
your favorite pager program (which is :program:`less` by default). Colors and
|
||||
text formatting are preserved. You can explore the scrollback buffer comfortably
|
||||
within the pager.
|
||||
|
||||
Additionally, you can pipe the contents of the scrollback buffer to an
|
||||
arbitrary, command running in a new :term:`window`, :term:`tab` or :term:`overlay`,
|
||||
for example::
|
||||
arbitrary, command running in a new :term:`window`, :term:`tab` or
|
||||
:term:`overlay`. For example::
|
||||
|
||||
map f1 launch --stdin-source=@screen_scrollback --stdin-add-formatting less +G -R
|
||||
|
||||
Would open the scrollback buffer in a new :term:`window` when you press the :kbd:`F1`
|
||||
key. See :sc:`show_scrollback <show_scrollback>` for details.
|
||||
Would open the scrollback buffer in a new :term:`window` when you press the
|
||||
:kbd:`F1` key. See :sc:`show_scrollback <show_scrollback>` for details.
|
||||
|
||||
If you want to use it with an editor such as vim to get more powerful features,
|
||||
you can see tips for doing so, in :iss:`this thread <719>`.
|
||||
If you want to use it with an editor such as :program:`vim` to get more powerful
|
||||
features, you can see tips for doing so, in :iss:`this thread <719>`.
|
||||
|
||||
If you wish to store very large amounts of scrollback to view using the piping or
|
||||
:sc:`show_scrollback <show_scrollback>` features, you can use the
|
||||
If you wish to store very large amounts of scrollback to view using the piping
|
||||
or :sc:`show_scrollback <show_scrollback>` features, you can use the
|
||||
:opt:`scrollback_pager_history_size` option.
|
||||
|
||||
|
||||
@@ -258,8 +268,8 @@ Integration with shells
|
||||
---------------------------------
|
||||
|
||||
kitty has the ability to integrate closely within common shells, such as `zsh
|
||||
<https://www.zsh.org/>`_, `fish <https://fishshell.com>`_ and `bash
|
||||
<https://www.gnu.org/software/bash/>`_ to enable features such as jumping to
|
||||
<https://www.zsh.org/>`__, `fish <https://fishshell.com>`__ and `bash
|
||||
<https://www.gnu.org/software/bash/>`__ to enable features such as jumping to
|
||||
previous prompts in the scrollback, viewing the output of the last command in
|
||||
:program:`less`, using the mouse to move the cursor while editing prompts, etc.
|
||||
See :doc:`shell-integration` for details.
|
||||
@@ -274,17 +284,16 @@ See :doc:`shell-integration` for details.
|
||||
Multiple copy/paste buffers
|
||||
-----------------------------
|
||||
|
||||
In addition to being able to copy/paste from the system clipboard, in |kitty| you
|
||||
can also setup an arbitrary number of copy paste buffers. To do so, simply add
|
||||
something like the following to your :file:`kitty.conf`::
|
||||
In addition to being able to copy/paste from the system clipboard, in |kitty|
|
||||
you can also setup an arbitrary number of copy paste buffers. To do so, simply
|
||||
add something like the following to your :file:`kitty.conf`::
|
||||
|
||||
map f1 copy_to_buffer a
|
||||
map f2 paste_from_buffer a
|
||||
|
||||
This will allow you to press :kbd:`F1` to copy the current selection to an
|
||||
internal buffer named ``a`` and :kbd:`F2` to paste from that buffer. The buffer
|
||||
names are arbitrary strings, so you can define as many such buffers as you
|
||||
need.
|
||||
names are arbitrary strings, so you can define as many such buffers as you need.
|
||||
|
||||
|
||||
Marks
|
||||
|
||||
@@ -2,30 +2,31 @@ Performance
|
||||
===================
|
||||
|
||||
The main goals for |kitty| performance are user perceived latency while typing
|
||||
and "smoothness" while scrolling as well as CPU usage. |kitty| tries hard to find
|
||||
an optimum balance for these. To that end it keeps a cache of each rendered
|
||||
glyph in video RAM so that font rendering is not a bottleneck. Interaction
|
||||
with child programs takes place in a separate thread from rendering, to improve
|
||||
and "smoothness" while scrolling as well as CPU usage. |kitty| tries hard to
|
||||
find an optimum balance for these. To that end it keeps a cache of each rendered
|
||||
glyph in video RAM so that font rendering is not a bottleneck. Interaction with
|
||||
child programs takes place in a separate thread from rendering, to improve
|
||||
smoothness.
|
||||
|
||||
There are two parameters you can tune to adjust the performance. :opt:`repaint_delay`
|
||||
and :opt:`input_delay`. These control the artificial delays introduced into the
|
||||
render loop to reduce CPU usage. See :ref:`conf-kitty-performance` for details.
|
||||
See also the :opt:`sync_to_monitor` option to further decrease latency at the cost
|
||||
of some `tearing <https://en.wikipedia.org/wiki/Screen_tearing>`_ while scrolling.
|
||||
There are two config options you can tune to adjust the performance,
|
||||
:opt:`repaint_delay` and :opt:`input_delay`. These control the artificial delays
|
||||
introduced into the render loop to reduce CPU usage. See
|
||||
:ref:`conf-kitty-performance` for details. See also the :opt:`sync_to_monitor`
|
||||
option to further decrease latency at the cost of some `screen tearing
|
||||
<https://en.wikipedia.org/wiki/Screen_tearing>`__ while scrolling.
|
||||
|
||||
You can generate detailed per-function performance data using `gperftools
|
||||
<https://github.com/gperftools/gperftools>`_. Build |kitty| with `make
|
||||
profile`. Run kitty and perform the task you want to analyse, for example,
|
||||
scrolling a large file with `less`. After you quit, function call statistics
|
||||
will be printed to `stdout` and you can use tools like *kcachegrind* for more
|
||||
detailed analysis.
|
||||
You can generate detailed per-function performance data using
|
||||
`gperftools <https://github.com/gperftools/gperftools>`__. Build |kitty| with
|
||||
``make profile``. Run kitty and perform the task you want to analyse, for
|
||||
example, scrolling a large file with :program:`less`. After you quit, function
|
||||
call statistics will be printed to STDOUT and you can use tools like
|
||||
*KCachegrind* for more detailed analysis.
|
||||
|
||||
Here are some CPU usage numbers for the task of scrolling a file continuously
|
||||
in less. The CPU usage is for the terminal process and X together and is
|
||||
measured using htop. The measurements are taken at the same font and window
|
||||
size for all terminals on a ``Intel(R) Core(TM) i7-4820K CPU @ 3.70GHz`` CPU
|
||||
with a ``Advanced Micro Devices, Inc. [AMD/ATI] Cape Verde XT [Radeon HD
|
||||
Here are some CPU usage numbers for the task of scrolling a file continuously in
|
||||
:program:`less`. The CPU usage is for the terminal process and X together and is
|
||||
measured using :program:`htop`. The measurements are taken at the same font and
|
||||
window size for all terminals on a ``Intel(R) Core(TM) i7-4820K CPU @ 3.70GHz``
|
||||
CPU with a ``Advanced Micro Devices, Inc. [AMD/ATI] Cape Verde XT [Radeon HD
|
||||
7770/8760 / R7 250X]`` GPU.
|
||||
|
||||
============== =========================
|
||||
@@ -49,11 +50,11 @@ admittedly biased, eyes).
|
||||
|
||||
.. note::
|
||||
|
||||
Some people have asked why kitty does not perform better than terminal XXX in
|
||||
the test of sinking large amounts of data, such as catting a large text
|
||||
file. The answer is because this is not a goal for kitty. kitty
|
||||
deliberately throttles input parsing and output rendering to minimize
|
||||
resource usage while still being able to sink output faster than any real
|
||||
world program can produce it. Reducing CPU usage, and hence battery drain
|
||||
while achieving instant response times and smooth scrolling to a human eye
|
||||
is a far more important goal.
|
||||
Some people have asked why kitty does not perform better than terminal XXX
|
||||
in the test of sinking large amounts of data, such as catting a large text
|
||||
file. The answer is because this is not a goal for kitty. kitty deliberately
|
||||
throttles input parsing and output rendering to minimize resource usage
|
||||
while still being able to sink output faster than any real world program can
|
||||
produce it. Reducing CPU usage, and hence battery drain while achieving
|
||||
instant response times and smooth scrolling to a human eye is a far more
|
||||
important goal.
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
Press mentions of kitty
|
||||
========================
|
||||
|
||||
`Console #88 <https://console.substack.com/p/console-88>`__
|
||||
`Python Bytes 272 <https://youtu.be/8HKliSbA-gQ?t=815>`__ (Feb 2022)
|
||||
A podcast demoing some of kitty's coolness
|
||||
|
||||
`Console #88 <https://console.substack.com/p/console-88>`__ (Jan 2022)
|
||||
An interview with Kovid about kitty
|
||||
|
||||
|
||||
Video reviews
|
||||
--------------
|
||||
|
||||
`Review (Jan 2021) <https://www.youtube.com/watch?v=TTzP2zYJn2k>`__
|
||||
A kitty review by distrotube
|
||||
|
||||
`Review (Dec 2020) <https://www.youtube.com/watch?v=KUMkLhFeBrI>`__
|
||||
A kitty review/intro by TechHut
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
Terminal protocol extensions
|
||||
===================================
|
||||
|
||||
|kitty| has extensions to the legacy terminal protocol, to enable advanced features.
|
||||
These are typically in the form of new or re-purposed escape codes. While these
|
||||
extensions are currently |kitty| specific, it would be nice to get some of them
|
||||
adopted more broadly, to push the state of terminal emulators forward.
|
||||
|kitty| has extensions to the legacy terminal protocol, to enable advanced
|
||||
features. These are typically in the form of new or re-purposed escape codes.
|
||||
While these extensions are currently |kitty| specific, it would be nice to get
|
||||
some of them adopted more broadly, to push the state of terminal emulators
|
||||
forward.
|
||||
|
||||
The goal of these extensions is to be as small and unobtrusive as possible,
|
||||
while filling in some gaps in the existing xterm protocol. In particular, one
|
||||
of the goals of this specification is explicitly not to "re-imagine" the tty.
|
||||
The tty should remain what it is -- a device for efficiently processing text
|
||||
while filling in some gaps in the existing xterm protocol. In particular, one of
|
||||
the goals of this specification is explicitly not to "re-imagine" the TTY. The
|
||||
TTY should remain what it is -- a device for efficiently processing text
|
||||
received as a simple byte stream. Another objective is to only move the minimum
|
||||
possible amount of extra functionality into the terminal program itself. This
|
||||
is to make it as easy to implement these protocol extensions as possible,
|
||||
thereby hopefully encouraging their widespread adoption.
|
||||
possible amount of extra functionality into the terminal program itself. This is
|
||||
to make it as easy to implement these protocol extensions as possible, thereby
|
||||
hopefully encouraging their widespread adoption.
|
||||
|
||||
If you wish to discuss these extensions, propose additions/changes to them
|
||||
please do so by opening issues in the `GitHub
|
||||
<https://github.com/kovidgoyal/kitty/issues>`_ bug tracker.
|
||||
If you wish to discuss these extensions, propose additions or changes to them,
|
||||
please do so by opening issues in the `GitHub bug tracker
|
||||
<https://github.com/kovidgoyal/kitty/issues>`__.
|
||||
|
||||
|
||||
.. toctree::
|
||||
|
||||
@@ -9,9 +9,9 @@ Quickstart
|
||||
binary
|
||||
build
|
||||
|
||||
Pre-built binaries of |kitty| are available for both macOS and Linux.
|
||||
See the :doc:`binary install instructions </binary>`. You can also
|
||||
:doc:`build from source </build>`.
|
||||
Pre-built binaries of |kitty| are available for both macOS and Linux. See the
|
||||
:doc:`binary install instructions </binary>`. You can also :doc:`build from
|
||||
source </build>`.
|
||||
|
||||
Additionally, you can use your favorite package manager to install the |kitty|
|
||||
package, but note that some Linux distribution packages are woefully outdated.
|
||||
@@ -25,4 +25,4 @@ and Linux.
|
||||
See :doc:`Configuring kitty <conf>` for help on configuring |kitty| and
|
||||
:doc:`Invocation <invocation>` for the command line arguments |kitty| supports.
|
||||
|
||||
For a tour of kitty's design and features, see the :doc:`/overview`.
|
||||
For a tour of kitty's design and features, see the :doc:`overview`.
|
||||
|
||||
@@ -7,34 +7,74 @@ form::
|
||||
|
||||
<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",
|
||||
"version": <kitty version>,
|
||||
"no_response": <Optional Boolean>,
|
||||
"payload": <Optional JSON object>,
|
||||
"version": "<kitty version>",
|
||||
"no_response": "<Optional Boolean>",
|
||||
"payload": "<Optional JSON object>"
|
||||
}
|
||||
|
||||
The ``version`` above is an array of the form :code:`[0, 14, 2]`. If you are developing a
|
||||
standalone client, use the kitty version that you are developing against. Using
|
||||
a version greater than the version of the kitty instance you are talking to,
|
||||
will cause a failure.
|
||||
The ``version`` above is an array of the form :code:`[0, 14, 2]`. If you are
|
||||
developing a standalone client, use the kitty version that you are developing
|
||||
against. Using a version greater than the version of the kitty instance you are
|
||||
talking to, will cause a failure.
|
||||
|
||||
Set ``no_response`` to ``true`` if you don't want a response from kitty.
|
||||
|
||||
The optional payload is a JSON object that is specific to the actual command being sent.
|
||||
The fields in the object for every command are documented below.
|
||||
The optional payload is a JSON object that is specific to the actual command
|
||||
being sent. The fields in the object for every command are documented below.
|
||||
|
||||
As a quick example showing how easy to use this protocol is, we will implement
|
||||
the ``@ ls`` command from the shell using only shell tools. First, run kitty
|
||||
as::
|
||||
the ``@ ls`` command from the shell using only shell tools.
|
||||
|
||||
First, run kitty as::
|
||||
|
||||
kitty -o allow_remote_control=socket-only --listen-on unix:/tmp/test
|
||||
|
||||
Now, in a different terminal, you can get the pretty printed ``@ ls`` output
|
||||
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 .
|
||||
|
||||
.. _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
|
||||
|
||||
@@ -4,9 +4,12 @@ Control kitty from scripts
|
||||
.. highlight:: sh
|
||||
|
||||
|kitty| can be controlled from scripts or the shell prompt. You can open new
|
||||
windows, send arbitrary text input to any window, name windows and tabs, etc.
|
||||
windows, send arbitrary text input to any window, change the title of windows
|
||||
and tabs, etc.
|
||||
|
||||
Let's walk through a few examples of controlling |kitty|.
|
||||
|
||||
|
||||
Tutorial
|
||||
------------
|
||||
|
||||
@@ -14,50 +17,52 @@ Start by running |kitty| as::
|
||||
|
||||
kitty -o allow_remote_control=yes -o enabled_layouts=tall
|
||||
|
||||
In order for control to work, :opt:`allow_remote_control` must be enabled in
|
||||
:file:`kitty.conf`. Here we turn it on explicitly at the command line.
|
||||
In order for control to work, :opt:`allow_remote_control` or
|
||||
: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::
|
||||
|
||||
kitty @ launch --title Output --keep-focus cat
|
||||
|
||||
This will open a new window, running the ``cat`` program that will appear next
|
||||
to the current window.
|
||||
This will open a new window, running the :program:`cat` program that will appear
|
||||
next to the current window.
|
||||
|
||||
Let's send some text to this new window::
|
||||
|
||||
kitty @ send-text --match cmdline:cat Hello, World
|
||||
|
||||
This will make ``Hello, World`` show up in the window running the ``cat`` program.
|
||||
The :option:`kitty @ send-text --match` option is very powerful, it 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 window, etc. See ``kitty @ send-text
|
||||
--help`` for details.
|
||||
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
|
||||
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
|
||||
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
|
||||
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 ls in the output window instead of the current
|
||||
window. You can use this technique to, for example, show the output of running
|
||||
``make`` in your editor in a different window. The possibilities are endless.
|
||||
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
|
||||
running :program:`make` in your editor in a different window. The possibilities
|
||||
are endless.
|
||||
|
||||
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
|
||||
window. Type ``Ctrl+D`` when you are ready to stop.
|
||||
window. Type :kbd:`Ctrl+D` when you are ready to stop.
|
||||
|
||||
Now, let's open a new tab::
|
||||
|
||||
kitty @ launch --type=tab --tab-title "My Tab" --keep-focus bash
|
||||
|
||||
This will open a new tab running the bash shell with the title "My Tab".
|
||||
We can change the title of the tab with::
|
||||
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::
|
||||
|
||||
@@ -65,79 +70,196 @@ Let's change the title of the current 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
|
||||
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::
|
||||
|
||||
kitty @ ls
|
||||
|
||||
This outputs a tree of data in JSON format. The top level of the tree is all
|
||||
operating system kitty windows. Each OS window has an id and a list of tabs.
|
||||
Each tab has its own id, a title and a list of windows. Each window has an id,
|
||||
title, current working directory, process id (PID) and command-line of the
|
||||
process running in the window. You can use this information with :option:`kitty @ focus-window --match`
|
||||
to control individual windows.
|
||||
:term:`OS windows <os_window>`. Each OS window has an id and a list of
|
||||
:term:`tabs <tab>`. Each tab has its own id, a title and a list of :term:`kitty
|
||||
windows <window>`. Each window has an id, title, current working directory,
|
||||
process id (PID) and command-line of the process running in the window. You can
|
||||
use this information with :option:`kitty @ focus-window --match` to control
|
||||
individual windows.
|
||||
|
||||
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. See ``kitty @ --help`` for more details.
|
||||
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.
|
||||
See ``kitty @ --help`` for more details.
|
||||
|
||||
Note that in the example's above, ``kitty @`` messaging works only when run 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 inside a |kitty| window, you have to implement a couple of
|
||||
extra steps. First start |kitty| as::
|
||||
In the example's above, ``kitty @`` messaging works only when run
|
||||
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
|
||||
inside a |kitty| window, see the section on :ref:`using a socket for remote control <rc_via_socket>`
|
||||
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
|
||||
|
||||
The :option:`kitty --listen-on` option tells |kitty| to listen for control messages at the
|
||||
specified path. See ``kitty --help`` for details. Now you can control this
|
||||
instance of |kitty| using the :option:`kitty @ --to` command line argument to ``kitty @``. For example::
|
||||
The :option:`kitty --listen-on` option tells |kitty| to listen for control
|
||||
messages at the specified UNIX-domain socket. See ``kitty --help`` for details.
|
||||
Now you can control this instance of |kitty| using the :option:`kitty @ --to`
|
||||
command line argument to ``kitty @``. For example::
|
||||
|
||||
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
|
||||
--------------------------
|
||||
|
||||
You can explore the |kitty| command language more easily using the builtin |kitty|
|
||||
shell. Run ``kitty @`` with no arguments and you will be dropped into the |kitty|
|
||||
shell with completion for |kitty| command names and options.
|
||||
You can explore the |kitty| command language more easily using the builtin
|
||||
|kitty| shell. Run ``kitty @`` with no arguments and you will be dropped into
|
||||
the |kitty| shell with completion for |kitty| command names and options.
|
||||
|
||||
You can even open the |kitty| shell inside a running |kitty| using a simple
|
||||
keyboard shortcut (:sc:`kitty_shell` by default).
|
||||
|
||||
.. note:: This has the added advantage that you don't need to use
|
||||
``allow_remote_control`` to make it work.
|
||||
:opt:`allow_remote_control` to make it work.
|
||||
|
||||
|
||||
Allowing only some windows to control kitty
|
||||
----------------------------------------------
|
||||
|
||||
If you do not want to allow all programs running in |kitty| to control it, you can selectively
|
||||
enable remote control for only some |kitty| windows. Simply create a shortcut
|
||||
such as::
|
||||
If you do not want to allow all programs running in |kitty| to control it, you
|
||||
can selectively enable remote control for only some |kitty| windows. Simply
|
||||
create a shortcut such as::
|
||||
|
||||
map ctrl+k launch --allow-remote-control some_program
|
||||
|
||||
Then programs running in windows created with that shortcut can use ``kitty @``
|
||||
to control kitty. Note that any program with the right level of permissions can
|
||||
still write to the pipes of any other program on the same computer and
|
||||
therefore can control |kitty|. It can, however, be useful to block programs
|
||||
running on other computers (for example, over ssh) or as other users.
|
||||
still write to the pipes of any other program on the same computer and therefore
|
||||
can control |kitty|. It can, however, be useful to block programs running on
|
||||
other computers (for example, over SSH) or as other users.
|
||||
|
||||
.. note:: You dont need ``allow_remote_control`` to make this work as it is
|
||||
.. note:: You don't need :opt:`allow_remote_control` to make this work as it is
|
||||
limited to only programs running in that specific window. Be careful with
|
||||
what programs you run in such windows, since they can effectively control
|
||||
kitty, as if you were running with ``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:
|
||||
@@ -148,15 +270,21 @@ Mapping key presses to remote control commands
|
||||
If you wish to trigger a remote control command easily with just a keypress,
|
||||
you can map it in :file:`kitty.conf`. For example::
|
||||
|
||||
map F1 remote_control set-spacing margin=30
|
||||
map f1 remote_control set-spacing margin=30
|
||||
|
||||
Then pressing the :kbd:`F1` key will set the active window margins to 30.
|
||||
The syntax for what follows :code:`remote_control` is exactly the same
|
||||
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
|
||||
as the syntax for what follows :code:`kitty @` above.
|
||||
|
||||
.. note:: You do not need ``allow_remote_control`` to use these mappings,
|
||||
as they are not actual remote programs, but are simply a way to resuse
|
||||
the remote control infrastructure via keybings.
|
||||
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,
|
||||
as they are not actual remote programs, but are simply a way to resuse the
|
||||
remote control infrastructure via keybings.
|
||||
|
||||
|
||||
Broadcasting what you type to all kitty windows
|
||||
@@ -166,17 +294,36 @@ As a simple illustration of the power of remote control, lets
|
||||
have what we type sent to all open kitty windows. To do that define the
|
||||
following mapping in :file:`kitty.conf`::
|
||||
|
||||
map F1 launch --allow-remote-control kitty +kitten broadcast
|
||||
map f1 launch --allow-remote-control kitty +kitten broadcast
|
||||
|
||||
Now press, F1 and start typing, what you type will be sent to all windows,
|
||||
Now press :kbd:`F1` and start typing, what you type will be sent to all windows,
|
||||
live, as you type it.
|
||||
|
||||
|
||||
The remote control protocol
|
||||
-----------------------------------------------
|
||||
|
||||
If you wish to develop your own client to talk to |kitty|, you
|
||||
can use the :doc:`protocol specification <rc_protocol>`.
|
||||
If you wish to develop your own client to talk to |kitty|, you can use the
|
||||
:doc:`remote control protocol specification <rc_protocol>`.
|
||||
|
||||
|
||||
.. _search_syntax:
|
||||
|
||||
Matching windows and tabs
|
||||
----------------------------
|
||||
|
||||
Many remote control operations operate on windows or tabs. To select these, the
|
||||
:code:`--match` option is often used. This allows matching using various
|
||||
sophisticated criteria such as title, ids, cmdlines, etc. These criteria are
|
||||
expressions of the form :code:`field:query`. Where :italic:`field` is the field
|
||||
against which to match and :italic:`query` is the expression to match. They can
|
||||
be further combined using Boolean operators, best illustrated with some
|
||||
examples::
|
||||
|
||||
title:"My special window" or id:43
|
||||
title:bash and env:USER=kovid
|
||||
not id:1
|
||||
(id:2 or id:3) and title:something
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
@@ -4,8 +4,8 @@ Shell integration
|
||||
-------------------
|
||||
|
||||
kitty has the ability to integrate closely within common shells, such as `zsh
|
||||
<https://www.zsh.org/>`_, `fish <https://fishshell.com>`_ and `bash
|
||||
<https://www.gnu.org/software/bash/>`_ to enable features such as jumping to
|
||||
<https://www.zsh.org/>`__, `fish <https://fishshell.com>`__ and `bash
|
||||
<https://www.gnu.org/software/bash/>`__ to enable features such as jumping to
|
||||
previous prompts in the scrollback, viewing the output of the last command in
|
||||
:program:`less`, using the mouse to move the cursor while editing prompts, etc.
|
||||
|
||||
@@ -22,7 +22,7 @@ Features
|
||||
|
||||
* Click with the mouse anywhere in the current command to move the cursor there
|
||||
|
||||
* Hold :kbd:`ctrl+shift` and right-click on any command output in the scrollback
|
||||
* Hold :kbd:`Ctrl+Shift` and right-click on any command output in the scrollback
|
||||
to view it in a pager
|
||||
|
||||
* The current working directory or the command being executed are automatically
|
||||
@@ -30,37 +30,57 @@ Features
|
||||
|
||||
* The text cursor is changed to a bar when editing commands at the shell prompt
|
||||
|
||||
* :ref:`clone_shell` with all environment variables and the working directory
|
||||
copied
|
||||
|
||||
* :ref:`Edit files in new kitty windows <edit_file>` even over SSH
|
||||
|
||||
* Glitch free window resizing even with complex prompts. Achieved by erasing
|
||||
the prompt on resize and allowing the shell to redraw it cleanly.
|
||||
|
||||
* Sophisticated completion for the :program:`kitty` command in the shell.
|
||||
|
||||
* 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
|
||||
---------------
|
||||
|
||||
Shell integration is controlled by the :opt:`shell_integration` option. By
|
||||
default, all shell integration is enabled. Individual features can be turned
|
||||
default, all integration features are enabled. Individual features can be turned
|
||||
off or it can be disabled entirely as well. The :opt:`shell_integration` option
|
||||
takes a space separated list of keywords:
|
||||
|
||||
disabled
|
||||
Turn off all shell integration
|
||||
Turn off all shell integration. The shell's launch environment is not
|
||||
modified and :envvar:`KITTY_SHELL_INTEGRATION` is not set. Useful for
|
||||
:ref:`manual integration <manual_shell_integration>`.
|
||||
|
||||
no-rc
|
||||
Do not modify the shell's launch environment to enable integration. Useful if you prefer
|
||||
to :ref:`manually enable integration <manual_shell_integration>`.
|
||||
Do not modify the shell's launch environment to enable integration. Useful
|
||||
if you prefer to load the kitty shell integration code yourself, either as
|
||||
part of :ref:`manually integration <manual_shell_integration>` or because
|
||||
you have some other software that sets up shell integration.
|
||||
This will still set the :envvar:`KITTY_SHELL_INTEGRATION` environment
|
||||
variable when kitty runs the shell.
|
||||
|
||||
no-cursor
|
||||
Turn off changing of the text cursor to a bar when editing text
|
||||
Turn off changing of the text cursor to a bar when editing shell command
|
||||
line.
|
||||
|
||||
no-title
|
||||
Turn off setting the kitty window/tab title based on shell state.
|
||||
Note that for the ``fish`` shell kitty relies on fish's native title
|
||||
setting functionality instead.
|
||||
Note that for the fish shell kitty relies on fish's native title setting
|
||||
functionality instead.
|
||||
|
||||
no-cwd
|
||||
Turn off reporting the current working directory. This is used to allow
|
||||
:ac:`new_window_with_cwd` and similar to open windows logged into remote
|
||||
machines using the :doc:`ssh kitten <kittens/ssh>` automatically with the
|
||||
same working directory as the current window.
|
||||
Note that for the fish shell this will not disable its built-in current
|
||||
working directory reporting.
|
||||
|
||||
no-prompt-mark
|
||||
Turn off marking of prompts. This disables jumping to prompt, browsing
|
||||
@@ -68,16 +88,16 @@ no-prompt-mark
|
||||
|
||||
no-complete
|
||||
Turn off completion for the kitty command.
|
||||
Note that for the ``fish`` shell this does not take effect, since fish
|
||||
already comes with a kitty completion script.
|
||||
Note that for the fish shell this does not take effect, since fish already
|
||||
comes with a kitty completion script.
|
||||
|
||||
|
||||
More ways to browse command output
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can add further key and mouse bindings to browse the output of commands
|
||||
easily. For example to select the output of a command by right clicking the mouse
|
||||
on the output, define the following in :file:`kitty.conf`:
|
||||
easily. For example to select the output of a command by right clicking the
|
||||
mouse on the output, define the following in :file:`kitty.conf`:
|
||||
|
||||
.. code:: conf
|
||||
|
||||
@@ -88,8 +108,8 @@ to be copied.
|
||||
|
||||
The feature to jump to previous prompts (
|
||||
:sc:`scroll_to_previous_prompt` and :sc:`scroll_to_next_prompt`) and mouse
|
||||
actions (:ref:`action-mouse_select_command_output` and :ref:`action-mouse_show_command_output`) can
|
||||
be integrated with browsing command output as well. For example, define the
|
||||
actions (:ac:`mouse_select_command_output` and :ac:`mouse_show_command_output`)
|
||||
can be integrated with browsing command output as well. For example, define the
|
||||
following mapping in :file:`kitty.conf`:
|
||||
|
||||
.. code:: conf
|
||||
@@ -121,38 +141,41 @@ How it works
|
||||
-----------------
|
||||
|
||||
At startup, kitty detects if the shell you have configured (either system wide
|
||||
or in kitty.conf) is a supported shell. If so, kitty injects some shell specific
|
||||
code into the shell, to enable shell integration. How it does so varies for
|
||||
different shells.
|
||||
or the :opt:`shell` option in :file:`kitty.conf`) is a supported shell. If so,
|
||||
kitty injects some shell specific code into the shell, to enable shell
|
||||
integration. How it does so varies for different shells.
|
||||
|
||||
|
||||
.. tab:: zsh
|
||||
|
||||
For zsh, kitty sets the ``ZDOTDIR`` environment variable to make zsh load
|
||||
kitty's :file:`.zshenv` which restores the original value of ``ZDOTDIR``
|
||||
and sources the original :file:`.zshenv`. It then loads the shell integration code.
|
||||
The remainder of zsh's startup process proceeds as normal.
|
||||
For zsh, kitty sets the :envvar:`ZDOTDIR` environment variable to make zsh
|
||||
load kitty's :file:`.zshenv` which restores the original value of
|
||||
:envvar:`ZDOTDIR` and sources the original :file:`.zshenv`. It then loads
|
||||
the shell integration code. The remainder of zsh's startup process proceeds
|
||||
as normal.
|
||||
|
||||
.. tab:: fish
|
||||
|
||||
For fish, to make it automatically load the integration code provided by
|
||||
kitty, the integration script directory path is prepended to the
|
||||
:code:`XDG_DATA_DIRS` environment variable. This is only applied to the fish
|
||||
process and will be cleaned up by the integration script after startup. No files
|
||||
are added or modified.
|
||||
:envvar:`XDG_DATA_DIRS` environment variable. This is only applied to the
|
||||
fish process and will be cleaned up by the integration script after startup.
|
||||
No files are added or modified.
|
||||
|
||||
.. tab:: bash
|
||||
|
||||
For bash, kitty adds a couple of lines to the bottom of :file:`~/.bashrc`
|
||||
(in an atomic manner) to load the shell integration code.
|
||||
For bash, kitty starts bash in POSIX mode, using the environment variable
|
||||
:envvar:`ENV` to load the shell integration script. This prevents bash from
|
||||
loading any startup files itself. The loading of the startup files is done
|
||||
by the integration script, after disabling POSIX mode. From the perspective
|
||||
of those scripts there should be no difference to running vanilla bash.
|
||||
|
||||
|
||||
Then, when launching the shell, kitty sets the environment variable
|
||||
:envvar:`KITTY_SHELL_INTEGRATION` to the value of the :opt:`shell_integration`
|
||||
option. The shell integration code reads the environment variable, turns on the
|
||||
specified integration functionality and then unsets the variable so as to not
|
||||
pollute the system. This has the nice effect that the changes to the shell's rc
|
||||
files become no-ops when running the shell in anything other than kitty itself.
|
||||
pollute the system.
|
||||
|
||||
The actual shell integration code uses hooks provided by each shell to send
|
||||
special escape codes to kitty, to perform the various tasks. You can see the
|
||||
@@ -185,13 +208,97 @@ code used for each shell below:
|
||||
</details>
|
||||
|
||||
|
||||
Shell integration over SSH
|
||||
----------------------------
|
||||
|
||||
The easiest way to have shell integration work when SSHing into remote systems
|
||||
is to use the :doc:`ssh kitten <kittens/ssh>`. Simply run::
|
||||
|
||||
kitty +kitten ssh hostname
|
||||
|
||||
And, by magic, you will be logged into the remote system with fully functional
|
||||
shell integration. Alternately, you can :ref:`setup shell integration manually
|
||||
<manual_shell_integration>`, by copying the kitty shell integration scripts to
|
||||
the remote server and editing the shell rc files there, as described below.
|
||||
|
||||
|
||||
.. _clone_shell:
|
||||
|
||||
Clone the current shell into a new window
|
||||
-----------------------------------------------
|
||||
|
||||
You can clone the current shell into a new kitty window by simply running the
|
||||
:command:`clone-in-kitty` command, for example:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
clone-in-kitty
|
||||
clone-in-kitty --type=tab
|
||||
clone-in-kitty --title "I am a clone"
|
||||
|
||||
This will open a new window running a new shell instance but with all
|
||||
environment variables and the current working directory copied. This even works
|
||||
over SSH when using :doc:`kittens/ssh`.
|
||||
|
||||
The :command:`clone-in-kitty` command takes almost all the same arguments as the
|
||||
:doc:`launch <launch>` command, so you can open a new tab instead or a new OS
|
||||
window, etc. Arguments of launch that can cause code execution or that don't
|
||||
make sense when cloning are ignored. Most prominently, the following options are
|
||||
ignored: :option:`--allow-remote-control <launch --allow-remote-control>`,
|
||||
:option:`--copy-cmdline <launch --copy-cmdline>`, :option:`--copy-env <launch
|
||||
--copy-env>`, :option:`--stdin-source <launch --stdin-source>`,
|
||||
:option:`--marker <launch --marker>` and :option:`--watcher <launch --watcher>`.
|
||||
|
||||
:command:`clone-in-kitty` can be configured to source arbitrary code in the
|
||||
cloned window using environment variables. It will automatically clone virtual
|
||||
environments created by the :link:`Python venv module
|
||||
<https://docs.python.org/3/library/venv.html>` or :link:`Conda
|
||||
<https://conda.io/>`. In addition, setting the
|
||||
env var :envvar:`KITTY_CLONE_SOURCE_CODE` to some shell code will cause that
|
||||
code to be run in the cloned window with :code:`eval`. Similarly, setting
|
||||
:envvar:`KITTY_CLONE_SOURCE_PATH` to the path of a file will cause that file to
|
||||
be sourced in the cloned window. This can be controlled by
|
||||
:opt:`clone_source_strategies`.
|
||||
|
||||
:command:`clone-in-kitty` works by asking the shell to serialize its internal
|
||||
state (mainly CWD and env vars) and this state is transmitted to kitty and
|
||||
restored by the shell integration scripts in the cloned window.
|
||||
|
||||
|
||||
.. _edit_file:
|
||||
|
||||
Edit files in new kitty windows even over SSH
|
||||
------------------------------------------------
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
edit-in-kitty myfile.txt
|
||||
edit-in-kitty --type tab --title "Editing My File" myfile.txt
|
||||
# open myfile.txt at line 75 (works with vim, neovim, emacs, nano, micro)
|
||||
edit-in-kitty +75 myfile.txt
|
||||
|
||||
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
|
||||
SSH (if you use the :doc:`ssh kitten <kittens/ssh>`), allowing you
|
||||
to easily edit remote files in your local editor with all its bells and
|
||||
whistles.
|
||||
|
||||
The :command:`edit-in-kitty` command takes almost all the same arguments as the
|
||||
:doc:`launch <launch>` command, so you can open a new tab instead or a new OS
|
||||
window, etc. Not all arguments are supported, see the discussion in the
|
||||
:ref:`clone_shell` section above.
|
||||
|
||||
In order to avoid remote code execution, kitty will only execute the configured
|
||||
editor and pass the file path to edit to it.
|
||||
|
||||
|
||||
.. _manual_shell_integration:
|
||||
|
||||
Manual shell integration
|
||||
----------------------------
|
||||
|
||||
The automatic shell integration is designed to be minimally intrusive, as such
|
||||
it wont work for sub-shells, terminal multiplexers, containers, remote systems, etc.
|
||||
it wont work for sub-shells, terminal multiplexers, containers, etc.
|
||||
For such systems, you should setup manual shell integration by adding some code
|
||||
to your shells startup files to load the shell integration script.
|
||||
|
||||
@@ -239,18 +346,17 @@ The value of :envvar:`KITTY_SHELL_INTEGRATION` is the same as that for
|
||||
completely, in which case simply do not set the
|
||||
:envvar:`KITTY_SHELL_INTEGRATION` variable at all.
|
||||
|
||||
If you want this to work while SSHing into a remote system, then you will
|
||||
need to add some code to the snippets above to check if :envvar:`KITTY_INSTALLATION_DIR`
|
||||
is empty and if so to set it to some hard coded location with the shell
|
||||
integration scripts that need to be copied onto the remote system.
|
||||
|
||||
In a container, you will need to install the kitty shell integration scripts
|
||||
and make sure the :envvar:`KITTY_INSTALLATION_DIR` environment variable is set
|
||||
to point to the location of the scripts.
|
||||
|
||||
Integration with other shells
|
||||
-------------------------------
|
||||
|
||||
There exist third-party integrations to use these features for various other shells:
|
||||
There exist third-party integrations to use these features for various other
|
||||
shells:
|
||||
|
||||
* Jupyter console via a patch (:iss:`4475`)
|
||||
* Jupyter console and IPython via a patch (:iss:`4475`)
|
||||
* `xonsh <https://github.com/xonsh/xonsh/issues/4623>`__
|
||||
|
||||
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
Colored and styled underlines
|
||||
================================
|
||||
|
||||
|kitty| supports colored and styled (wavy) underlines. This is of particular
|
||||
use in terminal editors such as vim and emacs to display red, wavy underlines
|
||||
under mis-spelled words and/or syntax errors. This is done by re-purposing some
|
||||
SGR escape codes that are not used in modern terminals (`CSI codes
|
||||
<https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences>`_)
|
||||
|kitty| supports colored and styled (wavy) underlines. This is of particular use
|
||||
in terminal based text editors such as :program:`vim` and :program:`emacs` to
|
||||
display red, wavy underlines under mis-spelled words and/or syntax errors. This
|
||||
is done by re-purposing some SGR escape codes that are not used in modern
|
||||
terminals (`CSI codes <https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences>`__)
|
||||
|
||||
To set the underline style::
|
||||
|
||||
<ESC>[4:0m # this is no underline
|
||||
<ESC>[4:1m # this is a straight underline
|
||||
<ESC>[4:2m # this is a double underline
|
||||
<ESC>[4:3m # this is a curly underline
|
||||
<ESC>[4:4m # this is a dotted underline
|
||||
<ESC>[4:5m # this is a dashed underline
|
||||
<ESC>[4m # this is a straight underline (for backwards compat)
|
||||
<ESC>[24m # this is no underline (for backwards compat)
|
||||
<ESC>[4:0m # no underline
|
||||
<ESC>[4:1m # straight underline
|
||||
<ESC>[4:2m # double underline
|
||||
<ESC>[4:3m # curly underline
|
||||
<ESC>[4:4m # dotted underline
|
||||
<ESC>[4:5m # dashed underline
|
||||
<ESC>[4m # straight underline (for backwards compat)
|
||||
<ESC>[24m # no underline (for backwards compat)
|
||||
|
||||
To set the underline color (this is reserved and as far as I can tell not actually used for anything)::
|
||||
To set the underline color (this is reserved and as far as I can tell not
|
||||
actually used for anything)::
|
||||
|
||||
<ESC>[58...m
|
||||
|
||||
@@ -29,8 +30,8 @@ To reset the underline color (also previously reserved and unused)::
|
||||
|
||||
<ESC>[59m
|
||||
|
||||
The underline color must remain the same under reverse video, if it has a
|
||||
color, if not, it should follow the foreground color.
|
||||
The underline color must remain the same under reverse video, if it has a color,
|
||||
if not, it should follow the foreground color.
|
||||
|
||||
To detect support for this feature in a terminal emulator, query the terminfo database
|
||||
for the ``Su`` boolean capability.
|
||||
To detect support for this feature in a terminal emulator, query the terminfo
|
||||
database for the ``Su`` boolean capability.
|
||||
|
||||
@@ -4,10 +4,10 @@ Unscrolling the screen
|
||||
========================
|
||||
|
||||
This is a small extension to the `SD (Pan up) escape code
|
||||
<https://vt100.net/docs/vt510-rm/SD.html>`_ from the VT-420 terminal. The
|
||||
``SD`` escape code normally causes the text on screen to scroll down by the
|
||||
specified number of lines, with empty lines appearing at the top of the screen.
|
||||
This extension allows the new lines to be filled in from the scrollback buffer
|
||||
<https://vt100.net/docs/vt510-rm/SD.html>`_ from the VT-420 terminal. The ``SD``
|
||||
escape code normally causes the text on screen to scroll down by the specified
|
||||
number of lines, with empty lines appearing at the top of the screen. This
|
||||
extension allows the new lines to be filled in from the scrollback buffer
|
||||
instead of being blank.
|
||||
|
||||
The motivation for this is that many modern shells will show completions in a
|
||||
@@ -22,17 +22,17 @@ scrolled down is implementation defined, but must be at least one screen worth.
|
||||
|
||||
The syntax of the escape code is identical to that of ``SD`` except that it has
|
||||
a trailing ``+`` modifier. This is legal under the `ECMA 48 standard
|
||||
<https://www.ecma-international.org/publications-and-standards/standards/ecma-48/>`_
|
||||
<https://www.ecma-international.org/publications-and-standards/standards/ecma-48/>`__
|
||||
and unused for any other purpose as far as I can tell. So for example, to
|
||||
unscroll three lines, the escape code would be::
|
||||
|
||||
CSI 3 + T
|
||||
|
||||
See `discussion here
|
||||
<https://gitlab.freedesktop.org/terminal-wg/specifications/-/issues/30>`_.
|
||||
<https://gitlab.freedesktop.org/terminal-wg/specifications/-/issues/30>`__.
|
||||
|
||||
.. versionadded:: 0.20.2
|
||||
|
||||
Also supported by the terminals:
|
||||
|
||||
* `mintty <https://github.com/mintty/mintty/releases/tag/3.5.2>`_
|
||||
* `mintty <https://github.com/mintty/mintty/releases/tag/3.5.2>`__
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!./kitty/launcher/kitty +launch
|
||||
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
@@ -38,6 +38,8 @@ def main() -> None:
|
||||
|
||||
from kittens.diff.options.definition import definition as kd
|
||||
write_output('kittens.diff', kd)
|
||||
from kittens.ssh.options.definition import definition as sd
|
||||
write_output('kittens.ssh', sd)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -358,32 +358,49 @@ static GLFWapplicationwillfinishlaunchingfun finish_launching_callback = NULL;
|
||||
finish_launching_callback();
|
||||
}
|
||||
|
||||
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename {
|
||||
(void)theApplication;
|
||||
if (!filename || !_glfw.ns.file_open_callback) return NO;
|
||||
const char *path = NULL;
|
||||
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
|
||||
(void)sender;
|
||||
if (!filename || !_glfw.ns.url_open_callback) return NO;
|
||||
const char *url = NULL;
|
||||
@try {
|
||||
path = [[NSFileManager defaultManager] fileSystemRepresentationWithPath: filename];
|
||||
url = [[[NSURL fileURLWithPath:filename] absoluteString] UTF8String];
|
||||
} @catch(NSException *exc) {
|
||||
NSLog(@"Converting openFile filename: %@ failed with error: %@", filename, exc.reason);
|
||||
return NO;
|
||||
}
|
||||
if (!path) return NO;
|
||||
return _glfw.ns.file_open_callback(path);
|
||||
if (!url) return NO;
|
||||
return _glfw.ns.url_open_callback(url);
|
||||
}
|
||||
|
||||
- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames {
|
||||
(void)sender;
|
||||
if (!_glfw.ns.file_open_callback || !filenames) return;
|
||||
if (!_glfw.ns.url_open_callback || !filenames) return;
|
||||
for (id x in filenames) {
|
||||
NSString *filename = x;
|
||||
const char *path = NULL;
|
||||
const char *url = NULL;
|
||||
@try {
|
||||
path = [[NSFileManager defaultManager] fileSystemRepresentationWithPath: filename];
|
||||
url = [[[NSURL fileURLWithPath:filename] absoluteString] UTF8String];
|
||||
} @catch(NSException *exc) {
|
||||
NSLog(@"Converting openFiles filename: %@ failed with error: %@", filename, exc.reason);
|
||||
}
|
||||
if (path) _glfw.ns.file_open_callback(path);
|
||||
if (url) _glfw.ns.url_open_callback(url);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove openFile and openFiles when the minimum supported macOS version is 10.13
|
||||
- (void)application:(NSApplication *)sender openURLs:(NSArray<NSURL *> *)urls
|
||||
{
|
||||
(void)sender;
|
||||
if (!_glfw.ns.url_open_callback || !urls) return;
|
||||
for (id x in urls) {
|
||||
NSURL *ns_url = x;
|
||||
const char *url = NULL;
|
||||
@try {
|
||||
url = [[ns_url absoluteString] UTF8String];
|
||||
} @catch(NSException *exc) {
|
||||
NSLog(@"Converting openURLs url: %@ failed with error: %@", ns_url, exc.reason);
|
||||
}
|
||||
if (url) _glfw.ns.url_open_callback(url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,7 +408,6 @@ static GLFWapplicationwillfinishlaunchingfun finish_launching_callback = NULL;
|
||||
{
|
||||
(void)notification;
|
||||
[NSApp stop:nil];
|
||||
if (_glfw.ns.file_open_callback) _glfw.ns.file_open_callback(":cocoa::application launched::");
|
||||
|
||||
CGDisplayRegisterReconfigurationCallback(display_reconfigured, NULL);
|
||||
_glfwCocoaPostEmptyEvent();
|
||||
@@ -563,7 +579,10 @@ is_shiftable_shortcut(int scv) {
|
||||
|
||||
static void
|
||||
build_global_shortcuts_lookup(void) {
|
||||
// dump these in a terminal with: defaults read com.apple.symbolichotkeys
|
||||
NSMutableDictionary<NSString*, NSNumber*> *temp = [NSMutableDictionary dictionaryWithCapacity:128]; // will be autoreleased
|
||||
NSMutableSet<NSNumber*> *temp_configured = [NSMutableSet setWithCapacity:128]; // will be autoreleased
|
||||
NSMutableSet<NSNumber*> *temp_missing_value = [NSMutableSet setWithCapacity:128]; // will be autoreleased
|
||||
NSDictionary *apple_settings = [[NSUserDefaults standardUserDefaults] persistentDomainForName:@"com.apple.symbolichotkeys"];
|
||||
if (apple_settings) {
|
||||
NSDictionary<NSString*, id> *symbolic_hotkeys = [apple_settings objectForKey:@"AppleSymbolicHotKeys"];
|
||||
@@ -574,9 +593,14 @@ build_global_shortcuts_lookup(void) {
|
||||
NSInteger sc = [key integerValue];
|
||||
NSDictionary *sc_value = obj;
|
||||
id enabled = [sc_value objectForKey:@"enabled"];
|
||||
if (!enabled || ![enabled isKindOfClass:[NSNumber class]] || ![(NSNumber*)enabled boolValue]) continue;
|
||||
if (!enabled || ![enabled isKindOfClass:[NSNumber class]]) continue;
|
||||
[temp_configured addObject:@(sc)];
|
||||
if (![enabled boolValue]) continue;
|
||||
id v = [sc_value objectForKey:@"value"];
|
||||
if (!v || ![v isKindOfClass:[NSDictionary class]]) continue;
|
||||
if (!v || ![v isKindOfClass:[NSDictionary class]]) {
|
||||
if ([enabled boolValue]) [temp_missing_value addObject:@(sc)];
|
||||
continue;
|
||||
}
|
||||
NSDictionary *value = v;
|
||||
id t = [value objectForKey:@"type"];
|
||||
if (!t || ![t isKindOfClass:[NSString class]] || ![t isEqualToString:@"standard"]) continue;
|
||||
@@ -601,6 +625,39 @@ build_global_shortcuts_lookup(void) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add global shortcut definitions when the default enabled shortcut is not defined,
|
||||
// or when the default enabled shortcut is not disabled and is missing a value.
|
||||
// Here are the shortcuts that are enabled by default in the standard ANSI (US) layout.
|
||||
// macOS provides separate configurations for some languages or keyboards.
|
||||
// In general, the rules here will not take effect.
|
||||
static char buf[64];
|
||||
#define S(i, t, m, k) if ([temp_configured member:@(i)] == nil || [temp_missing_value member:@(i)] != nil) { \
|
||||
snprintf(buf, sizeof(buf) - 1, #t":%lx:%ld", (unsigned long)m, (long)k); \
|
||||
temp[@(buf)] = @(i); \
|
||||
}
|
||||
|
||||
// launchpad & dock
|
||||
S(kSHKTurnDockHidingOnOrOff, c, (NSEventModifierFlagOption | NSEventModifierFlagCommand), 'd'); // Opt, Cmd, D
|
||||
// mission control
|
||||
S(kSHKMissionControl, v, NSEventModifierFlagControl, 126); // Ctrl, Arrow Up
|
||||
S(kSHKApplicationWindows, v, NSEventModifierFlagControl, 125); // Ctrl, Arrow Down
|
||||
// keyboard
|
||||
S(kSHKMoveFocusToTheMenuBar, v, NSEventModifierFlagControl, 120); // Ctrl, F2
|
||||
S(kSHKMoveFocusToTheDock, v, NSEventModifierFlagControl, 99); // Ctrl, F3
|
||||
S(kSHKMoveFocusToActiveOrNextWindow, v, NSEventModifierFlagControl, 118); // Ctrl, F4
|
||||
S(kSHKMoveFocusToActiveOrNextWindow, v, (NSEventModifierFlagShift | NSEventModifierFlagControl), 118); // Shift, Ctrl, F4
|
||||
S(kSHKMoveFocusToNextWindow, c, NSEventModifierFlagCommand, 96); // Cmd, `
|
||||
S(kSHKMoveFocusToNextWindow, c, (NSEventModifierFlagShift | NSEventModifierFlagCommand), 96); // Shift, Cmd, `
|
||||
S(kSHKMoveFocusToStatusMenus, v, NSEventModifierFlagControl, 100); // Ctrl, F8
|
||||
// input sources
|
||||
S(kSHKSelectThePreviousInputSource, c, NSEventModifierFlagControl, 32); // Ctrl, Space bar
|
||||
S(kSHKSelectNextSourceInInputMenu, c, (NSEventModifierFlagControl | NSEventModifierFlagOption), 32); // Ctrl, Opt, Space bar
|
||||
// spotlight
|
||||
S(kSHKShowSpotlightSearch, c, NSEventModifierFlagCommand, 32); // Cmd, Space bar
|
||||
S(kSHKShowFinderSearchWindow, c, (NSEventModifierFlagOption | NSEventModifierFlagCommand), 32); // Opt, Cmd, Space bar
|
||||
|
||||
#undef S
|
||||
global_shortcuts = [[NSDictionary dictionaryWithDictionary:temp] retain];
|
||||
/* NSLog(@"global_shortcuts: %@", global_shortcuts); */
|
||||
}
|
||||
@@ -610,11 +667,10 @@ is_active_apple_global_shortcut(NSEvent *event) {
|
||||
if (global_shortcuts == nil) build_global_shortcuts_lookup();
|
||||
NSEventModifierFlags modifierFlags = USEFUL_MODS([event modifierFlags]);
|
||||
static char lookup_key[64];
|
||||
|
||||
#define LOOKUP(t, k) \
|
||||
snprintf(lookup_key, sizeof(lookup_key) - 1, #t":%lx:%ld", (unsigned long)modifierFlags, (long)k); \
|
||||
NSNumber *sc = global_shortcuts[@(lookup_key)]; \
|
||||
if (sc != nil) return [sc intValue];
|
||||
if (sc != nil) return [sc intValue]; \
|
||||
|
||||
if ([event.charactersIgnoringModifiers length] == 1) {
|
||||
if (modifierFlags & NSEventModifierFlagShift) {
|
||||
@@ -714,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) {
|
||||
GLFWapplicationshouldhandlereopenfun previous = handle_reopen_callback;
|
||||
handle_reopen_callback = callback;
|
||||
@@ -752,9 +813,11 @@ int _glfwPlatformInit(void)
|
||||
[NSApp setDelegate:_glfw.ns.delegate];
|
||||
static struct {
|
||||
unsigned short virtual_key_code;
|
||||
NSEventModifierFlags input_source_switch_modifiers;
|
||||
NSTimeInterval timestamp;
|
||||
} last_keydown_shortcut_event;
|
||||
last_keydown_shortcut_event.virtual_key_code = 0xffff;
|
||||
last_keydown_shortcut_event.input_source_switch_modifiers = 0;
|
||||
|
||||
NSEvent* (^keydown_block)(NSEvent*) = ^ NSEvent* (NSEvent* event)
|
||||
{
|
||||
@@ -765,6 +828,7 @@ int _glfwPlatformInit(void)
|
||||
if ([[NSApp mainMenu] performKeyEquivalent:event]) {
|
||||
debug_key("keyDown triggerred global menu bar action 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 nil;
|
||||
}
|
||||
@@ -773,6 +837,16 @@ int _glfwPlatformInit(void)
|
||||
if (is_useful_apple_global_shortcut(global_shortcut)) {
|
||||
debug_key("keyDown triggerred global macOS shortcut ignoring\n");
|
||||
last_keydown_shortcut_event.virtual_key_code = [event keyCode];
|
||||
// record the modifier keys if switching to the next input source
|
||||
last_keydown_shortcut_event.input_source_switch_modifiers = (global_shortcut == kSHKSelectNextSourceInInputMenu) ? USEFUL_MODS([event modifierFlags]) : 0;
|
||||
last_keydown_shortcut_event.timestamp = [event timestamp];
|
||||
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;
|
||||
}
|
||||
@@ -805,6 +879,12 @@ int _glfwPlatformInit(void)
|
||||
debug_key("-------------- flags changed -----------------\n");
|
||||
debug_key("%s\n", [[event description] UTF8String]);
|
||||
last_keydown_shortcut_event.virtual_key_code = 0xffff;
|
||||
// switching to the next input source is only confirmed when all modifier keys are released
|
||||
if (last_keydown_shortcut_event.input_source_switch_modifiers) {
|
||||
if (!([event modifierFlags] & last_keydown_shortcut_event.input_source_switch_modifiers))
|
||||
last_keydown_shortcut_event.input_source_switch_modifiers = 0;
|
||||
return event;
|
||||
}
|
||||
NSWindow *kw = [NSApp keyWindow];
|
||||
if (kw && kw.contentView) [kw.contentView flagsChanged:event];
|
||||
else debug_key("flagsChanged ignored as no keyWindow present\n");
|
||||
@@ -912,8 +992,6 @@ void _glfwPlatformTerminate(void)
|
||||
_glfw.ns.appleSettings = nil;
|
||||
}
|
||||
|
||||
free(_glfw.ns.clipboardString);
|
||||
|
||||
_glfwTerminateNSGL();
|
||||
if (global_shortcuts != nil) { [global_shortcuts release]; global_shortcuts = nil; }
|
||||
|
||||
|
||||
10
glfw/cocoa_platform.h
vendored
10
glfw/cocoa_platform.h
vendored
@@ -68,7 +68,7 @@ typedef void* CVDisplayLinkRef;
|
||||
|
||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int, unsigned long);
|
||||
typedef bool (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||
typedef bool (* GLFWhandlefileopen)(const char*);
|
||||
typedef bool (* GLFWhandleurlopen)(const char*);
|
||||
typedef void (* GLFWapplicationwillfinishlaunchingfun)(void);
|
||||
typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
|
||||
typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);
|
||||
@@ -131,6 +131,7 @@ typedef struct _GLFWwindowNS
|
||||
bool maximized;
|
||||
bool retina;
|
||||
bool in_traditional_fullscreen;
|
||||
bool in_fullscreen_transition;
|
||||
bool titlebar_hidden;
|
||||
unsigned long pre_full_screen_style_mask;
|
||||
|
||||
@@ -153,6 +154,8 @@ typedef struct _GLFWwindowNS
|
||||
// Whether a render frame has been requested for this window
|
||||
bool renderFrameRequested;
|
||||
GLFWcocoarenderframefun renderFrameCallback;
|
||||
// update cursor after switching desktops with Mission Control
|
||||
bool delayed_cursor_update_requested;
|
||||
} _GLFWwindowNS;
|
||||
|
||||
typedef struct _GLFWDisplayLinkNS
|
||||
@@ -180,7 +183,6 @@ typedef struct _GLFWlibraryNS
|
||||
|
||||
char keyName[64];
|
||||
char text[256];
|
||||
char* clipboardString;
|
||||
CGPoint cascadePoint;
|
||||
// Where to place the cursor when re-enabled
|
||||
double restoreCursorPosX, restoreCursorPosY;
|
||||
@@ -199,8 +201,8 @@ typedef struct _GLFWlibraryNS
|
||||
_GLFWDisplayLinkNS entries[256];
|
||||
size_t count;
|
||||
} displayLinks;
|
||||
// the callback to handle file open events
|
||||
GLFWhandlefileopen file_open_callback;
|
||||
// the callback to handle url open events
|
||||
GLFWhandleurlopen url_open_callback;
|
||||
|
||||
} _GLFWlibraryNS;
|
||||
|
||||
|
||||
@@ -29,6 +29,9 @@
|
||||
#include "internal.h"
|
||||
#include "../kitty/monotonic.h"
|
||||
|
||||
#include <Availability.h>
|
||||
#import <CoreServices/CoreServices.h>
|
||||
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
|
||||
#include <float.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -582,6 +585,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
||||
}
|
||||
|
||||
- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow;
|
||||
- (void)request_delayed_cursor_update:(id)sender;
|
||||
|
||||
@end
|
||||
|
||||
@@ -692,6 +696,9 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
||||
_glfwPlatformGetCursorPos(window, &x, &y);
|
||||
_glfwInputCursorPos(window, x, y);
|
||||
}
|
||||
// macOS will send a delayed event to update the cursor to arrow after switching desktops.
|
||||
// So we need to delay and update the cursor once after that.
|
||||
[self performSelector:@selector(request_delayed_cursor_update:) withObject:nil afterDelay:0.3];
|
||||
}
|
||||
|
||||
- (void)windowDidResignKey:(NSNotification *)notification
|
||||
@@ -722,6 +729,38 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
||||
}
|
||||
}
|
||||
|
||||
- (void)request_delayed_cursor_update:(id)sender
|
||||
{
|
||||
(void)sender;
|
||||
if (window) window->ns.delayed_cursor_update_requested = true;
|
||||
}
|
||||
|
||||
- (void)windowWillEnterFullScreen:(NSNotification *)notification
|
||||
{
|
||||
(void)notification;
|
||||
if (window) window->ns.in_fullscreen_transition = true;
|
||||
}
|
||||
|
||||
- (void)windowDidEnterFullScreen:(NSNotification *)notification
|
||||
{
|
||||
(void)notification;
|
||||
if (window) window->ns.in_fullscreen_transition = false;
|
||||
[self performSelector:@selector(request_delayed_cursor_update:) withObject:nil afterDelay:0.3];
|
||||
}
|
||||
|
||||
- (void)windowWillExitFullScreen:(NSNotification *)notification
|
||||
{
|
||||
(void)notification;
|
||||
if (window) window->ns.in_fullscreen_transition = true;
|
||||
}
|
||||
|
||||
- (void)windowDidExitFullScreen:(NSNotification *)notification
|
||||
{
|
||||
(void)notification;
|
||||
if (window) window->ns.in_fullscreen_transition = false;
|
||||
[self performSelector:@selector(request_delayed_cursor_update:) withObject:nil afterDelay:0.3];
|
||||
}
|
||||
|
||||
@end // }}}
|
||||
|
||||
// Text input context class for the GLFW content view {{{
|
||||
@@ -904,6 +943,11 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
||||
|
||||
window->ns.cursorWarpDeltaX = 0;
|
||||
window->ns.cursorWarpDeltaY = 0;
|
||||
|
||||
if (window->ns.delayed_cursor_update_requested) {
|
||||
window->ns.delayed_cursor_update_requested = false;
|
||||
if (cursorInContentArea(window)) updateCursorImage(window);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)rightMouseDown:(NSEvent *)event
|
||||
@@ -1003,6 +1047,8 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
||||
|
||||
- (void)updateTrackingAreas
|
||||
{
|
||||
if (window && [window->ns.object areCursorRectsEnabled])
|
||||
[window->ns.object disableCursorRects];
|
||||
if (trackingArea != nil)
|
||||
{
|
||||
[self removeTrackingArea:trackingArea];
|
||||
@@ -1325,14 +1371,17 @@ is_ascii_control_char(char x) {
|
||||
options:options];
|
||||
if (!objs) return NO;
|
||||
const NSUInteger count = [objs count];
|
||||
NSMutableString *uri_list = [NSMutableString stringWithCapacity:4096]; // auto-released
|
||||
if (count)
|
||||
{
|
||||
for (NSUInteger i = 0; i < count; i++)
|
||||
{
|
||||
id obj = objs[i];
|
||||
if ([obj isKindOfClass:[NSURL class]]) {
|
||||
const char *path = [obj fileSystemRepresentation];
|
||||
_glfwInputDrop(window, "text/plain;charset=utf-8", path, strlen(path));
|
||||
NSURL *url = (NSURL*)obj;
|
||||
if ([uri_list length] > 0) [uri_list appendString:@("\n")];
|
||||
if (url.fileURL) [uri_list appendString:url.filePathURL.absoluteString];
|
||||
else [uri_list appendString:url.absoluteString];
|
||||
} else if ([obj isKindOfClass:[NSString class]]) {
|
||||
const char *text = [obj UTF8String];
|
||||
_glfwInputDrop(window, "text/plain;charset=utf-8", text, strlen(text));
|
||||
@@ -1342,6 +1391,7 @@ is_ascii_control_char(char x) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if ([uri_list length] > 0) _glfwInputDrop(window, "text/uri-list", uri_list.UTF8String, strlen(uri_list.UTF8String));
|
||||
|
||||
return YES;
|
||||
}
|
||||
@@ -1493,6 +1543,39 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
|
||||
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;
|
||||
}
|
||||
|
||||
@end
|
||||
// }}}
|
||||
|
||||
@@ -1554,8 +1637,11 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
|
||||
|
||||
- (void)toggleFullScreen:(nullable id)sender
|
||||
{
|
||||
if (glfw_window && glfw_window->ns.toggleFullscreenCallback && glfw_window->ns.toggleFullscreenCallback((GLFWwindow*)glfw_window) == 1)
|
||||
return;
|
||||
if (glfw_window) {
|
||||
if (glfw_window->ns.in_fullscreen_transition) return;
|
||||
if (glfw_window->ns.toggleFullscreenCallback && glfw_window->ns.toggleFullscreenCallback((GLFWwindow*)glfw_window) == 1) return;
|
||||
glfw_window->ns.in_fullscreen_transition = true;
|
||||
}
|
||||
// When resizeIncrements is set, Cocoa cannot restore the original window size after returning from fullscreen.
|
||||
const NSSize original = [self resizeIncrements];
|
||||
[self setResizeIncrements:NSMakeSize(1.0, 1.0)];
|
||||
@@ -1681,7 +1767,11 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
||||
|
||||
if (!createNativeWindow(window, wndconfig, fbconfig))
|
||||
return false;
|
||||
[window->ns.object setColorSpace:[NSColorSpace sRGBColorSpace]];
|
||||
switch((GlfwCocoaColorSpaces)wndconfig->ns.color_space) {
|
||||
case SRGB_COLORSPACE: [window->ns.object setColorSpace:[NSColorSpace sRGBColorSpace]]; break;
|
||||
case DISPLAY_P3_COLORSPACE: [window->ns.object setColorSpace:[NSColorSpace displayP3ColorSpace]]; break;
|
||||
case DEFAULT_COLORSPACE: break;
|
||||
}
|
||||
|
||||
if (ctxconfig->client != GLFW_NO_API)
|
||||
{
|
||||
@@ -2418,38 +2508,150 @@ bool _glfwPlatformToggleFullscreen(_GLFWwindow* w, unsigned int flags) {
|
||||
return made_fullscreen;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetClipboardString(const char* string)
|
||||
{
|
||||
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
||||
[pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil];
|
||||
[pasteboard setString:@(string) forType:NSPasteboardTypeString];
|
||||
// Clipboard {{{
|
||||
|
||||
#define UTI_ROUNDTRIP_PREFIX @"uti-is-typical-apple-nih."
|
||||
|
||||
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)
|
||||
{
|
||||
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
||||
|
||||
if (![[pasteboard types] containsObject:NSPasteboardTypeString])
|
||||
{
|
||||
_glfwInputError(GLFW_FORMAT_UNAVAILABLE,
|
||||
"Cocoa: Failed to retrieve string from pasteboard");
|
||||
return NULL;
|
||||
static const char*
|
||||
uti_to_mime(NSString *uti) {
|
||||
if ([uti isEqualToString:NSPasteboardTypeString]) return "text/plain";
|
||||
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];
|
||||
}
|
||||
|
||||
NSString* object = [pasteboard stringForType:NSPasteboardTypeString];
|
||||
if (!object)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Cocoa: Failed to retrieve object from pasteboard");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
free(_glfw.ns.clipboardString);
|
||||
_glfw.ns.clipboardString = _glfw_strdup([object UTF8String]);
|
||||
|
||||
return _glfw.ns.clipboardString;
|
||||
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};
|
||||
NSArray* objs = [pasteboard readObjectsForClasses:@[[NSURL class], [NSString class]] options:options];
|
||||
bool found = false;
|
||||
if (objs) {
|
||||
const NSUInteger count = [objs count];
|
||||
if (count) {
|
||||
NSMutableData *path_list = [NSMutableData dataWithCapacity:4096]; // auto-released
|
||||
NSMutableData *text_list = [NSMutableData dataWithCapacity:4096]; // auto-released
|
||||
for (NSUInteger i = 0; i < count; i++) {
|
||||
id obj = objs[i];
|
||||
if ([obj isKindOfClass:[NSURL class]]) {
|
||||
NSURL *url = (NSURL*)obj;
|
||||
if (url.fileURL && url.fileSystemRepresentation) {
|
||||
if ([path_list length] > 0) [path_list appendBytes:"\n" length:1];
|
||||
[path_list appendBytes:url.fileSystemRepresentation length:strlen(url.fileSystemRepresentation)];
|
||||
}
|
||||
} else if ([obj isKindOfClass:[NSString class]]) {
|
||||
if ([text_list length] > 0) [text_list appendBytes:"\n" length:1];
|
||||
[text_list appendData:[obj dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
}
|
||||
}
|
||||
const NSMutableData *text = nil;
|
||||
if (path_list.length > 0) text = path_list;
|
||||
else if (text_list.length > 0) text = text_list;
|
||||
if (text) {
|
||||
found = true;
|
||||
write_data(object, text.mutableBytes, text.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found) _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to retrieve text/plain from pasteboard");
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (_glfw.egl.ANGLE_platform_angle)
|
||||
@@ -2642,10 +2844,10 @@ GLFWAPI GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow *hand
|
||||
return previous;
|
||||
}
|
||||
|
||||
GLFWAPI GLFWhandlefileopen glfwSetCocoaFileOpenCallback(GLFWhandlefileopen callback) {
|
||||
GLFWAPI GLFWhandleurlopen glfwSetCocoaURLOpenCallback(GLFWhandleurlopen callback) {
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(nil);
|
||||
GLFWhandlefileopen prev = _glfw.ns.file_open_callback;
|
||||
_glfw.ns.file_open_callback = callback;
|
||||
GLFWhandleurlopen prev = _glfw.ns.url_open_callback;
|
||||
_glfw.ns.url_open_callback = callback;
|
||||
return prev;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
return true;
|
||||
}
|
||||
@@ -487,6 +478,9 @@ GLFWAPI void glfwSwapBuffers(GLFWwindow* handle)
|
||||
}
|
||||
|
||||
window->context.swapBuffers(window);
|
||||
#ifdef _GLFW_WAYLAND
|
||||
_glfwWaylandAfterBufferSwap(window);
|
||||
#endif
|
||||
}
|
||||
|
||||
GLFWAPI void glfwSwapInterval(int interval)
|
||||
|
||||
2
glfw/dbus_glfw.c
vendored
2
glfw/dbus_glfw.c
vendored
@@ -263,7 +263,7 @@ call_method_with_msg(DBusConnection *conn, DBusMessage *msg, int timeout, dbus_p
|
||||
|
||||
static bool
|
||||
call_method(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout, dbus_pending_callback callback, void *user_data, va_list ap) {
|
||||
if (!conn) return false;
|
||||
if (!conn || !path) return false;
|
||||
DBusMessage *msg = dbus_message_new_method_call(node, path, interface, method);
|
||||
if (!msg) return false;
|
||||
bool retval = false;
|
||||
|
||||
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.SwapInterval, _glfw.egl.handle, "eglSwapInterval");
|
||||
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");
|
||||
|
||||
if (!_glfw.egl.GetConfigAttrib ||
|
||||
@@ -428,6 +429,8 @@ bool _glfwInitEGL(void)
|
||||
extensionSupportedEGL("EGL_KHR_get_all_proc_addresses");
|
||||
_glfw.egl.KHR_context_flush_control =
|
||||
extensionSupportedEGL("EGL_KHR_context_flush_control");
|
||||
_glfw.egl.EXT_present_opaque =
|
||||
extensionSupportedEGL("EGL_EXT_present_opaque");
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -598,6 +601,11 @@ bool _glfwCreateContextEGL(_GLFWwindow* window,
|
||||
if (_glfw.egl.KHR_gl_colorspace)
|
||||
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);
|
||||
|
||||
|
||||
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_eglSwapInterval)(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*);
|
||||
#define eglGetConfigAttrib _glfw.egl.GetConfigAttrib
|
||||
#define eglGetConfigs _glfw.egl.GetConfigs
|
||||
@@ -149,6 +150,7 @@ typedef GLFWglproc (EGLAPIENTRY * PFN_eglGetProcAddress)(const char*);
|
||||
#define eglSwapBuffers _glfw.egl.SwapBuffers
|
||||
#define eglSwapInterval _glfw.egl.SwapInterval
|
||||
#define eglQueryString _glfw.egl.QueryString
|
||||
#define eglQuerySurface _glfw.egl.QuerySurface
|
||||
#define eglGetProcAddress _glfw.egl.GetProcAddress
|
||||
|
||||
typedef EGLDisplay (EGLAPIENTRY * PFNEGLGETPLATFORMDISPLAYEXTPROC)(EGLenum,void*,const EGLint*);
|
||||
@@ -160,11 +162,11 @@ typedef EGLSurface (EGLAPIENTRY * PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC)(EGLD
|
||||
//
|
||||
typedef struct _GLFWcontextEGL
|
||||
{
|
||||
EGLConfig config;
|
||||
EGLContext handle;
|
||||
EGLSurface surface;
|
||||
EGLConfig config;
|
||||
EGLContext handle;
|
||||
EGLSurface surface;
|
||||
|
||||
void* client;
|
||||
void* client;
|
||||
|
||||
} _GLFWcontextEGL;
|
||||
|
||||
@@ -186,6 +188,7 @@ typedef struct _GLFWlibraryEGL
|
||||
bool EXT_platform_base;
|
||||
bool EXT_platform_x11;
|
||||
bool EXT_platform_wayland;
|
||||
bool EXT_present_opaque;
|
||||
bool ANGLE_platform_angle;
|
||||
bool ANGLE_platform_angle_opengl;
|
||||
bool ANGLE_platform_angle_d3d;
|
||||
@@ -210,6 +213,7 @@ typedef struct _GLFWlibraryEGL
|
||||
PFN_eglSwapBuffers SwapBuffers;
|
||||
PFN_eglSwapInterval SwapInterval;
|
||||
PFN_eglQueryString QueryString;
|
||||
PFN_eglQuerySurface QuerySurface;
|
||||
PFN_eglGetProcAddress GetProcAddress;
|
||||
|
||||
PFNEGLGETPLATFORMDISPLAYEXTPROC GetPlatformDisplayEXT;
|
||||
|
||||
14
glfw/glfw.py
14
glfw/glfw.py
@@ -111,7 +111,7 @@ def init_env(
|
||||
|
||||
elif module == 'cocoa':
|
||||
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_))
|
||||
|
||||
elif module == 'wayland':
|
||||
@@ -172,6 +172,8 @@ class Arg:
|
||||
while self.name.startswith('*'):
|
||||
self.name = self.name[1:]
|
||||
self.type = self.type + '*'
|
||||
if '[' in self.name:
|
||||
self.type += '[' + self.name.partition('[')[-1]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'Arg({self.type}, {self.name})'
|
||||
@@ -233,7 +235,7 @@ def generate_wrappers(glfw_header: str) -> None:
|
||||
void* glfwGetNSGLContext(GLFWwindow *window)
|
||||
uint32_t glfwGetCocoaMonitor(GLFWmonitor* monitor)
|
||||
GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow* window, GLFWcocoatextinputfilterfun callback)
|
||||
GLFWhandlefileopen glfwSetCocoaFileOpenCallback(GLFWhandlefileopen callback)
|
||||
GLFWhandleurlopen glfwSetCocoaURLOpenCallback(GLFWhandleurlopen callback)
|
||||
GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *window, GLFWcocoatogglefullscreenfun callback)
|
||||
GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback)
|
||||
GLFWapplicationwillfinishlaunchingfun glfwSetApplicationWillFinishLaunching(GLFWapplicationwillfinishlaunchingfun callback)
|
||||
@@ -245,11 +247,15 @@ def generate_wrappers(glfw_header: str) -> None:
|
||||
const char* glfwGetPrimarySelectionString(GLFWwindow* window, void)
|
||||
int glfwGetNativeKeyForName(const char* key_name, int case_sensitive)
|
||||
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)
|
||||
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)
|
||||
void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler)
|
||||
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():
|
||||
if line:
|
||||
functions.append(Function(line.strip(), check_fail=False))
|
||||
@@ -277,7 +283,7 @@ const char *action_text, int32_t timeout, GLFWDBusnotificationcreatedfun callbac
|
||||
|
||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int,unsigned long);
|
||||
typedef bool (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||
typedef bool (* GLFWhandlefileopen)(const char*);
|
||||
typedef bool (* GLFWhandleurlopen)(const char*);
|
||||
typedef void (* GLFWapplicationwillfinishlaunchingfun)(void);
|
||||
typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
|
||||
typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);
|
||||
@@ -292,6 +298,8 @@ const char* load_glfw(const char* path);
|
||||
f.write(header)
|
||||
|
||||
code = '''
|
||||
// generated by glfw.py DO NOT edit
|
||||
|
||||
#define GFW_EXTERN
|
||||
#include "data-types.h"
|
||||
#include "glfw-wrapper.h"
|
||||
|
||||
90
glfw/glfw3.h
vendored
90
glfw/glfw3.h
vendored
@@ -512,6 +512,7 @@ typedef enum {
|
||||
* GLFW_LOCK_KEY_MODS input mode is set.
|
||||
*/
|
||||
#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)
|
||||
|
||||
/*! @} */
|
||||
@@ -1024,6 +1025,16 @@ typedef enum GLFWMouseButton {
|
||||
* [window hint](@ref GLFW_COCOA_GRAPHICS_SWITCHING_hint).
|
||||
*/
|
||||
#define GLFW_COCOA_GRAPHICS_SWITCHING 0x00023003
|
||||
/*! @brief macOS specific
|
||||
* [window hint](@ref GLFW_COCOA_COLOR_SPACE_hint).
|
||||
*/
|
||||
#define GLFW_COCOA_COLOR_SPACE 0x00023004
|
||||
typedef enum {
|
||||
DEFAULT_COLORSPACE = 0,
|
||||
SRGB_COLORSPACE = 1,
|
||||
DISPLAY_P3_COLORSPACE = 2,
|
||||
} GlfwCocoaColorSpaces;
|
||||
|
||||
/*! @brief X11 specific
|
||||
* [window hint](@ref GLFW_X11_CLASS_NAME_hint).
|
||||
*/
|
||||
@@ -1206,7 +1217,8 @@ typedef enum {
|
||||
typedef enum {
|
||||
GLFW_IME_NONE,
|
||||
GLFW_IME_PREEDIT_CHANGED,
|
||||
GLFW_IME_COMMIT_TEXT
|
||||
GLFW_IME_COMMIT_TEXT,
|
||||
GLFW_IME_WAYLAND_DONE_EVENT,
|
||||
} GLFWIMEState;
|
||||
|
||||
typedef enum {
|
||||
@@ -1704,7 +1716,21 @@ typedef void (* GLFWjoystickfun)(int,int);
|
||||
|
||||
typedef void (* GLFWuserdatafun)(unsigned long long, 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 char* (* GLFWcurrentselectionfun)(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.
|
||||
*
|
||||
@@ -1862,6 +1888,7 @@ 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 glfwRemoveTimer(unsigned long long);
|
||||
GLFWAPI GLFWdrawtextfun glfwSetDrawTextFunction(GLFWdrawtextfun function);
|
||||
GLFWAPI GLFWcurrentselectionfun glfwSetCurrentSelectionCallback(GLFWcurrentselectionfun callback);
|
||||
|
||||
/*! @brief Terminates the GLFW library.
|
||||
*
|
||||
@@ -2757,6 +2784,7 @@ GLFWAPI void glfwWindowHintString(int hint, const char* value);
|
||||
GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height, const char* title, GLFWmonitor* monitor, GLFWwindow* share);
|
||||
GLFWAPI bool glfwToggleFullscreen(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.
|
||||
*
|
||||
@@ -5205,64 +5233,8 @@ GLFWAPI const char* glfwGetGamepadName(int jid);
|
||||
*/
|
||||
GLFWAPI int glfwGetGamepadState(int jid, GLFWgamepadstate* state);
|
||||
|
||||
/*! @brief Sets the clipboard to the specified string.
|
||||
*
|
||||
* 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);
|
||||
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);
|
||||
|
||||
/*! @brief Returns the GLFW time.
|
||||
*
|
||||
|
||||
9
glfw/init.c
vendored
9
glfw/init.c
vendored
@@ -68,6 +68,8 @@ static void terminate(void)
|
||||
int i;
|
||||
|
||||
memset(&_glfw.callbacks, 0, sizeof(_glfw.callbacks));
|
||||
_glfw_free_clipboard_data(&_glfw.clipboard);
|
||||
_glfw_free_clipboard_data(&_glfw.primary);
|
||||
|
||||
while (_glfw.windowListHead)
|
||||
glfwDestroyWindow((GLFWwindow*) _glfw.windowListHead);
|
||||
@@ -386,3 +388,10 @@ GLFWAPI GLFWdrawtextfun glfwSetDrawTextFunction(GLFWdrawtextfun cbfun)
|
||||
_GLFW_SWAP_POINTERS(_glfw.callbacks.draw_text, 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;
|
||||
}
|
||||
|
||||
51
glfw/input.c
vendored
51
glfw/input.c
vendored
@@ -1526,36 +1526,41 @@ GLFWAPI int glfwGetGamepadState(int jid, GLFWgamepadstate* state)
|
||||
return true;
|
||||
}
|
||||
|
||||
GLFWAPI void glfwSetClipboardString(GLFWwindow* handle UNUSED, const char* string)
|
||||
{
|
||||
assert(string != NULL);
|
||||
void _glfw_free_clipboard_data(_GLFWClipboardData *cd) {
|
||||
if (cd->mime_types) {
|
||||
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();
|
||||
_glfwPlatformSetClipboardString(string);
|
||||
_glfwPlatformGetClipboard(clipboard_type, mime_type, write_data, object);
|
||||
}
|
||||
|
||||
GLFWAPI const char* glfwGetClipboardString(GLFWwindow* handle UNUSED)
|
||||
{
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
return _glfwPlatformGetClipboardString();
|
||||
}
|
||||
|
||||
#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND)
|
||||
GLFWAPI void glfwSetPrimarySelectionString(GLFWwindow* handle UNUSED, const char* string)
|
||||
{
|
||||
assert(string != NULL);
|
||||
|
||||
GLFWAPI void glfwSetClipboardDataTypes(GLFWClipboardType clipboard_type, const char* const *mime_types, size_t num_mime_types, GLFWclipboarditerfun get_data) {
|
||||
assert(mime_types != NULL);
|
||||
assert(get_data != NULL);
|
||||
_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)
|
||||
{
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(0);
|
||||
|
||||
31
glfw/internal.h
vendored
31
glfw/internal.h
vendored
@@ -34,6 +34,9 @@
|
||||
#endif
|
||||
|
||||
#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) || \
|
||||
defined(GLFW_INCLUDE_ES1) || \
|
||||
@@ -52,6 +55,8 @@
|
||||
#define GLFW_INCLUDE_NONE
|
||||
#include "glfw3.h"
|
||||
|
||||
#define EGL_PRESENT_OPAQUE_EXT 0x31df
|
||||
|
||||
#define _GLFW_INSERT_FIRST 0
|
||||
#define _GLFW_INSERT_LAST 1
|
||||
|
||||
@@ -307,6 +312,7 @@ struct _GLFWwndconfig
|
||||
bool scaleToMonitor;
|
||||
struct {
|
||||
bool retina;
|
||||
int color_space;
|
||||
char frameName[256];
|
||||
} ns;
|
||||
struct {
|
||||
@@ -438,6 +444,11 @@ struct _GLFWwindow
|
||||
bool rawMouseMotion;
|
||||
|
||||
_GLFWcontext context;
|
||||
#ifdef _GLFW_WAYLAND
|
||||
bool swaps_disallowed;
|
||||
#else
|
||||
const bool swaps_disallowed;
|
||||
#endif
|
||||
|
||||
struct {
|
||||
GLFWwindowposfun pos;
|
||||
@@ -553,6 +564,13 @@ struct _GLFWmutex
|
||||
_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
|
||||
//
|
||||
struct _GLFWlibrary
|
||||
@@ -567,6 +585,8 @@ struct _GLFWlibrary
|
||||
int refreshRate;
|
||||
} hints;
|
||||
|
||||
_GLFWClipboardData primary, clipboard;
|
||||
|
||||
_GLFWerror* errorListHead;
|
||||
_GLFWcursor* cursorListHead;
|
||||
_GLFWwindow* windowListHead;
|
||||
@@ -613,6 +633,7 @@ struct _GLFWlibrary
|
||||
GLFWjoystickfun joystick;
|
||||
GLFWapplicationclosefun application_close;
|
||||
GLFWdrawtextfun draw_text;
|
||||
GLFWcurrentselectionfun get_current_selection;
|
||||
} callbacks;
|
||||
|
||||
|
||||
@@ -665,12 +686,8 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode);
|
||||
bool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp);
|
||||
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp);
|
||||
|
||||
void _glfwPlatformSetClipboardString(const char* string);
|
||||
const char* _glfwPlatformGetClipboardString(void);
|
||||
#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND)
|
||||
void _glfwPlatformSetPrimarySelectionString(const char* string);
|
||||
const char* _glfwPlatformGetPrimarySelectionString(void);
|
||||
#endif
|
||||
void _glfwPlatformSetClipboard(GLFWClipboardType t);
|
||||
void _glfwPlatformGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object);
|
||||
|
||||
bool _glfwPlatformInitJoysticks(void);
|
||||
void _glfwPlatformTerminateJoysticks(void);
|
||||
@@ -846,3 +863,5 @@ void _glfwPlatformUpdateTimer(unsigned long long timer_id, monotonic_t interval,
|
||||
void _glfwPlatformRemoveTimer(unsigned long long timer_id);
|
||||
|
||||
char* _glfw_strdup(const char* source);
|
||||
|
||||
void _glfw_free_clipboard_data(_GLFWClipboardData *cd);
|
||||
|
||||
198
glfw/linux_desktop_settings.c
vendored
198
glfw/linux_desktop_settings.c
vendored
@@ -10,77 +10,118 @@
|
||||
#include <strings.h>
|
||||
#include <string.h>
|
||||
|
||||
static const char *DESKTOP_SERVICE = "org.freedesktop.portal.Desktop";
|
||||
static const char *DESKTOP_PATH = "/org/freedesktop/portal/desktop";
|
||||
static const char *DESKTOP_INTERFACE = "org.freedesktop.portal.Settings";
|
||||
static const char *GNOME_DESKTOP_NAMESPACE = "org.gnome.desktop.interface";
|
||||
#define DESKTOP_SERVICE "org.freedesktop.portal.Desktop"
|
||||
#define DESKTOP_PATH "/org/freedesktop/portal/desktop"
|
||||
#define DESKTOP_INTERFACE "org.freedesktop.portal.Settings"
|
||||
#define GNOME_DESKTOP_NAMESPACE "org.gnome.desktop.interface"
|
||||
#define FDO_DESKTOP_NAMESPACE "org.freedesktop.appearance"
|
||||
#define FDO_APPEARANCE_KEY "color-scheme"
|
||||
|
||||
|
||||
static char theme_name[64] = {0};
|
||||
static char theme_name[128] = {0};
|
||||
static int theme_size = -1;
|
||||
static bool gnome_cursor_theme_read = false, gnome_cursor_size_read = false;
|
||||
static uint32_t appearance = 0;
|
||||
static bool is_gnome = false;
|
||||
static bool cursor_theme_changed = false;
|
||||
|
||||
static bool
|
||||
parse_dbus_message_for_type(DBusMessage *const reply, const char *errmsg, const int type, void *value) {
|
||||
DBusMessageIter iter[3];
|
||||
dbus_message_iter_init(reply, &iter[0]);
|
||||
#define FAIL { _glfwInputError(GLFW_PLATFORM_ERROR, "%s", errmsg); return false; }
|
||||
if (dbus_message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_VARIANT) FAIL;
|
||||
dbus_message_iter_recurse(&iter[0], &iter[1]);
|
||||
if (dbus_message_iter_get_arg_type(&iter[1]) != DBUS_TYPE_VARIANT) FAIL;
|
||||
dbus_message_iter_recurse(&iter[1], &iter[2]);
|
||||
if (dbus_message_iter_get_arg_type(&iter[2]) != type) FAIL;
|
||||
dbus_message_iter_get_basic(&iter[2], value);
|
||||
return true;
|
||||
#undef FAIL
|
||||
}
|
||||
|
||||
#define HANDLER(name) void name(DBusMessage *msg, const char* errmsg, void *data) { \
|
||||
#define HANDLER(name) static void name(DBusMessage *msg, const char* errmsg, void *data) { \
|
||||
(void)data; \
|
||||
if (errmsg) { \
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "%s: failed with error: %s", #name, errmsg); \
|
||||
return; \
|
||||
}
|
||||
|
||||
HANDLER(on_gnome_cursor_theme_read)
|
||||
const char *name;
|
||||
if (!parse_dbus_message_for_type(msg, "Failed to get cursor theme name from reply", DBUS_TYPE_STRING, &name)) return;
|
||||
if (name && name[0]) {
|
||||
gnome_cursor_theme_read = true;
|
||||
strncpy(theme_name, name, sizeof(theme_name) - 1);
|
||||
if (gnome_cursor_size_read) _glfwPlatformChangeCursorTheme();
|
||||
static void
|
||||
process_fdo_setting(const char *key, DBusMessageIter *value) {
|
||||
if (strcmp(key, FDO_APPEARANCE_KEY) == 0) {
|
||||
if (dbus_message_iter_get_arg_type(value) == DBUS_TYPE_UINT32) {
|
||||
dbus_message_iter_get_basic(value, &appearance);
|
||||
if (appearance > 2) appearance = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HANDLER(on_gnome_cursor_size_read)
|
||||
int32_t sz;
|
||||
if (!parse_dbus_message_for_type(msg, "Failed to get cursor theme size from reply", DBUS_TYPE_INT32, &sz)) return;
|
||||
gnome_cursor_size_read = true;
|
||||
theme_size = sz;
|
||||
if (gnome_cursor_theme_read) _glfwPlatformChangeCursorTheme();
|
||||
}
|
||||
#undef HANDLER
|
||||
|
||||
|
||||
static bool
|
||||
call_read(DBusConnection *session_bus, dbus_pending_callback callback, const char *namespace, const char *key) {
|
||||
return glfw_dbus_call_method_with_reply(
|
||||
session_bus, DESKTOP_SERVICE, DESKTOP_PATH, DESKTOP_INTERFACE, "Read", DBUS_TIMEOUT_USE_DEFAULT,
|
||||
callback, NULL, DBUS_TYPE_STRING, &namespace, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID);
|
||||
}
|
||||
|
||||
static void
|
||||
get_from_gnome(void) {
|
||||
theme_size = 32;
|
||||
DBusConnection *session_bus = glfw_dbus_session_bus();
|
||||
if (session_bus) {
|
||||
const char *theme_key = "cursor-theme";
|
||||
call_read(session_bus, on_gnome_cursor_theme_read, GNOME_DESKTOP_NAMESPACE, theme_key);
|
||||
const char *size_key = "cursor-size";
|
||||
call_read(session_bus, on_gnome_cursor_size_read, GNOME_DESKTOP_NAMESPACE, size_key);
|
||||
process_gnome_setting(const char *key, DBusMessageIter *value) {
|
||||
if (strcmp(key, "cursor-size") == 0) {
|
||||
if (dbus_message_iter_get_arg_type(value) == DBUS_TYPE_INT32) {
|
||||
int32_t sz;
|
||||
dbus_message_iter_get_basic(value, &sz);
|
||||
if (sz > 0 && sz != theme_size) {
|
||||
theme_size = sz;
|
||||
cursor_theme_changed = true;
|
||||
}
|
||||
}
|
||||
} else if (strcmp(key, "cursor-theme") == 0) {
|
||||
if (dbus_message_iter_get_arg_type(value) == DBUS_TYPE_STRING) {
|
||||
const char *name;
|
||||
dbus_message_iter_get_basic(value, &name);
|
||||
if (name) {
|
||||
strncpy(theme_name, name, sizeof(theme_name) - 1);
|
||||
cursor_theme_changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
process_settings_dict(DBusMessageIter *array_iter, void(process_setting)(const char *, DBusMessageIter*)) {
|
||||
DBusMessageIter item_iter, value_iter;
|
||||
while (dbus_message_iter_get_arg_type(array_iter) == DBUS_TYPE_DICT_ENTRY) {
|
||||
dbus_message_iter_recurse(array_iter, &item_iter);
|
||||
if (dbus_message_iter_get_arg_type(&item_iter) == DBUS_TYPE_STRING) {
|
||||
const char *key;
|
||||
dbus_message_iter_get_basic(&item_iter, &key);
|
||||
if (dbus_message_iter_next(&item_iter) && dbus_message_iter_get_arg_type(&item_iter) == DBUS_TYPE_VARIANT) {
|
||||
dbus_message_iter_recurse(&item_iter, &value_iter);
|
||||
process_setting(key, &value_iter);
|
||||
}
|
||||
}
|
||||
if (!dbus_message_iter_next(array_iter)) break;
|
||||
}
|
||||
}
|
||||
|
||||
HANDLER(process_desktop_settings)
|
||||
cursor_theme_changed = false;
|
||||
DBusMessageIter root, array, item, settings;
|
||||
dbus_message_iter_init(msg, &root);
|
||||
#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");
|
||||
dbus_message_iter_recurse(&root, &array);
|
||||
while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_DICT_ENTRY) {
|
||||
dbus_message_iter_recurse(&array, &item);
|
||||
if (dbus_message_iter_get_arg_type(&item) == DBUS_TYPE_STRING) {
|
||||
const char *namespace;
|
||||
dbus_message_iter_get_basic(&item, &namespace);
|
||||
if (dbus_message_iter_next(&item) && dbus_message_iter_get_arg_type(&item) == DBUS_TYPE_ARRAY) {
|
||||
dbus_message_iter_recurse(&item, &settings);
|
||||
if (strcmp(namespace, FDO_DESKTOP_NAMESPACE) == 0) {
|
||||
process_settings_dict(&settings, process_fdo_setting);
|
||||
} else if (is_gnome && strcmp(namespace, GNOME_DESKTOP_NAMESPACE) == 0) {
|
||||
process_settings_dict(&settings, process_gnome_setting);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!dbus_message_iter_next(&array)) break;
|
||||
}
|
||||
#undef die
|
||||
if (cursor_theme_changed) _glfwPlatformChangeCursorTheme();
|
||||
}
|
||||
|
||||
#undef HANDLER
|
||||
|
||||
static bool
|
||||
read_desktop_settings(DBusConnection *session_bus) {
|
||||
DBusMessage *msg = dbus_message_new_method_call(DESKTOP_SERVICE, DESKTOP_PATH, DESKTOP_INTERFACE, "ReadAll");
|
||||
if (!msg) return false;
|
||||
DBusMessageIter iter, array_iter;
|
||||
dbus_message_iter_init_append(msg, &iter);
|
||||
if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &array_iter)) { dbus_message_unref(msg); return false; }
|
||||
if (!dbus_message_iter_close_container(&iter, &array_iter)) { dbus_message_unref(msg); return false; }
|
||||
bool ok = call_method_with_msg(session_bus, msg, DBUS_TIMEOUT_USE_DEFAULT, process_desktop_settings, NULL);
|
||||
dbus_message_unref(msg);
|
||||
return ok;
|
||||
}
|
||||
|
||||
void
|
||||
glfw_current_cursor_theme(const char **theme, int *size) {
|
||||
@@ -100,10 +141,55 @@ get_cursor_theme_from_env(void) {
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
on_color_scheme_change(DBusMessage *message) {
|
||||
DBusMessageIter iter[2];
|
||||
dbus_message_iter_init (message, &iter[0]);
|
||||
int current_type;
|
||||
while ((current_type = dbus_message_iter_get_arg_type (&iter[0])) != DBUS_TYPE_INVALID) {
|
||||
if (current_type == DBUS_TYPE_VARIANT) {
|
||||
dbus_message_iter_recurse(&iter[0], &iter[1]);
|
||||
if (dbus_message_iter_get_arg_type(&iter[1]) == DBUS_TYPE_UINT32) {
|
||||
uint32_t val = 0;
|
||||
dbus_message_iter_get_basic(&iter[1], &val);
|
||||
if (val > 2) val = 0;
|
||||
if (val != appearance) {
|
||||
appearance = val;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
dbus_message_iter_next(&iter[0]);
|
||||
}
|
||||
}
|
||||
|
||||
static DBusHandlerResult
|
||||
setting_changed(DBusConnection *conn UNUSED, DBusMessage *msg, void *user_data UNUSED) {
|
||||
/* printf("session_bus settings_changed invoked interface: %s member: %s\n", dbus_message_get_interface(msg), dbus_message_get_member(msg)); */
|
||||
if (dbus_message_is_signal(msg, DESKTOP_INTERFACE, "SettingChanged")) {
|
||||
const char *namespace = NULL, *key = NULL;
|
||||
if (glfw_dbus_get_args(msg, "Failed to get namespace and key from SettingChanged notification signal", DBUS_TYPE_STRING, &namespace, DBUS_TYPE_STRING, &key, DBUS_TYPE_INVALID)) {
|
||||
if (strcmp(namespace, FDO_DESKTOP_NAMESPACE) == 0) {
|
||||
if (strcmp(key, FDO_APPEARANCE_KEY) == 0) {
|
||||
on_color_scheme_change(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
glfw_initialize_desktop_settings(void) {
|
||||
get_cursor_theme_from_env();
|
||||
const char *desktop = getenv("XDG_CURRENT_DESKTOP");
|
||||
bool is_gnome = desktop && strncasecmp(desktop, "GNOME", sizeof("GNOME") - 1) == 0;
|
||||
if (is_gnome) get_from_gnome();
|
||||
is_gnome = desktop && strstr(desktop, "GNOME");
|
||||
DBusConnection *session_bus = glfw_dbus_session_bus();
|
||||
if (session_bus) {
|
||||
if (!read_desktop_settings(session_bus)) _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to read desktop settings, make sure you have the desktop portal running.");
|
||||
dbus_bus_add_match(session_bus, "type='signal',interface='" DESKTOP_INTERFACE "',member='SettingChanged'", NULL);
|
||||
dbus_connection_add_filter(session_bus, setting_changed, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,8 @@
|
||||
"unstable/idle-inhibit/idle-inhibit-unstable-v1.xml",
|
||||
"unstable/xdg-decoration/xdg-decoration-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": [
|
||||
"wl_init.c",
|
||||
|
||||
11
glfw/window.c
vendored
11
glfw/window.c
vendored
@@ -71,7 +71,7 @@ void _glfwInputWindowFocus(_GLFWwindow* window, bool focused)
|
||||
_glfw.focusedWindowId = window->id;
|
||||
}
|
||||
|
||||
_GLFWwindow* _glfwFocusedWindow() {
|
||||
_GLFWwindow* _glfwFocusedWindow(void) {
|
||||
if (_glfw.focusedWindowId) {
|
||||
_GLFWwindow *w = _glfw.windowListHead;
|
||||
while (w) {
|
||||
@@ -332,6 +332,8 @@ void glfwDefaultWindowHints(void)
|
||||
|
||||
// The default is to use full Retina resolution framebuffers
|
||||
_glfw.hints.window.ns.retina = true;
|
||||
// use the default colorspace assigned by the system
|
||||
_glfw.hints.window.ns.color_space = 0;
|
||||
}
|
||||
|
||||
GLFWAPI void glfwWindowHint(int hint, int value)
|
||||
@@ -412,6 +414,9 @@ GLFWAPI void glfwWindowHint(int hint, int value)
|
||||
case GLFW_COCOA_RETINA_FRAMEBUFFER:
|
||||
_glfw.hints.window.ns.retina = value ? true : false;
|
||||
return;
|
||||
case GLFW_COCOA_COLOR_SPACE:
|
||||
_glfw.hints.window.ns.color_space = value;
|
||||
return;
|
||||
case GLFW_COCOA_GRAPHICS_SWITCHING:
|
||||
_glfw.hints.context.nsgl.offline = value ? true : false;
|
||||
return;
|
||||
@@ -1062,6 +1067,10 @@ GLFWAPI bool glfwIsFullscreen(GLFWwindow* wh, unsigned int flags) {
|
||||
return _glfwPlatformIsFullscreen((_GLFWwindow*)wh, flags);
|
||||
}
|
||||
|
||||
GLFWAPI bool glfwAreSwapsAllowed(const GLFWwindow* wh) {
|
||||
return !(((_GLFWwindow*)wh)->swaps_disallowed);
|
||||
}
|
||||
|
||||
GLFWAPI void glfwSetWindowUserPointer(GLFWwindow* handle, void* pointer)
|
||||
{
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
|
||||
62
glfw/wl_client_side_decorations.c
vendored
62
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) {
|
||||
// 1D Normalized Gaussian
|
||||
const kernel_type half = size / (kernel_type)2;
|
||||
kernel_type sum = 0;
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
kernel_type f = (i - half);
|
||||
blur_kernel[i] = (kernel_type)exp(- f * f / sigma);
|
||||
sum += blur_kernel[i];
|
||||
}
|
||||
for (size_t i = 0; i < size; i++) blur_kernel[i] /= sum;
|
||||
kernel_type sum = 0;
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
kernel_type f = (i - half);
|
||||
blur_kernel[i] = (kernel_type)exp(- f * f / sigma);
|
||||
sum += blur_kernel[i];
|
||||
}
|
||||
for (size_t i = 0; i < size; i++) blur_kernel[i] /= sum;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -102,13 +102,34 @@ init_buffer_pair(_GLFWWaylandBufferPair *pair, size_t width, size_t height, unsi
|
||||
return 2 * pair->size_in_bytes;
|
||||
}
|
||||
|
||||
static bool
|
||||
window_has_buffer(_GLFWwindow *window, struct wl_buffer *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; }
|
||||
Q(left); Q(top); Q(right); Q(bottom);
|
||||
#undef Q
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
alloc_buffer_pair(_GLFWWaylandBufferPair *pair, struct wl_shm_pool *pool, uint8_t *data, size_t *offset) {
|
||||
buffer_release_event(void *data, struct wl_buffer *buffer) {
|
||||
wl_buffer_destroy(buffer);
|
||||
_GLFWwindow *window = _glfwWindowForId((uintptr_t)data);
|
||||
if (window && window_has_buffer(window, buffer)) decs.buffer_destroyed = true;
|
||||
}
|
||||
|
||||
static struct wl_buffer_listener handle_buffer_events = {.release = buffer_release_event};
|
||||
|
||||
static void
|
||||
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->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);
|
||||
*offset += pair->size_in_bytes;
|
||||
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_needs_to_be_destroyed = true;
|
||||
wl_buffer_add_listener(pair->b, &handle_buffer_events, (void*)window_id);
|
||||
*offset += pair->size_in_bytes;
|
||||
pair->front = pair->a; pair->back = pair->b;
|
||||
pair->data.front = pair->data.a; pair->data.back = pair->data.b;
|
||||
@@ -287,7 +308,7 @@ create_shm_buffers(_GLFWwindow* window) {
|
||||
struct wl_shm_pool* pool = wl_shm_create_pool(_glfw.wl.shm, fd, decs.mapping.size);
|
||||
close(fd);
|
||||
size_t offset = 0;
|
||||
#define a(which) alloc_buffer_pair(&decs.which.buffer, pool, decs.mapping.data, &offset)
|
||||
#define a(which) alloc_buffer_pair(window->id, &decs.which.buffer, pool, decs.mapping.data, &offset)
|
||||
a(top); a(left); a(bottom); a(right);
|
||||
#undef a
|
||||
wl_shm_pool_destroy(pool);
|
||||
@@ -313,8 +334,8 @@ free_csd_surfaces(_GLFWwindow *window) {
|
||||
static void
|
||||
free_csd_buffers(_GLFWwindow *window) {
|
||||
#define d(which) { \
|
||||
if (decs.which.buffer.a) wl_buffer_destroy(decs.which.buffer.a); \
|
||||
if (decs.which.buffer.b) wl_buffer_destroy(decs.which.buffer.b); \
|
||||
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)); \
|
||||
}
|
||||
d(left); d(top); d(right); d(bottom);
|
||||
@@ -337,9 +358,10 @@ create_csd_surfaces(_GLFWwindow *window, _GLFWWaylandCSDEdge *s) {
|
||||
}
|
||||
|
||||
#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_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
|
||||
ensure_csd_resources(_GLFWwindow *window) {
|
||||
@@ -352,11 +374,14 @@ ensure_csd_resources(_GLFWwindow *window) {
|
||||
decs.for_window_state.scale != window->wl.scale ||
|
||||
!decs.mapping.data
|
||||
);
|
||||
const bool needs_update = focus_changed || size_changed || !decs.left.surface;
|
||||
const bool needs_update = focus_changed || size_changed || !decs.left.surface || decs.buffer_destroyed;
|
||||
debug("CSD: old.size: %dx%d new.size: %dx%d needs_update: %d size_changed: %d buffer_destroyed: %d\n",
|
||||
decs.for_window_state.width, decs.for_window_state.height, window->wl.width, window->wl.height, needs_update, size_changed, decs.buffer_destroyed);
|
||||
if (!needs_update) return false;
|
||||
if (size_changed) {
|
||||
if (size_changed || decs.buffer_destroyed) {
|
||||
free_csd_buffers(window);
|
||||
if (!create_shm_buffers(window)) return false;
|
||||
decs.buffer_destroyed = false;
|
||||
}
|
||||
|
||||
int32_t x, y, scale = window->wl.scale < 1 ? 1 : window->wl.scale;
|
||||
@@ -409,7 +434,7 @@ change_csd_title(_GLFWwindow *window) {
|
||||
|
||||
void
|
||||
set_csd_window_geometry(_GLFWwindow *window, int32_t *width, int32_t *height) {
|
||||
bool has_csd = window->decorated && !window->wl.decorations.serverSide && window->wl.decorations.left.surface && !(window->wl.toplevel_states & TOPLEVEL_STATE_FULLSCREEN);
|
||||
bool has_csd = window->decorated && !window->wl.decorations.serverSide && window->wl.decorations.left.surface && !(window->wl.current.toplevel_states & TOPLEVEL_STATE_FULLSCREEN);
|
||||
bool size_specified_by_compositor = *width > 0 && *height > 0;
|
||||
if (!size_specified_by_compositor) {
|
||||
*width = window->wl.user_requested_content_size.width;
|
||||
@@ -431,8 +456,5 @@ set_titlebar_color(_GLFWwindow *window, uint32_t color, bool use_system_color) {
|
||||
decs.use_custom_titlebar_color = use_custom_color;
|
||||
decs.titlebar_color = color;
|
||||
}
|
||||
if (window->decorated && decs.top.surface) {
|
||||
update_title_bar(window);
|
||||
damage_csd(top, decs.top.buffer.front);
|
||||
}
|
||||
change_csd_title(window);
|
||||
}
|
||||
|
||||
119
glfw/wl_init.c
vendored
119
glfw/wl_init.c
vendored
@@ -54,6 +54,8 @@
|
||||
#endif
|
||||
|
||||
|
||||
#define debug(...) if (_glfw.hints.init.debugRendering) fprintf(stderr, __VA_ARGS__);
|
||||
|
||||
static int min(int n1, int n2)
|
||||
{
|
||||
return n1 < n2 ? n1 : n2;
|
||||
@@ -96,9 +98,8 @@ static void pointerHandleEnter(void* data UNUSED,
|
||||
if (!window)
|
||||
return;
|
||||
}
|
||||
|
||||
window->wl.decorations.focus = focus;
|
||||
_glfw.wl.serial = serial;
|
||||
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial;
|
||||
_glfw.wl.pointerFocus = window;
|
||||
|
||||
window->wl.hovered = true;
|
||||
@@ -142,10 +143,19 @@ static void setCursor(GLFWCursorShape shape, _GLFWwindow* window)
|
||||
|
||||
if (!image)
|
||||
return;
|
||||
if (image->width % scale || image->height % scale) {
|
||||
static uint32_t warned_width = 0, warned_height = 0;
|
||||
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);
|
||||
if (!buffer)
|
||||
return;
|
||||
debug("Calling wl_pointer_set_cursor in setCursor with surface: %p\n", (void*)surface);
|
||||
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.serial,
|
||||
surface,
|
||||
image->hotspot_x / scale,
|
||||
@@ -243,7 +253,7 @@ static void pointerHandleButton(void* data UNUSED,
|
||||
window->wl.decorations.last_click_on_top_decoration_at = monotonic();
|
||||
if (window->wl.decorations.last_click_on_top_decoration_at - last_click_at <= _glfwPlatformGetDoubleClickInterval(window)) {
|
||||
window->wl.decorations.last_click_on_top_decoration_at = 0;
|
||||
if (window->wl.toplevel_states & TOPLEVEL_STATE_MAXIMIZED)
|
||||
if (window->wl.current.toplevel_states & TOPLEVEL_STATE_MAXIMIZED)
|
||||
xdg_toplevel_unset_maximized(window->wl.xdg.toplevel);
|
||||
else
|
||||
xdg_toplevel_set_maximized(window->wl.xdg.toplevel);
|
||||
@@ -301,7 +311,7 @@ static void pointerHandleButton(void* data UNUSED,
|
||||
if (window->wl.decorations.focus != CENTRAL_WINDOW)
|
||||
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
|
||||
* codes. */
|
||||
@@ -331,20 +341,83 @@ static void pointerHandleAxis(void* data UNUSED,
|
||||
assert(axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL ||
|
||||
axis == WL_POINTER_AXIS_VERTICAL_SCROLL);
|
||||
|
||||
if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL)
|
||||
x = -wl_fixed_to_double(value);
|
||||
else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
|
||||
y = -wl_fixed_to_double(value);
|
||||
if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) {
|
||||
if (window->wl.axis_discrete_count.x) {
|
||||
window->wl.axis_discrete_count.x--;
|
||||
return;
|
||||
}
|
||||
x = -wl_fixed_to_double(value) * (window->wl.scale);
|
||||
}
|
||||
else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {
|
||||
if (window->wl.axis_discrete_count.y) {
|
||||
window->wl.axis_discrete_count.y--;
|
||||
return;
|
||||
}
|
||||
y = -wl_fixed_to_double(value) * (window->wl.scale);
|
||||
}
|
||||
|
||||
_glfwInputScroll(window, x, y, 1, _glfw.wl.xkb.states.modifiers);
|
||||
}
|
||||
|
||||
static void pointerHandleFrame(void* data UNUSED,
|
||||
struct wl_pointer* pointer UNUSED)
|
||||
{
|
||||
_GLFWwindow* window = _glfw.wl.pointerFocus;
|
||||
if (window) {
|
||||
window->wl.axis_discrete_count.x = 0;
|
||||
window->wl.axis_discrete_count.y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void pointerHandleAxisSource(void* data UNUSED,
|
||||
struct wl_pointer* pointer UNUSED,
|
||||
uint32_t source UNUSED)
|
||||
{
|
||||
}
|
||||
|
||||
static void pointerHandleAxisStop(void *data UNUSED,
|
||||
struct wl_pointer *wl_pointer UNUSED,
|
||||
uint32_t time UNUSED,
|
||||
uint32_t axis UNUSED)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
static void pointerHandleAxisDiscrete(void *data UNUSED,
|
||||
struct wl_pointer *wl_pointer UNUSED,
|
||||
uint32_t axis,
|
||||
int32_t discrete)
|
||||
{
|
||||
_GLFWwindow* window = _glfw.wl.pointerFocus;
|
||||
double x = 0.0, y = 0.0;
|
||||
if (!window)
|
||||
return;
|
||||
|
||||
assert(axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL ||
|
||||
axis == WL_POINTER_AXIS_VERTICAL_SCROLL);
|
||||
|
||||
if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) {
|
||||
x = -discrete;
|
||||
window->wl.axis_discrete_count.x++;
|
||||
}
|
||||
else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {
|
||||
y = -discrete;
|
||||
window->wl.axis_discrete_count.y++;
|
||||
}
|
||||
|
||||
_glfwInputScroll(window, x, y, 0, _glfw.wl.xkb.states.modifiers);
|
||||
}
|
||||
|
||||
static const struct wl_pointer_listener pointerListener = {
|
||||
pointerHandleEnter,
|
||||
pointerHandleLeave,
|
||||
pointerHandleMotion,
|
||||
pointerHandleButton,
|
||||
pointerHandleAxis,
|
||||
pointerHandleFrame,
|
||||
pointerHandleAxisSource,
|
||||
pointerHandleAxisStop,
|
||||
pointerHandleAxisDiscrete,
|
||||
};
|
||||
|
||||
static void keyboardHandleKeymap(void* data UNUSED,
|
||||
@@ -390,7 +463,7 @@ static void keyboardHandleEnter(void* data UNUSED,
|
||||
return;
|
||||
}
|
||||
|
||||
_glfw.wl.serial = serial;
|
||||
_glfw.wl.serial = serial; _glfw.wl.input_serial = serial;
|
||||
_glfw.wl.keyboardFocusId = window->id;
|
||||
_glfwInputWindowFocus(window, true);
|
||||
uint32_t* key;
|
||||
@@ -443,7 +516,7 @@ static void keyboardHandleKey(void* data UNUSED,
|
||||
return;
|
||||
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);
|
||||
|
||||
if (action == GLFW_PRESS && _glfw.wl.keyboardRepeatRate > 0 && glfw_xkb_should_repeat(&_glfw.wl.xkb, key))
|
||||
@@ -466,7 +539,7 @@ static void keyboardHandleModifiers(void* data UNUSED,
|
||||
uint32_t modsLocked,
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -504,6 +577,7 @@ static void seatHandleCapabilities(void* data UNUSED,
|
||||
{
|
||||
wl_pointer_destroy(_glfw.wl.pointer);
|
||||
_glfw.wl.pointer = NULL;
|
||||
if (_glfw.wl.cursorAnimationTimer) toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0);
|
||||
}
|
||||
|
||||
if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !_glfw.wl.keyboard)
|
||||
@@ -515,6 +589,8 @@ static void seatHandleCapabilities(void* data UNUSED,
|
||||
{
|
||||
wl_keyboard_destroy(_glfw.wl.keyboard);
|
||||
_glfw.wl.keyboard = NULL;
|
||||
_glfw.wl.keyboardFocusId = 0;
|
||||
if (_glfw.wl.keyRepeatInfo.keyRepeatTimer) toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -571,7 +647,7 @@ static void registryHandleGlobal(void* data UNUSED,
|
||||
{
|
||||
if (!_glfw.wl.seat)
|
||||
{
|
||||
_glfw.wl.seatVersion = min(4, version);
|
||||
_glfw.wl.seatVersion = min(5, version);
|
||||
_glfw.wl.seat =
|
||||
wl_registry_bind(registry, name, &wl_seat_interface,
|
||||
_glfw.wl.seatVersion);
|
||||
@@ -643,6 +719,9 @@ static void registryHandleGlobal(void* data UNUSED,
|
||||
_glfwSetupWaylandPrimarySelectionDevice();
|
||||
}
|
||||
}
|
||||
else if (strstr(interface, "xdg_activation_v1") != 0) {
|
||||
_glfw.wl.xdg_activation_v1 = wl_registry_bind(registry, name, &xdg_activation_v1_interface, 1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -806,6 +885,14 @@ int _glfwPlatformInit(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();
|
||||
if (_glfw.wl.egl.handle)
|
||||
{
|
||||
@@ -850,6 +937,8 @@ void _glfwPlatformTerminate(void)
|
||||
zwp_idle_inhibit_manager_v1_destroy(_glfw.wl.idleInhibitManager);
|
||||
if (_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++) {
|
||||
if (_glfw.wl.dataOffers[doi].id) {
|
||||
destroy_data_offer(&_glfw.wl.dataOffers[doi]);
|
||||
@@ -863,6 +952,8 @@ void _glfwPlatformTerminate(void)
|
||||
zwp_primary_selection_device_v1_destroy(_glfw.wl.primarySelectionDevice);
|
||||
if (_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)
|
||||
wl_registry_destroy(_glfw.wl.registry);
|
||||
if (_glfw.wl.display)
|
||||
@@ -871,10 +962,6 @@ void _glfwPlatformTerminate(void)
|
||||
wl_display_disconnect(_glfw.wl.display);
|
||||
}
|
||||
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)
|
||||
|
||||
55
glfw/wl_platform.h
vendored
55
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-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 "wl_text_input.h"
|
||||
#include "wayland-xdg-activation-v1-client-protocol.h"
|
||||
|
||||
#define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL)
|
||||
#define _glfw_dlclose(handle) dlclose(handle)
|
||||
@@ -102,6 +104,7 @@ typedef struct _GLFWWaylandBufferPair {
|
||||
struct { uint8_t *a, *b, *front, *back; } data;
|
||||
bool has_pending_update;
|
||||
size_t size_in_bytes, width, height, stride;
|
||||
bool a_needs_to_be_destroyed, b_needs_to_be_destroyed;
|
||||
} _GLFWWaylandBufferPair;
|
||||
|
||||
typedef struct _GLFWWaylandCSDEdge {
|
||||
@@ -116,17 +119,29 @@ typedef enum WaylandWindowState {
|
||||
TOPLEVEL_STATE_NONE = 0,
|
||||
TOPLEVEL_STATE_MAXIMIZED = 1,
|
||||
TOPLEVEL_STATE_FULLSCREEN = 2,
|
||||
TOPLEVEL_STATE_RESIZING = 4,
|
||||
TOPLEVEL_STATE_ACTIVATED = 8,
|
||||
TOPLEVEL_STATE_TILED_LEFT = 16,
|
||||
TOPLEVEL_STATE_TILED_RIGHT = 32,
|
||||
TOPLEVEL_STATE_TILED_TOP = 64,
|
||||
TOPLEVEL_STATE_TILED_BOTTOM = 128,
|
||||
TOPLEVEL_STATE_RESIZING = 4,
|
||||
TOPLEVEL_STATE_ACTIVATED = 8,
|
||||
TOPLEVEL_STATE_TILED_LEFT = 16,
|
||||
TOPLEVEL_STATE_TILED_RIGHT = 32,
|
||||
TOPLEVEL_STATE_TILED_TOP = 64,
|
||||
TOPLEVEL_STATE_TILED_BOTTOM = 128,
|
||||
} 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;
|
||||
|
||||
enum WaylandWindowPendingState {
|
||||
PENDING_STATE_TOPLEVEL = 1,
|
||||
PENDING_STATE_DECORATION = 2
|
||||
};
|
||||
|
||||
// Wayland-specific per-window data
|
||||
//
|
||||
@@ -137,6 +152,7 @@ typedef struct _GLFWwindowWayland
|
||||
bool hovered;
|
||||
bool transparent;
|
||||
struct wl_surface* surface;
|
||||
bool waiting_for_swap_to_commit;
|
||||
struct wl_egl_window* native;
|
||||
struct wl_callback* callback;
|
||||
|
||||
@@ -168,7 +184,7 @@ typedef struct _GLFWwindowWayland
|
||||
struct zwp_idle_inhibitor_v1* idleInhibitor;
|
||||
|
||||
struct {
|
||||
bool serverSide;
|
||||
bool serverSide, buffer_destroyed;
|
||||
_GLFWdecorationSideWayland focus;
|
||||
_GLFWWaylandCSDEdge top, left, right, bottom;
|
||||
|
||||
@@ -210,9 +226,20 @@ typedef struct _GLFWwindowWayland
|
||||
int32_t width, height;
|
||||
} user_requested_content_size;
|
||||
|
||||
uint32_t toplevel_states;
|
||||
bool maximize_on_first_show;
|
||||
// counters for ignoring axis events following axis_discrete events in the
|
||||
// same frame along the same axis
|
||||
struct {
|
||||
unsigned int x, y;
|
||||
} axis_discrete_count;
|
||||
bool surface_configured_once;
|
||||
|
||||
uint32_t pending_state;
|
||||
struct {
|
||||
int width, height;
|
||||
uint32_t toplevel_states;
|
||||
uint32_t decoration_mode;
|
||||
} current, pending;
|
||||
} _GLFWwindowWayland;
|
||||
|
||||
typedef enum _GLFWWaylandOfferType
|
||||
@@ -261,13 +288,14 @@ typedef struct _GLFWlibraryWayland
|
||||
struct zwp_primary_selection_device_manager_v1* primarySelectionDeviceManager;
|
||||
struct zwp_primary_selection_device_v1* primarySelectionDevice;
|
||||
struct zwp_primary_selection_source_v1* dataSourceForPrimarySelection;
|
||||
struct xdg_activation_v1* xdg_activation_v1;
|
||||
|
||||
int compositorVersion;
|
||||
int seatVersion;
|
||||
|
||||
struct wl_surface* cursorSurface;
|
||||
GLFWCursorShape cursorPreviousShape;
|
||||
uint32_t serial;
|
||||
uint32_t serial, input_serial;
|
||||
|
||||
int32_t keyboardRepeatRate;
|
||||
monotonic_t keyboardRepeatDelay;
|
||||
@@ -301,12 +329,14 @@ typedef struct _GLFWlibraryWayland
|
||||
PFN_wl_egl_window_resize window_resize;
|
||||
} egl;
|
||||
|
||||
struct {
|
||||
glfw_wl_xdg_activation_request *array;
|
||||
size_t capacity, sz;
|
||||
} activation_requests;
|
||||
|
||||
EventLoopData eventLoopData;
|
||||
char* pasteString;
|
||||
char* clipboardString;
|
||||
size_t dataOffersCounter;
|
||||
_GLFWWaylandDataOffer dataOffers[8];
|
||||
char* primarySelectionString;
|
||||
} _GLFWlibraryWayland;
|
||||
|
||||
// Wayland-specific per-monitor data
|
||||
@@ -340,6 +370,7 @@ typedef struct _GLFWcursorWayland
|
||||
|
||||
|
||||
void _glfwAddOutputWayland(uint32_t name, uint32_t version);
|
||||
void _glfwWaylandAfterBufferSwap(_GLFWwindow *window);
|
||||
void _glfwSetupWaylandDataDevice(void);
|
||||
void _glfwSetupWaylandPrimarySelectionDevice(void);
|
||||
void animateCursorImage(id_type timer_id, void *data);
|
||||
|
||||
53
glfw/wl_text_input.c
vendored
53
glfw/wl_text_input.c
vendored
@@ -9,12 +9,15 @@
|
||||
#include "internal.h"
|
||||
#include "wayland-text-input-unstable-v3-client-protocol.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#define debug(...) if (_glfw.hints.init.debugKeyboard) printf(__VA_ARGS__);
|
||||
|
||||
static struct zwp_text_input_v3* text_input;
|
||||
static struct zwp_text_input_manager_v3* text_input_manager;
|
||||
static char *pending_pre_edit = NULL;
|
||||
static char *current_pre_edit = 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;
|
||||
|
||||
static void commit(void) {
|
||||
@@ -47,7 +50,7 @@ static void
|
||||
send_text(const char *text, GLFWIMEState ime_state) {
|
||||
_GLFWwindow *w = _glfwFocusedWindow();
|
||||
if (w && w->callbacks.keyboard) {
|
||||
GLFWkeyevent fake_ev = {.action = GLFW_PRESS};
|
||||
GLFWkeyevent fake_ev = {.action = text ? GLFW_PRESS : GLFW_RELEASE};
|
||||
fake_ev.text = text;
|
||||
fake_ev.ime_state = ime_state;
|
||||
w->callbacks.keyboard((GLFWwindow*) w, &fake_ev);
|
||||
@@ -84,15 +87,26 @@ text_input_delete_surrounding_text(
|
||||
}
|
||||
|
||||
static void
|
||||
text_input_done(void *data UNUSED, struct zwp_text_input_v3 *txt_input UNUSED, uint32_t serial UNUSED) {
|
||||
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);
|
||||
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;
|
||||
}
|
||||
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;
|
||||
} else {
|
||||
free(current_pre_edit);
|
||||
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) {
|
||||
send_text(pending_commit, GLFW_IME_COMMIT_TEXT);
|
||||
@@ -130,6 +144,7 @@ _glfwWaylandDestroyTextInput(void) {
|
||||
if (text_input_manager) zwp_text_input_manager_v3_destroy(text_input_manager);
|
||||
text_input = NULL; text_input_manager = NULL;
|
||||
free(pending_pre_edit); pending_pre_edit = NULL;
|
||||
free(current_pre_edit); current_pre_edit = NULL;
|
||||
free(pending_commit); pending_commit = NULL;
|
||||
}
|
||||
|
||||
@@ -139,15 +154,35 @@ _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
|
||||
switch(ev->type) {
|
||||
case GLFW_IME_UPDATE_FOCUS:
|
||||
debug("\ntext-input: updating IME focus state, focused: %d\n", ev->focused);
|
||||
if (ev->focused) zwp_text_input_v3_enable(text_input); else zwp_text_input_v3_disable(text_input);
|
||||
if (ev->focused) {
|
||||
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);
|
||||
} else {
|
||||
free(pending_pre_edit); pending_pre_edit = NULL;
|
||||
if (current_pre_edit) {
|
||||
// Clear pre-edit text
|
||||
send_text(NULL, GLFW_IME_PREEDIT_CHANGED);
|
||||
free(current_pre_edit); current_pre_edit = NULL;
|
||||
}
|
||||
if (pending_commit) {
|
||||
free(pending_commit); pending_commit = NULL;
|
||||
}
|
||||
zwp_text_input_v3_disable(text_input);
|
||||
}
|
||||
commit();
|
||||
break;
|
||||
case GLFW_IME_UPDATE_CURSOR_POSITION: {
|
||||
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;
|
||||
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();
|
||||
if (left != last_cursor_left || top != last_cursor_top || width != last_cursor_width || height != last_cursor_height) {
|
||||
last_cursor_left = left;
|
||||
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;
|
||||
}
|
||||
|
||||
649
glfw/wl_window.c
vendored
649
glfw/wl_window.c
vendored
@@ -44,6 +44,59 @@
|
||||
|
||||
#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);
|
||||
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)
|
||||
{
|
||||
struct wl_shm_pool* pool;
|
||||
@@ -147,6 +200,7 @@ setCursorImage(_GLFWwindow* window, bool on_theme_change) {
|
||||
cursorWayland->yhot = image->hotspot_y;
|
||||
}
|
||||
|
||||
debug("Calling wl_pointer_set_cursor in setCursorImage with surface: %p\n", (void*)surface);
|
||||
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.serial,
|
||||
surface,
|
||||
cursorWayland->xhot / scale,
|
||||
@@ -197,8 +251,17 @@ static bool checkScaleChange(_GLFWwindow* window)
|
||||
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.
|
||||
static void setOpaqueRegion(_GLFWwindow* window)
|
||||
static void setOpaqueRegion(_GLFWwindow* window, bool commit_surface)
|
||||
{
|
||||
struct wl_region* region;
|
||||
|
||||
@@ -208,10 +271,22 @@ static void setOpaqueRegion(_GLFWwindow* window)
|
||||
|
||||
wl_region_add(region, 0, 0, window->wl.width, window->wl.height);
|
||||
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);
|
||||
}
|
||||
|
||||
static void
|
||||
swap_buffers(_GLFWwindow *window) {
|
||||
// this will attach the buffer to the surface,
|
||||
// the client is responsible for clearing the buffer to an appropriate blank
|
||||
window->swaps_disallowed = false;
|
||||
GLFWwindow *current = glfwGetCurrentContext();
|
||||
bool context_is_current = ((_GLFWwindow*)current)->id == window->id;
|
||||
if (!context_is_current) glfwMakeContextCurrent((GLFWwindow*)window);
|
||||
window->context.swapBuffers(window);
|
||||
if (!context_is_current) glfwMakeContextCurrent(current);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
resizeFramebuffer(_GLFWwindow* window) {
|
||||
@@ -220,10 +295,21 @@ resizeFramebuffer(_GLFWwindow* window) {
|
||||
int scaledHeight = 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);
|
||||
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);
|
||||
}
|
||||
|
||||
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*
|
||||
clipboard_mime(void) {
|
||||
@@ -234,7 +320,7 @@ clipboard_mime(void) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void
|
||||
static bool
|
||||
dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, int32_t height) {
|
||||
bool size_changed = width != window->wl.width || height != window->wl.height;
|
||||
bool scale_changed = checkScaleChange(window);
|
||||
@@ -246,12 +332,14 @@ dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, int32_t height
|
||||
}
|
||||
|
||||
if (scale_changed) {
|
||||
if (!size_changed)
|
||||
resizeFramebuffer(window);
|
||||
debug("Scale changed to %d in dispatchChangesAfterConfigure\n", window->wl.scale);
|
||||
if (!size_changed) resizeFramebuffer(window);
|
||||
_glfwInputWindowContentScale(window, window->wl.scale, window->wl.scale);
|
||||
}
|
||||
|
||||
_glfwInputWindowDamage(window);
|
||||
|
||||
return size_changed || scale_changed;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -269,24 +357,9 @@ xdgDecorationHandleConfigure(void* data,
|
||||
uint32_t mode)
|
||||
{
|
||||
_GLFWwindow* window = data;
|
||||
|
||||
bool has_server_side_decorations = (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
|
||||
debug("XDG decoration configure event received: has_server_side_decorations: %d\n", has_server_side_decorations);
|
||||
if (has_server_side_decorations == window->wl.decorations.serverSide) return;
|
||||
window->wl.decorations.serverSide = has_server_side_decorations;
|
||||
int width = window->wl.width, height = window->wl.height;
|
||||
if (window->wl.decorations.serverSide) {
|
||||
free_csd_surfaces(window);
|
||||
height += window->wl.decorations.metrics.visible_titlebar_height;
|
||||
} else {
|
||||
ensure_csd_resources(window);
|
||||
}
|
||||
set_csd_window_geometry(window, &width, &height);
|
||||
dispatchChangesAfterConfigure(window, width, height);
|
||||
ensure_csd_resources(window);
|
||||
wl_surface_commit(window->wl.surface);
|
||||
debug("final window content size: %dx%d\n", window->wl.width, window->wl.height);
|
||||
inform_compositor_of_window_geometry(window, "configure-decorations");
|
||||
window->wl.pending.decoration_mode = mode;
|
||||
window->wl.pending_state |= PENDING_STATE_DECORATION;
|
||||
debug("XDG decoration configure event received: has_server_side_decorations: %d\n", (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE));
|
||||
}
|
||||
|
||||
static const struct zxdg_toplevel_decoration_v1_listener xdgDecorationListener = {
|
||||
@@ -311,6 +384,7 @@ static void surfaceHandleEnter(void *data,
|
||||
window->wl.monitors[window->wl.monitorsCount++] = monitor;
|
||||
|
||||
if (checkScaleChange(window)) {
|
||||
debug("Scale changed to %d in surface enter event\n", window->wl.scale);
|
||||
resizeFramebuffer(window);
|
||||
_glfwInputWindowContentScale(window, window->wl.scale, window->wl.scale);
|
||||
ensure_csd_resources(window);
|
||||
@@ -336,6 +410,7 @@ static void surfaceHandleLeave(void *data,
|
||||
window->wl.monitors[--window->wl.monitorsCount] = NULL;
|
||||
|
||||
if (checkScaleChange(window)) {
|
||||
debug("Scale changed to %d in surface leave event\n", window->wl.scale);
|
||||
resizeFramebuffer(window);
|
||||
_glfwInputWindowContentScale(window, window->wl.scale, window->wl.scale);
|
||||
ensure_csd_resources(window);
|
||||
@@ -378,9 +453,22 @@ static bool createSurface(_GLFWwindow* window,
|
||||
|
||||
wl_surface_set_user_data(window->wl.surface, window);
|
||||
|
||||
window->wl.native = wl_egl_window_create(window->wl.surface,
|
||||
wndconfig->width,
|
||||
wndconfig->height);
|
||||
// If we already have been notified of the primary monitor scale, assume
|
||||
// the window will be created on it and so avoid a rescale roundtrip in the common
|
||||
// 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)
|
||||
return false;
|
||||
|
||||
@@ -388,11 +476,13 @@ static bool createSurface(_GLFWwindow* window,
|
||||
window->wl.height = wndconfig->height;
|
||||
window->wl.user_requested_content_size.width = wndconfig->width;
|
||||
window->wl.user_requested_content_size.height = wndconfig->height;
|
||||
window->wl.scale = 1;
|
||||
|
||||
window->wl.scale = scale;
|
||||
|
||||
if (!window->wl.transparent)
|
||||
setOpaqueRegion(window);
|
||||
setOpaqueRegion(window, false);
|
||||
|
||||
wl_surface_set_buffer_scale(window->wl.surface, scale);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -416,7 +506,7 @@ static void setFullscreen(_GLFWwindow* window, _GLFWmonitor* monitor, bool on)
|
||||
|
||||
bool
|
||||
_glfwPlatformIsFullscreen(_GLFWwindow *window, unsigned int flags UNUSED) {
|
||||
return window->wl.toplevel_states & TOPLEVEL_STATE_FULLSCREEN;
|
||||
return window->wl.current.toplevel_states & TOPLEVEL_STATE_FULLSCREEN;
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -458,7 +548,7 @@ xdgToplevelHandleConfigure(void* data,
|
||||
if (new_states & TOPLEVEL_STATE_RESIZING) {
|
||||
if (width) window->wl.user_requested_content_size.width = width;
|
||||
if (height) window->wl.user_requested_content_size.height = height;
|
||||
if (!(window->wl.toplevel_states & TOPLEVEL_STATE_RESIZING)) _glfwInputLiveResize(window, true);
|
||||
if (!(window->wl.current.toplevel_states & TOPLEVEL_STATE_RESIZING)) _glfwInputLiveResize(window, true);
|
||||
}
|
||||
if (width != 0 && height != 0)
|
||||
{
|
||||
@@ -475,16 +565,11 @@ xdgToplevelHandleConfigure(void* data,
|
||||
}
|
||||
}
|
||||
}
|
||||
bool live_resize_done = !(new_states & TOPLEVEL_STATE_RESIZING) && (window->wl.toplevel_states & TOPLEVEL_STATE_RESIZING);
|
||||
window->wl.toplevel_states = new_states;
|
||||
set_csd_window_geometry(window, &width, &height);
|
||||
dispatchChangesAfterConfigure(window, width, height);
|
||||
debug("final window content size: %dx%d\n", window->wl.width, window->wl.height);
|
||||
_glfwInputWindowFocus(window, window->wl.toplevel_states & TOPLEVEL_STATE_ACTIVATED);
|
||||
ensure_csd_resources(window);
|
||||
wl_surface_commit(window->wl.surface);
|
||||
inform_compositor_of_window_geometry(window, "configure");
|
||||
if (live_resize_done) _glfwInputLiveResize(window, false);
|
||||
|
||||
window->wl.pending.toplevel_states = new_states;
|
||||
window->wl.pending.width = width;
|
||||
window->wl.pending.height = height;
|
||||
window->wl.pending_state |= PENDING_STATE_TOPLEVEL;
|
||||
}
|
||||
|
||||
static void xdgToplevelHandleClose(void* data,
|
||||
@@ -499,11 +584,66 @@ static const struct xdg_toplevel_listener xdgToplevelListener = {
|
||||
xdgToplevelHandleClose
|
||||
};
|
||||
|
||||
static void xdgSurfaceHandleConfigure(void* data UNUSED,
|
||||
static void xdgSurfaceHandleConfigure(void* data,
|
||||
struct xdg_surface* surface,
|
||||
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;
|
||||
xdg_surface_ack_configure(surface, serial);
|
||||
if (window->wl.pending_state & PENDING_STATE_TOPLEVEL) {
|
||||
uint32_t new_states = window->wl.pending.toplevel_states;
|
||||
int width = window->wl.pending.width;
|
||||
int height = window->wl.pending.height;
|
||||
if (!window->wl.surface_configured_once) {
|
||||
window->wl.surface_configured_once = true;
|
||||
swap_buffers(window);
|
||||
}
|
||||
|
||||
if (new_states != window->wl.current.toplevel_states ||
|
||||
width != window->wl.current.width ||
|
||||
height != window->wl.current.height) {
|
||||
|
||||
bool live_resize_done = !(new_states & TOPLEVEL_STATE_RESIZING) && (window->wl.current.toplevel_states & TOPLEVEL_STATE_RESIZING);
|
||||
window->wl.current.toplevel_states = new_states;
|
||||
window->wl.current.width = width;
|
||||
window->wl.current.height = height;
|
||||
_glfwInputWindowFocus(window, window->wl.current.toplevel_states & TOPLEVEL_STATE_ACTIVATED);
|
||||
if (live_resize_done) _glfwInputLiveResize(window, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (window->wl.pending_state & PENDING_STATE_DECORATION) {
|
||||
uint32_t mode = window->wl.pending.decoration_mode;
|
||||
bool has_server_side_decorations = (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
|
||||
window->wl.decorations.serverSide = has_server_side_decorations;
|
||||
window->wl.current.decoration_mode = mode;
|
||||
}
|
||||
|
||||
bool resized = false;
|
||||
if (window->wl.pending_state) {
|
||||
int width = window->wl.pending.width, height = window->wl.pending.height;
|
||||
set_csd_window_geometry(window, &width, &height);
|
||||
resized = dispatchChangesAfterConfigure(window, width, height);
|
||||
if (window->wl.decorations.serverSide) {
|
||||
free_csd_surfaces(window);
|
||||
} else {
|
||||
ensure_csd_resources(window);
|
||||
}
|
||||
debug("final window content size: %dx%d resized: %d\n", width, height, resized);
|
||||
}
|
||||
|
||||
inform_compositor_of_window_geometry(window, "configure");
|
||||
commit_window_surface_if_safe(window);
|
||||
window->wl.pending_state = 0;
|
||||
}
|
||||
|
||||
static const struct xdg_surface_listener xdgSurfaceListener = {
|
||||
@@ -746,6 +886,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
||||
initialize_csd_metrics(window);
|
||||
window->wl.transparent = fbconfig->transparent;
|
||||
strncpy(window->wl.appId, wndconfig->wl.appId, sizeof(window->wl.appId));
|
||||
window->swaps_disallowed = true;
|
||||
|
||||
if (!createSurface(window, wndconfig))
|
||||
return false;
|
||||
@@ -901,7 +1042,7 @@ void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
|
||||
window->wl.width = w; window->wl.height = h;
|
||||
resizeFramebuffer(window);
|
||||
ensure_csd_resources(window);
|
||||
wl_surface_commit(window->wl.surface);
|
||||
commit_window_surface_if_safe(window);
|
||||
inform_compositor_of_window_geometry(window, "SetWindowSize");
|
||||
}
|
||||
}
|
||||
@@ -918,7 +1059,7 @@ void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
|
||||
maxwidth = maxheight = 0;
|
||||
xdg_toplevel_set_min_size(window->wl.xdg.toplevel, minwidth, minheight);
|
||||
xdg_toplevel_set_max_size(window->wl.xdg.toplevel, maxwidth, maxheight);
|
||||
wl_surface_commit(window->wl.surface);
|
||||
commit_window_surface_if_safe(window);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -955,7 +1096,7 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
||||
if (window->decorated && !window->monitor && !window->wl.decorations.serverSide)
|
||||
{
|
||||
if (top)
|
||||
*top = window->wl.decorations.metrics.top;
|
||||
*top = window->wl.decorations.metrics.top - window->wl.decorations.metrics.visible_titlebar_height;
|
||||
if (left)
|
||||
*left = window->wl.decorations.metrics.width;
|
||||
if (right)
|
||||
@@ -991,7 +1132,7 @@ void _glfwPlatformRestoreWindow(_GLFWwindow* window)
|
||||
{
|
||||
if (window->monitor)
|
||||
xdg_toplevel_unset_fullscreen(window->wl.xdg.toplevel);
|
||||
if (window->wl.toplevel_states & TOPLEVEL_STATE_MAXIMIZED)
|
||||
if (window->wl.current.toplevel_states & TOPLEVEL_STATE_MAXIMIZED)
|
||||
xdg_toplevel_unset_maximized(window->wl.xdg.toplevel);
|
||||
// There is no way to unset minimized, or even to know if we are
|
||||
// minimized, so there is nothing to do in this case.
|
||||
@@ -1024,38 +1165,51 @@ void _glfwPlatformHideWindow(_GLFWwindow* window)
|
||||
xdg_surface_destroy(window->wl.xdg.surface);
|
||||
window->wl.xdg.toplevel = NULL;
|
||||
window->wl.xdg.surface = NULL;
|
||||
window->wl.surface_configured_once = false;
|
||||
window->swaps_disallowed = true;
|
||||
}
|
||||
window->wl.visible = false;
|
||||
}
|
||||
|
||||
void _glfwPlatformRequestWindowAttention(_GLFWwindow* window UNUSED)
|
||||
{
|
||||
// TODO
|
||||
static bool notified = false;
|
||||
if (!notified) {
|
||||
_glfwInputError(GLFW_FEATURE_UNIMPLEMENTED,
|
||||
"Wayland: Window attention request not implemented yet");
|
||||
notified = true;
|
||||
static void
|
||||
request_attention(GLFWwindow *window, const char *token, void *data UNUSED) {
|
||||
if (window && token && token[0]) xdg_activation_v1_activate(_glfw.wl.xdg_activation_v1, token, ((_GLFWwindow*)window)->wl.surface);
|
||||
}
|
||||
|
||||
static bool
|
||||
has_activation_in_flight(_GLFWwindow* window, GLFWactivationcallback callback) {
|
||||
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)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
_glfwInputError(GLFW_FEATURE_UNAVAILABLE,
|
||||
"Wayland: The platform does not support setting the input focus");
|
||||
// Attempt to focus the window by using the activation protocol, whether it works
|
||||
// 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,
|
||||
@@ -1092,7 +1246,7 @@ int _glfwPlatformWindowVisible(_GLFWwindow* window)
|
||||
|
||||
int _glfwPlatformWindowMaximized(_GLFWwindow* window)
|
||||
{
|
||||
return window->wl.toplevel_states & TOPLEVEL_STATE_MAXIMIZED;
|
||||
return window->wl.current.toplevel_states & TOPLEVEL_STATE_MAXIMIZED;
|
||||
}
|
||||
|
||||
int _glfwPlatformWindowHovered(_GLFWwindow* window)
|
||||
@@ -1140,7 +1294,7 @@ void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled)
|
||||
}
|
||||
else
|
||||
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)
|
||||
@@ -1204,7 +1358,7 @@ void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y)
|
||||
zwp_locked_pointer_v1_set_cursor_position_hint(
|
||||
window->wl.pointerLock.lockedPointer,
|
||||
wl_fixed_from_double(x), wl_fixed_from_double(y));
|
||||
wl_surface_commit(window->wl.surface);
|
||||
commit_window_surface_if_safe(window);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1360,6 +1514,7 @@ static void lockPointer(_GLFWwindow* window)
|
||||
window->wl.pointerLock.relativePointer = relativePointer;
|
||||
window->wl.pointerLock.lockedPointer = lockedPointer;
|
||||
|
||||
debug("Calling wl_pointer_set_cursor in lockPointer with surface: %p\n", NULL);
|
||||
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.serial,
|
||||
NULL, 0, 0);
|
||||
}
|
||||
@@ -1396,57 +1551,73 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
|
||||
}
|
||||
else if (window->cursorMode == GLFW_CURSOR_HIDDEN)
|
||||
{
|
||||
debug("Calling wl_pointer_set_cursor in _glfwPlatformSetCursor with surface: %p\n", NULL);
|
||||
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.serial, NULL, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void send_text(char *text, int fd)
|
||||
{
|
||||
if (text) {
|
||||
size_t len = strlen(text), pos = 0;
|
||||
monotonic_t start = glfwGetTime();
|
||||
while (pos < len && glfwGetTime() - start < s_to_monotonic_t(2ll)) {
|
||||
ssize_t ret = write(fd, text + pos, len - pos);
|
||||
if (ret < 0) {
|
||||
if (errno == EAGAIN || errno == EINTR) continue;
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Could not copy writing to destination fd failed with error: %s", strerror(errno));
|
||||
break;
|
||||
}
|
||||
if (ret > 0) {
|
||||
start = glfwGetTime();
|
||||
pos += ret;
|
||||
}
|
||||
static bool
|
||||
write_all(int fd, const char *data, size_t sz) {
|
||||
monotonic_t start = glfwGetTime();
|
||||
size_t pos = 0;
|
||||
while (pos < sz && glfwGetTime() - start < s_to_monotonic_t(2ll)) {
|
||||
ssize_t ret = write(fd, data + pos, sz - pos);
|
||||
if (ret < 0) {
|
||||
if (errno == EAGAIN || errno == EINTR) continue;
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Could not copy writing to destination fd failed with error: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
if (ret > 0) {
|
||||
start = glfwGetTime();
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
const char *mime_type UNUSED, int fd) {
|
||||
send_text(_glfw.wl.primarySelectionString, fd);
|
||||
const char *mime_type, int 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);
|
||||
size_t capacity = 0;
|
||||
char *buf = NULL;
|
||||
*data_sz = 0;
|
||||
struct pollfd fds;
|
||||
fds.fd = data_pipe;
|
||||
fds.events = POLLIN;
|
||||
monotonic_t start = glfwGetTime();
|
||||
#define bail(...) { \
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, __VA_ARGS__); \
|
||||
free(buf); buf = NULL; \
|
||||
close(data_pipe); \
|
||||
return NULL; \
|
||||
return; \
|
||||
}
|
||||
|
||||
char buf[8192];
|
||||
|
||||
while (glfwGetTime() - start < s_to_monotonic_t(2ll)) {
|
||||
int ret = poll(&fds, 1, 2000);
|
||||
if (ret == -1) {
|
||||
@@ -1456,34 +1627,65 @@ static char* read_offer_string(int data_pipe, size_t *data_sz) {
|
||||
if (!ret) {
|
||||
bail("Wayland: Failed to read clipboard data from pipe (timed out)");
|
||||
}
|
||||
if (capacity <= *data_sz || capacity - *data_sz <= 64) {
|
||||
capacity += 4096;
|
||||
buf = realloc(buf, capacity);
|
||||
if (!buf) {
|
||||
bail("Wayland: Failed to allocate memory to read clipboard data");
|
||||
}
|
||||
}
|
||||
ret = read(data_pipe, buf + *data_sz, capacity - *data_sz - 1);
|
||||
ret = read(data_pipe, buf, sizeof(buf));
|
||||
if (ret == -1) {
|
||||
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue;
|
||||
bail("Wayland: Failed to read clipboard data from pipe with error: %s", strerror(errno));
|
||||
}
|
||||
if (ret == 0) { close(data_pipe); buf[*data_sz] = 0; return buf; }
|
||||
*data_sz += ret;
|
||||
if (ret == 0) { close(data_pipe); return; }
|
||||
if (!write_data(object, buf, ret)) bail("Wayland: call to write_data() failed with data from data offer");
|
||||
start = glfwGetTime();
|
||||
}
|
||||
bail("Wayland: Failed to read clipboard data from pipe (timed out)");
|
||||
#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);
|
||||
}
|
||||
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];
|
||||
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]);
|
||||
close(pipefd[1]);
|
||||
size_t sz = 0;
|
||||
return read_offer_string(pipefd[0], &sz);
|
||||
read_offer(pipefd[0], write_data, object);
|
||||
}
|
||||
|
||||
static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime, size_t *sz) {
|
||||
@@ -1495,24 +1697,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) {
|
||||
if (_glfw.wl.dataSourceForClipboard == wl_data_source)
|
||||
if (_glfw.wl.dataSourceForClipboard == wl_data_source) {
|
||||
_glfw.wl.dataSourceForClipboard = NULL;
|
||||
_glfw_free_clipboard_data(&_glfw.clipboard);
|
||||
}
|
||||
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) {
|
||||
if (_glfw.wl.dataSourceForPrimarySelection == primary_selection_source)
|
||||
if (_glfw.wl.dataSourceForPrimarySelection == primary_selection_source) {
|
||||
_glfw.wl.dataSourceForPrimarySelection = NULL;
|
||||
_glfw_free_clipboard_data(&_glfw.primary);
|
||||
}
|
||||
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 = {
|
||||
.send = _glfwSendClipboardText,
|
||||
.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 = {
|
||||
@@ -1789,30 +2000,69 @@ static bool _glfwEnsureDataDevice(void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetClipboardString(const char* string)
|
||||
{
|
||||
if (!_glfwEnsureDataDevice()) return;
|
||||
free(_glfw.wl.clipboardString);
|
||||
_glfw.wl.clipboardString = _glfw_strdup(string);
|
||||
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;
|
||||
typedef void(*add_offer_func)(void*, const char *mime);
|
||||
|
||||
|
||||
void
|
||||
_glfwPlatformSetClipboard(GLFWClipboardType t) {
|
||||
_GLFWClipboardData *cd = NULL;
|
||||
void *data_source;
|
||||
add_offer_func f;
|
||||
if (t == GLFW_CLIPBOARD) {
|
||||
if (!_glfwEnsureDataDevice()) return;
|
||||
cd = &_glfw.clipboard;
|
||||
f = (add_offer_func)wl_data_source_offer;
|
||||
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);
|
||||
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);
|
||||
if (t == GLFW_CLIPBOARD) {
|
||||
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
|
||||
@@ -1830,81 +2080,47 @@ plain_text_mime_for_offer(const _GLFWWaylandDataOffer *d) {
|
||||
A("text/plain");
|
||||
A("UTF8_STRING");
|
||||
A("STRING");
|
||||
A("TEXT");
|
||||
#undef A
|
||||
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++) {
|
||||
_GLFWWaylandDataOffer *d = _glfw.wl.dataOffers + i;
|
||||
if (d->id && d->offer_type == CLIPBOARD) {
|
||||
if (d->is_self_offer) return _glfw.wl.clipboardString;
|
||||
const char *mime = plain_text_mime_for_offer(d);
|
||||
if (mime) {
|
||||
free(_glfw.wl.pasteString);
|
||||
size_t sz = 0;
|
||||
_glfw.wl.pasteString = read_data_offer(d->id, mime, &sz);
|
||||
return _glfw.wl.pasteString;
|
||||
if (d->id && d->offer_type == offer_type) {
|
||||
if (d->is_self_offer) {
|
||||
write_data(object, NULL, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetPrimarySelectionString(const char* string)
|
||||
{
|
||||
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;
|
||||
}
|
||||
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 (mime_type == NULL) {
|
||||
bool ok = true;
|
||||
for (size_t o = 0; o < d->mimes_count; o++) {
|
||||
const char *q = d->mimes[o];
|
||||
if (strchr(d->mimes[0], '/')) {
|
||||
if (strcmp(q, clipboard_mime()) == 0) continue;
|
||||
if (strcmp(q, "text/plain;charset=utf-8") == 0) q = "text/plain";
|
||||
} else {
|
||||
if (strcmp(q, "UTF8_STRING") == 0 || strcmp(q, "STRING") == 0 || strcmp(q, "TEXT") == 0) q = "text/plain";
|
||||
}
|
||||
if (ok) ok = write_data(object, q, strlen(q));
|
||||
}
|
||||
return;
|
||||
}
|
||||
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)
|
||||
@@ -2026,6 +2242,18 @@ GLFWAPI struct wl_surface* glfwGetWaylandWindow(GLFWwindow* handle)
|
||||
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) {
|
||||
return glfw_xkb_keysym_from_name(keyName, caseSensitive);
|
||||
}
|
||||
@@ -2034,12 +2262,19 @@ GLFWAPI void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
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);
|
||||
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);
|
||||
wl_surface_commit(window->wl.surface);
|
||||
if (window->wl.waiting_for_swap_to_commit) {
|
||||
callback(id);
|
||||
window->wl.frameCallbackData.id = 0;
|
||||
window->wl.frameCallbackData.callback = NULL;
|
||||
window->wl.frameCallbackData.current_wl_callback = NULL;
|
||||
} 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");
|
||||
_glfw.x11.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 =
|
||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WORKAREA");
|
||||
_glfw.x11.NET_CURRENT_DESKTOP =
|
||||
@@ -147,6 +149,8 @@ static void detectEWMH(void)
|
||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_FRAME_EXTENTS");
|
||||
_glfw.x11.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);
|
||||
}
|
||||
@@ -680,8 +684,14 @@ void _glfwPlatformTerminate(void)
|
||||
|
||||
glfw_xkb_release(&_glfw.x11.xkb);
|
||||
glfw_dbus_terminate(&_glfw.x11.dbus);
|
||||
free(_glfw.x11.primarySelectionString);
|
||||
free(_glfw.x11.clipboardString);
|
||||
if (_glfw.x11.mime_atoms.array) {
|
||||
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)
|
||||
{
|
||||
|
||||
18
glfw/x11_platform.h
vendored
18
glfw/x11_platform.h
vendored
@@ -207,6 +207,16 @@ typedef struct _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
|
||||
//
|
||||
typedef struct _GLFWlibraryX11
|
||||
@@ -225,10 +235,6 @@ typedef struct _GLFWlibraryX11
|
||||
XContext context;
|
||||
// Most recent error code received by X error handler
|
||||
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
|
||||
double restoreCursorPosX, restoreCursorPosY;
|
||||
// The window whose disabled cursor mode is active
|
||||
@@ -247,6 +253,7 @@ typedef struct _GLFWlibraryX11
|
||||
Atom NET_WM_PING;
|
||||
Atom NET_WM_WINDOW_TYPE;
|
||||
Atom NET_WM_WINDOW_TYPE_NORMAL;
|
||||
Atom NET_WM_WINDOW_TYPE_DOCK;
|
||||
Atom NET_WM_STATE;
|
||||
Atom NET_WM_STATE_ABOVE;
|
||||
Atom NET_WM_STATE_FULLSCREEN;
|
||||
@@ -262,6 +269,7 @@ typedef struct _GLFWlibraryX11
|
||||
Atom NET_ACTIVE_WINDOW;
|
||||
Atom NET_FRAME_EXTENTS;
|
||||
Atom NET_REQUEST_FRAME_EXTENTS;
|
||||
Atom NET_WM_STRUT_PARTIAL;
|
||||
Atom MOTIF_WM_HINTS;
|
||||
|
||||
// Xdnd (drag and drop) atoms
|
||||
@@ -291,6 +299,8 @@ typedef struct _GLFWlibraryX11
|
||||
|
||||
// XRM database atom
|
||||
Atom RESOURCE_MANAGER;
|
||||
// Atoms for MIME types
|
||||
AtomArray mime_atoms, clipboard_atoms, primary_atoms;
|
||||
|
||||
struct {
|
||||
bool available;
|
||||
|
||||
366
glfw/x11_window.c
vendored
366
glfw/x11_window.c
vendored
@@ -716,19 +716,60 @@ static bool createNativeWindow(_GLFWwindow* window,
|
||||
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);
|
||||
}
|
||||
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
|
||||
//
|
||||
static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
|
||||
{
|
||||
int i;
|
||||
char* selectionString = NULL;
|
||||
const Atom formats[] = { _glfw.x11.UTF8_STRING, XA_STRING };
|
||||
const int formatCount = sizeof(formats) / sizeof(formats[0]);
|
||||
const AtomArray *aa;
|
||||
const _GLFWClipboardData *cd;
|
||||
|
||||
if (request->selection == _glfw.x11.PRIMARY)
|
||||
selectionString = _glfw.x11.primarySelectionString;
|
||||
else
|
||||
selectionString = _glfw.x11.clipboardString;
|
||||
if (request->selection == _glfw.x11.PRIMARY) {
|
||||
aa = &_glfw.x11.primary_atoms;
|
||||
cd = &_glfw.primary;
|
||||
} else {
|
||||
aa = &_glfw.x11.clipboard_atoms;
|
||||
cd = &_glfw.clipboard;
|
||||
}
|
||||
|
||||
if (request->property == None)
|
||||
{
|
||||
@@ -741,11 +782,10 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
|
||||
{
|
||||
// The list of supported targets was requested
|
||||
|
||||
const Atom targets[] = { _glfw.x11.TARGETS,
|
||||
_glfw.x11.MULTIPLE,
|
||||
_glfw.x11.UTF8_STRING,
|
||||
XA_STRING };
|
||||
|
||||
Atom *targets = calloc(aa->sz + 2, sizeof(Atom));
|
||||
targets[0] = _glfw.x11.TARGETS;
|
||||
targets[1] = _glfw.x11.MULTIPLE;
|
||||
for (size_t i = 0; i < aa->sz; i++) targets[i+2] = aa->array[i].atom;
|
||||
XChangeProperty(_glfw.x11.display,
|
||||
request->requestor,
|
||||
request->property,
|
||||
@@ -753,8 +793,8 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
|
||||
32,
|
||||
PropModeReplace,
|
||||
(unsigned char*) targets,
|
||||
sizeof(targets) / sizeof(targets[0]));
|
||||
|
||||
aa->sz + 2);
|
||||
free(targets);
|
||||
return request->property;
|
||||
}
|
||||
|
||||
@@ -763,7 +803,7 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
|
||||
// Multiple conversions were requested
|
||||
|
||||
Atom* targets;
|
||||
unsigned long i, count;
|
||||
size_t i, j, count;
|
||||
|
||||
count = _glfwGetWindowPropertyX11(request->requestor,
|
||||
request->property,
|
||||
@@ -772,24 +812,25 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
|
||||
|
||||
for (i = 0; i < count; i += 2)
|
||||
{
|
||||
int j;
|
||||
|
||||
for (j = 0; j < formatCount; j++)
|
||||
for (j = 0; j < aa->sz; j++)
|
||||
{
|
||||
if (targets[i] == formats[j])
|
||||
if (targets[i] == aa->array[j].atom)
|
||||
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,
|
||||
targets[i + 1],
|
||||
targets[i],
|
||||
8,
|
||||
PropModeReplace,
|
||||
(unsigned char *) selectionString,
|
||||
strlen(selectionString));
|
||||
(unsigned char *) data,
|
||||
sz);
|
||||
free(data);
|
||||
}
|
||||
else
|
||||
targets[i + 1] = None;
|
||||
@@ -828,20 +869,22 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
|
||||
|
||||
// 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
|
||||
|
||||
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->property,
|
||||
request->target,
|
||||
8,
|
||||
PropModeReplace,
|
||||
(unsigned char *) selectionString,
|
||||
strlen(selectionString));
|
||||
(unsigned char *) data,
|
||||
sz);
|
||||
free(data);
|
||||
|
||||
return request->property;
|
||||
}
|
||||
@@ -856,13 +899,11 @@ static void handleSelectionClear(XEvent* event)
|
||||
{
|
||||
if (event->xselectionclear.selection == _glfw.x11.PRIMARY)
|
||||
{
|
||||
free(_glfw.x11.primarySelectionString);
|
||||
_glfw.x11.primarySelectionString = NULL;
|
||||
_glfw_free_clipboard_data(&_glfw.primary);
|
||||
}
|
||||
else
|
||||
{
|
||||
free(_glfw.x11.clipboardString);
|
||||
_glfw.x11.clipboardString = NULL;
|
||||
_glfw_free_clipboard_data(&_glfw.clipboard);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -881,34 +922,21 @@ static void handleSelectionRequest(XEvent* event)
|
||||
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;
|
||||
const Atom targets[] = { _glfw.x11.UTF8_STRING, XA_STRING };
|
||||
const size_t targetCount = sizeof(targets) / sizeof(targets[0]);
|
||||
|
||||
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;
|
||||
#define XFREE(x) { if (x) XFree(x); x = NULL; }
|
||||
if (XGetSelectionOwner(_glfw.x11.display, selection) == _glfw.x11.helperWindowHandle) {
|
||||
write_data(object, NULL, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
free(*selectionString);
|
||||
*selectionString = NULL;
|
||||
|
||||
for (size_t i = 0; i < targetCount; i++)
|
||||
bool found = false;
|
||||
for (size_t i = 0; !found && i < num_targets; i++)
|
||||
{
|
||||
char* data;
|
||||
Atom actualType;
|
||||
int actualFormat;
|
||||
unsigned long itemCount, bytesAfter;
|
||||
char* data = NULL;
|
||||
Atom actualType = None;
|
||||
int actualFormat = 0;
|
||||
unsigned long itemCount = 0, bytesAfter = 0;
|
||||
monotonic_t start = glfwGetTime();
|
||||
XEvent notification, dummy;
|
||||
|
||||
@@ -925,8 +953,7 @@ static const char* getSelectionString(Atom selection)
|
||||
¬ification))
|
||||
{
|
||||
monotonic_t time = glfwGetTime();
|
||||
if (time - start > s_to_monotonic_t(2ll))
|
||||
return "";
|
||||
if (time - start > s_to_monotonic_t(2ll)) return;
|
||||
waitForX11Event(s_to_monotonic_t(2ll) - (time - start));
|
||||
}
|
||||
|
||||
@@ -953,9 +980,6 @@ static const char* getSelectionString(Atom selection)
|
||||
|
||||
if (actualType == _glfw.x11.INCR)
|
||||
{
|
||||
size_t size = 1;
|
||||
char* string = NULL;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
start = glfwGetTime();
|
||||
@@ -966,13 +990,12 @@ static const char* getSelectionString(Atom selection)
|
||||
{
|
||||
monotonic_t time = glfwGetTime();
|
||||
if (time - start > s_to_monotonic_t(2ll)) {
|
||||
free(string);
|
||||
return "";
|
||||
return;
|
||||
}
|
||||
waitForX11Event(s_to_monotonic_t(2ll) - (time - start));
|
||||
}
|
||||
|
||||
XFree(data);
|
||||
XFREE(data);
|
||||
XGetWindowProperty(_glfw.x11.display,
|
||||
notification.xselection.requestor,
|
||||
notification.xselection.property,
|
||||
@@ -988,47 +1011,41 @@ static const char* getSelectionString(Atom selection)
|
||||
|
||||
if (itemCount)
|
||||
{
|
||||
size += itemCount;
|
||||
string = realloc(string, size);
|
||||
string[size - itemCount - 1] = '\0';
|
||||
strcat(string, data);
|
||||
}
|
||||
|
||||
if (!itemCount)
|
||||
{
|
||||
if (targets[i] == XA_STRING)
|
||||
{
|
||||
*selectionString = convertLatin1toUTF8(string);
|
||||
free(string);
|
||||
const char *string = data;
|
||||
if (targets[i] == XA_STRING) {
|
||||
string = convertLatin1toUTF8(data);
|
||||
itemCount = strlen(string);
|
||||
}
|
||||
else
|
||||
*selectionString = string;
|
||||
bool ok = write_data(object, string, itemCount);
|
||||
if (string != data) free((void*)string);
|
||||
if (!ok) { XFREE(data); break; }
|
||||
} else { found = true; break; }
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (actualType == targets[i])
|
||||
{
|
||||
if (targets[i] == XA_STRING)
|
||||
*selectionString = convertLatin1toUTF8(data);
|
||||
else
|
||||
*selectionString = _glfw_strdup(data);
|
||||
if (targets[i] == XA_STRING) {
|
||||
const char *string = convertLatin1toUTF8(data);
|
||||
write_data(object, string, strlen(string)); free((void*)string);
|
||||
} else write_data(object, data, itemCount);
|
||||
found = true;
|
||||
}
|
||||
else if (actualType == XA_ATOM && targets[i] == _glfw.x11.TARGETS) {
|
||||
found = true;
|
||||
write_data(object, data, itemCount);
|
||||
}
|
||||
|
||||
XFree(data);
|
||||
XFREE(data);
|
||||
|
||||
if (*selectionString)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!*selectionString)
|
||||
if (!found && report_not_found)
|
||||
{
|
||||
_glfwInputError(GLFW_FORMAT_UNAVAILABLE,
|
||||
"X11: Failed to convert selection to string");
|
||||
"X11: Failed to convert selection to data from clipboard");
|
||||
}
|
||||
|
||||
return *selectionString;
|
||||
#undef XFREE
|
||||
}
|
||||
|
||||
// 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
|
||||
//
|
||||
static void processEvent(XEvent *event)
|
||||
@@ -2849,51 +2851,112 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor UNUSED)
|
||||
}
|
||||
}
|
||||
|
||||
void _glfwPlatformSetClipboardString(const char* string)
|
||||
{
|
||||
char* copy = _glfw_strdup(string);
|
||||
free(_glfw.x11.clipboardString);
|
||||
_glfw.x11.clipboardString = copy;
|
||||
static MimeAtom atom_for_mime(const char *mime) {
|
||||
for (size_t i = 0; i < _glfw.x11.mime_atoms.sz; i++) {
|
||||
MimeAtom ma = _glfw.x11.mime_atoms.array[i];
|
||||
if (strcmp(ma.mime, mime) == 0) {
|
||||
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);
|
||||
}
|
||||
_glfw.x11.mime_atoms.array[_glfw.x11.mime_atoms.sz++] = ma;
|
||||
return ma;
|
||||
}
|
||||
|
||||
XSetSelectionOwner(_glfw.x11.display,
|
||||
_glfw.x11.CLIPBOARD,
|
||||
_glfw.x11.helperWindowHandle,
|
||||
CurrentTime);
|
||||
|
||||
if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.CLIPBOARD) !=
|
||||
_glfw.x11.helperWindowHandle)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"X11: Failed to become owner of clipboard selection");
|
||||
void _glfwPlatformSetClipboard(GLFWClipboardType t) {
|
||||
Atom which = None;
|
||||
_GLFWClipboardData *cd = NULL;
|
||||
AtomArray *aa = NULL;
|
||||
switch (t) {
|
||||
case GLFW_CLIPBOARD: which = _glfw.x11.CLIPBOARD; cd = &_glfw.clipboard; aa = &_glfw.x11.clipboard_atoms; break;
|
||||
case GLFW_PRIMARY_SELECTION: which = _glfw.x11.PRIMARY; cd = &_glfw.primary; aa = &_glfw.x11.primary_atoms; break;
|
||||
}
|
||||
XSetSelectionOwner(_glfw.x11.display, which, _glfw.x11.helperWindowHandle, CurrentTime);
|
||||
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)
|
||||
{
|
||||
return getSelectionString(_glfw.x11.CLIPBOARD);
|
||||
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);
|
||||
}
|
||||
memcpy(cw->buf + cw->sz, data, sz);
|
||||
cw->sz += sz;
|
||||
return true;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetPrimarySelectionString(const char* string)
|
||||
{
|
||||
free(_glfw.x11.primarySelectionString);
|
||||
_glfw.x11.primarySelectionString = _glfw_strdup(string);
|
||||
|
||||
XSetSelectionOwner(_glfw.x11.display,
|
||||
_glfw.x11.PRIMARY,
|
||||
_glfw.x11.helperWindowHandle,
|
||||
CurrentTime);
|
||||
|
||||
if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.PRIMARY) !=
|
||||
_glfw.x11.helperWindowHandle)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"X11: Failed to become owner of primary selection");
|
||||
static void
|
||||
get_available_mime_types(Atom which_clipboard, GLFWclipboardwritedatafun write_data, void *object) {
|
||||
chunked_writer cw = {0};
|
||||
getSelectionString(which_clipboard, &_glfw.x11.TARGETS, 1, write_chunk, &cw, false);
|
||||
size_t count = 0;
|
||||
bool ok = true;
|
||||
if (cw.buf) {
|
||||
Atom *atoms = (Atom*)cw.buf;
|
||||
count = cw.sz / sizeof(Atom);
|
||||
char **names = calloc(count, sizeof(char*));
|
||||
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)
|
||||
{
|
||||
return getSelectionString(_glfw.x11.PRIMARY);
|
||||
void
|
||||
_glfwPlatformGetClipboard(GLFWClipboardType clipboard_type, const char* mime_type, GLFWclipboardwritedatafun write_data, void *object) {
|
||||
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) {
|
||||
// we need to do this because GTK/GNOME is developed by morons
|
||||
// they convert text/plain to DOS line endings
|
||||
// https://gitlab.gnome.org/GNOME/gtk/-/issues/2307
|
||||
atoms[count++] = atom_for_mime("text/plain;charset=utf-8").atom;
|
||||
atoms[count++] = _glfw.x11.UTF8_STRING;
|
||||
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)
|
||||
@@ -3134,3 +3197,18 @@ GLFWAPI int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc)
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
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);
|
||||
}
|
||||
|
||||
2
glfw/xkb_glfw.c
vendored
2
glfw/xkb_glfw.c
vendored
@@ -33,7 +33,7 @@
|
||||
#include <X11/XKBlib.h>
|
||||
#endif
|
||||
|
||||
#define debug(...) if (_glfw.hints.init.debugKeyboard) printf(__VA_ARGS__);
|
||||
#define debug(...) if (_glfw.hints.init.debugKeyboard) fprintf(stderr, __VA_ARGS__);
|
||||
|
||||
#ifdef XKB_HAS_NO_UTF32
|
||||
#include "xkb-compat-shim.h"
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from contextlib import suppress
|
||||
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
|
||||
@@ -16,11 +17,12 @@ from kitty.typing import BossType, KeyEventType, TypedDict
|
||||
from kitty.utils import ScreenSize
|
||||
|
||||
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
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import readline
|
||||
debug
|
||||
else:
|
||||
readline = None
|
||||
|
||||
@@ -74,7 +76,7 @@ class HistoryCompleter:
|
||||
def option_text() -> str:
|
||||
return '''\
|
||||
--type -t
|
||||
choices=line,yesno,choices
|
||||
choices=line,yesno,choices,password
|
||||
default=line
|
||||
Type of input. Defaults to asking for a line of text.
|
||||
|
||||
@@ -92,17 +94,31 @@ be used for completions and via the browse history readline bindings.
|
||||
--choice -c
|
||||
type=list
|
||||
dest=choices
|
||||
A choice for the choices type. Every choice has the syntax: letter:text Where
|
||||
letter is the accelerator key and text is the corresponding text. There can be
|
||||
an optional color specification after the letter to indicate what color it should
|
||||
be.
|
||||
For example: y:Yes and n;red:No
|
||||
A choice for the choices type. Can be specified multiple times. Every choice has
|
||||
the syntax: ``letter[;color]:text``. Where :italic:`letter` is the accelerator key
|
||||
and :italic:`text` is the corresponding text. There can be an optional color
|
||||
specification after the letter to indicate what color it should be.
|
||||
For example: :code:`y:Yes` and :code:`n;red:No`
|
||||
|
||||
|
||||
--default -d
|
||||
A default choice or text. If unspecified, it is "y" for :code:`yesno`, the first choice
|
||||
for :code:`choices` and empty for others. The default choice is selected when the user
|
||||
presses the Enter key.
|
||||
A default choice or text. If unspecified, it is :code:`y` for the type
|
||||
:code:`yesno`, the first choice for :code:`choices` and empty for others types.
|
||||
The default choice is selected when the user presses the :kbd:`Enter` key.
|
||||
|
||||
|
||||
--prompt -p
|
||||
default="> "
|
||||
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.
|
||||
'''
|
||||
|
||||
|
||||
@@ -140,10 +156,63 @@ def extra_for(width: int, screen_width: int) -> int:
|
||||
return max(0, screen_width - width) // 2 + 1
|
||||
|
||||
|
||||
class Choose(Handler):
|
||||
class Password(Handler):
|
||||
|
||||
def __init__(self, cli_opts: AskCLIOptions, prompt: str, is_password: bool = True, initial_text: str = '') -> None:
|
||||
self.cli_opts = cli_opts
|
||||
self.prompt = prompt
|
||||
self.initial_text = initial_text
|
||||
from kittens.tui.line_edit import LineEdit
|
||||
self.line_edit = LineEdit(is_password=is_password)
|
||||
|
||||
def initialize(self) -> None:
|
||||
self.cmd.set_cursor_shape('beam')
|
||||
if self.initial_text:
|
||||
self.line_edit.on_text(self.initial_text, True)
|
||||
self.draw_screen()
|
||||
|
||||
@Handler.atomic_update
|
||||
def draw_screen(self) -> None:
|
||||
self.cmd.clear_screen()
|
||||
if self.cli_opts.message:
|
||||
for line in self.cli_opts.message.splitlines():
|
||||
self.print(line)
|
||||
self.print()
|
||||
self.line_edit.write(self.write, self.prompt)
|
||||
|
||||
def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
|
||||
self.line_edit.on_text(text, in_bracketed_paste)
|
||||
self.draw_screen()
|
||||
|
||||
def on_key(self, key_event: KeyEventType) -> None:
|
||||
if self.line_edit.on_key(key_event):
|
||||
self.draw_screen()
|
||||
return
|
||||
if key_event.matches('enter'):
|
||||
self.quit_loop(0)
|
||||
if key_event.matches('esc'):
|
||||
self.quit_loop(1)
|
||||
|
||||
def on_resize(self, screen_size: ScreenSize) -> None:
|
||||
self.screen_size = screen_size
|
||||
self.draw_screen()
|
||||
|
||||
def on_interrupt(self) -> None:
|
||||
self.quit_loop(1)
|
||||
on_eot = on_interrupt
|
||||
|
||||
@property
|
||||
def response(self) -> str:
|
||||
if self._tui_loop.return_code == 0:
|
||||
return self.line_edit.current_input
|
||||
return ''
|
||||
|
||||
|
||||
class Choose(Handler): # {{{
|
||||
mouse_tracking = MouseTracking.buttons_only
|
||||
|
||||
def __init__(self, cli_opts: AskCLIOptions) -> None:
|
||||
self.prefix_style_pat = re.compile(r'(?:\x1b\[[^m]*?m)+')
|
||||
self.cli_opts = cli_opts
|
||||
self.choices: Dict[str, Choice] = {}
|
||||
self.clickable_ranges: Dict[str, List[Range]] = {}
|
||||
@@ -165,6 +234,18 @@ class Choose(Handler):
|
||||
self.response_on_accept = cli_opts.default or ''
|
||||
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.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:
|
||||
self.cmd.set_cursor_visible(False)
|
||||
@@ -173,24 +254,35 @@ class Choose(Handler):
|
||||
def finalize(self) -> None:
|
||||
self.cmd.set_cursor_visible(True)
|
||||
|
||||
def draw_long_text(self, text: str) -> int:
|
||||
y = 0
|
||||
def draw_long_text(self, text: str) -> Iterator[str]:
|
||||
if not text:
|
||||
yield ''
|
||||
return
|
||||
width = self.screen_size.cols - 2
|
||||
m = self.prefix_style_pat.match(text)
|
||||
prefix = m.group() if m else ''
|
||||
while text:
|
||||
t, text = truncate_at_space(text, width)
|
||||
t = t.strip()
|
||||
self.print(' ' * extra_for(wcswidth(t), width), styled(t, bold=True), sep='')
|
||||
y += 1
|
||||
return y
|
||||
yield ' ' * extra_for(wcswidth(t), width) + styled(prefix + t, bold=True)
|
||||
|
||||
@Handler.atomic_update
|
||||
def draw_screen(self) -> None:
|
||||
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)
|
||||
if self.cli_opts.message:
|
||||
for line in self.cli_opts.message.splitlines():
|
||||
y += self.draw_long_text(line)
|
||||
for line in msg_lines:
|
||||
if self.replacement_text in 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:
|
||||
self.print()
|
||||
y += 1
|
||||
@@ -205,7 +297,8 @@ class Choose(Handler):
|
||||
current_line_length = 0
|
||||
current_line: 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:
|
||||
self.clickable_ranges[choice.letter] = []
|
||||
@@ -218,7 +311,7 @@ class Choose(Handler):
|
||||
current_line = []
|
||||
current_line_length = 0
|
||||
current_line.append((choice.letter, text))
|
||||
current_line_length += sz
|
||||
current_line_length += sz + sep_sz
|
||||
if current_line:
|
||||
lines.append(current_line)
|
||||
|
||||
@@ -257,10 +350,12 @@ class Choose(Handler):
|
||||
self.print(' ' * offset, line, sep='', end='' if is_last else '\r\n')
|
||||
y += 1
|
||||
|
||||
self.cmd.set_line_wrapping(False)
|
||||
for boxed_line in lines:
|
||||
print_line(top, *boxed_line)
|
||||
print_line(middle, *boxed_line)
|
||||
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:
|
||||
if y + 3 <= self.screen_size.rows:
|
||||
@@ -316,6 +411,14 @@ class Choose(Handler):
|
||||
self.quit_loop(0)
|
||||
elif self.cli_opts.type == 'yesno':
|
||||
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:
|
||||
if key_event.matches('esc'):
|
||||
@@ -331,6 +434,8 @@ class Choose(Handler):
|
||||
self.response = letter
|
||||
self.quit_loop(0)
|
||||
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:
|
||||
self.screen_size = screen_size
|
||||
@@ -339,6 +444,7 @@ class Choose(Handler):
|
||||
def on_interrupt(self) -> None:
|
||||
self.quit_loop(1)
|
||||
on_eot = on_interrupt
|
||||
# }}}
|
||||
|
||||
|
||||
def main(args: List[str]) -> Response:
|
||||
@@ -352,7 +458,7 @@ def main(args: List[str]) -> Response:
|
||||
except SystemExit as e:
|
||||
if e.code != 0:
|
||||
print(e.args[0])
|
||||
input('Press enter to quit...')
|
||||
input('Press Enter to quit')
|
||||
raise SystemExit(e.code)
|
||||
|
||||
if cli_opts.type in ('yesno', 'choices'):
|
||||
@@ -361,6 +467,15 @@ def main(args: List[str]) -> Response:
|
||||
loop.loop(handler)
|
||||
return {'items': items, 'response': handler.response}
|
||||
|
||||
prompt = cli_opts.prompt
|
||||
if prompt[0] == prompt[-1] and prompt[0] in '\'"':
|
||||
prompt = prompt[1:-1]
|
||||
if cli_opts.type == 'password':
|
||||
loop = Loop()
|
||||
phandler = Password(cli_opts, prompt)
|
||||
loop.loop(phandler)
|
||||
return {'items': items, 'response': phandler.response}
|
||||
|
||||
import readline as rl
|
||||
readline = rl
|
||||
from kitty.shell import init_readline
|
||||
@@ -371,7 +486,6 @@ def main(args: List[str]) -> Response:
|
||||
if cli_opts.message:
|
||||
print(styled(cli_opts.message, bold=True))
|
||||
|
||||
prompt = '> '
|
||||
with suppress(KeyboardInterrupt, EOFError):
|
||||
if cli_opts.default:
|
||||
def prefill_text() -> None:
|
||||
|
||||
@@ -99,7 +99,7 @@ class Broadcast(Handler):
|
||||
|
||||
|
||||
OPTIONS = (MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t')).format
|
||||
help_text = 'Broadcast typed text to all 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 ...]'
|
||||
|
||||
|
||||
|
||||
@@ -231,11 +231,11 @@ static PyMethodDef module_methods[] = {
|
||||
};
|
||||
|
||||
static struct PyModuleDef module = {
|
||||
.m_base = PyModuleDef_HEAD_INIT,
|
||||
.m_name = "subseq_matcher", /* name of module */
|
||||
.m_doc = NULL,
|
||||
.m_size = -1,
|
||||
.m_methods = module_methods
|
||||
.m_base = PyModuleDef_HEAD_INIT,
|
||||
.m_name = "subseq_matcher", /* name of module */
|
||||
.m_doc = NULL,
|
||||
.m_size = -1,
|
||||
.m_methods = module_methods
|
||||
};
|
||||
|
||||
EXPORTED PyMODINIT_FUNC
|
||||
|
||||
@@ -1,63 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import codecs
|
||||
import io
|
||||
import os
|
||||
import select
|
||||
import sys
|
||||
from typing import List, NoReturn, Optional
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import ClipboardCLIOptions
|
||||
from kitty.fast_data_types import parse_input_from_terminal
|
||||
|
||||
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)
|
||||
|
||||
from ..tui.operations import (
|
||||
raw_mode, request_from_clipboard, write_to_clipboard
|
||||
)
|
||||
|
||||
OPTIONS = r'''
|
||||
--get-clipboard
|
||||
default=False
|
||||
type=bool-set
|
||||
Output the current contents of the clipboard to STDOUT. Note that by default
|
||||
kitty will prompt you asking to allow access to the clipboard. Can be controlled
|
||||
kitty will prompt for permission to access the clipboard. Can be controlled
|
||||
by :opt:`clipboard_control`.
|
||||
|
||||
|
||||
@@ -84,6 +48,62 @@ popup, see :opt:`clipboard_control` for details.
|
||||
'''
|
||||
|
||||
usage = ''
|
||||
got_capability_response = False
|
||||
got_clipboard_response = False
|
||||
clipboard_contents = ''
|
||||
clipboard_from_primary = False
|
||||
|
||||
|
||||
def ignore(x: str) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def on_text(x: str) -> None:
|
||||
if '\x03' in x:
|
||||
raise KeyboardInterrupt()
|
||||
if '\x04' in x:
|
||||
raise EOFError()
|
||||
|
||||
|
||||
def on_dcs(dcs: str) -> None:
|
||||
global got_capability_response
|
||||
if dcs.startswith('1+r'):
|
||||
got_capability_response = True
|
||||
|
||||
|
||||
def on_osc(osc: str) -> None:
|
||||
global clipboard_contents, clipboard_from_primary, got_clipboard_response
|
||||
idx = osc.find(';')
|
||||
if idx <= 0:
|
||||
return
|
||||
q = osc[:idx]
|
||||
if q == '52':
|
||||
got_clipboard_response = True
|
||||
widx = osc.find(';', idx + 1)
|
||||
if widx < idx:
|
||||
clipboard_from_primary = osc.find('p', idx + 1) > -1
|
||||
clipboard_contents = ''
|
||||
else:
|
||||
from base64 import standard_b64decode
|
||||
clipboard_from_primary = osc.find('p', idx+1, widx) > -1
|
||||
data = memoryview(osc.encode('ascii'))
|
||||
clipboard_contents = standard_b64decode(data[widx+1:]).decode('utf-8')
|
||||
|
||||
|
||||
def wait_loop(tty_fd: int) -> None:
|
||||
os.set_blocking(tty_fd, False)
|
||||
decoder = codecs.getincrementaldecoder('utf-8')('ignore')
|
||||
with raw_mode(tty_fd):
|
||||
buf = ''
|
||||
while not got_capability_response and not got_clipboard_response:
|
||||
rd = select.select([tty_fd], [], [])[0]
|
||||
if rd:
|
||||
raw = os.read(tty_fd, io.DEFAULT_BUFFER_SIZE)
|
||||
if not raw:
|
||||
raise EOFError()
|
||||
data = decoder.decode(raw)
|
||||
buf = (buf + data) if buf else data
|
||||
buf = parse_input_from_terminal(on_text, on_dcs, ignore, on_osc, ignore, ignore, buf, False)
|
||||
|
||||
|
||||
def main(args: List[str]) -> NoReturn:
|
||||
@@ -93,14 +113,34 @@ def main(args: List[str]) -> NoReturn:
|
||||
data: Optional[bytes] = None
|
||||
if not sys.stdin.isatty():
|
||||
data = sys.stdin.buffer.read()
|
||||
sys.stdin = open(os.ctermid())
|
||||
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)
|
||||
wait_for_capability_response = False
|
||||
data_to_write = []
|
||||
if data:
|
||||
data_to_write.append(write_to_clipboard(data, cli_opts.use_primary).encode('ascii'))
|
||||
if not cli_opts.get_clipboard and cli_opts.wait_for_completion:
|
||||
data_to_write.append(b'\x1bP+q544e\x1b\\')
|
||||
wait_for_capability_response = True
|
||||
if cli_opts.get_clipboard:
|
||||
data_to_write.append(request_from_clipboard(cli_opts.use_primary).encode('ascii'))
|
||||
wait_for_capability_response = True
|
||||
tty_fd = os.open(os.ctermid(), os.O_RDWR | os.O_CLOEXEC)
|
||||
retcode = 0
|
||||
with open(tty_fd, 'wb', closefd=True) as ttyf:
|
||||
for x in data_to_write:
|
||||
ttyf.write(x)
|
||||
ttyf.flush()
|
||||
if wait_for_capability_response:
|
||||
try:
|
||||
wait_loop(tty_fd)
|
||||
except KeyboardInterrupt:
|
||||
sys.excepthook = lambda *a: None
|
||||
raise
|
||||
except EOFError:
|
||||
retcode = 1
|
||||
if clipboard_contents:
|
||||
print(end=clipboard_contents)
|
||||
|
||||
raise SystemExit(retcode)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -2,12 +2,16 @@
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import re
|
||||
from contextlib import suppress
|
||||
from fnmatch import fnmatch
|
||||
from functools import lru_cache
|
||||
from hashlib import md5
|
||||
from typing import (
|
||||
TYPE_CHECKING, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Union
|
||||
)
|
||||
|
||||
from kitty.guess_mime_type import guess_type
|
||||
from typing import TYPE_CHECKING, Dict, List, Set, Optional, Iterator, Tuple, Union
|
||||
from kitty.utils import control_codes_pat
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .highlight import DiffHighlight # noqa
|
||||
@@ -37,6 +41,8 @@ class Segment:
|
||||
|
||||
class Collection:
|
||||
|
||||
ignore_names: Tuple[str, ...] = ()
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.changes: Dict[str, str] = {}
|
||||
self.renames: Dict[str, str] = {}
|
||||
@@ -105,22 +111,34 @@ def resolve_remote_name(path: str, default: str) -> str:
|
||||
return default
|
||||
|
||||
|
||||
def allowed_items(items: Sequence[str], ignore_patterns: Sequence[str]) -> Iterator[str]:
|
||||
for name in items:
|
||||
for pat in ignore_patterns:
|
||||
if fnmatch(name, pat):
|
||||
break
|
||||
else:
|
||||
yield name
|
||||
|
||||
|
||||
def walk(base: str, names: Set[str], pmap: Dict[str, str], ignore_names: Tuple[str, ...]) -> None:
|
||||
for dirpath, dirnames, filenames in os.walk(base):
|
||||
dirnames[:] = allowed_items(dirnames, ignore_names)
|
||||
for filename in allowed_items(filenames, ignore_names):
|
||||
path = os.path.abspath(os.path.join(dirpath, filename))
|
||||
path_name_map[path] = name = os.path.relpath(path, base)
|
||||
names.add(name)
|
||||
pmap[name] = path
|
||||
|
||||
|
||||
def collect_files(collection: Collection, left: str, right: str) -> None:
|
||||
left_names: Set[str] = set()
|
||||
right_names: Set[str] = set()
|
||||
left_path_map: Dict[str, str] = {}
|
||||
right_path_map: Dict[str, str] = {}
|
||||
|
||||
def walk(base: str, names: Set[str], pmap: Dict[str, str]) -> None:
|
||||
for dirpath, dirnames, filenames in os.walk(base):
|
||||
for filename in filenames:
|
||||
path = os.path.abspath(os.path.join(dirpath, filename))
|
||||
path_name_map[path] = name = os.path.relpath(path, base)
|
||||
names.add(name)
|
||||
pmap[name] = path
|
||||
walk(left, left_names, left_path_map, collection.ignore_names)
|
||||
walk(right, right_names, right_path_map, collection.ignore_names)
|
||||
|
||||
walk(left, left_names, left_path_map)
|
||||
walk(right, right_names, right_path_map)
|
||||
common_names = left_names & right_names
|
||||
changed_names = {n for n in common_names if data_for_path(left_path_map[n]) != data_for_path(right_path_map[n])}
|
||||
for n in changed_names:
|
||||
@@ -143,12 +161,9 @@ def collect_files(collection: Collection, left: str, right: str) -> None:
|
||||
collection.add_add(right_path_map[name])
|
||||
|
||||
|
||||
sanitize_pat = re.compile('[\x00-\x09\x0b-\x1f\x7f\x80-\x9f]')
|
||||
|
||||
|
||||
def sanitize(text: str) -> str:
|
||||
ntext = text.replace('\r\n', '⏎\n')
|
||||
return sanitize_pat.sub('░', ntext)
|
||||
return control_codes_pat().sub('░', ntext)
|
||||
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
|
||||
@@ -161,13 +161,15 @@ def highlight_collection(collection: Collection, aliases: Optional[Dict[str, str
|
||||
if p:
|
||||
is_binary = isinstance(data_for_path(p), bytes)
|
||||
if not is_binary:
|
||||
jobs[executor.submit(highlight_for_diff, p, aliases)] = p
|
||||
jobs[executor.submit(highlight_for_diff, p, aliases or {})] = p
|
||||
for future in concurrent.futures.as_completed(jobs):
|
||||
path = jobs[future]
|
||||
try:
|
||||
highlights = future.result()
|
||||
except Exception as e:
|
||||
return f'Running syntax highlighting for {path} generated an exception: {e}'
|
||||
import traceback
|
||||
tb = traceback.format_exc()
|
||||
return f'Running syntax highlighting for {path} generated an exception: {e} with traceback:\n{tb}'
|
||||
ans[path] = highlights
|
||||
return ans
|
||||
|
||||
|
||||
@@ -293,10 +293,11 @@ class DiffHandler(Handler):
|
||||
|
||||
def scroll_lines(self, amt: int = 1) -> None:
|
||||
new_pos = max(0, min(self.scroll_pos + amt, self.max_scroll_pos))
|
||||
amt = new_pos - self.scroll_pos
|
||||
if new_pos == self.scroll_pos:
|
||||
self.cmd.bell()
|
||||
return
|
||||
if abs(new_pos - self.scroll_pos) >= self.num_lines - 1:
|
||||
if abs(amt) >= self.num_lines - 1:
|
||||
self.scroll_pos = new_pos
|
||||
self.draw_screen()
|
||||
return
|
||||
@@ -570,8 +571,8 @@ OPTIONS = partial('''\
|
||||
--context
|
||||
type=int
|
||||
default=-1
|
||||
Number of lines of context to show between changes. Negative values
|
||||
use the number set in diff.conf
|
||||
Number of lines of context to show between changes. Negative values use the
|
||||
number set in :file:`diff.conf`.
|
||||
|
||||
|
||||
--config
|
||||
@@ -598,7 +599,7 @@ class ShowWarning:
|
||||
|
||||
|
||||
showwarning = ShowWarning()
|
||||
help_text = 'Show a side-by-side diff of the specified files/directories. You can also use ssh:hostname:remote-file-path to diff remote files.'
|
||||
help_text = 'Show a side-by-side diff of the specified files/directories. You can also use :italic:`ssh:hostname:remote-file-path` to diff remote files.'
|
||||
usage = 'file_or_directory_left file_or_directory_right'
|
||||
|
||||
|
||||
@@ -654,6 +655,7 @@ def main(args: List[str]) -> None:
|
||||
opts = init_config(cli_opts)
|
||||
set_diff_command(opts.diff_cmd)
|
||||
lines_for_path.replace_tab_by = opts.replace_tab_by
|
||||
Collection.ignore_names = tuple(opts.ignore_name)
|
||||
left, right = map(get_remote_file, (left, right))
|
||||
if os.path.isdir(left) != os.path.isdir(right):
|
||||
raise SystemExit('The items to be diffed should both be either directories or files. Comparing a directory to a file is not valid.')
|
||||
|
||||
@@ -24,8 +24,8 @@ agr('diff', 'Diffing')
|
||||
opt('syntax_aliases', 'pyj:py pyi:py recipe:py',
|
||||
option_type='syntax_aliases',
|
||||
long_text='''
|
||||
File extension aliases for syntax highlight For example, to syntax highlight
|
||||
:file:`file.xyz` as :file:`file.abc` use a setting of :code:`xyz:abc`
|
||||
File extension aliases for syntax highlight. For example, to syntax highlight
|
||||
:file:`file.xyz` as :file:`file.abc` use a setting of :code:`xyz:abc`.
|
||||
'''
|
||||
)
|
||||
|
||||
@@ -37,8 +37,9 @@ opt('num_context_lines', '3',
|
||||
opt('diff_cmd', 'auto',
|
||||
long_text='''
|
||||
The diff command to use. Must contain the placeholder :code:`_CONTEXT_` which
|
||||
will be replaced by the number of lines of context. The default is to search the
|
||||
system for either git or diff and use that, if found.
|
||||
will be replaced by the number of lines of context. The default special value
|
||||
:code:`auto` is to search the system for either :program:`git` or
|
||||
:program:`diff` and use that, if found.
|
||||
'''
|
||||
)
|
||||
|
||||
@@ -46,6 +47,21 @@ opt('replace_tab_by', '\\x20\\x20\\x20\\x20',
|
||||
option_type='python_string',
|
||||
long_text='The string to replace tabs with. Default is to use four spaces.'
|
||||
)
|
||||
|
||||
opt('+ignore_name', '',
|
||||
option_type='store_multiple',
|
||||
add_to_default=False,
|
||||
long_text='''
|
||||
A glob pattern that is matched against only the filename of files and directories. Matching
|
||||
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::
|
||||
|
||||
ignore_name .git
|
||||
ignore_name *~
|
||||
ignore_name *.pyc
|
||||
''',
|
||||
)
|
||||
|
||||
egr() # }}}
|
||||
|
||||
# colors {{{
|
||||
|
||||
9
kittens/diff/options/parse.py
generated
9
kittens/diff/options/parse.py
generated
@@ -1,7 +1,7 @@
|
||||
# generated by gen-config.py DO NOT edit
|
||||
|
||||
import typing
|
||||
from kittens.diff.options.utils import parse_map, 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
|
||||
|
||||
|
||||
@@ -37,6 +37,10 @@ class Parser:
|
||||
def hunk_margin_bg(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['hunk_margin_bg'] = to_color(val)
|
||||
|
||||
def ignore_name(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
for k, v in store_multiple(val, ans["ignore_name"]):
|
||||
ans["ignore_name"][k] = v
|
||||
|
||||
def margin_bg(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['margin_bg'] = to_color(val)
|
||||
|
||||
@@ -89,11 +93,12 @@ class Parser:
|
||||
|
||||
def create_result_dict() -> typing.Dict[str, typing.Any]:
|
||||
return {
|
||||
'ignore_name': {},
|
||||
'map': [],
|
||||
}
|
||||
|
||||
|
||||
actions = frozenset(('map',))
|
||||
actions: typing.FrozenSet[str] = frozenset(('map',))
|
||||
|
||||
|
||||
def merge_result_dicts(defaults: typing.Dict[str, typing.Any], vals: typing.Dict[str, typing.Any]) -> typing.Dict[str, typing.Any]:
|
||||
|
||||
3
kittens/diff/options/types.py
generated
3
kittens/diff/options/types.py
generated
@@ -20,6 +20,7 @@ option_names = ( # {{{
|
||||
'highlight_removed_bg',
|
||||
'hunk_bg',
|
||||
'hunk_margin_bg',
|
||||
'ignore_name',
|
||||
'map',
|
||||
'margin_bg',
|
||||
'margin_fg',
|
||||
@@ -64,6 +65,7 @@ class Options:
|
||||
syntax_aliases: typing.Dict[str, str] = {'pyj': 'py', 'pyi': 'py', 'recipe': 'py'}
|
||||
title_bg: Color = Color(255, 255, 255)
|
||||
title_fg: Color = Color(0, 0, 0)
|
||||
ignore_name: typing.Dict[str, str] = {}
|
||||
map: typing.List[typing.Tuple[kitty.types.ParsedShortcut, kitty.conf.utils.KeyAction]] = []
|
||||
key_definitions: KittensKeyMap = {}
|
||||
config_paths: typing.Tuple[str, ...] = ()
|
||||
@@ -116,6 +118,7 @@ class Options:
|
||||
|
||||
|
||||
defaults = Options()
|
||||
defaults.ignore_name = {}
|
||||
defaults.map = [
|
||||
# quit
|
||||
(ParsedShortcut(mods=0, key_name='q'), KeyAction('quit')), # noqa
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# License: GPLv3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
from typing import Any, Dict, Iterable, Tuple, Union
|
||||
from typing import Any, Container, Dict, Iterable, Tuple, Union
|
||||
|
||||
from kitty.conf.utils import (
|
||||
KeyFuncWrapper, KittensKeyDefinition, parse_kittens_key
|
||||
@@ -58,6 +58,12 @@ def syntax_aliases(raw: str) -> Dict[str, str]:
|
||||
return ans
|
||||
|
||||
|
||||
def store_multiple(val: str, current_val: Container[str]) -> Iterable[Tuple[str, str]]:
|
||||
val = val.strip()
|
||||
if val not in current_val:
|
||||
yield val, val
|
||||
|
||||
|
||||
def parse_map(val: str) -> Iterable[KittensKeyDefinition]:
|
||||
x = parse_kittens_key(val, func_with_args.args_funcs)
|
||||
if x is not None:
|
||||
|
||||
@@ -153,7 +153,7 @@ class Hunk:
|
||||
if c.left_start + c.left_count != self.left_start + self.left_count:
|
||||
raise ValueError(f'Left side line mismatch {c.left_start + c.left_count} != {self.left_start + self.left_count}')
|
||||
if c.right_start + c.right_count != self.right_start + self.right_count:
|
||||
raise ValueError(f'Left side line mismatch {c.right_start + c.right_count} != {self.right_start + self.right_count}')
|
||||
raise ValueError(f'Right side line mismatch {c.right_start + c.right_count} != {self.right_start + self.right_count}')
|
||||
for c in self.chunks:
|
||||
c.finalize()
|
||||
|
||||
@@ -200,7 +200,7 @@ def parse_patch(raw: str) -> Patch:
|
||||
else:
|
||||
if current_hunk is None:
|
||||
continue
|
||||
q = line[0]
|
||||
q = line[0] if line else ''
|
||||
if q == '+':
|
||||
current_hunk.add_line()
|
||||
elif q == '-':
|
||||
|
||||
@@ -36,6 +36,8 @@ def images_supported() -> bool:
|
||||
|
||||
class Ref:
|
||||
|
||||
__slots__: Tuple[str, ...] = ()
|
||||
|
||||
def __setattr__(self, name: str, value: object) -> None:
|
||||
raise AttributeError("can't set attribute")
|
||||
|
||||
|
||||
@@ -187,11 +187,11 @@ static PyMethodDef module_methods[] = {
|
||||
};
|
||||
|
||||
static struct PyModuleDef module = {
|
||||
.m_base = PyModuleDef_HEAD_INIT,
|
||||
.m_name = "diff_speedup", /* name of module */
|
||||
.m_doc = NULL,
|
||||
.m_size = -1,
|
||||
.m_methods = module_methods
|
||||
.m_base = PyModuleDef_HEAD_INIT,
|
||||
.m_name = "diff_speedup", /* name of module */
|
||||
.m_doc = NULL,
|
||||
.m_size = -1,
|
||||
.m_methods = module_methods
|
||||
};
|
||||
|
||||
EXPORTED PyMODINIT_FUNC
|
||||
|
||||
@@ -9,23 +9,26 @@ from functools import lru_cache
|
||||
from gettext import gettext as _
|
||||
from itertools import repeat
|
||||
from typing import (
|
||||
Any, Callable, Dict, Generator, Iterable, List, Optional, Pattern,
|
||||
Sequence, Set, Tuple, Type, cast
|
||||
Any, Callable, Dict, Generator, Iterable, Iterator, List, Optional,
|
||||
Pattern, Sequence, Set, Tuple, Type, cast
|
||||
)
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.cli_stub import HintsCLIOptions
|
||||
from kitty.clipboard import set_clipboard_string, set_primary_selection
|
||||
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.typing import BossType, KittyCommonOpts
|
||||
from kitty.utils import (
|
||||
ScreenSize, resolve_custom_file, screen_size_function, set_primary_selection
|
||||
ScreenSize, kitty_ansi_sanitizer_pat, resolve_custom_file,
|
||||
screen_size_function
|
||||
)
|
||||
|
||||
from ..tui.handler import Handler, result_handler
|
||||
from ..tui.loop import Loop
|
||||
from ..tui.operations import faint, styled
|
||||
from ..tui.utils import report_error, report_unhandled_error
|
||||
|
||||
|
||||
@lru_cache()
|
||||
@@ -42,6 +45,7 @@ DEFAULT_HINT_ALPHABET = string.digits + string.ascii_lowercase
|
||||
DEFAULT_REGEX = r'(?m)^\s*(.+)\s*$'
|
||||
FILE_EXTENSION = r'\.(?:[a-zA-Z0-9]{2,7}|[ahcmo])(?!\.)'
|
||||
PATH_REGEX = fr'(?:\S*?/[\r\S]+)|(?:\S[\r\S]*{FILE_EXTENSION})\b'
|
||||
DEFAULT_LINENUM_REGEX = fr'(?P<path>{PATH_REGEX}):(?P<line>\d+)'
|
||||
|
||||
|
||||
class Mark:
|
||||
@@ -62,6 +66,10 @@ class Mark:
|
||||
self.is_hyperlink = is_hyperlink
|
||||
self.group_id = group_id
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (f'Mark(index={self.index!r}, start={self.start!r}, end={self.end!r},'
|
||||
f' text={self.text!r}, groupdict={self.groupdict!r}, is_hyperlink={self.is_hyperlink!r}, group_id={self.group_id!r})')
|
||||
|
||||
|
||||
@lru_cache(maxsize=2048)
|
||||
def encode_hint(num: int, alphabet: str) -> str:
|
||||
@@ -116,6 +124,9 @@ def render(text: str, current_input: str, all_marks: Sequence[Mark], ignore_mark
|
||||
|
||||
class Hints(Handler):
|
||||
|
||||
use_alternate_screen = False # disabled to avoid screen being blanked at exit causing flicker
|
||||
overlay_ready_report_needed = True
|
||||
|
||||
def __init__(self, text: str, all_marks: Sequence[Mark], index_map: Dict[int, Mark], args: HintsCLIOptions):
|
||||
self.text, self.index_map = text, index_map
|
||||
self.alphabet = args.alphabet or DEFAULT_HINT_ALPHABET
|
||||
@@ -220,17 +231,17 @@ class Hints(Handler):
|
||||
self.write(self.current_text)
|
||||
|
||||
|
||||
def regex_finditer(pat: 'Pattern[str]', minimum_match_length: int, text: str) -> Generator[Tuple[int, int, Dict[str, str]], None, None]:
|
||||
def regex_finditer(pat: 'Pattern[str]', minimum_match_length: int, text: str) -> Iterator[Tuple[int, int, 're.Match[str]']]:
|
||||
has_named_groups = bool(pat.groupindex)
|
||||
for m in pat.finditer(text):
|
||||
s, e = m.span(0 if has_named_groups else pat.groups)
|
||||
while e > s + 1 and text[e-1] == '\0':
|
||||
e -= 1
|
||||
if e - s >= minimum_match_length:
|
||||
yield s, e, m.groupdict()
|
||||
yield s, e, m
|
||||
|
||||
|
||||
closing_bracket_map = {'(': ')', '[': ']', '{': '}', '<': '>', '*': '*', '"': '"', "'": "'"}
|
||||
closing_bracket_map = {'(': ')', '[': ']', '{': '}', '<': '>', '*': '*', '"': '"', "'": "'", "“": "”", "‘": "’"}
|
||||
opening_brackets = ''.join(closing_bracket_map)
|
||||
PostprocessorFunc = Callable[[str, int, int], Tuple[int, int]]
|
||||
postprocessor_map: Dict[str, PostprocessorFunc] = {}
|
||||
@@ -288,11 +299,12 @@ def quotes(text: str, s: int, e: int) -> Tuple[int, int]:
|
||||
# Remove matching quotes
|
||||
if s < e <= len(text):
|
||||
before = text[s]
|
||||
if before in '\'"':
|
||||
if text[e-1] == before:
|
||||
if before in '\'"“‘':
|
||||
q = closing_bracket_map[before]
|
||||
if text[e-1] == q:
|
||||
s += 1
|
||||
e -= 1
|
||||
elif text[e:e+1] == before:
|
||||
elif text[e:e+1] == q:
|
||||
s += 1
|
||||
return s, e
|
||||
|
||||
@@ -312,16 +324,22 @@ def ip(text: str, s: int, e: int) -> Tuple[int, int]:
|
||||
return s, e
|
||||
|
||||
|
||||
def mark(pattern: str, post_processors: Iterable[PostprocessorFunc], text: str, args: HintsCLIOptions) -> Generator[Mark, None, None]:
|
||||
def mark(pattern: str, post_processors: Iterable[PostprocessorFunc], text: str, args: HintsCLIOptions) -> Iterator[Mark]:
|
||||
pat = re.compile(pattern)
|
||||
for idx, (s, e, groupdict) in enumerate(regex_finditer(pat, args.minimum_match_length, text)):
|
||||
sanitize_pat = re.compile('[\r\n\0]')
|
||||
for idx, (s, e, match_object) in enumerate(regex_finditer(pat, args.minimum_match_length, text)):
|
||||
try:
|
||||
for func in post_processors:
|
||||
s, e = func(text, s, e)
|
||||
except InvalidMatch:
|
||||
continue
|
||||
|
||||
mark_text = re.sub('[\r\n\0]', '', text[s:e])
|
||||
groupdict = match_object.groupdict()
|
||||
for group_name in groupdict:
|
||||
group_idx = pat.groupindex[group_name]
|
||||
gs, ge = match_object.span(group_idx)
|
||||
gs, ge = max(gs, s), min(ge, e)
|
||||
groupdict[group_name] = sanitize_pat.sub('', text[gs:ge])
|
||||
mark_text = sanitize_pat.sub('', text[s:e])
|
||||
yield Mark(idx, s, e, mark_text, groupdict)
|
||||
|
||||
|
||||
@@ -394,7 +412,10 @@ def convert_text(text: str, cols: int) -> str:
|
||||
appended = False
|
||||
for line in full_line.split('\r'):
|
||||
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')
|
||||
appended = True
|
||||
if appended:
|
||||
@@ -414,7 +435,7 @@ def parse_input(text: str) -> str:
|
||||
def linenum_marks(text: str, args: HintsCLIOptions, Mark: Type[Mark], extra_cli_args: Sequence[str], *a: Any) -> Generator[Mark, None, None]:
|
||||
regex = args.regex
|
||||
if regex == DEFAULT_REGEX:
|
||||
regex = fr'(?P<path>{PATH_REGEX}):(?P<line>\d+)'
|
||||
regex = DEFAULT_LINENUM_REGEX
|
||||
yield from mark(regex, [brackets, quotes], text, args)
|
||||
|
||||
|
||||
@@ -430,11 +451,7 @@ def load_custom_processor(customize_processing: str) -> Any:
|
||||
return runpy.run_path(custom_path, run_name='__main__')
|
||||
|
||||
|
||||
def remove_sgr(text: str) -> str:
|
||||
return re.sub(r'\x1b\[.*?m', '', text)
|
||||
|
||||
|
||||
def process_hyperlinks(text: str) -> Tuple[str, Tuple[Mark, ...]]:
|
||||
def process_escape_codes(text: str) -> Tuple[str, Tuple[Mark, ...]]:
|
||||
hyperlinks: List[Mark] = []
|
||||
removed_size = idx = 0
|
||||
active_hyperlink_url: Optional[str] = None
|
||||
@@ -457,6 +474,9 @@ def process_hyperlinks(text: str) -> Tuple[str, Tuple[Mark, ...]]:
|
||||
def process_hyperlink(m: 're.Match[str]') -> str:
|
||||
nonlocal removed_size, active_hyperlink_url, active_hyperlink_id, active_hyperlink_start_offset
|
||||
raw = m.group()
|
||||
if not raw.startswith('\x1b]8'):
|
||||
removed_size += len(raw)
|
||||
return ''
|
||||
start = m.start() - removed_size
|
||||
removed_size += len(raw)
|
||||
if active_hyperlink_url is not None:
|
||||
@@ -474,7 +494,7 @@ def process_hyperlinks(text: str) -> Tuple[str, Tuple[Mark, ...]]:
|
||||
|
||||
return ''
|
||||
|
||||
text = re.sub(r'\x1b\]8.+?\x1b\\', process_hyperlink, text)
|
||||
text = kitty_ansi_sanitizer_pat().sub(process_hyperlink, text)
|
||||
if active_hyperlink_url is not None:
|
||||
add_hyperlink(len(text))
|
||||
return text, tuple(hyperlinks)
|
||||
@@ -482,8 +502,8 @@ def process_hyperlinks(text: str) -> Tuple[str, Tuple[Mark, ...]]:
|
||||
|
||||
def run(args: HintsCLIOptions, text: str, extra_cli_args: Sequence[str] = ()) -> Optional[Dict[str, Any]]:
|
||||
try:
|
||||
text = parse_input(remove_sgr(text))
|
||||
text, hyperlinks = process_hyperlinks(text)
|
||||
text = parse_input(text)
|
||||
text, hyperlinks = process_escape_codes(text)
|
||||
pattern, post_processors = functions_for(args)
|
||||
if args.type == 'linenum':
|
||||
args.customize_processing = '::linenum::'
|
||||
@@ -499,7 +519,7 @@ def run(args: HintsCLIOptions, text: str, extra_cli_args: Sequence[str] = ()) ->
|
||||
all_marks = tuple(mark(pattern, post_processors, text, args))
|
||||
if not all_marks:
|
||||
none_of = {'url': 'URLs', 'hyperlink': 'hyperlinks'}.get(args.type, 'matches')
|
||||
input(_('No {} found, press Enter to quit.').format(none_of))
|
||||
report_error(_('No {} found.').format(none_of))
|
||||
return None
|
||||
|
||||
largest_index = all_marks[-1].index
|
||||
@@ -511,11 +531,7 @@ def run(args: HintsCLIOptions, text: str, extra_cli_args: Sequence[str] = ()) ->
|
||||
m.index = largest_index - m.index + offset
|
||||
index_map = {m.index: m for m in all_marks}
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
input('Press Enter to quit.')
|
||||
raise SystemExit(1)
|
||||
|
||||
report_unhandled_error()
|
||||
return run_loop(args, text, all_marks, index_map, extra_cli_args)
|
||||
|
||||
|
||||
@@ -524,12 +540,27 @@ OPTIONS = r'''
|
||||
--program
|
||||
type=list
|
||||
What program to use to open matched text. Defaults to the default open program
|
||||
for the operating system. Use a value of :file:`-` to paste the match into the
|
||||
terminal window instead. A value of :file:`@` will copy the match to the
|
||||
clipboard. A value of :file:`*` will copy the match to the primary selection
|
||||
(on systems that support primary selections). A value of :file:`default` will
|
||||
run the default open program. Can be specified multiple times to run multiple
|
||||
programs.
|
||||
for the operating system. Various special values are supported:
|
||||
|
||||
:code:`-`
|
||||
paste the match into the terminal window.
|
||||
|
||||
: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
|
||||
@@ -537,22 +568,22 @@ default=url
|
||||
choices=url,regex,path,line,hash,word,linenum,hyperlink,ip
|
||||
The type of text to search for. A value of :code:`linenum` is special, it looks
|
||||
for error messages using the pattern specified with :option:`--regex`, which
|
||||
must have the named groups, :code:`path` and :code:`line`. If not specified,
|
||||
must have the named groups: :code:`path` and :code:`line`. If not specified,
|
||||
will look for :code:`path:line`. The :option:`--linenum-action` option
|
||||
controls where to display the selected error message, other options are ignored.
|
||||
|
||||
|
||||
--regex
|
||||
default={default_regex}
|
||||
The regular expression to use when :option:`kitty +kitten hints --type`=regex.
|
||||
The regular expression is in python syntax. If you specify a numbered group in
|
||||
the regular expression only the group will be matched. This allow you to match
|
||||
text ignoring a prefix/suffix, as needed. The default expression matches lines.
|
||||
To match text over multiple lines you should prefix the regular expression with
|
||||
The regular expression to use when option :option:`--type` is set to
|
||||
:code:`regex`, in python syntax. If you specify a numbered group in the regular
|
||||
expression, only the group will be matched. This allow you to match text
|
||||
ignoring a prefix/suffix, as needed. The default expression matches lines. To
|
||||
match text over multiple lines, you should prefix the regular expression with
|
||||
:code:`(?ms)`, which turns on MULTILINE and DOTALL modes for the regex engine.
|
||||
If you specify named groups and a :option:`kitty +kitten hints --program` then
|
||||
the program will be passed arguments corresponding to each named group of
|
||||
the form key=value.
|
||||
If you specify named groups and a :option:`--program`, then the program will be
|
||||
passed arguments corresponding to each named group of the form
|
||||
:code:`key=value`.
|
||||
|
||||
|
||||
--linenum-action
|
||||
@@ -564,22 +595,22 @@ window, :code:`window` a new kitty window, :code:`tab` a new tab,
|
||||
:code:`os_window` a new OS window and :code:`background` run in the background.
|
||||
The actual action is whatever arguments are provided to the kitten, for
|
||||
example:
|
||||
:code:`kitty + kitten hints --type=linenum --linenum-action=tab vim +{line} {path}`
|
||||
:code:`kitty +kitten hints --type=linenum --linenum-action=tab vim +{line} {path}`
|
||||
will open the matched path at the matched line number in vim in
|
||||
a new kitty tab. Note that only when using :code:`self` are the special values for
|
||||
:option:`kitty +kitten hints --program` to copy/paste the text respected.
|
||||
a new kitty tab. Note that in order to use :option:`--program` to copy or paste
|
||||
text, you need to use the special value :code:`self`.
|
||||
|
||||
|
||||
--url-prefixes
|
||||
default=default
|
||||
Comma separated list of recognized URL prefixes. Defaults, to
|
||||
the list of prefixes defined in kitty.conf.
|
||||
Comma separated list of recognized URL prefixes. Defaults to the list of
|
||||
prefixes defined by the :opt:`url_prefixes` option in :file:`kitty.conf`.
|
||||
|
||||
|
||||
--word-characters
|
||||
Characters to consider as part of a word. In addition, all characters marked as
|
||||
alphanumeric in the unicode database will be considered as word characters.
|
||||
Defaults to the select_by_word_characters setting from kitty.conf.
|
||||
alphanumeric in the Unicode database will be considered as word characters.
|
||||
Defaults to the :opt:`select_by_word_characters` option from :file:`kitty.conf`.
|
||||
|
||||
|
||||
--minimum-match-length
|
||||
@@ -590,26 +621,26 @@ The minimum number of characters to consider a match.
|
||||
|
||||
--multiple
|
||||
type=bool-set
|
||||
Select multiple matches and perform the action on all of them together at the end.
|
||||
In this mode, press :kbd:`Esc` to finish selecting.
|
||||
Select multiple matches and perform the action on all of them together at the
|
||||
end. In this mode, press :kbd:`Esc` to finish selecting.
|
||||
|
||||
|
||||
--multiple-joiner
|
||||
default=auto
|
||||
String to use to join multiple selections when copying to the clipboard or
|
||||
inserting into the terminal. The special strings: "space", "newline", "empty",
|
||||
"json" and "auto" are interpreted as a space character, a newline an empty
|
||||
joiner, a JSON serialized list and an automatic choice, based on the type of
|
||||
text being selected. In addition, integers are interpreted as zero-based
|
||||
indices into the list of selections. You can use 0 for the first selection and
|
||||
-1 for the last.
|
||||
String for joining multiple selections when copying to the clipboard or
|
||||
inserting into the terminal. The special values are: :code:`space` - a space
|
||||
character, :code:`newline` - a newline, :code:`empty` - an empty joiner,
|
||||
:code:`json` - a JSON serialized list, :code:`auto` - an automatic choice, based
|
||||
on the type of text being selected. In addition, integers are interpreted as
|
||||
zero-based indices into the list of selections. You can use :code:`0` for the
|
||||
first selection and :code:`-1` for the last.
|
||||
|
||||
|
||||
--add-trailing-space
|
||||
default=auto
|
||||
choices=auto,always,never
|
||||
Add trailing space after matched text. Defaults to auto, which adds the space
|
||||
when used together with :option:`--multiple`.
|
||||
Add trailing space after matched text. Defaults to :code:`auto`, which adds the
|
||||
space when used together with :option:`--multiple`.
|
||||
|
||||
|
||||
--hints-offset
|
||||
@@ -620,45 +651,47 @@ greater than or equal to zero are respected.
|
||||
|
||||
|
||||
--alphabet
|
||||
The list of characters to use for hints. The default is to use numbers and lowercase
|
||||
English alphabets. Specify your preference as a string of characters. Note that
|
||||
unless you specify the hints offset as zero the first match will be highlighted with
|
||||
the second character you specify.
|
||||
The list of characters to use for hints. The default is to use numbers and
|
||||
lowercase English alphabets. Specify your preference as a string of characters.
|
||||
Note that you need to specify the :option:`--hints-offset` as zero to use the
|
||||
first character to highlight the first match, otherwise it will start with the
|
||||
second character by default.
|
||||
|
||||
|
||||
--ascending
|
||||
type=bool-set
|
||||
Have the hints increase from top to bottom instead of decreasing from top to bottom.
|
||||
Make the hints increase from top to bottom, instead of decreasing from top to
|
||||
bottom.
|
||||
|
||||
|
||||
--hints-foreground-color
|
||||
default=black
|
||||
type=str
|
||||
The foreground color for hints
|
||||
The foreground color for hints.
|
||||
|
||||
|
||||
--hints-background-color
|
||||
default=green
|
||||
type=str
|
||||
The background color for hints
|
||||
The background color for hints.
|
||||
|
||||
|
||||
--hints-text-color
|
||||
default=gray
|
||||
type=str
|
||||
The foreground color for text pointed to by the hints
|
||||
The foreground color for text pointed to by the hints.
|
||||
|
||||
|
||||
--customize-processing
|
||||
Name of a python file in the kitty config directory which will be imported to provide
|
||||
custom implementations for pattern finding and performing actions
|
||||
on selected matches. See {hints_url}
|
||||
for details. You can also specify absolute paths to load the script from elsewhere.
|
||||
Name of a python file in the kitty config directory which will be imported to
|
||||
provide custom implementations for pattern finding and performing actions
|
||||
on selected matches. You can also specify absolute paths to load the script from
|
||||
elsewhere. See {hints_url} for details.
|
||||
|
||||
|
||||
--window-title
|
||||
The window title for the hints window, default title is selected based on
|
||||
the type of text being hinted.
|
||||
The title for the hints window, default title is based on the type of text being
|
||||
hinted.
|
||||
'''.format(
|
||||
default_regex=DEFAULT_REGEX,
|
||||
line='{{line}}', path='{{path}}',
|
||||
@@ -676,9 +709,7 @@ def main(args: List[str]) -> Optional[Dict[str, Any]]:
|
||||
text = ''
|
||||
if sys.stdin.isatty():
|
||||
if '--help' not in args and '-h' not in args:
|
||||
print('You must pass the text to be hinted on STDIN', file=sys.stderr)
|
||||
input(_('Press Enter to quit'))
|
||||
return None
|
||||
report_unhandled_error('You must pass the text to be hinted on STDIN')
|
||||
else:
|
||||
text = sys.stdin.buffer.read().decode('utf-8')
|
||||
sys.stdin = open(os.ctermid())
|
||||
@@ -686,29 +717,33 @@ def main(args: List[str]) -> Optional[Dict[str, Any]]:
|
||||
opts, items = parse_hints_args(args[1:])
|
||||
except SystemExit as e:
|
||||
if e.code != 0:
|
||||
print(e.args[0], file=sys.stderr)
|
||||
input(_('Press Enter to quit'))
|
||||
report_unhandled_error(e.args[0])
|
||||
return None
|
||||
if items and not (opts.customize_processing or opts.type == 'linenum'):
|
||||
print('Extra command line arguments present: {}'.format(' '.join(items)), file=sys.stderr)
|
||||
input(_('Press Enter to quit'))
|
||||
report_unhandled_error('Extra command line arguments present: {}'.format(' '.join(items)))
|
||||
try:
|
||||
return run(opts, text, items)
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
input(_('Press Enter to quit'))
|
||||
report_unhandled_error()
|
||||
return None
|
||||
|
||||
|
||||
def linenum_process_result(data: Dict[str, Any]) -> Tuple[str, int]:
|
||||
lnum_pat = re.compile(r'(:\d+)$')
|
||||
for match, g in zip(data['match'], data['groupdicts']):
|
||||
path, line = g['path'], g['line']
|
||||
if path and line:
|
||||
m = lnum_pat.search(path)
|
||||
if m is not None:
|
||||
line = m.group(1)[1:]
|
||||
path = path.rpartition(':')[0]
|
||||
return os.path.expanduser(path), int(line)
|
||||
return '', -1
|
||||
|
||||
|
||||
def linenum_handle_result(args: List[str], data: Dict[str, Any], target_window_id: int, boss: BossType, extra_cli_args: Sequence[str], *a: Any) -> None:
|
||||
for m, g in zip(data['match'], data['groupdicts']):
|
||||
if m:
|
||||
path, line = g['path'], g['line']
|
||||
path = os.path.expanduser(path.split(':')[-1])
|
||||
line = int(line)
|
||||
break
|
||||
else:
|
||||
path, line = linenum_process_result(data)
|
||||
if not path:
|
||||
return
|
||||
|
||||
cmd = [x.format(path=path, line=line) for x in extra_cli_args or ('vim', '+{line}', '{path}')]
|
||||
@@ -739,7 +774,7 @@ def linenum_handle_result(args: List[str], data: Dict[str, Any], target_window_i
|
||||
}[action])(*cmd)
|
||||
|
||||
|
||||
@result_handler(type_of_input='screen-ansi')
|
||||
@result_handler(type_of_input='screen-ansi', has_ready_notification=Hints.overlay_ready_report_needed)
|
||||
def handle_result(args: List[str], data: Dict[str, Any], target_window_id: int, boss: BossType) -> None:
|
||||
if data['customize_processing']:
|
||||
m = load_custom_processor(data['customize_processing'])
|
||||
@@ -781,12 +816,13 @@ def handle_result(args: List[str], data: Dict[str, Any], target_window_id: int,
|
||||
if program == '-':
|
||||
w = boss.window_id_map.get(target_window_id)
|
||||
if w is not None:
|
||||
w.paste(joined_text())
|
||||
w.paste_text(joined_text())
|
||||
elif program == '@':
|
||||
set_clipboard_string(joined_text())
|
||||
elif program == '*':
|
||||
set_primary_selection(joined_text())
|
||||
else:
|
||||
from kitty.conf.utils import to_cmdline
|
||||
cwd = data['cwd']
|
||||
program = get_options().open_url_with if program == 'default' else program
|
||||
if text_type == 'hyperlink':
|
||||
@@ -795,12 +831,20 @@ def handle_result(args: List[str], data: Dict[str, Any], target_window_id: int,
|
||||
if w is not None:
|
||||
w.open_url(m, hyperlink_id=1, cwd=cwd)
|
||||
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):
|
||||
if groupdict:
|
||||
m = []
|
||||
for k, v in groupdict.items():
|
||||
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__':
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user