Compare commits
593 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
6fa4110da9 | ||
|
|
43ceaf0b7e | ||
|
|
301ca76d14 | ||
|
|
a591304412 | ||
|
|
8fffd6070a | ||
|
|
a899a8ab6d | ||
|
|
9c13e8a549 | ||
|
|
68d2ea3f27 | ||
|
|
2c0269930f | ||
|
|
c4953504ba | ||
|
|
e7ee6d9e26 | ||
|
|
1cb3875cb5 | ||
|
|
14ae2d0862 | ||
|
|
539ec69d3f | ||
|
|
bc873ad838 | ||
|
|
f056b7c1da | ||
|
|
2e790a119a | ||
|
|
4a3ed62809 | ||
|
|
4110074580 | ||
|
|
c21f00c476 | ||
|
|
1ca1c2edad | ||
|
|
ba0f61d752 | ||
|
|
74e70d2548 | ||
|
|
74921c1373 | ||
|
|
a22f37b919 | ||
|
|
9f250fdf26 | ||
|
|
d92eba82bc | ||
|
|
a052958a20 | ||
|
|
81e7e6a356 | ||
|
|
953cf26369 | ||
|
|
7649e2f3ea | ||
|
|
2a6a77467c | ||
|
|
dc61adf9d8 | ||
|
|
e0c4a90aa3 | ||
|
|
5cdff7d6d0 | ||
|
|
4f6c16984a | ||
|
|
7e364b1606 | ||
|
|
a24df07d25 | ||
|
|
347c0d4de4 | ||
|
|
ca2e9f571f | ||
|
|
9be57de4a9 | ||
|
|
cc84ee9734 | ||
|
|
b5ad030a14 | ||
|
|
40b5c43920 | ||
|
|
9f2be8b696 | ||
|
|
4397e60206 | ||
|
|
caab98e70b | ||
|
|
11a4b75ffa | ||
|
|
d25dceacdd | ||
|
|
b9a612fb9c | ||
|
|
3b9a7914ee | ||
|
|
f646dfa238 | ||
|
|
0b2a533b44 | ||
|
|
0e1246c15b | ||
|
|
a987fb81a0 | ||
|
|
4d920b182f | ||
|
|
22f077b384 | ||
|
|
4c78a50dcf | ||
|
|
9d87b08c52 | ||
|
|
97e58c5b86 | ||
|
|
b3f678cf6f | ||
|
|
7f3715cd3e | ||
|
|
a3aaaa1efb | ||
|
|
d1531632a2 | ||
|
|
2151969ae5 | ||
|
|
44e3a6f0fb | ||
|
|
364bad504c | ||
|
|
0648b4f568 | ||
|
|
a47b3bf578 | ||
|
|
3aec573d40 | ||
|
|
5d4b0c0bef | ||
|
|
11c0343420 | ||
|
|
287b843d13 | ||
|
|
1fea4777fa | ||
|
|
b5a2f94185 | ||
|
|
1000376a4f | ||
|
|
02e1272502 | ||
|
|
393e5eb158 | ||
|
|
b2d0c35fc2 | ||
|
|
3ee12082f2 | ||
|
|
1ce4fc9c8c | ||
|
|
57e2866b68 | ||
|
|
195fc1a270 | ||
|
|
39e56f1864 | ||
|
|
da96dfcad2 | ||
|
|
053bb999d3 | ||
|
|
e914f0df8e | ||
|
|
78cd83845f | ||
|
|
f6228cc81e | ||
|
|
83884f42f8 | ||
|
|
fbaf3e2f49 | ||
|
|
c0be0f74d8 | ||
|
|
a9e014226e | ||
|
|
b498d86436 | ||
|
|
ff836a85aa | ||
|
|
688a3eaef7 | ||
|
|
3b15b46d08 | ||
|
|
f21ddae94f | ||
|
|
cd9b752926 | ||
|
|
655a5a2808 | ||
|
|
5f4e519d61 | ||
|
|
720bf7efd6 | ||
|
|
3867620be5 | ||
|
|
03a7361325 | ||
|
|
e8c683db8b | ||
|
|
0a3acd9738 | ||
|
|
08f159c2ba | ||
|
|
d4e7587686 | ||
|
|
d219654387 | ||
|
|
32e31a3c6b | ||
|
|
9419125387 | ||
|
|
1bbab217d0 | ||
|
|
05d648d411 | ||
|
|
5c832d3190 | ||
|
|
ce7282a53c | ||
|
|
0d33380e6a | ||
|
|
9944b895a6 | ||
|
|
fe7045dd2f | ||
|
|
d17757cc84 | ||
|
|
7ef73babdd | ||
|
|
a47aca6fdb | ||
|
|
bf9d47fddd | ||
|
|
4fe10fadb0 | ||
|
|
d29faa0986 | ||
|
|
88ee5e95fc | ||
|
|
9a119255fe | ||
|
|
7ce11050cd | ||
|
|
9b4d1219b8 | ||
|
|
4dd29c726a | ||
|
|
17e31565e2 | ||
|
|
dd72deb546 | ||
|
|
7e82179c79 | ||
|
|
d61e655953 | ||
|
|
cd0ca95877 | ||
|
|
d70610f5d9 | ||
|
|
f9cfb6f989 | ||
|
|
1f4372b34c | ||
|
|
76e7e7d61f | ||
|
|
c09dc73476 | ||
|
|
b090602fdb | ||
|
|
16d3e82515 | ||
|
|
9caa6bb306 | ||
|
|
acf227803f | ||
|
|
f255c15fa9 | ||
|
|
7ce74ac688 | ||
|
|
6090673bb5 | ||
|
|
d1b5420b08 | ||
|
|
e0f2b96fd2 | ||
|
|
47fdce6483 | ||
|
|
bac84f31b3 | ||
|
|
21bc5bf8c0 | ||
|
|
0ae928f409 | ||
|
|
7bf7cc284d | ||
|
|
ff1efebf70 | ||
|
|
9334ed773e | ||
|
|
5d8a0c4b09 | ||
|
|
c5e8a36f9b | ||
|
|
39f9efc816 | ||
|
|
17a3be8cb1 | ||
|
|
ee2c9775a7 | ||
|
|
da486153de | ||
|
|
dd192ad0b7 | ||
|
|
3c6c36487f | ||
|
|
85eedb96ac | ||
|
|
3a8d867c87 | ||
|
|
6a2522452f | ||
|
|
f9a4b6bb0d | ||
|
|
f1fbfe297d | ||
|
|
2c0ac895e6 | ||
|
|
e86c7d668c | ||
|
|
4778e0eb23 | ||
|
|
c3ded5eb72 | ||
|
|
323355d4bb | ||
|
|
92075a44d8 | ||
|
|
386bb837d8 | ||
|
|
ea28029581 | ||
|
|
507e570887 | ||
|
|
5182f79152 | ||
|
|
71106bcd39 | ||
|
|
cf91e1973b | ||
|
|
2170d4e21a | ||
|
|
2538e94390 | ||
|
|
0be0836890 | ||
|
|
725ddf4f8b | ||
|
|
ce6f99044e | ||
|
|
30b81d98f1 | ||
|
|
f0d2d01a36 | ||
|
|
ce32e64692 | ||
|
|
7ea8a7d45f | ||
|
|
b32edf54f8 | ||
|
|
06ff685f8e | ||
|
|
c6ecdf0fa4 | ||
|
|
ca4840717b | ||
|
|
c473df4393 | ||
|
|
fa6c74a9b1 | ||
|
|
8174ecb318 | ||
|
|
5f20d69309 | ||
|
|
aab121b35d | ||
|
|
1a374a7678 | ||
|
|
396906860d | ||
|
|
85254746bf | ||
|
|
c3c7ad78c1 | ||
|
|
77787bdc7c | ||
|
|
9813ad04c2 | ||
|
|
d2c20f9548 | ||
|
|
e51b520897 | ||
|
|
35207af3de | ||
|
|
96269510d3 | ||
|
|
ceb7c440a1 | ||
|
|
25e1edf52e | ||
|
|
7a903adeea | ||
|
|
914f4e9ec9 | ||
|
|
4c5673dc4d | ||
|
|
8529a60d77 | ||
|
|
58da5d4d29 | ||
|
|
4ce6d718c9 | ||
|
|
dd31ee60f2 | ||
|
|
8bb2da3c37 | ||
|
|
47dbaac54f | ||
|
|
346405299b | ||
|
|
a0476c03c3 | ||
|
|
9dd5410433 | ||
|
|
72745dfb62 | ||
|
|
3a0d8ada6a | ||
|
|
b53d756bc2 | ||
|
|
bb8ebf32ae | ||
|
|
704487f0ce | ||
|
|
a9bdf70275 | ||
|
|
017da1159c | ||
|
|
ab5b9fb329 | ||
|
|
e0700d45de | ||
|
|
a2c4f830b3 | ||
|
|
ecf4fcdeb0 | ||
|
|
0a89fe2e17 | ||
|
|
5b5651ca7d | ||
|
|
5e5cd1acc8 | ||
|
|
79fd01093a | ||
|
|
ce63ddfe54 | ||
|
|
a56471f7ed | ||
|
|
96b46c554e | ||
|
|
a819023ab7 | ||
|
|
9504f50bd4 | ||
|
|
8fe71e3dc8 | ||
|
|
34d7643bed | ||
|
|
682eb7d802 | ||
|
|
f86a954131 | ||
|
|
8e3f6d37d9 | ||
|
|
2c38360ca0 | ||
|
|
f9d2167c97 | ||
|
|
17cada7a6a | ||
|
|
19e6f70655 | ||
|
|
d106089cb9 | ||
|
|
0f7f69c218 | ||
|
|
f9171a767c | ||
|
|
34e939f9a1 | ||
|
|
ba76cacd74 | ||
|
|
fd1b64dcc6 | ||
|
|
7c33d7ef17 | ||
|
|
2ccfecb5ae | ||
|
|
a40ff85e1a | ||
|
|
62953c2057 | ||
|
|
0e6091773d | ||
|
|
4c8ef26141 | ||
|
|
7023c7a8ae | ||
|
|
b2bfc4408e | ||
|
|
5b8aca3a2f | ||
|
|
6715ee888e | ||
|
|
4293325539 | ||
|
|
24c44861ef | ||
|
|
eafd20b4b3 | ||
|
|
d9e5edac01 | ||
|
|
f7be4fab48 | ||
|
|
50c822e05a | ||
|
|
6c1fd3015f | ||
|
|
de3b9634ac | ||
|
|
722f6163aa | ||
|
|
82a4e72c85 | ||
|
|
c40bd7ce82 | ||
|
|
356897a32d | ||
|
|
ac0c10c170 | ||
|
|
362c4147e4 | ||
|
|
9b9eae7923 | ||
|
|
8622c47348 | ||
|
|
1bfc46a24f | ||
|
|
8de787a2f3 | ||
|
|
fcc75689d8 | ||
|
|
1eed35b8bb | ||
|
|
1454af2d41 | ||
|
|
cbb2597667 | ||
|
|
9ca691c0c6 | ||
|
|
b9f21f285c | ||
|
|
bf84f95a94 | ||
|
|
d42f2071c3 | ||
|
|
5c343c918d | ||
|
|
5bfed4fe2f | ||
|
|
6c139600a0 | ||
|
|
b80b8524e6 | ||
|
|
ffa63e27ea | ||
|
|
a155b081b7 | ||
|
|
04807453ec | ||
|
|
1b7582e85c | ||
|
|
e106562ef1 | ||
|
|
d3a20f00d6 | ||
|
|
de5443a4db | ||
|
|
e8b19e08fa | ||
|
|
a574081012 | ||
|
|
4f06ce9d72 | ||
|
|
6713580455 | ||
|
|
86f6c946b3 | ||
|
|
9194d1db6b | ||
|
|
420a1b018a | ||
|
|
8009b85073 | ||
|
|
2a58af2be9 | ||
|
|
4decac26f6 | ||
|
|
a6b74f190f | ||
|
|
bfbe60c90e | ||
|
|
384b2f8462 | ||
|
|
d875615c03 | ||
|
|
9aefcfe56f | ||
|
|
266e70222f | ||
|
|
d4639e2aea | ||
|
|
ca8975891b | ||
|
|
bed3d2eb50 | ||
|
|
78495a7f64 | ||
|
|
f66914f7bb | ||
|
|
58e1f6ee1f | ||
|
|
0625035202 | ||
|
|
f8fc272f58 | ||
|
|
6d61de8a8f | ||
|
|
4c08709df1 | ||
|
|
58a28da94d | ||
|
|
26b976d17d | ||
|
|
3141fc3f05 | ||
|
|
a45d6c8b55 | ||
|
|
dadf496a68 | ||
|
|
dae8ae33f0 |
26
.github/workflows/ci.py
vendored
26
.github/workflows/ci.py
vendored
@@ -30,13 +30,17 @@ def install_deps():
|
|||||||
print('Installing kitty dependencies...')
|
print('Installing kitty dependencies...')
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
if is_macos:
|
if is_macos:
|
||||||
items = (x.strip() for x in open('Brewfile').readlines() if not x.startswith('#'))
|
items = (x.split()[1].strip('"') for x in open('Brewfile').readlines() if x.strip().startswith('brew '))
|
||||||
run('brew', 'install', *items)
|
run('brew', 'install', 'fish', *items)
|
||||||
else:
|
else:
|
||||||
run('sudo apt-get update')
|
run('sudo apt-get update')
|
||||||
run('sudo apt-get install -y libgl1-mesa-dev libxi-dev libxrandr-dev libxinerama-dev ca-certificates'
|
run('sudo apt-get install -y libgl1-mesa-dev libxi-dev libxrandr-dev libxinerama-dev ca-certificates'
|
||||||
' libxcursor-dev libxcb-xkb-dev libdbus-1-dev libxkbcommon-dev libharfbuzz-dev libx11-xcb-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')
|
' 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:
|
if is_bundle:
|
||||||
install_bundle()
|
install_bundle()
|
||||||
else:
|
else:
|
||||||
@@ -48,7 +52,7 @@ def install_deps():
|
|||||||
|
|
||||||
def build_kitty():
|
def build_kitty():
|
||||||
python = shutil.which('python3') if is_bundle else sys.executable
|
python = shutil.which('python3') if is_bundle else sys.executable
|
||||||
cmd = '{} setup.py build --verbose'.format(python)
|
cmd = f'{python} setup.py build --verbose'
|
||||||
if os.environ.get('KITTY_SANITIZE') == '1':
|
if os.environ.get('KITTY_SANITIZE') == '1':
|
||||||
cmd += ' --debug --sanitize'
|
cmd += ' --debug --sanitize'
|
||||||
run(cmd)
|
run(cmd)
|
||||||
@@ -59,8 +63,8 @@ def test_kitty():
|
|||||||
|
|
||||||
|
|
||||||
def package_kitty():
|
def package_kitty():
|
||||||
py = 'python3' if is_macos else 'python'
|
python = 'python3' if is_macos else 'python'
|
||||||
run(py + ' setup.py linux-package --update-check-interval=0 --verbose')
|
run(f'{python} setup.py linux-package --update-check-interval=0 --verbose')
|
||||||
if is_macos:
|
if is_macos:
|
||||||
run('python3 setup.py kitty.app --update-check-interval=0 --verbose')
|
run('python3 setup.py kitty.app --update-check-interval=0 --verbose')
|
||||||
run('kitty.app/Contents/MacOS/kitty +runpy "from kitty.constants import *; print(kitty_exe())"')
|
run('kitty.app/Contents/MacOS/kitty +runpy "from kitty.constants import *; print(kitty_exe())"')
|
||||||
@@ -76,11 +80,11 @@ def replace_in_file(path, src, dest):
|
|||||||
def setup_bundle_env():
|
def setup_bundle_env():
|
||||||
global SW
|
global SW
|
||||||
os.environ['SW'] = SW = '/Users/Shared/kitty-build/sw/sw' if is_macos else os.path.join(os.environ['GITHUB_WORKSPACE'], 'sw')
|
os.environ['SW'] = SW = '/Users/Shared/kitty-build/sw/sw' if is_macos else os.path.join(os.environ['GITHUB_WORKSPACE'], 'sw')
|
||||||
os.environ['PKG_CONFIG_PATH'] = SW + '/lib/pkgconfig'
|
os.environ['PKG_CONFIG_PATH'] = os.path.join(SW, 'lib', 'pkgconfig')
|
||||||
if is_macos:
|
if is_macos:
|
||||||
os.environ['PATH'] = '{}:{}'.format('/usr/local/opt/sphinx-doc/bin', os.environ['PATH'])
|
os.environ['PATH'] = '{}:{}'.format('/usr/local/opt/sphinx-doc/bin', os.environ['PATH'])
|
||||||
else:
|
else:
|
||||||
os.environ['LD_LIBRARY_PATH'] = SW + '/lib'
|
os.environ['LD_LIBRARY_PATH'] = os.path.join(SW, 'lib')
|
||||||
os.environ['PYTHONHOME'] = SW
|
os.environ['PYTHONHOME'] = SW
|
||||||
os.environ['PATH'] = '{}:{}'.format(os.path.join(SW, 'bin'), os.environ['PATH'])
|
os.environ['PATH'] = '{}:{}'.format(os.path.join(SW, 'bin'), os.environ['PATH'])
|
||||||
|
|
||||||
@@ -111,7 +115,7 @@ def main():
|
|||||||
setup_bundle_env()
|
setup_bundle_env()
|
||||||
else:
|
else:
|
||||||
if not is_macos and 'pythonLocation' in os.environ:
|
if not is_macos and 'pythonLocation' in os.environ:
|
||||||
os.environ['LD_LIBRARY_PATH'] = '{}/lib'.format(os.environ['pythonLocation'])
|
os.environ['LD_LIBRARY_PATH'] = os.path.join(os.environ['pythonLocation'], 'lib')
|
||||||
action = sys.argv[-1]
|
action = sys.argv[-1]
|
||||||
if action in ('build', 'package'):
|
if action in ('build', 'package'):
|
||||||
install_deps()
|
install_deps()
|
||||||
@@ -122,7 +126,7 @@ def main():
|
|||||||
elif action == 'test':
|
elif action == 'test':
|
||||||
test_kitty()
|
test_kitty()
|
||||||
else:
|
else:
|
||||||
raise SystemExit('Unknown action: ' + action)
|
raise SystemExit(f'Unknown action: {action}')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -88,10 +88,10 @@ jobs:
|
|||||||
run: python test.py mypy
|
run: python test.py mypy
|
||||||
|
|
||||||
- name: Build man page
|
- name: Build man page
|
||||||
run: make FAIL_WARN=-W man
|
run: make FAIL_WARN=1 man
|
||||||
|
|
||||||
- name: Build HTML docs
|
- name: Build HTML docs
|
||||||
run: make FAIL_WARN=-W html
|
run: make FAIL_WARN=1 html
|
||||||
|
|
||||||
bundle:
|
bundle:
|
||||||
name: Bundle test (${{ matrix.os }})
|
name: Bundle test (${{ matrix.os }})
|
||||||
|
|||||||
1
.github/workflows/codeql-analysis.yml
vendored
1
.github/workflows/codeql-analysis.yml
vendored
@@ -27,6 +27,7 @@ jobs:
|
|||||||
uses: github/codeql-action/init@v1
|
uses: github/codeql-action/init@v1
|
||||||
with:
|
with:
|
||||||
languages: python, c
|
languages: python, c
|
||||||
|
setup-python-dependencies: false
|
||||||
|
|
||||||
- name: Build kitty
|
- name: Build kitty
|
||||||
run: python3 .github/workflows/ci.py build
|
run: python3 .github/workflows/ci.py build
|
||||||
|
|||||||
14
Brewfile
14
Brewfile
@@ -1,7 +1,7 @@
|
|||||||
pkg-config
|
brew "pkg-config"
|
||||||
zlib
|
brew "zlib"
|
||||||
librsync
|
brew "librsync"
|
||||||
python
|
brew "python"
|
||||||
imagemagick
|
brew "imagemagick"
|
||||||
harfbuzz
|
brew "harfbuzz"
|
||||||
sphinx-doc
|
brew "sphinx-doc"
|
||||||
|
|||||||
13
Makefile
13
Makefile
@@ -5,6 +5,10 @@ ifdef VERBOSE
|
|||||||
VVAL=--verbose
|
VVAL=--verbose
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
ifdef FAIL_WARN
|
||||||
|
export FAIL_WARN
|
||||||
|
endif
|
||||||
|
|
||||||
all:
|
all:
|
||||||
python3 setup.py $(VVAL)
|
python3 setup.py $(VVAL)
|
||||||
|
|
||||||
@@ -32,13 +36,16 @@ app:
|
|||||||
python3 setup.py kitty.app $(VVAL)
|
python3 setup.py kitty.app $(VVAL)
|
||||||
|
|
||||||
man:
|
man:
|
||||||
$(MAKE) FAIL_WARN=$(FAIL_WARN) -C docs man
|
$(MAKE) -C docs man
|
||||||
|
|
||||||
html:
|
html:
|
||||||
$(MAKE) FAIL_WARN=$(FAIL_WARN) -C docs html
|
$(MAKE) -C docs html
|
||||||
|
|
||||||
|
dirhtml:
|
||||||
|
$(MAKE) -C docs dirhtml
|
||||||
|
|
||||||
linkcheck:
|
linkcheck:
|
||||||
$(MAKE) FAIL_WARN=$(FAIL_WARN) -C docs linkcheck
|
$(MAKE) -C docs linkcheck
|
||||||
|
|
||||||
website:
|
website:
|
||||||
./publish.py --only website
|
./publish.py --only website
|
||||||
|
|||||||
@@ -11,4 +11,4 @@ https://www.reddit.com/r/KittyTerminal[Reddit community]
|
|||||||
|
|
||||||
Packaging status in various repositories:
|
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"]
|
||||||
|
|||||||
56
__main__.py
56
__main__.py
@@ -31,14 +31,16 @@ def runpy(args: List[str]) -> None:
|
|||||||
|
|
||||||
def hold(args: List[str]) -> None:
|
def hold(args: List[str]) -> None:
|
||||||
import subprocess
|
import subprocess
|
||||||
import tty
|
try:
|
||||||
from contextlib import suppress
|
ret = subprocess.Popen(args[1:]).wait()
|
||||||
ret = subprocess.Popen(args[1:]).wait()
|
except KeyboardInterrupt:
|
||||||
with suppress(BaseException):
|
pass
|
||||||
print('\n\x1b[1;32mPress any key to exit', end='', flush=True)
|
except FileNotFoundError:
|
||||||
with suppress(BaseException):
|
print(f'Could not find {args[1]!r} to execute', file=sys.stderr)
|
||||||
tty.setraw(sys.stdin.fileno())
|
except Exception as e:
|
||||||
sys.stdin.buffer.read(1)
|
print(e, file=sys.stderr)
|
||||||
|
from kitty.utils import hold_till_enter
|
||||||
|
hold_till_enter()
|
||||||
raise SystemExit(ret)
|
raise SystemExit(ret)
|
||||||
|
|
||||||
|
|
||||||
@@ -47,6 +49,13 @@ def complete(args: List[str]) -> None:
|
|||||||
complete_main(args[1:], entry_points, namespaced_entry_points)
|
complete_main(args[1:], entry_points, namespaced_entry_points)
|
||||||
|
|
||||||
|
|
||||||
|
def open_urls(args: List[str]) -> None:
|
||||||
|
setattr(sys, 'cmdline_args_for_open', True)
|
||||||
|
sys.argv = ['kitty'] + args[1:]
|
||||||
|
from kitty.main import main as kitty_main
|
||||||
|
kitty_main()
|
||||||
|
|
||||||
|
|
||||||
def launch(args: List[str]) -> None:
|
def launch(args: List[str]) -> None:
|
||||||
import runpy
|
import runpy
|
||||||
sys.argv = args[1:]
|
sys.argv = args[1:]
|
||||||
@@ -69,6 +78,27 @@ def launch(args: List[str]) -> None:
|
|||||||
runpy.run_path(exe, run_name='__main__')
|
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:
|
def run_kitten(args: List[str]) -> None:
|
||||||
try:
|
try:
|
||||||
kitten = args[1]
|
kitten = args[1]
|
||||||
@@ -104,10 +134,6 @@ entry_points = {
|
|||||||
# These two are here for backwards compat
|
# These two are here for backwards compat
|
||||||
'icat': icat,
|
'icat': icat,
|
||||||
'list-fonts': list_fonts,
|
'list-fonts': list_fonts,
|
||||||
'runpy': runpy,
|
|
||||||
'launch': launch,
|
|
||||||
'kitten': run_kitten,
|
|
||||||
'edit-config': edit_config_file,
|
|
||||||
|
|
||||||
'@': remote_control,
|
'@': remote_control,
|
||||||
'+': namespaced,
|
'+': namespaced,
|
||||||
@@ -115,6 +141,12 @@ entry_points = {
|
|||||||
namespaced_entry_points = {k: v for k, v in entry_points.items() if k[0] not in '+@'}
|
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['hold'] = hold
|
||||||
namespaced_entry_points['complete'] = complete
|
namespaced_entry_points['complete'] = complete
|
||||||
|
namespaced_entry_points['runpy'] = runpy
|
||||||
|
namespaced_entry_points['launch'] = launch
|
||||||
|
namespaced_entry_points['open'] = open_urls
|
||||||
|
namespaced_entry_points['kitten'] = run_kitten
|
||||||
|
namespaced_entry_points['edit-config'] = edit_config_file
|
||||||
|
namespaced_entry_points['shebang'] = shebang
|
||||||
|
|
||||||
|
|
||||||
def setup_openssl_environment() -> None:
|
def setup_openssl_environment() -> None:
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import tempfile
|
|||||||
|
|
||||||
def compile_terminfo(base):
|
def compile_terminfo(base):
|
||||||
with tempfile.TemporaryDirectory() as tdir:
|
with tempfile.TemporaryDirectory() as tdir:
|
||||||
proc = subprocess.run(['tic', '-x', '-o' + tdir, 'terminfo/kitty.terminfo'], check=True, stderr=subprocess.PIPE)
|
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$'
|
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():
|
for error in proc.stderr.decode('utf-8').splitlines():
|
||||||
if not re.match(regex, error):
|
if not re.match(regex, error):
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ def initialize_constants():
|
|||||||
kitty_constants = {}
|
kitty_constants = {}
|
||||||
src = read_src_file('constants.py')
|
src = read_src_file('constants.py')
|
||||||
nv = re.search(r'Version\((\d+), (\d+), (\d+)\)', src)
|
nv = re.search(r'Version\((\d+), (\d+), (\d+)\)', src)
|
||||||
kitty_constants['version'] = '%s.%s.%s' % (nv.group(1), nv.group(2), nv.group(3))
|
kitty_constants['version'] = f'{nv.group(1)}.{nv.group(2)}.{nv.group(3)}'
|
||||||
kitty_constants['appname'] = re.search(
|
kitty_constants['appname'] = re.search(
|
||||||
r'appname: str\s+=\s+(u{0,1})[\'"]([^\'"]+)[\'"]', src
|
r'appname: str\s+=\s+(u{0,1})[\'"]([^\'"]+)[\'"]', src
|
||||||
).group(2)
|
).group(2)
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ def binary_includes():
|
|||||||
'ncursesw', 'readline', 'brotlicommon', 'brotlienc', 'brotlidec'
|
'ncursesw', 'readline', 'brotlicommon', 'brotlienc', 'brotlidec'
|
||||||
))) + (
|
))) + (
|
||||||
get_dll_path('bz2', 2), get_dll_path('ssl', 2), get_dll_path('crypto', 2),
|
get_dll_path('bz2', 2), get_dll_path('ssl', 2), get_dll_path('crypto', 2),
|
||||||
get_dll_path('python' + py_ver, 2),
|
get_dll_path(f'python{py_ver}', 2),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ class Env:
|
|||||||
def __init__(self, package_dir):
|
def __init__(self, package_dir):
|
||||||
self.base = package_dir
|
self.base = package_dir
|
||||||
self.lib_dir = j(self.base, 'lib')
|
self.lib_dir = j(self.base, 'lib')
|
||||||
self.py_dir = j(self.lib_dir, 'python' + py_ver)
|
self.py_dir = j(self.lib_dir, f'python{py_ver}')
|
||||||
os.makedirs(self.py_dir)
|
os.makedirs(self.py_dir)
|
||||||
self.bin_dir = j(self.base, 'bin')
|
self.bin_dir = j(self.base, 'bin')
|
||||||
self.obj_dir = mkdtemp('launchers-')
|
self.obj_dir = mkdtemp('launchers-')
|
||||||
@@ -107,7 +107,7 @@ def add_ca_certs(env):
|
|||||||
|
|
||||||
def copy_python(env):
|
def copy_python(env):
|
||||||
print('Copying python...')
|
print('Copying python...')
|
||||||
srcdir = j(PREFIX, 'lib/python' + py_ver)
|
srcdir = j(PREFIX, f'lib/python{py_ver}')
|
||||||
|
|
||||||
for x in os.listdir(srcdir):
|
for x in os.listdir(srcdir):
|
||||||
y = j(srcdir, x)
|
y = j(srcdir, x)
|
||||||
@@ -187,11 +187,11 @@ def strip_files(files, argv_max=(256 * 1024)):
|
|||||||
|
|
||||||
|
|
||||||
def strip_binaries(files):
|
def strip_binaries(files):
|
||||||
print('Stripping %d files...' % len(files))
|
print(f'Stripping {len(files)} files...')
|
||||||
before = sum(os.path.getsize(x) for x in files)
|
before = sum(os.path.getsize(x) for x in files)
|
||||||
strip_files(files)
|
strip_files(files)
|
||||||
after = sum(os.path.getsize(x) for x in files)
|
after = sum(os.path.getsize(x) for x in files)
|
||||||
print('Stripped %.1f MB' % ((before - after) / (1024 * 1024.)))
|
print('Stripped {:.1f} MB'.format((before - after) / (1024 * 1024.)))
|
||||||
|
|
||||||
|
|
||||||
def create_tarfile(env, compression_level='9'):
|
def create_tarfile(env, compression_level='9'):
|
||||||
@@ -203,7 +203,7 @@ def create_tarfile(env, compression_level='9'):
|
|||||||
if err.errno != errno.ENOENT:
|
if err.errno != errno.ENOENT:
|
||||||
raise
|
raise
|
||||||
os.mkdir(base)
|
os.mkdir(base)
|
||||||
dist = os.path.join(base, '%s-%s-%s.tar' % (kitty_constants['appname'], kitty_constants['version'], arch))
|
dist = os.path.join(base, f'{kitty_constants["appname"]}-{kitty_constants["version"]}-{arch}.tar')
|
||||||
with tarfile.open(dist, mode='w', format=tarfile.PAX_FORMAT) as tf:
|
with tarfile.open(dist, mode='w', format=tarfile.PAX_FORMAT) as tf:
|
||||||
cwd = os.getcwd()
|
cwd = os.getcwd()
|
||||||
os.chdir(env.base)
|
os.chdir(env.base)
|
||||||
@@ -213,13 +213,13 @@ def create_tarfile(env, compression_level='9'):
|
|||||||
finally:
|
finally:
|
||||||
os.chdir(cwd)
|
os.chdir(cwd)
|
||||||
print('Compressing archive...')
|
print('Compressing archive...')
|
||||||
ans = dist.rpartition('.')[0] + '.txz'
|
ans = f'{dist.rpartition(".")[0]}.txz'
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
subprocess.check_call(['xz', '--threads=0', '-f', '-' + compression_level, dist])
|
subprocess.check_call(['xz', '--threads=0', '-f', f'-{compression_level}', dist])
|
||||||
secs = time.time() - start_time
|
secs = time.time() - start_time
|
||||||
print('Compressed in %d minutes %d seconds' % (secs // 60, secs % 60))
|
print('Compressed in {} minutes {} seconds'.format(secs // 60, secs % 60))
|
||||||
os.rename(dist + '.xz', ans)
|
os.rename(f'{dist}.xz', ans)
|
||||||
print('Archive %s created: %.2f MB' % (
|
print('Archive {} created: {:.2f} MB'.format(
|
||||||
os.path.basename(ans), os.stat(ans).st_size / (1024.**2)))
|
os.path.basename(ans), os.stat(ans).st_size / (1024.**2)))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ def strip_files(files, argv_max=(256 * 1024)):
|
|||||||
def files_in(folder):
|
def files_in(folder):
|
||||||
for record in os.walk(folder):
|
for record in os.walk(folder):
|
||||||
for f in record[-1]:
|
for f in record[-1]:
|
||||||
yield os.path.join(record[0], f)
|
yield join(record[0], f)
|
||||||
|
|
||||||
|
|
||||||
def expand_dirs(items, exclude=lambda x: x.endswith('.so')):
|
def expand_dirs(items, exclude=lambda x: x.endswith('.so')):
|
||||||
@@ -103,7 +103,7 @@ def expand_dirs(items, exclude=lambda x: x.endswith('.so')):
|
|||||||
|
|
||||||
|
|
||||||
def do_sign(app_dir):
|
def do_sign(app_dir):
|
||||||
with current_dir(os.path.join(app_dir, 'Contents')):
|
with current_dir(join(app_dir, 'Contents')):
|
||||||
# Sign all .so files
|
# Sign all .so files
|
||||||
so_files = {x for x in files_in('.') if x.endswith('.so')}
|
so_files = {x for x in files_in('.') if x.endswith('.so')}
|
||||||
codesign(so_files)
|
codesign(so_files)
|
||||||
@@ -153,7 +153,7 @@ class Freeze(object):
|
|||||||
self.to_strip = []
|
self.to_strip = []
|
||||||
self.warnings = []
|
self.warnings = []
|
||||||
self.py_ver = py_ver
|
self.py_ver = py_ver
|
||||||
self.python_stdlib = join(self.resources_dir, 'Python', 'lib', 'python' + self.py_ver)
|
self.python_stdlib = join(self.resources_dir, 'Python', 'lib', f'python{self.py_ver}')
|
||||||
self.site_packages = self.python_stdlib # hack to avoid needing to add site-packages to path
|
self.site_packages = self.python_stdlib # hack to avoid needing to add site-packages to path
|
||||||
self.obj_dir = mkdtemp('launchers-')
|
self.obj_dir = mkdtemp('launchers-')
|
||||||
|
|
||||||
@@ -177,7 +177,7 @@ class Freeze(object):
|
|||||||
self.run_tests()
|
self.run_tests()
|
||||||
# self.run_shell()
|
# self.run_shell()
|
||||||
|
|
||||||
ret = self.makedmg(self.build_dir, APPNAME + '-' + VERSION)
|
ret = self.makedmg(self.build_dir, f'{APPNAME}-{VERSION}')
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@@ -185,8 +185,17 @@ class Freeze(object):
|
|||||||
def add_ca_certs(self):
|
def add_ca_certs(self):
|
||||||
print('\nDownloading CA certs...')
|
print('\nDownloading CA certs...')
|
||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
cdata = urlopen(kitty_constants['cacerts_url']).read()
|
cdata = None
|
||||||
dest = os.path.join(self.contents_dir, 'Resources', 'cacert.pem')
|
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:
|
with open(dest, 'wb') as f:
|
||||||
f.write(cdata)
|
f.write(cdata)
|
||||||
|
|
||||||
@@ -197,7 +206,7 @@ class Freeze(object):
|
|||||||
|
|
||||||
@flush
|
@flush
|
||||||
def run_tests(self):
|
def run_tests(self):
|
||||||
iv['run_tests'](os.path.join(self.contents_dir, 'MacOS', 'kitty'))
|
iv['run_tests'](join(self.contents_dir, 'MacOS', 'kitty'))
|
||||||
|
|
||||||
@flush
|
@flush
|
||||||
def set_id(self, path_to_lib, new_id):
|
def set_id(self, path_to_lib, new_id):
|
||||||
@@ -222,10 +231,10 @@ class Freeze(object):
|
|||||||
@flush
|
@flush
|
||||||
def get_local_dependencies(self, path_to_lib):
|
def get_local_dependencies(self, path_to_lib):
|
||||||
for x, is_id in self.get_dependencies(path_to_lib):
|
for x, is_id in self.get_dependencies(path_to_lib):
|
||||||
for y in (PREFIX + '/lib/', PREFIX + '/python/Python.framework/', '@rpath/'):
|
for y in (f'{PREFIX}/lib/', f'{PREFIX}/python/Python.framework/', '@rpath/'):
|
||||||
if x.startswith(y):
|
if x.startswith(y):
|
||||||
if y == PREFIX + '/python/Python.framework/':
|
if y == f'{PREFIX}/python/Python.framework/':
|
||||||
y = PREFIX + '/python/'
|
y = f'{PREFIX}/python/'
|
||||||
yield x, x[len(y):], is_id
|
yield x, x[len(y):], is_id
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -239,7 +248,7 @@ class Freeze(object):
|
|||||||
self.to_strip.append(path_to_lib)
|
self.to_strip.append(path_to_lib)
|
||||||
old_mode = flipwritable(path_to_lib)
|
old_mode = flipwritable(path_to_lib)
|
||||||
for dep, bname, is_id in self.get_local_dependencies(path_to_lib):
|
for dep, bname, is_id in self.get_local_dependencies(path_to_lib):
|
||||||
ndep = self.FID + '/' + bname
|
ndep = f'{self.FID}/{bname}'
|
||||||
self.change_dep(dep, ndep, is_id, path_to_lib)
|
self.change_dep(dep, ndep, is_id, path_to_lib)
|
||||||
ldeps = list(self.get_local_dependencies(path_to_lib))
|
ldeps = list(self.get_local_dependencies(path_to_lib))
|
||||||
if ldeps:
|
if ldeps:
|
||||||
@@ -252,7 +261,7 @@ class Freeze(object):
|
|||||||
@flush
|
@flush
|
||||||
def add_python_framework(self):
|
def add_python_framework(self):
|
||||||
print('\nAdding Python framework')
|
print('\nAdding Python framework')
|
||||||
src = join(PREFIX + '/python', 'Python.framework')
|
src = join(f'{PREFIX}/python', 'Python.framework')
|
||||||
x = join(self.frameworks_dir, 'Python.framework')
|
x = join(self.frameworks_dir, 'Python.framework')
|
||||||
curr = os.path.realpath(join(src, 'Versions', 'Current'))
|
curr = os.path.realpath(join(src, 'Versions', 'Current'))
|
||||||
currd = join(x, 'Versions', basename(curr))
|
currd = join(x, 'Versions', basename(curr))
|
||||||
@@ -262,12 +271,12 @@ class Freeze(object):
|
|||||||
shutil.copy2(join(curr, 'Python'), currd)
|
shutil.copy2(join(curr, 'Python'), currd)
|
||||||
self.set_id(
|
self.set_id(
|
||||||
join(currd, 'Python'),
|
join(currd, 'Python'),
|
||||||
self.FID + '/Python.framework/Versions/%s/Python' % basename(curr))
|
f'{self.FID}/Python.framework/Versions/{basename(curr)}/Python')
|
||||||
# The following is needed for codesign
|
# The following is needed for codesign
|
||||||
with current_dir(x):
|
with current_dir(x):
|
||||||
os.symlink(basename(curr), 'Versions/Current')
|
os.symlink(basename(curr), 'Versions/Current')
|
||||||
for y in ('Python', 'Resources'):
|
for y in ('Python', 'Resources'):
|
||||||
os.symlink('Versions/Current/%s' % y, y)
|
os.symlink(f'Versions/Current/{y}', y)
|
||||||
|
|
||||||
@flush
|
@flush
|
||||||
def install_dylib(self, path, set_id=True):
|
def install_dylib(self, path, set_id=True):
|
||||||
@@ -275,7 +284,7 @@ class Freeze(object):
|
|||||||
if set_id:
|
if set_id:
|
||||||
self.set_id(
|
self.set_id(
|
||||||
join(self.frameworks_dir, basename(path)),
|
join(self.frameworks_dir, basename(path)),
|
||||||
self.FID + '/' + basename(path))
|
f'{self.FID}/{basename(path)}')
|
||||||
self.fix_dependencies_in_lib(join(self.frameworks_dir, basename(path)))
|
self.fix_dependencies_in_lib(join(self.frameworks_dir, basename(path)))
|
||||||
|
|
||||||
@flush
|
@flush
|
||||||
@@ -291,11 +300,11 @@ class Freeze(object):
|
|||||||
'rsync.2',
|
'rsync.2',
|
||||||
):
|
):
|
||||||
print('\nAdding', x)
|
print('\nAdding', x)
|
||||||
x = 'lib%s.dylib' % x
|
x = f'lib{x}.dylib'
|
||||||
src = join(PREFIX, 'lib', x)
|
src = join(PREFIX, 'lib', x)
|
||||||
shutil.copy2(src, self.frameworks_dir)
|
shutil.copy2(src, self.frameworks_dir)
|
||||||
dest = join(self.frameworks_dir, x)
|
dest = join(self.frameworks_dir, x)
|
||||||
self.set_id(dest, self.FID + '/' + x)
|
self.set_id(dest, f'{self.FID}/{x}')
|
||||||
self.fix_dependencies_in_lib(dest)
|
self.fix_dependencies_in_lib(dest)
|
||||||
|
|
||||||
@flush
|
@flush
|
||||||
@@ -321,7 +330,7 @@ class Freeze(object):
|
|||||||
@flush
|
@flush
|
||||||
def add_stdlib(self):
|
def add_stdlib(self):
|
||||||
print('\nAdding python stdlib')
|
print('\nAdding python stdlib')
|
||||||
src = PREFIX + '/python/Python.framework/Versions/Current/lib/python' + self.py_ver
|
src = f'{PREFIX}/python/Python.framework/Versions/Current/lib/python{self.py_ver}'
|
||||||
dest = self.python_stdlib
|
dest = self.python_stdlib
|
||||||
if not os.path.exists(dest):
|
if not os.path.exists(dest):
|
||||||
os.makedirs(dest)
|
os.makedirs(dest)
|
||||||
@@ -345,19 +354,19 @@ class Freeze(object):
|
|||||||
kitty_dir = join(self.resources_dir, 'kitty')
|
kitty_dir = join(self.resources_dir, 'kitty')
|
||||||
bases = ('kitty', 'kittens', 'kitty_tests')
|
bases = ('kitty', 'kittens', 'kitty_tests')
|
||||||
for x in bases:
|
for x in bases:
|
||||||
dest = os.path.join(self.python_stdlib, x)
|
dest = join(self.python_stdlib, x)
|
||||||
os.rename(os.path.join(kitty_dir, x), dest)
|
os.rename(join(kitty_dir, x), dest)
|
||||||
if x == 'kitty':
|
if x == 'kitty':
|
||||||
shutil.rmtree(os.path.join(dest, 'launcher'))
|
shutil.rmtree(join(dest, 'launcher'))
|
||||||
os.rename(os.path.join(kitty_dir, '__main__.py'), os.path.join(self.python_stdlib, 'kitty_main.py'))
|
os.rename(join(kitty_dir, '__main__.py'), join(self.python_stdlib, 'kitty_main.py'))
|
||||||
shutil.rmtree(os.path.join(kitty_dir, '__pycache__'))
|
shutil.rmtree(join(kitty_dir, '__pycache__'))
|
||||||
pdir = os.path.join(dirname(self.python_stdlib), 'kitty-extensions')
|
pdir = join(dirname(self.python_stdlib), 'kitty-extensions')
|
||||||
os.mkdir(pdir)
|
os.mkdir(pdir)
|
||||||
print('Extracting extension modules from', self.python_stdlib, 'to', pdir)
|
print('Extracting extension modules from', self.python_stdlib, 'to', pdir)
|
||||||
ext_map = extract_extension_modules(self.python_stdlib, pdir)
|
ext_map = extract_extension_modules(self.python_stdlib, pdir)
|
||||||
shutil.copy(os.path.join(os.path.dirname(self_dir), 'site.py'), os.path.join(self.python_stdlib, 'site.py'))
|
shutil.copy(join(os.path.dirname(self_dir), 'site.py'), join(self.python_stdlib, 'site.py'))
|
||||||
for x in bases:
|
for x in bases:
|
||||||
iv['sanitize_source_folder'](os.path.join(self.python_stdlib, x))
|
iv['sanitize_source_folder'](join(self.python_stdlib, x))
|
||||||
self.compile_py_modules()
|
self.compile_py_modules()
|
||||||
freeze_python(self.python_stdlib, pdir, self.obj_dir, ext_map, develop_mode_env_var='KITTY_DEVELOP_FROM', remove_pyc_files=True)
|
freeze_python(self.python_stdlib, pdir, self.obj_dir, ext_map, develop_mode_env_var='KITTY_DEVELOP_FROM', remove_pyc_files=True)
|
||||||
iv['build_frozen_launcher']([path_to_freeze_dir(), self.obj_dir])
|
iv['build_frozen_launcher']([path_to_freeze_dir(), self.obj_dir])
|
||||||
@@ -434,23 +443,23 @@ class Freeze(object):
|
|||||||
''' Copy a directory d into a dmg named volname '''
|
''' Copy a directory d into a dmg named volname '''
|
||||||
print('\nMaking dmg...')
|
print('\nMaking dmg...')
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
destdir = os.path.join(SW, 'dist')
|
destdir = join(SW, 'dist')
|
||||||
try:
|
try:
|
||||||
shutil.rmtree(destdir)
|
shutil.rmtree(destdir)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
os.mkdir(destdir)
|
os.mkdir(destdir)
|
||||||
dmg = os.path.join(destdir, volname + '.dmg')
|
dmg = join(destdir, f'{volname}.dmg')
|
||||||
if os.path.exists(dmg):
|
if os.path.exists(dmg):
|
||||||
os.unlink(dmg)
|
os.unlink(dmg)
|
||||||
tdir = tempfile.mkdtemp()
|
tdir = tempfile.mkdtemp()
|
||||||
appdir = os.path.join(tdir, os.path.basename(d))
|
appdir = join(tdir, os.path.basename(d))
|
||||||
shutil.copytree(d, appdir, symlinks=True)
|
shutil.copytree(d, appdir, symlinks=True)
|
||||||
if self.sign_installers:
|
if self.sign_installers:
|
||||||
with timeit() as times:
|
with timeit() as times:
|
||||||
sign_app(appdir, self.notarize)
|
sign_app(appdir, self.notarize)
|
||||||
print('Signing completed in %d minutes %d seconds' % tuple(times))
|
print('Signing completed in {} minutes {} seconds'.format(*times))
|
||||||
os.symlink('/Applications', os.path.join(tdir, 'Applications'))
|
os.symlink('/Applications', join(tdir, 'Applications'))
|
||||||
size_in_mb = int(
|
size_in_mb = int(
|
||||||
subprocess.check_output(['du', '-s', '-k', tdir]).decode('utf-8')
|
subprocess.check_output(['du', '-s', '-k', tdir]).decode('utf-8')
|
||||||
.split()[0]) / 1024.
|
.split()[0]) / 1024.
|
||||||
@@ -466,10 +475,10 @@ class Freeze(object):
|
|||||||
print('\nCreating dmg...')
|
print('\nCreating dmg...')
|
||||||
with timeit() as times:
|
with timeit() as times:
|
||||||
subprocess.check_call(cmd + [dmg])
|
subprocess.check_call(cmd + [dmg])
|
||||||
print('dmg created in %d minutes and %d seconds' % tuple(times))
|
print('dmg created in {} minutes and {} seconds'.format(*times))
|
||||||
shutil.rmtree(tdir)
|
shutil.rmtree(tdir)
|
||||||
size = os.stat(dmg).st_size / (1024 * 1024.)
|
size = os.stat(dmg).st_size / (1024 * 1024.)
|
||||||
print('\nInstaller size: %.2fMB\n' % size)
|
print(f'\nInstaller size: {size:.2f}MB\n')
|
||||||
return dmg
|
return dmg
|
||||||
|
|
||||||
|
|
||||||
@@ -477,7 +486,7 @@ def main():
|
|||||||
args = globals()['args']
|
args = globals()['args']
|
||||||
ext_dir = globals()['ext_dir']
|
ext_dir = globals()['ext_dir']
|
||||||
Freeze(
|
Freeze(
|
||||||
os.path.join(ext_dir, kitty_constants['appname'] + '.app'),
|
join(ext_dir, f'{kitty_constants["appname"]}.app'),
|
||||||
dont_strip=args.dont_strip,
|
dont_strip=args.dont_strip,
|
||||||
sign_installers=args.sign_installers,
|
sign_installers=args.sign_installers,
|
||||||
notarize=args.notarize,
|
notarize=args.notarize,
|
||||||
|
|||||||
@@ -166,8 +166,8 @@
|
|||||||
{
|
{
|
||||||
"name": "pygments",
|
"name": "pygments",
|
||||||
"unix": {
|
"unix": {
|
||||||
"filename": "Pygments-2.7.4.tar.gz",
|
"filename": "Pygments-2.11.2.tar.gz",
|
||||||
"hash": "sha256:df49d09b498e83c1a73128295860250b0b7edd4c723a32e9bc0d295c7c2ec337",
|
"hash": "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a",
|
||||||
"urls": ["pypi"]
|
"urls": ["pypi"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -273,8 +273,8 @@
|
|||||||
"name": "wayland",
|
"name": "wayland",
|
||||||
"os": "linux",
|
"os": "linux",
|
||||||
"unix": {
|
"unix": {
|
||||||
"filename": "wayland-1.19.0.tar.xz",
|
"filename": "wayland-1.20.0.tar.xz",
|
||||||
"hash": "sha256:baccd902300d354581cd5ad3cc49daa4921d55fb416a5883e218750fef166d15",
|
"hash": "sha256:b8a034154c7059772e0fdbd27dbfcda6c732df29cae56a82274f6ec5d7cd8725",
|
||||||
"urls": ["https://wayland.freedesktop.org/releases/{filename}"]
|
"urls": ["https://wayland.freedesktop.org/releases/{filename}"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -283,8 +283,8 @@
|
|||||||
"name": "wayland-protocols",
|
"name": "wayland-protocols",
|
||||||
"os": "linux",
|
"os": "linux",
|
||||||
"unix": {
|
"unix": {
|
||||||
"filename": "wayland-protocols-1.20.tar.xz",
|
"filename": "wayland-protocols-1.25.tar.xz",
|
||||||
"hash": "sha256:9782b7a1a863d82d7c92478497d13c758f52e7da4f197aa16443f73de77e4de7",
|
"hash": "sha256:f1ff0f7199d0a0da337217dd8c99979967808dc37731a1e759e822b75b571460",
|
||||||
"urls": ["https://wayland.freedesktop.org/releases/{filename}"]
|
"urls": ["https://wayland.freedesktop.org/releases/{filename}"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
# Minimal makefile for Sphinx documentation
|
# Minimal makefile for Sphinx documentation
|
||||||
#
|
#
|
||||||
|
|
||||||
|
ifdef FAIL_WARN
|
||||||
|
override FAIL_WARN=-W
|
||||||
|
endif
|
||||||
|
|
||||||
# You can set these variables from the command line.
|
# You can set these variables from the command line.
|
||||||
SPHINXOPTS = -n -q -j auto -T $(FAIL_WARN) $(OPTS)
|
SPHINXOPTS = -n -q -j auto -T $(FAIL_WARN) $(OPTS)
|
||||||
SPHINXBUILD = sphinx-build
|
SPHINXBUILD = sphinx-build
|
||||||
|
|||||||
6
docs/_static/custom.css
vendored
6
docs/_static/custom.css
vendored
@@ -28,3 +28,9 @@ details > summary {
|
|||||||
text-decoration-color: var(--color-link-underline);
|
text-decoration-color: var(--color-link-underline);
|
||||||
text-decoration-line: underline;
|
text-decoration-line: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* pygments adds an underline to some white-space, this is particularly visible in
|
||||||
|
* dark mode. Remove it. */
|
||||||
|
.highlight .w {
|
||||||
|
text-decoration: none
|
||||||
|
}
|
||||||
|
|||||||
BIN
docs/_static/poster.png
vendored
Normal file
BIN
docs/_static/poster.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
@@ -77,8 +77,11 @@ You can also define a shortcut to switch to the previously active window::
|
|||||||
|
|
||||||
map ctrl+p nth_window -1
|
map ctrl+p nth_window -1
|
||||||
|
|
||||||
``nth_window`` will focus the nth window for positive numbers and the
|
``nth_window`` will focus the nth window for positive numbers (starting from
|
||||||
previously active windows for negative numbers.
|
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.
|
||||||
|
|
||||||
.. _detach_window:
|
.. _detach_window:
|
||||||
|
|
||||||
@@ -127,12 +130,12 @@ Restore font size :sc:`reset_font_size` (also :kbd:`⌘+0` on
|
|||||||
Toggle fullscreen :sc:`toggle_fullscreen` (also :kbd:`⌃+⌘+f` on macOS)
|
Toggle fullscreen :sc:`toggle_fullscreen` (also :kbd:`⌃+⌘+f` on macOS)
|
||||||
Toggle maximized :sc:`toggle_maximized`
|
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)
|
||||||
Click URL using the keyboard :sc:`open_url`
|
Open URL in web browser :sc:`open_url`
|
||||||
Reset the terminal :sc:`reset_terminal`
|
Reset the terminal :sc:`reset_terminal` (also :kbd:`⌥+⌘+r` on macOS)
|
||||||
Reload :file:`kitty.conf` :sc:`reload_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:`⌘+⌥+f6` on macOS)
|
Debug :file:`kitty.conf` :sc:`debug_config` (also :kbd:`⌥+⌘+,` on macOS)
|
||||||
Pass current selection to program :sc:`pass_selection_to_program`
|
Pass current selection to program :sc:`pass_selection_to_program`
|
||||||
Edit |kitty| config file :sc:`edit_config_file`
|
Edit |kitty| config file :sc:`edit_config_file` (also :kbd:`⌘+,` on macOS)
|
||||||
Open a |kitty| shell :sc:`kitty_shell`
|
Open a |kitty| shell :sc:`kitty_shell`
|
||||||
Increase background opacity :sc:`increase_background_opacity`
|
Increase background opacity :sc:`increase_background_opacity`
|
||||||
Decrease background opacity :sc:`decrease_background_opacity`
|
Decrease background opacity :sc:`decrease_background_opacity`
|
||||||
|
|||||||
@@ -53,8 +53,10 @@ particular desktop, but it should work for most major desktop environments.
|
|||||||
ln -s ~/.local/kitty.app/bin/kitty ~/.local/bin/
|
ln -s ~/.local/kitty.app/bin/kitty ~/.local/bin/
|
||||||
# Place the kitty.desktop file somewhere it can be found by the OS
|
# Place the kitty.desktop file somewhere it can be found by the OS
|
||||||
cp ~/.local/kitty.app/share/applications/kitty.desktop ~/.local/share/applications/
|
cp ~/.local/kitty.app/share/applications/kitty.desktop ~/.local/share/applications/
|
||||||
# Update the path to the kitty icon in the kitty.desktop file
|
# If you want to open text files and images in kitty via your file manager also add the kitty-open.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
|
cp ~/.local/kitty.app/share/applications/kitty-open.desktop ~/.local/share/applications/
|
||||||
|
# Update the path to the kitty 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
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
If you use the venerable `stow <https://www.gnu.org/software/stow/>`_
|
If you use the venerable `stow <https://www.gnu.org/software/stow/>`_
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ servers into which they ssh, without needing to install all of |kitty|.
|
|||||||
``python -m pip -r docs/requirements.txt``.
|
``python -m pip -r docs/requirements.txt``.
|
||||||
|
|
||||||
This applies to creating packages for |kitty| for macOS package managers such as
|
This applies to creating packages for |kitty| for macOS package managers such as
|
||||||
brew or MacPorts as well.
|
Homebrew or MacPorts as well.
|
||||||
|
|
||||||
|
|
||||||
Changing defaults for packages
|
Changing defaults for packages
|
||||||
@@ -190,13 +190,11 @@ update-checking
|
|||||||
|
|
||||||
shell-integration
|
shell-integration
|
||||||
|kitty| by default injects its :ref:`shell_integration` code into the user's
|
|kitty| by default injects its :ref:`shell_integration` code into the user's
|
||||||
shell using environment variables or (for bash only) modifying
|
shell using environment variables. For a package, it might make more sense
|
||||||
the user's :file:`~/.bashrc` file.
|
to distribute the shell integration scripts into the system-wide shell
|
||||||
For a package, it might make more sense to distribute the shell
|
vendor locations. The shell integration files are found in the
|
||||||
integration scripts into the system-wide shell vendor locations. The
|
:file:`shell-integration` directory. Copy them to the system wide shell
|
||||||
shell integration files are found in the :file:`shell-integration`
|
vendor locations for each shell, and use::
|
||||||
directory. Copy them to the system wide shell vendor locations for each
|
|
||||||
shell, and use::
|
|
||||||
|
|
||||||
./setup.py linux-package --shell-integration=enabled\ no-rc
|
./setup.py linux-package --shell-integration=enabled\ no-rc
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ Changelog
|
|||||||
|kitty| is a feature-rich, cross-platform, *fast*, GPU based terminal.
|
|kitty| is a feature-rich, cross-platform, *fast*, GPU based terminal.
|
||||||
To update |kitty|, :doc:`follow the instructions <binary>`.
|
To update |kitty|, :doc:`follow the instructions <binary>`.
|
||||||
|
|
||||||
|
.. recent major features {{{
|
||||||
|
|
||||||
Recent major new features
|
Recent major new features
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
@@ -16,7 +18,7 @@ Recent major new features
|
|||||||
|
|
||||||
.. raw:: html
|
.. raw:: html
|
||||||
|
|
||||||
<video controls width="640" height="360">
|
<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.mp4" type="video/mp4">
|
||||||
<source src="https://download.calibre-ebook.com/videos/kitty.webm" type="video/webm">
|
<source src="https://download.calibre-ebook.com/videos/kitty.webm" type="video/webm">
|
||||||
</video>
|
</video>
|
||||||
@@ -68,11 +70,189 @@ command.
|
|||||||
|
|
||||||
Hints for selecting kitty windows visually
|
Hints for selecting kitty windows visually
|
||||||
|
|
||||||
|
.. }}}
|
||||||
|
|
||||||
Detailed list of changes
|
Detailed list of changes
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
|
||||||
|
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]
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- macOS: Allow opening text files, images and directories with kitty when
|
||||||
|
launched using "Open with" in Finder (:iss:`4460`)
|
||||||
|
|
||||||
|
- Allow including config files matching glob patterns in :file:`kitty.conf`
|
||||||
|
(:iss:`4533`)
|
||||||
|
|
||||||
|
- Shell integration: Fix bash integration not working when ``PROMPT_COMMAND``
|
||||||
|
is used to change the prompt variables (:iss:`4476`)
|
||||||
|
|
||||||
|
- Shell integration: Fix cursor shape not being restored to default when
|
||||||
|
running commands in the shell
|
||||||
|
|
||||||
|
- Improve the UI of the ask kitten (:iss:`4545`)
|
||||||
|
|
||||||
|
- Allow customizing the placement and formatting of the
|
||||||
|
:opt:`tab_activity_symbol` and :opt:`bell_on_tab` symbols
|
||||||
|
by adding them to the :opt:`tab_title_template` (:iss:`4581`, :pull:`4507`)
|
||||||
|
|
||||||
|
- macOS: Persist "Secure Keyboard Entry" across restarts to match the behavior
|
||||||
|
of Terminal.app (:iss:`4471`)
|
||||||
|
|
||||||
|
- hints kitten: Fix common single letter extension files not being detected
|
||||||
|
(:iss:`4491`)
|
||||||
|
|
||||||
|
- Support dotted and dashed underline styles (:pull:`4529`)
|
||||||
|
|
||||||
|
- For the vertical and horizontal layouts have the windows arranged on a ring
|
||||||
|
rather than a plane. This means the first and last window are considered
|
||||||
|
neighbors (:iss:`4494`)
|
||||||
|
|
||||||
|
- A new action to clear the current selection (:iss:`4600`)
|
||||||
|
|
||||||
|
- Shell integration: fish: Fix cursor shape not working with fish's vi mode
|
||||||
|
(:iss:`4508`)
|
||||||
|
|
||||||
|
- Shell integration: fish: Dont override fish's native title setting functionality.
|
||||||
|
See `discussion <https://github.com/fish-shell/fish-shell/issues/8641>`__.
|
||||||
|
|
||||||
|
- macOS: Fix hiding via :kbd:`cmd+h` not working on macOS 10.15.7 (:iss:`4472`)
|
||||||
|
|
||||||
|
- Draw the dots for braille characters more evenly spaced at all font sizes (:iss:`4499`)
|
||||||
|
|
||||||
|
- icat kitten: Add options to mirror images and remove their transparency
|
||||||
|
before displaying them (:iss:`4513`)
|
||||||
|
|
||||||
|
- macOS: Respect the users system-wide global keyboard shortcut preferences
|
||||||
|
(:iss:`4501`)
|
||||||
|
|
||||||
|
- macOS: Fix a few key-presses causing beeps from Cocoa's text input system
|
||||||
|
(:iss:`4489`)
|
||||||
|
|
||||||
|
- macOS: Fix using shortcuts from the global menu bar as subsequent key presses
|
||||||
|
in a multi key mapping not working (:iss:`4519`)
|
||||||
|
|
||||||
|
- Fix getting last command output not working correctly when the screen is
|
||||||
|
scrolled (:pull:`4522`)
|
||||||
|
|
||||||
|
- Show number of windows per tab in the :ac:`select_tab` action (:pull:`4523`)
|
||||||
|
|
||||||
|
- macOS: Fix the shift key not clearing pre-edit text in IME (:iss:`4541`)
|
||||||
|
|
||||||
|
- Fix clicking in a window to focus it and typing immediately sometimes having
|
||||||
|
unexpected effects if at a shell prompt (:iss:`4128`)
|
||||||
|
|
||||||
|
- themes kitten: Allow writing to a different file than :file:`kitty.conf`.
|
||||||
|
|
||||||
|
|
||||||
|
0.24.1 [2022-01-06]
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- Shell integration: Work around conflicts with some zsh plugins (:iss:`4428`)
|
||||||
|
|
||||||
|
- Have the zero width space and various other characters from the *Other,
|
||||||
|
formatting* Unicode category be treated as combining characters (:iss:`4439`)
|
||||||
|
|
||||||
|
- Fix using ``--shell-integration`` with :file:`setup.py` broken (:iss:`4434`)
|
||||||
|
|
||||||
|
- Fix showing debug information not working if kitty's :file:`STDIN` is not a tty
|
||||||
|
(:iss:`4424`)
|
||||||
|
|
||||||
|
- Linux: Fix a regression that broke rendering of emoji with variation selectors
|
||||||
|
(:iss:`4444`)
|
||||||
|
|
||||||
|
|
||||||
0.24.0 [2022-01-04]
|
0.24.0 [2022-01-04]
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@@ -331,7 +511,7 @@ Detailed list of changes
|
|||||||
0.22.0 [2021-07-26]
|
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
|
- When right clicking to extend a selection, move the nearest selection
|
||||||
boundary rather than the end of the selection. To restore previous behavior
|
boundary rather than the end of the selection. To restore previous behavior
|
||||||
|
|||||||
86
docs/conf.py
86
docs/conf.py
@@ -13,7 +13,7 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import Any, Callable, Dict, Iterable, List, Match, Optional, Tuple
|
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple
|
||||||
|
|
||||||
from docutils import nodes
|
from docutils import nodes
|
||||||
from docutils.parsers.rst.roles import set_classes
|
from docutils.parsers.rst.roles import set_classes
|
||||||
@@ -22,16 +22,13 @@ from pygments.token import ( # type: ignore
|
|||||||
Comment, Keyword, Literal, Name, Number, String, Whitespace
|
Comment, Keyword, Literal, Name, Number, String, Whitespace
|
||||||
)
|
)
|
||||||
from sphinx import addnodes, version_info
|
from sphinx import addnodes, version_info
|
||||||
from sphinx.builders.html.transforms import KeyboardTransform
|
|
||||||
from sphinx.util.logging import getLogger
|
from sphinx.util.logging import getLogger
|
||||||
|
|
||||||
KeyboardTransform.builders = ('html', 'dirhtml') # type: ignore
|
|
||||||
|
|
||||||
kitty_src = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
kitty_src = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
if kitty_src not in sys.path:
|
if kitty_src not in sys.path:
|
||||||
sys.path.insert(0, kitty_src)
|
sys.path.insert(0, kitty_src)
|
||||||
|
|
||||||
from kitty.conf.types import Definition # noqa
|
from kitty.conf.types import Definition, expand_opt_references # noqa
|
||||||
from kitty.constants import str_version, website_url # noqa
|
from kitty.constants import str_version, website_url # noqa
|
||||||
|
|
||||||
# config {{{
|
# config {{{
|
||||||
@@ -62,6 +59,7 @@ extensions = [
|
|||||||
'sphinx.ext.ifconfig',
|
'sphinx.ext.ifconfig',
|
||||||
'sphinx.ext.viewcode',
|
'sphinx.ext.viewcode',
|
||||||
'sphinx.ext.githubpages',
|
'sphinx.ext.githubpages',
|
||||||
|
'sphinx.ext.extlinks',
|
||||||
'sphinx_copybutton',
|
'sphinx_copybutton',
|
||||||
'sphinx_inline_tabs',
|
'sphinx_inline_tabs',
|
||||||
"sphinxext.opengraph",
|
"sphinxext.opengraph",
|
||||||
@@ -170,24 +168,11 @@ texinfo_documents = [
|
|||||||
|
|
||||||
# GitHub linking inline roles {{{
|
# GitHub linking inline roles {{{
|
||||||
|
|
||||||
def num_role(
|
extlinks = {
|
||||||
which: str, name: str, rawtext: str, text: str, lineno: int, inliner: Any, options: Any = {}, content: Any = []
|
'iss': ('https://github.com/kovidgoyal/kitty/issues/%s', '#%s'),
|
||||||
) -> Tuple[List[nodes.reference], List[nodes.problematic]]:
|
'pull': ('https://github.com/kovidgoyal/kitty/pull/%s', '#%s'),
|
||||||
' Link to a github issue '
|
'disc': ('https://github.com/kovidgoyal/kitty/discussions/%s', '#%s'),
|
||||||
try:
|
}
|
||||||
issue_num = int(text)
|
|
||||||
if issue_num <= 0:
|
|
||||||
raise ValueError
|
|
||||||
except ValueError:
|
|
||||||
msg = inliner.reporter.error(
|
|
||||||
'GitHub issue number must be a number greater than or equal to 1; '
|
|
||||||
'"%s" is invalid.' % text, line=lineno)
|
|
||||||
prb = inliner.problematic(rawtext, rawtext, msg)
|
|
||||||
return [prb], [msg]
|
|
||||||
url = f'https://github.com/kovidgoyal/kitty/{which}/{issue_num}'
|
|
||||||
set_classes(options)
|
|
||||||
node = nodes.reference(rawtext, f'#{issue_num}', refuri=url, **options)
|
|
||||||
return [node], []
|
|
||||||
|
|
||||||
|
|
||||||
def commit_role(
|
def commit_role(
|
||||||
@@ -232,7 +217,8 @@ if you specify a program-to-run you can use the special placeholder
|
|||||||
from kitty.remote_control import cli_msg, global_options_spec
|
from kitty.remote_control import cli_msg, global_options_spec
|
||||||
with open('generated/cli-kitty-at.rst', 'w') as f:
|
with open('generated/cli-kitty-at.rst', 'w') as f:
|
||||||
p = partial(print, file=f)
|
p = partial(print, file=f)
|
||||||
p('kitty @\n' + '-' * 80)
|
p('kitty @')
|
||||||
|
p('-' * 80)
|
||||||
p('.. program::', 'kitty @')
|
p('.. program::', 'kitty @')
|
||||||
p('\n\n' + as_rst(
|
p('\n\n' + as_rst(
|
||||||
global_options_spec, message=cli_msg, usage='command ...', appname='kitty @'))
|
global_options_spec, message=cli_msg, usage='command ...', appname='kitty @'))
|
||||||
@@ -240,7 +226,8 @@ if you specify a program-to-run you can use the special placeholder
|
|||||||
for cmd_name in sorted(all_command_names()):
|
for cmd_name in sorted(all_command_names()):
|
||||||
func = command_for_name(cmd_name)
|
func = command_for_name(cmd_name)
|
||||||
p(f'.. _at_{func.name}:\n')
|
p(f'.. _at_{func.name}:\n')
|
||||||
p('kitty @', func.name + '\n' + '-' * 120)
|
p('kitty @', func.name)
|
||||||
|
p('-' * 120)
|
||||||
p('.. program::', 'kitty @', func.name)
|
p('.. program::', 'kitty @', func.name)
|
||||||
p('\n\n' + as_rst(*cli_params_for(func)))
|
p('\n\n' + as_rst(*cli_params_for(func)))
|
||||||
from kittens.runner import get_kitten_cli_docs
|
from kittens.runner import get_kitten_cli_docs
|
||||||
@@ -249,12 +236,12 @@ if you specify a program-to-run you can use the special placeholder
|
|||||||
if data:
|
if data:
|
||||||
with open(f'generated/cli-kitten-{kitten}.rst', 'w') as f:
|
with open(f'generated/cli-kitten-{kitten}.rst', 'w') as f:
|
||||||
p = partial(print, file=f)
|
p = partial(print, file=f)
|
||||||
p('.. program::', f'kitty +kitten {kitten}')
|
p('.. program::', 'kitty +kitten', kitten)
|
||||||
p(f'\nSource code for {kitten}')
|
p('\nSource code for', kitten)
|
||||||
p('-' * 72)
|
p('-' * 72)
|
||||||
p(f'\nThe source code for this kitten is `available on GitHub <https://github.com/kovidgoyal/kitty/tree/master/kittens/{kitten}>`_.')
|
p(f'\nThe source code for this kitten is `available on GitHub <https://github.com/kovidgoyal/kitty/tree/master/kittens/{kitten}>`_.')
|
||||||
p('\nCommand Line Interface')
|
p('\nCommand Line Interface')
|
||||||
p('-' * 72, file=f)
|
p('-' * 72)
|
||||||
p('\n\n' + option_spec_as_rst(
|
p('\n\n' + option_spec_as_rst(
|
||||||
data['options'], message=data['help_text'], usage=data['usage'], appname=f'kitty +kitten {kitten}',
|
data['options'], message=data['help_text'], usage=data['usage'], appname=f'kitty +kitten {kitten}',
|
||||||
heading_char='^'))
|
heading_char='^'))
|
||||||
@@ -379,19 +366,6 @@ def link_role(
|
|||||||
return [node], []
|
return [node], []
|
||||||
|
|
||||||
|
|
||||||
def expand_opt_references(conf_name: str, text: str) -> str:
|
|
||||||
conf_name += '.'
|
|
||||||
|
|
||||||
def expand(m: Match[str]) -> str:
|
|
||||||
ref = m.group(1)
|
|
||||||
if '<' not in ref and '.' not in ref:
|
|
||||||
full_ref = conf_name + ref
|
|
||||||
return ':opt:`{} <{}>`'.format(ref, full_ref)
|
|
||||||
return str(m.group())
|
|
||||||
|
|
||||||
return re.sub(r':opt:`(.+?)`', expand, text)
|
|
||||||
|
|
||||||
|
|
||||||
opt_aliases: Dict[str, str] = {}
|
opt_aliases: Dict[str, str] = {}
|
||||||
shortcut_slugs: Dict[str, Tuple[str, str]] = {}
|
shortcut_slugs: Dict[str, Tuple[str, str]] = {}
|
||||||
|
|
||||||
@@ -414,7 +388,7 @@ def parse_opt_node(env: Any, sig: str, signode: Any) -> str:
|
|||||||
opt_aliases[optname] = firstname
|
opt_aliases[optname] = firstname
|
||||||
count += 1
|
count += 1
|
||||||
if not firstname:
|
if not firstname:
|
||||||
raise ValueError('{} is not a valid opt'.format(sig))
|
raise ValueError(f'{sig} is not a valid opt')
|
||||||
return firstname
|
return firstname
|
||||||
|
|
||||||
|
|
||||||
@@ -425,23 +399,33 @@ def parse_shortcut_node(env: Any, sig: str, signode: Any) -> str:
|
|||||||
return sig
|
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]:
|
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]
|
conf_name, opt = target.partition('.')[::2]
|
||||||
if not opt:
|
if not opt:
|
||||||
conf_name, opt = 'kitty', conf_name
|
conf_name, opt = 'kitty', conf_name
|
||||||
full_name = conf_name + '.' + opt
|
full_name = f'{conf_name}.{opt}'
|
||||||
return title, opt_aliases.get(full_name, full_name)
|
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]:
|
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]
|
conf_name, slug = target.partition('.')[::2]
|
||||||
if not slug:
|
if not slug:
|
||||||
conf_name, slug = 'kitty', conf_name
|
conf_name, slug = 'kitty', conf_name
|
||||||
full_name = conf_name + '.' + slug
|
full_name = f'{conf_name}.{slug}'
|
||||||
try:
|
try:
|
||||||
target, stitle = shortcut_slugs[full_name]
|
target, stitle = shortcut_slugs[full_name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
logger.warning('Unknown shortcut: {}'.format(target), location=refnode)
|
logger.warning(f'Unknown shortcut: {target}', location=refnode)
|
||||||
else:
|
else:
|
||||||
if not has_explicit_title:
|
if not has_explicit_title:
|
||||||
title = stitle
|
title = stitle
|
||||||
@@ -470,6 +454,15 @@ def write_conf_docs(app: Any, all_kitten_names: Iterable[str]) -> None:
|
|||||||
sc_role.process_link = process_shortcut_link
|
sc_role.process_link = process_shortcut_link
|
||||||
shortcut_slugs.clear()
|
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:
|
def generate_default_config(definition: Definition, name: str) -> None:
|
||||||
with open(f'generated/conf-{name}.rst', 'w', encoding='utf-8') as f:
|
with open(f'generated/conf-{name}.rst', 'w', encoding='utf-8') as f:
|
||||||
print('.. highlight:: conf\n', file=f)
|
print('.. highlight:: conf\n', file=f)
|
||||||
@@ -520,9 +513,6 @@ def setup(app: Any) -> None:
|
|||||||
app.connect('html-page-context', add_html_context)
|
app.connect('html-page-context', add_html_context)
|
||||||
app.add_lexer('session', SessionLexer() if version_info[0] < 3 else SessionLexer)
|
app.add_lexer('session', SessionLexer() if version_info[0] < 3 else SessionLexer)
|
||||||
app.add_role('link', link_role)
|
app.add_role('link', link_role)
|
||||||
app.add_role('iss', partial(num_role, 'issues'))
|
|
||||||
app.add_role('pull', partial(num_role, 'pull'))
|
|
||||||
app.add_role('disc', partial(num_role, 'discussions'))
|
|
||||||
app.add_role('commit', commit_role)
|
app.add_role('commit', commit_role)
|
||||||
# monkey patch sphinx_inline_tabs to avoid a warning about parallel reads
|
# monkey patch sphinx_inline_tabs to avoid a warning about parallel reads
|
||||||
# see https://github.com/pradyunsg/sphinx-inline-tabs/issues/26
|
# see https://github.com/pradyunsg/sphinx-inline-tabs/issues/26
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ kitty.conf
|
|||||||
rendering frames-per-second. See below for an overview of all customization
|
rendering frames-per-second. See below for an overview of all customization
|
||||||
possibilities.
|
possibilities.
|
||||||
|
|
||||||
You can open the config file within kitty by pressing :sc:`edit_config_file`.
|
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
|
You can reload the config file within kitty by pressing
|
||||||
:sc:`reload_config_file` (:kbd:`⌃+⌘+,` on macOS) or sending kitty the ``SIGUSR1`` signal.
|
:sc:`reload_config_file` (:kbd:`⌃+⌘+,` on macOS) or sending kitty the ``SIGUSR1`` signal.
|
||||||
You can also display the current configuration by pressing the :sc:`debug_config`
|
You can also display the current configuration by pressing :sc:`debug_config`
|
||||||
(:kbd:`⌘+⌥+f6` on macOS) key.
|
(:kbd:`⌥+⌘+,` on macOS).
|
||||||
|
|
||||||
.. _confloc:
|
.. _confloc:
|
||||||
|
|
||||||
@@ -25,12 +25,14 @@ character. This works only if the ``#`` character is the first character
|
|||||||
in the line.
|
in the line.
|
||||||
|
|
||||||
You can include secondary config files via the :code:`include` directive. If
|
You can include secondary config files via the :code:`include` directive. If
|
||||||
you use a relative path for include, it is resolved with respect to the
|
you use a relative path for :code:`include`, it is resolved with respect to the
|
||||||
location of the current config file. Note that environment variables are
|
location of the current config file. Note that environment variables are
|
||||||
expanded, so :code:`${USER}.conf` becomes :file:`name.conf` if
|
expanded, so :code:`${USER}.conf` becomes :file:`name.conf` if
|
||||||
:code:`USER=name`. For example::
|
:code:`USER=name`. Also, you can use :code:`globinclude` to include files
|
||||||
|
matching a shell glob pattern. For example::
|
||||||
|
|
||||||
include other.conf
|
include other.conf
|
||||||
|
globinclude kitty.d/**/*.conf
|
||||||
|
|
||||||
|
|
||||||
.. note:: Syntax highlighting for :file:`kitty.conf` in vim is available via
|
.. note:: Syntax highlighting for :file:`kitty.conf` in vim is available via
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ region of the screen.
|
|||||||
The motivation for this extension is the various problems with the existing
|
The motivation for this extension is the various problems with the existing
|
||||||
solution for erasing to background color, namely the *background color erase
|
solution for erasing to background color, namely the *background color erase
|
||||||
(bce)* capability. See
|
(bce)* capability. See
|
||||||
`this discussion <https://github.com/kovidgoyal/kitty/issues/160#issuecomment-346470545>`_
|
:iss:`this discussion <160#issuecomment-346470545>`
|
||||||
and `this FAQ <https://invisible-island.net/ncurses/ncurses.faq.html#bce_mismatches>`_
|
and `this FAQ <https://invisible-island.net/ncurses/ncurses.faq.html#bce_mismatches>`_
|
||||||
for a summary of problems with *bce*.
|
for a summary of problems with *bce*.
|
||||||
|
|
||||||
|
|||||||
28
docs/faq.rst
28
docs/faq.rst
@@ -6,21 +6,22 @@ Frequently Asked Questions
|
|||||||
Some special symbols are rendered small/truncated in kitty?
|
Some special symbols are rendered small/truncated in kitty?
|
||||||
-----------------------------------------------------------
|
-----------------------------------------------------------
|
||||||
|
|
||||||
The number of cells a unicode character takes up are controlled by 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. 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,
|
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
|
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
|
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
|
let the character overflow into neighboring cells, which is fine if the
|
||||||
neighboring cell is empty, but looks terrible if it is not.
|
neighboring cell is empty, but looks terrible if it is not.
|
||||||
|
|
||||||
Some programs, like powerline, vim with fancy gutter symbols/status-bar, etc.
|
Some programs, like Powerline, vim with fancy gutter symbols/status-bar, etc.
|
||||||
misuse unicode characters from the private use area to represent symbols. Often
|
use Unicode characters from the private use area to represent symbols. Often
|
||||||
these symbols are square and should be rendered in two cells. However, since
|
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
|
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
|
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
|
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?
|
Using a color theme with a background color does not work well in vim?
|
||||||
@@ -208,7 +209,7 @@ bitmapped fonts.
|
|||||||
|
|
||||||
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
|
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
|
||||||
|
|
||||||
If your font is not listed in ``kitty list-fonts`` it means that it is not
|
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::
|
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
|
fc-list : family spacing outline scalable | grep -e spacing=100 -e spacing=90 | grep -e outline=True | grep -e scalable=True
|
||||||
@@ -236,7 +237,7 @@ command to rebuild your fontconfig cache::
|
|||||||
|
|
||||||
fc-cache -r
|
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?
|
How can I assign a single global shortcut to bring up the kitty terminal?
|
||||||
@@ -245,7 +246,7 @@ How can I assign a single global shortcut to bring up the kitty terminal?
|
|||||||
Bringing up applications on a single key press is the job of the window
|
Bringing up applications on a single key press is the job of the window
|
||||||
manager/desktop environment. For ways to do it with kitty (or indeed any
|
manager/desktop environment. For ways to do it with kitty (or indeed any
|
||||||
terminal) in different environments,
|
terminal) in different environments,
|
||||||
see `here <https://github.com/kovidgoyal/kitty/issues/45>`_.
|
see :iss:`here <45>`.
|
||||||
|
|
||||||
|
|
||||||
I do not like the kitty icon!
|
I do not like the kitty icon!
|
||||||
@@ -270,6 +271,10 @@ homepage:
|
|||||||
:target: https://github.com/hristost/kitty-alternative-icon
|
:target: https://github.com/hristost/kitty-alternative-icon
|
||||||
:width: 256
|
:width: 256
|
||||||
|
|
||||||
|
.. image:: https://github.com/igrmk/whiskers/raw/main/whiskers.svg
|
||||||
|
:target: https://github.com/igrmk/whiskers
|
||||||
|
:width: 256
|
||||||
|
|
||||||
On macOS you can change the icon by following the steps:
|
On macOS you can change the icon by following the steps:
|
||||||
|
|
||||||
#. Find :file:`kitty.app` in the Applications folder, select it and press :kbd:`⌘+i`
|
#. Find :file:`kitty.app` in the Applications folder, select it and press :kbd:`⌘+i`
|
||||||
@@ -337,8 +342,7 @@ sorts of places where they may or may not work.
|
|||||||
I am using tmux and have a problem
|
I am using tmux and have a problem
|
||||||
--------------------------------------
|
--------------------------------------
|
||||||
|
|
||||||
First, terminal multiplexers are `a bad idea
|
First, terminal multiplexers are :iss:`a bad idea <391#issuecomment-638320745>`, do
|
||||||
<https://github.com/kovidgoyal/kitty/issues/391#issuecomment-638320745>`_, do
|
|
||||||
not use them, if at all possible. kitty contains features that do all of what
|
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`).
|
tmux does, but better, with the exception of remote persistence (:iss:`391`).
|
||||||
If you still want to use tmux, read on.
|
If you still want to use tmux, read on.
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ emulator. The major design goals are:
|
|||||||
below as well as above the text, with alpha blending. The graphics should also scroll with the text, automatically.
|
below as well as above the text, with alpha blending. The graphics should also scroll with the text, automatically.
|
||||||
* Should use optimizations when the client is running on the same computer as the terminal emulator.
|
* Should use optimizations when the client is running on the same computer as the terminal emulator.
|
||||||
|
|
||||||
For some discussion regarding the design choices, see `#33
|
For some discussion regarding the design choices, see :iss:`33`.
|
||||||
<https://github.com/kovidgoyal/kitty/issues/33>`_.
|
|
||||||
|
|
||||||
To see a quick demo, inside a |kitty| terminal run::
|
To see a quick demo, inside a |kitty| terminal run::
|
||||||
|
|
||||||
@@ -47,6 +46,7 @@ Some programs and libraries that use the kitty graphics protocol:
|
|||||||
Other terminals that have implemented the graphics protocol:
|
Other terminals that have implemented the graphics protocol:
|
||||||
|
|
||||||
* `WezTerm <https://github.com/wez/wezterm/issues/986>`_
|
* `WezTerm <https://github.com/wez/wezterm/issues/986>`_
|
||||||
|
* `Konsole <https://invent.kde.org/utilities/konsole/-/merge_requests/594>`_
|
||||||
|
|
||||||
|
|
||||||
Getting the window size
|
Getting the window size
|
||||||
@@ -109,7 +109,7 @@ features of the graphics protocol:
|
|||||||
|
|
||||||
def serialize_gr_command(**cmd):
|
def serialize_gr_command(**cmd):
|
||||||
payload = cmd.pop('payload', None)
|
payload = cmd.pop('payload', None)
|
||||||
cmd = ','.join('{}={}'.format(k, v) for k, v in cmd.items())
|
cmd = ','.join(f'{k}={v}' for k, v in cmd.items())
|
||||||
ans = []
|
ans = []
|
||||||
w = ans.append
|
w = ans.append
|
||||||
w(b'\033_G'), w(cmd.encode('ascii'))
|
w(b'\033_G'), w(cmd.encode('ascii'))
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ kitty
|
|||||||
changelog
|
changelog
|
||||||
integrations
|
integrations
|
||||||
protocol-extensions
|
protocol-extensions
|
||||||
|
press-mentions
|
||||||
|
|
||||||
|
|
||||||
.. tab:: Fast
|
.. tab:: Fast
|
||||||
|
|
||||||
* Offloads rendering to the GPU for :doc:`lower system load <performance>`
|
* Offloads rendering to the GPU for :doc:`lower system load <performance>`
|
||||||
* Uses threaded rendering for
|
* Uses threaded rendering for :iss:`absolutely minimal latency <2701#issuecomment-636497270>`
|
||||||
`absolutely minimal latency <https://github.com/kovidgoyal/kitty/issues/2701#issuecomment-636497270>`_
|
|
||||||
* Performance tradeoffs can be :ref:`tuned <conf-kitty-performance>`
|
* Performance tradeoffs can be :ref:`tuned <conf-kitty-performance>`
|
||||||
|
|
||||||
.. tab:: Capable
|
.. tab:: Capable
|
||||||
@@ -61,7 +61,7 @@ kitty
|
|||||||
|
|
||||||
.. raw:: html
|
.. raw:: html
|
||||||
|
|
||||||
<video controls width="640" height="360">
|
<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.mp4" type="video/mp4">
|
||||||
<source src="https://download.calibre-ebook.com/videos/kitty.webm" type="video/webm">
|
<source src="https://download.calibre-ebook.com/videos/kitty.webm" type="video/webm">
|
||||||
</video>
|
</video>
|
||||||
|
|||||||
@@ -441,6 +441,7 @@ mode* (the ``smkx/rmkx`` terminfo capabilities). This form is used only in
|
|||||||
"F10", "kf10", "CSI 21 ~"
|
"F10", "kf10", "CSI 21 ~"
|
||||||
"F11", "kf11", "CSI 23 ~"
|
"F11", "kf11", "CSI 23 ~"
|
||||||
"F12", "kf12", "CSI 24 ~"
|
"F12", "kf12", "CSI 24 ~"
|
||||||
|
"MENU", "kf16", "CSI 29 ~"
|
||||||
|
|
||||||
There are a few more functional keys that have special cased legacy encodings.
|
There are a few more functional keys that have special cased legacy encodings.
|
||||||
These are present because they are commonly used and for the sake of legacy
|
These are present because they are commonly used and for the sake of legacy
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ terminal program, you can tell the kittens system to run the
|
|||||||
|
|
||||||
For example, here is a kitten that "zooms/unzooms" the current terminal window
|
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
|
by switching to the stack layout or back to the previous layout. This is
|
||||||
equivalent to the builtin :ref:`action-toggle_layout` action.
|
equivalent to the builtin :ac:`toggle_layout` action.
|
||||||
|
|
||||||
Create a file in the kitty config folder, :file:`~/.config/kitty/zoom_toggle.py`
|
Create a file in the kitty config folder, :file:`~/.config/kitty/zoom_toggle.py`
|
||||||
|
|
||||||
@@ -325,7 +325,7 @@ Kittens created by kitty users
|
|||||||
`smart-scroll <https://github.com/yurikhan/kitty-smart-scroll>`_
|
`smart-scroll <https://github.com/yurikhan/kitty-smart-scroll>`_
|
||||||
Makes the kitty scroll bindings work in full screen applications
|
Makes the kitty scroll bindings work in full screen applications
|
||||||
|
|
||||||
`insert password <https://github.com/kovidgoyal/kitty/issues/1222>`_
|
:iss:`insert password <1222>`
|
||||||
Insert a password from a CLI password manager, taking care to only do it at
|
Insert a password from a CLI password manager, taking care to only do it at
|
||||||
a password prompt.
|
a password prompt.
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,12 @@ 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
|
for example, by ``ls --hyperlink=auto``. You can also :doc:`customize what actions are
|
||||||
taken for different types of URLs <../open_actions>`.
|
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
|
||||||
|
starting with that letter are displayed. Pressing the second letter will
|
||||||
|
select that hint or press :kbd:`Enter` or :kbd:`Space` to select the empty
|
||||||
|
hint.
|
||||||
|
|
||||||
The hints kitten is very powerful to see more detailed help on its various
|
The hints kitten is very powerful to see more detailed help on its various
|
||||||
options and modes of operation, see below. You can use these options to
|
options and modes of operation, see below. You can use these options to
|
||||||
create mappings in :file:`kitty.conf` to select various different text
|
create mappings in :file:`kitty.conf` to select various different text
|
||||||
|
|||||||
@@ -45,17 +45,31 @@ You can now run searches with::
|
|||||||
hg some-search-term
|
hg some-search-term
|
||||||
|
|
||||||
If you want to enable completion, for the kitten, you can delegate completion
|
If you want to enable completion, for the kitten, you can delegate completion
|
||||||
to rg. For that, instead of using an alias create a simple wrapper script named
|
to rg. How to do that varies based on the shell:
|
||||||
:file:`hg` somewhere in your ``PATH``:
|
|
||||||
|
|
||||||
.. code-block:: sh
|
|
||||||
|
|
||||||
#!/bin/sh
|
.. tab:: zsh
|
||||||
exec kitty +kitten hyperlinked_grep "$@"
|
|
||||||
|
|
||||||
Then, for example, for ZSH, add the following to :file:`.zshrc`::
|
Instead of using an alias create a simple wrapper script named
|
||||||
|
:file:`hg` somewhere in your ``PATH``:
|
||||||
|
|
||||||
compdef _rg hg
|
.. code-block:: sh
|
||||||
|
|
||||||
|
#!/bin/sh
|
||||||
|
exec kitty +kitten hyperlinked_grep "$@"
|
||||||
|
|
||||||
|
Then, add the following to :file:`.zshrc`::
|
||||||
|
|
||||||
|
compdef _rg hg
|
||||||
|
|
||||||
|
.. tab:: fish
|
||||||
|
|
||||||
|
You can combine both the aliasing/wrapping and pointing fish
|
||||||
|
to rg's autocompletion with a fish "wrapper" function in your :file:`config.fish`:
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
function hg --wraps rg; kitty +kitten hyperlinked_grep $argv; end
|
||||||
|
|
||||||
To learn more about kitty's powerful framework for customizing URL click
|
To learn more about kitty's powerful framework for customizing URL click
|
||||||
actions, :doc:`see here </open_actions>`.
|
actions, :doc:`see here </open_actions>`.
|
||||||
|
|||||||
@@ -35,6 +35,18 @@ Then you can simply use ``icat image.png`` to view images.
|
|||||||
|
|
||||||
The ``icat`` kitten has various command line arguments to allow it to be used
|
The ``icat`` kitten has various command line arguments to allow it to be used
|
||||||
from inside other programs to display images. In particular, :option:`--place`,
|
from inside other programs to display images. In particular, :option:`--place`,
|
||||||
:option:`--detect-support` 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.
|
||||||
|
|
||||||
|
|
||||||
.. include:: /generated/cli-kitten-icat.rst
|
.. include:: /generated/cli-kitten-icat.rst
|
||||||
|
|||||||
@@ -95,6 +95,12 @@ the command line:
|
|||||||
``@cursor-y``
|
``@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::
|
For example::
|
||||||
|
|
||||||
|
|||||||
@@ -128,33 +128,47 @@ The Splits Layout
|
|||||||
|
|
||||||
This is the most flexible layout. You can create any arrangement of windows
|
This is the most flexible layout. You can create any arrangement of windows
|
||||||
by splitting exiting windows repeatedly. To best use this layout you should
|
by splitting exiting windows repeatedly. To best use this layout you should
|
||||||
define a few extra keybindings in :file:`kitty.conf`::
|
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
|
map shift+up move_window up
|
||||||
map shift+left move_window left
|
map shift+left move_window left
|
||||||
map shift+right move_window right
|
map shift+right move_window right
|
||||||
map shift+down move_window down
|
map shift+down move_window down
|
||||||
|
|
||||||
|
# Switch focus to the neighboring window in the indicated direction
|
||||||
map ctrl+left neighboring_window left
|
map ctrl+left neighboring_window left
|
||||||
map ctrl+right neighboring_window right
|
map ctrl+right neighboring_window right
|
||||||
map ctrl+up neighboring_window up
|
map ctrl+up neighboring_window up
|
||||||
map ctrl+down neighboring_window down
|
map ctrl+down neighboring_window down
|
||||||
|
|
||||||
Now you can create horizontal and vertical splits by using :kbd:`F5` and
|
Windows can be resized using :ref:`window_resizing`. You can swap the windows
|
||||||
:kbd:`F6`. You can move them around using :kbd:`shift+arrow` keys
|
in a split using the ``rotate`` action with an argument of ``180`` and rotate
|
||||||
and you can move focus to neighboring windows using :kbd:`ctrl+arrow` keys.
|
and swap with an argument of ``270``.
|
||||||
You can switch an existing split from horizontal to vertical and vice versa
|
|
||||||
using :kbd:`F7`. Finally, windows can be resized using :ref:`window_resizing`.
|
|
||||||
|
|
||||||
Note that 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
|
This layout takes one option, ``split_axis`` that controls whether new windows
|
||||||
are placed into vertical or horizontal splits, by default::
|
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::
|
||||||
|
|
||||||
enabled_layouts splits:split_axis=horizontal
|
enabled_layouts splits:split_axis=horizontal
|
||||||
|
|
||||||
@@ -239,6 +253,8 @@ action, for example::
|
|||||||
map ctrl+right resize_window wider
|
map ctrl+right resize_window wider
|
||||||
map ctrl+up resize_window taller
|
map ctrl+up resize_window taller
|
||||||
map ctrl+down resize_window shorter 3
|
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 ``resize_window`` action has a second, optional argument to control
|
||||||
the resizing increment (a positive integer that defaults to 1).
|
the resizing increment (a positive integer that defaults to 1).
|
||||||
|
|||||||
@@ -100,3 +100,69 @@ lines. The various available criteria are:
|
|||||||
``file``
|
``file``
|
||||||
A shell glob pattern that must match the filename, for example:
|
A shell glob pattern that must match the filename, for example:
|
||||||
``image-??.png``
|
``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 and
|
||||||
|
URLs onto the kitty dock icon to open them with kitty. The default actions are:
|
||||||
|
|
||||||
|
* 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
|
||||||
|
|
||||||
|
These actions can also be executed from the command line by running::
|
||||||
|
|
||||||
|
open -a kitty.app file_or_url ... (on macOS only)
|
||||||
|
or
|
||||||
|
kitty +open file_or_url ...
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
.. 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 kitty config directory, just like
|
||||||
|
the :file:`open-actions.conf` file above. For example:
|
||||||
|
|
||||||
|
.. code:: conf
|
||||||
|
|
||||||
|
# Open script files based on their shebangs
|
||||||
|
protocol file
|
||||||
|
ext sh,command,tool
|
||||||
|
action launch --hold --type=os-window kitty +shebang $FILE_PATH {SHELL}
|
||||||
|
|
||||||
|
# Open shell specific script files
|
||||||
|
protocol file
|
||||||
|
ext fish,bash,zsh
|
||||||
|
action launch --hold --type=os-window kitty +shebang $FILE_PATH __ext__
|
||||||
|
|
||||||
|
# Open directories
|
||||||
|
protocol file
|
||||||
|
mime inode/directory
|
||||||
|
action launch --type=os-window --cwd $FILE_PATH
|
||||||
|
|
||||||
|
# Open text files without fragments in the editor
|
||||||
|
protocol file
|
||||||
|
mime text/*
|
||||||
|
action launch --type=os-window $EDITOR $FILE_PATH
|
||||||
|
|
||||||
|
# Open image files with icat
|
||||||
|
protocol file
|
||||||
|
mime image/*
|
||||||
|
action launch --type=os-window kitty +kitten icat --hold $FILE_PATH
|
||||||
|
|
||||||
|
# Open ssh URLs with ssh command
|
||||||
|
protocol ssh
|
||||||
|
action launch --type=os-window ssh $URL
|
||||||
|
|||||||
@@ -247,8 +247,7 @@ Would open the scrollback buffer in a new :term:`window` when you press the :kbd
|
|||||||
key. See :sc:`show_scrollback <show_scrollback>` for details.
|
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,
|
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
|
you can see tips for doing so, in :iss:`this thread <719>`.
|
||||||
`this thread <https://github.com/kovidgoyal/kitty/issues/719>`_.
|
|
||||||
|
|
||||||
If you wish to store very large amounts of scrollback to view using the piping or
|
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
|
:sc:`show_scrollback <show_scrollback>` features, you can use the
|
||||||
|
|||||||
18
docs/press-mentions.rst
Normal file
18
docs/press-mentions.rst
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
Press mentions of kitty
|
||||||
|
========================
|
||||||
|
|
||||||
|
`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
|
||||||
@@ -26,7 +26,7 @@ Features
|
|||||||
to view it in a pager
|
to view it in a pager
|
||||||
|
|
||||||
* The current working directory or the command being executed are automatically
|
* The current working directory or the command being executed are automatically
|
||||||
displayed in the kitty window titlebar/tab title.
|
displayed in the kitty window titlebar/tab title
|
||||||
|
|
||||||
* The text cursor is changed to a bar when editing commands at the shell prompt
|
* The text cursor is changed to a bar when editing commands at the shell prompt
|
||||||
|
|
||||||
@@ -48,17 +48,24 @@ off or it can be disabled entirely as well. The :opt:`shell_integration` option
|
|||||||
takes a space separated list of keywords:
|
takes a space separated list of keywords:
|
||||||
|
|
||||||
disabled
|
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
|
no-rc
|
||||||
Do not modify the shell's launch environment to enable integration. Useful if you prefer
|
Do not modify the shell's launch environment to enable integration. Useful if you prefer
|
||||||
to :ref:`manually enable integration <manual_shell_integration>`.
|
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
|
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 text
|
||||||
|
|
||||||
no-title
|
no-title
|
||||||
Turn off setting the kitty window/tab title based on shell state
|
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.
|
||||||
|
|
||||||
no-prompt-mark
|
no-prompt-mark
|
||||||
Turn off marking of prompts. This disables jumping to prompt, browsing
|
Turn off marking of prompts. This disables jumping to prompt, browsing
|
||||||
@@ -66,6 +73,8 @@ no-prompt-mark
|
|||||||
|
|
||||||
no-complete
|
no-complete
|
||||||
Turn off completion for the kitty command.
|
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.
|
||||||
|
|
||||||
|
|
||||||
More ways to browse command output
|
More ways to browse command output
|
||||||
@@ -84,7 +93,7 @@ to be copied.
|
|||||||
|
|
||||||
The feature to jump to previous prompts (
|
The feature to jump to previous prompts (
|
||||||
:sc:`scroll_to_previous_prompt` and :sc:`scroll_to_next_prompt`) and mouse
|
: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
|
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
|
be integrated with browsing command output as well. For example, define the
|
||||||
following mapping in :file:`kitty.conf`:
|
following mapping in :file:`kitty.conf`:
|
||||||
|
|
||||||
@@ -129,19 +138,24 @@ different shells.
|
|||||||
and sources the original :file:`.zshenv`. It then loads the shell integration code.
|
and sources the original :file:`.zshenv`. It then loads the shell integration code.
|
||||||
The remainder of zsh's startup process proceeds as normal.
|
The remainder of zsh's startup process proceeds as normal.
|
||||||
|
|
||||||
.. 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.
|
|
||||||
|
|
||||||
.. tab:: fish
|
.. tab:: fish
|
||||||
|
|
||||||
For fish, to make it automatically load the integration code provided by
|
For fish, to make it automatically load the integration code provided by
|
||||||
kitty, the integration script directory path is prepended to the
|
kitty, the integration script directory path is prepended to the
|
||||||
:code:`XDG_DATA_DIRS` environment variable. This is only applied to the fish
|
``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
|
process and will be cleaned up by the integration script after startup. No files
|
||||||
are added or modified.
|
are added or modified.
|
||||||
|
|
||||||
|
.. tab:: bash
|
||||||
|
|
||||||
|
For bash, kitty starts bash in POSIX mode, using the environment variable
|
||||||
|
``ENV`` to load the shell integrtion 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
|
Then, when launching the shell, kitty sets the environment variable
|
||||||
:envvar:`KITTY_SHELL_INTEGRATION` to the value of the :opt:`shell_integration`
|
:envvar:`KITTY_SHELL_INTEGRATION` to the value of the :opt:`shell_integration`
|
||||||
option. The shell integration code reads the environment variable, turns on the
|
option. The shell integration code reads the environment variable, turns on the
|
||||||
@@ -168,6 +182,7 @@ code used for each shell below:
|
|||||||
|
|
||||||
.. literalinclude:: ../shell-integration/fish/vendor_conf.d/kitty-shell-integration.fish
|
.. literalinclude:: ../shell-integration/fish/vendor_conf.d/kitty-shell-integration.fish
|
||||||
:language: fish
|
:language: fish
|
||||||
|
:force:
|
||||||
|
|
||||||
.. tab:: bash
|
.. tab:: bash
|
||||||
|
|
||||||
@@ -197,15 +212,6 @@ First, in :file:`kitty.conf` set:
|
|||||||
|
|
||||||
Then in your shell's rc file, add the lines:
|
Then in your shell's rc file, add the lines:
|
||||||
|
|
||||||
.. tab:: bash
|
|
||||||
|
|
||||||
.. code-block:: sh
|
|
||||||
|
|
||||||
if test -n "$KITTY_INSTALLATION_DIR"; then
|
|
||||||
export KITTY_SHELL_INTEGRATION="enabled"
|
|
||||||
source "$KITTY_INSTALLATION_DIR/shell-integration/bash/kitty.bash"
|
|
||||||
fi
|
|
||||||
|
|
||||||
.. tab:: zsh
|
.. tab:: zsh
|
||||||
|
|
||||||
.. code-block:: sh
|
.. code-block:: sh
|
||||||
@@ -228,17 +234,35 @@ Then in your shell's rc file, add the lines:
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
.. tab:: bash
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
if test -n "$KITTY_INSTALLATION_DIR"; then
|
||||||
|
export KITTY_SHELL_INTEGRATION="enabled"
|
||||||
|
source "$KITTY_INSTALLATION_DIR/shell-integration/bash/kitty.bash"
|
||||||
|
fi
|
||||||
|
|
||||||
The value of :envvar:`KITTY_SHELL_INTEGRATION` is the same as that for
|
The value of :envvar:`KITTY_SHELL_INTEGRATION` is the same as that for
|
||||||
:opt:`shell_integration`, except if you want to disable shell integration
|
:opt:`shell_integration`, except if you want to disable shell integration
|
||||||
completely, in which case simply do not set the
|
completely, in which case simply do not set the
|
||||||
:envvar:`KITTY_SHELL_INTEGRATION` variable at all.
|
:envvar:`KITTY_SHELL_INTEGRATION` variable at all.
|
||||||
|
|
||||||
If you want this to work while SSHing into a remote system, then you will
|
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 :code:`KITTY_INSTALLATION_DIR`
|
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
|
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.
|
integration scripts that need to be copied onto the remote system.
|
||||||
|
|
||||||
|
|
||||||
|
Integration with other shells
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
There exist third-party integrations to use these features for various other shells:
|
||||||
|
|
||||||
|
* Jupyter console via a patch (:iss:`4475`)
|
||||||
|
* `xonsh <https://github.com/xonsh/xonsh/issues/4623>`__
|
||||||
|
|
||||||
|
|
||||||
Notes for shell developers
|
Notes for shell developers
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ To set the underline style::
|
|||||||
<ESC>[4:1m # this is a straight underline
|
<ESC>[4:1m # this is a straight underline
|
||||||
<ESC>[4:2m # this is a double underline
|
<ESC>[4:2m # this is a double underline
|
||||||
<ESC>[4:3m # this is a curly underline
|
<ESC>[4:3m # this is a curly underline
|
||||||
<ESC>[4:4m # this is a dotted underline (not implemented in kitty)
|
<ESC>[4:4m # this is a dotted underline
|
||||||
<ESC>[4:5m # this is a dashed underline (not implemented in kitty)
|
<ESC>[4:5m # this is a dashed underline
|
||||||
<ESC>[4m # this is a straight underline (for backwards compat)
|
<ESC>[4m # this is a straight underline (for backwards compat)
|
||||||
<ESC>[24m # this is no underline (for backwards compat)
|
<ESC>[24m # this is no underline (for backwards compat)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# License: GPLv3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPLv3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Any, DefaultDict, Dict, FrozenSet, List, Tuple, Union
|
from typing import Any, DefaultDict, Dict, FrozenSet, List, Tuple, Union
|
||||||
@@ -42,7 +43,7 @@ def parse_flag(keymap: KeymapType, type_map: Dict[str, Any], command_class: str)
|
|||||||
lines = []
|
lines = []
|
||||||
for ch in type_map['flag']:
|
for ch in type_map['flag']:
|
||||||
attr, allowed_values = keymap[ch]
|
attr, allowed_values = keymap[ch]
|
||||||
q = ' && '.join(f"g.{attr} != '{x}'" for x in allowed_values)
|
q = ' && '.join(f"g.{attr} != '{x}'" for x in sorted(allowed_values))
|
||||||
lines.append(f'''
|
lines.append(f'''
|
||||||
case {attr}: {{
|
case {attr}: {{
|
||||||
g.{attr} = screen->parser_buf[pos++] & 0xff;
|
g.{attr} = screen->parser_buf[pos++] & 0xff;
|
||||||
@@ -67,7 +68,7 @@ def cmd_for_report(report_name: str, keymap: KeymapType, type_map: Dict[str, Any
|
|||||||
flag_fmt, flag_attrs = [], []
|
flag_fmt, flag_attrs = [], []
|
||||||
cv = {'flag': 'c', 'int': 'i', 'uint': 'I'}[atype]
|
cv = {'flag': 'c', 'int': 'i', 'uint': 'I'}[atype]
|
||||||
for ch in type_map[atype]:
|
for ch in type_map[atype]:
|
||||||
flag_fmt.append('s' + cv)
|
flag_fmt.append(f's{cv}')
|
||||||
attr = keymap[ch][0]
|
attr = keymap[ch][0]
|
||||||
flag_attrs.append(f'"{attr}", {conv}g.{attr}')
|
flag_attrs.append(f'"{attr}", {conv}g.{attr}')
|
||||||
return ' '.join(flag_fmt), ', '.join(flag_attrs)
|
return ' '.join(flag_fmt), ', '.join(flag_attrs)
|
||||||
@@ -240,7 +241,7 @@ static inline void
|
|||||||
|
|
||||||
def write_header(text: str, path: str) -> None:
|
def write_header(text: str, path: str) -> None:
|
||||||
with open(path, 'w') as f:
|
with open(path, 'w') as f:
|
||||||
print(f'// This file is generated by {__file__} do not edit!', file=f, end='\n\n')
|
print(f'// This file is generated by {os.path.basename(__file__)} do not edit!', file=f, end='\n\n')
|
||||||
print('#pragma once', file=f)
|
print('#pragma once', file=f)
|
||||||
print(text, file=f)
|
print(text, file=f)
|
||||||
subprocess.check_call(['clang-format', '-i', path])
|
subprocess.check_call(['clang-format', '-i', path])
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ def patch_file(path: str, what: str, text: str, start_marker: str = '/* ', end_m
|
|||||||
end = raw.index(end_q)
|
end = raw.index(end_q)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise SystemExit(f'Failed to find "{end_q}" in {path}')
|
raise SystemExit(f'Failed to find "{end_q}" in {path}')
|
||||||
raw = raw[:start] + start_q + '\n' + text + '\n' + raw[end:]
|
raw = f'{raw[:start]}{start_q}\n{text}\n{raw[end:]}'
|
||||||
f.seek(0)
|
f.seek(0)
|
||||||
f.truncate(0)
|
f.truncate(0)
|
||||||
f.write(raw)
|
f.write(raw)
|
||||||
@@ -368,7 +368,7 @@ def generate_ctrl_mapping() -> None:
|
|||||||
val = str(ctrl_mapping[k])
|
val = str(ctrl_mapping[k])
|
||||||
items.append(val)
|
items.append(val)
|
||||||
if k in "\\'":
|
if k in "\\'":
|
||||||
k = '\\' + k
|
k = f'\\{k}'
|
||||||
mi.append(f" case '{k}': return {val};")
|
mi.append(f" case '{k}': return {val};")
|
||||||
|
|
||||||
for line_items in chunks(items, 6):
|
for line_items in chunks(items, 6):
|
||||||
|
|||||||
@@ -49,11 +49,10 @@ class_maps: Dict[str, Set[int]] = {}
|
|||||||
all_symbols: Set[int] = set()
|
all_symbols: Set[int] = set()
|
||||||
name_map: Dict[int, str] = {}
|
name_map: Dict[int, str] = {}
|
||||||
word_search_map: DefaultDict[str, Set[int]] = defaultdict(set)
|
word_search_map: DefaultDict[str, Set[int]] = defaultdict(set)
|
||||||
zwj = 0x200d
|
|
||||||
soft_hyphen = 0xad
|
soft_hyphen = 0xad
|
||||||
flag_codepoints = frozenset(range(0x1F1E6, 0x1F1E6 + 26))
|
flag_codepoints = frozenset(range(0x1F1E6, 0x1F1E6 + 26))
|
||||||
# See https://github.com/harfbuzz/harfbuzz/issues/169
|
# See https://github.com/harfbuzz/harfbuzz/issues/169
|
||||||
marks = set(emoji_skin_tone_modifiers) | {zwj} | flag_codepoints
|
marks = set(emoji_skin_tone_modifiers) | flag_codepoints
|
||||||
not_assigned = set(range(0, sys.maxunicode))
|
not_assigned = set(range(0, sys.maxunicode))
|
||||||
property_maps: Dict[str, Set[int]] = defaultdict(set)
|
property_maps: Dict[str, Set[int]] = defaultdict(set)
|
||||||
|
|
||||||
@@ -69,7 +68,6 @@ def parse_prop_list() -> None:
|
|||||||
property_maps[name] |= chars
|
property_maps[name] |= chars
|
||||||
# see https://www.unicode.org/faq/unsup_char.html#3
|
# see https://www.unicode.org/faq/unsup_char.html#3
|
||||||
marks |= property_maps['Other_Default_Ignorable_Code_Point']
|
marks |= property_maps['Other_Default_Ignorable_Code_Point']
|
||||||
marks.add(soft_hyphen)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_ucd() -> None:
|
def parse_ucd() -> None:
|
||||||
@@ -113,6 +111,12 @@ def parse_ucd() -> None:
|
|||||||
marks.add(codepoint)
|
marks.add(codepoint)
|
||||||
elif category.startswith('S'):
|
elif category.startswith('S'):
|
||||||
all_symbols.add(codepoint)
|
all_symbols.add(codepoint)
|
||||||
|
elif category == 'Cf':
|
||||||
|
# we add Cf to marks as it contains things like tags and zero
|
||||||
|
# width chars. Not sure if *all* of Cf should be treated as
|
||||||
|
# combining chars, might need to add individual exceptions in
|
||||||
|
# the future.
|
||||||
|
marks.add(codepoint)
|
||||||
|
|
||||||
with open('nerd-fonts-glyphs.txt') as f:
|
with open('nerd-fonts-glyphs.txt') as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
@@ -382,9 +386,9 @@ def gen_ucd() -> None:
|
|||||||
ascii_range='false'
|
ascii_range='false'
|
||||||
)
|
)
|
||||||
category_test(
|
category_test(
|
||||||
'is_non_rendered_char', p, 'Cc Cs'.split(),
|
'is_non_rendered_char', p, 'Cc Cs Cf'.split(),
|
||||||
'Other_Default_Ignorable_Code_Point and soft hyphen',
|
'Other_Default_Ignorable_Code_Point and soft hyphen',
|
||||||
extra_chars=property_maps['Other_Default_Ignorable_Code_Point'] | {soft_hyphen},
|
extra_chars=property_maps['Other_Default_Ignorable_Code_Point'] | set(range(0xfe00, 0xfe0f + 1)),
|
||||||
ascii_range='false'
|
ascii_range='false'
|
||||||
)
|
)
|
||||||
category_test('is_word_char', p, {c for c in class_maps if c[0] in 'LN'}, 'L and N categories')
|
category_test('is_word_char', p, {c for c in class_maps if c[0] in 'LN'}, 'L and N categories')
|
||||||
|
|||||||
@@ -264,6 +264,8 @@ display_reconfigured(CGDirectDisplayID display UNUSED, CGDisplayChangeSummaryFla
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static NSDictionary<NSString*,NSNumber*> *global_shortcuts = nil;
|
||||||
|
|
||||||
@interface GLFWHelper : NSObject
|
@interface GLFWHelper : NSObject
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -280,6 +282,16 @@ display_reconfigured(CGDirectDisplayID display UNUSED, CGDisplayChangeSummaryFla
|
|||||||
(void)object;
|
(void)object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// watch for settings change and rebuild global_shortcuts using key/value observing on NSUserDefaults
|
||||||
|
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
|
||||||
|
{
|
||||||
|
(void)keyPath; (void)object; (void)change; (void)context;
|
||||||
|
if (global_shortcuts != nil) {
|
||||||
|
[global_shortcuts release];
|
||||||
|
global_shortcuts = nil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@end // GLFWHelper
|
@end // GLFWHelper
|
||||||
|
|
||||||
// Delegate for application related notifications {{{
|
// Delegate for application related notifications {{{
|
||||||
@@ -346,32 +358,49 @@ static GLFWapplicationwillfinishlaunchingfun finish_launching_callback = NULL;
|
|||||||
finish_launching_callback();
|
finish_launching_callback();
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename {
|
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
|
||||||
(void)theApplication;
|
(void)sender;
|
||||||
if (!filename || !_glfw.ns.file_open_callback) return NO;
|
if (!filename || !_glfw.ns.url_open_callback) return NO;
|
||||||
const char *path = NULL;
|
const char *url = NULL;
|
||||||
@try {
|
@try {
|
||||||
path = [[NSFileManager defaultManager] fileSystemRepresentationWithPath: filename];
|
url = [[[NSURL fileURLWithPath:filename] absoluteString] UTF8String];
|
||||||
} @catch(NSException *exc) {
|
} @catch(NSException *exc) {
|
||||||
NSLog(@"Converting openFile filename: %@ failed with error: %@", filename, exc.reason);
|
NSLog(@"Converting openFile filename: %@ failed with error: %@", filename, exc.reason);
|
||||||
return NO;
|
return NO;
|
||||||
}
|
}
|
||||||
if (!path) return NO;
|
if (!url) return NO;
|
||||||
return _glfw.ns.file_open_callback(path);
|
return _glfw.ns.url_open_callback(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames {
|
- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames {
|
||||||
(void)sender;
|
(void)sender;
|
||||||
if (!_glfw.ns.file_open_callback || !filenames) return;
|
if (!_glfw.ns.url_open_callback || !filenames) return;
|
||||||
for (id x in filenames) {
|
for (id x in filenames) {
|
||||||
NSString *filename = x;
|
NSString *filename = x;
|
||||||
const char *path = NULL;
|
const char *url = NULL;
|
||||||
@try {
|
@try {
|
||||||
path = [[NSFileManager defaultManager] fileSystemRepresentationWithPath: filename];
|
url = [[[NSURL fileURLWithPath:filename] absoluteString] UTF8String];
|
||||||
} @catch(NSException *exc) {
|
} @catch(NSException *exc) {
|
||||||
NSLog(@"Converting openFiles filename: %@ failed with error: %@", filename, exc.reason);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,7 +408,6 @@ static GLFWapplicationwillfinishlaunchingfun finish_launching_callback = NULL;
|
|||||||
{
|
{
|
||||||
(void)notification;
|
(void)notification;
|
||||||
[NSApp stop:nil];
|
[NSApp stop:nil];
|
||||||
if (_glfw.ns.file_open_callback) _glfw.ns.file_open_callback(":cocoa::application launched::");
|
|
||||||
|
|
||||||
CGDisplayRegisterReconfigurationCallback(display_reconfigured, NULL);
|
CGDisplayRegisterReconfigurationCallback(display_reconfigured, NULL);
|
||||||
_glfwCocoaPostEmptyEvent();
|
_glfwCocoaPostEmptyEvent();
|
||||||
@@ -453,72 +481,293 @@ void* _glfwLoadLocalVulkanLoaderNS(void)
|
|||||||
////// GLFW platform API //////
|
////// GLFW platform API //////
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
static bool
|
/**
|
||||||
is_modified_tab(NSEvent *event, NSEventModifierFlags modifierFlags) {
|
* Apple Symbolic HotKeys Ids
|
||||||
switch ((NSUInteger)modifierFlags) {
|
* To find this symbolic hot keys indices do:
|
||||||
// No need to handle shift+tab, [shift]+option+tab
|
* 1. open Terminal
|
||||||
case NSEventModifierFlagShift:
|
* 2. restore defaults in System Preferences > Keyboard > Shortcuts
|
||||||
case NSEventModifierFlagOption:
|
* 3. defaults read com.apple.symbolichotkeys > current.txt
|
||||||
case (NSEventModifierFlagShift | NSEventModifierFlagOption):
|
* 4. enable/disable given symbolic hot key in System Preferences > Keyboard > Shortcuts
|
||||||
// Do not intercept cmd+tab, shift+cmd+tab
|
* 5. defaults read com.apple.symbolichotkeys | diff -C 5 current.txt -
|
||||||
case NSEventModifierFlagCommand:
|
* 6. restore defaults in System Preferences > Keyboard > Shortcuts
|
||||||
case (NSEventModifierFlagShift | NSEventModifierFlagCommand):
|
*/
|
||||||
return false;
|
typedef enum AppleShortcutNames {
|
||||||
default:
|
// launchpad & dock
|
||||||
break;
|
kSHKTurnDockHidingOnOrOff = 52, // Opt, Cmd, D
|
||||||
}
|
kSHKShowLaunchpad = 160, //
|
||||||
// ctrl+whatever+tab, option+cmd+tab
|
|
||||||
if (
|
// display
|
||||||
(
|
kSHKDecreaseDisplayBrightness1 = 53, // F14 (Fn)
|
||||||
(modifierFlags & NSEventModifierFlagControl) ||
|
kSHKDecreaseDisplayBrightness2 = 55, // F14 (Fn, Ctrl)
|
||||||
modifierFlags == (NSEventModifierFlagOption | NSEventModifierFlagCommand)
|
kSHKIncreaseDisplayBrightness1 = 54, // F15 (Fn)
|
||||||
) && [event.charactersIgnoringModifiers isEqualToString:@"\t"]
|
kSHKIncreaseDisplayBrightness2 = 56, // F15 (Fn, Ctrl)
|
||||||
) return true;
|
|
||||||
// shift+whatever+tab
|
// mission control
|
||||||
if (
|
kSHKMissionControl = 32, // Ctrl, Arrow Up
|
||||||
(modifierFlags & NSEventModifierFlagShift) &&
|
kSHKShowNotificationCenter = 163, //
|
||||||
[event.charactersIgnoringModifiers isEqualToString:@"\x19"]
|
kSHKTurnDoNotDisturbOnOrOff = 175, //
|
||||||
) return true;
|
kSHKApplicationWindows = 33, // Ctrl, Arrow Down
|
||||||
return false;
|
kSHKShowDesktop = 36, // F11
|
||||||
}
|
kSHKMoveLeftASpace = 79, // Ctrl, Arrow Left
|
||||||
|
kSHKMoveRightASpace = 81, // Ctrl, Arrow Right
|
||||||
|
kSHKSwitchToDesktop1 = 118, // Ctrl, 1
|
||||||
|
kSHKSwitchToDesktop2 = 119, // Ctrl, 2
|
||||||
|
kSHKSwitchToDesktop3 = 120, // Ctrl, 3
|
||||||
|
kSHKSwitchToDesktop4 = 121, // Ctrl, 4
|
||||||
|
kSHKQuickNote = 190, // Fn, Q
|
||||||
|
|
||||||
|
// keyboard
|
||||||
|
kSHKChangeTheWayTabMovesFocus = 13, // Ctrl, F7
|
||||||
|
kSHKTurnKeyboardAccessOnOrOff = 12, // Ctrl, F1
|
||||||
|
kSHKMoveFocusToTheMenuBar = 7, // Ctrl, F2
|
||||||
|
kSHKMoveFocusToTheDock = 8, // Ctrl, F3
|
||||||
|
kSHKMoveFocusToActiveOrNextWindow = 9, // Ctrl, F4
|
||||||
|
kSHKMoveFocusToTheWindowToolbar = 10, // Ctrl, F5
|
||||||
|
kSHKMoveFocusToTheFloatingWindow = 11, // Ctrl, F6
|
||||||
|
kSHKMoveFocusToNextWindow = 27, // Cmd, `
|
||||||
|
kSHKMoveFocusToStatusMenus = 57, // Ctrl, F8
|
||||||
|
|
||||||
|
// input sources
|
||||||
|
kSHKSelectThePreviousInputSource = 60, // Ctrl, Space bar
|
||||||
|
kSHKSelectNextSourceInInputMenu = 61, // Ctrl, Opt, Space bar
|
||||||
|
|
||||||
|
// screenshots
|
||||||
|
kSHKSavePictureOfScreenAsAFile = 28, // Shift, Cmd, 3
|
||||||
|
kSHKCopyPictureOfScreenToTheClipboard = 29, // Ctrl, Shift, Cmd, 3
|
||||||
|
kSHKSavePictureOfSelectedAreaAsAFile = 30, // Shift, Cmd, 4
|
||||||
|
kSHKCopyPictureOfSelectedAreaToTheClipboard = 31, // Ctrl, Shift, Cmd, 4
|
||||||
|
kSHKScreenshotAndRecordingOptions = 184, // Shift, Cmd, 5
|
||||||
|
|
||||||
|
// spotlight
|
||||||
|
kSHKShowSpotlightSearch = 64, // Cmd, Space bar
|
||||||
|
kSHKShowFinderSearchWindow = 65, // Opt, Cmd, Space bar
|
||||||
|
|
||||||
|
// accessibility
|
||||||
|
kSHKTurnZoomOnOrOff = 15, // Opt, Cmd, 8
|
||||||
|
kSHKTurnImageSmoothingOnOrOff = 23, // Opt, Cmd, Backslash "\"
|
||||||
|
kSHKZoomOut = 19, // Opt, Cmd, -
|
||||||
|
kSHKZoomIn = 17, // Opt, Cmd, =
|
||||||
|
kSHKTurnFocusFollowingOnOrOff = 179, //
|
||||||
|
|
||||||
|
kSHKIncreaseContrast = 25, // Ctrl, Opt, Cmd, .
|
||||||
|
kSHKDecreaseContrast = 26, // Ctrl, Opt, Cmd, ,
|
||||||
|
|
||||||
|
kSHKInvertColors = 21, // Ctrl, Opt, Cmd, 8
|
||||||
|
kSHKTurnVoiceOverOnOrOff = 59, // Cmd, F5
|
||||||
|
kSHKShowAccessibilityControls = 162, // Opt, Cmd, F5
|
||||||
|
|
||||||
|
// app shortcuts
|
||||||
|
kSHKShowHelpMenu = 98, // Shift, Cmd, /
|
||||||
|
|
||||||
|
// deprecated (Not shown on macOS Monterey)
|
||||||
|
kSHKMoveFocusToTheWindowDrawer = 51, // Opt, Cmd, `
|
||||||
|
kSHKShowDashboard = 62, // F12
|
||||||
|
kSHKLookUpInDictionary = 70, // Shift, Cmd, E
|
||||||
|
kSHKHideAndShowFrontRow = 73, // Cmd, Esc
|
||||||
|
kSHKActivateSpaces = 75, // F8
|
||||||
|
|
||||||
|
// unknown
|
||||||
|
kSHKUnknown = 0, //
|
||||||
|
} AppleShortcutNames;
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
is_cmd_period(NSEvent *event, NSEventModifierFlags modifierFlags) {
|
is_shiftable_shortcut(int scv) {
|
||||||
if (modifierFlags != NSEventModifierFlagCommand) return false;
|
return scv == kSHKMoveFocusToActiveOrNextWindow || scv == kSHKMoveFocusToNextWindow;
|
||||||
if ([event.charactersIgnoringModifiers isEqualToString:@"."]) return true;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
#define USEFUL_MODS(x) (x & (NSEventModifierFlagShift | NSEventModifierFlagOption | NSEventModifierFlagCommand | NSEventModifierFlagControl))
|
||||||
is_modified_special_key(NSEvent *event, NSEventModifierFlags modifierFlags) {
|
|
||||||
// really one should use [[NSUserDefaults standardUserDefaults] valueForDefaultsDomain:@"com.apple.symbolichotkeys" key:@"AppleSymbolicHotKeys"]
|
static void
|
||||||
// to get the list of global shortcuts and pass through the important ones,
|
build_global_shortcuts_lookup(void) {
|
||||||
// see https://stackoverflow.com/questions/21878482/what-do-the-parameter-values-in-applesymbolichotkeys-plist-dict-represent
|
// dump these in a terminal with: defaults read com.apple.symbolichotkeys
|
||||||
// however given that in order to know which integers are which actions in that dict one needs reverse engineering
|
NSMutableDictionary<NSString*, NSNumber*> *temp = [NSMutableDictionary dictionaryWithCapacity:128]; // will be autoreleased
|
||||||
// see https://stackoverflow.com/questions/866056/how-do-i-programmatically-get-the-shortcut-keys-reserved-by-mac-os-x
|
NSMutableSet<NSNumber*> *temp_configured = [NSMutableSet setWithCapacity:128]; // will be autoreleased
|
||||||
// it's too much effort.
|
NSMutableSet<NSNumber*> *temp_missing_value = [NSMutableSet setWithCapacity:128]; // will be autoreleased
|
||||||
if ([event.charactersIgnoringModifiers length] != 1) return false;
|
NSDictionary *apple_settings = [[NSUserDefaults standardUserDefaults] persistentDomainForName:@"com.apple.symbolichotkeys"];
|
||||||
const unichar ch = [event.charactersIgnoringModifiers characterAtIndex:0];
|
if (apple_settings) {
|
||||||
if (modifierFlags == (NSEventModifierFlagControl | NSEventModifierFlagShift)) {
|
NSDictionary<NSString*, id> *symbolic_hotkeys = [apple_settings objectForKey:@"AppleSymbolicHotKeys"];
|
||||||
switch (ch) {
|
if (symbolic_hotkeys) {
|
||||||
case 0x1b: // Esc
|
for (NSString *key in symbolic_hotkeys) {
|
||||||
case NSF1FunctionKey: case NSF2FunctionKey: case NSF3FunctionKey: case NSF4FunctionKey:
|
id obj = symbolic_hotkeys[key];
|
||||||
case NSF5FunctionKey: case NSF6FunctionKey: case NSF7FunctionKey: case NSF8FunctionKey:
|
if (![key isKindOfClass:[NSString class]] || ![obj isKindOfClass:[NSDictionary class]]) continue;
|
||||||
case NSF9FunctionKey: case NSF10FunctionKey: case NSF11FunctionKey: case NSF12FunctionKey:
|
NSInteger sc = [key integerValue];
|
||||||
case NSF13FunctionKey: case NSF14FunctionKey: case NSF15FunctionKey: case NSF16FunctionKey:
|
NSDictionary *sc_value = obj;
|
||||||
case NSF17FunctionKey: case NSF18FunctionKey: case NSF19FunctionKey:
|
id enabled = [sc_value objectForKey:@"enabled"];
|
||||||
return true;
|
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]]) {
|
||||||
|
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;
|
||||||
|
id p = [value objectForKey:@"parameters"];
|
||||||
|
if (!p || ![p isKindOfClass:[NSArray class]] || [(NSArray*)p count] < 2) continue;
|
||||||
|
NSArray<NSNumber*> *parameters = p;
|
||||||
|
NSInteger ch = [parameters[0] isKindOfClass:[NSNumber class]] ? [parameters[0] integerValue] : 0xffff;
|
||||||
|
NSInteger vk = [parameters[1] isKindOfClass:[NSNumber class]] ? [parameters[1] integerValue] : 0xffff;
|
||||||
|
NSEventModifierFlags mods = ([parameters count] > 2 && [parameters[2] isKindOfClass:[NSNumber class]]) ? [parameters[2] unsignedIntegerValue] : 0;
|
||||||
|
mods = USEFUL_MODS(mods);
|
||||||
|
static char buf[64];
|
||||||
|
#define S(x, k) snprintf(buf, sizeof(buf) - 1, #x":%lx:%ld", (unsigned long)mods, (long)k)
|
||||||
|
if (ch == 0xffff) { if (vk == 0xffff) continue; S(v, vk); } else S(c, ch);
|
||||||
|
temp[@(buf)] = @(sc);
|
||||||
|
// the move to next window shortcuts also respond to the same shortcut + shift
|
||||||
|
if (is_shiftable_shortcut([key intValue]) && !(mods & NSEventModifierFlagShift)) {
|
||||||
|
mods |= NSEventModifierFlagShift;
|
||||||
|
if (ch == 0xffff) S(v, vk); else S(c, ch);
|
||||||
|
temp[@(buf)] = @(sc);
|
||||||
|
}
|
||||||
|
#undef S
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
switch (ch) {
|
|
||||||
case 0x1b: // Esc
|
// Add global shortcut definitions when the default enabled shortcut is not defined,
|
||||||
if (modifierFlags & (NSEventModifierFlagCommand | NSEventModifierFlagControl)) return true;
|
// or when the default enabled shortcut is not disabled and is missing a value.
|
||||||
break;
|
// Here are the shortcuts that are enabled by default in the standard ANSI (US) layout.
|
||||||
case NSHelpFunctionKey: // For some reason keyboards with an insert key have it mapped to help
|
// macOS provides separate configurations for some languages or keyboards.
|
||||||
if (!modifierFlags || modifierFlags == NSEventModifierFlagShift) return true;
|
// In general, the rules here will not take effect.
|
||||||
break;
|
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); */
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
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 ([event.charactersIgnoringModifiers length] == 1) {
|
||||||
|
if (modifierFlags & NSEventModifierFlagShift) {
|
||||||
|
const uint32_t ch_without_shift = vk_to_unicode_key_with_current_layout([event keyCode]);
|
||||||
|
if (ch_without_shift < GLFW_FKEY_FIRST || ch_without_shift > GLFW_FKEY_LAST) {
|
||||||
|
LOOKUP(c, ch_without_shift);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const unichar ch = [event.charactersIgnoringModifiers characterAtIndex:0];
|
||||||
|
LOOKUP(c, ch);
|
||||||
|
}
|
||||||
|
unsigned short vk = [event keyCode];
|
||||||
|
if (vk != 0xffff) {
|
||||||
|
LOOKUP(v, vk);
|
||||||
|
}
|
||||||
|
#undef LOOKUP
|
||||||
|
return kSHKUnknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
is_useful_apple_global_shortcut(int sc) {
|
||||||
|
switch(sc) {
|
||||||
|
// launchpad & dock
|
||||||
|
case kSHKTurnDockHidingOnOrOff: // Opt, Cmd, D
|
||||||
|
case kSHKShowLaunchpad: //
|
||||||
|
|
||||||
|
// display
|
||||||
|
case kSHKDecreaseDisplayBrightness1: // F14 (Fn)
|
||||||
|
case kSHKDecreaseDisplayBrightness2: // F14 (Fn, Ctrl)
|
||||||
|
case kSHKIncreaseDisplayBrightness1: // F15 (Fn)
|
||||||
|
case kSHKIncreaseDisplayBrightness2: // F14 (Fn, Ctrl)
|
||||||
|
|
||||||
|
// mission control
|
||||||
|
case kSHKMissionControl: // Ctrl, Arrow Up
|
||||||
|
case kSHKShowNotificationCenter: //
|
||||||
|
case kSHKTurnDoNotDisturbOnOrOff: //
|
||||||
|
case kSHKApplicationWindows: // Ctrl, Arrow Down
|
||||||
|
case kSHKShowDesktop: // F11
|
||||||
|
case kSHKMoveLeftASpace: // Ctrl, Arrow Left
|
||||||
|
case kSHKMoveRightASpace: // Ctrl, Arrow Right
|
||||||
|
case kSHKSwitchToDesktop1: // Ctrl, 1
|
||||||
|
case kSHKSwitchToDesktop2: // Ctrl, 2
|
||||||
|
case kSHKSwitchToDesktop3: // Ctrl, 3
|
||||||
|
case kSHKSwitchToDesktop4: // Ctrl, 4
|
||||||
|
case kSHKQuickNote: // Fn, Q
|
||||||
|
|
||||||
|
// keyboard
|
||||||
|
/* case kSHKChangeTheWayTabMovesFocus: // Ctrl, F7 */
|
||||||
|
/* case kSHKTurnKeyboardAccessOnOrOff: // Ctrl, F1 */
|
||||||
|
case kSHKMoveFocusToTheMenuBar: // Ctrl, F2
|
||||||
|
case kSHKMoveFocusToTheDock: // Ctrl, F3
|
||||||
|
case kSHKMoveFocusToActiveOrNextWindow: // Ctrl, F4
|
||||||
|
/* case kSHKMoveFocusToTheWindowToolbar: // Ctrl, F5 */
|
||||||
|
/* case kSHKMoveFocusToTheFloatingWindow: // Ctrl, F6 */
|
||||||
|
case kSHKMoveFocusToNextWindow: // Cmd, `
|
||||||
|
case kSHKMoveFocusToStatusMenus: // Ctrl, F8
|
||||||
|
|
||||||
|
// input sources
|
||||||
|
case kSHKSelectThePreviousInputSource: // Ctrl, Space bar
|
||||||
|
case kSHKSelectNextSourceInInputMenu: // Ctrl, Opt, Space bar
|
||||||
|
|
||||||
|
// screenshots
|
||||||
|
/* case kSHKSavePictureOfScreenAsAFile: // Shift, Cmd, 3 */
|
||||||
|
/* case kSHKCopyPictureOfScreenToTheClipboard: // Ctrl, Shift, Cmd, 3 */
|
||||||
|
/* case kSHKSavePictureOfSelectedAreaAsAFile: // Shift, Cmd, 4 */
|
||||||
|
/* case kSHKCopyPictureOfSelectedAreaToTheClipboard: // Ctrl, Shift, Cmd, 4 */
|
||||||
|
/* case kSHKScreenshotAndRecordingOptions: // Shift, Cmd, 5 */
|
||||||
|
|
||||||
|
// spotlight
|
||||||
|
case kSHKShowSpotlightSearch: // Cmd, Space bar
|
||||||
|
case kSHKShowFinderSearchWindow: // Opt, Cmd, Space bar
|
||||||
|
|
||||||
|
// accessibility
|
||||||
|
/* case kSHKTurnZoomOnOrOff: // Opt, Cmd, 8 */
|
||||||
|
/* case kSHKTurnImageSmoothingOnOrOff: // Opt, Cmd, Backslash "\" */
|
||||||
|
/* case kSHKZoomOut: // Opt, Cmd, - */
|
||||||
|
/* case kSHKZoomIn: // Opt, Cmd, = */
|
||||||
|
/* case kSHKTurnFocusFollowingOnOrOff: // */
|
||||||
|
/* case kSHKIncreaseContrast: // Ctrl, Opt, Cmd, . */
|
||||||
|
/* case kSHKDecreaseContrast: // Ctrl, Opt, Cmd, , */
|
||||||
|
/* case kSHKInvertColors: // Ctrl, Opt, Cmd, 8 */
|
||||||
|
/* case kSHKTurnVoiceOverOnOrOff: // Cmd, F5 */
|
||||||
|
/* case kSHKShowAccessibilityControls: // Opt, Cmd, F5 */
|
||||||
|
|
||||||
|
// app shortcuts
|
||||||
|
/* case kSHKShowHelpMenu: // Shift, Cmd, / */
|
||||||
|
|
||||||
|
// deprecated (Not shown on macOS Monterey)
|
||||||
|
/* case kSHKMoveFocusToTheWindowDrawer: // Opt, Cmd, ` */
|
||||||
|
/* case kSHKShowDashboard: // F12 */
|
||||||
|
/* case kSHKLookUpInDictionary: // Shift, Cmd, E */
|
||||||
|
/* case kSHKHideAndShowFrontRow: // Cmd, Esc */
|
||||||
|
/* case kSHKActivateSpaces: // F8 */
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GLFWAPI GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback) {
|
GLFWAPI GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback) {
|
||||||
@@ -557,35 +806,76 @@ int _glfwPlatformInit(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
[NSApp setDelegate:_glfw.ns.delegate];
|
[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)
|
NSEvent* (^keydown_block)(NSEvent*) = ^ NSEvent* (NSEvent* event)
|
||||||
{
|
{
|
||||||
NSEventModifierFlags modifierFlags = [event modifierFlags] & (NSEventModifierFlagShift | NSEventModifierFlagOption | NSEventModifierFlagCommand | NSEventModifierFlagControl);
|
debug_key("---------------- key down -------------------\n");
|
||||||
if (is_modified_special_key(event, modifierFlags) || is_modified_tab(event, modifierFlags) || is_cmd_period(event, modifierFlags)) {
|
debug_key("%s\n", [[event description] UTF8String]);
|
||||||
// Cocoa swallows various key presses, so route them explicitly
|
if (!_glfw.ignoreOSKeyboardProcessing) {
|
||||||
[[NSApp keyWindow].contentView keyDown:event];
|
// first check if there is a global menu bar shortcut
|
||||||
return nil;
|
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;
|
||||||
|
}
|
||||||
|
// now check if there is a useful apple shortcut
|
||||||
|
int global_shortcut = is_active_apple_global_shortcut(event);
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
last_keydown_shortcut_event.virtual_key_code = 0xffff;
|
||||||
return event;
|
NSWindow *kw = [NSApp keyWindow];
|
||||||
|
if (kw && kw.contentView) [kw.contentView keyDown:event];
|
||||||
|
else debug_key("keyDown ignored as no keyWindow present\n");
|
||||||
|
return nil;
|
||||||
};
|
};
|
||||||
|
|
||||||
NSEvent* (^keyup_block)(NSEvent*) = ^ NSEvent* (NSEvent* event)
|
NSEvent* (^keyup_block)(NSEvent*) = ^ NSEvent* (NSEvent* event)
|
||||||
{
|
{
|
||||||
NSEventModifierFlags modifierFlags = [event modifierFlags] & (NSEventModifierFlagShift | NSEventModifierFlagOption | NSEventModifierFlagCommand | NSEventModifierFlagControl);
|
debug_key("----------------- key up --------------------\n");
|
||||||
if (modifierFlags & NSEventModifierFlagCommand) {
|
debug_key("%s\n", [[event description] UTF8String]);
|
||||||
// From https://cocoadev.github.io/GameKeyboardHandlingAlmost/
|
if (last_keydown_shortcut_event.virtual_key_code != 0xffff && last_keydown_shortcut_event.virtual_key_code == [event keyCode]) {
|
||||||
// This works around an AppKit bug, where key up events while holding
|
// ignore as the corresponding key down event triggered a menu bar or macOS shortcut
|
||||||
// down the command key don't get sent to the key window.
|
last_keydown_shortcut_event.virtual_key_code = 0xffff;
|
||||||
[[NSApp keyWindow] sendEvent:event];
|
debug_key("keyUp ignored as corresponds to previous keyDown that trigerred a shortcut\n");
|
||||||
}
|
|
||||||
if (is_modified_special_key(event, modifierFlags) || is_modified_tab(event, modifierFlags) || is_cmd_period(event, modifierFlags)) {
|
|
||||||
// Cocoa swallows various key presses, so route them explicitly
|
|
||||||
[[NSApp keyWindow].contentView keyUp:event];
|
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
|
NSWindow *kw = [NSApp keyWindow];
|
||||||
|
if (kw && kw.contentView) [kw.contentView keyUp:event];
|
||||||
|
else debug_key("keyUp ignored as no keyWindow present\n");
|
||||||
|
return nil;
|
||||||
|
};
|
||||||
|
|
||||||
return event;
|
NSEvent* (^flags_changed_block)(NSEvent*) = ^ NSEvent* (NSEvent* event)
|
||||||
|
{
|
||||||
|
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");
|
||||||
|
return nil;
|
||||||
};
|
};
|
||||||
|
|
||||||
_glfw.ns.keyUpMonitor =
|
_glfw.ns.keyUpMonitor =
|
||||||
@@ -594,6 +884,9 @@ int _glfwPlatformInit(void)
|
|||||||
_glfw.ns.keyDownMonitor =
|
_glfw.ns.keyDownMonitor =
|
||||||
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyDown
|
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyDown
|
||||||
handler:keydown_block];
|
handler:keydown_block];
|
||||||
|
_glfw.ns.flagsChangedMonitor =
|
||||||
|
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskFlagsChanged
|
||||||
|
handler:flags_changed_block];
|
||||||
|
|
||||||
if (_glfw.hints.init.ns.chdir)
|
if (_glfw.hints.init.ns.chdir)
|
||||||
changeToResourcesDirectory();
|
changeToResourcesDirectory();
|
||||||
@@ -606,6 +899,13 @@ int _glfwPlatformInit(void)
|
|||||||
};
|
};
|
||||||
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
|
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
|
||||||
|
|
||||||
|
NSUserDefaults *apple_settings = [[NSUserDefaults alloc] initWithSuiteName:@"com.apple.symbolichotkeys"];
|
||||||
|
[apple_settings addObserver:_glfw.ns.helper
|
||||||
|
forKeyPath:@"AppleSymbolicHotKeys"
|
||||||
|
options:NSKeyValueObservingOptionNew
|
||||||
|
context:NULL];
|
||||||
|
_glfw.ns.appleSettings = apple_settings;
|
||||||
|
|
||||||
[[NSNotificationCenter defaultCenter]
|
[[NSNotificationCenter defaultCenter]
|
||||||
addObserver:_glfw.ns.helper
|
addObserver:_glfw.ns.helper
|
||||||
selector:@selector(selectedKeyboardInputSourceChanged:)
|
selector:@selector(selectedKeyboardInputSourceChanged:)
|
||||||
@@ -661,6 +961,8 @@ void _glfwPlatformTerminate(void)
|
|||||||
object:nil];
|
object:nil];
|
||||||
[[NSNotificationCenter defaultCenter]
|
[[NSNotificationCenter defaultCenter]
|
||||||
removeObserver:_glfw.ns.helper];
|
removeObserver:_glfw.ns.helper];
|
||||||
|
if (_glfw.ns.appleSettings)
|
||||||
|
[_glfw.ns.appleSettings removeObserver:_glfw.ns.helper forKeyPath:@"AppleSymbolicHotKeys"];
|
||||||
[_glfw.ns.helper release];
|
[_glfw.ns.helper release];
|
||||||
_glfw.ns.helper = nil;
|
_glfw.ns.helper = nil;
|
||||||
}
|
}
|
||||||
@@ -669,10 +971,18 @@ void _glfwPlatformTerminate(void)
|
|||||||
[NSEvent removeMonitor:_glfw.ns.keyUpMonitor];
|
[NSEvent removeMonitor:_glfw.ns.keyUpMonitor];
|
||||||
if (_glfw.ns.keyDownMonitor)
|
if (_glfw.ns.keyDownMonitor)
|
||||||
[NSEvent removeMonitor:_glfw.ns.keyDownMonitor];
|
[NSEvent removeMonitor:_glfw.ns.keyDownMonitor];
|
||||||
|
if (_glfw.ns.flagsChangedMonitor)
|
||||||
|
[NSEvent removeMonitor:_glfw.ns.flagsChangedMonitor];
|
||||||
|
|
||||||
|
if (_glfw.ns.appleSettings != nil) {
|
||||||
|
[_glfw.ns.appleSettings release];
|
||||||
|
_glfw.ns.appleSettings = nil;
|
||||||
|
}
|
||||||
|
|
||||||
free(_glfw.ns.clipboardString);
|
free(_glfw.ns.clipboardString);
|
||||||
|
|
||||||
_glfwTerminateNSGL();
|
_glfwTerminateNSGL();
|
||||||
|
if (global_shortcuts != nil) { [global_shortcuts release]; global_shortcuts = nil; }
|
||||||
|
|
||||||
} // autoreleasepool
|
} // autoreleasepool
|
||||||
}
|
}
|
||||||
|
|||||||
16
glfw/cocoa_platform.h
vendored
16
glfw/cocoa_platform.h
vendored
@@ -64,10 +64,11 @@ typedef void* CVDisplayLinkRef;
|
|||||||
#define NSOpenGLContextParameterSurfaceOpacity NSOpenGLCPSurfaceOpacity
|
#define NSOpenGLContextParameterSurfaceOpacity NSOpenGLCPSurfaceOpacity
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define debug_key(...) if (_glfw.hints.init.debugKeyboard) { fprintf(stderr, __VA_ARGS__); fflush(stderr); }
|
||||||
|
|
||||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int, unsigned long);
|
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int, unsigned long);
|
||||||
typedef bool (* GLFWapplicationshouldhandlereopenfun)(int);
|
typedef bool (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||||
typedef bool (* GLFWhandlefileopen)(const char*);
|
typedef bool (* GLFWhandleurlopen)(const char*);
|
||||||
typedef void (* GLFWapplicationwillfinishlaunchingfun)(void);
|
typedef void (* GLFWapplicationwillfinishlaunchingfun)(void);
|
||||||
typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
|
typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
|
||||||
typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);
|
typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);
|
||||||
@@ -130,6 +131,7 @@ typedef struct _GLFWwindowNS
|
|||||||
bool maximized;
|
bool maximized;
|
||||||
bool retina;
|
bool retina;
|
||||||
bool in_traditional_fullscreen;
|
bool in_traditional_fullscreen;
|
||||||
|
bool in_fullscreen_transition;
|
||||||
bool titlebar_hidden;
|
bool titlebar_hidden;
|
||||||
unsigned long pre_full_screen_style_mask;
|
unsigned long pre_full_screen_style_mask;
|
||||||
|
|
||||||
@@ -152,6 +154,8 @@ typedef struct _GLFWwindowNS
|
|||||||
// Whether a render frame has been requested for this window
|
// Whether a render frame has been requested for this window
|
||||||
bool renderFrameRequested;
|
bool renderFrameRequested;
|
||||||
GLFWcocoarenderframefun renderFrameCallback;
|
GLFWcocoarenderframefun renderFrameCallback;
|
||||||
|
// update cursor after switching desktops with Mission Control
|
||||||
|
bool delayed_cursor_update_requested;
|
||||||
} _GLFWwindowNS;
|
} _GLFWwindowNS;
|
||||||
|
|
||||||
typedef struct _GLFWDisplayLinkNS
|
typedef struct _GLFWDisplayLinkNS
|
||||||
@@ -173,8 +177,8 @@ typedef struct _GLFWlibraryNS
|
|||||||
IOHIDManagerRef hidManager;
|
IOHIDManagerRef hidManager;
|
||||||
id unicodeData;
|
id unicodeData;
|
||||||
id helper;
|
id helper;
|
||||||
id keyUpMonitor;
|
id keyUpMonitor, keyDownMonitor, flagsChangedMonitor;
|
||||||
id keyDownMonitor;
|
id appleSettings;
|
||||||
id nibObjects;
|
id nibObjects;
|
||||||
|
|
||||||
char keyName[64];
|
char keyName[64];
|
||||||
@@ -198,8 +202,8 @@ typedef struct _GLFWlibraryNS
|
|||||||
_GLFWDisplayLinkNS entries[256];
|
_GLFWDisplayLinkNS entries[256];
|
||||||
size_t count;
|
size_t count;
|
||||||
} displayLinks;
|
} displayLinks;
|
||||||
// the callback to handle file open events
|
// the callback to handle url open events
|
||||||
GLFWhandlefileopen file_open_callback;
|
GLFWhandleurlopen url_open_callback;
|
||||||
|
|
||||||
} _GLFWlibraryNS;
|
} _GLFWlibraryNS;
|
||||||
|
|
||||||
@@ -231,7 +235,6 @@ typedef struct _GLFWtimerNS
|
|||||||
|
|
||||||
} _GLFWtimerNS;
|
} _GLFWtimerNS;
|
||||||
|
|
||||||
|
|
||||||
void _glfwPollMonitorsNS(void);
|
void _glfwPollMonitorsNS(void);
|
||||||
void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired);
|
void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired);
|
||||||
void _glfwRestoreVideoModeNS(_GLFWmonitor* monitor);
|
void _glfwRestoreVideoModeNS(_GLFWmonitor* monitor);
|
||||||
@@ -248,3 +251,4 @@ void _glfwShutdownCVDisplayLink(unsigned long long, void*);
|
|||||||
void _glfwCocoaPostEmptyEvent(void);
|
void _glfwCocoaPostEmptyEvent(void);
|
||||||
void _glfw_create_cv_display_link(_GLFWDisplayLinkNS *entry);
|
void _glfw_create_cv_display_link(_GLFWDisplayLinkNS *entry);
|
||||||
_GLFWDisplayLinkNS* _glfw_create_display_link(CGDirectDisplayID);
|
_GLFWDisplayLinkNS* _glfw_create_display_link(CGDirectDisplayID);
|
||||||
|
uint32_t vk_to_unicode_key_with_current_layout(uint16_t keycode);
|
||||||
|
|||||||
@@ -32,11 +32,6 @@
|
|||||||
#include <float.h>
|
#include <float.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101300)
|
|
||||||
#define NSControlStateValueOn NSOnState
|
|
||||||
#define NSControlStateValueOff NSOffState
|
|
||||||
#define NSControlStateValueMixed NSMixedState
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static const char*
|
static const char*
|
||||||
polymorphic_string_as_utf8(id string) {
|
polymorphic_string_as_utf8(id string) {
|
||||||
@@ -259,7 +254,7 @@ is_pua_char(uint32_t ch) {
|
|||||||
return (0xE000 <= ch && ch <= 0xF8FF) || (0xF0000 <= ch && ch <= 0xFFFFF) || (0x100000 <= ch && ch <= 0x10FFFF);
|
return (0xE000 <= ch && ch <= 0xF8FF) || (0xF0000 <= ch && ch <= 0xFFFFF) || (0x100000 <= ch && ch <= 0x10FFFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t
|
uint32_t
|
||||||
vk_to_unicode_key_with_current_layout(uint16_t keycode)
|
vk_to_unicode_key_with_current_layout(uint16_t keycode)
|
||||||
{
|
{
|
||||||
UInt32 dead_key_state = 0;
|
UInt32 dead_key_state = 0;
|
||||||
@@ -513,8 +508,6 @@ translateFlags(NSUInteger flags)
|
|||||||
return mods;
|
return mods;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define debug_key(...) if (_glfw.hints.init.debugKeyboard) { fprintf(stderr, __VA_ARGS__); fflush(stderr); }
|
|
||||||
|
|
||||||
static const char*
|
static const char*
|
||||||
format_mods(int mods) {
|
format_mods(int mods) {
|
||||||
static char buf[128];
|
static char buf[128];
|
||||||
@@ -576,166 +569,11 @@ get_window_size_without_border_in_logical_pixels(_GLFWwindow *window) {
|
|||||||
return [window->ns.object contentRectForFrameRect:[window->ns.object frame]];
|
return [window->ns.object contentRectForFrameRect:[window->ns.object frame]];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translate a GLFW keycode to a Cocoa modifier flag
|
|
||||||
//
|
|
||||||
static NSUInteger
|
|
||||||
translateKeyToModifierFlag(uint32_t key)
|
|
||||||
{
|
|
||||||
switch (key)
|
|
||||||
{
|
|
||||||
case GLFW_FKEY_LEFT_SHIFT:
|
|
||||||
case GLFW_FKEY_RIGHT_SHIFT:
|
|
||||||
return NSEventModifierFlagShift;
|
|
||||||
case GLFW_FKEY_LEFT_CONTROL:
|
|
||||||
case GLFW_FKEY_RIGHT_CONTROL:
|
|
||||||
return NSEventModifierFlagControl;
|
|
||||||
case GLFW_FKEY_LEFT_ALT:
|
|
||||||
case GLFW_FKEY_RIGHT_ALT:
|
|
||||||
return NSEventModifierFlagOption;
|
|
||||||
case GLFW_FKEY_LEFT_SUPER:
|
|
||||||
case GLFW_FKEY_RIGHT_SUPER:
|
|
||||||
return NSEventModifierFlagCommand;
|
|
||||||
case GLFW_FKEY_CAPS_LOCK:
|
|
||||||
return NSEventModifierFlagCapsLock;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defines a constant for empty ranges in NSTextInputClient
|
// Defines a constant for empty ranges in NSTextInputClient
|
||||||
//
|
//
|
||||||
static const NSRange kEmptyRange = { NSNotFound, 0 };
|
static const NSRange kEmptyRange = { NSNotFound, 0 };
|
||||||
|
|
||||||
|
|
||||||
// SecureKeyboardEntryController {{{
|
|
||||||
@interface SecureKeyboardEntryController : NSObject
|
|
||||||
|
|
||||||
@property (nonatomic, readonly) BOOL isDesired;
|
|
||||||
@property (nonatomic, readonly, getter=isEnabled) BOOL enabled;
|
|
||||||
|
|
||||||
+ (instancetype)sharedInstance;
|
|
||||||
|
|
||||||
- (void)toggle;
|
|
||||||
- (void)update;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation SecureKeyboardEntryController {
|
|
||||||
int _count;
|
|
||||||
BOOL _desired;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ (instancetype)sharedInstance {
|
|
||||||
static id instance;
|
|
||||||
static dispatch_once_t onceToken;
|
|
||||||
dispatch_once(&onceToken, ^{
|
|
||||||
instance = [[self alloc] init];
|
|
||||||
});
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)init {
|
|
||||||
self = [super init];
|
|
||||||
if (self) {
|
|
||||||
_desired = false;
|
|
||||||
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
||||||
selector:@selector(applicationDidResignActive:)
|
|
||||||
name:NSApplicationDidResignActiveNotification
|
|
||||||
object:nil];
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
||||||
selector:@selector(applicationDidBecomeActive:)
|
|
||||||
name:NSApplicationDidBecomeActiveNotification
|
|
||||||
object:nil];
|
|
||||||
if ([NSApp isActive]) {
|
|
||||||
[self update];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - API
|
|
||||||
|
|
||||||
- (void)toggle {
|
|
||||||
// Set _desired to the opposite of the current state.
|
|
||||||
_desired = !_desired;
|
|
||||||
debug_key("toggle called. Setting desired to %d", _desired);
|
|
||||||
|
|
||||||
// Try to set the system's state of secure input to the desired state.
|
|
||||||
[self update];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)isEnabled {
|
|
||||||
return !!IsSecureEventInputEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)isDesired {
|
|
||||||
return _desired;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Notifications
|
|
||||||
|
|
||||||
- (void)applicationDidResignActive:(NSNotification *)notification {
|
|
||||||
(void)notification;
|
|
||||||
if (_count > 0) {
|
|
||||||
debug_key("Application resigning active.");
|
|
||||||
[self update];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)applicationDidBecomeActive:(NSNotification *)notification {
|
|
||||||
(void)notification;
|
|
||||||
if (self.isDesired) {
|
|
||||||
debug_key("Application became active.");
|
|
||||||
[self update];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - Private
|
|
||||||
|
|
||||||
- (BOOL)allowed {
|
|
||||||
return [NSApp isActive];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)update {
|
|
||||||
debug_key("Update secure keyboard entry. desired=%d active=%d\n",
|
|
||||||
(int)self.isDesired, (int)[NSApp isActive]);
|
|
||||||
const BOOL secure = self.isDesired && [self allowed];
|
|
||||||
|
|
||||||
if (secure && _count > 0) {
|
|
||||||
debug_key("Want to turn on secure input but it's already on\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!secure && _count == 0) {
|
|
||||||
debug_key("Want to turn off secure input but it's already off\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
debug_key("Before: IsSecureEventInputEnabled returns %d ", (int)self.isEnabled);
|
|
||||||
if (secure) {
|
|
||||||
OSErr err = EnableSecureEventInput();
|
|
||||||
debug_key("EnableSecureEventInput err=%d ", (int)err);
|
|
||||||
if (err) {
|
|
||||||
debug_key("EnableSecureEventInput failed with error %d ", (int)err);
|
|
||||||
} else {
|
|
||||||
_count += 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
OSErr err = DisableSecureEventInput();
|
|
||||||
debug_key("DisableSecureEventInput err=%d ", (int)err);
|
|
||||||
if (err) {
|
|
||||||
debug_key("DisableSecureEventInput failed with error %d ", (int)err);
|
|
||||||
} else {
|
|
||||||
_count -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
debug_key("After: IsSecureEventInputEnabled returns %d\n", (int)self.isEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
// }}}
|
|
||||||
|
|
||||||
// Delegate for window related notifications {{{
|
// Delegate for window related notifications {{{
|
||||||
|
|
||||||
@interface GLFWWindowDelegate : NSObject
|
@interface GLFWWindowDelegate : NSObject
|
||||||
@@ -744,6 +582,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
|||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow;
|
- (instancetype)initWithGlfwWindow:(_GLFWwindow *)initWindow;
|
||||||
|
- (void)request_delayed_cursor_update:(id)sender;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@@ -854,6 +693,9 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
|||||||
_glfwPlatformGetCursorPos(window, &x, &y);
|
_glfwPlatformGetCursorPos(window, &x, &y);
|
||||||
_glfwInputCursorPos(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
|
- (void)windowDidResignKey:(NSNotification *)notification
|
||||||
@@ -866,9 +708,11 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
|||||||
_glfwInputWindowFocus(window, false);
|
_glfwInputWindowFocus(window, false);
|
||||||
// IME is cancelled when losing the focus
|
// IME is cancelled when losing the focus
|
||||||
if ([window->ns.view hasMarkedText]) {
|
if ([window->ns.view hasMarkedText]) {
|
||||||
|
[[window->ns.view inputContext] discardMarkedText];
|
||||||
[window->ns.view unmarkText];
|
[window->ns.view unmarkText];
|
||||||
GLFWkeyevent dummy = {.action = GLFW_RELEASE, .ime_state = GLFW_IME_PREEDIT_CHANGED};
|
GLFWkeyevent dummy = {.action = GLFW_RELEASE, .ime_state = GLFW_IME_PREEDIT_CHANGED};
|
||||||
_glfwInputKeyboard(window, &dummy);
|
_glfwInputKeyboard(window, &dummy);
|
||||||
|
_glfw.ns.text[0] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -882,8 +726,54 @@ 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 // }}}
|
@end // }}}
|
||||||
|
|
||||||
|
// Text input context class for the GLFW content view {{{
|
||||||
|
|
||||||
|
@interface GLFWTextInputContext : NSTextInputContext
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation GLFWTextInputContext
|
||||||
|
- (void)doCommandBySelector:(SEL)selector
|
||||||
|
{
|
||||||
|
// interpretKeyEvents: May call insertText: or doCommandBySelector:.
|
||||||
|
// With the default macOS keybindings, pressing certain key combinations
|
||||||
|
// (e.g. Ctrl+/, Ctrl+Cmd+Down/Left/Right) will produce a beep sound.
|
||||||
|
debug_key("\n\tTextInputCtx: doCommandBySelector: (%s)\n", [NSStringFromSelector(selector) UTF8String]);
|
||||||
|
}
|
||||||
|
@end // }}}
|
||||||
|
|
||||||
// Content view class for the GLFW window {{{
|
// Content view class for the GLFW window {{{
|
||||||
|
|
||||||
@@ -891,9 +781,11 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
|||||||
{
|
{
|
||||||
_GLFWwindow* window;
|
_GLFWwindow* window;
|
||||||
NSTrackingArea* trackingArea;
|
NSTrackingArea* trackingArea;
|
||||||
|
GLFWTextInputContext* input_context;
|
||||||
NSMutableAttributedString* markedText;
|
NSMutableAttributedString* markedText;
|
||||||
NSRect markedRect;
|
NSRect markedRect;
|
||||||
bool marked_text_cleared_by_insert;
|
bool marked_text_cleared_by_insert;
|
||||||
|
int in_key_handler;
|
||||||
NSString *input_source_at_last_key_event;
|
NSString *input_source_at_last_key_event;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -911,9 +803,11 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
|||||||
{
|
{
|
||||||
window = initWindow;
|
window = initWindow;
|
||||||
trackingArea = nil;
|
trackingArea = nil;
|
||||||
|
input_context = [[GLFWTextInputContext alloc] initWithClient:self];
|
||||||
markedText = [[NSMutableAttributedString alloc] init];
|
markedText = [[NSMutableAttributedString alloc] init];
|
||||||
markedRect = NSMakeRect(0.0, 0.0, 0.0, 0.0);
|
markedRect = NSMakeRect(0.0, 0.0, 0.0, 0.0);
|
||||||
input_source_at_last_key_event = nil;
|
input_source_at_last_key_event = nil;
|
||||||
|
in_key_handler = 0;
|
||||||
|
|
||||||
[self updateTrackingAreas];
|
[self updateTrackingAreas];
|
||||||
[self registerForDraggedTypes:@[NSPasteboardTypeFileURL, NSPasteboardTypeString]];
|
[self registerForDraggedTypes:@[NSPasteboardTypeFileURL, NSPasteboardTypeString]];
|
||||||
@@ -927,6 +821,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
|||||||
[trackingArea release];
|
[trackingArea release];
|
||||||
[markedText release];
|
[markedText release];
|
||||||
if (input_source_at_last_key_event) [input_source_at_last_key_event release];
|
if (input_source_at_last_key_event) [input_source_at_last_key_event release];
|
||||||
|
[input_context release];
|
||||||
[super dealloc];
|
[super dealloc];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1045,6 +940,11 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
|||||||
|
|
||||||
window->ns.cursorWarpDeltaX = 0;
|
window->ns.cursorWarpDeltaX = 0;
|
||||||
window->ns.cursorWarpDeltaY = 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
|
- (void)rightMouseDown:(NSEvent *)event
|
||||||
@@ -1144,6 +1044,8 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
|||||||
|
|
||||||
- (void)updateTrackingAreas
|
- (void)updateTrackingAreas
|
||||||
{
|
{
|
||||||
|
if (window && [window->ns.object areCursorRectsEnabled])
|
||||||
|
[window->ns.object disableCursorRects];
|
||||||
if (trackingArea != nil)
|
if (trackingArea != nil)
|
||||||
{
|
{
|
||||||
[self removeTrackingArea:trackingArea];
|
[self removeTrackingArea:trackingArea];
|
||||||
@@ -1166,6 +1068,11 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
|||||||
[super updateTrackingAreas];
|
[super updateTrackingAreas];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSTextInputContext *)inputContext
|
||||||
|
{
|
||||||
|
return input_context;
|
||||||
|
}
|
||||||
|
|
||||||
static UInt32
|
static UInt32
|
||||||
convert_cocoa_to_carbon_modifiers(NSUInteger flags) {
|
convert_cocoa_to_carbon_modifiers(NSUInteger flags) {
|
||||||
UInt32 mods = 0;
|
UInt32 mods = 0;
|
||||||
@@ -1231,15 +1138,16 @@ is_ascii_control_char(char x) {
|
|||||||
#define UPDATE_PRE_EDIT_TEXT glfw_keyevent.text = [[markedText string] UTF8String]; glfw_keyevent.ime_state = GLFW_IME_PREEDIT_CHANGED; _glfwInputKeyboard(window, &glfw_keyevent);
|
#define UPDATE_PRE_EDIT_TEXT glfw_keyevent.text = [[markedText string] UTF8String]; glfw_keyevent.ime_state = GLFW_IME_PREEDIT_CHANGED; _glfwInputKeyboard(window, &glfw_keyevent);
|
||||||
|
|
||||||
const bool previous_has_marked_text = [self hasMarkedText];
|
const bool previous_has_marked_text = [self hasMarkedText];
|
||||||
NSTextInputContext *inpctx = [NSTextInputContext currentInputContext];
|
if (input_context && (!input_source_at_last_key_event || ![input_source_at_last_key_event isEqualToString:input_context.selectedKeyboardInputSource])) {
|
||||||
if (inpctx && (!input_source_at_last_key_event || ![input_source_at_last_key_event isEqualToString:inpctx.selectedKeyboardInputSource])) {
|
|
||||||
if (input_source_at_last_key_event) {
|
if (input_source_at_last_key_event) {
|
||||||
debug_key("Input source changed, clearing pre-edit text and resetting deadkey state\n");
|
debug_key("Input source changed, clearing pre-edit text and resetting deadkey state\n");
|
||||||
GLFWkeyevent dummy = {.action = GLFW_RELEASE, .ime_state = GLFW_IME_PREEDIT_CHANGED};
|
GLFWkeyevent dummy = {.action = GLFW_RELEASE, .ime_state = GLFW_IME_PREEDIT_CHANGED};
|
||||||
window->ns.deadKeyState = 0;
|
window->ns.deadKeyState = 0;
|
||||||
_glfwInputKeyboard(window, &dummy); // clear pre-edit text
|
_glfwInputKeyboard(window, &dummy); // clear pre-edit text
|
||||||
|
[input_source_at_last_key_event release];
|
||||||
|
input_source_at_last_key_event = nil;
|
||||||
}
|
}
|
||||||
input_source_at_last_key_event = [inpctx.selectedKeyboardInputSource retain];
|
input_source_at_last_key_event = [input_context.selectedKeyboardInputSource retain];
|
||||||
[self unmarkText];
|
[self unmarkText];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1247,7 +1155,7 @@ is_ascii_control_char(char x) {
|
|||||||
const NSUInteger flags = [event modifierFlags];
|
const NSUInteger flags = [event modifierFlags];
|
||||||
const int mods = translateFlags(flags);
|
const int mods = translateFlags(flags);
|
||||||
const uint32_t key = translateKey(keycode, true);
|
const uint32_t key = translateKey(keycode, true);
|
||||||
const bool process_text = !window->ns.textInputFilterCallback || window->ns.textInputFilterCallback(key, mods, keycode, flags) != 1;
|
const bool process_text = !_glfw.ignoreOSKeyboardProcessing && (!window->ns.textInputFilterCallback || window->ns.textInputFilterCallback(key, mods, keycode, flags) != 1);
|
||||||
_glfw.ns.text[0] = 0;
|
_glfw.ns.text[0] = 0;
|
||||||
if (keycode == 0x33 /* backspace */ || keycode == 0x35 /* escape */) [self unmarkText];
|
if (keycode == 0x33 /* backspace */ || keycode == 0x35 /* escape */) [self unmarkText];
|
||||||
GLFWkeyevent glfw_keyevent = {.key = key, .native_key = keycode, .native_key_id = keycode, .action = GLFW_PRESS, .mods = mods};
|
GLFWkeyevent glfw_keyevent = {.key = key, .native_key = keycode, .native_key_id = keycode, .action = GLFW_PRESS, .mods = mods};
|
||||||
@@ -1257,7 +1165,7 @@ is_ascii_control_char(char x) {
|
|||||||
// keyboard unicode data is not available.
|
// keyboard unicode data is not available.
|
||||||
if (process_text) {
|
if (process_text) {
|
||||||
// this will call insertText with the text for this event, if any
|
// this will call insertText with the text for this event, if any
|
||||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
[self interpretKeyEvents:@[event]];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
static UniChar text[256];
|
static UniChar text[256];
|
||||||
@@ -1284,8 +1192,10 @@ is_ascii_control_char(char x) {
|
|||||||
keycode, safe_name_for_keycode(keycode), key, format_mods(mods), char_count, window->ns.deadKeyState, event.ARepeat);
|
keycode, safe_name_for_keycode(keycode), key, format_mods(mods), char_count, window->ns.deadKeyState, event.ARepeat);
|
||||||
marked_text_cleared_by_insert = false;
|
marked_text_cleared_by_insert = false;
|
||||||
if (process_text) {
|
if (process_text) {
|
||||||
|
in_key_handler = 1;
|
||||||
// this will call insertText which will fill up _glfw.ns.text
|
// this will call insertText which will fill up _glfw.ns.text
|
||||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
[self interpretKeyEvents:@[event]];
|
||||||
|
in_key_handler = 0;
|
||||||
} else {
|
} else {
|
||||||
window->ns.deadKeyState = 0;
|
window->ns.deadKeyState = 0;
|
||||||
}
|
}
|
||||||
@@ -1314,6 +1224,7 @@ is_ascii_control_char(char x) {
|
|||||||
}
|
}
|
||||||
if (([self hasMarkedText] || previous_has_marked_text) && !_glfw.ns.text[0]) {
|
if (([self hasMarkedText] || previous_has_marked_text) && !_glfw.ns.text[0]) {
|
||||||
// do not pass keys like BACKSPACE while there's pre-edit text, let IME handle it
|
// do not pass keys like BACKSPACE while there's pre-edit text, let IME handle it
|
||||||
|
debug_key("Ignoring key press as IME is active and it generated no text\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1329,35 +1240,60 @@ is_ascii_control_char(char x) {
|
|||||||
// insertText followed by setMarkedText
|
// insertText followed by setMarkedText
|
||||||
UPDATE_PRE_EDIT_TEXT;
|
UPDATE_PRE_EDIT_TEXT;
|
||||||
}
|
}
|
||||||
#undef CLEAR_PRE_EDIT_TEXT
|
|
||||||
#undef UPDATE_PRE_EDIT_TEXT
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)flagsChanged:(NSEvent *)event
|
- (void)flagsChanged:(NSEvent *)event
|
||||||
{
|
{
|
||||||
int action = GLFW_RELEASE;
|
int action = GLFW_RELEASE;
|
||||||
const unsigned int modifierFlags =
|
const char old_first_char = _glfw.ns.text[0];
|
||||||
[event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
|
_glfw.ns.text[0] = 0;
|
||||||
const uint32_t key = translateKey([event keyCode], false);
|
const NSUInteger modifierFlags = [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
|
||||||
|
const uint32_t key = vk_code_to_functional_key_code([event keyCode]);
|
||||||
|
const unsigned int keycode = [event keyCode];
|
||||||
const int mods = translateFlags(modifierFlags);
|
const int mods = translateFlags(modifierFlags);
|
||||||
const NSUInteger keyFlag = translateKeyToModifierFlag(key);
|
const bool process_text = !_glfw.ignoreOSKeyboardProcessing && (!window->ns.textInputFilterCallback || window->ns.textInputFilterCallback(key, mods, keycode, modifierFlags) != 1);
|
||||||
|
const char *mod_name = "unknown";
|
||||||
|
|
||||||
if (keyFlag & modifierFlags)
|
switch(key) {
|
||||||
{
|
case GLFW_FKEY_CAPS_LOCK:
|
||||||
int current_action = GLFW_RELEASE;
|
mod_name = "capslock";
|
||||||
for (unsigned i = 0; i < arraysz(window->activated_keys); i++) {
|
action = modifierFlags & NSEventModifierFlagCapsLock ? GLFW_PRESS : GLFW_RELEASE; break;
|
||||||
if (window->activated_keys[i].key == key) {
|
case GLFW_FKEY_LEFT_SUPER:
|
||||||
current_action = window->activated_keys[i].action;
|
case GLFW_FKEY_RIGHT_SUPER:
|
||||||
break;
|
mod_name = "super";
|
||||||
}
|
action = modifierFlags & NSEventModifierFlagCommand ? GLFW_PRESS : GLFW_RELEASE; break;
|
||||||
}
|
case GLFW_FKEY_LEFT_CONTROL:
|
||||||
if (current_action == GLFW_PRESS)
|
case GLFW_FKEY_RIGHT_CONTROL:
|
||||||
action = GLFW_RELEASE;
|
mod_name = "ctrl";
|
||||||
else
|
action = modifierFlags & NSEventModifierFlagControl ? GLFW_PRESS : GLFW_RELEASE; break;
|
||||||
action = GLFW_PRESS;
|
case GLFW_FKEY_LEFT_ALT:
|
||||||
|
case GLFW_FKEY_RIGHT_ALT:
|
||||||
|
mod_name = "alt";
|
||||||
|
action = modifierFlags & NSEventModifierFlagOption ? GLFW_PRESS : GLFW_RELEASE; break;
|
||||||
|
case GLFW_FKEY_LEFT_SHIFT:
|
||||||
|
case GLFW_FKEY_RIGHT_SHIFT:
|
||||||
|
mod_name = "shift";
|
||||||
|
action = modifierFlags & NSEventModifierFlagShift ? GLFW_PRESS : GLFW_RELEASE; break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
GLFWkeyevent glfw_keyevent = {.key = key, .native_key = keycode, .native_key_id = keycode, .action = action, .mods = mods};
|
||||||
GLFWkeyevent glfw_keyevent = {.key = key, .native_key = [event keyCode], .native_key_id = [event keyCode], .action = action, .mods = mods};
|
debug_key("\x1b[33mflagsChanged:\x1b[m modifier: %s native_key: 0x%x (%s) glfw_key: 0x%x %s\n",
|
||||||
|
mod_name, keycode, safe_name_for_keycode(keycode), key, format_mods(mods));
|
||||||
|
marked_text_cleared_by_insert = false;
|
||||||
|
if (process_text && input_context) {
|
||||||
|
// this will call insertText which will fill up _glfw.ns.text
|
||||||
|
in_key_handler = 2;
|
||||||
|
[input_context handleEvent:event];
|
||||||
|
in_key_handler = 0;
|
||||||
|
if (marked_text_cleared_by_insert) {
|
||||||
|
debug_key("Clearing pre-edit text because insertText called from flagsChanged\n");
|
||||||
|
CLEAR_PRE_EDIT_TEXT;
|
||||||
|
if (_glfw.ns.text[0]) glfw_keyevent.text = _glfw.ns.text;
|
||||||
|
else _glfw.ns.text[0] = old_first_char;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
glfw_keyevent.ime_state = GLFW_IME_NONE;
|
||||||
_glfwInputKeyboard(window, &glfw_keyevent);
|
_glfwInputKeyboard(window, &glfw_keyevent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1374,6 +1310,9 @@ is_ascii_control_char(char x) {
|
|||||||
_glfwInputKeyboard(window, &glfw_keyevent);
|
_glfwInputKeyboard(window, &glfw_keyevent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#undef CLEAR_PRE_EDIT_TEXT
|
||||||
|
#undef UPDATE_PRE_EDIT_TEXT
|
||||||
|
|
||||||
- (void)scrollWheel:(NSEvent *)event
|
- (void)scrollWheel:(NSEvent *)event
|
||||||
{
|
{
|
||||||
double deltaX = [event scrollingDeltaX];
|
double deltaX = [event scrollingDeltaX];
|
||||||
@@ -1429,14 +1368,17 @@ is_ascii_control_char(char x) {
|
|||||||
options:options];
|
options:options];
|
||||||
if (!objs) return NO;
|
if (!objs) return NO;
|
||||||
const NSUInteger count = [objs count];
|
const NSUInteger count = [objs count];
|
||||||
|
NSMutableString *uri_list = [NSMutableString stringWithCapacity:4096]; // auto-released
|
||||||
if (count)
|
if (count)
|
||||||
{
|
{
|
||||||
for (NSUInteger i = 0; i < count; i++)
|
for (NSUInteger i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
id obj = objs[i];
|
id obj = objs[i];
|
||||||
if ([obj isKindOfClass:[NSURL class]]) {
|
if ([obj isKindOfClass:[NSURL class]]) {
|
||||||
const char *path = [obj fileSystemRepresentation];
|
NSURL *url = (NSURL*)obj;
|
||||||
_glfwInputDrop(window, "text/plain;charset=utf-8", path, strlen(path));
|
if ([uri_list length] > 0) [uri_list appendString:@("\n")];
|
||||||
|
if (url.fileURL) [uri_list appendFormat:@("file://%s"), url.fileSystemRepresentation];
|
||||||
|
else [uri_list appendString:url.absoluteString];
|
||||||
} else if ([obj isKindOfClass:[NSString class]]) {
|
} else if ([obj isKindOfClass:[NSString class]]) {
|
||||||
const char *text = [obj UTF8String];
|
const char *text = [obj UTF8String];
|
||||||
_glfwInputDrop(window, "text/plain;charset=utf-8", text, strlen(text));
|
_glfwInputDrop(window, "text/plain;charset=utf-8", text, strlen(text));
|
||||||
@@ -1446,6 +1388,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;
|
return YES;
|
||||||
}
|
}
|
||||||
@@ -1472,8 +1415,19 @@ is_ascii_control_char(char x) {
|
|||||||
selectedRange:(NSRange)selectedRange
|
selectedRange:(NSRange)selectedRange
|
||||||
replacementRange:(NSRange)replacementRange
|
replacementRange:(NSRange)replacementRange
|
||||||
{
|
{
|
||||||
debug_key("\n\tsetMarkedText: %s selectedRange: (%lu, %lu) replacementRange: (%lu, %lu)\n", polymorphic_string_as_utf8(string), selectedRange.location, selectedRange.length, replacementRange.location, replacementRange.length);
|
const char *s = polymorphic_string_as_utf8(string);
|
||||||
if (string == nil) { [self unmarkText]; return; }
|
debug_key("\n\tsetMarkedText: %s selectedRange: (%lu, %lu) replacementRange: (%lu, %lu)\n", s, selectedRange.location, selectedRange.length, replacementRange.location, replacementRange.length);
|
||||||
|
if (string == nil || !s[0]) {
|
||||||
|
bool had_marked_text = [self hasMarkedText];
|
||||||
|
[self unmarkText];
|
||||||
|
if (had_marked_text && (!in_key_handler || in_key_handler == 2)) {
|
||||||
|
debug_key("Clearing pre-edit because setMarkedText called from %s\n", in_key_handler ? "flagsChanged" : "event loop");
|
||||||
|
GLFWkeyevent glfw_keyevent = {.ime_state = GLFW_IME_PREEDIT_CHANGED};
|
||||||
|
_glfwInputKeyboard(window, &glfw_keyevent);
|
||||||
|
_glfw.ns.text[0] = 0;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if ([string isKindOfClass:[NSAttributedString class]]) {
|
if ([string isKindOfClass:[NSAttributedString class]]) {
|
||||||
if (((NSMutableAttributedString*)string).length == 0) { [self unmarkText]; return; }
|
if (((NSMutableAttributedString*)string).length == 0) { [self unmarkText]; return; }
|
||||||
[markedText release];
|
[markedText release];
|
||||||
@@ -1483,6 +1437,12 @@ is_ascii_control_char(char x) {
|
|||||||
[markedText release];
|
[markedText release];
|
||||||
markedText = [[NSMutableAttributedString alloc] initWithString:string];
|
markedText = [[NSMutableAttributedString alloc] initWithString:string];
|
||||||
}
|
}
|
||||||
|
if (!in_key_handler || in_key_handler == 2) {
|
||||||
|
debug_key("Updating IME text in kitty from setMarkedText called from %s: %s\n", in_key_handler ? "flagsChanged" : "event loop", _glfw.ns.text);
|
||||||
|
GLFWkeyevent glfw_keyevent = {.text=[[markedText string] UTF8String], .ime_state = GLFW_IME_PREEDIT_CHANGED};
|
||||||
|
_glfwInputKeyboard(window, &glfw_keyevent);
|
||||||
|
_glfw.ns.text[0] = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)unmarkText
|
- (void)unmarkText
|
||||||
@@ -1491,15 +1451,23 @@ is_ascii_control_char(char x) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
|
void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
|
||||||
[w->ns.view updateIMEStateFor: ev->type left:(CGFloat)ev->cursor.left top:(CGFloat)ev->cursor.top cellWidth:(CGFloat)ev->cursor.width cellHeight:(CGFloat)ev->cursor.height];
|
[w->ns.view updateIMEStateFor: ev->type focused:(bool)ev->focused left:(CGFloat)ev->cursor.left top:(CGFloat)ev->cursor.top cellWidth:(CGFloat)ev->cursor.width cellHeight:(CGFloat)ev->cursor.height];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)updateIMEStateFor:(GLFWIMEUpdateType)which
|
- (void)updateIMEStateFor:(GLFWIMEUpdateType)which
|
||||||
|
focused:(bool)focused
|
||||||
left:(CGFloat)left
|
left:(CGFloat)left
|
||||||
top:(CGFloat)top
|
top:(CGFloat)top
|
||||||
cellWidth:(CGFloat)cellWidth
|
cellWidth:(CGFloat)cellWidth
|
||||||
cellHeight:(CGFloat)cellHeight
|
cellHeight:(CGFloat)cellHeight
|
||||||
{
|
{
|
||||||
|
if (which == GLFW_IME_UPDATE_FOCUS && !focused && [self hasMarkedText] && window) {
|
||||||
|
[input_context discardMarkedText];
|
||||||
|
[self unmarkText];
|
||||||
|
GLFWkeyevent glfw_keyevent = {.ime_state = GLFW_IME_PREEDIT_CHANGED};
|
||||||
|
_glfwInputKeyboard(window, &glfw_keyevent);
|
||||||
|
_glfw.ns.text[0] = 0;
|
||||||
|
}
|
||||||
if (which != GLFW_IME_UPDATE_CURSOR_POSITION) return;
|
if (which != GLFW_IME_UPDATE_CURSOR_POSITION) return;
|
||||||
left /= window->ns.xscale;
|
left /= window->ns.xscale;
|
||||||
top /= window->ns.yscale;
|
top /= window->ns.yscale;
|
||||||
@@ -1543,14 +1511,28 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
|
|||||||
{
|
{
|
||||||
const char *utf8 = polymorphic_string_as_utf8(string);
|
const char *utf8 = polymorphic_string_as_utf8(string);
|
||||||
debug_key("\n\tinsertText: %s replacementRange: (%lu, %lu)\n", utf8, replacementRange.location, replacementRange.length);
|
debug_key("\n\tinsertText: %s replacementRange: (%lu, %lu)\n", utf8, replacementRange.location, replacementRange.length);
|
||||||
if ([self hasMarkedText]) {
|
if ([self hasMarkedText] && !is_ascii_control_char(utf8[0])) {
|
||||||
[self unmarkText];
|
[self unmarkText];
|
||||||
marked_text_cleared_by_insert = true;
|
marked_text_cleared_by_insert = true;
|
||||||
|
if (!in_key_handler) {
|
||||||
|
debug_key("Clearing pre-edit because insertText called from event loop\n");
|
||||||
|
GLFWkeyevent glfw_keyevent = {.ime_state = GLFW_IME_PREEDIT_CHANGED};
|
||||||
|
_glfwInputKeyboard(window, &glfw_keyevent);
|
||||||
|
_glfw.ns.text[0] = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// insertText can be called multiple times for a single key event
|
// insertText can be called multiple times for a single key event
|
||||||
char *s = _glfw.ns.text + strnlen(_glfw.ns.text, sizeof(_glfw.ns.text));
|
char *s = _glfw.ns.text + strnlen(_glfw.ns.text, sizeof(_glfw.ns.text));
|
||||||
snprintf(s, sizeof(_glfw.ns.text) - (s - _glfw.ns.text), "%s", utf8);
|
snprintf(s, sizeof(_glfw.ns.text) - (s - _glfw.ns.text), "%s", utf8);
|
||||||
_glfw.ns.text[sizeof(_glfw.ns.text) - 1] = 0;
|
_glfw.ns.text[sizeof(_glfw.ns.text) - 1] = 0;
|
||||||
|
if ((!in_key_handler || in_key_handler == 2) && _glfw.ns.text[0]) {
|
||||||
|
if (!is_ascii_control_char(_glfw.ns.text[0])) {
|
||||||
|
debug_key("Sending text to kitty from insertText called from %s: %s\n", in_key_handler ? "flagsChanged" : "event loop", _glfw.ns.text);
|
||||||
|
GLFWkeyevent glfw_keyevent = {.text=_glfw.ns.text, .ime_state=GLFW_IME_COMMIT_TEXT};
|
||||||
|
_glfwInputKeyboard(window, &glfw_keyevent);
|
||||||
|
}
|
||||||
|
_glfw.ns.text[0] = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)doCommandBySelector:(SEL)selector
|
- (void)doCommandBySelector:(SEL)selector
|
||||||
@@ -1597,19 +1579,6 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
|
|||||||
|
|
||||||
- (BOOL)validateMenuItem:(NSMenuItem *)item {
|
- (BOOL)validateMenuItem:(NSMenuItem *)item {
|
||||||
if (item.action == @selector(performMiniaturize:)) return YES;
|
if (item.action == @selector(performMiniaturize:)) return YES;
|
||||||
if (item.action == @selector(toggleSecureInput:)) {
|
|
||||||
SecureKeyboardEntryController *controller = [SecureKeyboardEntryController sharedInstance];
|
|
||||||
if (controller.isEnabled) {
|
|
||||||
if (controller.isDesired) {
|
|
||||||
item.state = NSControlStateValueOn;
|
|
||||||
} else {
|
|
||||||
item.state = NSControlStateValueMixed;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
item.state = controller.isDesired ? NSControlStateValueOn : NSControlStateValueOff;
|
|
||||||
}
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
return [super validateMenuItem:item];
|
return [super validateMenuItem:item];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1619,11 +1588,6 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
|
|||||||
else [super performMiniaturize:sender];
|
else [super performMiniaturize:sender];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)toggleSecureInput:(id)sender {
|
|
||||||
(void)sender;
|
|
||||||
[[SecureKeyboardEntryController sharedInstance] toggle];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)canBecomeKeyWindow
|
- (BOOL)canBecomeKeyWindow
|
||||||
{
|
{
|
||||||
// Required for NSWindowStyleMaskBorderless windows
|
// Required for NSWindowStyleMaskBorderless windows
|
||||||
@@ -1637,8 +1601,11 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
|
|||||||
|
|
||||||
- (void)toggleFullScreen:(nullable id)sender
|
- (void)toggleFullScreen:(nullable id)sender
|
||||||
{
|
{
|
||||||
if (glfw_window && glfw_window->ns.toggleFullscreenCallback && glfw_window->ns.toggleFullscreenCallback((GLFWwindow*)glfw_window) == 1)
|
if (glfw_window) {
|
||||||
return;
|
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.
|
// When resizeIncrements is set, Cocoa cannot restore the original window size after returning from fullscreen.
|
||||||
const NSSize original = [self resizeIncrements];
|
const NSSize original = [self resizeIncrements];
|
||||||
[self setResizeIncrements:NSMakeSize(1.0, 1.0)];
|
[self setResizeIncrements:NSMakeSize(1.0, 1.0)];
|
||||||
@@ -2449,7 +2416,7 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor UNUSED)
|
|||||||
bool _glfwPlatformIsFullscreen(_GLFWwindow* w, unsigned int flags) {
|
bool _glfwPlatformIsFullscreen(_GLFWwindow* w, unsigned int flags) {
|
||||||
NSWindow *window = w->ns.object;
|
NSWindow *window = w->ns.object;
|
||||||
bool traditional = !(flags & 1);
|
bool traditional = !(flags & 1);
|
||||||
if (traditional) { if(@available(macOS 11.0, *)) return w->ns.in_traditional_fullscreen; }
|
if (traditional) { if(@available(macOS 10.15.7, *)) return w->ns.in_traditional_fullscreen; }
|
||||||
NSWindowStyleMask sm = [window styleMask];
|
NSWindowStyleMask sm = [window styleMask];
|
||||||
return sm & NSWindowStyleMaskFullScreen;
|
return sm & NSWindowStyleMaskFullScreen;
|
||||||
}
|
}
|
||||||
@@ -2460,8 +2427,9 @@ bool _glfwPlatformToggleFullscreen(_GLFWwindow* w, unsigned int flags) {
|
|||||||
bool traditional = !(flags & 1);
|
bool traditional = !(flags & 1);
|
||||||
NSWindowStyleMask sm = [window styleMask];
|
NSWindowStyleMask sm = [window styleMask];
|
||||||
if (traditional) {
|
if (traditional) {
|
||||||
if (@available(macOS 11.0, *)) {
|
if (@available(macOS 10.15.7, *)) {
|
||||||
// As of Big Turd NSWindowStyleMaskFullScreen is no longer useable
|
// As of Big Turd NSWindowStyleMaskFullScreen is no longer useable
|
||||||
|
// Also no longer compatible after a minor release of macOS 10.15.7
|
||||||
if (!w->ns.in_traditional_fullscreen) {
|
if (!w->ns.in_traditional_fullscreen) {
|
||||||
w->ns.pre_full_screen_style_mask = sm;
|
w->ns.pre_full_screen_style_mask = sm;
|
||||||
[window setStyleMask: NSWindowStyleMaskBorderless];
|
[window setStyleMask: NSWindowStyleMaskBorderless];
|
||||||
@@ -2724,10 +2692,10 @@ GLFWAPI GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow *hand
|
|||||||
return previous;
|
return previous;
|
||||||
}
|
}
|
||||||
|
|
||||||
GLFWAPI GLFWhandlefileopen glfwSetCocoaFileOpenCallback(GLFWhandlefileopen callback) {
|
GLFWAPI GLFWhandleurlopen glfwSetCocoaURLOpenCallback(GLFWhandleurlopen callback) {
|
||||||
_GLFW_REQUIRE_INIT_OR_RETURN(nil);
|
_GLFW_REQUIRE_INIT_OR_RETURN(nil);
|
||||||
GLFWhandlefileopen prev = _glfw.ns.file_open_callback;
|
GLFWhandleurlopen prev = _glfw.ns.url_open_callback;
|
||||||
_glfw.ns.file_open_callback = callback;
|
_glfw.ns.url_open_callback = callback;
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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
|
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) {
|
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);
|
DBusMessage *msg = dbus_message_new_method_call(node, path, interface, method);
|
||||||
if (!msg) return false;
|
if (!msg) return false;
|
||||||
bool retval = false;
|
bool retval = false;
|
||||||
|
|||||||
18
glfw/glfw.py
18
glfw/glfw.py
@@ -67,7 +67,7 @@ class Env:
|
|||||||
|
|
||||||
def wayland_protocol_file_name(base: str, ext: str = 'c') -> str:
|
def wayland_protocol_file_name(base: str, ext: str = 'c') -> str:
|
||||||
base = os.path.basename(base).rpartition('.')[0]
|
base = os.path.basename(base).rpartition('.')[0]
|
||||||
return 'wayland-{}-client-protocol.{}'.format(base, ext)
|
return f'wayland-{base}-client-protocol.{ext}'
|
||||||
|
|
||||||
|
|
||||||
def init_env(
|
def init_env(
|
||||||
@@ -80,7 +80,7 @@ def init_env(
|
|||||||
) -> Env:
|
) -> Env:
|
||||||
ans = env.copy()
|
ans = env.copy()
|
||||||
ans.cflags.append('-fPIC')
|
ans.cflags.append('-fPIC')
|
||||||
ans.cppflags.append('-D_GLFW_' + module.upper())
|
ans.cppflags.append(f'-D_GLFW_{module.upper()}')
|
||||||
ans.cppflags.append('-D_GLFW_BUILD_DLL')
|
ans.cppflags.append('-D_GLFW_BUILD_DLL')
|
||||||
|
|
||||||
with open(os.path.join(base, 'source-info.json')) as f:
|
with open(os.path.join(base, 'source-info.json')) as f:
|
||||||
@@ -111,7 +111,7 @@ def init_env(
|
|||||||
|
|
||||||
elif module == 'cocoa':
|
elif module == 'cocoa':
|
||||||
ans.cppflags.append('-DGL_SILENCE_DEPRECATION')
|
ans.cppflags.append('-DGL_SILENCE_DEPRECATION')
|
||||||
for f_ in 'Cocoa Carbon IOKit CoreFoundation CoreVideo'.split():
|
for f_ in 'Cocoa IOKit CoreFoundation CoreVideo'.split():
|
||||||
ans.ldpaths.extend(('-framework', f_))
|
ans.ldpaths.extend(('-framework', f_))
|
||||||
|
|
||||||
elif module == 'wayland':
|
elif module == 'wayland':
|
||||||
@@ -150,14 +150,14 @@ def build_wayland_protocols(
|
|||||||
for protocol in env.wayland_protocols:
|
for protocol in env.wayland_protocols:
|
||||||
src = os.path.join(env.wayland_packagedir, protocol)
|
src = os.path.join(env.wayland_packagedir, protocol)
|
||||||
if not os.path.exists(src):
|
if not os.path.exists(src):
|
||||||
raise SystemExit('The wayland-protocols package on your system is missing the {} protocol definition file'.format(protocol))
|
raise SystemExit(f'The wayland-protocols package on your system is missing the {protocol} protocol definition file')
|
||||||
for ext in 'hc':
|
for ext in 'hc':
|
||||||
dest = wayland_protocol_file_name(src, ext)
|
dest = wayland_protocol_file_name(src, ext)
|
||||||
dest = os.path.join(dest_dir, dest)
|
dest = os.path.join(dest_dir, dest)
|
||||||
if newer(dest, src):
|
if newer(dest, src):
|
||||||
q = 'client-header' if ext == 'h' else env.wayland_scanner_code
|
q = 'client-header' if ext == 'h' else env.wayland_scanner_code
|
||||||
items.append(Command(
|
items.append(Command(
|
||||||
'Generating {} ...'.format(emphasis(os.path.basename(dest))),
|
f'Generating {emphasis(os.path.basename(dest))} ...',
|
||||||
[env.wayland_scanner, q, src, dest], lambda: True))
|
[env.wayland_scanner, q, src, dest], lambda: True))
|
||||||
if items:
|
if items:
|
||||||
parallel_run(items)
|
parallel_run(items)
|
||||||
@@ -174,7 +174,7 @@ class Arg:
|
|||||||
self.type = self.type + '*'
|
self.type = self.type + '*'
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return 'Arg({}, {})'.format(self.type, self.name)
|
return f'Arg({self.type}, {self.name})'
|
||||||
|
|
||||||
|
|
||||||
class Function:
|
class Function:
|
||||||
@@ -233,7 +233,7 @@ def generate_wrappers(glfw_header: str) -> None:
|
|||||||
void* glfwGetNSGLContext(GLFWwindow *window)
|
void* glfwGetNSGLContext(GLFWwindow *window)
|
||||||
uint32_t glfwGetCocoaMonitor(GLFWmonitor* monitor)
|
uint32_t glfwGetCocoaMonitor(GLFWmonitor* monitor)
|
||||||
GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow* window, GLFWcocoatextinputfilterfun callback)
|
GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow* window, GLFWcocoatextinputfilterfun callback)
|
||||||
GLFWhandlefileopen glfwSetCocoaFileOpenCallback(GLFWhandlefileopen callback)
|
GLFWhandleurlopen glfwSetCocoaURLOpenCallback(GLFWhandleurlopen callback)
|
||||||
GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *window, GLFWcocoatogglefullscreenfun callback)
|
GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *window, GLFWcocoatogglefullscreenfun callback)
|
||||||
GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback)
|
GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback)
|
||||||
GLFWapplicationwillfinishlaunchingfun glfwSetApplicationWillFinishLaunching(GLFWapplicationwillfinishlaunchingfun callback)
|
GLFWapplicationwillfinishlaunchingfun glfwSetApplicationWillFinishLaunching(GLFWapplicationwillfinishlaunchingfun callback)
|
||||||
@@ -277,7 +277,7 @@ const char *action_text, int32_t timeout, GLFWDBusnotificationcreatedfun callbac
|
|||||||
|
|
||||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int,unsigned long);
|
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int,unsigned long);
|
||||||
typedef bool (* GLFWapplicationshouldhandlereopenfun)(int);
|
typedef bool (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||||
typedef bool (* GLFWhandlefileopen)(const char*);
|
typedef bool (* GLFWhandleurlopen)(const char*);
|
||||||
typedef void (* GLFWapplicationwillfinishlaunchingfun)(void);
|
typedef void (* GLFWapplicationwillfinishlaunchingfun)(void);
|
||||||
typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
|
typedef bool (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
|
||||||
typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);
|
typedef void (* GLFWcocoarenderframefun)(GLFWwindow*);
|
||||||
@@ -292,6 +292,8 @@ const char* load_glfw(const char* path);
|
|||||||
f.write(header)
|
f.write(header)
|
||||||
|
|
||||||
code = '''
|
code = '''
|
||||||
|
// generated by glfw.py DO NOT edit
|
||||||
|
|
||||||
#define GFW_EXTERN
|
#define GFW_EXTERN
|
||||||
#include "data-types.h"
|
#include "data-types.h"
|
||||||
#include "glfw-wrapper.h"
|
#include "glfw-wrapper.h"
|
||||||
|
|||||||
3
glfw/glfw3.h
vendored
3
glfw/glfw3.h
vendored
@@ -4119,6 +4119,9 @@ GLFWAPI GLFWwindowcontentscalefun glfwSetWindowContentScaleCallback(GLFWwindow*
|
|||||||
*/
|
*/
|
||||||
GLFWAPI void glfwPostEmptyEvent(void);
|
GLFWAPI void glfwPostEmptyEvent(void);
|
||||||
|
|
||||||
|
GLFWAPI bool glfwGetIgnoreOSKeyboardProcessing(void);
|
||||||
|
GLFWAPI void glfwSetIgnoreOSKeyboardProcessing(bool enabled);
|
||||||
|
|
||||||
/*! @brief Returns the value of an input option for the specified window.
|
/*! @brief Returns the value of an input option for the specified window.
|
||||||
*
|
*
|
||||||
* This function returns the value of an input option for the specified window.
|
* This function returns the value of an input option for the specified window.
|
||||||
|
|||||||
12
glfw/ibus_glfw.c
vendored
12
glfw/ibus_glfw.c
vendored
@@ -24,6 +24,17 @@
|
|||||||
//
|
//
|
||||||
//========================================================================
|
//========================================================================
|
||||||
|
|
||||||
|
/* To test under X11 start IBUS as:
|
||||||
|
* ibus-daemon -drxR
|
||||||
|
* Setup the input sources you want with:
|
||||||
|
* ibus-setup
|
||||||
|
* Switch to the input source you want to test with:
|
||||||
|
* ibus engine name
|
||||||
|
* You can list available engines with:
|
||||||
|
* ibus list-engine
|
||||||
|
* Then run kitty as:
|
||||||
|
* GLFW_IM_MODULE=ibus kitty
|
||||||
|
*/
|
||||||
#define _GNU_SOURCE
|
#define _GNU_SOURCE
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
@@ -226,6 +237,7 @@ message_handler(DBusConnection *conn UNUSED, DBusMessage *msg, void *user_data)
|
|||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
debug("IBUS: HidePreeditText\n");
|
debug("IBUS: HidePreeditText\n");
|
||||||
|
send_text("", GLFW_IME_PREEDIT_CHANGED);
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
debug("IBUS: ShowPreeditText\n");
|
debug("IBUS: ShowPreeditText\n");
|
||||||
|
|||||||
1
glfw/init.c
vendored
1
glfw/init.c
vendored
@@ -227,6 +227,7 @@ GLFWAPI int glfwInit(monotonic_t start_time)
|
|||||||
|
|
||||||
memset(&_glfw, 0, sizeof(_glfw));
|
memset(&_glfw, 0, sizeof(_glfw));
|
||||||
_glfw.hints.init = _glfwInitHints;
|
_glfw.hints.init = _glfwInitHints;
|
||||||
|
_glfw.ignoreOSKeyboardProcessing = false;
|
||||||
|
|
||||||
if (!_glfwPlatformInit())
|
if (!_glfwPlatformInit())
|
||||||
{
|
{
|
||||||
|
|||||||
8
glfw/input.c
vendored
8
glfw/input.c
vendored
@@ -667,6 +667,14 @@ void _glfwCenterCursorInContentArea(_GLFWwindow* window)
|
|||||||
////// GLFW public API //////
|
////// GLFW public API //////
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
GLFWAPI bool glfwGetIgnoreOSKeyboardProcessing(void) {
|
||||||
|
return _glfw.ignoreOSKeyboardProcessing;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLFWAPI void glfwSetIgnoreOSKeyboardProcessing(bool enabled) {
|
||||||
|
_glfw.ignoreOSKeyboardProcessing = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
GLFWAPI int glfwGetInputMode(GLFWwindow* handle, int mode)
|
GLFWAPI int glfwGetInputMode(GLFWwindow* handle, int mode)
|
||||||
{
|
{
|
||||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||||
|
|||||||
2
glfw/internal.h
vendored
2
glfw/internal.h
vendored
@@ -584,6 +584,8 @@ struct _GLFWlibrary
|
|||||||
_GLFWtls contextSlot;
|
_GLFWtls contextSlot;
|
||||||
_GLFWmutex errorLock;
|
_GLFWmutex errorLock;
|
||||||
|
|
||||||
|
bool ignoreOSKeyboardProcessing;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
bool available;
|
bool available;
|
||||||
void* handle;
|
void* handle;
|
||||||
|
|||||||
73
glfw/wl_init.c
vendored
73
glfw/wl_init.c
vendored
@@ -331,20 +331,83 @@ static void pointerHandleAxis(void* data UNUSED,
|
|||||||
assert(axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL ||
|
assert(axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL ||
|
||||||
axis == WL_POINTER_AXIS_VERTICAL_SCROLL);
|
axis == WL_POINTER_AXIS_VERTICAL_SCROLL);
|
||||||
|
|
||||||
if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL)
|
if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) {
|
||||||
x = -wl_fixed_to_double(value);
|
if (window->wl.axis_discrete_count.x) {
|
||||||
else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
|
window->wl.axis_discrete_count.x--;
|
||||||
y = -wl_fixed_to_double(value);
|
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);
|
_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 = {
|
static const struct wl_pointer_listener pointerListener = {
|
||||||
pointerHandleEnter,
|
pointerHandleEnter,
|
||||||
pointerHandleLeave,
|
pointerHandleLeave,
|
||||||
pointerHandleMotion,
|
pointerHandleMotion,
|
||||||
pointerHandleButton,
|
pointerHandleButton,
|
||||||
pointerHandleAxis,
|
pointerHandleAxis,
|
||||||
|
pointerHandleFrame,
|
||||||
|
pointerHandleAxisSource,
|
||||||
|
pointerHandleAxisStop,
|
||||||
|
pointerHandleAxisDiscrete,
|
||||||
};
|
};
|
||||||
|
|
||||||
static void keyboardHandleKeymap(void* data UNUSED,
|
static void keyboardHandleKeymap(void* data UNUSED,
|
||||||
@@ -571,7 +634,7 @@ static void registryHandleGlobal(void* data UNUSED,
|
|||||||
{
|
{
|
||||||
if (!_glfw.wl.seat)
|
if (!_glfw.wl.seat)
|
||||||
{
|
{
|
||||||
_glfw.wl.seatVersion = min(4, version);
|
_glfw.wl.seatVersion = min(5, version);
|
||||||
_glfw.wl.seat =
|
_glfw.wl.seat =
|
||||||
wl_registry_bind(registry, name, &wl_seat_interface,
|
wl_registry_bind(registry, name, &wl_seat_interface,
|
||||||
_glfw.wl.seatVersion);
|
_glfw.wl.seatVersion);
|
||||||
|
|||||||
5
glfw/wl_platform.h
vendored
5
glfw/wl_platform.h
vendored
@@ -212,6 +212,11 @@ typedef struct _GLFWwindowWayland
|
|||||||
|
|
||||||
uint32_t toplevel_states;
|
uint32_t toplevel_states;
|
||||||
bool maximize_on_first_show;
|
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;
|
||||||
|
|
||||||
} _GLFWwindowWayland;
|
} _GLFWwindowWayland;
|
||||||
|
|
||||||
|
|||||||
4
glfw/xkb_glfw.c
vendored
4
glfw/xkb_glfw.c
vendored
@@ -33,7 +33,7 @@
|
|||||||
#include <X11/XKBlib.h>
|
#include <X11/XKBlib.h>
|
||||||
#endif
|
#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
|
#ifdef XKB_HAS_NO_UTF32
|
||||||
#include "xkb-compat-shim.h"
|
#include "xkb-compat-shim.h"
|
||||||
@@ -827,7 +827,7 @@ glfw_xkb_key_from_ime(_GLFWIBUSKeyEvent *ev, bool handled_by_ime, bool failed) {
|
|||||||
xkb_keycode_t prev_handled_press = last_handled_press_keycode;
|
xkb_keycode_t prev_handled_press = last_handled_press_keycode;
|
||||||
last_handled_press_keycode = 0;
|
last_handled_press_keycode = 0;
|
||||||
bool is_release = ev->glfw_ev.action == GLFW_RELEASE;
|
bool is_release = ev->glfw_ev.action == GLFW_RELEASE;
|
||||||
debug("From IBUS: native_key: 0x%x name: %s is_release: %d\n", ev->glfw_ev.native_key, glfw_xkb_keysym_name(ev->glfw_ev.key), is_release);
|
debug("From IBUS: native_key: 0x%x name: %s is_release: %d handled_by_ime: %d\n", ev->glfw_ev.native_key, glfw_xkb_keysym_name(ev->glfw_ev.key), is_release, handled_by_ime);
|
||||||
if (window && !handled_by_ime && !(is_release && ev->glfw_ev.native_key == (int) prev_handled_press)) {
|
if (window && !handled_by_ime && !(is_release && ev->glfw_ev.native_key == (int) prev_handled_press)) {
|
||||||
debug("↳ to application: glfw_keycode: 0x%x (%s) keysym: 0x%x (%s) action: %s %s text: %s\n",
|
debug("↳ to application: glfw_keycode: 0x%x (%s) keysym: 0x%x (%s) action: %s %s text: %s\n",
|
||||||
ev->glfw_ev.native_key, _glfwGetKeyName(ev->glfw_ev.native_key), ev->glfw_ev.key, glfw_xkb_keysym_name(ev->glfw_ev.key),
|
ev->glfw_ev.native_key, _glfwGetKeyName(ev->glfw_ev.native_key), ev->glfw_ev.key, glfw_xkb_keysym_name(ev->glfw_ev.key),
|
||||||
|
|||||||
@@ -4,13 +4,15 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from typing import TYPE_CHECKING, Dict, List, NamedTuple, Optional, Tuple
|
from typing import (
|
||||||
|
TYPE_CHECKING, Callable, Dict, List, NamedTuple, Optional, Tuple
|
||||||
|
)
|
||||||
|
|
||||||
from kitty.cli import parse_args
|
from kitty.cli import parse_args
|
||||||
from kitty.cli_stub import AskCLIOptions
|
from kitty.cli_stub import AskCLIOptions
|
||||||
from kitty.constants import cache_dir
|
from kitty.constants import cache_dir
|
||||||
from kitty.fast_data_types import wcswidth
|
from kitty.fast_data_types import truncate_point_for_length, wcswidth
|
||||||
from kitty.typing import BossType, TypedDict
|
from kitty.typing import BossType, KeyEventType, TypedDict
|
||||||
from kitty.utils import ScreenSize
|
from kitty.utils import ScreenSize
|
||||||
|
|
||||||
from ..tui.handler import Handler, result_handler
|
from ..tui.handler import Handler, result_handler
|
||||||
@@ -95,6 +97,12 @@ 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
|
an optional color specification after the letter to indicate what color it should
|
||||||
be.
|
be.
|
||||||
For example: y:Yes and n;red:No
|
For example: y:Yes and 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.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
@@ -107,6 +115,7 @@ class Choice(NamedTuple):
|
|||||||
text: str
|
text: str
|
||||||
idx: int
|
idx: int
|
||||||
color: str
|
color: str
|
||||||
|
letter: str
|
||||||
|
|
||||||
|
|
||||||
class Range(NamedTuple):
|
class Range(NamedTuple):
|
||||||
@@ -118,27 +127,44 @@ class Range(NamedTuple):
|
|||||||
return y == self.y and self.start <= x <= self.end
|
return y == self.y and self.start <= x <= self.end
|
||||||
|
|
||||||
|
|
||||||
|
def truncate_at_space(text: str, width: int) -> Tuple[str, str]:
|
||||||
|
p = truncate_point_for_length(text, width)
|
||||||
|
if p < len(text):
|
||||||
|
i = text.rfind(' ', 0, p + 1)
|
||||||
|
if i > 0 and p - i < 12:
|
||||||
|
p = i + 1
|
||||||
|
return text[:p], text[p:]
|
||||||
|
|
||||||
|
|
||||||
|
def extra_for(width: int, screen_width: int) -> int:
|
||||||
|
return max(0, screen_width - width) // 2 + 1
|
||||||
|
|
||||||
|
|
||||||
class Choose(Handler):
|
class Choose(Handler):
|
||||||
mouse_tracking = MouseTracking.buttons_only
|
mouse_tracking = MouseTracking.buttons_only
|
||||||
|
|
||||||
def __init__(self, cli_opts: AskCLIOptions) -> None:
|
def __init__(self, cli_opts: AskCLIOptions) -> None:
|
||||||
self.cli_opts = cli_opts
|
self.cli_opts = cli_opts
|
||||||
self.response = 'n' if cli_opts.type == 'yesno' else ''
|
|
||||||
self.allowed = frozenset('yn')
|
|
||||||
self.choices: Dict[str, Choice] = {}
|
self.choices: Dict[str, Choice] = {}
|
||||||
self.clickable_ranges: Dict[str, Range] = {}
|
self.clickable_ranges: Dict[str, List[Range]] = {}
|
||||||
if cli_opts.type != 'yesno':
|
if cli_opts.type == 'yesno':
|
||||||
|
self.allowed = frozenset('yn')
|
||||||
|
else:
|
||||||
allowed = []
|
allowed = []
|
||||||
for choice in cli_opts.choices:
|
for choice in cli_opts.choices:
|
||||||
color = 'green'
|
|
||||||
letter, text = choice.split(':', maxsplit=1)
|
letter, text = choice.split(':', maxsplit=1)
|
||||||
|
color = ''
|
||||||
if ';' in letter:
|
if ';' in letter:
|
||||||
letter, color = letter.split(';', maxsplit=1)
|
letter, color = letter.split(';', maxsplit=1)
|
||||||
letter = letter.lower()
|
letter = letter.lower()
|
||||||
idx = text.lower().index(letter)
|
idx = text.lower().index(letter)
|
||||||
allowed.append(letter)
|
allowed.append(letter)
|
||||||
self.choices[letter] = Choice(text, idx, color)
|
self.choices[letter] = Choice(text, idx, color, letter)
|
||||||
self.allowed = frozenset(allowed)
|
self.allowed = frozenset(allowed)
|
||||||
|
self.response = ''
|
||||||
|
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]
|
||||||
|
|
||||||
def initialize(self) -> None:
|
def initialize(self) -> None:
|
||||||
self.cmd.set_cursor_visible(False)
|
self.cmd.set_cursor_visible(False)
|
||||||
@@ -147,52 +173,164 @@ class Choose(Handler):
|
|||||||
def finalize(self) -> None:
|
def finalize(self) -> None:
|
||||||
self.cmd.set_cursor_visible(True)
|
self.cmd.set_cursor_visible(True)
|
||||||
|
|
||||||
|
def draw_long_text(self, text: str) -> int:
|
||||||
|
y = 0
|
||||||
|
width = self.screen_size.cols - 2
|
||||||
|
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
|
||||||
|
|
||||||
@Handler.atomic_update
|
@Handler.atomic_update
|
||||||
def draw_screen(self) -> None:
|
def draw_screen(self) -> None:
|
||||||
self.cmd.clear_screen()
|
self.cmd.clear_screen()
|
||||||
y = 1
|
y = max(0, self.screen_size.rows // 2 - 2)
|
||||||
|
self.print(end='\r\n'*y)
|
||||||
if self.cli_opts.message:
|
if self.cli_opts.message:
|
||||||
self.cmd.styled(self.cli_opts.message, bold=True)
|
for line in self.cli_opts.message.splitlines():
|
||||||
y += wcswidth(self.cli_opts.message) // self.screen_size.cols
|
y += self.draw_long_text(line)
|
||||||
self.print()
|
if self.screen_size.rows > 2:
|
||||||
|
self.print()
|
||||||
|
y += 1
|
||||||
if self.cli_opts.type == 'yesno':
|
if self.cli_opts.type == 'yesno':
|
||||||
self.draw_yesno(y)
|
self.draw_yesno(y)
|
||||||
else:
|
else:
|
||||||
self.draw_choice(y)
|
self.draw_choice(y)
|
||||||
|
|
||||||
def draw_choice(self, y: int) -> None:
|
def draw_choice_boxes(self, y: int, *choices: Choice) -> None:
|
||||||
x = 0
|
|
||||||
self.clickable_ranges.clear()
|
self.clickable_ranges.clear()
|
||||||
|
width = self.screen_size.cols - 2
|
||||||
|
current_line_length = 0
|
||||||
|
current_line: List[Tuple[str, str]] = []
|
||||||
|
lines: List[List[Tuple[str, str]]] = []
|
||||||
|
sep, sep_sz = ' ', 2
|
||||||
|
|
||||||
|
for choice in choices:
|
||||||
|
self.clickable_ranges[choice.letter] = []
|
||||||
|
text = ' ' + choice.text[:choice.idx]
|
||||||
|
text += styled(choice.text[choice.idx], fg=choice.color or 'green')
|
||||||
|
text += choice.text[choice.idx + 1:] + ' '
|
||||||
|
sz = wcswidth(text)
|
||||||
|
if sz + sep_sz + current_line_length > width:
|
||||||
|
lines.append(current_line)
|
||||||
|
current_line = []
|
||||||
|
current_line_length = 0
|
||||||
|
current_line.append((choice.letter, text))
|
||||||
|
current_line_length += sz
|
||||||
|
if current_line:
|
||||||
|
lines.append(current_line)
|
||||||
|
|
||||||
|
def top(text: str) -> str:
|
||||||
|
return '╭' + '─' * wcswidth(text) + '╮'
|
||||||
|
|
||||||
|
def middle(text: str) -> str:
|
||||||
|
return f'│{text}│'
|
||||||
|
|
||||||
|
def bottom(text: str) -> str:
|
||||||
|
return '╰' + '─' * wcswidth(text) + '╯'
|
||||||
|
|
||||||
|
def highlight(text: str, only_edges: bool = False) -> str:
|
||||||
|
if only_edges:
|
||||||
|
return styled(text[0], fg='yellow') + text[1:-1] + styled(text[-1], fg='yellow')
|
||||||
|
return styled(text, fg='yellow')
|
||||||
|
|
||||||
|
def print_line(add_borders: Callable[[str], str], *items: Tuple[str, str], is_last: bool = False) -> None:
|
||||||
|
nonlocal y
|
||||||
|
texts = []
|
||||||
|
positions = []
|
||||||
|
x = 0
|
||||||
|
for (letter, text) in items:
|
||||||
|
positions.append((letter, x, wcswidth(text) + 2))
|
||||||
|
text = add_borders(text)
|
||||||
|
if letter == self.response_on_accept:
|
||||||
|
text = highlight(text, only_edges=add_borders is middle)
|
||||||
|
text += sep
|
||||||
|
x += wcswidth(text)
|
||||||
|
texts.append(text)
|
||||||
|
line = ''.join(texts).rstrip()
|
||||||
|
offset = extra_for(wcswidth(line), width)
|
||||||
|
for (letter, x, sz) in positions:
|
||||||
|
x += offset
|
||||||
|
self.clickable_ranges[letter].append(Range(x, x + sz - 1, y))
|
||||||
|
self.print(' ' * offset, line, sep='', end='' if is_last else '\r\n')
|
||||||
|
y += 1
|
||||||
|
|
||||||
|
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])
|
||||||
|
|
||||||
|
def draw_choice(self, y: int) -> None:
|
||||||
|
if y + 3 <= self.screen_size.rows:
|
||||||
|
self.draw_choice_boxes(y, *self.choices.values())
|
||||||
|
return
|
||||||
|
self.clickable_ranges.clear()
|
||||||
|
current_line = ''
|
||||||
|
current_ranges: Dict[str, int] = {}
|
||||||
|
width = self.screen_size.cols - 2
|
||||||
|
|
||||||
|
def commit_line(end: str = '\r\n') -> None:
|
||||||
|
nonlocal current_line, y
|
||||||
|
x = extra_for(wcswidth(current_line), width)
|
||||||
|
self.print(' ' * x + current_line, end=end)
|
||||||
|
for letter, sz in current_ranges.items():
|
||||||
|
self.clickable_ranges[letter] = [Range(x, x + sz - 3, y)]
|
||||||
|
x += sz
|
||||||
|
current_ranges.clear()
|
||||||
|
y += 1
|
||||||
|
current_line = ''
|
||||||
|
|
||||||
for letter, choice in self.choices.items():
|
for letter, choice in self.choices.items():
|
||||||
text = choice.text[:choice.idx]
|
text = choice.text[:choice.idx]
|
||||||
text += styled(choice.text[choice.idx], fg=choice.color)
|
text += styled(choice.text[choice.idx], fg=choice.color or 'green', underline='straight' if letter == self.response_on_accept else None)
|
||||||
text += choice.text[choice.idx + 1:]
|
text += choice.text[choice.idx + 1:]
|
||||||
text += ' '
|
text += ' '
|
||||||
sz = wcswidth(text)
|
sz = wcswidth(text)
|
||||||
if sz + x >= self.screen_size.cols:
|
if sz + wcswidth(current_line) >= width:
|
||||||
y += 1
|
commit_line()
|
||||||
x = 0
|
current_line += text
|
||||||
self.print()
|
current_ranges[letter] = sz
|
||||||
self.clickable_ranges[letter] = Range(x, x + sz - 1, y)
|
if current_line:
|
||||||
x += sz
|
commit_line(end='')
|
||||||
self.print(text, end='')
|
|
||||||
|
|
||||||
def draw_yesno(self, y: int) -> None:
|
def draw_yesno(self, y: int) -> None:
|
||||||
self.clickable_ranges = {'y': Range(2, 4, y), 'n': Range(8, 9, y)}
|
yes = styled('Y', fg='green') + 'es'
|
||||||
self.print(' ', styled('Y', fg='green') + 'es', ' ', styled('N', fg='red') + 'o')
|
no = styled('N', fg='red') + 'o'
|
||||||
|
if y + 3 <= self.screen_size.rows:
|
||||||
|
self.draw_choice_boxes(y, Choice('Yes', 0, 'green', 'y'), Choice('No', 0, 'red', 'n'))
|
||||||
|
return
|
||||||
|
sep = ' ' * 3
|
||||||
|
text = yes + sep + no
|
||||||
|
w = wcswidth(text)
|
||||||
|
x = extra_for(w, self.screen_size.cols - 2)
|
||||||
|
nx = x + wcswidth(yes) + len(sep)
|
||||||
|
self.clickable_ranges = {'y': [Range(x, x + wcswidth(yes) - 1, y)], 'n': [Range(nx, nx + wcswidth(no) - 1, y)]}
|
||||||
|
self.print(' ' * x + text, end='')
|
||||||
|
|
||||||
def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
|
def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
|
||||||
text = text.lower()
|
text = text.lower()
|
||||||
if text in self.allowed:
|
if text in self.allowed:
|
||||||
self.response = text
|
self.response = text
|
||||||
self.quit_loop(0)
|
self.quit_loop(0)
|
||||||
|
elif self.cli_opts.type == 'yesno':
|
||||||
|
self.on_interrupt()
|
||||||
|
|
||||||
|
def on_key(self, key_event: KeyEventType) -> None:
|
||||||
|
if key_event.matches('esc'):
|
||||||
|
self.on_interrupt()
|
||||||
|
elif key_event.matches('enter'):
|
||||||
|
self.response = self.response_on_accept
|
||||||
|
self.quit_loop(0)
|
||||||
|
|
||||||
def on_click(self, ev: MouseEvent) -> None:
|
def on_click(self, ev: MouseEvent) -> None:
|
||||||
for letter, r in self.clickable_ranges.items():
|
for letter, ranges in self.clickable_ranges.items():
|
||||||
if r.has_point(ev.cell_x, ev.cell_y):
|
for r in ranges:
|
||||||
self.response = letter
|
if r.has_point(ev.cell_x, ev.cell_y):
|
||||||
self.quit_loop(0)
|
self.response = letter
|
||||||
break
|
self.quit_loop(0)
|
||||||
|
return
|
||||||
|
|
||||||
def on_resize(self, screen_size: ScreenSize) -> None:
|
def on_resize(self, screen_size: ScreenSize) -> None:
|
||||||
self.screen_size = screen_size
|
self.screen_size = screen_size
|
||||||
@@ -235,7 +373,15 @@ def main(args: List[str]) -> Response:
|
|||||||
|
|
||||||
prompt = '> '
|
prompt = '> '
|
||||||
with suppress(KeyboardInterrupt, EOFError):
|
with suppress(KeyboardInterrupt, EOFError):
|
||||||
response = input(prompt)
|
if cli_opts.default:
|
||||||
|
def prefill_text() -> None:
|
||||||
|
readline.insert_text(cli_opts.default or '')
|
||||||
|
readline.redisplay()
|
||||||
|
readline.set_pre_input_hook(prefill_text)
|
||||||
|
response = input(prompt)
|
||||||
|
readline.set_pre_input_hook()
|
||||||
|
else:
|
||||||
|
response = input(prompt)
|
||||||
return {'items': items, 'response': response}
|
return {'items': items, 'response': response}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -32,8 +32,7 @@ class Segment:
|
|||||||
self.end_code: Optional[str] = None
|
self.end_code: Optional[str] = None
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return 'Segment(start={!r}, start_code={!r}, end={!r}, end_code={!r})'.format(
|
return f'Segment(start={self.start!r}, start_code={self.start_code!r}, end={self.end!r}, end_code={self.end_code!r})'
|
||||||
self.start, self.start_code, self.end, self.end_code)
|
|
||||||
|
|
||||||
|
|
||||||
class Collection:
|
class Collection:
|
||||||
@@ -102,7 +101,7 @@ def remote_hostname(path: str) -> Tuple[Optional[str], Optional[str]]:
|
|||||||
def resolve_remote_name(path: str, default: str) -> str:
|
def resolve_remote_name(path: str, default: str) -> str:
|
||||||
remote_dir, rh = remote_hostname(path)
|
remote_dir, rh = remote_hostname(path)
|
||||||
if remote_dir and rh:
|
if remote_dir and rh:
|
||||||
return rh + ':' + os.path.relpath(path, remote_dir)
|
return f'{rh}:{os.path.relpath(path, remote_dir)}'
|
||||||
return default
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,10 @@
|
|||||||
import concurrent
|
import concurrent
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from typing import IO, Dict, Iterable, List, Optional, Tuple, Union, cast
|
from concurrent.futures import ProcessPoolExecutor
|
||||||
|
from typing import (
|
||||||
|
IO, Dict, Iterable, Iterator, List, Optional, Tuple, Union, cast
|
||||||
|
)
|
||||||
|
|
||||||
from pygments import highlight # type: ignore
|
from pygments import highlight # type: ignore
|
||||||
from pygments.formatter import Formatter # type: ignore
|
from pygments.formatter import Formatter # type: ignore
|
||||||
@@ -94,7 +97,7 @@ def highlight_data(code: str, filename: str, aliases: Optional[Dict[str, str]] =
|
|||||||
base, ext = os.path.splitext(filename)
|
base, ext = os.path.splitext(filename)
|
||||||
alias = aliases.get(ext[1:])
|
alias = aliases.get(ext[1:])
|
||||||
if alias is not None:
|
if alias is not None:
|
||||||
filename = base + '.' + alias
|
filename = f'{base}.{alias}'
|
||||||
try:
|
try:
|
||||||
lexer = get_lexer_for_filename(filename, stripnl=False)
|
lexer = get_lexer_for_filename(filename, stripnl=False)
|
||||||
except ClassNotFound:
|
except ClassNotFound:
|
||||||
@@ -136,10 +139,22 @@ def highlight_for_diff(path: str, aliases: Dict[str, str]) -> DiffHighlight:
|
|||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
|
process_pool_executor: Optional[ProcessPoolExecutor] = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_highlight_processes() -> Iterator[int]:
|
||||||
|
if process_pool_executor is None:
|
||||||
|
return
|
||||||
|
for pid in process_pool_executor._processes:
|
||||||
|
yield pid
|
||||||
|
|
||||||
|
|
||||||
def highlight_collection(collection: Collection, aliases: Optional[Dict[str, str]] = None) -> Union[str, Dict[str, DiffHighlight]]:
|
def highlight_collection(collection: Collection, aliases: Optional[Dict[str, str]] = None) -> Union[str, Dict[str, DiffHighlight]]:
|
||||||
|
global process_pool_executor
|
||||||
jobs = {}
|
jobs = {}
|
||||||
ans: Dict[str, DiffHighlight] = {}
|
ans: Dict[str, DiffHighlight] = {}
|
||||||
with get_process_pool_executor(prefer_fork=True) as executor:
|
with get_process_pool_executor(prefer_fork=True) as executor:
|
||||||
|
process_pool_executor = executor
|
||||||
for path, item_type, other_path in collection:
|
for path, item_type, other_path in collection:
|
||||||
if item_type != 'rename':
|
if item_type != 'rename':
|
||||||
for p in (path, other_path):
|
for p in (path, other_path):
|
||||||
@@ -159,8 +174,9 @@ def highlight_collection(collection: Collection, aliases: Optional[Dict[str, str
|
|||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
# kitty +runpy "from kittens.diff.highlight import main; main()" file
|
# kitty +runpy "from kittens.diff.highlight import main; main()" file
|
||||||
from .options.types import defaults
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from .options.types import defaults
|
||||||
initialize_highlighter()
|
initialize_highlighter()
|
||||||
with open(sys.argv[-1]) as f:
|
with open(sys.argv[-1]) as f:
|
||||||
highlighted = highlight_data(f.read(), f.name, defaults.syntax_aliases)
|
highlighted = highlight_data(f.read(), f.name, defaults.syntax_aliases)
|
||||||
|
|||||||
@@ -10,10 +10,11 @@ import tempfile
|
|||||||
import warnings
|
import warnings
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
from enum import Enum, auto
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
from typing import (
|
from typing import (
|
||||||
Any, DefaultDict, Dict, Iterable, List, Optional, Tuple, Union
|
Any, DefaultDict, Dict, Iterable, Iterator, List, Optional, Tuple, Union
|
||||||
)
|
)
|
||||||
|
|
||||||
from kitty.cli import CONFIG_HELP, parse_args
|
from kitty.cli import CONFIG_HELP, parse_args
|
||||||
@@ -44,7 +45,8 @@ from .search import BadRegex, Search
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
from .highlight import (
|
from .highlight import (
|
||||||
DiffHighlight, highlight_collection, initialize_highlighter
|
DiffHighlight, get_highlight_processes, highlight_collection,
|
||||||
|
initialize_highlighter
|
||||||
)
|
)
|
||||||
has_highlighter = True
|
has_highlighter = True
|
||||||
DiffHighlight
|
DiffHighlight
|
||||||
@@ -54,8 +56,24 @@ except ImportError:
|
|||||||
def highlight_collection(collection: 'Collection', aliases: Optional[Dict[str, str]] = None) -> Union[str, Dict[str, 'DiffHighlight']]:
|
def highlight_collection(collection: 'Collection', aliases: Optional[Dict[str, str]] = None) -> Union[str, Dict[str, 'DiffHighlight']]:
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
def get_highlight_processes() -> Iterator[int]:
|
||||||
|
if has_highlighter:
|
||||||
|
yield -1
|
||||||
|
|
||||||
INITIALIZING, COLLECTED, DIFFED, COMMAND, MESSAGE = range(5)
|
|
||||||
|
class State(Enum):
|
||||||
|
initializing = auto()
|
||||||
|
collected = auto()
|
||||||
|
diffed = auto()
|
||||||
|
command = auto()
|
||||||
|
message = auto()
|
||||||
|
|
||||||
|
|
||||||
|
class BackgroundWork(Enum):
|
||||||
|
none = auto()
|
||||||
|
collecting = auto()
|
||||||
|
diffing = auto()
|
||||||
|
highlighting = auto()
|
||||||
|
|
||||||
|
|
||||||
def generate_diff(collection: Collection, context: int) -> Union[str, Dict[str, Patch]]:
|
def generate_diff(collection: Collection, context: int) -> Union[str, Dict[str, Patch]]:
|
||||||
@@ -76,7 +94,7 @@ class DiffHandler(Handler):
|
|||||||
image_manager_class = ImageManager
|
image_manager_class = ImageManager
|
||||||
|
|
||||||
def __init__(self, args: DiffCLIOptions, opts: DiffOptions, left: str, right: str) -> None:
|
def __init__(self, args: DiffCLIOptions, opts: DiffOptions, left: str, right: str) -> None:
|
||||||
self.state = INITIALIZING
|
self.state = State.initializing
|
||||||
self.message = ''
|
self.message = ''
|
||||||
self.current_search_is_regex = True
|
self.current_search_is_regex = True
|
||||||
self.current_search: Optional[Search] = None
|
self.current_search: Optional[Search] = None
|
||||||
@@ -90,16 +108,20 @@ class DiffHandler(Handler):
|
|||||||
if self.current_context_count < 0:
|
if self.current_context_count < 0:
|
||||||
self.current_context_count = self.original_context_count = self.opts.num_context_lines
|
self.current_context_count = self.original_context_count = self.opts.num_context_lines
|
||||||
self.highlighting_done = False
|
self.highlighting_done = False
|
||||||
|
self.doing_background_work = BackgroundWork.none
|
||||||
self.restore_position: Optional[Reference] = None
|
self.restore_position: Optional[Reference] = None
|
||||||
for key_def, action in self.opts.key_definitions.items():
|
for key_def, action in self.opts.key_definitions.items():
|
||||||
self.add_shortcut(action, key_def)
|
self.add_shortcut(action, key_def)
|
||||||
|
|
||||||
|
def terminate(self, return_code: int = 0) -> None:
|
||||||
|
self.quit_loop(return_code)
|
||||||
|
|
||||||
def perform_action(self, action: KeyAction) -> None:
|
def perform_action(self, action: KeyAction) -> None:
|
||||||
func, args = action
|
func, args = action
|
||||||
if func == 'quit':
|
if func == 'quit':
|
||||||
self.quit_loop(0)
|
self.terminate()
|
||||||
return
|
return
|
||||||
if self.state <= DIFFED:
|
if self.state.value <= State.diffed.value:
|
||||||
if func == 'scroll_by':
|
if func == 'scroll_by':
|
||||||
return self.scroll_lines(int(args[0] or 0))
|
return self.scroll_lines(int(args[0] or 0))
|
||||||
if func == 'scroll_to':
|
if func == 'scroll_to':
|
||||||
@@ -130,8 +152,9 @@ class DiffHandler(Handler):
|
|||||||
def create_collection(self) -> None:
|
def create_collection(self) -> None:
|
||||||
|
|
||||||
def collect_done(collection: Collection) -> None:
|
def collect_done(collection: Collection) -> None:
|
||||||
|
self.doing_background_work = BackgroundWork.none
|
||||||
self.collection = collection
|
self.collection = collection
|
||||||
self.state = COLLECTED
|
self.state = State.collected
|
||||||
self.generate_diff()
|
self.generate_diff()
|
||||||
|
|
||||||
def collect(left: str, right: str) -> None:
|
def collect(left: str, right: str) -> None:
|
||||||
@@ -139,15 +162,17 @@ class DiffHandler(Handler):
|
|||||||
self.asyncio_loop.call_soon_threadsafe(collect_done, collection)
|
self.asyncio_loop.call_soon_threadsafe(collect_done, collection)
|
||||||
|
|
||||||
self.asyncio_loop.run_in_executor(None, collect, self.left, self.right)
|
self.asyncio_loop.run_in_executor(None, collect, self.left, self.right)
|
||||||
|
self.doing_background_work = BackgroundWork.collecting
|
||||||
|
|
||||||
def generate_diff(self) -> None:
|
def generate_diff(self) -> None:
|
||||||
|
|
||||||
def diff_done(diff_map: Union[str, Dict[str, Patch]]) -> None:
|
def diff_done(diff_map: Union[str, Dict[str, Patch]]) -> None:
|
||||||
|
self.doing_background_work = BackgroundWork.none
|
||||||
if isinstance(diff_map, str):
|
if isinstance(diff_map, str):
|
||||||
self.report_traceback_on_exit = diff_map
|
self.report_traceback_on_exit = diff_map
|
||||||
self.quit_loop(1)
|
self.terminate(1)
|
||||||
return
|
return
|
||||||
self.state = DIFFED
|
self.state = State.diffed
|
||||||
self.diff_map = diff_map
|
self.diff_map = diff_map
|
||||||
self.calculate_statistics()
|
self.calculate_statistics()
|
||||||
self.render_diff()
|
self.render_diff()
|
||||||
@@ -163,7 +188,7 @@ class DiffHandler(Handler):
|
|||||||
initialize_highlighter(self.opts.pygments_style)
|
initialize_highlighter(self.opts.pygments_style)
|
||||||
except StyleNotFound as e:
|
except StyleNotFound as e:
|
||||||
self.report_traceback_on_exit = str(e)
|
self.report_traceback_on_exit = str(e)
|
||||||
self.quit_loop(1)
|
self.terminate(1)
|
||||||
return
|
return
|
||||||
self.syntax_highlight()
|
self.syntax_highlight()
|
||||||
|
|
||||||
@@ -172,13 +197,15 @@ class DiffHandler(Handler):
|
|||||||
self.asyncio_loop.call_soon_threadsafe(diff_done, diff_map)
|
self.asyncio_loop.call_soon_threadsafe(diff_done, diff_map)
|
||||||
|
|
||||||
self.asyncio_loop.run_in_executor(None, diff, self.collection, self.current_context_count)
|
self.asyncio_loop.run_in_executor(None, diff, self.collection, self.current_context_count)
|
||||||
|
self.doing_background_work = BackgroundWork.diffing
|
||||||
|
|
||||||
def syntax_highlight(self) -> None:
|
def syntax_highlight(self) -> None:
|
||||||
|
|
||||||
def highlighting_done(hdata: Union[str, Dict[str, 'DiffHighlight']]) -> None:
|
def highlighting_done(hdata: Union[str, Dict[str, 'DiffHighlight']]) -> None:
|
||||||
|
self.doing_background_work = BackgroundWork.none
|
||||||
if isinstance(hdata, str):
|
if isinstance(hdata, str):
|
||||||
self.report_traceback_on_exit = hdata
|
self.report_traceback_on_exit = hdata
|
||||||
self.quit_loop(1)
|
self.terminate(1)
|
||||||
return
|
return
|
||||||
set_highlight_data(hdata)
|
set_highlight_data(hdata)
|
||||||
self.render_diff()
|
self.render_diff()
|
||||||
@@ -189,6 +216,7 @@ class DiffHandler(Handler):
|
|||||||
self.asyncio_loop.call_soon_threadsafe(highlighting_done, result)
|
self.asyncio_loop.call_soon_threadsafe(highlighting_done, result)
|
||||||
|
|
||||||
self.asyncio_loop.run_in_executor(None, highlight, self.collection, self.opts.syntax_aliases)
|
self.asyncio_loop.run_in_executor(None, highlight, self.collection, self.opts.syntax_aliases)
|
||||||
|
self.doing_background_work = BackgroundWork.highlighting
|
||||||
|
|
||||||
def calculate_statistics(self) -> None:
|
def calculate_statistics(self) -> None:
|
||||||
self.added_count = self.collection.added_count
|
self.added_count = self.collection.added_count
|
||||||
@@ -304,7 +332,7 @@ class DiffHandler(Handler):
|
|||||||
self.create_collection()
|
self.create_collection()
|
||||||
|
|
||||||
def enforce_cursor_state(self) -> None:
|
def enforce_cursor_state(self) -> None:
|
||||||
self.cmd.set_cursor_visible(self.state == COMMAND)
|
self.cmd.set_cursor_visible(self.state is State.command)
|
||||||
|
|
||||||
def draw_lines(self, num: int, offset: int = 0) -> None:
|
def draw_lines(self, num: int, offset: int = 0) -> None:
|
||||||
offset += self.scroll_pos
|
offset += self.scroll_pos
|
||||||
@@ -319,7 +347,7 @@ class DiffHandler(Handler):
|
|||||||
text = line.text
|
text = line.text
|
||||||
if line.image_data is not None:
|
if line.image_data is not None:
|
||||||
image_involved = True
|
image_involved = True
|
||||||
self.write('\r\x1b[K' + text + '\x1b[0m')
|
self.write(f'\r\x1b[K{text}\x1b[0m')
|
||||||
if self.current_search is not None:
|
if self.current_search is not None:
|
||||||
self.current_search.highlight_line(self.write, lpos)
|
self.current_search.highlight_line(self.write, lpos)
|
||||||
if i < num - 1:
|
if i < num - 1:
|
||||||
@@ -407,7 +435,7 @@ class DiffHandler(Handler):
|
|||||||
|
|
||||||
def draw_screen(self) -> None:
|
def draw_screen(self) -> None:
|
||||||
self.enforce_cursor_state()
|
self.enforce_cursor_state()
|
||||||
if self.state < DIFFED:
|
if self.state.value < State.diffed.value:
|
||||||
self.cmd.clear_screen()
|
self.cmd.clear_screen()
|
||||||
self.write(_('Calculating diff, please wait...'))
|
self.write(_('Calculating diff, please wait...'))
|
||||||
return
|
return
|
||||||
@@ -417,14 +445,14 @@ class DiffHandler(Handler):
|
|||||||
self.draw_status_line()
|
self.draw_status_line()
|
||||||
|
|
||||||
def draw_status_line(self) -> None:
|
def draw_status_line(self) -> None:
|
||||||
if self.state < DIFFED:
|
if self.state.value < State.diffed.value:
|
||||||
return
|
return
|
||||||
self.enforce_cursor_state()
|
self.enforce_cursor_state()
|
||||||
self.cmd.set_cursor_position(0, self.num_lines)
|
self.cmd.set_cursor_position(0, self.num_lines)
|
||||||
self.cmd.clear_to_eol()
|
self.cmd.clear_to_eol()
|
||||||
if self.state is COMMAND:
|
if self.state is State.command:
|
||||||
self.line_edit.write(self.write)
|
self.line_edit.write(self.write)
|
||||||
elif self.state is MESSAGE:
|
elif self.state is State.message:
|
||||||
self.cmd.styled(self.message, reverse=True)
|
self.cmd.styled(self.message, reverse=True)
|
||||||
else:
|
else:
|
||||||
sp = f'{self.scroll_pos/self.max_scroll_pos:.0%}' if self.scroll_pos and self.max_scroll_pos else '0%'
|
sp = f'{self.scroll_pos/self.max_scroll_pos:.0%}' if self.scroll_pos and self.max_scroll_pos else '0%'
|
||||||
@@ -437,7 +465,7 @@ class DiffHandler(Handler):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
counts = styled(f'{len(self.current_search)} matches', fg=self.opts.margin_fg)
|
counts = styled(f'{len(self.current_search)} matches', fg=self.opts.margin_fg)
|
||||||
suffix = counts + ' ' + scroll_frac
|
suffix = f'{counts} {scroll_frac}'
|
||||||
prefix = styled(':', fg=self.opts.margin_fg)
|
prefix = styled(':', fg=self.opts.margin_fg)
|
||||||
filler = self.screen_size.cols - wcswidth(prefix) - wcswidth(suffix)
|
filler = self.screen_size.cols - wcswidth(prefix) - wcswidth(suffix)
|
||||||
text = '{}{}{}'.format(prefix, ' ' * filler, suffix)
|
text = '{}{}{}'.format(prefix, ' ' * filler, suffix)
|
||||||
@@ -447,16 +475,16 @@ class DiffHandler(Handler):
|
|||||||
new_ctx = max(0, new_ctx)
|
new_ctx = max(0, new_ctx)
|
||||||
if new_ctx != self.current_context_count:
|
if new_ctx != self.current_context_count:
|
||||||
self.current_context_count = new_ctx
|
self.current_context_count = new_ctx
|
||||||
self.state = COLLECTED
|
self.state = State.collected
|
||||||
self.generate_diff()
|
self.generate_diff()
|
||||||
self.restore_position = self.current_position
|
self.restore_position = self.current_position
|
||||||
self.draw_screen()
|
self.draw_screen()
|
||||||
|
|
||||||
def start_search(self, is_regex: bool, is_backward: bool) -> None:
|
def start_search(self, is_regex: bool, is_backward: bool) -> None:
|
||||||
if self.state != DIFFED:
|
if self.state is not State.diffed:
|
||||||
self.cmd.bell()
|
self.cmd.bell()
|
||||||
return
|
return
|
||||||
self.state = COMMAND
|
self.state = State.command
|
||||||
self.line_edit.clear()
|
self.line_edit.clear()
|
||||||
self.line_edit.add_text('?' if is_backward else '/')
|
self.line_edit.add_text('?' if is_backward else '/')
|
||||||
self.current_search_is_regex = is_regex
|
self.current_search_is_regex = is_regex
|
||||||
@@ -470,50 +498,50 @@ class DiffHandler(Handler):
|
|||||||
try:
|
try:
|
||||||
self.current_search = Search(self.opts, query[1:], self.current_search_is_regex, query[0] == '?')
|
self.current_search = Search(self.opts, query[1:], self.current_search_is_regex, query[0] == '?')
|
||||||
except BadRegex:
|
except BadRegex:
|
||||||
self.state = MESSAGE
|
self.state = State.message
|
||||||
self.message = sanitize(_('Bad regex: {}').format(query[1:]))
|
self.message = sanitize(_('Bad regex: {}').format(query[1:]))
|
||||||
self.cmd.bell()
|
self.cmd.bell()
|
||||||
else:
|
else:
|
||||||
if self.current_search(self.diff_lines, self.margin_size, self.screen_size.cols):
|
if self.current_search(self.diff_lines, self.margin_size, self.screen_size.cols):
|
||||||
self.scroll_to_next_match(include_current=True)
|
self.scroll_to_next_match(include_current=True)
|
||||||
else:
|
else:
|
||||||
self.state = MESSAGE
|
self.state = State.message
|
||||||
self.message = sanitize(_('No matches found'))
|
self.message = sanitize(_('No matches found'))
|
||||||
self.cmd.bell()
|
self.cmd.bell()
|
||||||
|
|
||||||
def on_key_event(self, key_event: KeyEvent, in_bracketed_paste: bool = False) -> None:
|
def on_key_event(self, key_event: KeyEvent, in_bracketed_paste: bool = False) -> None:
|
||||||
if key_event.text:
|
if key_event.text:
|
||||||
if self.state is COMMAND:
|
if self.state is State.command:
|
||||||
self.line_edit.on_text(key_event.text, in_bracketed_paste)
|
self.line_edit.on_text(key_event.text, in_bracketed_paste)
|
||||||
self.draw_status_line()
|
self.draw_status_line()
|
||||||
return
|
return
|
||||||
if self.state is MESSAGE:
|
if self.state is State.message:
|
||||||
self.state = DIFFED
|
self.state = State.diffed
|
||||||
self.draw_status_line()
|
self.draw_status_line()
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
if self.state is MESSAGE:
|
if self.state is State.message:
|
||||||
if key_event.type is not EventType.RELEASE:
|
if key_event.type is not EventType.RELEASE:
|
||||||
self.state = DIFFED
|
self.state = State.diffed
|
||||||
self.draw_status_line()
|
self.draw_status_line()
|
||||||
return
|
return
|
||||||
if self.state is COMMAND:
|
if self.state is State.command:
|
||||||
if self.line_edit.on_key(key_event):
|
if self.line_edit.on_key(key_event):
|
||||||
if not self.line_edit.current_input:
|
if not self.line_edit.current_input:
|
||||||
self.state = DIFFED
|
self.state = State.diffed
|
||||||
self.draw_status_line()
|
self.draw_status_line()
|
||||||
return
|
return
|
||||||
if key_event.matches('enter'):
|
if key_event.matches('enter'):
|
||||||
self.state = DIFFED
|
self.state = State.diffed
|
||||||
self.do_search()
|
self.do_search()
|
||||||
self.line_edit.clear()
|
self.line_edit.clear()
|
||||||
self.draw_screen()
|
self.draw_screen()
|
||||||
return
|
return
|
||||||
if key_event.matches('esc'):
|
if key_event.matches('esc'):
|
||||||
self.state = DIFFED
|
self.state = State.diffed
|
||||||
self.draw_status_line()
|
self.draw_status_line()
|
||||||
return
|
return
|
||||||
if self.state >= DIFFED and self.current_search is not None and key_event.matches('esc'):
|
if self.state.value >= State.diffed.value and self.current_search is not None and key_event.matches('esc'):
|
||||||
self.current_search = None
|
self.current_search = None
|
||||||
self.draw_screen()
|
self.draw_screen()
|
||||||
return
|
return
|
||||||
@@ -526,16 +554,16 @@ class DiffHandler(Handler):
|
|||||||
def on_resize(self, screen_size: ScreenSize) -> None:
|
def on_resize(self, screen_size: ScreenSize) -> None:
|
||||||
self.screen_size = screen_size
|
self.screen_size = screen_size
|
||||||
self.set_scrolling_region()
|
self.set_scrolling_region()
|
||||||
if self.state > COLLECTED:
|
if self.state.value > State.collected.value:
|
||||||
self.image_manager.delete_all_sent_images()
|
self.image_manager.delete_all_sent_images()
|
||||||
self.render_diff()
|
self.render_diff()
|
||||||
self.draw_screen()
|
self.draw_screen()
|
||||||
|
|
||||||
def on_interrupt(self) -> None:
|
def on_interrupt(self) -> None:
|
||||||
self.quit_loop(1)
|
self.terminate(1)
|
||||||
|
|
||||||
def on_eot(self) -> None:
|
def on_eot(self) -> None:
|
||||||
self.quit_loop(1)
|
self.terminate(1)
|
||||||
|
|
||||||
|
|
||||||
OPTIONS = partial('''\
|
OPTIONS = partial('''\
|
||||||
@@ -639,9 +667,10 @@ def main(args: List[str]) -> None:
|
|||||||
for message in showwarning.warnings:
|
for message in showwarning.warnings:
|
||||||
from kitty.utils import safe_print
|
from kitty.utils import safe_print
|
||||||
safe_print(message, file=sys.stderr)
|
safe_print(message, file=sys.stderr)
|
||||||
highlight_processes = getattr(highlight_collection, 'processes', ())
|
if handler.doing_background_work is BackgroundWork.highlighting:
|
||||||
terminate_processes(tuple(highlight_processes))
|
terminate_processes(tuple(get_highlight_processes()))
|
||||||
terminate_processes(tuple(worker_processes))
|
elif handler.doing_background_work == BackgroundWork.diffing:
|
||||||
|
terminate_processes(tuple(worker_processes))
|
||||||
if loop.return_code != 0:
|
if loop.return_code != 0:
|
||||||
if handler.report_traceback_on_exit:
|
if handler.report_traceback_on_exit:
|
||||||
print(handler.report_traceback_on_exit, file=sys.stderr)
|
print(handler.report_traceback_on_exit, file=sys.stderr)
|
||||||
|
|||||||
@@ -244,14 +244,14 @@ class Differ:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f'Running git diff for {left_path} vs. {right_path} generated an exception: {e}'
|
return f'Running git diff for {left_path} vs. {right_path} generated an exception: {e}'
|
||||||
if not ok:
|
if not ok:
|
||||||
return output + f'\nRunning git diff for {left_path} vs. {right_path} failed'
|
return f'{output}\nRunning git diff for {left_path} vs. {right_path} failed'
|
||||||
left_lines = lines_for_path(left_path)
|
left_lines = lines_for_path(left_path)
|
||||||
right_lines = lines_for_path(right_path)
|
right_lines = lines_for_path(right_path)
|
||||||
try:
|
try:
|
||||||
patch = parse_patch(output)
|
patch = parse_patch(output)
|
||||||
except Exception:
|
except Exception:
|
||||||
import traceback
|
import traceback
|
||||||
return traceback.format_exc() + f'\nParsing diff for {left_path} vs. {right_path} failed'
|
return f'{traceback.format_exc()}\nParsing diff for {left_path} vs. {right_path} failed'
|
||||||
else:
|
else:
|
||||||
ans[key] = patch
|
ans[key] = patch
|
||||||
return ans
|
return ans
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ def human_readable(size: int, sep: str = ' ') -> str:
|
|||||||
s = s[:s.find(".")+2]
|
s = s[:s.find(".")+2]
|
||||||
if s.endswith('.0'):
|
if s.endswith('.0'):
|
||||||
s = s[:-2]
|
s = s[:-2]
|
||||||
return s + sep + suffix
|
return f'{s}{sep}{suffix}'
|
||||||
|
|
||||||
|
|
||||||
def fit_in(text: str, count: int) -> str:
|
def fit_in(text: str, count: int) -> str:
|
||||||
@@ -110,7 +110,7 @@ def fit_in(text: str, count: int) -> str:
|
|||||||
return text
|
return text
|
||||||
if count > 1:
|
if count > 1:
|
||||||
p = truncate_point_for_length(text, count - 1)
|
p = truncate_point_for_length(text, count - 1)
|
||||||
return text[:p] + '…'
|
return f'{text[:p]}…'
|
||||||
|
|
||||||
|
|
||||||
def fill_in(text: str, sz: int) -> str:
|
def fill_in(text: str, sz: int) -> str:
|
||||||
@@ -127,8 +127,8 @@ def place_in(text: str, sz: int) -> str:
|
|||||||
def format_func(which: str) -> Callable[[str], str]:
|
def format_func(which: str) -> Callable[[str], str]:
|
||||||
def formatted(text: str) -> str:
|
def formatted(text: str) -> str:
|
||||||
fmt = formats[which]
|
fmt = formats[which]
|
||||||
return '\x1b[' + fmt + 'm' + text + '\x1b[0m'
|
return f'\x1b[{fmt}m{text}\x1b[0m'
|
||||||
formatted.__name__ = which + '_format'
|
formatted.__name__ = f'{which}_format'
|
||||||
return formatted
|
return formatted
|
||||||
|
|
||||||
|
|
||||||
@@ -148,8 +148,8 @@ highlight_map = {'remove': ('removed_highlight', 'removed'), 'add': ('added_high
|
|||||||
|
|
||||||
def highlight_boundaries(ltype: str) -> Tuple[str, str]:
|
def highlight_boundaries(ltype: str) -> Tuple[str, str]:
|
||||||
s, e = highlight_map[ltype]
|
s, e = highlight_map[ltype]
|
||||||
start = '\x1b[' + formats[s] + 'm'
|
start = f'\x1b[{formats[s]}m'
|
||||||
stop = '\x1b[' + formats[e] + 'm'
|
stop = f'\x1b[{formats[e]}m'
|
||||||
return start, stop
|
return start, stop
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ from kitty.fast_data_types import get_options, set_clipboard_string
|
|||||||
from kitty.key_encoding import KeyEvent
|
from kitty.key_encoding import KeyEvent
|
||||||
from kitty.typing import BossType, KittyCommonOpts
|
from kitty.typing import BossType, KittyCommonOpts
|
||||||
from kitty.utils import (
|
from kitty.utils import (
|
||||||
ScreenSize, resolve_custom_file, screen_size_function, set_primary_selection
|
ScreenSize, kitty_ansi_sanitizer_pat, resolve_custom_file,
|
||||||
|
screen_size_function, set_primary_selection
|
||||||
)
|
)
|
||||||
|
|
||||||
from ..tui.handler import Handler, result_handler
|
from ..tui.handler import Handler, result_handler
|
||||||
@@ -40,6 +41,8 @@ def kitty_common_opts() -> KittyCommonOpts:
|
|||||||
|
|
||||||
DEFAULT_HINT_ALPHABET = string.digits + string.ascii_lowercase
|
DEFAULT_HINT_ALPHABET = string.digits + string.ascii_lowercase
|
||||||
DEFAULT_REGEX = r'(?m)^\s*(.+)\s*$'
|
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'
|
||||||
|
|
||||||
|
|
||||||
class Mark:
|
class Mark:
|
||||||
@@ -184,7 +187,7 @@ class Hints(Handler):
|
|||||||
self.current_input = self.current_input[:-1]
|
self.current_input = self.current_input[:-1]
|
||||||
self.current_text = None
|
self.current_text = None
|
||||||
self.draw_screen()
|
self.draw_screen()
|
||||||
elif key_event.matches('enter') and self.current_input:
|
elif (key_event.matches('enter') or key_event.matches('space')) and self.current_input:
|
||||||
try:
|
try:
|
||||||
idx = decode_hint(self.current_input, self.alphabet)
|
idx = decode_hint(self.current_input, self.alphabet)
|
||||||
self.chosen.append(self.index_map[idx])
|
self.chosen.append(self.index_map[idx])
|
||||||
@@ -228,7 +231,7 @@ def regex_finditer(pat: 'Pattern[str]', minimum_match_length: int, text: str) ->
|
|||||||
yield s, e, m.groupdict()
|
yield s, e, m.groupdict()
|
||||||
|
|
||||||
|
|
||||||
closing_bracket_map = {'(': ')', '[': ']', '{': '}', '<': '>', '*': '*', '"': '"', "'": "'"}
|
closing_bracket_map = {'(': ')', '[': ']', '{': '}', '<': '>', '*': '*', '"': '"', "'": "'", "“": "”", "‘": "’"}
|
||||||
opening_brackets = ''.join(closing_bracket_map)
|
opening_brackets = ''.join(closing_bracket_map)
|
||||||
PostprocessorFunc = Callable[[str, int, int], Tuple[int, int]]
|
PostprocessorFunc = Callable[[str, int, int], Tuple[int, int]]
|
||||||
postprocessor_map: Dict[str, PostprocessorFunc] = {}
|
postprocessor_map: Dict[str, PostprocessorFunc] = {}
|
||||||
@@ -271,9 +274,13 @@ def brackets(text: str, s: int, e: int) -> Tuple[int, int]:
|
|||||||
# Remove matching brackets
|
# Remove matching brackets
|
||||||
if s < e <= len(text):
|
if s < e <= len(text):
|
||||||
before = text[s]
|
before = text[s]
|
||||||
if before in '({[<' and text[e-1] == closing_bracket_map[before]:
|
if before in '({[<':
|
||||||
s += 1
|
q = closing_bracket_map[before]
|
||||||
e -= 1
|
if text[e-1] == q:
|
||||||
|
s += 1
|
||||||
|
e -= 1
|
||||||
|
elif text[e:e+1] == q:
|
||||||
|
s += 1
|
||||||
return s, e
|
return s, e
|
||||||
|
|
||||||
|
|
||||||
@@ -282,9 +289,13 @@ def quotes(text: str, s: int, e: int) -> Tuple[int, int]:
|
|||||||
# Remove matching quotes
|
# Remove matching quotes
|
||||||
if s < e <= len(text):
|
if s < e <= len(text):
|
||||||
before = text[s]
|
before = text[s]
|
||||||
if before in '\'"' and text[e-1] == before:
|
if before in '\'"“‘':
|
||||||
s += 1
|
q = closing_bracket_map[before]
|
||||||
e -= 1
|
if text[e-1] == q:
|
||||||
|
s += 1
|
||||||
|
e -= 1
|
||||||
|
elif text[e:e+1] == q:
|
||||||
|
s += 1
|
||||||
return s, e
|
return s, e
|
||||||
|
|
||||||
|
|
||||||
@@ -348,7 +359,7 @@ def functions_for(args: HintsCLIOptions) -> Tuple[str, List[PostprocessorFunc]]:
|
|||||||
)
|
)
|
||||||
post_processors.append(url)
|
post_processors.append(url)
|
||||||
elif args.type == 'path':
|
elif args.type == 'path':
|
||||||
pattern = r'(?:\S*?/[\r\S]+)|(?:\S[\r\S]*\.[a-zA-Z0-9\r]{2,7})'
|
pattern = PATH_REGEX
|
||||||
post_processors.extend((brackets, quotes))
|
post_processors.extend((brackets, quotes))
|
||||||
elif args.type == 'line':
|
elif args.type == 'line':
|
||||||
pattern = '(?m)^\\s*(.+)[\\s\0]*$'
|
pattern = '(?m)^\\s*(.+)[\\s\0]*$'
|
||||||
@@ -405,7 +416,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]:
|
def linenum_marks(text: str, args: HintsCLIOptions, Mark: Type[Mark], extra_cli_args: Sequence[str], *a: Any) -> Generator[Mark, None, None]:
|
||||||
regex = args.regex
|
regex = args.regex
|
||||||
if regex == DEFAULT_REGEX:
|
if regex == DEFAULT_REGEX:
|
||||||
regex = r'(?P<path>(?:\S*/\S+?)|(?:\S+[.][a-zA-Z0-9]{2,7})):(?P<line>\d+)'
|
regex = fr'(?P<path>{PATH_REGEX}):(?P<line>\d+)'
|
||||||
yield from mark(regex, [brackets, quotes], text, args)
|
yield from mark(regex, [brackets, quotes], text, args)
|
||||||
|
|
||||||
|
|
||||||
@@ -421,11 +432,7 @@ def load_custom_processor(customize_processing: str) -> Any:
|
|||||||
return runpy.run_path(custom_path, run_name='__main__')
|
return runpy.run_path(custom_path, run_name='__main__')
|
||||||
|
|
||||||
|
|
||||||
def remove_sgr(text: str) -> str:
|
def process_escape_codes(text: str) -> Tuple[str, Tuple[Mark, ...]]:
|
||||||
return re.sub(r'\x1b\[.*?m', '', text)
|
|
||||||
|
|
||||||
|
|
||||||
def process_hyperlinks(text: str) -> Tuple[str, Tuple[Mark, ...]]:
|
|
||||||
hyperlinks: List[Mark] = []
|
hyperlinks: List[Mark] = []
|
||||||
removed_size = idx = 0
|
removed_size = idx = 0
|
||||||
active_hyperlink_url: Optional[str] = None
|
active_hyperlink_url: Optional[str] = None
|
||||||
@@ -448,6 +455,9 @@ def process_hyperlinks(text: str) -> Tuple[str, Tuple[Mark, ...]]:
|
|||||||
def process_hyperlink(m: 're.Match[str]') -> str:
|
def process_hyperlink(m: 're.Match[str]') -> str:
|
||||||
nonlocal removed_size, active_hyperlink_url, active_hyperlink_id, active_hyperlink_start_offset
|
nonlocal removed_size, active_hyperlink_url, active_hyperlink_id, active_hyperlink_start_offset
|
||||||
raw = m.group()
|
raw = m.group()
|
||||||
|
if not raw.startswith('\x1b]8'):
|
||||||
|
removed_size += len(raw)
|
||||||
|
return ''
|
||||||
start = m.start() - removed_size
|
start = m.start() - removed_size
|
||||||
removed_size += len(raw)
|
removed_size += len(raw)
|
||||||
if active_hyperlink_url is not None:
|
if active_hyperlink_url is not None:
|
||||||
@@ -465,7 +475,7 @@ def process_hyperlinks(text: str) -> Tuple[str, Tuple[Mark, ...]]:
|
|||||||
|
|
||||||
return ''
|
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:
|
if active_hyperlink_url is not None:
|
||||||
add_hyperlink(len(text))
|
add_hyperlink(len(text))
|
||||||
return text, tuple(hyperlinks)
|
return text, tuple(hyperlinks)
|
||||||
@@ -473,8 +483,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]]:
|
def run(args: HintsCLIOptions, text: str, extra_cli_args: Sequence[str] = ()) -> Optional[Dict[str, Any]]:
|
||||||
try:
|
try:
|
||||||
text = parse_input(remove_sgr(text))
|
text = parse_input(text)
|
||||||
text, hyperlinks = process_hyperlinks(text)
|
text, hyperlinks = process_escape_codes(text)
|
||||||
pattern, post_processors = functions_for(args)
|
pattern, post_processors = functions_for(args)
|
||||||
if args.type == 'linenum':
|
if args.type == 'linenum':
|
||||||
args.customize_processing = '::linenum::'
|
args.customize_processing = '::linenum::'
|
||||||
@@ -692,14 +702,26 @@ def main(args: List[str]) -> Optional[Dict[str, Any]]:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
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:
|
def linenum_process_result(data: Dict[str, Any]) -> Tuple[str, int]:
|
||||||
|
pat = re.compile(r':(\d+)$')
|
||||||
for m, g in zip(data['match'], data['groupdicts']):
|
for m, g in zip(data['match'], data['groupdicts']):
|
||||||
if m:
|
if m:
|
||||||
path, line = g['path'], g['line']
|
path, line = g['path'], g['line']
|
||||||
path = os.path.expanduser(path.split(':')[-1])
|
# look for trailers on path of the for :number
|
||||||
line = int(line)
|
while True:
|
||||||
break
|
m = pat.search(path)
|
||||||
else:
|
if m is None:
|
||||||
|
break
|
||||||
|
line = m.group(1)
|
||||||
|
path = path[:-len(m.group())]
|
||||||
|
|
||||||
|
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:
|
||||||
|
path, line = linenum_process_result(data)
|
||||||
|
if not path:
|
||||||
return
|
return
|
||||||
|
|
||||||
cmd = [x.format(path=path, line=line) for x in extra_cli_args or ('vim', '+{line}', '{path}')]
|
cmd = [x.format(path=path, line=line) for x in extra_cli_args or ('vim', '+{line}', '{path}')]
|
||||||
@@ -720,7 +742,7 @@ def linenum_handle_result(args: List[str], data: Dict[str, Any], target_window_i
|
|||||||
else:
|
else:
|
||||||
import shlex
|
import shlex
|
||||||
text = ' '.join(shlex.quote(arg) for arg in cmd)
|
text = ' '.join(shlex.quote(arg) for arg in cmd)
|
||||||
w.paste_bytes(text + '\r')
|
w.paste_bytes(f'{text}\r')
|
||||||
elif action == 'background':
|
elif action == 'background':
|
||||||
import subprocess
|
import subprocess
|
||||||
subprocess.Popen(cmd, cwd=data['cwd'])
|
subprocess.Popen(cmd, cwd=data['cwd'])
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from kitty.cli import parse_args
|
|||||||
from kitty.cli_stub import IcatCLIOptions
|
from kitty.cli_stub import IcatCLIOptions
|
||||||
from kitty.constants import appname
|
from kitty.constants import appname
|
||||||
from kitty.guess_mime_type import guess_type
|
from kitty.guess_mime_type import guess_type
|
||||||
|
from kitty.rgb import to_color
|
||||||
from kitty.types import run_once
|
from kitty.types import run_once
|
||||||
from kitty.typing import GRT_f, GRT_t
|
from kitty.typing import GRT_f, GRT_t
|
||||||
from kitty.utils import (
|
from kitty.utils import (
|
||||||
@@ -55,6 +56,19 @@ are smaller than the specified area to be scaled up to use as much
|
|||||||
of the specified area as possible.
|
of the specified area as possible.
|
||||||
|
|
||||||
|
|
||||||
|
--background
|
||||||
|
default=none
|
||||||
|
Specify a background color, this will cause transparent images to be composited on
|
||||||
|
top of the specified color.
|
||||||
|
|
||||||
|
|
||||||
|
--mirror
|
||||||
|
default=none
|
||||||
|
type=choices
|
||||||
|
choices=none,horizontal,vertical,both
|
||||||
|
Mirror the image about a horizontal or vertical axis or both.
|
||||||
|
|
||||||
|
|
||||||
--clear
|
--clear
|
||||||
type=bool-set
|
type=bool-set
|
||||||
Remove all images currently displayed on the screen.
|
Remove all images currently displayed on the screen.
|
||||||
@@ -88,7 +102,7 @@ detecting image display support.
|
|||||||
--print-window-size
|
--print-window-size
|
||||||
type=bool-set
|
type=bool-set
|
||||||
Print out the window size as :italic:`widthxheight` (in pixels) and quit. This is a
|
Print out the window size as :italic:`widthxheight` (in pixels) and quit. This is a
|
||||||
convenience method to query the window size if using kitty icat from a
|
convenience method to query the window size if using :code:`kitty +kitten icat` from a
|
||||||
scripting language that cannot make termios calls.
|
scripting language that cannot make termios calls.
|
||||||
|
|
||||||
|
|
||||||
@@ -307,6 +321,9 @@ class ParsedOpts:
|
|||||||
|
|
||||||
place: Optional['Place'] = None
|
place: Optional['Place'] = None
|
||||||
z_index: int = 0
|
z_index: int = 0
|
||||||
|
remove_alpha: str = ''
|
||||||
|
flip: bool = False
|
||||||
|
flop: bool = False
|
||||||
|
|
||||||
|
|
||||||
def process(path: str, args: IcatCLIOptions, parsed_opts: ParsedOpts, is_tempfile: bool) -> bool:
|
def process(path: str, args: IcatCLIOptions, parsed_opts: ParsedOpts, is_tempfile: bool) -> bool:
|
||||||
@@ -316,9 +333,10 @@ def process(path: str, args: IcatCLIOptions, parsed_opts: ParsedOpts, is_tempfil
|
|||||||
available_height = parsed_opts.place.height * (ss.height // ss.rows) if parsed_opts.place else 10 * m.height
|
available_height = parsed_opts.place.height * (ss.height // ss.rows) if parsed_opts.place else 10 * m.height
|
||||||
needs_scaling = m.width > available_width or m.height > available_height
|
needs_scaling = m.width > available_width or m.height > available_height
|
||||||
needs_scaling = needs_scaling or args.scale_up
|
needs_scaling = needs_scaling or args.scale_up
|
||||||
|
needs_conversion = needs_scaling or bool(parsed_opts.remove_alpha) or parsed_opts.flip or parsed_opts.flop
|
||||||
file_removed = False
|
file_removed = False
|
||||||
use_number = 0
|
use_number = 0
|
||||||
if m.fmt == 'png' and not needs_scaling:
|
if m.fmt == 'png' and not needs_conversion:
|
||||||
outfile = path
|
outfile = path
|
||||||
transmit_mode: 'GRT_t' = 't' if is_tempfile else 'f'
|
transmit_mode: 'GRT_t' = 't' if is_tempfile else 'f'
|
||||||
fmt: 'GRT_f' = 100
|
fmt: 'GRT_f' = 100
|
||||||
@@ -328,13 +346,17 @@ def process(path: str, args: IcatCLIOptions, parsed_opts: ParsedOpts, is_tempfil
|
|||||||
fmt = 24 if m.mode == 'rgb' else 32
|
fmt = 24 if m.mode == 'rgb' else 32
|
||||||
transmit_mode = 't'
|
transmit_mode = 't'
|
||||||
if len(m) == 1 or args.loop == 0:
|
if len(m) == 1 or args.loop == 0:
|
||||||
outfile, width, height = render_as_single_image(path, m, available_width, available_height, args.scale_up)
|
outfile, width, height = render_as_single_image(
|
||||||
|
path, m, available_width, available_height, args.scale_up,
|
||||||
|
remove_alpha=parsed_opts.remove_alpha, flip=parsed_opts.flip, flop=parsed_opts.flop)
|
||||||
else:
|
else:
|
||||||
import struct
|
import struct
|
||||||
use_number = max(1, struct.unpack('@I', os.urandom(4))[0])
|
use_number = max(1, struct.unpack('@I', os.urandom(4))[0])
|
||||||
with NamedTemporaryFile() as f:
|
with NamedTemporaryFile() as f:
|
||||||
prefix = f.name
|
prefix = f.name
|
||||||
frame_data = render_image(path, prefix, m, available_width, available_height, args.scale_up)
|
frame_data = render_image(
|
||||||
|
path, prefix, m, available_width, available_height, args.scale_up,
|
||||||
|
remove_alpha=parsed_opts.remove_alpha, flip=parsed_opts.flip, flop=parsed_opts.flop)
|
||||||
outfile, width, height = frame_data.frames[0].path, frame_data.width, frame_data.height
|
outfile, width, height = frame_data.frames[0].path, frame_data.width, frame_data.height
|
||||||
show(
|
show(
|
||||||
outfile, width, height, parsed_opts.z_index, fmt, transmit_mode,
|
outfile, width, height, parsed_opts.z_index, fmt, transmit_mode,
|
||||||
@@ -449,9 +471,8 @@ def process_single_item(
|
|||||||
file_removed = False
|
file_removed = False
|
||||||
try:
|
try:
|
||||||
if isinstance(item, bytes):
|
if isinstance(item, bytes):
|
||||||
tf = NamedTemporaryFile(prefix='stdin-image-data-', delete=False)
|
with NamedTemporaryFile(prefix='stdin-image-data-', delete=False) as tf:
|
||||||
tf.write(item)
|
tf.write(item)
|
||||||
tf.close()
|
|
||||||
item = tf.name
|
item = tf.name
|
||||||
is_tempfile = True
|
is_tempfile = True
|
||||||
if url_pat is not None and url_pat.match(item) is not None:
|
if url_pat is not None and url_pat.match(item) is not None:
|
||||||
@@ -527,6 +548,13 @@ def main(args: List[str] = sys.argv) -> None:
|
|||||||
parsed_opts.z_index = parse_z_index(cli_opts.z_index)
|
parsed_opts.z_index = parse_z_index(cli_opts.z_index)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise SystemExit(f'Not a valid z-index specification: {cli_opts.z_index}')
|
raise SystemExit(f'Not a valid z-index specification: {cli_opts.z_index}')
|
||||||
|
if cli_opts.background != 'none':
|
||||||
|
ra = to_color(cli_opts.background)
|
||||||
|
if ra is None:
|
||||||
|
raise SystemExit(f'Not a valid color specification: {cli_opts.background}')
|
||||||
|
parsed_opts.remove_alpha = ra.as_sharp
|
||||||
|
parsed_opts.flip = cli_opts.mirror in ('both', 'vertical')
|
||||||
|
parsed_opts.flop = cli_opts.mirror in ('both', 'horizontal')
|
||||||
|
|
||||||
if cli_opts.detect_support:
|
if cli_opts.detect_support:
|
||||||
if not detect_support(wait_for=cli_opts.detection_timeout, silent=True):
|
if not detect_support(wait_for=cli_opts.detection_timeout, silent=True):
|
||||||
@@ -550,16 +578,26 @@ def main(args: List[str] = sys.argv) -> None:
|
|||||||
raise SystemExit(f'The --place option can only be used with a single image, not {items}')
|
raise SystemExit(f'The --place option can only be used with a single image, not {items}')
|
||||||
sys.stdout.buffer.write(b'\0337') # save cursor
|
sys.stdout.buffer.write(b'\0337') # save cursor
|
||||||
url_pat = re.compile(r'(?:https?|ftp)://', flags=re.I)
|
url_pat = re.compile(r'(?:https?|ftp)://', flags=re.I)
|
||||||
|
|
||||||
|
def hold_if_needed(exit_code_or_msg: Union[int, str]) -> None:
|
||||||
|
if cli_opts.hold:
|
||||||
|
if isinstance(exit_code_or_msg, str):
|
||||||
|
print(exit_code_or_msg, file=sys.stderr, flush=True)
|
||||||
|
exit_code_or_msg = 1
|
||||||
|
with open(os.ctermid()) as tty, raw_mode(tty.fileno()):
|
||||||
|
tty.buffer.read(1)
|
||||||
|
raise SystemExit(exit_code_or_msg)
|
||||||
|
|
||||||
for item in items:
|
for item in items:
|
||||||
try:
|
try:
|
||||||
process_single_item(item, cli_opts, parsed_opts, url_pat)
|
process_single_item(item, cli_opts, parsed_opts, url_pat)
|
||||||
except NoImageMagick as e:
|
except NoImageMagick as e:
|
||||||
raise SystemExit(str(e))
|
hold_if_needed(str(e))
|
||||||
except OutdatedImageMagick as e:
|
except OutdatedImageMagick as e:
|
||||||
print(e.detailed_error, file=sys.stderr)
|
print(e.detailed_error, file=sys.stderr)
|
||||||
raise SystemExit(str(e))
|
hold_if_needed(str(e))
|
||||||
except ConvertFailed as e:
|
except ConvertFailed as e:
|
||||||
raise SystemExit(str(e))
|
hold_if_needed(str(e))
|
||||||
except OpenFailed as e:
|
except OpenFailed as e:
|
||||||
errors.append(e)
|
errors.append(e)
|
||||||
if parsed_opts.place:
|
if parsed_opts.place:
|
||||||
@@ -567,11 +605,8 @@ def main(args: List[str] = sys.argv) -> None:
|
|||||||
if errors:
|
if errors:
|
||||||
for err in errors:
|
for err in errors:
|
||||||
print(err, file=sys.stderr)
|
print(err, file=sys.stderr)
|
||||||
if cli_opts.hold:
|
hold_if_needed(1 if errors else 0)
|
||||||
with open(os.ctermid()) as tty:
|
raise SystemExit()
|
||||||
with raw_mode(tty.fileno()):
|
|
||||||
tty.buffer.read(1)
|
|
||||||
raise SystemExit(1 if errors else 0)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -85,9 +85,9 @@ def create_strut(
|
|||||||
'-id',
|
'-id',
|
||||||
str(int(win_id)), '-format', '_NET_WM_STRUT_PARTIAL', '32cccccccccccc',
|
str(int(win_id)), '-format', '_NET_WM_STRUT_PARTIAL', '32cccccccccccc',
|
||||||
'-set', '_NET_WM_STRUT_PARTIAL',
|
'-set', '_NET_WM_STRUT_PARTIAL',
|
||||||
'{left},{right},{top},{bottom},'
|
f'{left},{right},{top},{bottom},'
|
||||||
'{left_start_y},{left_end_y},{right_start_y},{right_end_y},'
|
f'{left_start_y},{left_end_y},{right_start_y},{right_end_y},'
|
||||||
'{top_start_x},{top_end_x},{bottom_start_x},{bottom_end_x}'.format(**locals())
|
f'{top_start_x},{top_end_x},{bottom_start_x},{bottom_end_x}'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ def main(args: List[str] = sys.argv) -> None:
|
|||||||
raise SystemExit(f'Unknown queries: {", ".join(extra)}')
|
raise SystemExit(f'Unknown queries: {", ".join(extra)}')
|
||||||
|
|
||||||
for key, val in do_queries(queries, cli_opts).items():
|
for key, val in do_queries(queries, cli_opts).items():
|
||||||
print(key + ':', val)
|
print(f'{key}:', val)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -122,10 +122,17 @@ class ControlMaster:
|
|||||||
cmd.extend(['-i', conn_data.identity_file])
|
cmd.extend(['-i', conn_data.identity_file])
|
||||||
self.batch_cmd_prefix = cmd + ['-o', 'BatchMode=yes']
|
self.batch_cmd_prefix = cmd + ['-o', 'BatchMode=yes']
|
||||||
|
|
||||||
|
def check_call(self, cmd: List[str]) -> None:
|
||||||
|
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL)
|
||||||
|
stdout = p.communicate()[0]
|
||||||
|
if p.wait() != 0:
|
||||||
|
out = stdout.decode('utf-8', 'replace')
|
||||||
|
raise Exception(f'The ssh command: {shlex.join(cmd)} failed with exit code {p.returncode} and output: {out}')
|
||||||
|
|
||||||
def __enter__(self) -> 'ControlMaster':
|
def __enter__(self) -> 'ControlMaster':
|
||||||
subprocess.check_call(
|
self.check_call(
|
||||||
self.cmd_prefix + ['-o', 'ControlMaster=auto', '-fN', self.conn_data.hostname])
|
self.cmd_prefix + ['-o', 'ControlMaster=auto', '-fN', self.conn_data.hostname])
|
||||||
subprocess.check_call(
|
self.check_call(
|
||||||
self.batch_cmd_prefix + ['-O', 'check', self.conn_data.hostname])
|
self.batch_cmd_prefix + ['-O', 'check', self.conn_data.hostname])
|
||||||
if not self.dest:
|
if not self.dest:
|
||||||
self.tdir = tempfile.mkdtemp()
|
self.tdir = tempfile.mkdtemp()
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ def real_main(args: List[str]) -> None:
|
|||||||
|
|
||||||
def main(args: List[str]) -> None:
|
def main(args: List[str]) -> None:
|
||||||
try:
|
try:
|
||||||
with suppress(KeyboardInterrupt):
|
with suppress(KeyboardInterrupt, EOFError):
|
||||||
real_main(args)
|
real_main(args)
|
||||||
except Exception:
|
except Exception:
|
||||||
import traceback
|
import traceback
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class KeysHandler(Handler):
|
|||||||
self.cmd.colored(etype + ' ', 'yellow')
|
self.cmd.colored(etype + ' ', 'yellow')
|
||||||
self.cmd.styled(key_event.text, italic=True)
|
self.cmd.styled(key_event.text, italic=True)
|
||||||
self.print()
|
self.print()
|
||||||
rep = 'CSI ' + encode_key_event(key_event)[2:]
|
rep = f'CSI {encode_key_event(key_event)[2:]}'
|
||||||
rep = rep.replace(';', ' ; ').replace(':', ' : ')[:-1] + ' ' + rep[-1]
|
rep = rep.replace(';', ' ; ').replace(':', ' : ')[:-1] + ' ' + rep[-1]
|
||||||
self.cmd.styled(rep, fg='magenta')
|
self.cmd.styled(rep, fg='magenta')
|
||||||
if (key_event.shifted_key or key_event.alternate_key):
|
if (key_event.shifted_key or key_event.alternate_key):
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ def print_key(raw: bytearray) -> None:
|
|||||||
unix = ''
|
unix = ''
|
||||||
for ch in raw:
|
for ch in raw:
|
||||||
if ch < len(ctrl_keys):
|
if ch < len(ctrl_keys):
|
||||||
unix += '^' + ctrl_keys[ch]
|
unix += f'^{ctrl_keys[ch]}'
|
||||||
elif ch == 127:
|
elif ch == 127:
|
||||||
unix += '^?'
|
unix += '^?'
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -281,7 +281,7 @@ def complete_choices(ans: Completions, prefix: str, title: str, choices: Iterabl
|
|||||||
if q.startswith(effective_prefix):
|
if q.startswith(effective_prefix):
|
||||||
if comma_separated:
|
if comma_separated:
|
||||||
tq = q
|
tq = q
|
||||||
q = hidden_prefix + q + ','
|
q = f'{hidden_prefix}{q},'
|
||||||
word_transforms[q] = tq
|
word_transforms[q] = tq
|
||||||
matches[q] = ''
|
matches[q] = ''
|
||||||
ans.add_match_group(title, matches, trailing_space=not comma_separated, word_transforms=word_transforms)
|
ans.add_match_group(title, matches, trailing_space=not comma_separated, word_transforms=word_transforms)
|
||||||
|
|||||||
@@ -20,12 +20,17 @@ cat >$tmp << 'TERMEOF'
|
|||||||
TERMINFO
|
TERMINFO
|
||||||
TERMEOF
|
TERMEOF
|
||||||
|
|
||||||
tic_out=$(tic -x -o $HOME/.terminfo $tmp 2>&1)
|
tname=.terminfo
|
||||||
|
if [ -e "/usr/share/misc/terminfo.cdb" ]; then
|
||||||
|
# NetBSD requires this see https://github.com/kovidgoyal/kitty/issues/4622
|
||||||
|
tname=".terminfo.cdb"
|
||||||
|
fi
|
||||||
|
tic_out=$(tic -x -o $HOME/$tname $tmp 2>&1)
|
||||||
rc=$?
|
rc=$?
|
||||||
rm $tmp
|
rm $tmp
|
||||||
if [ "$rc" != "0" ]; then echo "$tic_out"; exit 1; fi
|
if [ "$rc" != "0" ]; then echo "$tic_out"; exit 1; fi
|
||||||
if [ -z "$USER" ]; then export USER=$(whoami); fi
|
if [ -z "$USER" ]; then export USER=$(whoami); fi
|
||||||
export TERMINFO="$HOME/.terminfo"
|
export TERMINFO="$HOME/$tname"
|
||||||
login_shell=""
|
login_shell=""
|
||||||
python=""
|
python=""
|
||||||
|
|
||||||
@@ -109,8 +114,12 @@ import subprocess, os, sys, pwd, binascii, json
|
|||||||
# macOS ships with an ancient version of tic that cannot read from stdin, so we
|
# macOS ships with an ancient version of tic that cannot read from stdin, so we
|
||||||
# create a temp file for it
|
# create a temp file for it
|
||||||
with NamedTemporaryFile() as tmp:
|
with NamedTemporaryFile() as tmp:
|
||||||
|
tname = '.terminfo'
|
||||||
|
if os.path.exists('/usr/share/misc/terminfo.cdb'):
|
||||||
|
tname += '.cdb'
|
||||||
tmp.write(binascii.unhexlify('{terminfo}'))
|
tmp.write(binascii.unhexlify('{terminfo}'))
|
||||||
p = subprocess.Popen(['tic', '-x', '-o', os.path.expanduser('~/.terminfo'), tmp.name], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
tmp.flush()
|
||||||
|
p = subprocess.Popen(['tic', '-x', '-o', os.path.expanduser('~/' + tname), tmp.name], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
stdout, stderr = p.communicate()
|
stdout, stderr = p.communicate()
|
||||||
if p.wait() != 0:
|
if p.wait() != 0:
|
||||||
getattr(sys.stderr, 'buffer', sys.stderr).write(stdout + stderr)
|
getattr(sys.stderr, 'buffer', sys.stderr).write(stdout + stderr)
|
||||||
@@ -131,7 +140,7 @@ def get_ssh_cli() -> Tuple[Set[str], Set[str]]:
|
|||||||
other_ssh_args: Set[str] = set()
|
other_ssh_args: Set[str] = set()
|
||||||
boolean_ssh_args: Set[str] = set()
|
boolean_ssh_args: Set[str] = set()
|
||||||
for k, v in ssh_options().items():
|
for k, v in ssh_options().items():
|
||||||
k = '-' + k
|
k = f'-{k}'
|
||||||
if v:
|
if v:
|
||||||
other_ssh_args.add(k)
|
other_ssh_args.add(k)
|
||||||
else:
|
else:
|
||||||
@@ -213,7 +222,7 @@ class InvalidSSHArgs(ValueError):
|
|||||||
|
|
||||||
def parse_ssh_args(args: List[str]) -> Tuple[List[str], List[str], bool]:
|
def parse_ssh_args(args: List[str]) -> Tuple[List[str], List[str], bool]:
|
||||||
boolean_ssh_args, other_ssh_args = get_ssh_cli()
|
boolean_ssh_args, other_ssh_args = get_ssh_cli()
|
||||||
passthrough_args = {'-' + x for x in 'Nnf'}
|
passthrough_args = {f'-{x}' for x in 'Nnf'}
|
||||||
ssh_args = []
|
ssh_args = []
|
||||||
server_args: List[str] = []
|
server_args: List[str] = []
|
||||||
expecting_option_val = False
|
expecting_option_val = False
|
||||||
@@ -230,7 +239,7 @@ def parse_ssh_args(args: List[str]) -> Tuple[List[str], List[str], bool]:
|
|||||||
# could be a multi-character option
|
# could be a multi-character option
|
||||||
all_args = argument[1:]
|
all_args = argument[1:]
|
||||||
for i, arg in enumerate(all_args):
|
for i, arg in enumerate(all_args):
|
||||||
arg = '-' + arg
|
arg = f'-{arg}'
|
||||||
if arg in passthrough_args:
|
if arg in passthrough_args:
|
||||||
passthrough = True
|
passthrough = True
|
||||||
if arg in boolean_ssh_args:
|
if arg in boolean_ssh_args:
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ def patch_conf(raw: str, theme_name: str) -> str:
|
|||||||
nraw = raw + addition
|
nraw = raw + addition
|
||||||
# comment out all existing color definitions
|
# comment out all existing color definitions
|
||||||
color_conf_items = ( # {{{
|
color_conf_items = ( # {{{
|
||||||
# generated by gen_config.py do not EDIT
|
# generated by gen-config.py DO NOT edit
|
||||||
# ALL_COLORS_START
|
# ALL_COLORS_START
|
||||||
'active_border_color',
|
'active_border_color',
|
||||||
'active_tab_background',
|
'active_tab_background',
|
||||||
@@ -376,7 +376,7 @@ def fetch_themes(
|
|||||||
|
|
||||||
needs_delete = False
|
needs_delete = False
|
||||||
try:
|
try:
|
||||||
with tempfile.NamedTemporaryFile(suffix='-' + os.path.basename(dest_path), dir=os.path.dirname(dest_path), delete=False) as f:
|
with tempfile.NamedTemporaryFile(suffix=f'-{os.path.basename(dest_path)}', dir=os.path.dirname(dest_path), delete=False) as f:
|
||||||
needs_delete = True
|
needs_delete = True
|
||||||
shutil.copyfileobj(res, f)
|
shutil.copyfileobj(res, f)
|
||||||
f.flush()
|
f.flush()
|
||||||
@@ -405,7 +405,7 @@ def theme_name_from_file_name(fname: str) -> str:
|
|||||||
ans = ans.replace('_', ' ')
|
ans = ans.replace('_', ' ')
|
||||||
|
|
||||||
def camel_case(m: 'Match[str]') -> str:
|
def camel_case(m: 'Match[str]') -> str:
|
||||||
return str(m.group(1) + ' ' + m.group(2))
|
return f'{m.group(1)} {m.group(2)}'
|
||||||
|
|
||||||
ans = re.sub(r'([a-z])([A-Z])', camel_case, ans)
|
ans = re.sub(r'([a-z])([A-Z])', camel_case, ans)
|
||||||
return ' '.join(x.capitalize() for x in filter(None, ans.split()))
|
return ' '.join(x.capitalize() for x in filter(None, ans.split()))
|
||||||
@@ -522,10 +522,10 @@ class Theme:
|
|||||||
def save_in_dir(self, dirpath: str) -> None:
|
def save_in_dir(self, dirpath: str) -> None:
|
||||||
atomic_save(self.raw.encode('utf-8'), os.path.join(dirpath, f'{self.name}.conf'))
|
atomic_save(self.raw.encode('utf-8'), os.path.join(dirpath, f'{self.name}.conf'))
|
||||||
|
|
||||||
def save_in_conf(self, confdir: str, reload_in: str) -> None:
|
def save_in_conf(self, confdir: str, reload_in: str, config_file_name: str = 'kitty.conf') -> None:
|
||||||
os.makedirs(confdir, exist_ok=True)
|
os.makedirs(confdir, exist_ok=True)
|
||||||
atomic_save(self.raw.encode('utf-8'), os.path.join(confdir, 'current-theme.conf'))
|
atomic_save(self.raw.encode('utf-8'), os.path.join(confdir, 'current-theme.conf'))
|
||||||
confpath = os.path.realpath(os.path.join(confdir, 'kitty.conf'))
|
confpath = os.path.realpath(os.path.join(confdir, config_file_name))
|
||||||
try:
|
try:
|
||||||
with open(confpath) as f:
|
with open(confpath) as f:
|
||||||
raw = f.read()
|
raw = f.read()
|
||||||
@@ -533,7 +533,7 @@ class Theme:
|
|||||||
raw = ''
|
raw = ''
|
||||||
nraw = patch_conf(raw, self.name)
|
nraw = patch_conf(raw, self.name)
|
||||||
if raw:
|
if raw:
|
||||||
with open(confpath + '.bak', 'w') as f:
|
with open(f'{confpath}.bak', 'w') as f:
|
||||||
f.write(raw)
|
f.write(raw)
|
||||||
atomic_save(nraw.encode('utf-8'), confpath)
|
atomic_save(nraw.encode('utf-8'), confpath)
|
||||||
if reload_in == 'parent':
|
if reload_in == 'parent':
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ def limit_length(text: str, limit: int = 32) -> str:
|
|||||||
x = truncate_point_for_length(text, limit - 1)
|
x = truncate_point_for_length(text, limit - 1)
|
||||||
if x >= len(text):
|
if x >= len(text):
|
||||||
return text
|
return text
|
||||||
return text[:x] + '…'
|
return f'{text[:x]}…'
|
||||||
|
|
||||||
|
|
||||||
class State(Enum):
|
class State(Enum):
|
||||||
@@ -332,7 +332,7 @@ class ThemesHandler(Handler):
|
|||||||
for line, width, is_current in self.themes_list.lines(num_rows):
|
for line, width, is_current in self.themes_list.lines(num_rows):
|
||||||
num_rows -= 1
|
num_rows -= 1
|
||||||
if is_current:
|
if is_current:
|
||||||
line = line.replace(MARK_AFTER, '\033[' + color_code('green') + 'm')
|
line = line.replace(MARK_AFTER, f'\033[{color_code("green")}m')
|
||||||
self.cmd.styled('>' if is_current else ' ', fg='green')
|
self.cmd.styled('>' if is_current else ' ', fg='green')
|
||||||
self.cmd.styled(line, bold=is_current, fg='green' if is_current else None)
|
self.cmd.styled(line, bold=is_current, fg='green' if is_current else None)
|
||||||
self.cmd.move_cursor_by(mw - width, 'right')
|
self.cmd.move_cursor_by(mw - width, 'right')
|
||||||
@@ -433,7 +433,7 @@ class ThemesHandler(Handler):
|
|||||||
def draw_accepting_screen(self) -> None:
|
def draw_accepting_screen(self) -> None:
|
||||||
name = self.themes_list.current_theme.name
|
name = self.themes_list.current_theme.name
|
||||||
name = styled(name, bold=True, fg="green")
|
name = styled(name, bold=True, fg="green")
|
||||||
kc = styled('kitty.conf', italic=True)
|
kc = styled(self.cli_opts.config_file_name, italic=True)
|
||||||
|
|
||||||
def ac(x: str) -> str:
|
def ac(x: str) -> str:
|
||||||
return styled(x, fg='red')
|
return styled(x, fg='red')
|
||||||
@@ -465,7 +465,7 @@ class ThemesHandler(Handler):
|
|||||||
self.quit_on_next_key_release = 0
|
self.quit_on_next_key_release = 0
|
||||||
return
|
return
|
||||||
if key_event.matches_text('m'):
|
if key_event.matches_text('m'):
|
||||||
self.themes_list.current_theme.save_in_conf(config_dir, self.cli_opts.reload_in)
|
self.themes_list.current_theme.save_in_conf(config_dir, self.cli_opts.reload_in, self.cli_opts.config_file_name)
|
||||||
self.update_recent()
|
self.update_recent()
|
||||||
self.quit_on_next_key_release = 0
|
self.quit_on_next_key_release = 0
|
||||||
return
|
return
|
||||||
@@ -536,6 +536,13 @@ type=bool-set
|
|||||||
default=false
|
default=false
|
||||||
When running non-interactively, dump the specified theme to STDOUT
|
When running non-interactively, dump the specified theme to STDOUT
|
||||||
instead of changing kitty.conf.
|
instead of changing kitty.conf.
|
||||||
|
|
||||||
|
|
||||||
|
--config-file-name
|
||||||
|
default=kitty.conf
|
||||||
|
The name or path to the config file to edit. Relative paths are interpreted
|
||||||
|
with respect to the kitty config directory. By default the kitty config file,
|
||||||
|
kitty.conf is edited.
|
||||||
'''.format
|
'''.format
|
||||||
|
|
||||||
|
|
||||||
@@ -559,7 +566,7 @@ def non_interactive(cli_opts: ThemesCLIOptions, theme_name: str) -> None:
|
|||||||
if cli_opts.dump_theme:
|
if cli_opts.dump_theme:
|
||||||
print(theme.raw)
|
print(theme.raw)
|
||||||
return
|
return
|
||||||
theme.save_in_conf(config_dir, cli_opts.reload_in)
|
theme.save_in_conf(config_dir, cli_opts.reload_in, cli_opts.config_file_name)
|
||||||
|
|
||||||
|
|
||||||
def main(args: List[str]) -> None:
|
def main(args: List[str]) -> None:
|
||||||
|
|||||||
@@ -155,12 +155,12 @@ def develop() -> None:
|
|||||||
import sys
|
import sys
|
||||||
src = sys.argv[-1]
|
src = sys.argv[-1]
|
||||||
sig_loader = LoadSignature()
|
sig_loader = LoadSignature()
|
||||||
with open(src + '.sig', 'wb') as f:
|
with open(f'{src}.sig', 'wb') as f:
|
||||||
for chunk in signature_of_file(src):
|
for chunk in signature_of_file(src):
|
||||||
sig_loader.add_chunk(chunk)
|
sig_loader.add_chunk(chunk)
|
||||||
f.write(chunk)
|
f.write(chunk)
|
||||||
sig_loader.commit()
|
sig_loader.commit()
|
||||||
with open(src + '.delta', 'wb') as f, PatchFile(src, src + '.output') as patcher:
|
with open(f'{src}.delta', 'wb') as f, PatchFile(src, f'{src}.output') as patcher:
|
||||||
for chunk in delta_for_file(src, sig_loader.signature):
|
for chunk in delta_for_file(src, sig_loader.signature):
|
||||||
f.write(chunk)
|
f.write(chunk)
|
||||||
patcher.write(chunk)
|
patcher.write(chunk)
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ def render_path_in_width(path: str, width: int) -> str:
|
|||||||
if wcswidth(path) <= width:
|
if wcswidth(path) <= width:
|
||||||
return path
|
return path
|
||||||
x = truncate_point_for_length(path, width - 1)
|
x = truncate_point_for_length(path, width - 1)
|
||||||
return path[:x] + '…'
|
return f'{path[:x]}…'
|
||||||
|
|
||||||
|
|
||||||
def render_seconds(val: float) -> str:
|
def render_seconds(val: float) -> str:
|
||||||
|
|||||||
@@ -331,7 +331,7 @@ class Dircolors:
|
|||||||
# change .xyz to *.xyz
|
# change .xyz to *.xyz
|
||||||
yield '*' + pair[0], pair[1]
|
yield '*' + pair[0], pair[1]
|
||||||
|
|
||||||
return ':'.join('%s=%s' % pair for pair in gen_pairs())
|
return ':'.join('{}={}'.format(*pair) for pair in gen_pairs())
|
||||||
|
|
||||||
def _format_code(self, text: str, code: str) -> str:
|
def _format_code(self, text: str, code: str) -> str:
|
||||||
val = self.codes.get(code)
|
val = self.codes.get(code)
|
||||||
|
|||||||
@@ -189,7 +189,9 @@ def render_image(
|
|||||||
m: ImageData,
|
m: ImageData,
|
||||||
available_width: int, available_height: int,
|
available_width: int, available_height: int,
|
||||||
scale_up: bool,
|
scale_up: bool,
|
||||||
only_first_frame: bool = False
|
only_first_frame: bool = False,
|
||||||
|
remove_alpha: str = '',
|
||||||
|
flip: bool = False, flop: bool = False,
|
||||||
) -> RenderedImage:
|
) -> RenderedImage:
|
||||||
import tempfile
|
import tempfile
|
||||||
has_multiple_frames = len(m) > 1
|
has_multiple_frames = len(m) > 1
|
||||||
@@ -202,7 +204,15 @@ def render_image(
|
|||||||
if exe is None:
|
if exe is None:
|
||||||
raise OSError('Failed to find the ImageMagick convert executable, make sure it is present in PATH')
|
raise OSError('Failed to find the ImageMagick convert executable, make sure it is present in PATH')
|
||||||
cmd = [exe]
|
cmd = [exe]
|
||||||
cmd += ['-background', 'none', '--', path]
|
if remove_alpha:
|
||||||
|
cmd += ['-background', remove_alpha, '-alpha', 'remove']
|
||||||
|
else:
|
||||||
|
cmd += ['-background', 'none']
|
||||||
|
if flip:
|
||||||
|
cmd.append('-flip')
|
||||||
|
if flop:
|
||||||
|
cmd.append('-flop')
|
||||||
|
cmd += ['--', path]
|
||||||
if only_first_frame and has_multiple_frames:
|
if only_first_frame and has_multiple_frames:
|
||||||
cmd[-1] += '[0]'
|
cmd[-1] += '[0]'
|
||||||
cmd.append('-auto-orient')
|
cmd.append('-auto-orient')
|
||||||
@@ -287,12 +297,15 @@ def render_as_single_image(
|
|||||||
path: str, m: ImageData,
|
path: str, m: ImageData,
|
||||||
available_width: int, available_height: int,
|
available_width: int, available_height: int,
|
||||||
scale_up: bool,
|
scale_up: bool,
|
||||||
tdir: Optional[str] = None
|
tdir: Optional[str] = None,
|
||||||
|
remove_alpha: str = '', flip: bool = False, flop: bool = False,
|
||||||
) -> Tuple[str, int, int]:
|
) -> Tuple[str, int, int]:
|
||||||
import tempfile
|
import tempfile
|
||||||
fd, output = tempfile.mkstemp(prefix='icat-', suffix=f'.{m.mode}', dir=tdir)
|
fd, output = tempfile.mkstemp(prefix='icat-', suffix=f'.{m.mode}', dir=tdir)
|
||||||
os.close(fd)
|
os.close(fd)
|
||||||
result = render_image(path, output, m, available_width, available_height, scale_up, only_first_frame=True)
|
result = render_image(
|
||||||
|
path, output, m, available_width, available_height, scale_up,
|
||||||
|
only_first_frame=True, remove_alpha=remove_alpha, flip=flip, flop=flop)
|
||||||
os.rename(result.frames[0].path, output)
|
os.rename(result.frames[0].path, output)
|
||||||
return output, result.width, result.height
|
return output, result.width, result.height
|
||||||
|
|
||||||
|
|||||||
@@ -110,14 +110,16 @@ class TermManager:
|
|||||||
|
|
||||||
class MouseButton(IntFlag):
|
class MouseButton(IntFlag):
|
||||||
NONE, LEFT, MIDDLE, RIGHT, FOURTH, FIFTH = 0, 1, 2, 4, 8, 16
|
NONE, LEFT, MIDDLE, RIGHT, FOURTH, FIFTH = 0, 1, 2, 4, 8, 16
|
||||||
|
WHEEL_UP, WHEEL_DOWN = -1, -2
|
||||||
|
|
||||||
|
|
||||||
bmap = {0: MouseButton.LEFT, 1: MouseButton.MIDDLE, 2: MouseButton.RIGHT}
|
bmap = {0: MouseButton.LEFT, 1: MouseButton.MIDDLE, 2: MouseButton.RIGHT}
|
||||||
MOTION_INDICATOR = 1 << 5
|
|
||||||
EXTRA_BUTTON_INDICATOR = 1 << 6
|
|
||||||
SHIFT_INDICATOR = 1 << 2
|
SHIFT_INDICATOR = 1 << 2
|
||||||
ALT_INDICATOR = 1 << 3
|
ALT_INDICATOR = 1 << 3
|
||||||
CTRL_INDICATOR = 1 << 4
|
CTRL_INDICATOR = 1 << 4
|
||||||
|
MOTION_INDICATOR = 1 << 5
|
||||||
|
SCROLL_BUTTON_INDICATOR = 1 << 6
|
||||||
|
EXTRA_BUTTON_INDICATOR = 1 << 7
|
||||||
|
|
||||||
|
|
||||||
class EventType(Enum):
|
class EventType(Enum):
|
||||||
@@ -149,7 +151,9 @@ def decode_sgr_mouse(text: str, screen_size: ScreenSize) -> MouseEvent:
|
|||||||
buttons: MouseButton = MouseButton.NONE
|
buttons: MouseButton = MouseButton.NONE
|
||||||
cb3 = cb & 3
|
cb3 = cb & 3
|
||||||
if cb3 != 3:
|
if cb3 != 3:
|
||||||
if cb & EXTRA_BUTTON_INDICATOR:
|
if cb & SCROLL_BUTTON_INDICATOR:
|
||||||
|
buttons = MouseButton.WHEEL_DOWN if cb3 & 1 else MouseButton.WHEEL_UP
|
||||||
|
elif cb & EXTRA_BUTTON_INDICATOR:
|
||||||
buttons |= MouseButton.FIFTH if cb3 & 1 else MouseButton.FOURTH
|
buttons |= MouseButton.FIFTH if cb3 & 1 else MouseButton.FOURTH
|
||||||
else:
|
else:
|
||||||
buttons |= bmap[cb3]
|
buttons |= bmap[cb3]
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ import sys
|
|||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from typing import (
|
from typing import IO, Any, Callable, Dict, Generator, Optional, TypeVar, Union
|
||||||
IO, Any, Callable, Dict, Generator, Optional, TypeVar, Union
|
|
||||||
)
|
|
||||||
|
|
||||||
from kitty.fast_data_types import Color
|
from kitty.fast_data_types import Color
|
||||||
from kitty.rgb import color_as_sharp, to_color
|
from kitty.rgb import color_as_sharp, to_color
|
||||||
from kitty.typing import GraphicsCommandType, HandlerType, ScreenSize
|
from kitty.typing import (
|
||||||
|
GraphicsCommandType, HandlerType, ScreenSize, UnderlineLiteral
|
||||||
|
)
|
||||||
|
|
||||||
from .operations_stub import CMD
|
from .operations_stub import CMD
|
||||||
|
|
||||||
@@ -163,14 +163,11 @@ def set_scrolling_region(screen_size: Optional['ScreenSize'] = None, top: Option
|
|||||||
|
|
||||||
@cmd
|
@cmd
|
||||||
def scroll_screen(amt: int = 1) -> str:
|
def scroll_screen(amt: int = 1) -> str:
|
||||||
return '\033[' + str(abs(amt)) + ('T' if amt < 0 else 'S')
|
return f'\033[{abs(amt)}{"T" if amt < 0 else "S"}'
|
||||||
|
|
||||||
|
|
||||||
STANDARD_COLORS = {name: i for i, name in enumerate(
|
STANDARD_COLORS = {'black': 0, 'red': 1, 'green': 2, 'yellow': 3, 'blue': 4, 'magenta': 5, 'cyan': 6, 'gray': 7, 'white': 7}
|
||||||
'black red green yellow blue magenta cyan gray'.split())}
|
UNDERLINE_STYLES = {'straight': 1, 'double': 2, 'curly': 3, 'dotted': 4, 'dashed': 5}
|
||||||
STANDARD_COLORS['white'] = STANDARD_COLORS['gray']
|
|
||||||
UNDERLINE_STYLES = {name: i + 1 for i, name in enumerate(
|
|
||||||
'straight double curly'.split())}
|
|
||||||
|
|
||||||
|
|
||||||
ColorSpec = Union[int, str, Color]
|
ColorSpec = Union[int, str, Color]
|
||||||
@@ -217,7 +214,7 @@ def styled(
|
|||||||
bg_intense: bool = False,
|
bg_intense: bool = False,
|
||||||
italic: Optional[bool] = None,
|
italic: Optional[bool] = None,
|
||||||
bold: Optional[bool] = None,
|
bold: Optional[bool] = None,
|
||||||
underline: Optional[str] = None,
|
underline: Optional[UnderlineLiteral] = None,
|
||||||
underline_color: Optional[ColorSpec] = None,
|
underline_color: Optional[ColorSpec] = None,
|
||||||
reverse: Optional[bool] = None,
|
reverse: Optional[bool] = None,
|
||||||
dim: Optional[bool] = None,
|
dim: Optional[bool] = None,
|
||||||
@@ -291,7 +288,7 @@ class MouseTracking(Enum):
|
|||||||
full = auto()
|
full = auto()
|
||||||
|
|
||||||
|
|
||||||
def init_state(alternate_screen: bool = True, mouse_tracking: MouseTracking = MouseTracking.none) -> str:
|
def init_state(alternate_screen: bool = True, mouse_tracking: MouseTracking = MouseTracking.none, kitty_keyboard_mode: bool = True) -> str:
|
||||||
sc = SAVE_CURSOR if alternate_screen else ''
|
sc = SAVE_CURSOR if alternate_screen else ''
|
||||||
ans = (
|
ans = (
|
||||||
S7C1T + sc + SAVE_PRIVATE_MODE_VALUES + reset_mode(Mode.LNM) +
|
S7C1T + sc + SAVE_PRIVATE_MODE_VALUES + reset_mode(Mode.LNM) +
|
||||||
@@ -314,7 +311,10 @@ def init_state(alternate_screen: bool = True, mouse_tracking: MouseTracking = Mo
|
|||||||
ans += set_mode(Mode.MOUSE_MOTION_TRACKING)
|
ans += set_mode(Mode.MOUSE_MOTION_TRACKING)
|
||||||
elif mouse_tracking is MouseTracking.full:
|
elif mouse_tracking is MouseTracking.full:
|
||||||
ans += set_mode(Mode.MOUSE_MOVE_TRACKING)
|
ans += set_mode(Mode.MOUSE_MOVE_TRACKING)
|
||||||
ans += '\033[>31u' # extended keyboard mode
|
if kitty_keyboard_mode:
|
||||||
|
ans += '\033[>31u' # extended keyboard mode
|
||||||
|
else:
|
||||||
|
ans += '\033[>u' # legacy keyboard mode
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
@@ -465,7 +465,7 @@ def as_type_stub() -> str:
|
|||||||
for name, func in all_cmds.items():
|
for name, func in all_cmds.items():
|
||||||
args = ', '.join(func_sig(func))
|
args = ', '.join(func_sig(func))
|
||||||
if args:
|
if args:
|
||||||
args = ', ' + args
|
args = f', {args}'
|
||||||
methods.append(f' def {name}(self{args}) -> str: pass')
|
methods.append(f' def {name}(self{args}) -> str: pass')
|
||||||
ans += ['', '', 'class CMD:'] + methods
|
ans += ['', '', 'class CMD:'] + methods
|
||||||
|
|
||||||
|
|||||||
@@ -192,7 +192,7 @@ class Table:
|
|||||||
if w < 2:
|
if w < 2:
|
||||||
text += ' ' * (2 - w)
|
text += ' ' * (2 - w)
|
||||||
if len(desc) > space_for_desc:
|
if len(desc) > space_for_desc:
|
||||||
text += desc[:space_for_desc - 1] + '…'
|
text += f'{desc[:space_for_desc - 1]}…'
|
||||||
else:
|
else:
|
||||||
text += desc
|
text += desc
|
||||||
extra = space_for_desc - len(desc)
|
extra = space_for_desc - len(desc)
|
||||||
|
|||||||
2
kittens/unicode_input/names.h
generated
2
kittens/unicode_input/names.h
generated
@@ -1,4 +1,4 @@
|
|||||||
// unicode data, built from the unicode standard on: 2021-10-07
|
// unicode data, built from the unicode standard on: 2022-01-05
|
||||||
// see gen-wcwidth.py
|
// see gen-wcwidth.py
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "data-types.h"
|
#include "data-types.h"
|
||||||
|
|||||||
@@ -99,10 +99,7 @@ def as_rst() -> str:
|
|||||||
|
|
||||||
for action in allg[group]:
|
for action in allg[group]:
|
||||||
a('')
|
a('')
|
||||||
a(f'.. _action-{action.name}:')
|
a(f'.. action:: {action.name}')
|
||||||
a('')
|
|
||||||
a(action.name)
|
|
||||||
a('+' * len(action.name))
|
|
||||||
a('')
|
a('')
|
||||||
a(action.short_help)
|
a(action.short_help)
|
||||||
a('')
|
a('')
|
||||||
|
|||||||
171
kitty/boss.py
Executable file → Normal file
171
kitty/boss.py
Executable file → Normal file
@@ -5,12 +5,14 @@ import atexit
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
|
from time import monotonic
|
||||||
from typing import (
|
from typing import (
|
||||||
Any, Callable, Container, Dict, Iterable, Iterator, List, Optional, Tuple,
|
Any, Callable, Container, Dict, Iterable, Iterator, List, Optional,
|
||||||
Union
|
Sequence, Tuple, Union
|
||||||
)
|
)
|
||||||
from weakref import WeakValueDictionary
|
from weakref import WeakValueDictionary
|
||||||
|
|
||||||
@@ -39,7 +41,7 @@ from .fast_data_types import (
|
|||||||
set_application_quit_request, set_background_image, set_boss,
|
set_application_quit_request, set_background_image, set_boss,
|
||||||
set_clipboard_string, set_in_sequence_mode, set_options,
|
set_clipboard_string, set_in_sequence_mode, set_options,
|
||||||
set_os_window_size, set_os_window_title, thread_write, toggle_fullscreen,
|
set_os_window_size, set_os_window_title, thread_write, toggle_fullscreen,
|
||||||
toggle_maximized
|
toggle_maximized, toggle_secure_input
|
||||||
)
|
)
|
||||||
from .key_encoding import get_name_to_functional_number_map
|
from .key_encoding import get_name_to_functional_number_map
|
||||||
from .keys import get_shortcut, shortcut_matches
|
from .keys import get_shortcut, shortcut_matches
|
||||||
@@ -229,7 +231,6 @@ class Boss:
|
|||||||
global_shortcuts: Dict[str, SingleKey]
|
global_shortcuts: Dict[str, SingleKey]
|
||||||
):
|
):
|
||||||
set_layout_options(opts)
|
set_layout_options(opts)
|
||||||
self.cocoa_application_launched = False
|
|
||||||
self.clipboard_buffers: Dict[str, str] = {}
|
self.clipboard_buffers: Dict[str, str] = {}
|
||||||
self.update_check_process: Optional['PopenType[bytes]'] = None
|
self.update_check_process: Optional['PopenType[bytes]'] = None
|
||||||
self.window_id_map: WeakValueDictionary[int, Window] = WeakValueDictionary()
|
self.window_id_map: WeakValueDictionary[int, Window] = WeakValueDictionary()
|
||||||
@@ -273,9 +274,9 @@ class Boss:
|
|||||||
for sc in self.global_shortcuts.values():
|
for sc in self.global_shortcuts.values():
|
||||||
self.keymap.pop(sc, None)
|
self.keymap.pop(sc, None)
|
||||||
|
|
||||||
def startup_first_child(self, os_window_id: Optional[int]) -> None:
|
def startup_first_child(self, os_window_id: Optional[int], startup_sessions: Sequence[Optional[Session]] = ()) -> None:
|
||||||
startup_sessions = create_sessions(get_options(), self.args, default_session=get_options().startup_session)
|
si = startup_sessions or create_sessions(get_options(), self.args, default_session=get_options().startup_session)
|
||||||
for startup_session in startup_sessions:
|
for startup_session in si:
|
||||||
self.add_os_window(startup_session, os_window_id=os_window_id)
|
self.add_os_window(startup_session, os_window_id=os_window_id)
|
||||||
os_window_id = None
|
os_window_id = None
|
||||||
if self.args.start_as != 'normal':
|
if self.args.start_as != 'normal':
|
||||||
@@ -546,6 +547,10 @@ class Boss:
|
|||||||
from .cli_stub import CLIOptions
|
from .cli_stub import CLIOptions
|
||||||
startup_id = data.get('startup_id')
|
startup_id = data.get('startup_id')
|
||||||
args, rest = parse_args(data['args'][1:], result_class=CLIOptions)
|
args, rest = parse_args(data['args'][1:], result_class=CLIOptions)
|
||||||
|
cmdline_args_for_open = data.get('cmdline_args_for_open')
|
||||||
|
if cmdline_args_for_open:
|
||||||
|
self.launch_urls(*cmdline_args_for_open, no_replace_window=True)
|
||||||
|
return None
|
||||||
args.args = rest
|
args.args = rest
|
||||||
opts = create_opts(args)
|
opts = create_opts(args)
|
||||||
if args.session == '-':
|
if args.session == '-':
|
||||||
@@ -653,18 +658,13 @@ class Boss:
|
|||||||
msg = _('Are you sure you want to close this window?')
|
msg = _('Are you sure you want to close this window?')
|
||||||
if window.has_running_program:
|
if window.has_running_program:
|
||||||
msg += ' ' + _('It is running a program.')
|
msg += ' ' + _('It is running a program.')
|
||||||
self._run_kitten(
|
self.confirm(msg, self.handle_close_window_confirmation, window.id, window=window)
|
||||||
'ask', ['--type=yesno', '--message', msg],
|
|
||||||
window=window,
|
|
||||||
custom_callback=partial(self.handle_close_window_confirmation, window.id)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self.mark_window_for_close(window)
|
self.mark_window_for_close(window)
|
||||||
|
|
||||||
def handle_close_window_confirmation(self, window_id: int, data: Dict[str, Any], *a: Any) -> None:
|
def handle_close_window_confirmation(self, allowed: bool, window_id: int) -> None:
|
||||||
if data['response'] != 'y':
|
if allowed:
|
||||||
return
|
self.mark_window_for_close(window_id)
|
||||||
self.mark_window_for_close(window_id)
|
|
||||||
|
|
||||||
@ac('tab', 'Close the current tab')
|
@ac('tab', 'Close the current tab')
|
||||||
def close_tab(self, tab: Optional[Tab] = None) -> None:
|
def close_tab(self, tab: Optional[Tab] = None) -> None:
|
||||||
@@ -672,13 +672,35 @@ class Boss:
|
|||||||
if tab:
|
if tab:
|
||||||
self.confirm_tab_close(tab)
|
self.confirm_tab_close(tab)
|
||||||
|
|
||||||
def confirm(self, msg: str, callback: Callable[..., None], *args: Any,
|
def confirm(
|
||||||
window: Optional[Window] = None, confirm_on_cancel: bool = False) -> None:
|
self, msg: str, # can contain newlines and ANSI formatting
|
||||||
|
callback: Callable[..., None], # called with True or False and *args
|
||||||
|
*args: Any, # passed to the callback function
|
||||||
|
window: Optional[Window] = None, # the window associated with the confirmation
|
||||||
|
confirm_on_cancel: bool = False, # on closing window
|
||||||
|
confirm_on_accept: bool = True, # on pressing enter
|
||||||
|
) -> None:
|
||||||
def callback_(res: Dict[str, Any], x: int, boss: Boss) -> None:
|
def callback_(res: Dict[str, Any], x: int, boss: Boss) -> None:
|
||||||
callback(res.get('response') == 'y', *args)
|
callback(res.get('response') == 'y', *args)
|
||||||
self._run_kitten('ask', ['--type=yesno', '--message', msg],
|
self._run_kitten('ask', ['--type=yesno', '--message', msg, '--default', 'y' if confirm_on_accept else 'n'],
|
||||||
window=window, custom_callback=callback_, default_data={'response': 'y' if confirm_on_cancel else 'n'})
|
window=window, custom_callback=callback_, default_data={'response': 'y' if confirm_on_cancel else 'n'})
|
||||||
|
|
||||||
|
def choose(
|
||||||
|
self, msg: str, # can contain newlines and ANSI formatting
|
||||||
|
callback: Callable[..., None], # called with the choice or empty string when aborted
|
||||||
|
*choices: str, # The choices, see the help for the ask kitten for format of a choice
|
||||||
|
window: Optional[Window] = None, # the window associated with the confirmation
|
||||||
|
default: str = '', # the default choice when the user presses Enter
|
||||||
|
) -> None:
|
||||||
|
def callback_(res: Dict[str, Any], x: int, boss: Boss) -> None:
|
||||||
|
callback(res.get('response') or '')
|
||||||
|
cmd = ['--type=choices', '--message', msg]
|
||||||
|
if default:
|
||||||
|
cmd += ['-d', default]
|
||||||
|
for c in choices:
|
||||||
|
cmd += ['-c', c]
|
||||||
|
self._run_kitten('ask', cmd, window=window, custom_callback=callback_, default_data={'response': ''})
|
||||||
|
|
||||||
def confirm_tab_close(self, tab: Tab) -> None:
|
def confirm_tab_close(self, tab: Tab) -> None:
|
||||||
x = get_options().confirm_os_window_close
|
x = get_options().confirm_os_window_close
|
||||||
num = tab.number_of_windows_with_running_programs if x < 0 else len(tab)
|
num = tab.number_of_windows_with_running_programs if x < 0 else len(tab)
|
||||||
@@ -721,11 +743,22 @@ class Boss:
|
|||||||
def toggle_maximized(self, os_window_id: int = 0) -> None:
|
def toggle_maximized(self, os_window_id: int = 0) -> None:
|
||||||
toggle_maximized(os_window_id)
|
toggle_maximized(os_window_id)
|
||||||
|
|
||||||
|
@ac('misc', 'Toggle macOS secure keyboard entry')
|
||||||
|
def toggle_macos_secure_keyboard_entry(self) -> None:
|
||||||
|
toggle_secure_input()
|
||||||
|
|
||||||
def start(self, first_os_window_id: int) -> None:
|
def start(self, first_os_window_id: int) -> None:
|
||||||
if not getattr(self, 'io_thread_started', False):
|
if not getattr(self, 'io_thread_started', False):
|
||||||
self.child_monitor.start()
|
self.child_monitor.start()
|
||||||
self.io_thread_started = True
|
self.io_thread_started = True
|
||||||
self.startup_first_child(first_os_window_id)
|
urls: List[str] = getattr(sys, 'cmdline_args_for_open', [])
|
||||||
|
sess = create_sessions(get_options(), self.args, special_window=SpecialWindow([kitty_exe(), '+runpy', 'input()']))
|
||||||
|
if urls:
|
||||||
|
delattr(sys, 'cmdline_args_for_open')
|
||||||
|
self.startup_first_child(first_os_window_id, startup_sessions=tuple(sess))
|
||||||
|
self.launch_urls(*urls)
|
||||||
|
else:
|
||||||
|
self.startup_first_child(first_os_window_id)
|
||||||
|
|
||||||
if get_options().update_check_interval > 0 and not hasattr(self, 'update_check_started'):
|
if get_options().update_check_interval > 0 and not hasattr(self, 'update_check_started'):
|
||||||
from .update_check import run_update_check
|
from .update_check import run_update_check
|
||||||
@@ -758,7 +791,8 @@ class Boss:
|
|||||||
map kitty_mod+f11 clear_terminal scrollback active
|
map kitty_mod+f11 clear_terminal scrollback active
|
||||||
# Scroll the contents of the screen into the scrollback
|
# Scroll the contents of the screen into the scrollback
|
||||||
map kitty_mod+f12 clear_terminal scroll active
|
map kitty_mod+f12 clear_terminal scroll active
|
||||||
|
# Clear everything up to the line with the cursor
|
||||||
|
map kitty_mod+f9 clear_terminal to_cursor active
|
||||||
''')
|
''')
|
||||||
def clear_terminal(self, action: str, only_active: bool) -> None:
|
def clear_terminal(self, action: str, only_active: bool) -> None:
|
||||||
if only_active:
|
if only_active:
|
||||||
@@ -771,8 +805,10 @@ class Boss:
|
|||||||
reset = action == 'reset'
|
reset = action == 'reset'
|
||||||
how = 3 if action == 'scrollback' else 2
|
how = 3 if action == 'scrollback' else 2
|
||||||
for w in windows:
|
for w in windows:
|
||||||
if action == 'scroll':
|
if action in ('to_cursor', 'scroll'):
|
||||||
w.screen.scroll_until_cursor()
|
w.screen.scroll_until_cursor_prompt()
|
||||||
|
if action == 'to_cursor':
|
||||||
|
w.screen.clear_scrollback()
|
||||||
continue
|
continue
|
||||||
w.screen.cursor.x = w.screen.cursor.y = 0
|
w.screen.cursor.x = w.screen.cursor.y = 0
|
||||||
if reset:
|
if reset:
|
||||||
@@ -1185,7 +1221,12 @@ class Boss:
|
|||||||
if w is not None:
|
if w is not None:
|
||||||
text = data.decode('utf-8', 'replace')
|
text = data.decode('utf-8', 'replace')
|
||||||
if mime == 'text/uri-list':
|
if mime == 'text/uri-list':
|
||||||
text = '\n'.join(parse_uri_list(text))
|
urls = parse_uri_list(text)
|
||||||
|
if w.at_prompt:
|
||||||
|
import shlex
|
||||||
|
text = ' '.join(map(shlex.quote, urls))
|
||||||
|
else:
|
||||||
|
text = '\n'.join(urls)
|
||||||
w.paste(text)
|
w.paste(text)
|
||||||
|
|
||||||
@ac('win', 'Focus the nth OS window')
|
@ac('win', 'Focus the nth OS window')
|
||||||
@@ -1414,12 +1455,14 @@ class Boss:
|
|||||||
def set_tab_title(self) -> None:
|
def set_tab_title(self) -> None:
|
||||||
tab = self.active_tab
|
tab = self.active_tab
|
||||||
if tab:
|
if tab:
|
||||||
args = ['--name=tab-title', '--message', _('Enter the new title for this tab below.'), 'do_set_tab_title', str(tab.id)]
|
args = [
|
||||||
|
'--name=tab-title', '--message', _('Enter the new title for this tab below.'),
|
||||||
|
'--default', tab.name or tab.title, 'do_set_tab_title', str(tab.id)]
|
||||||
self._run_kitten('ask', args)
|
self._run_kitten('ask', args)
|
||||||
|
|
||||||
def do_set_tab_title(self, title: str, tab_id: int) -> None:
|
def do_set_tab_title(self, title: str, tab_id: int) -> None:
|
||||||
tm = self.active_tab_manager
|
tm = self.active_tab_manager
|
||||||
if tm is not None and title:
|
if tm is not None:
|
||||||
tab_id = int(tab_id)
|
tab_id = int(tab_id)
|
||||||
for tab in tm.tabs:
|
for tab in tm.tabs:
|
||||||
if tab.id == tab_id:
|
if tab.id == tab_id:
|
||||||
@@ -1788,6 +1831,8 @@ class Boss:
|
|||||||
self._create_tab(list(args), cwd_from=cwd_from)
|
self._create_tab(list(args), cwd_from=cwd_from)
|
||||||
|
|
||||||
def new_tab_with_wd(self, wd: str) -> None:
|
def new_tab_with_wd(self, wd: str) -> None:
|
||||||
|
if not self.os_window_map:
|
||||||
|
self.add_os_window()
|
||||||
special_window = SpecialWindow(None, cwd=wd)
|
special_window = SpecialWindow(None, cwd=wd)
|
||||||
self._new_tab(special_window)
|
self._new_tab(special_window)
|
||||||
|
|
||||||
@@ -2072,9 +2117,10 @@ class Boss:
|
|||||||
|
|
||||||
def choose_entry(
|
def choose_entry(
|
||||||
self, title: str, entries: Iterable[Tuple[Union[_T, str, None], str]],
|
self, title: str, entries: Iterable[Tuple[Union[_T, str, None], str]],
|
||||||
callback: Callable[[Union[_T, str, None]], None]
|
callback: Callable[[Union[_T, str, None]], None],
|
||||||
|
subtitle: str = ''
|
||||||
) -> Optional[Window]:
|
) -> Optional[Window]:
|
||||||
lines = [title, '']
|
lines = [title, subtitle, '']
|
||||||
idx_map: List[Union[_T, str]] = []
|
idx_map: List[Union[_T, str]] = []
|
||||||
ans: Union[str, _T, None] = None
|
ans: Union[str, _T, None] = None
|
||||||
fmt = ': {1}'
|
fmt = ': {1}'
|
||||||
@@ -2110,8 +2156,19 @@ class Boss:
|
|||||||
if tab.id == ans:
|
if tab.id == ans:
|
||||||
self.set_active_tab(tab)
|
self.set_active_tab(tab)
|
||||||
|
|
||||||
|
def format_tab_title(tab: Tab) -> str:
|
||||||
|
w = 'windows' if tab.num_window_groups > 1 else 'window'
|
||||||
|
return f'{tab.name or tab.title} [{tab.num_window_groups} {w}]'
|
||||||
|
|
||||||
ct = self.active_tab
|
ct = self.active_tab
|
||||||
self.choose_entry('Choose a tab to switch to', ((t.id, t.title) for t in self.all_tabs if t is not ct), chosen)
|
st = ''
|
||||||
|
if ct is not None:
|
||||||
|
st = f'Current tab: {format_tab_title(ct)}'
|
||||||
|
self.choose_entry(
|
||||||
|
'Choose a tab to switch to',
|
||||||
|
((t.id, format_tab_title(t)) for t in self.all_tabs if t is not ct),
|
||||||
|
chosen, subtitle=st
|
||||||
|
)
|
||||||
|
|
||||||
@ac('win', '''
|
@ac('win', '''
|
||||||
Detach a window, moving it to another tab or OS Window
|
Detach a window, moving it to another tab or OS Window
|
||||||
@@ -2173,8 +2230,6 @@ class Boss:
|
|||||||
|
|
||||||
# Can be called with kitty -o "map f1 send_test_notification"
|
# Can be called with kitty -o "map f1 send_test_notification"
|
||||||
def send_test_notification(self) -> None:
|
def send_test_notification(self) -> None:
|
||||||
from time import monotonic
|
|
||||||
|
|
||||||
from .notify import notify
|
from .notify import notify
|
||||||
now = monotonic()
|
now = monotonic()
|
||||||
ident = f'test-notify-{now}'
|
ident = f'test-notify-{now}'
|
||||||
@@ -2196,23 +2251,45 @@ class Boss:
|
|||||||
output = '\n'.join(f'{k}={v}' for k, v in os.environ.items())
|
output = '\n'.join(f'{k}={v}' for k, v in os.environ.items())
|
||||||
self.display_scrollback(w, output, title=_('Current kitty env vars'), report_cursor=False)
|
self.display_scrollback(w, output, title=_('Current kitty env vars'), report_cursor=False)
|
||||||
|
|
||||||
def open_file(self, path: str) -> None:
|
def launch_urls(self, *urls: str, no_replace_window: bool = False) -> None:
|
||||||
if path == ":cocoa::application launched::":
|
from .launch import force_window_launch
|
||||||
self.cocoa_application_launched = True
|
from .open_actions import actions_for_launch
|
||||||
return
|
actions: List[KeyAction] = []
|
||||||
|
failures = []
|
||||||
def new_os_window() -> None:
|
for url in urls:
|
||||||
self.new_os_window(path)
|
uactions = tuple(actions_for_launch(url))
|
||||||
|
if uactions:
|
||||||
if self.cocoa_application_launched or not self.os_window_map:
|
actions.extend(uactions)
|
||||||
return new_os_window()
|
else:
|
||||||
|
failures.append(url)
|
||||||
tab = self.active_tab
|
tab = self.active_tab
|
||||||
if tab is None:
|
if tab is not None:
|
||||||
return new_os_window()
|
w = tab.active_window
|
||||||
w = tab.active_window
|
else:
|
||||||
self.new_window(path)
|
w = None
|
||||||
if w is not None:
|
needs_window_replaced = False
|
||||||
tab.remove_window(w)
|
if not no_replace_window and not get_options().startup_session:
|
||||||
|
if w is not None and w.id == 1 and monotonic() - w.started_at < 2 and len(tuple(self.all_windows)) == 1:
|
||||||
|
# first window, soon after startup replace it
|
||||||
|
needs_window_replaced = True
|
||||||
|
|
||||||
|
def clear_initial_window() -> None:
|
||||||
|
if needs_window_replaced and tab is not None and w is not None:
|
||||||
|
tab.remove_window(w)
|
||||||
|
|
||||||
|
if failures:
|
||||||
|
with force_window_launch(needs_window_replaced):
|
||||||
|
from kittens.tui.operations import styled
|
||||||
|
spec = '\n '.join(styled(u, fg='red') for u in failures)
|
||||||
|
self.launch('--hold', '--type=os-window', kitty_exe(), '+runpy', fr'print("Unknown URL type, cannot open:\n\n ", {spec!r});')
|
||||||
|
clear_initial_window()
|
||||||
|
needs_window_replaced = False
|
||||||
|
if actions:
|
||||||
|
with force_window_launch(needs_window_replaced):
|
||||||
|
self.dispatch_action(actions.pop(0))
|
||||||
|
clear_initial_window()
|
||||||
|
if actions:
|
||||||
|
self.drain_actions(actions)
|
||||||
|
|
||||||
@ac('debug', 'Show the effective configuration kitty is running with')
|
@ac('debug', 'Show the effective configuration kitty is running with')
|
||||||
def debug_config(self) -> None:
|
def debug_config(self) -> None:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#version GLSL_VERSION
|
#version GLSL_VERSION
|
||||||
#define WHICH_PROGRAM
|
#define {WHICH_PROGRAM}
|
||||||
#define NOT_TRANSPARENT
|
#define NOT_TRANSPARENT
|
||||||
|
|
||||||
#if defined(SIMPLE) || defined(BACKGROUND) || defined(SPECIAL)
|
#if defined(SIMPLE) || defined(BACKGROUND) || defined(SPECIAL)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#version GLSL_VERSION
|
#version GLSL_VERSION
|
||||||
#define WHICH_PROGRAM
|
#define {WHICH_PROGRAM}
|
||||||
#define NOT_TRANSPARENT
|
#define NOT_TRANSPARENT
|
||||||
#define DECORATION_SHIFT {DECORATION_SHIFT}
|
#define DECORATION_SHIFT {DECORATION_SHIFT}
|
||||||
#define REVERSE_SHIFT {REVERSE_SHIFT}
|
#define REVERSE_SHIFT {REVERSE_SHIFT}
|
||||||
@@ -79,8 +79,8 @@ const uint COLOR_MASK = uint(0x4000);
|
|||||||
const uint ZERO = uint(0);
|
const uint ZERO = uint(0);
|
||||||
const uint ONE = uint(1);
|
const uint ONE = uint(1);
|
||||||
const uint TWO = uint(2);
|
const uint TWO = uint(2);
|
||||||
const uint THREE = uint(3);
|
const uint STRIKE_SPRITE_INDEX = uint({STRIKE_SPRITE_INDEX});
|
||||||
const uint FOUR = uint(4);
|
const uint DECORATION_MASK = uint({DECORATION_MASK});
|
||||||
|
|
||||||
vec3 color_to_vec(uint c) {
|
vec3 color_to_vec(uint c) {
|
||||||
uint r, g, b;
|
uint r, g, b;
|
||||||
@@ -193,8 +193,8 @@ void main() {
|
|||||||
foreground = choose_color(float(is_selected & ONE), selection_color, foreground);
|
foreground = choose_color(float(is_selected & ONE), selection_color, foreground);
|
||||||
decoration_fg = choose_color(float(is_selected & ONE), selection_color, decoration_fg);
|
decoration_fg = choose_color(float(is_selected & ONE), selection_color, decoration_fg);
|
||||||
// Underline and strike through (rendered via sprites)
|
// Underline and strike through (rendered via sprites)
|
||||||
underline_pos = choose_color(in_url, to_sprite_pos(pos, url_style, ZERO, ZERO), to_sprite_pos(pos, (text_attrs >> DECORATION_SHIFT) & THREE, ZERO, ZERO));
|
underline_pos = choose_color(in_url, to_sprite_pos(pos, url_style, ZERO, ZERO), to_sprite_pos(pos, (text_attrs >> DECORATION_SHIFT) & DECORATION_MASK, ZERO, ZERO));
|
||||||
strike_pos = to_sprite_pos(pos, ((text_attrs >> STRIKE_SHIFT) & ONE) * FOUR, ZERO, ZERO);
|
strike_pos = to_sprite_pos(pos, ((text_attrs >> STRIKE_SHIFT) & ONE) * STRIKE_SPRITE_INDEX, ZERO, ZERO);
|
||||||
|
|
||||||
// Cursor
|
// Cursor
|
||||||
cursor_color_vec = vec4(color_to_vec(cursor_bg), 1.0);
|
cursor_color_vec = vec4(color_to_vec(cursor_bg), 1.0);
|
||||||
|
|||||||
@@ -553,8 +553,6 @@ pyset_iutf8(ChildMonitor *self, PyObject *args) {
|
|||||||
#undef INCREF_CHILD
|
#undef INCREF_CHILD
|
||||||
#undef DECREF_CHILD
|
#undef DECREF_CHILD
|
||||||
|
|
||||||
extern void cocoa_update_menu_bar_title(PyObject*);
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
cursor_needs_render(Window *w) {
|
cursor_needs_render(Window *w) {
|
||||||
return w->cursor_visible_at_last_render != w->render_data.screen->cursor_render_info.is_visible || w->last_cursor_x != w->render_data.screen->cursor_render_info.x || w->last_cursor_y != w->render_data.screen->cursor_render_info.y || w->last_cursor_shape != w->render_data.screen->cursor_render_info.shape;
|
return w->cursor_visible_at_last_render != w->render_data.screen->cursor_render_info.is_visible || w->last_cursor_x != w->render_data.screen->cursor_render_info.x || w->last_cursor_y != w->render_data.screen->cursor_render_info.y || w->last_cursor_shape != w->render_data.screen->cursor_render_info.shape;
|
||||||
@@ -592,7 +590,7 @@ change_menubar_title(PyObject *title UNUSED) {
|
|||||||
static PyObject *current_title = NULL;
|
static PyObject *current_title = NULL;
|
||||||
if (title != current_title) {
|
if (title != current_title) {
|
||||||
current_title = title;
|
current_title = title;
|
||||||
if (title && OPT(macos_show_window_title_in) & MENUBAR) cocoa_update_menu_bar_title(title);
|
if (title && OPT(macos_show_window_title_in) & MENUBAR) update_menu_bar_title(title);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -1001,21 +999,21 @@ static bool cocoa_pending_actions[NUM_COCOA_PENDING_ACTIONS] = {0};
|
|||||||
static bool has_cocoa_pending_actions = false;
|
static bool has_cocoa_pending_actions = false;
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char* wd;
|
char* wd;
|
||||||
char **open_files;
|
char **open_urls;
|
||||||
size_t open_files_count;
|
size_t open_urls_count;
|
||||||
size_t open_files_capacity;
|
size_t open_urls_capacity;
|
||||||
} CocoaPendingActionsData;
|
} CocoaPendingActionsData;
|
||||||
static CocoaPendingActionsData cocoa_pending_actions_data = {0};
|
static CocoaPendingActionsData cocoa_pending_actions_data = {0};
|
||||||
|
|
||||||
void
|
void
|
||||||
set_cocoa_pending_action(CocoaPendingAction action, const char *wd) {
|
set_cocoa_pending_action(CocoaPendingAction action, const char *data) {
|
||||||
if (wd) {
|
if (data) {
|
||||||
if (action == OPEN_FILE) {
|
if (action == LAUNCH_URLS) {
|
||||||
ensure_space_for(&cocoa_pending_actions_data, open_files, char*, cocoa_pending_actions_data.open_files_count + 8, open_files_capacity, 8, true);
|
ensure_space_for(&cocoa_pending_actions_data, open_urls, char*, cocoa_pending_actions_data.open_urls_count + 8, open_urls_capacity, 8, true);
|
||||||
cocoa_pending_actions_data.open_files[cocoa_pending_actions_data.open_files_count++] = strdup(wd);
|
cocoa_pending_actions_data.open_urls[cocoa_pending_actions_data.open_urls_count++] = strdup(data);
|
||||||
} else {
|
} else {
|
||||||
if (cocoa_pending_actions_data.wd) free(cocoa_pending_actions_data.wd);
|
if (cocoa_pending_actions_data.wd) free(cocoa_pending_actions_data.wd);
|
||||||
cocoa_pending_actions_data.wd = strdup(wd);
|
cocoa_pending_actions_data.wd = strdup(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cocoa_pending_actions[action] = true;
|
cocoa_pending_actions[action] = true;
|
||||||
@@ -1038,22 +1036,25 @@ process_cocoa_pending_actions(void) {
|
|||||||
if (cocoa_pending_actions[NEW_WINDOW]) { call_boss(new_window, NULL); }
|
if (cocoa_pending_actions[NEW_WINDOW]) { call_boss(new_window, NULL); }
|
||||||
if (cocoa_pending_actions[CLOSE_WINDOW]) { call_boss(close_window, NULL); }
|
if (cocoa_pending_actions[CLOSE_WINDOW]) { call_boss(close_window, NULL); }
|
||||||
if (cocoa_pending_actions[RESET_TERMINAL]) { call_boss(clear_terminal, "sO", "reset", Py_True ); }
|
if (cocoa_pending_actions[RESET_TERMINAL]) { call_boss(clear_terminal, "sO", "reset", Py_True ); }
|
||||||
|
if (cocoa_pending_actions[CLEAR_TERMINAL_AND_SCROLLBACK]) { call_boss(clear_terminal, "sO", "to_cursor", Py_True ); }
|
||||||
if (cocoa_pending_actions[RELOAD_CONFIG]) { call_boss(load_config_file, NULL); }
|
if (cocoa_pending_actions[RELOAD_CONFIG]) { call_boss(load_config_file, NULL); }
|
||||||
|
if (cocoa_pending_actions[TOGGLE_MACOS_SECURE_KEYBOARD_ENTRY]) { call_boss(toggle_macos_secure_keyboard_entry, NULL); }
|
||||||
|
if (cocoa_pending_actions[TOGGLE_FULLSCREEN]) { call_boss(toggle_fullscreen, NULL); }
|
||||||
if (cocoa_pending_actions_data.wd) {
|
if (cocoa_pending_actions_data.wd) {
|
||||||
if (cocoa_pending_actions[NEW_OS_WINDOW_WITH_WD]) { call_boss(new_os_window_with_wd, "s", cocoa_pending_actions_data.wd); }
|
if (cocoa_pending_actions[NEW_OS_WINDOW_WITH_WD]) { call_boss(new_os_window_with_wd, "s", cocoa_pending_actions_data.wd); }
|
||||||
if (cocoa_pending_actions[NEW_TAB_WITH_WD]) { call_boss(new_tab_with_wd, "s", cocoa_pending_actions_data.wd); }
|
if (cocoa_pending_actions[NEW_TAB_WITH_WD]) { call_boss(new_tab_with_wd, "s", cocoa_pending_actions_data.wd); }
|
||||||
free(cocoa_pending_actions_data.wd);
|
free(cocoa_pending_actions_data.wd);
|
||||||
cocoa_pending_actions_data.wd = NULL;
|
cocoa_pending_actions_data.wd = NULL;
|
||||||
}
|
}
|
||||||
if (cocoa_pending_actions_data.open_files_count) {
|
if (cocoa_pending_actions_data.open_urls_count) {
|
||||||
for (unsigned cpa = 0; cpa < cocoa_pending_actions_data.open_files_count; cpa++) {
|
for (unsigned cpa = 0; cpa < cocoa_pending_actions_data.open_urls_count; cpa++) {
|
||||||
if (cocoa_pending_actions_data.open_files[cpa]) {
|
if (cocoa_pending_actions_data.open_urls[cpa]) {
|
||||||
call_boss(open_file, "s", cocoa_pending_actions_data.open_files[cpa]);
|
call_boss(launch_urls, "s", cocoa_pending_actions_data.open_urls[cpa]);
|
||||||
free(cocoa_pending_actions_data.open_files[cpa]);
|
free(cocoa_pending_actions_data.open_urls[cpa]);
|
||||||
cocoa_pending_actions_data.open_files[cpa] = NULL;
|
cocoa_pending_actions_data.open_urls[cpa] = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cocoa_pending_actions_data.open_files_count = 0;
|
cocoa_pending_actions_data.open_urls_count = 0;
|
||||||
}
|
}
|
||||||
memset(cocoa_pending_actions, 0, sizeof(cocoa_pending_actions));
|
memset(cocoa_pending_actions, 0, sizeof(cocoa_pending_actions));
|
||||||
has_cocoa_pending_actions = false;
|
has_cocoa_pending_actions = false;
|
||||||
@@ -1113,11 +1114,11 @@ main_loop(ChildMonitor *self, PyObject *a UNUSED) {
|
|||||||
run_main_loop(process_global_state, self);
|
run_main_loop(process_global_state, self);
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
if (cocoa_pending_actions_data.wd) { free(cocoa_pending_actions_data.wd); cocoa_pending_actions_data.wd = NULL; }
|
if (cocoa_pending_actions_data.wd) { free(cocoa_pending_actions_data.wd); cocoa_pending_actions_data.wd = NULL; }
|
||||||
if (cocoa_pending_actions_data.open_files) {
|
if (cocoa_pending_actions_data.open_urls) {
|
||||||
for (unsigned cpa = 0; cpa < cocoa_pending_actions_data.open_files_count; cpa++) {
|
for (unsigned cpa = 0; cpa < cocoa_pending_actions_data.open_urls_count; cpa++) {
|
||||||
if (cocoa_pending_actions_data.open_files[cpa]) free(cocoa_pending_actions_data.open_files[cpa]);
|
if (cocoa_pending_actions_data.open_urls[cpa]) free(cocoa_pending_actions_data.open_urls[cpa]);
|
||||||
}
|
}
|
||||||
free(cocoa_pending_actions_data.open_files); cocoa_pending_actions_data.open_files = NULL;
|
free(cocoa_pending_actions_data.open_urls); cocoa_pending_actions_data.open_urls = NULL;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (PyErr_Occurred()) return NULL;
|
if (PyErr_Occurred()) return NULL;
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ else:
|
|||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
with open('/proc/' + x + '/stat', 'rb') as f:
|
with open(f'/proc/{x}/stat', 'rb') as f:
|
||||||
raw = f.read().decode('utf-8')
|
raw = f.read().decode('utf-8')
|
||||||
except OSError:
|
except OSError:
|
||||||
continue
|
continue
|
||||||
@@ -196,7 +196,7 @@ class Child:
|
|||||||
allow_remote_control: bool = False
|
allow_remote_control: bool = False
|
||||||
):
|
):
|
||||||
self.allow_remote_control = allow_remote_control
|
self.allow_remote_control = allow_remote_control
|
||||||
self.argv = argv
|
self.argv = list(argv)
|
||||||
if cwd_from is not None:
|
if cwd_from is not None:
|
||||||
try:
|
try:
|
||||||
cwd = cwd_of_process(cwd_from)
|
cwd = cwd_of_process(cwd_from)
|
||||||
@@ -233,7 +233,7 @@ class Child:
|
|||||||
opts = fast_data_types.get_options()
|
opts = fast_data_types.get_options()
|
||||||
if 'disabled' not in opts.shell_integration:
|
if 'disabled' not in opts.shell_integration:
|
||||||
from .shell_integration import modify_shell_environ
|
from .shell_integration import modify_shell_environ
|
||||||
modify_shell_environ(self.argv[0], opts, env)
|
modify_shell_environ(opts, env, self.argv)
|
||||||
env = {k: v for k, v in env.items() if v is not DELETE_ENV_VAR}
|
env = {k: v for k, v in env.items() if v is not DELETE_ENV_VAR}
|
||||||
return env
|
return env
|
||||||
|
|
||||||
@@ -268,7 +268,7 @@ class Child:
|
|||||||
# https://github.com/kovidgoyal/kitty/issues/1870
|
# https://github.com/kovidgoyal/kitty/issues/1870
|
||||||
# xterm, urxvt, konsole and gnome-terminal do not do it in my
|
# xterm, urxvt, konsole and gnome-terminal do not do it in my
|
||||||
# testing.
|
# testing.
|
||||||
argv[0] = ('-' + exe.split('/')[-1])
|
argv[0] = (f'-{exe.split("/")[-1]}')
|
||||||
exe = which(exe) or exe
|
exe = which(exe) or exe
|
||||||
pid = fast_data_types.spawn(exe, self.cwd, tuple(argv), env, master, slave, stdin_read_fd, stdin_write_fd, ready_read_fd, ready_write_fd)
|
pid = fast_data_types.spawn(exe, self.cwd, tuple(argv), env, master, slave, stdin_read_fd, stdin_write_fd, ready_read_fd, ready_write_fd)
|
||||||
os.close(slave)
|
os.close(slave)
|
||||||
@@ -344,8 +344,18 @@ class Child:
|
|||||||
assert self.child_fd is not None
|
assert self.child_fd is not None
|
||||||
pgrp = os.tcgetpgrp(self.child_fd)
|
pgrp = os.tcgetpgrp(self.child_fd)
|
||||||
foreground_processes = processes_in_group(pgrp) if pgrp >= 0 else []
|
foreground_processes = processes_in_group(pgrp) if pgrp >= 0 else []
|
||||||
if len(foreground_processes) == 1:
|
if foreground_processes:
|
||||||
return foreground_processes[0]
|
# there is no easy way that I know of to know which process is the
|
||||||
|
# foreground process in this group from the users perspective,
|
||||||
|
# so we assume the one with the highest PID is as that is most
|
||||||
|
# likely to be the newest process. This situation can happen
|
||||||
|
# for example with a shell script such as:
|
||||||
|
# #!/bin/bash
|
||||||
|
# cd /tmp
|
||||||
|
# vim
|
||||||
|
# With this script , the foreground process group will contain
|
||||||
|
# both the bash instance running the script and vim.
|
||||||
|
return max(foreground_processes)
|
||||||
return self.pid
|
return self.pid
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ def generate_stub() -> None:
|
|||||||
for cmd_name in all_command_names():
|
for cmd_name in all_command_names():
|
||||||
cmd = command_for_name(cmd_name)
|
cmd = command_for_name(cmd_name)
|
||||||
if cmd.options_spec:
|
if cmd.options_spec:
|
||||||
do(cmd.options_spec, cmd.__class__.__name__ + 'RCOptions')
|
do(cmd.options_spec, f'{cmd.__class__.__name__}RCOptions')
|
||||||
|
|
||||||
save_type_stub(text, __file__)
|
save_type_stub(text, __file__)
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ from contextlib import suppress
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
CSI = '\033['
|
CSI = '\x1b['
|
||||||
OSC = '\033]'
|
OSC = '\x1b]'
|
||||||
|
|
||||||
|
|
||||||
def write(x: str) -> None:
|
def write(x: str) -> None:
|
||||||
@@ -42,11 +42,11 @@ def screen_alternate_keypad_mode() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def screen_cursor_position(y: int, x: int) -> None:
|
def screen_cursor_position(y: int, x: int) -> None:
|
||||||
write(CSI + f'{y};{x}H')
|
write(f'{CSI}{y};{x}H')
|
||||||
|
|
||||||
|
|
||||||
def screen_cursor_forward(amt: int) -> None:
|
def screen_cursor_forward(amt: int) -> None:
|
||||||
write(CSI + '%sC' % amt)
|
write(f'{CSI}{amt}C')
|
||||||
|
|
||||||
|
|
||||||
def screen_save_cursor() -> None:
|
def screen_save_cursor() -> None:
|
||||||
@@ -58,105 +58,105 @@ def screen_restore_cursor() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def screen_cursor_back1(amt: int) -> None:
|
def screen_cursor_back1(amt: int) -> None:
|
||||||
write(CSI + '%sD' % amt)
|
write(f'{CSI}{amt}D')
|
||||||
|
|
||||||
|
|
||||||
def screen_save_modes() -> None:
|
def screen_save_modes() -> None:
|
||||||
write(CSI + '?s')
|
write(f'{CSI}?s')
|
||||||
|
|
||||||
|
|
||||||
def screen_restore_modes() -> None:
|
def screen_restore_modes() -> None:
|
||||||
write(CSI + '?r')
|
write(f'{CSI}?r')
|
||||||
|
|
||||||
|
|
||||||
def screen_designate_charset(which: int, to: int) -> None:
|
def screen_designate_charset(which: int, to: int) -> None:
|
||||||
w = '()'[int(which)]
|
w = '()'[int(which)]
|
||||||
t = chr(int(to))
|
t = chr(int(to))
|
||||||
write('\033' + w + t)
|
write(f'\x1b{w}{t}')
|
||||||
|
|
||||||
|
|
||||||
def select_graphic_rendition(*a: int) -> None:
|
def select_graphic_rendition(*a: int) -> None:
|
||||||
write(CSI + '%sm' % ';'.join(map(str, a)))
|
write(f'{CSI}{";".join(map(str, a))}m')
|
||||||
|
|
||||||
|
|
||||||
def screen_cursor_to_column(c: int) -> None:
|
def screen_cursor_to_column(c: int) -> None:
|
||||||
write(CSI + '%dG' % c)
|
write(f'{CSI}{c}G')
|
||||||
|
|
||||||
|
|
||||||
def screen_cursor_to_line(ln: int) -> None:
|
def screen_cursor_to_line(ln: int) -> None:
|
||||||
write(CSI + '%dd' % ln)
|
write(f'{CSI}{ln}d')
|
||||||
|
|
||||||
|
|
||||||
def screen_set_mode(x: int, private: bool) -> None:
|
def screen_set_mode(x: int, private: bool) -> None:
|
||||||
write(CSI + ('?' if private else '') + str(x) + 'h')
|
write(f'{CSI}{"?" if private else ""}{x}h')
|
||||||
|
|
||||||
|
|
||||||
def screen_save_mode(x: int, private: bool) -> None:
|
def screen_save_mode(x: int, private: bool) -> None:
|
||||||
write(CSI + ('?' if private else '') + str(x) + 's')
|
write(f'{CSI}{"?" if private else ""}{x}s')
|
||||||
|
|
||||||
|
|
||||||
def screen_reset_mode(x: int, private: bool) -> None:
|
def screen_reset_mode(x: int, private: bool) -> None:
|
||||||
write(CSI + ('?' if private else '') + str(x) + 'l')
|
write(f'{CSI}{"?" if private else ""}{x}l')
|
||||||
|
|
||||||
|
|
||||||
def screen_restore_mode(x: int, private: bool) -> None:
|
def screen_restore_mode(x: int, private: bool) -> None:
|
||||||
write(CSI + ('?' if private else '') + str(x) + 'r')
|
write(f'{CSI}{"?" if private else ""}{x}r')
|
||||||
|
|
||||||
|
|
||||||
def screen_set_margins(t: int, b: int) -> None:
|
def screen_set_margins(t: int, b: int) -> None:
|
||||||
write(CSI + '%d;%dr' % (t, b))
|
write(f'{CSI}{t};{b}r')
|
||||||
|
|
||||||
|
|
||||||
def screen_indexn(n: int) -> None:
|
def screen_indexn(n: int) -> None:
|
||||||
write(CSI + '%dS' % n)
|
write(f'{CSI}{n}S')
|
||||||
|
|
||||||
|
|
||||||
def screen_delete_characters(count: int) -> None:
|
def screen_delete_characters(count: int) -> None:
|
||||||
write(CSI + '%dP' % count)
|
write(f'{CSI}{count}P')
|
||||||
|
|
||||||
|
|
||||||
def screen_push_colors(which: int) -> None:
|
def screen_push_colors(which: int) -> None:
|
||||||
write(CSI + '%d#P' % which)
|
write(f'{CSI}{which}#P')
|
||||||
|
|
||||||
|
|
||||||
def screen_pop_colors(which: int) -> None:
|
def screen_pop_colors(which: int) -> None:
|
||||||
write(CSI + '%d#Q' % which)
|
write(f'{CSI}{which}#Q')
|
||||||
|
|
||||||
|
|
||||||
def screen_report_colors() -> None:
|
def screen_report_colors() -> None:
|
||||||
write(CSI + '#R')
|
write(f'{CSI}#R')
|
||||||
|
|
||||||
|
|
||||||
def screen_repeat_character(num: int) -> None:
|
def screen_repeat_character(num: int) -> None:
|
||||||
write(CSI + '%db' % num)
|
write(f'{CSI}{num}b')
|
||||||
|
|
||||||
|
|
||||||
def screen_insert_characters(count: int) -> None:
|
def screen_insert_characters(count: int) -> None:
|
||||||
write(CSI + '%d@' % count)
|
write(f'{CSI}{count}@')
|
||||||
|
|
||||||
|
|
||||||
def screen_scroll(count: int) -> None:
|
def screen_scroll(count: int) -> None:
|
||||||
write(CSI + '%dS' % count)
|
write(f'{CSI}{count}S')
|
||||||
|
|
||||||
|
|
||||||
def screen_erase_in_display(how: int, private: bool) -> None:
|
def screen_erase_in_display(how: int, private: bool) -> None:
|
||||||
write(CSI + ('?' if private else '') + str(how) + 'J')
|
write(f'{CSI}{"?" if private else ""}{how}J')
|
||||||
|
|
||||||
|
|
||||||
def screen_erase_in_line(how: int, private: bool) -> None:
|
def screen_erase_in_line(how: int, private: bool) -> None:
|
||||||
write(CSI + ('?' if private else '') + str(how) + 'K')
|
write(f'{CSI}{"?" if private else ""}{how}K')
|
||||||
|
|
||||||
|
|
||||||
def screen_delete_lines(num: int) -> None:
|
def screen_delete_lines(num: int) -> None:
|
||||||
write(CSI + str(num) + 'M')
|
write(f'{CSI}{num}M')
|
||||||
|
|
||||||
|
|
||||||
def screen_cursor_up2(count: int) -> None:
|
def screen_cursor_up2(count: int) -> None:
|
||||||
write(CSI + '%dA' % count)
|
write(f'{CSI}{count}A')
|
||||||
|
|
||||||
|
|
||||||
def screen_cursor_down(count: int) -> None:
|
def screen_cursor_down(count: int) -> None:
|
||||||
write(CSI + '%dB' % count)
|
write(f'{CSI}{count}B')
|
||||||
|
|
||||||
|
|
||||||
def screen_carriage_return() -> None:
|
def screen_carriage_return() -> None:
|
||||||
@@ -176,11 +176,11 @@ def screen_backspace() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def screen_set_cursor(mode: int, secondary: int) -> None:
|
def screen_set_cursor(mode: int, secondary: int) -> None:
|
||||||
write(CSI + '%d q' % secondary)
|
write(f'{CSI}{secondary} q')
|
||||||
|
|
||||||
|
|
||||||
def screen_insert_lines(num: int) -> None:
|
def screen_insert_lines(num: int) -> None:
|
||||||
write(CSI + '%dL' % num)
|
write(f'{CSI}{num}L')
|
||||||
|
|
||||||
|
|
||||||
def draw(*a: str) -> None:
|
def draw(*a: str) -> None:
|
||||||
@@ -188,7 +188,7 @@ def draw(*a: str) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def screen_manipulate_title_stack(op: int, which: int) -> None:
|
def screen_manipulate_title_stack(op: int, which: int) -> None:
|
||||||
write(CSI + '%d;%dt' % (op, which))
|
write(f'{CSI}{op};{which}t')
|
||||||
|
|
||||||
|
|
||||||
def report_device_attributes(mode: int, char: int) -> None:
|
def report_device_attributes(mode: int, char: int) -> None:
|
||||||
@@ -197,21 +197,22 @@ def report_device_attributes(mode: int, char: int) -> None:
|
|||||||
x += chr(char)
|
x += chr(char)
|
||||||
if mode:
|
if mode:
|
||||||
x += str(mode)
|
x += str(mode)
|
||||||
write(CSI + x + 'c')
|
write(f'{CSI}{x}c')
|
||||||
|
|
||||||
|
|
||||||
def screen_decsace(mode: int) -> None:
|
def screen_decsace(mode: int) -> None:
|
||||||
write(CSI + str(mode) + '*x')
|
write(f'{CSI}{mode}*x')
|
||||||
|
|
||||||
|
|
||||||
def screen_set_8bit_controls(mode: int) -> None:
|
def screen_set_8bit_controls(mode: int) -> None:
|
||||||
write('\x1b ' + ('G' if mode else 'F'))
|
write(f'\x1b {"G" if mode else "F"}')
|
||||||
|
|
||||||
|
|
||||||
def write_osc(code: int, string: str = '') -> None:
|
def write_osc(code: int, string: str = '') -> None:
|
||||||
if string:
|
if string:
|
||||||
string = ';' + string
|
write(f'{OSC}{code};{string}\x07')
|
||||||
write(OSC + str(code) + string + '\x07')
|
else:
|
||||||
|
write(f'{OSC}{code}\x07')
|
||||||
|
|
||||||
|
|
||||||
set_dynamic_color = set_color_table_color = process_cwd_notification = write_osc
|
set_dynamic_color = set_color_table_color = process_cwd_notification = write_osc
|
||||||
@@ -235,7 +236,7 @@ def clipboard_control(payload: str) -> None:
|
|||||||
clipboard_control_pending += data.lstrip(';')
|
clipboard_control_pending += data.lstrip(';')
|
||||||
payload = clipboard_control_pending
|
payload = clipboard_control_pending
|
||||||
clipboard_control_pending = ''
|
clipboard_control_pending = ''
|
||||||
write(OSC + payload + '\x07')
|
write(f'{OSC}{payload}\x07')
|
||||||
|
|
||||||
|
|
||||||
def replay(raw: str) -> None:
|
def replay(raw: str) -> None:
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "state.h"
|
#include "state.h"
|
||||||
#include "cleanup.h"
|
#include "cleanup.h"
|
||||||
#include "monotonic.h"
|
#include "monotonic.h"
|
||||||
|
#include <Carbon/Carbon.h>
|
||||||
#include <Cocoa/Cocoa.h>
|
#include <Cocoa/Cocoa.h>
|
||||||
#ifndef KITTY_USE_DEPRECATED_MACOS_NOTIFICATION_API
|
#ifndef KITTY_USE_DEPRECATED_MACOS_NOTIFICATION_API
|
||||||
#include <UserNotifications/UserNotifications.h>
|
#include <UserNotifications/UserNotifications.h>
|
||||||
@@ -19,6 +20,11 @@
|
|||||||
#include <crt_externs.h>
|
#include <crt_externs.h>
|
||||||
#include <objc/runtime.h>
|
#include <objc/runtime.h>
|
||||||
|
|
||||||
|
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101300)
|
||||||
|
#define NSControlStateValueOn NSOnState
|
||||||
|
#define NSControlStateValueOff NSOffState
|
||||||
|
#define NSControlStateValueMixed NSMixedState
|
||||||
|
#endif
|
||||||
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200)
|
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200)
|
||||||
#define NSWindowStyleMaskResizable NSResizableWindowMask
|
#define NSWindowStyleMaskResizable NSResizableWindowMask
|
||||||
#define NSEventModifierFlagOption NSAlternateKeyMask
|
#define NSEventModifierFlagOption NSAlternateKeyMask
|
||||||
@@ -75,6 +81,137 @@ find_app_name(void) {
|
|||||||
return @"kitty";
|
return @"kitty";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define debug_key(...) if (OPT(debug_keyboard)) { fprintf(stderr, __VA_ARGS__); fflush(stderr); }
|
||||||
|
|
||||||
|
// SecureKeyboardEntryController {{{
|
||||||
|
@interface SecureKeyboardEntryController : NSObject
|
||||||
|
|
||||||
|
@property (nonatomic, readonly) BOOL isDesired;
|
||||||
|
@property (nonatomic, readonly, getter=isEnabled) BOOL enabled;
|
||||||
|
|
||||||
|
+ (instancetype)sharedInstance;
|
||||||
|
|
||||||
|
- (void)toggle;
|
||||||
|
- (void)update;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation SecureKeyboardEntryController {
|
||||||
|
int _count;
|
||||||
|
BOOL _desired;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (instancetype)sharedInstance {
|
||||||
|
static id instance;
|
||||||
|
static dispatch_once_t onceToken;
|
||||||
|
dispatch_once(&onceToken, ^{
|
||||||
|
instance = [[self alloc] init];
|
||||||
|
});
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (instancetype)init {
|
||||||
|
self = [super init];
|
||||||
|
if (self) {
|
||||||
|
_desired = false;
|
||||||
|
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
|
selector:@selector(applicationDidResignActive:)
|
||||||
|
name:NSApplicationDidResignActiveNotification
|
||||||
|
object:nil];
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
|
selector:@selector(applicationDidBecomeActive:)
|
||||||
|
name:NSApplicationDidBecomeActiveNotification
|
||||||
|
object:nil];
|
||||||
|
if ([NSApp isActive]) {
|
||||||
|
[self update];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - API
|
||||||
|
|
||||||
|
- (void)toggle {
|
||||||
|
// Set _desired to the opposite of the current state.
|
||||||
|
_desired = !_desired;
|
||||||
|
debug_key("SecureKeyboardEntry: toggle called. Setting desired to %d ", _desired);
|
||||||
|
|
||||||
|
// Try to set the system's state of secure input to the desired state.
|
||||||
|
[self update];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)isEnabled {
|
||||||
|
return !!IsSecureEventInputEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)isDesired {
|
||||||
|
return _desired;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Notifications
|
||||||
|
|
||||||
|
- (void)applicationDidResignActive:(NSNotification *)notification {
|
||||||
|
(void)notification;
|
||||||
|
if (_count > 0) {
|
||||||
|
debug_key("SecureKeyboardEntry: Application resigning active.");
|
||||||
|
[self update];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationDidBecomeActive:(NSNotification *)notification {
|
||||||
|
(void)notification;
|
||||||
|
if (self.isDesired) {
|
||||||
|
debug_key("SecureKeyboardEntry: Application became active.");
|
||||||
|
[self update];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma mark - Private
|
||||||
|
|
||||||
|
- (BOOL)allowed {
|
||||||
|
return [NSApp isActive];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)update {
|
||||||
|
debug_key("Update secure keyboard entry. desired=%d active=%d\n",
|
||||||
|
(int)self.isDesired, (int)[NSApp isActive]);
|
||||||
|
const BOOL secure = self.isDesired && [self allowed];
|
||||||
|
|
||||||
|
if (secure && _count > 0) {
|
||||||
|
debug_key("Want to turn on secure input but it's already on\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!secure && _count == 0) {
|
||||||
|
debug_key("Want to turn off secure input but it's already off\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_key("Before: IsSecureEventInputEnabled returns %d ", (int)self.isEnabled);
|
||||||
|
if (secure) {
|
||||||
|
OSErr err = EnableSecureEventInput();
|
||||||
|
debug_key("EnableSecureEventInput err=%d ", (int)err);
|
||||||
|
if (err) {
|
||||||
|
debug_key("EnableSecureEventInput failed with error %d ", (int)err);
|
||||||
|
} else {
|
||||||
|
_count += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
OSErr err = DisableSecureEventInput();
|
||||||
|
debug_key("DisableSecureEventInput err=%d ", (int)err);
|
||||||
|
if (err) {
|
||||||
|
debug_key("DisableSecureEventInput failed with error %d ", (int)err);
|
||||||
|
} else {
|
||||||
|
_count -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug_key("After: IsSecureEventInputEnabled returns %d\n", (int)self.isEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
|
// }}}
|
||||||
|
|
||||||
@interface GlobalMenuTarget : NSObject
|
@interface GlobalMenuTarget : NSObject
|
||||||
+ (GlobalMenuTarget *) shared_instance;
|
+ (GlobalMenuTarget *) shared_instance;
|
||||||
@end
|
@end
|
||||||
@@ -94,13 +231,25 @@ PENDING(previous_tab, PREVIOUS_TAB)
|
|||||||
PENDING(new_window, NEW_WINDOW)
|
PENDING(new_window, NEW_WINDOW)
|
||||||
PENDING(close_window, CLOSE_WINDOW)
|
PENDING(close_window, CLOSE_WINDOW)
|
||||||
PENDING(reset_terminal, RESET_TERMINAL)
|
PENDING(reset_terminal, RESET_TERMINAL)
|
||||||
|
PENDING(clear_terminal_and_scrollback, CLEAR_TERMINAL_AND_SCROLLBACK)
|
||||||
PENDING(reload_config, RELOAD_CONFIG)
|
PENDING(reload_config, RELOAD_CONFIG)
|
||||||
|
PENDING(toggle_macos_secure_keyboard_entry, TOGGLE_MACOS_SECURE_KEYBOARD_ENTRY)
|
||||||
|
PENDING(toggle_fullscreen, TOGGLE_FULLSCREEN)
|
||||||
|
|
||||||
- (void)open_kitty_website_url:(id)sender {
|
- (void)open_kitty_website_url:(id)sender {
|
||||||
(void)sender;
|
(void)sender;
|
||||||
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://sw.kovidgoyal.net/kitty/"]];
|
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://sw.kovidgoyal.net/kitty/"]];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (BOOL)validateMenuItem:(NSMenuItem *)item {
|
||||||
|
if (item.action == @selector(toggle_macos_secure_keyboard_entry:)) {
|
||||||
|
item.state = [SecureKeyboardEntryController sharedInstance].isDesired ? NSControlStateValueOn : NSControlStateValueOff;
|
||||||
|
} else if (item.action == @selector(toggle_fullscreen:)) {
|
||||||
|
item.title = ([NSApp currentSystemPresentationOptions] & NSApplicationPresentationFullScreen) ? @"Exit Full Screen" : @"Enter Full Screen";
|
||||||
|
}
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
#undef PENDING
|
#undef PENDING
|
||||||
|
|
||||||
+ (GlobalMenuTarget *) shared_instance
|
+ (GlobalMenuTarget *) shared_instance
|
||||||
@@ -108,8 +257,11 @@ PENDING(reload_config, RELOAD_CONFIG)
|
|||||||
static GlobalMenuTarget *sharedGlobalMenuTarget = nil;
|
static GlobalMenuTarget *sharedGlobalMenuTarget = nil;
|
||||||
@synchronized(self)
|
@synchronized(self)
|
||||||
{
|
{
|
||||||
if (!sharedGlobalMenuTarget)
|
if (!sharedGlobalMenuTarget) {
|
||||||
sharedGlobalMenuTarget = [[GlobalMenuTarget alloc] init];
|
sharedGlobalMenuTarget = [[GlobalMenuTarget alloc] init];
|
||||||
|
SecureKeyboardEntryController *k = [SecureKeyboardEntryController sharedInstance];
|
||||||
|
if (!k.isDesired && [[NSUserDefaults standardUserDefaults] boolForKey:@"SecureKeyboardEntry"]) [k toggle];
|
||||||
|
}
|
||||||
return sharedGlobalMenuTarget;
|
return sharedGlobalMenuTarget;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,7 +274,8 @@ typedef struct {
|
|||||||
} GlobalShortcut;
|
} GlobalShortcut;
|
||||||
typedef struct {
|
typedef struct {
|
||||||
GlobalShortcut new_os_window, close_os_window, close_tab, edit_config_file, reload_config;
|
GlobalShortcut new_os_window, close_os_window, close_tab, edit_config_file, reload_config;
|
||||||
GlobalShortcut previous_tab, next_tab, new_tab, new_window, close_window, reset_terminal;
|
GlobalShortcut previous_tab, next_tab, new_tab, new_window, close_window, reset_terminal, clear_terminal_and_scrollback;
|
||||||
|
GlobalShortcut toggle_macos_secure_keyboard_entry, toggle_fullscreen;
|
||||||
} GlobalShortcuts;
|
} GlobalShortcuts;
|
||||||
static GlobalShortcuts global_shortcuts;
|
static GlobalShortcuts global_shortcuts;
|
||||||
|
|
||||||
@@ -136,7 +289,8 @@ cocoa_set_global_shortcut(PyObject *self UNUSED, PyObject *args) {
|
|||||||
#define Q(x) if (strcmp(name, #x) == 0) gs = &global_shortcuts.x
|
#define Q(x) if (strcmp(name, #x) == 0) gs = &global_shortcuts.x
|
||||||
Q(new_os_window); else Q(close_os_window); else Q(close_tab); else Q(edit_config_file);
|
Q(new_os_window); else Q(close_os_window); else Q(close_tab); else Q(edit_config_file);
|
||||||
else Q(new_tab); else Q(next_tab); else Q(previous_tab);
|
else Q(new_tab); else Q(next_tab); else Q(previous_tab);
|
||||||
else Q(new_window); else Q(close_window); else Q(reset_terminal); else Q(reload_config);
|
else Q(new_window); else Q(close_window); else Q(reset_terminal); else Q(clear_terminal_and_scrollback); else Q(reload_config);
|
||||||
|
else Q(toggle_macos_secure_keyboard_entry); else Q(toggle_fullscreen);
|
||||||
#undef Q
|
#undef Q
|
||||||
if (gs == NULL) { PyErr_SetString(PyExc_KeyError, "Unknown shortcut name"); return NULL; }
|
if (gs == NULL) { PyErr_SetString(PyExc_KeyError, "Unknown shortcut name"); return NULL; }
|
||||||
int cocoa_mods;
|
int cocoa_mods;
|
||||||
@@ -229,6 +383,7 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
|
|||||||
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
|
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
|
||||||
willPresentNotification:(UNNotification *)notification
|
willPresentNotification:(UNNotification *)notification
|
||||||
withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
|
withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
|
||||||
|
(void)(center); (void)notification;
|
||||||
UNNotificationPresentationOptions options = UNNotificationPresentationOptionSound;
|
UNNotificationPresentationOptions options = UNNotificationPresentationOptionSound;
|
||||||
if (@available(macOS 11.0, *)) options |= UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner;
|
if (@available(macOS 11.0, *)) options |= UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner;
|
||||||
else options |= (1 << 2); // UNNotificationPresentationOptionAlert avoid deprecated warning
|
else options |= (1 << 2); // UNNotificationPresentationOptionAlert avoid deprecated warning
|
||||||
@@ -368,6 +523,19 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
|
|||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (BOOL)openFileURLs:(NSPasteboard*)pasteboard
|
||||||
|
userData:(NSString *) UNUSED userData error:(NSError **) UNUSED error {
|
||||||
|
NSDictionary *options = @{ NSPasteboardURLReadingFileURLsOnlyKey: @YES };
|
||||||
|
NSArray *urlArray = [pasteboard readObjectsForClasses:[NSArray arrayWithObject:[NSURL class]] options:options];
|
||||||
|
for (NSURL *url in urlArray) {
|
||||||
|
NSString *path = [url path];
|
||||||
|
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
|
||||||
|
set_cocoa_pending_action(LAUNCH_URLS, [[[NSURL fileURLWithPath:path] absoluteString] UTF8String]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
// global menu {{{
|
// global menu {{{
|
||||||
@@ -419,10 +587,7 @@ cocoa_create_global_menu(void) {
|
|||||||
keyEquivalent:@""];
|
keyEquivalent:@""];
|
||||||
[appMenu addItem:[NSMenuItem separatorItem]];
|
[appMenu addItem:[NSMenuItem separatorItem]];
|
||||||
|
|
||||||
[[appMenu addItemWithTitle:@"Secure Keyboard Entry"
|
MENU_ITEM(appMenu, @"Secure Keyboard Entry", toggle_macos_secure_keyboard_entry);
|
||||||
action:@selector(toggleSecureInput:)
|
|
||||||
keyEquivalent:@"s"]
|
|
||||||
setKeyEquivalentModifierMask:NSEventModifierFlagOption | NSEventModifierFlagCommand];
|
|
||||||
[appMenu addItem:[NSMenuItem separatorItem]];
|
[appMenu addItem:[NSMenuItem separatorItem]];
|
||||||
|
|
||||||
[appMenu addItemWithTitle:[NSString stringWithFormat:@"Quit %@", app_name]
|
[appMenu addItemWithTitle:[NSString stringWithFormat:@"Quit %@", app_name]
|
||||||
@@ -445,6 +610,7 @@ cocoa_create_global_menu(void) {
|
|||||||
MENU_ITEM(shellMenu, @"Close Window", close_window);
|
MENU_ITEM(shellMenu, @"Close Window", close_window);
|
||||||
[shellMenu addItem:[NSMenuItem separatorItem]];
|
[shellMenu addItem:[NSMenuItem separatorItem]];
|
||||||
MENU_ITEM(shellMenu, @"Reset", reset_terminal);
|
MENU_ITEM(shellMenu, @"Reset", reset_terminal);
|
||||||
|
MENU_ITEM(shellMenu, @"Clear to Cursor Line", clear_terminal_and_scrollback);
|
||||||
[shellMenu release];
|
[shellMenu release];
|
||||||
|
|
||||||
NSMenuItem* windowMenuItem =
|
NSMenuItem* windowMenuItem =
|
||||||
@@ -473,10 +639,7 @@ cocoa_create_global_menu(void) {
|
|||||||
keyEquivalent:@""] setTarget:global_menu_target];
|
keyEquivalent:@""] setTarget:global_menu_target];
|
||||||
|
|
||||||
[windowMenu addItem:[NSMenuItem separatorItem]];
|
[windowMenu addItem:[NSMenuItem separatorItem]];
|
||||||
[[windowMenu addItemWithTitle:@"Enter Full Screen"
|
MENU_ITEM(windowMenu, @"Enter Full Screen", toggle_fullscreen);
|
||||||
action:@selector(toggleFullScreen:)
|
|
||||||
keyEquivalent:@"f"]
|
|
||||||
setKeyEquivalentModifierMask:NSEventModifierFlagControl | NSEventModifierFlagCommand];
|
|
||||||
[NSApp setWindowsMenu:windowMenu];
|
[NSApp setWindowsMenu:windowMenu];
|
||||||
[windowMenu release];
|
[windowMenu release];
|
||||||
|
|
||||||
@@ -508,7 +671,16 @@ cocoa_create_global_menu(void) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
cocoa_update_menu_bar_title(PyObject *pytitle) {
|
cocoa_update_menu_bar_title(PyObject *pytitle) {
|
||||||
NSString *title = @(PyUnicode_AsUTF8(pytitle));
|
NSString *title = nil;
|
||||||
|
if (OPT(macos_menubar_title_max_length) > 0 && PyUnicode_GetLength(pytitle) > OPT(macos_menubar_title_max_length)) {
|
||||||
|
static char fmt[64];
|
||||||
|
snprintf(fmt, sizeof(fmt), "%%%ld.%ldU%%s", OPT(macos_menubar_title_max_length), OPT(macos_menubar_title_max_length));
|
||||||
|
DECREF_AFTER_FUNCTION PyObject *st = PyUnicode_FromFormat(fmt, pytitle, "…");
|
||||||
|
if (st) title = @(PyUnicode_AsUTF8(st));
|
||||||
|
} else {
|
||||||
|
title = @(PyUnicode_AsUTF8(pytitle));
|
||||||
|
}
|
||||||
|
if (!title) return;
|
||||||
NSMenu *bar = [NSApp mainMenu];
|
NSMenu *bar = [NSApp mainMenu];
|
||||||
if (title_menu != NULL) {
|
if (title_menu != NULL) {
|
||||||
[bar removeItem:title_menu];
|
[bar removeItem:title_menu];
|
||||||
@@ -547,6 +719,13 @@ cocoa_alt_option_key_pressed(NSUInteger flags) {
|
|||||||
return (q & flags) == q;
|
return (q & flags) == q;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
cocoa_toggle_secure_keyboard_entry(void) {
|
||||||
|
SecureKeyboardEntryController *k = [SecureKeyboardEntryController sharedInstance];
|
||||||
|
[k toggle];
|
||||||
|
[[NSUserDefaults standardUserDefaults] setBool:k.isDesired forKey:@"SecureKeyboardEntry"];
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
cocoa_focus_window(void *w) {
|
cocoa_focus_window(void *w) {
|
||||||
NSWindow *window = (NSWindow*)w;
|
NSWindow *window = (NSWindow*)w;
|
||||||
@@ -659,6 +838,33 @@ cocoa_set_titlebar_color(void *w, color_type titlebar_color)
|
|||||||
} // autoreleasepool
|
} // autoreleasepool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
cocoa_set_url_handler(PyObject UNUSED *self, PyObject *args) {
|
||||||
|
@autoreleasepool {
|
||||||
|
|
||||||
|
const char *url_scheme = NULL, *bundle_id = NULL;
|
||||||
|
if (!PyArg_ParseTuple(args, "s|z", &url_scheme, &bundle_id)) return NULL;
|
||||||
|
if (!url_scheme || url_scheme[0] == '\0') {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "Empty url scheme");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString *scheme = [NSString stringWithUTF8String:url_scheme];
|
||||||
|
NSString *identifier = @"";
|
||||||
|
if (!bundle_id) {
|
||||||
|
identifier = [[NSBundle mainBundle] bundleIdentifier];
|
||||||
|
if (!identifier || identifier.length == 0) identifier = @"net.kovidgoyal.kitty";
|
||||||
|
} else if (bundle_id[0] != '\0') {
|
||||||
|
identifier = [NSString stringWithUTF8String:bundle_id];
|
||||||
|
}
|
||||||
|
// This API has been marked as deprecated. It will need to be replaced when a new approach is available.
|
||||||
|
OSStatus err = LSSetDefaultHandlerForURLScheme((CFStringRef)scheme, (CFStringRef)identifier);
|
||||||
|
if (err == noErr) Py_RETURN_NONE;
|
||||||
|
PyErr_Format(PyExc_OSError, "Failed to set default handler with error code: %d", err);
|
||||||
|
return NULL;
|
||||||
|
} // autoreleasepool
|
||||||
|
}
|
||||||
|
|
||||||
static NSSound *beep_sound = nil;
|
static NSSound *beep_sound = nil;
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -708,13 +914,21 @@ static PyMethodDef module_methods[] = {
|
|||||||
{"cocoa_set_global_shortcut", (PyCFunction)cocoa_set_global_shortcut, METH_VARARGS, ""},
|
{"cocoa_set_global_shortcut", (PyCFunction)cocoa_set_global_shortcut, METH_VARARGS, ""},
|
||||||
{"cocoa_send_notification", (PyCFunction)cocoa_send_notification, METH_VARARGS, ""},
|
{"cocoa_send_notification", (PyCFunction)cocoa_send_notification, METH_VARARGS, ""},
|
||||||
{"cocoa_set_notification_activated_callback", (PyCFunction)set_notification_activated_callback, METH_O, ""},
|
{"cocoa_set_notification_activated_callback", (PyCFunction)set_notification_activated_callback, METH_O, ""},
|
||||||
|
{"cocoa_set_url_handler", (PyCFunction)cocoa_set_url_handler, METH_VARARGS, ""},
|
||||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
uncaughtExceptionHandler(NSException *exception) {
|
||||||
|
log_error("Unhandled exception in Cocoa: %s", [[exception description] UTF8String]);
|
||||||
|
log_error("Stack trace:\n%s", [[exception.callStackSymbols description] UTF8String]);
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
init_cocoa(PyObject *module) {
|
init_cocoa(PyObject *module) {
|
||||||
memset(&global_shortcuts, 0, sizeof(global_shortcuts));
|
memset(&global_shortcuts, 0, sizeof(global_shortcuts));
|
||||||
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
|
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
|
||||||
register_at_exit_cleanup_func(COCOA_CLEANUP_FUNC, cleanup);
|
register_at_exit_cleanup_func(COCOA_CLEANUP_FUNC, cleanup);
|
||||||
|
NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -270,7 +270,7 @@ def zsh_output_serializer(ans: Completions) -> str:
|
|||||||
yield ans
|
yield ans
|
||||||
|
|
||||||
for description, matches in ans.match_groups.items():
|
for description, matches in ans.match_groups.items():
|
||||||
cmd = ['compadd', '-U', '-J', shlex.quote(description), '-X', shlex.quote('%B' + description + '%b')]
|
cmd = ['compadd', '-U', '-J', shlex.quote(description), '-X', shlex.quote(f'%B{description}%b')]
|
||||||
if not matches.trailing_space:
|
if not matches.trailing_space:
|
||||||
cmd += ['-S', '""']
|
cmd += ['-S', '""']
|
||||||
if matches.is_files:
|
if matches.is_files:
|
||||||
@@ -350,10 +350,10 @@ def fish2_output_serializer(ans: Completions) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def completions_for_first_word(ans: Completions, prefix: str, entry_points: Iterable[str], namespaced_entry_points: Iterable[str]) -> None:
|
def completions_for_first_word(ans: Completions, prefix: str, entry_points: Iterable[str], namespaced_entry_points: Iterable[str]) -> None:
|
||||||
cmds = ['@' + c for c in remote_control_command_names()]
|
cmds = [f'@{c}' for c in remote_control_command_names()]
|
||||||
ans.add_match_group('Entry points', {
|
ans.add_match_group('Entry points', {
|
||||||
k: '' for k in
|
k: '' for k in
|
||||||
list(entry_points) + cmds + ['+' + k for k in namespaced_entry_points]
|
list(entry_points) + cmds + [f'+{k}' for k in namespaced_entry_points]
|
||||||
if not prefix or k.startswith(prefix)
|
if not prefix or k.startswith(prefix)
|
||||||
})
|
})
|
||||||
if prefix:
|
if prefix:
|
||||||
@@ -443,7 +443,7 @@ def complete_alias_map(
|
|||||||
long_opt = option_map.get(parts[0])
|
long_opt = option_map.get(parts[0])
|
||||||
if long_opt is not None and complete_args is not None:
|
if long_opt is not None and complete_args is not None:
|
||||||
complete_args(ans, long_opt, parts[1], Delegate())
|
complete_args(ans, long_opt, parts[1], Delegate())
|
||||||
ans.add_prefix(parts[0] + '=')
|
ans.add_prefix(f'{parts[0]}=')
|
||||||
return
|
return
|
||||||
opt = option_map.get(w)
|
opt = option_map.get(w)
|
||||||
if w is last_word and not new_word:
|
if w is last_word and not new_word:
|
||||||
@@ -682,7 +682,7 @@ def find_completions(words: Sequence[str], new_word: bool, entry_points: Iterabl
|
|||||||
if words[0].startswith('@'):
|
if words[0].startswith('@'):
|
||||||
if len(words) == 1 and not new_word:
|
if len(words) == 1 and not new_word:
|
||||||
prefix = words[0]
|
prefix = words[0]
|
||||||
ans.add_match_group('Remote control commands', {'@' + c: '' for c in remote_control_command_names() if c.startswith(prefix)})
|
ans.add_match_group('Remote control commands', {f'@{c}': '' for c in remote_control_command_names() if c.startswith(prefix)})
|
||||||
else:
|
else:
|
||||||
complete_remote_command(ans, words[0][1:], words[1:], new_word)
|
complete_remote_command(ans, words[0][1:], words[1:], new_word)
|
||||||
if words[0] == '+':
|
if words[0] == '+':
|
||||||
|
|||||||
@@ -39,17 +39,16 @@ def generate_class(defn: Definition, loc: str) -> Tuple[str, str]:
|
|||||||
imports: Set[Tuple[str, str]] = set()
|
imports: Set[Tuple[str, str]] = set()
|
||||||
tc_imports: Set[Tuple[str, str]] = set()
|
tc_imports: Set[Tuple[str, str]] = set()
|
||||||
|
|
||||||
def type_name(x: type) -> str:
|
|
||||||
ans = x.__name__
|
|
||||||
if x.__module__ and x.__module__ != 'builtins':
|
|
||||||
imports.add((x.__module__, x.__name__))
|
|
||||||
return ans
|
|
||||||
|
|
||||||
def option_type_as_str(x: Any) -> str:
|
def option_type_as_str(x: Any) -> str:
|
||||||
if hasattr(x, '__name__'):
|
needs_import = False
|
||||||
return type_name(x)
|
if type(x) is type:
|
||||||
ans = repr(x)
|
ans = x.__name__
|
||||||
ans = ans.replace('NoneType', 'None')
|
needs_import = True
|
||||||
|
else:
|
||||||
|
ans = repr(x)
|
||||||
|
ans = ans.replace('NoneType', 'None')
|
||||||
|
if needs_import and getattr(x, '__module__', None) and x.__module__ not in ('builtins', 'typing'):
|
||||||
|
imports.add((x.__module__, x.__name__))
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
def option_type_data(option: Union[Option, MultiOption]) -> Tuple[Callable[[Any], Any], str]:
|
def option_type_data(option: Union[Option, MultiOption]) -> Tuple[Callable[[Any], Any], str]:
|
||||||
@@ -121,6 +120,8 @@ def generate_class(defn: Definition, loc: str) -> Tuple[str, str]:
|
|||||||
tc_imports.add((func.__module__, func.__name__))
|
tc_imports.add((func.__module__, func.__name__))
|
||||||
|
|
||||||
defval = repr(func(option.defval_as_string))
|
defval = repr(func(option.defval_as_string))
|
||||||
|
if len(defval) > 100:
|
||||||
|
defval += ' # noqa'
|
||||||
if option.macos_defval is not unset:
|
if option.macos_defval is not unset:
|
||||||
md = repr(func(option.macos_defval))
|
md = repr(func(option.macos_defval))
|
||||||
defval = f'{md} if is_macos else {defval}'
|
defval = f'{md} if is_macos else {defval}'
|
||||||
@@ -418,9 +419,9 @@ def generate_c_conversion(loc: str, ctypes: List[Option]) -> str:
|
|||||||
def write_output(loc: str, defn: Definition) -> None:
|
def write_output(loc: str, defn: Definition) -> None:
|
||||||
cls, tc = generate_class(defn, loc)
|
cls, tc = generate_class(defn, loc)
|
||||||
with open(os.path.join(*loc.split('.'), 'options', 'types.py'), 'w') as f:
|
with open(os.path.join(*loc.split('.'), 'options', 'types.py'), 'w') as f:
|
||||||
f.write(cls + '\n')
|
f.write(f'{cls}\n')
|
||||||
with open(os.path.join(*loc.split('.'), 'options', 'parse.py'), 'w') as f:
|
with open(os.path.join(*loc.split('.'), 'options', 'parse.py'), 'w') as f:
|
||||||
f.write(tc + '\n')
|
f.write(f'{tc}\n')
|
||||||
ctypes = []
|
ctypes = []
|
||||||
for opt in defn.root_group.iter_all_non_groups():
|
for opt in defn.root_group.iter_all_non_groups():
|
||||||
if isinstance(opt, Option) and opt.ctype:
|
if isinstance(opt, Option) and opt.ctype:
|
||||||
@@ -428,7 +429,7 @@ def write_output(loc: str, defn: Definition) -> None:
|
|||||||
if ctypes:
|
if ctypes:
|
||||||
c = generate_c_conversion(loc, ctypes)
|
c = generate_c_conversion(loc, ctypes)
|
||||||
with open(os.path.join(*loc.split('.'), 'options', 'to-c-generated.h'), 'w') as f:
|
with open(os.path.join(*loc.split('.'), 'options', 'to-c-generated.h'), 'w') as f:
|
||||||
f.write(c + '\n')
|
f.write(f'{c}\n')
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
@@ -454,6 +455,6 @@ def main() -> None:
|
|||||||
loc = package_name
|
loc = package_name
|
||||||
cls, tc = generate_class(defn, loc)
|
cls, tc = generate_class(defn, loc)
|
||||||
with open(os.path.join(os.path.dirname(path), 'kitten_options_types.py'), 'w') as f:
|
with open(os.path.join(os.path.dirname(path), 'kitten_options_types.py'), 'w') as f:
|
||||||
f.write(cls + '\n')
|
f.write(f'{cls}\n')
|
||||||
with open(os.path.join(os.path.dirname(path), 'kitten_options_parse.py'), 'w') as f:
|
with open(os.path.join(os.path.dirname(path), 'kitten_options_parse.py'), 'w') as f:
|
||||||
f.write(tc + '\n')
|
f.write(f'{tc}\n')
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ def expand_opt_references(conf_name: str, text: str) -> str:
|
|||||||
def expand(m: 'Match[str]') -> str:
|
def expand(m: 'Match[str]') -> str:
|
||||||
ref = m.group(1)
|
ref = m.group(1)
|
||||||
if '<' not in ref and '.' not in ref:
|
if '<' not in ref and '.' not in ref:
|
||||||
full_ref = conf_name + ref
|
# full ref
|
||||||
return f':opt:`{ref} <{full_ref}>`'
|
return f':opt:`{ref} <{conf_name}{ref}>`'
|
||||||
return str(m.group())
|
return str(m.group())
|
||||||
|
|
||||||
return re.sub(r':opt:`(.+?)`', expand, text)
|
return re.sub(r':opt:`(.+?)`', expand, text)
|
||||||
@@ -53,6 +53,7 @@ def ref_map() -> Dict[str, str]:
|
|||||||
'sessions': f'{website_url("overview")}#startup-sessions',
|
'sessions': f'{website_url("overview")}#startup-sessions',
|
||||||
'functional': f'{website_url("keyboard-protocol")}#functional-key-definitions',
|
'functional': f'{website_url("keyboard-protocol")}#functional-key-definitions',
|
||||||
'shell_integration': website_url("shell-integration"),
|
'shell_integration': website_url("shell-integration"),
|
||||||
|
'github_discussions': 'https://github.com/kovidgoyal/kitty/discussions',
|
||||||
}
|
}
|
||||||
for actions in get_all_actions().values():
|
for actions in get_all_actions().values():
|
||||||
for ac in actions:
|
for ac in actions:
|
||||||
@@ -66,6 +67,14 @@ def remove_markup(text: str) -> str:
|
|||||||
if m.group(1) == 'ref':
|
if m.group(1) == 'ref':
|
||||||
q = m.group(2).split('<')[-1].rstrip('>')
|
q = m.group(2).split('<')[-1].rstrip('>')
|
||||||
return ref_map()[q]
|
return ref_map()[q]
|
||||||
|
if m.group(1) == 'ac':
|
||||||
|
q = m.group(2).split('<')[-1].rstrip('>')
|
||||||
|
return ref_map()[f'action-{q}']
|
||||||
|
if m.group(1) == 'disc':
|
||||||
|
parts = m.group(2).split('<')
|
||||||
|
t = parts[0].strip()
|
||||||
|
q = parts[-1].rstrip('>')
|
||||||
|
return f'{t} {ref_map()["github_discussions"]}/{q}'
|
||||||
return str(m.group(2))
|
return str(m.group(2))
|
||||||
|
|
||||||
return re.sub(r':([a-zA-Z0-9]+):`(.+?)`', sub, text, flags=re.DOTALL)
|
return re.sub(r':([a-zA-Z0-9]+):`(.+?)`', sub, text, flags=re.DOTALL)
|
||||||
@@ -214,7 +223,7 @@ class Option:
|
|||||||
if not self.documented:
|
if not self.documented:
|
||||||
return ans
|
return ans
|
||||||
mopts = [self] + option_group
|
mopts = [self] + option_group
|
||||||
a('.. opt:: ' + ', '.join(conf_name + '.' + mo.name for mo in mopts))
|
a('.. opt:: ' + ', '.join(f'{conf_name}.{mo.name}' for mo in mopts))
|
||||||
a('.. code-block:: conf')
|
a('.. code-block:: conf')
|
||||||
a('')
|
a('')
|
||||||
sz = max(len(x.name) for x in mopts)
|
sz = max(len(x.name) for x in mopts)
|
||||||
@@ -330,7 +339,7 @@ class Mapping:
|
|||||||
raise ValueError(f'The shortcut for {self.name} has no short_text')
|
raise ValueError(f'The shortcut for {self.name} has no short_text')
|
||||||
sc_text = f'{conf_name}.{self.short_text}'
|
sc_text = f'{conf_name}.{self.short_text}'
|
||||||
shortcut_slugs[f'{conf_name}.{self.name}'] = (sc_text, self.key_text.replace('kitty_mod', kitty_mod))
|
shortcut_slugs[f'{conf_name}.{self.name}'] = (sc_text, self.key_text.replace('kitty_mod', kitty_mod))
|
||||||
a('.. shortcut:: ' + sc_text)
|
a(f'.. shortcut:: {sc_text}')
|
||||||
block_started = False
|
block_started = False
|
||||||
for sc in [self] + action_group:
|
for sc in [self] + action_group:
|
||||||
if sc.add_to_default and sc.documented:
|
if sc.add_to_default and sc.documented:
|
||||||
@@ -534,7 +543,7 @@ class Group:
|
|||||||
ans[i] = ' '.join(parts)
|
ans[i] = ' '.join(parts)
|
||||||
|
|
||||||
if commented:
|
if commented:
|
||||||
ans = [x if x.startswith('#') or not x.strip() else ('# ' + x) for x in ans]
|
ans = [x if x.startswith('#') or not x.strip() else (f'# {x}') for x in ans]
|
||||||
|
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|||||||
@@ -72,9 +72,10 @@ class ToCmdline:
|
|||||||
def __exit__(self, *a: Any) -> None:
|
def __exit__(self, *a: Any) -> None:
|
||||||
self.override_env = None
|
self.override_env = None
|
||||||
|
|
||||||
def filter_env_vars(self, *a: str) -> 'ToCmdline':
|
def filter_env_vars(self, *a: str, **override: str) -> 'ToCmdline':
|
||||||
remove = frozenset(a)
|
remove = frozenset(a)
|
||||||
self.override_env = {k: v for k, v in os.environ.items() if k not in remove}
|
self.override_env = {k: v for k, v in os.environ.items() if k not in remove}
|
||||||
|
self.override_env.update(override)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __call__(self, x: str) -> List[str]:
|
def __call__(self, x: str) -> List[str]:
|
||||||
@@ -169,24 +170,30 @@ def parse_line(
|
|||||||
log_error(f'Ignoring invalid config line: {line}')
|
log_error(f'Ignoring invalid config line: {line}')
|
||||||
return
|
return
|
||||||
key, val = m.groups()
|
key, val = m.groups()
|
||||||
if key == 'include':
|
if key in ('include', 'globinclude'):
|
||||||
val = os.path.expandvars(os.path.expanduser(val.strip()))
|
val = os.path.expandvars(os.path.expanduser(val.strip()))
|
||||||
if not os.path.isabs(val):
|
if key == 'globinclude':
|
||||||
val = os.path.join(base_path_for_includes, val)
|
from pathlib import Path
|
||||||
try:
|
vals = tuple(map(lambda x: str(os.fspath(x)), Path(base_path_for_includes).glob(val)))
|
||||||
with open(val, encoding='utf-8', errors='replace') as include:
|
else:
|
||||||
with currently_parsing.set_file(val):
|
if not os.path.isabs(val):
|
||||||
_parse(include, parse_conf_item, ans, accumulate_bad_lines)
|
val = os.path.join(base_path_for_includes, val)
|
||||||
except FileNotFoundError:
|
vals = (val,)
|
||||||
log_error(
|
for val in vals:
|
||||||
'Could not find included config file: {}, ignoring'.
|
try:
|
||||||
format(val)
|
with open(val, encoding='utf-8', errors='replace') as include:
|
||||||
)
|
with currently_parsing.set_file(val):
|
||||||
except OSError:
|
_parse(include, parse_conf_item, ans, accumulate_bad_lines)
|
||||||
log_error(
|
except FileNotFoundError:
|
||||||
'Could not read from included config file: {}, ignoring'.
|
log_error(
|
||||||
format(val)
|
'Could not find included config file: {}, ignoring'.
|
||||||
)
|
format(val)
|
||||||
|
)
|
||||||
|
except OSError:
|
||||||
|
log_error(
|
||||||
|
'Could not read from included config file: {}, ignoring'.
|
||||||
|
format(val)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
if not parse_conf_item(key, val, ans):
|
if not parse_conf_item(key, val, ans):
|
||||||
log_error(f'Ignoring unknown config key: {key}')
|
log_error(f'Ignoring unknown config key: {key}')
|
||||||
@@ -316,8 +323,7 @@ def parse_kittens_func_args(action: str, args_funcs: Dict[str, KeyFunc[Tuple[str
|
|||||||
parser = args_funcs[func]
|
parser = args_funcs[func]
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
raise KeyError(
|
raise KeyError(
|
||||||
'Unknown action: {}. Check if map action: '
|
f'Unknown action: {func}. Check if map action: {action} is valid'
|
||||||
'{} is valid'.format(func, action)
|
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -49,13 +49,12 @@ def atomic_save(data: bytes, path: str) -> None:
|
|||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
log_error('Failed to delete temp file {} for atomic save with error: {}'.format(
|
log_error(f'Failed to delete temp file {p} for atomic save with error: {err}')
|
||||||
p, err))
|
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def cached_values_for(name: str) -> Generator[Dict[str, Any], None, None]:
|
def cached_values_for(name: str) -> Generator[Dict[str, Any], None, None]:
|
||||||
cached_path = os.path.join(cache_dir(), name + '.json')
|
cached_path = os.path.join(cache_dir(), f'{name}.json')
|
||||||
cached_values: Dict[str, Any] = {}
|
cached_values: Dict[str, Any] = {}
|
||||||
try:
|
try:
|
||||||
with open(cached_path, 'rb') as f:
|
with open(cached_path, 'rb') as f:
|
||||||
@@ -63,8 +62,7 @@ def cached_values_for(name: str) -> Generator[Dict[str, Any], None, None]:
|
|||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
log_error('Failed to load cached in {} values with error: {}'.format(
|
log_error(f'Failed to load cached in {name} values with error: {err}')
|
||||||
name, err))
|
|
||||||
|
|
||||||
yield cached_values
|
yield cached_values
|
||||||
|
|
||||||
@@ -72,8 +70,7 @@ def cached_values_for(name: str) -> Generator[Dict[str, Any], None, None]:
|
|||||||
data = json.dumps(cached_values).encode('utf-8')
|
data = json.dumps(cached_values).encode('utf-8')
|
||||||
atomic_save(data, cached_path)
|
atomic_save(data, cached_path)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
log_error('Failed to save cached values with error: {}'.format(
|
log_error(f'Failed to save cached values with error: {err}')
|
||||||
err))
|
|
||||||
|
|
||||||
|
|
||||||
def commented_out_default_config() -> str:
|
def commented_out_default_config() -> str:
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class Version(NamedTuple):
|
|||||||
|
|
||||||
appname: str = 'kitty'
|
appname: str = 'kitty'
|
||||||
kitty_face = '🐱'
|
kitty_face = '🐱'
|
||||||
version: Version = Version(0, 24, 0)
|
version: Version = Version(0, 24, 3)
|
||||||
str_version: str = '.'.join(map(str, version))
|
str_version: str = '.'.join(map(str, version))
|
||||||
_plat = sys.platform.lower()
|
_plat = sys.platform.lower()
|
||||||
is_macos: bool = 'darwin' in _plat
|
is_macos: bool = 'darwin' in _plat
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ START_ALLOW_CASE_RANGE
|
|||||||
case 3:
|
case 3:
|
||||||
self->italic = true; break;
|
self->italic = true; break;
|
||||||
case 4:
|
case 4:
|
||||||
if (i < count) { self->decoration = MIN(3, params[i]); i++; }
|
if (i < count) { self->decoration = MIN(5, params[i]); i++; }
|
||||||
else self->decoration = 1;
|
else self->decoration = 1;
|
||||||
break;
|
break;
|
||||||
case 7:
|
case 7:
|
||||||
@@ -161,7 +161,7 @@ apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, int *params, un
|
|||||||
S(italic, true);
|
S(italic, true);
|
||||||
case 4: {
|
case 4: {
|
||||||
uint8_t val = 1;
|
uint8_t val = 1;
|
||||||
if (i < count) { val = MIN(3, params[i]); i++; }
|
if (i < count) { val = MIN(5, params[i]); i++; }
|
||||||
S(decoration, val);
|
S(decoration, val);
|
||||||
}
|
}
|
||||||
case 7:
|
case 7:
|
||||||
|
|||||||
@@ -302,6 +302,8 @@ PyInit_fast_data_types(void) {
|
|||||||
s(STRIKETHROUGH, strike); s(DIM, dim); s(DECORATION, decoration);
|
s(STRIKETHROUGH, strike); s(DIM, dim); s(DECORATION, decoration);
|
||||||
#undef s
|
#undef s
|
||||||
PyModule_AddIntConstant(m, "MARK_MASK", MARK_MASK);
|
PyModule_AddIntConstant(m, "MARK_MASK", MARK_MASK);
|
||||||
|
PyModule_AddIntConstant(m, "DECORATION_MASK", DECORATION_MASK);
|
||||||
|
PyModule_AddIntConstant(m, "NUM_UNDERLINE_STYLES", NUM_UNDERLINE_STYLES);
|
||||||
PyModule_AddStringMacro(m, ERROR_PREFIX);
|
PyModule_AddStringMacro(m, ERROR_PREFIX);
|
||||||
#ifdef KITTY_VCS_REV
|
#ifdef KITTY_VCS_REV
|
||||||
PyModule_AddStringMacro(m, KITTY_VCS_REV);
|
PyModule_AddStringMacro(m, KITTY_VCS_REV);
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ typedef struct {
|
|||||||
typedef union CellAttrs {
|
typedef union CellAttrs {
|
||||||
struct {
|
struct {
|
||||||
uint16_t width : 2;
|
uint16_t width : 2;
|
||||||
uint16_t decoration : 2;
|
uint16_t decoration : 3;
|
||||||
uint16_t bold : 1;
|
uint16_t bold : 1;
|
||||||
uint16_t italic : 1;
|
uint16_t italic : 1;
|
||||||
uint16_t reverse : 1;
|
uint16_t reverse : 1;
|
||||||
@@ -154,6 +154,8 @@ typedef union CellAttrs {
|
|||||||
} CellAttrs;
|
} CellAttrs;
|
||||||
#define MARK_MASK (3u)
|
#define MARK_MASK (3u)
|
||||||
#define WIDTH_MASK (3u)
|
#define WIDTH_MASK (3u)
|
||||||
|
#define DECORATION_MASK (7u)
|
||||||
|
#define NUM_UNDERLINE_STYLES (5u)
|
||||||
#define SGR_MASK (~(((CellAttrs){.width=WIDTH_MASK, .mark=MARK_MASK}).val))
|
#define SGR_MASK (~(((CellAttrs){.width=WIDTH_MASK, .mark=MARK_MASK}).val))
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|||||||
@@ -7,11 +7,10 @@ import socket
|
|||||||
import sys
|
import sys
|
||||||
import termios
|
import termios
|
||||||
import time
|
import time
|
||||||
|
from contextlib import suppress
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from typing import (
|
from typing import IO, Callable, Dict, Iterator, Optional, Set, TypeVar
|
||||||
IO, Callable, Dict, Generator, Iterable, Iterator, Optional, Set, Tuple
|
|
||||||
)
|
|
||||||
|
|
||||||
from kittens.tui.operations import colored, styled
|
from kittens.tui.operations import colored, styled
|
||||||
|
|
||||||
@@ -19,14 +18,15 @@ from .cli import version
|
|||||||
from .constants import (
|
from .constants import (
|
||||||
extensions_dir, is_macos, is_wayland, kitty_base_dir, kitty_exe, shell_path
|
extensions_dir, is_macos, is_wayland, kitty_base_dir, kitty_exe, shell_path
|
||||||
)
|
)
|
||||||
from .fast_data_types import num_users, Color
|
from .fast_data_types import Color, num_users
|
||||||
from .options.types import Options as KittyOpts, defaults
|
from .options.types import Options as KittyOpts, defaults
|
||||||
from .options.utils import MouseMap
|
from .options.utils import SequenceMap
|
||||||
from .rgb import color_as_sharp
|
from .rgb import color_as_sharp
|
||||||
from .types import MouseEvent, SingleKey
|
from .types import MouseEvent, Shortcut
|
||||||
from .typing import SequenceMap
|
|
||||||
|
|
||||||
ShortcutMap = Dict[Tuple[SingleKey, ...], str]
|
AnyEvent = TypeVar('AnyEvent', MouseEvent, Shortcut)
|
||||||
|
Print = Callable[..., None]
|
||||||
|
ShortcutMap = Dict[Shortcut, str]
|
||||||
|
|
||||||
|
|
||||||
def green(x: str) -> str:
|
def green(x: str) -> str:
|
||||||
@@ -41,81 +41,41 @@ def title(x: str) -> str:
|
|||||||
return colored(x, 'blue', intense=True)
|
return colored(x, 'blue', intense=True)
|
||||||
|
|
||||||
|
|
||||||
def mod_to_names(mods: int) -> Generator[str, None, None]:
|
def print_event(ev: AnyEvent, defn: str, print: Print) -> None:
|
||||||
from .fast_data_types import (
|
print(f'\t{ev.human_repr} → {defn}')
|
||||||
GLFW_MOD_ALT, GLFW_MOD_CAPS_LOCK, GLFW_MOD_CONTROL, GLFW_MOD_HYPER,
|
|
||||||
GLFW_MOD_META, GLFW_MOD_NUM_LOCK, GLFW_MOD_SHIFT, GLFW_MOD_SUPER
|
|
||||||
)
|
|
||||||
modmap = {'shift': GLFW_MOD_SHIFT, 'alt': GLFW_MOD_ALT, 'ctrl': GLFW_MOD_CONTROL, ('cmd' if is_macos else 'super'): GLFW_MOD_SUPER,
|
|
||||||
'hyper': GLFW_MOD_HYPER, 'meta': GLFW_MOD_META, 'num_lock': GLFW_MOD_NUM_LOCK, 'caps_lock': GLFW_MOD_CAPS_LOCK}
|
|
||||||
for name, val in modmap.items():
|
|
||||||
if mods & val:
|
|
||||||
yield name
|
|
||||||
|
|
||||||
|
|
||||||
def print_shortcut(key_sequence: Iterable[SingleKey], defn: str, print: Callable[..., None]) -> None:
|
def print_mapping_changes(defns: Dict[AnyEvent, str], changes: Set[AnyEvent], text: str, print: Print) -> None:
|
||||||
from .fast_data_types import glfw_get_key_name
|
|
||||||
keys = []
|
|
||||||
for key_spec in key_sequence:
|
|
||||||
names = []
|
|
||||||
mods, is_native, key = key_spec
|
|
||||||
names = list(mod_to_names(mods))
|
|
||||||
if key:
|
|
||||||
kname = glfw_get_key_name(0, key) if is_native else glfw_get_key_name(key, 0)
|
|
||||||
names.append(kname or f'{key}')
|
|
||||||
keys.append('+'.join(names))
|
|
||||||
|
|
||||||
print('\t' + ' > '.join(keys), defn)
|
|
||||||
|
|
||||||
|
|
||||||
def print_shortcut_changes(defns: ShortcutMap, text: str, changes: Set[Tuple[SingleKey, ...]], print: Callable[..., None]) -> None:
|
|
||||||
if changes:
|
if changes:
|
||||||
print(title(text))
|
print(title(text))
|
||||||
|
|
||||||
for k in sorted(changes):
|
for k in sorted(changes):
|
||||||
print_shortcut(k, defns[k], print)
|
print_event(k, defns[k], print)
|
||||||
|
|
||||||
|
|
||||||
def compare_keymaps(final: ShortcutMap, initial: ShortcutMap, print: Callable[..., None]) -> None:
|
def compare_maps(final: Dict[AnyEvent, str], initial: Dict[AnyEvent, str], print: Print) -> None:
|
||||||
|
is_mouse = False
|
||||||
|
for k in initial:
|
||||||
|
if isinstance(k, MouseEvent):
|
||||||
|
is_mouse = True
|
||||||
|
break
|
||||||
added = set(final) - set(initial)
|
added = set(final) - set(initial)
|
||||||
removed = set(initial) - set(final)
|
removed = set(initial) - set(final)
|
||||||
changed = {k for k in set(final) & set(initial) if final[k] != initial[k]}
|
changed = {k for k in set(final) & set(initial) if final[k] != initial[k]}
|
||||||
print_shortcut_changes(final, 'Added shortcuts:', added, print)
|
which = 'mouse actions' if is_mouse else 'shortcuts'
|
||||||
print_shortcut_changes(initial, 'Removed shortcuts:', removed, print)
|
print_mapping_changes(final, added, f'Added {which}:', print)
|
||||||
print_shortcut_changes(final, 'Changed shortcuts:', changed, print)
|
print_mapping_changes(initial, removed, f'Removed {which}:', print)
|
||||||
|
print_mapping_changes(final, changed, f'Changed {which}:', print)
|
||||||
|
|
||||||
|
|
||||||
def flatten_sequence_map(m: SequenceMap) -> ShortcutMap:
|
def flatten_sequence_map(m: SequenceMap) -> ShortcutMap:
|
||||||
ans = {}
|
ans = {}
|
||||||
for key_spec, rest_map in m.items():
|
for key_spec, rest_map in m.items():
|
||||||
for r, action in rest_map.items():
|
for r, action in rest_map.items():
|
||||||
ans[(key_spec,) + (r)] = action
|
ans[Shortcut((key_spec,) + (r))] = action
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
def compare_mousemaps(final: MouseMap, initial: MouseMap, print: Callable[..., None]) -> None:
|
def compare_opts(opts: KittyOpts, print: Print) -> None:
|
||||||
added = set(final) - set(initial)
|
|
||||||
removed = set(initial) - set(final)
|
|
||||||
changed = {k for k in set(final) & set(initial) if final[k] != initial[k]}
|
|
||||||
|
|
||||||
def print_mouse_action(trigger: MouseEvent, defn: str) -> None:
|
|
||||||
names = list(mod_to_names(trigger.mods)) + [f'b{trigger.button+1}']
|
|
||||||
when = {-1: 'repeat', 1: 'press', 2: 'doublepress', 3: 'triplepress'}.get(trigger.repeat_count, trigger.repeat_count)
|
|
||||||
grabbed = 'grabbed' if trigger.grabbed else 'ungrabbed'
|
|
||||||
print('\t', '+'.join(names), when, grabbed, defn)
|
|
||||||
|
|
||||||
def print_changes(defns: MouseMap, changes: Set[MouseEvent], text: str) -> None:
|
|
||||||
if changes:
|
|
||||||
print(title(text))
|
|
||||||
for k in sorted(changes):
|
|
||||||
print_mouse_action(k, defns[k])
|
|
||||||
|
|
||||||
print_changes(final, added, 'Added mouse actions:')
|
|
||||||
print_changes(initial, removed, 'Removed mouse actions:')
|
|
||||||
print_changes(final, changed, 'Changed mouse actions:')
|
|
||||||
|
|
||||||
|
|
||||||
def compare_opts(opts: KittyOpts, print: Callable[..., None]) -> None:
|
|
||||||
from .config import load_config
|
from .config import load_config
|
||||||
print()
|
print()
|
||||||
print('Config options different from defaults:')
|
print('Config options different from defaults:')
|
||||||
@@ -131,7 +91,7 @@ def compare_opts(opts: KittyOpts, print: Callable[..., None]) -> None:
|
|||||||
for f in changed_opts:
|
for f in changed_opts:
|
||||||
val = getattr(opts, f)
|
val = getattr(opts, f)
|
||||||
if isinstance(val, dict):
|
if isinstance(val, dict):
|
||||||
print(f'{title(f)}:')
|
print(title(f'{f}:'))
|
||||||
if f == 'symbol_map':
|
if f == 'symbol_map':
|
||||||
for k in sorted(val):
|
for k in sorted(val):
|
||||||
print(f'\tU+{k[0]:04x} - U+{k[1]:04x} → {val[k]}')
|
print(f'\tU+{k[0]:04x} - U+{k[1]:04x} → {val[k]}')
|
||||||
@@ -144,14 +104,14 @@ def compare_opts(opts: KittyOpts, print: Callable[..., None]) -> None:
|
|||||||
else:
|
else:
|
||||||
print(fmt.format(f), str(getattr(opts, f)))
|
print(fmt.format(f), str(getattr(opts, f)))
|
||||||
|
|
||||||
compare_mousemaps(opts.mousemap, default_opts.mousemap, print)
|
compare_maps(opts.mousemap, default_opts.mousemap, print)
|
||||||
final_, initial_ = opts.keymap, default_opts.keymap
|
final_, initial_ = opts.keymap, default_opts.keymap
|
||||||
final: ShortcutMap = {(k,): v for k, v in final_.items()}
|
final: ShortcutMap = {Shortcut((k,)): v for k, v in final_.items()}
|
||||||
initial: ShortcutMap = {(k,): v for k, v in initial_.items()}
|
initial: ShortcutMap = {Shortcut((k,)): v for k, v in initial_.items()}
|
||||||
final_s, initial_s = map(flatten_sequence_map, (opts.sequence_map, default_opts.sequence_map))
|
final_s, initial_s = map(flatten_sequence_map, (opts.sequence_map, default_opts.sequence_map))
|
||||||
final.update(final_s)
|
final.update(final_s)
|
||||||
initial.update(initial_s)
|
initial.update(initial_s)
|
||||||
compare_keymaps(final, initial, print)
|
compare_maps(final, initial, print)
|
||||||
if colors:
|
if colors:
|
||||||
print(f'{title("Colors")}:', end='\n\t')
|
print(f'{title("Colors")}:', end='\n\t')
|
||||||
print('\n\t'.join(sorted(colors)))
|
print('\n\t'.join(sorted(colors)))
|
||||||
@@ -162,16 +122,22 @@ class IssueData:
|
|||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.uname = os.uname()
|
self.uname = os.uname()
|
||||||
self.s, self.n, self.r, self.v, self.m = self.uname
|
self.s, self.n, self.r, self.v, self.m = self.uname
|
||||||
self.hostname = self.o = socket.gethostname()
|
try:
|
||||||
|
self.hostname = self.o = socket.gethostname()
|
||||||
|
except Exception:
|
||||||
|
self.hostname = self.o = 'localhost'
|
||||||
_time = time.localtime()
|
_time = time.localtime()
|
||||||
self.formatted_time = self.d = time.strftime('%a %b %d %Y', _time)
|
self.formatted_time = self.d = time.strftime('%a %b %d %Y', _time)
|
||||||
self.formatted_date = self.t = time.strftime('%H:%M:%S', _time)
|
self.formatted_date = self.t = time.strftime('%H:%M:%S', _time)
|
||||||
try:
|
try:
|
||||||
self.tty_name = format_tty_name(os.ttyname(sys.stdin.fileno()))
|
self.tty_name = format_tty_name(os.ctermid())
|
||||||
except OSError:
|
except OSError:
|
||||||
self.tty_name = '(none)'
|
self.tty_name = '(none)'
|
||||||
self.l = self.tty_name # noqa
|
self.l = self.tty_name # noqa
|
||||||
self.baud_rate = termios.tcgetattr(sys.stdin.fileno())[5]
|
self.baud_rate = 0
|
||||||
|
if sys.stdin.isatty():
|
||||||
|
with suppress(OSError):
|
||||||
|
self.baud_rate = termios.tcgetattr(sys.stdin.fileno())[5]
|
||||||
self.b = str(self.baud_rate)
|
self.b = str(self.baud_rate)
|
||||||
try:
|
try:
|
||||||
self.num_users = num_users()
|
self.num_users = num_users()
|
||||||
@@ -218,13 +184,23 @@ def debug_config(opts: KittyOpts) -> str:
|
|||||||
import subprocess
|
import subprocess
|
||||||
p(' '.join(subprocess.check_output(['sw_vers']).decode('utf-8').splitlines()).strip())
|
p(' '.join(subprocess.check_output(['sw_vers']).decode('utf-8').splitlines()).strip())
|
||||||
if os.path.exists('/etc/issue'):
|
if os.path.exists('/etc/issue'):
|
||||||
with open('/etc/issue', encoding='utf-8', errors='replace') as f:
|
try:
|
||||||
p(end=''.join(IssueData().parse_issue_file(f)))
|
idata = IssueData()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
with open('/etc/issue', encoding='utf-8', errors='replace') as f:
|
||||||
|
try:
|
||||||
|
datums = idata.parse_issue_file(f)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
p(end=''.join(datums))
|
||||||
if os.path.exists('/etc/lsb-release'):
|
if os.path.exists('/etc/lsb-release'):
|
||||||
with open('/etc/lsb-release', encoding='utf-8', errors='replace') as f:
|
with open('/etc/lsb-release', encoding='utf-8', errors='replace') as f:
|
||||||
p(f.read().strip())
|
p(f.read().strip())
|
||||||
if not is_macos:
|
if not is_macos:
|
||||||
p('Running under:' + green('Wayland' if is_wayland() else 'X11'))
|
p('Running under:', green('Wayland' if is_wayland() else 'X11'))
|
||||||
p(green('Frozen:'), 'True' if getattr(sys, 'frozen', False) else 'False')
|
p(green('Frozen:'), 'True' if getattr(sys, 'frozen', False) else 'False')
|
||||||
p(green('Paths:'))
|
p(green('Paths:'))
|
||||||
p(yellow(' kitty:'), os.path.realpath(kitty_exe()))
|
p(yellow(' kitty:'), os.path.realpath(kitty_exe()))
|
||||||
@@ -238,4 +214,20 @@ def debug_config(opts: KittyOpts) -> str:
|
|||||||
p(green('Loaded config overrides:'))
|
p(green('Loaded config overrides:'))
|
||||||
p(' ', '\n '.join(opts.config_overrides))
|
p(' ', '\n '.join(opts.config_overrides))
|
||||||
compare_opts(opts, p)
|
compare_opts(opts, p)
|
||||||
|
p()
|
||||||
|
p(green('Important environment variables seen by the kitty process:'))
|
||||||
|
|
||||||
|
def penv(k: str) -> None:
|
||||||
|
v = os.environ.get(k)
|
||||||
|
if v is not None:
|
||||||
|
p('\t' + k.ljust(35), styled(v, dim=True))
|
||||||
|
|
||||||
|
for k in (
|
||||||
|
'PATH LANG KITTY_CONFIG_DIRECTORY KITTY_CACHE_DIRECTORY VISUAL EDITOR SHELL'
|
||||||
|
' GLFW_IM_MODULE KITTY_WAYLAND_DETECT_MODIFIERS DISPLAY WAYLAND_DISPLAY USER XCURSOR_SIZE'
|
||||||
|
).split():
|
||||||
|
penv(k)
|
||||||
|
for k in os.environ:
|
||||||
|
if k.startswith('LC_') or k.startswith('XDG_'):
|
||||||
|
penv(k)
|
||||||
return out.getvalue()
|
return out.getvalue()
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ play_canberra_sound(const char *which_sound, const char *event_id, bool is_path,
|
|||||||
which_type, which_sound,
|
which_type, which_sound,
|
||||||
"event.description", event_id,
|
"event.description", event_id,
|
||||||
"media.role", media_role,
|
"media.role", media_role,
|
||||||
|
"canberra.cache-control", "permanent",
|
||||||
NULL
|
NULL
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
26
kitty/emoji.h
generated
26
kitty/emoji.h
generated
@@ -1,4 +1,4 @@
|
|||||||
// unicode data, built from the unicode standard on: 2021-10-07
|
// unicode data, built from the unicode standard on: 2022-01-05
|
||||||
// see gen-wcwidth.py
|
// see gen-wcwidth.py
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "data-types.h"
|
#include "data-types.h"
|
||||||
@@ -276,7 +276,7 @@ is_emoji(char_type code) {
|
|||||||
return true;
|
return true;
|
||||||
case 0x1f6d5 ... 0x1f6d7:
|
case 0x1f6d5 ... 0x1f6d7:
|
||||||
return true;
|
return true;
|
||||||
case 0x1f6e0 ... 0x1f6e5:
|
case 0x1f6dd ... 0x1f6e5:
|
||||||
return true;
|
return true;
|
||||||
case 0x1f6e9:
|
case 0x1f6e9:
|
||||||
return true;
|
return true;
|
||||||
@@ -288,29 +288,31 @@ is_emoji(char_type code) {
|
|||||||
return true;
|
return true;
|
||||||
case 0x1f7e0 ... 0x1f7eb:
|
case 0x1f7e0 ... 0x1f7eb:
|
||||||
return true;
|
return true;
|
||||||
|
case 0x1f7f0:
|
||||||
|
return true;
|
||||||
case 0x1f90c ... 0x1f93a:
|
case 0x1f90c ... 0x1f93a:
|
||||||
return true;
|
return true;
|
||||||
case 0x1f93c ... 0x1f945:
|
case 0x1f93c ... 0x1f945:
|
||||||
return true;
|
return true;
|
||||||
case 0x1f947 ... 0x1f978:
|
case 0x1f947 ... 0x1f9ff:
|
||||||
return true;
|
|
||||||
case 0x1f97a ... 0x1f9cb:
|
|
||||||
return true;
|
|
||||||
case 0x1f9cd ... 0x1f9ff:
|
|
||||||
return true;
|
return true;
|
||||||
case 0x1fa70 ... 0x1fa74:
|
case 0x1fa70 ... 0x1fa74:
|
||||||
return true;
|
return true;
|
||||||
case 0x1fa78 ... 0x1fa7a:
|
case 0x1fa78 ... 0x1fa7c:
|
||||||
return true;
|
return true;
|
||||||
case 0x1fa80 ... 0x1fa86:
|
case 0x1fa80 ... 0x1fa86:
|
||||||
return true;
|
return true;
|
||||||
case 0x1fa90 ... 0x1faa8:
|
case 0x1fa90 ... 0x1faac:
|
||||||
return true;
|
return true;
|
||||||
case 0x1fab0 ... 0x1fab6:
|
case 0x1fab0 ... 0x1faba:
|
||||||
return true;
|
return true;
|
||||||
case 0x1fac0 ... 0x1fac2:
|
case 0x1fac0 ... 0x1fac5:
|
||||||
return true;
|
return true;
|
||||||
case 0x1fad0 ... 0x1fad6:
|
case 0x1fad0 ... 0x1fad9:
|
||||||
|
return true;
|
||||||
|
case 0x1fae0 ... 0x1fae7:
|
||||||
|
return true;
|
||||||
|
case 0x1faf0 ... 0x1faf6:
|
||||||
return true;
|
return true;
|
||||||
default: return false;
|
default: return false;
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user