Compare commits
704 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbccfb9d0d | ||
|
|
9fdd8e4ae4 | ||
|
|
6b6dea5190 | ||
|
|
5af551c96d | ||
|
|
6761fdca1a | ||
|
|
725da39ecb | ||
|
|
9005fc359d | ||
|
|
33e4a44008 | ||
|
|
3bddb9d95b | ||
|
|
fa01e0b76f | ||
|
|
f8f188ecff | ||
|
|
07935d9bd3 | ||
|
|
a827f4190c | ||
|
|
18c108ed03 | ||
|
|
2d70059f47 | ||
|
|
63ff3cfb9f | ||
|
|
ca70f22d46 | ||
|
|
c247a6c0cc | ||
|
|
7421a0b9e3 | ||
|
|
30b5e8bf8f | ||
|
|
96475ce3ee | ||
|
|
24669fb6af | ||
|
|
851885568f | ||
|
|
8efbc479e1 | ||
|
|
2167bfd5b5 | ||
|
|
490d4455af | ||
|
|
c94e06ccd2 | ||
|
|
ea37799fb0 | ||
|
|
d00fbd73fc | ||
|
|
361b23e3f1 | ||
|
|
8feccdf63f | ||
|
|
ed905fcf6d | ||
|
|
50a6abaac1 | ||
|
|
718f7e77a1 | ||
|
|
c4aeb1adba | ||
|
|
d9c69f0a54 | ||
|
|
5afdbbe6f1 | ||
|
|
4273cb35bf | ||
|
|
89be7a031d | ||
|
|
06c5127856 | ||
|
|
fd298256d7 | ||
|
|
e0246044b7 | ||
|
|
2f677990b2 | ||
|
|
da2e4c8503 | ||
|
|
a1eb236616 | ||
|
|
11186735c2 | ||
|
|
a038f62a5b | ||
|
|
f45acc055e | ||
|
|
43a25c49c4 | ||
|
|
e47e2b2560 | ||
|
|
7652065134 | ||
|
|
d0da418e49 | ||
|
|
77e72cfd9a | ||
|
|
7b2ecb0498 | ||
|
|
318c5bca67 | ||
|
|
d52da934ca | ||
|
|
21951e3668 | ||
|
|
64bef38813 | ||
|
|
fe67e3dde7 | ||
|
|
fc5e74e457 | ||
|
|
fdbd8df7b2 | ||
|
|
246972b59f | ||
|
|
6057296499 | ||
|
|
b4b2207b93 | ||
|
|
b881364c50 | ||
|
|
76059882c4 | ||
|
|
be71f5f1f4 | ||
|
|
021dfcad39 | ||
|
|
4e7498b854 | ||
|
|
494035c94b | ||
|
|
9c4230ad9c | ||
|
|
056c2c12bb | ||
|
|
a97c440872 | ||
|
|
653d823175 | ||
|
|
c2a2dbc88c | ||
|
|
77492d31cf | ||
|
|
aaa609109e | ||
|
|
2367fad90b | ||
|
|
aaa521bd24 | ||
|
|
d22686da10 | ||
|
|
48b93ab28f | ||
|
|
703a1b83a8 | ||
|
|
c9441bd3e2 | ||
|
|
9de6d619ee | ||
|
|
9aae31ef2f | ||
|
|
dca04c549b | ||
|
|
69e76d6eeb | ||
|
|
b4a93c67c9 | ||
|
|
f098e6c973 | ||
|
|
f8ebba3954 | ||
|
|
37ee693de2 | ||
|
|
f77ad2b41d | ||
|
|
e99eb2e72b | ||
|
|
86b89072f3 | ||
|
|
022bee0560 | ||
|
|
204ea44946 | ||
|
|
3f2489b306 | ||
|
|
291a2d6295 | ||
|
|
b244d13e8a | ||
|
|
b33324d07c | ||
|
|
ae2a641db5 | ||
|
|
1cac757365 | ||
|
|
992de77040 | ||
|
|
f6b5e54890 | ||
|
|
d20c65ef80 | ||
|
|
4a8562a85f | ||
|
|
1ba7b5f5a3 | ||
|
|
f895b7f8cb | ||
|
|
16542e96bf | ||
|
|
3005b8b9d6 | ||
|
|
35653ef4b4 | ||
|
|
36b3582825 | ||
|
|
c17c801a31 | ||
|
|
460c353898 | ||
|
|
3fa7007e91 | ||
|
|
4cf6373203 | ||
|
|
e0b3bb1830 | ||
|
|
47ec372c30 | ||
|
|
d5366a421f | ||
|
|
3d5977669e | ||
|
|
36da668e6f | ||
|
|
052913c656 | ||
|
|
0ef055a2b2 | ||
|
|
a7bd6922e5 | ||
|
|
8d9ba4a772 | ||
|
|
222beedc2a | ||
|
|
8ba8320189 | ||
|
|
3501a2a1da | ||
|
|
819ee94a70 | ||
|
|
7e7dbc5ecf | ||
|
|
3a6bc94d25 | ||
|
|
07a4cec616 | ||
|
|
60de840d69 | ||
|
|
928167c0b2 | ||
|
|
2aac220cdb | ||
|
|
d8a74e8a3c | ||
|
|
019e6efeda | ||
|
|
a7a7216b30 | ||
|
|
ae8ca5272b | ||
|
|
06c1236cc6 | ||
|
|
4d3982c5cf | ||
|
|
a0ff625f61 | ||
|
|
b44345d78e | ||
|
|
96229becd1 | ||
|
|
b269cab0b8 | ||
|
|
0b3c8210f3 | ||
|
|
074614bc8e | ||
|
|
4ceb4cfc22 | ||
|
|
0bb08d8bdd | ||
|
|
b9e6557f72 | ||
|
|
7ed268ea2f | ||
|
|
a91b3b9487 | ||
|
|
cdf236d01b | ||
|
|
f54efb7886 | ||
|
|
0ffee56170 | ||
|
|
ab97646576 | ||
|
|
33b62dcabc | ||
|
|
39e6e91d69 | ||
|
|
dedd80c6b5 | ||
|
|
df11da74e9 | ||
|
|
9991dd6ce4 | ||
|
|
e777364f91 | ||
|
|
a32ed5ae5f | ||
|
|
db1e48848d | ||
|
|
c02fa79591 | ||
|
|
6f0a59c696 | ||
|
|
bc715d4348 | ||
|
|
55e4761302 | ||
|
|
18fe2e8dfa | ||
|
|
18a07a78b5 | ||
|
|
aa7024b264 | ||
|
|
c67ab2a6ff | ||
|
|
d15eab31da | ||
|
|
a6900443d5 | ||
|
|
235bcacc7b | ||
|
|
3e26f96744 | ||
|
|
718111582a | ||
|
|
f5cdbe53ea | ||
|
|
bf761a7189 | ||
|
|
05ee01b4dd | ||
|
|
53166d659e | ||
|
|
34aea3bca1 | ||
|
|
f26519863f | ||
|
|
a57e96500a | ||
|
|
5e27c21856 | ||
|
|
4601cd13dc | ||
|
|
ad92541ff3 | ||
|
|
3f77a2044e | ||
|
|
75e57a48e0 | ||
|
|
160fbd2071 | ||
|
|
f5743f4610 | ||
|
|
82f9aecacb | ||
|
|
be6556c884 | ||
|
|
f941ffb1fe | ||
|
|
cbdba0b6aa | ||
|
|
ce04f102a7 | ||
|
|
fc3bd2d15f | ||
|
|
a958cabf01 | ||
|
|
d0a2eab79e | ||
|
|
b835afefee | ||
|
|
d0a7be90f9 | ||
|
|
1f3550b84c | ||
|
|
939212e1d4 | ||
|
|
7db6318806 | ||
|
|
e5d9182606 | ||
|
|
d519553581 | ||
|
|
e99b1be023 | ||
|
|
e460866ebc | ||
|
|
23aea68e32 | ||
|
|
65c2cbe93a | ||
|
|
ee31ba0c76 | ||
|
|
3ed9d9a069 | ||
|
|
721e51c117 | ||
|
|
09daaf1ec3 | ||
|
|
b2d62f10d2 | ||
|
|
0d542b2f60 | ||
|
|
04fd058e0f | ||
|
|
31a0a9427e | ||
|
|
04f10ba570 | ||
|
|
34b7b2e4e8 | ||
|
|
09183772d3 | ||
|
|
3e4eca5816 | ||
|
|
30c6c4697f | ||
|
|
b650275d45 | ||
|
|
174bd5c57f | ||
|
|
ec51a219a7 | ||
|
|
cca2fb967c | ||
|
|
5b547d9b06 | ||
|
|
facb2df3f6 | ||
|
|
8395076da5 | ||
|
|
e113e0cba7 | ||
|
|
407d19c2dc | ||
|
|
382367506a | ||
|
|
bd5a85a4e7 | ||
|
|
631f97eb26 | ||
|
|
0c0ce4844c | ||
|
|
6bf290149e | ||
|
|
60fa812b60 | ||
|
|
61f3a39aa0 | ||
|
|
a6949df727 | ||
|
|
d839a31d7f | ||
|
|
1de510f46f | ||
|
|
0359bbe4be | ||
|
|
40b355e593 | ||
|
|
adf9c4fc5f | ||
|
|
ae9d049011 | ||
|
|
d24977d164 | ||
|
|
3035d64127 | ||
|
|
70c9c058e2 | ||
|
|
1d9f73adec | ||
|
|
fca95af49b | ||
|
|
62fe392041 | ||
|
|
097d11819b | ||
|
|
691286f376 | ||
|
|
c910f6c832 | ||
|
|
65f9ac32ef | ||
|
|
27f2a2e3d5 | ||
|
|
88aab20376 | ||
|
|
a045ac3bd7 | ||
|
|
722b605407 | ||
|
|
7f53745077 | ||
|
|
7f902c4c03 | ||
|
|
1205c2055f | ||
|
|
f08f0f9dbd | ||
|
|
093c05e16a | ||
|
|
787100a4dc | ||
|
|
c493583f25 | ||
|
|
b4afbe47b3 | ||
|
|
6c4aeeeadd | ||
|
|
5b01519367 | ||
|
|
c494759606 | ||
|
|
2102c48b87 | ||
|
|
c498f4e8ab | ||
|
|
05a0166ebd | ||
|
|
0484365de6 | ||
|
|
cbab3b56de | ||
|
|
715f7985c7 | ||
|
|
2637018b53 | ||
|
|
1fa66ca45d | ||
|
|
13b10f691d | ||
|
|
3fe7d91713 | ||
|
|
98864091ff | ||
|
|
7750a461aa | ||
|
|
ac98b85157 | ||
|
|
21f6c6a5a8 | ||
|
|
b8d1491b99 | ||
|
|
d9c168d824 | ||
|
|
ee7f4ee1a4 | ||
|
|
ee3ce3c7d8 | ||
|
|
a57f38dbd5 | ||
|
|
3c4cb3c35b | ||
|
|
12cfe6d1d1 | ||
|
|
c64e8fc434 | ||
|
|
15f534a8f9 | ||
|
|
8e09e809aa | ||
|
|
fec5a80b89 | ||
|
|
1d50a188c4 | ||
|
|
b0d34b136f | ||
|
|
7eb234c250 | ||
|
|
d52434c0aa | ||
|
|
efc0b830cf | ||
|
|
4a8c4c4601 | ||
|
|
e498cedf56 | ||
|
|
6c8a52875e | ||
|
|
c1397fd366 | ||
|
|
3323ddcdef | ||
|
|
1be394cda7 | ||
|
|
f292092ffc | ||
|
|
b82e74f99a | ||
|
|
9b293ad66a | ||
|
|
3a4420f2d3 | ||
|
|
1eb8d6e845 | ||
|
|
e2f97dc44a | ||
|
|
ea9af96d2b | ||
|
|
e4033d997c | ||
|
|
bc38357193 | ||
|
|
618b486fd5 | ||
|
|
1986403e42 | ||
|
|
cf51f80b79 | ||
|
|
2fe17aa889 | ||
|
|
f76c8ad30b | ||
|
|
c421d3bb59 | ||
|
|
3d862da2be | ||
|
|
10b15caad9 | ||
|
|
e08238d5a0 | ||
|
|
d0104660c8 | ||
|
|
a4c2a9ef0d | ||
|
|
ee39d7ba14 | ||
|
|
62cb035afe | ||
|
|
7a8bc6b5d2 | ||
|
|
0d86ac23bb | ||
|
|
897fd9486e | ||
|
|
39d3d093c4 | ||
|
|
3f0b52bef4 | ||
|
|
4a04f27fde | ||
|
|
5fb02c0439 | ||
|
|
edfb4ef3f1 | ||
|
|
f037923b00 | ||
|
|
424aafb514 | ||
|
|
1b64167a3e | ||
|
|
a9a86e4604 | ||
|
|
1dc7c23b2f | ||
|
|
5bdcfbc2ce | ||
|
|
74e9bf4f31 | ||
|
|
0652529839 | ||
|
|
772d6597a9 | ||
|
|
4e99194022 | ||
|
|
f0ff2f4ea7 | ||
|
|
d583c533c2 | ||
|
|
ec1f219850 | ||
|
|
21d586cc86 | ||
|
|
8f7139d48f | ||
|
|
cfb6dae04d | ||
|
|
bceac3ee0b | ||
|
|
38d6edb0ca | ||
|
|
1b41a3bcee | ||
|
|
c54f934f6e | ||
|
|
94052bb712 | ||
|
|
7eb1dcc7ee | ||
|
|
626fbe827e | ||
|
|
28c154ecf0 | ||
|
|
8176f661ea | ||
|
|
0195e7f841 | ||
|
|
141b8a1481 | ||
|
|
be3ab90cff | ||
|
|
9782f16635 | ||
|
|
6930edfd8a | ||
|
|
6b1bec6550 | ||
|
|
dac0304815 | ||
|
|
cfcf81b5fb | ||
|
|
f072a02215 | ||
|
|
5f6e21da9b | ||
|
|
b9b38a4ec1 | ||
|
|
e5a7ba4f22 | ||
|
|
1dcf3f2afb | ||
|
|
2ec84969dd | ||
|
|
a08b945124 | ||
|
|
16f712fbaa | ||
|
|
1f63ff1d41 | ||
|
|
49d8f4689f | ||
|
|
e1f993938a | ||
|
|
ab65b9b2fb | ||
|
|
f7a621f89f | ||
|
|
b808a05e5e | ||
|
|
72c37c519d | ||
|
|
af4b072b29 | ||
|
|
739ed906f0 | ||
|
|
9fd462077a | ||
|
|
cb3671343e | ||
|
|
b18b705632 | ||
|
|
9963e78636 | ||
|
|
e39635ea16 | ||
|
|
9329b2c202 | ||
|
|
791f70e5a2 | ||
|
|
8272c4a429 | ||
|
|
f0357b061e | ||
|
|
7438e0e997 | ||
|
|
cca672e536 | ||
|
|
0fa12a687d | ||
|
|
d2b900c77f | ||
|
|
21242d9144 | ||
|
|
03fd138b63 | ||
|
|
1eeaef6dbb | ||
|
|
b9a93b68c4 | ||
|
|
e3728942dc | ||
|
|
7f0674ac27 | ||
|
|
60fcf7ab3d | ||
|
|
ee82a6ff18 | ||
|
|
bac5e402b7 | ||
|
|
732108109e | ||
|
|
ab3b86d65b | ||
|
|
d4d63506e1 | ||
|
|
3ad69f8ba3 | ||
|
|
9fd50a6ac0 | ||
|
|
9b5c8a18d4 | ||
|
|
9f3112ca56 | ||
|
|
aa9de47da3 | ||
|
|
5eaeda4402 | ||
|
|
ecb03d7dba | ||
|
|
88612d3691 | ||
|
|
2c0e70a423 | ||
|
|
d6b70f10d3 | ||
|
|
40b01fb702 | ||
|
|
89222d93f4 | ||
|
|
b3db3e5732 | ||
|
|
b45161ecc3 | ||
|
|
d4fc0af7de | ||
|
|
5ef8cce1d7 | ||
|
|
c98a147f94 | ||
|
|
e9975cb7eb | ||
|
|
33f0ac83c1 | ||
|
|
3bd45966c6 | ||
|
|
1b7244f3c8 | ||
|
|
1949f3c3d5 | ||
|
|
6a51ce5dc4 | ||
|
|
c0c8e1ce5e | ||
|
|
6a3d6191bd | ||
|
|
b2aa07d29f | ||
|
|
c84fb68bf8 | ||
|
|
128b9e1cd0 | ||
|
|
bbe8bcb28d | ||
|
|
6dc9c22543 | ||
|
|
59f5781f19 | ||
|
|
d61fb9cc42 | ||
|
|
ace052ee70 | ||
|
|
1451a8ccc0 | ||
|
|
547959c1ef | ||
|
|
b1a5e98630 | ||
|
|
f3a1ee7b9f | ||
|
|
b0a39b127d | ||
|
|
e36f1157cc | ||
|
|
053b9a786c | ||
|
|
2561d937da | ||
|
|
55f457caf4 | ||
|
|
f5cc2fd8ad | ||
|
|
e630b76fac | ||
|
|
0b6948cb4f | ||
|
|
b0a2dea747 | ||
|
|
f8395e36a1 | ||
|
|
6ec58f58ce | ||
|
|
96cbfe946c | ||
|
|
ac686a47c1 | ||
|
|
e05aaf77ce | ||
|
|
6b371eb8a8 | ||
|
|
f6660a0d55 | ||
|
|
3b9f213ea2 | ||
|
|
4f05024550 | ||
|
|
4d2d9d5574 | ||
|
|
14f44e79f7 | ||
|
|
ab960ea12d | ||
|
|
d276317e3a | ||
|
|
8b3b0064a9 | ||
|
|
66b890bfd3 | ||
|
|
2454ecd586 | ||
|
|
b891f4f874 | ||
|
|
92d06b3088 | ||
|
|
3067103b18 | ||
|
|
0e248b3faa | ||
|
|
d1a76bbcd9 | ||
|
|
1d91edfb00 | ||
|
|
cf7ecd5e66 | ||
|
|
63a2ecb89c | ||
|
|
8e6b58b1db | ||
|
|
0f594ef950 | ||
|
|
0c1b76c17a | ||
|
|
1c11ba8dbd | ||
|
|
529d5b2226 | ||
|
|
9fe16867c6 | ||
|
|
1d275c6b35 | ||
|
|
d19b29ced0 | ||
|
|
bbefcb2cd2 | ||
|
|
c864cba4a2 | ||
|
|
42546f4371 | ||
|
|
ff09bdcbf1 | ||
|
|
f36a9fffa3 | ||
|
|
933cced76c | ||
|
|
c51e7d3bde | ||
|
|
d3b31224cd | ||
|
|
ec9bb2302c | ||
|
|
009ef54de7 | ||
|
|
0e0e25f986 | ||
|
|
8b02b89bd6 | ||
|
|
8ba3c4ba50 | ||
|
|
16764c79b2 | ||
|
|
96c444d041 | ||
|
|
ec1c72e9a3 | ||
|
|
f9b1f6be5c | ||
|
|
9201d27875 | ||
|
|
ecbe79e04f | ||
|
|
6e34a7559b | ||
|
|
ea8b17565d | ||
|
|
a143faaf05 | ||
|
|
acdb56ad5d | ||
|
|
1f90238b18 | ||
|
|
89b636376e | ||
|
|
59f39ae3ab | ||
|
|
a92b20f85a | ||
|
|
e71c432b7c | ||
|
|
b6a5d09d4f | ||
|
|
9235d2b8e4 | ||
|
|
605847fd75 | ||
|
|
6222273adc | ||
|
|
6a759ea7d5 | ||
|
|
d8a7114dba | ||
|
|
ab818ed89f | ||
|
|
1bb6540b64 | ||
|
|
ddd8209c45 | ||
|
|
1dc3dfe58f | ||
|
|
634d262b64 | ||
|
|
d964146f8c | ||
|
|
05b817f5f5 | ||
|
|
eb2ec1833c | ||
|
|
094ddd9333 | ||
|
|
e05d48a574 | ||
|
|
927bfcd37a | ||
|
|
42cabace47 | ||
|
|
9a1f14d05c | ||
|
|
4189aa8f14 | ||
|
|
5fa5bf8142 | ||
|
|
000c1cf306 | ||
|
|
28447d3389 | ||
|
|
ab7f3310c4 | ||
|
|
e5a720c6fa | ||
|
|
8d20dbe81d | ||
|
|
1fc666d82c | ||
|
|
1b2a1a1e77 | ||
|
|
9af3081f45 | ||
|
|
78def6e6bf | ||
|
|
b4269199b4 | ||
|
|
cb289125ab | ||
|
|
a4e3ce31dd | ||
|
|
8a725d3d3e | ||
|
|
c48fa0f078 | ||
|
|
31aa782362 | ||
|
|
892637683c | ||
|
|
1cf6f28fe8 | ||
|
|
29f9394257 | ||
|
|
8fd92df7ae | ||
|
|
374f7abb5d | ||
|
|
3d1e604a6c | ||
|
|
1ca49b9380 | ||
|
|
9bd9cf8052 | ||
|
|
ed934a0206 | ||
|
|
dc891760ec | ||
|
|
97401f843a | ||
|
|
833865cb28 | ||
|
|
02635d506e | ||
|
|
f018e1a075 | ||
|
|
c79cde43ae | ||
|
|
8a812c9d02 | ||
|
|
3df78de3f8 | ||
|
|
bd4831e2ee | ||
|
|
341a34ea9e | ||
|
|
011866a101 | ||
|
|
1486af8770 | ||
|
|
f06e04ffc5 | ||
|
|
d72098bdf8 | ||
|
|
57871e1983 | ||
|
|
21dbb0e820 | ||
|
|
007f377f16 | ||
|
|
da15477aaf | ||
|
|
4f34ad0a8d | ||
|
|
6ed9dccde2 | ||
|
|
ce2b3265be | ||
|
|
14ea69c9c7 | ||
|
|
72e2307c16 | ||
|
|
0dd3334811 | ||
|
|
5485436ea1 | ||
|
|
9e044750ce | ||
|
|
f84cb2f07a | ||
|
|
e0ff6bcc5d | ||
|
|
1695f6800c | ||
|
|
810f149501 | ||
|
|
05d51d8693 | ||
|
|
ac4c819f57 | ||
|
|
e7cc654bb4 | ||
|
|
bf37e3f77f | ||
|
|
af9f7bb0d2 | ||
|
|
0b7abf5690 | ||
|
|
490ae248f8 | ||
|
|
e552bcb0c6 | ||
|
|
2c27316c4a | ||
|
|
c49dd12855 | ||
|
|
544f97a3fe | ||
|
|
6a909e0700 | ||
|
|
40a6ad9a1f | ||
|
|
039f2b49e8 | ||
|
|
5ae2406a03 | ||
|
|
2c3235d452 | ||
|
|
d2ea71a403 | ||
|
|
feb97b2313 | ||
|
|
0b662ecb9a | ||
|
|
3d0da77c80 | ||
|
|
9918397190 | ||
|
|
b3e2562c00 | ||
|
|
255cacf912 | ||
|
|
8268f9f612 | ||
|
|
92b8798cad | ||
|
|
fbb4db8a83 | ||
|
|
19a672caf2 | ||
|
|
5a85c890ba | ||
|
|
55556aaac4 | ||
|
|
31853ee20c | ||
|
|
037bfeff82 | ||
|
|
bf1c919547 | ||
|
|
202ec17a08 | ||
|
|
98ede457a0 | ||
|
|
3c772c3576 | ||
|
|
5b1eaa741c | ||
|
|
1a1aa2d6aa | ||
|
|
6e9aecdff3 | ||
|
|
95187632bf | ||
|
|
e3e28a023c | ||
|
|
65bb71779a | ||
|
|
29dd766a67 | ||
|
|
851145e69e | ||
|
|
b2e300c440 | ||
|
|
38998a4869 | ||
|
|
21f1fd55f8 | ||
|
|
c6c6a18375 | ||
|
|
56063b283e | ||
|
|
d27e7ead49 | ||
|
|
342164ede1 | ||
|
|
1061e40b98 | ||
|
|
377115a594 | ||
|
|
e405b68da1 | ||
|
|
d875d7baed | ||
|
|
9c48e4965c | ||
|
|
8675659fcb | ||
|
|
3b226618ad | ||
|
|
6879a492dc | ||
|
|
4070255dde | ||
|
|
dd9e484336 | ||
|
|
78cda7aa74 | ||
|
|
9ced447fa3 | ||
|
|
8e665ffedc | ||
|
|
99ea6c08a7 | ||
|
|
b0bfa2b2fc | ||
|
|
3797294838 | ||
|
|
3d6995d1e6 | ||
|
|
e91eb27e56 | ||
|
|
3a4b614ae0 | ||
|
|
26a6d6bef9 | ||
|
|
dca81589a7 | ||
|
|
5b7c697dfe | ||
|
|
18d6b8a0ad | ||
|
|
cdee601137 | ||
|
|
e74c08ff39 | ||
|
|
5bfe2fb8d3 | ||
|
|
55fc5c4358 | ||
|
|
d6a603d536 | ||
|
|
5c85d915cc | ||
|
|
57f8519a52 | ||
|
|
056be45dd5 | ||
|
|
3ee22e545e | ||
|
|
db5aa36f44 | ||
|
|
cc767061cc | ||
|
|
1d22b75090 | ||
|
|
4313531432 | ||
|
|
1faddeb402 | ||
|
|
2b035739f8 | ||
|
|
aeed20087e | ||
|
|
a239f41495 | ||
|
|
b36d6967a5 | ||
|
|
7c1d13b7db | ||
|
|
3e99949db8 | ||
|
|
aa12a65a8f | ||
|
|
18804efb7e | ||
|
|
a5d27196ea | ||
|
|
5af6d88ffb | ||
|
|
472a3bc69f | ||
|
|
f0f1c911ea | ||
|
|
fcb0033d25 | ||
|
|
c8b1e76783 | ||
|
|
b50f621e61 | ||
|
|
77c4f5fecc | ||
|
|
e27ea3337a | ||
|
|
23fbf6f157 | ||
|
|
321e5081ed | ||
|
|
33cac29f87 | ||
|
|
12be438dea | ||
|
|
00092db6ad | ||
|
|
619c3a0025 | ||
|
|
f12697c897 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,6 +9,7 @@ logo/*.iconset
|
|||||||
compile_commands.json
|
compile_commands.json
|
||||||
glad/out
|
glad/out
|
||||||
asan-launcher
|
asan-launcher
|
||||||
|
asan-launcher.dSYM
|
||||||
kitty-profile
|
kitty-profile
|
||||||
dev
|
dev
|
||||||
__pycache__
|
__pycache__
|
||||||
|
|||||||
26
.travis.yml
26
.travis.yml
@@ -4,7 +4,7 @@ matrix:
|
|||||||
fast_finish: true
|
fast_finish: true
|
||||||
include:
|
include:
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: xenial
|
||||||
group: beta
|
group: beta
|
||||||
language: python
|
language: python
|
||||||
sudo: false
|
sudo: false
|
||||||
@@ -12,14 +12,16 @@ matrix:
|
|||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
packages:
|
packages:
|
||||||
|
- libgl1-mesa-dev
|
||||||
- libxi-dev
|
- libxi-dev
|
||||||
- libxrandr-dev
|
- libxrandr-dev
|
||||||
- libxinerama-dev
|
- libxinerama-dev
|
||||||
- libxcursor-dev
|
- libxcursor-dev
|
||||||
- libxcb-xkb-dev
|
- libxcb-xkb-dev
|
||||||
|
- libdbus-1-dev
|
||||||
|
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: xenial
|
||||||
group: beta
|
group: beta
|
||||||
sudo: false
|
sudo: false
|
||||||
env:
|
env:
|
||||||
@@ -29,6 +31,7 @@ matrix:
|
|||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
packages:
|
packages:
|
||||||
|
- libgl1-mesa-dev
|
||||||
- libfontconfig1-dev
|
- libfontconfig1-dev
|
||||||
- libharfbuzz-dev
|
- libharfbuzz-dev
|
||||||
- libxi-dev
|
- libxi-dev
|
||||||
@@ -37,10 +40,11 @@ matrix:
|
|||||||
- libxcursor-dev
|
- libxcursor-dev
|
||||||
- libxcb-xkb-dev
|
- libxcb-xkb-dev
|
||||||
- libpng-dev
|
- libpng-dev
|
||||||
|
- libdbus-1-dev
|
||||||
- python3-pil
|
- python3-pil
|
||||||
|
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: xenial
|
||||||
group: beta
|
group: beta
|
||||||
sudo: false
|
sudo: false
|
||||||
env:
|
env:
|
||||||
@@ -50,6 +54,7 @@ matrix:
|
|||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
packages:
|
packages:
|
||||||
|
- libgl1-mesa-dev
|
||||||
- libfontconfig1-dev
|
- libfontconfig1-dev
|
||||||
- libharfbuzz-dev
|
- libharfbuzz-dev
|
||||||
- libxi-dev
|
- libxi-dev
|
||||||
@@ -58,10 +63,11 @@ matrix:
|
|||||||
- libxcursor-dev
|
- libxcursor-dev
|
||||||
- libxcb-xkb-dev
|
- libxcb-xkb-dev
|
||||||
- libpng-dev
|
- libpng-dev
|
||||||
|
- libdbus-1-dev
|
||||||
- python3-pil
|
- python3-pil
|
||||||
|
|
||||||
- os: linux
|
- os: linux
|
||||||
dist: trusty
|
dist: xenial
|
||||||
group: beta
|
group: beta
|
||||||
sudo: false
|
sudo: false
|
||||||
env:
|
env:
|
||||||
@@ -71,6 +77,7 @@ matrix:
|
|||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
packages:
|
packages:
|
||||||
|
- libgl1-mesa-dev
|
||||||
- libfontconfig1-dev
|
- libfontconfig1-dev
|
||||||
- libharfbuzz-dev
|
- libharfbuzz-dev
|
||||||
- libxi-dev
|
- libxi-dev
|
||||||
@@ -79,6 +86,7 @@ matrix:
|
|||||||
- libxcursor-dev
|
- libxcursor-dev
|
||||||
- libxcb-xkb-dev
|
- libxcb-xkb-dev
|
||||||
- libpng-dev
|
- libpng-dev
|
||||||
|
- libdbus-1-dev
|
||||||
- python3-pil
|
- python3-pil
|
||||||
- clang
|
- clang
|
||||||
- os: osx
|
- os: osx
|
||||||
@@ -87,6 +95,10 @@ matrix:
|
|||||||
|
|
||||||
- os: osx
|
- os: osx
|
||||||
language: generic
|
language: generic
|
||||||
|
addons:
|
||||||
|
homebrew:
|
||||||
|
update: true
|
||||||
|
brewfile: true
|
||||||
env: USE_BREW=1 BUILD_PKG=osx-bundle
|
env: USE_BREW=1 BUILD_PKG=osx-bundle
|
||||||
|
|
||||||
env:
|
env:
|
||||||
@@ -101,12 +113,6 @@ install: |
|
|||||||
if [[ "$RUN_FLAKE" == "1" ]]; then pip install flake8; fi
|
if [[ "$RUN_FLAKE" == "1" ]]; then pip install flake8; fi
|
||||||
if [[ "$TRAVIS_OS_NAME" == 'osx' ]]; then
|
if [[ "$TRAVIS_OS_NAME" == 'osx' ]]; then
|
||||||
if [[ "$USE_BREW" == "1" ]]; then
|
if [[ "$USE_BREW" == "1" ]]; then
|
||||||
brew update;
|
|
||||||
brew upgrade python;
|
|
||||||
brew install harfbuzz --without-graphite2 --without-icu4c --without-freetype;
|
|
||||||
brew install imagemagick
|
|
||||||
brew install optipng
|
|
||||||
brew install librsvg
|
|
||||||
logo/make.py
|
logo/make.py
|
||||||
else
|
else
|
||||||
mkdir -p $SW;
|
mkdir -p $SW;
|
||||||
|
|||||||
5
Brewfile
Normal file
5
Brewfile
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
brew 'imagemagick'
|
||||||
|
brew 'optipng'
|
||||||
|
brew 'librsvg'
|
||||||
|
brew 'harfbuzz'
|
||||||
|
brew 'python'
|
||||||
@@ -10,9 +10,9 @@ config to reproduce the issue with).
|
|||||||
|
|
||||||
### Contributing code
|
### Contributing code
|
||||||
|
|
||||||
Install [the dependencies](https://github.com/kovidgoyal/kitty#dependencies)
|
Install [the dependencies](https://sw.kovidgoyal.net/kitty/build.html#dependencies)
|
||||||
using your favorite package manager. Build and run kitty [from
|
using your favorite package manager. Build and run kitty [from
|
||||||
source](https://github.com/kovidgoyal/kitty#install-and-run-from-source).
|
source](https://sw.kovidgoyal.net/kitty/build.html#install-and-run-from-source).
|
||||||
|
|
||||||
Make a fork, submit your Pull Request. If it's a large/controversial change, open an issue
|
Make a fork, submit your Pull Request. If it's a large/controversial change, open an issue
|
||||||
beforehand to discuss it, so that you don't waste your time making a pull
|
beforehand to discuss it, so that you don't waste your time making a pull
|
||||||
|
|||||||
19
__main__.py
19
__main__.py
@@ -7,8 +7,9 @@ import os
|
|||||||
|
|
||||||
|
|
||||||
def icat(args):
|
def icat(args):
|
||||||
from kittens.icat.main import main
|
from kittens.runner import run_kitten
|
||||||
main(args)
|
sys.argv = args
|
||||||
|
run_kitten('icat')
|
||||||
|
|
||||||
|
|
||||||
def list_fonts(args):
|
def list_fonts(args):
|
||||||
@@ -33,10 +34,21 @@ def hold(args):
|
|||||||
raise SystemExit(ret)
|
raise SystemExit(ret)
|
||||||
|
|
||||||
|
|
||||||
|
def complete(args):
|
||||||
|
from kitty.complete import main
|
||||||
|
main(args[1:], entry_points, namespaced_entry_points)
|
||||||
|
|
||||||
|
|
||||||
def launch(args):
|
def launch(args):
|
||||||
import runpy
|
import runpy
|
||||||
sys.argv = args[1:]
|
sys.argv = args[1:]
|
||||||
runpy.run_path(args[1], run_name='__main__')
|
exe = args[1]
|
||||||
|
if exe.startswith(':'):
|
||||||
|
import shutil
|
||||||
|
exe = shutil.which(exe[1:])
|
||||||
|
if not exe:
|
||||||
|
raise SystemExit('{} not found in PATH'.format(args[1][1:]))
|
||||||
|
runpy.run_path(exe, run_name='__main__')
|
||||||
|
|
||||||
|
|
||||||
def run_kitten(args):
|
def run_kitten(args):
|
||||||
@@ -69,6 +81,7 @@ 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
|
||||||
|
|
||||||
|
|
||||||
def setup_openssl_environment():
|
def setup_openssl_environment():
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
# You can set these variables from the command line.
|
# You can set these variables from the command line.
|
||||||
SPHINXOPTS = -T $(FAIL_WARN)
|
SPHINXOPTS = -T $(FAIL_WARN)
|
||||||
SPHINXBUILD = python3 .. +launch $(shell which sphinx-build)
|
SPHINXBUILD = python3 .. +launch :sphinx-build
|
||||||
SPHINXPROJ = kitty
|
SPHINXPROJ = kitty
|
||||||
SOURCEDIR = .
|
SOURCEDIR = .
|
||||||
BUILDDIR = _build
|
BUILDDIR = _build
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ the following simple command:
|
|||||||
|
|
||||||
The binaries will be installed in the standard location for your OS,
|
The binaries will be installed in the standard location for your OS,
|
||||||
:file:`/Applications/kitty.app` on macOS and :file:`~/.local/kitty.app` on
|
:file:`/Applications/kitty.app` on macOS and :file:`~/.local/kitty.app` on
|
||||||
Linux. The installer only touches files in that directory.
|
Linux. The installer only touches files in that directory. To update kitty,
|
||||||
|
simply re-run the command.
|
||||||
|
|
||||||
|
|
||||||
Manually installing
|
Manually installing
|
||||||
@@ -29,6 +30,25 @@ the :file:`.dmg` and install as normal. If you are on Linux, download the tarbal
|
|||||||
and extract it into a directory. The |kitty| executable will be in the
|
and extract it into a directory. The |kitty| executable will be in the
|
||||||
:file:`bin` sub-directory.
|
:file:`bin` sub-directory.
|
||||||
|
|
||||||
|
Desktop integration on Linux
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
If you want the kitty icon to appear in the taskbar and an entry for it to be
|
||||||
|
present in the menus, you will need to install the :file:`kitty.desktop` file.
|
||||||
|
The details of the following procedure may need to be adjusted for your
|
||||||
|
particular desktop, but it should work for most major desktop environments.
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
# Create a symbolic link to add kitty to PATH (assuming ~/.local/bin is in
|
||||||
|
# your PATH)
|
||||||
|
ln -s ~/.local/kitty.app/bin/kitty ~/.local/bin/
|
||||||
|
# Place the kitty.desktop file somewhere it can be found by the OS
|
||||||
|
cp ~/.local/kitty.app/share/applications/kitty.desktop ~/.local/share/applications
|
||||||
|
# Update the path to the kitty icon in the kitty.desktop file
|
||||||
|
sed -i "s/Icon\=kitty/Icon\=\/home\/$USER\/.local\/kitty.app\/share\/icons\/hicolor\/256x256\/apps\/kitty.png/g" ~/.local/share/applications/kitty.desktop
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Customizing the installation
|
Customizing the installation
|
||||||
--------------------------------
|
--------------------------------
|
||||||
@@ -58,6 +78,12 @@ Customizing the installation
|
|||||||
|ins| \\
|
|ins| \\
|
||||||
installer=/path/to/dmg or tarball
|
installer=/path/to/dmg or tarball
|
||||||
|
|
||||||
|
Uninstalling
|
||||||
|
----------------
|
||||||
|
|
||||||
|
All the installer does is copy the kitty files into the install directory. To
|
||||||
|
uninstall, simply delete that directory.
|
||||||
|
|
||||||
|
|
||||||
Building from source
|
Building from source
|
||||||
------------------------
|
------------------------
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ Build-time dependencies:
|
|||||||
* gcc or clang
|
* gcc or clang
|
||||||
* pkg-config
|
* pkg-config
|
||||||
* For building on Linux in addition to the above dependencies you might also need to install the ``-dev`` packages for:
|
* For building on Linux in addition to the above dependencies you might also need to install the ``-dev`` packages for:
|
||||||
``libxcursor-dev``, ``libxrandr-dev``, ``libxi-dev``, ``libxinerama-dev``, ``libgl1-mesa-dev`` and ``libxkbcommon-x11-dev``,
|
``libdbus-1-dev``, ``libxcursor-dev``, ``libxrandr-dev``, ``libxi-dev``, ``libxinerama-dev``, ``libgl1-mesa-dev``, ``libxkbcommon-x11-dev``, ``libfontconfig-dev`` and ``libpython-dev``.
|
||||||
if they are not already installed by your distro.
|
if they are not already installed by your distro.
|
||||||
|
|
||||||
Install and run from source
|
Install and run from source
|
||||||
@@ -57,6 +57,25 @@ If that works, you can create a script to launch |kitty|:
|
|||||||
And place it in :file:`~/bin` or :file:`/usr/bin` so that you can run |kitty| using
|
And place it in :file:`~/bin` or :file:`/usr/bin` so that you can run |kitty| using
|
||||||
just ``kitty``.
|
just ``kitty``.
|
||||||
|
|
||||||
|
|
||||||
|
Building kitty.app on macOS from source
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
Install `imagemagick`, `optipng` and `librsvg` using `brew` or similar (needed
|
||||||
|
for the logo generation step). And run::
|
||||||
|
|
||||||
|
make app
|
||||||
|
|
||||||
|
This :file:`kitty.app` unlike the released one does not include its own copy of
|
||||||
|
python and the other dependencies. So if you ever un-install/upgrade those dependencies
|
||||||
|
you might have to rebuild the app.
|
||||||
|
|
||||||
|
Note that the released :file:`kitty.dmg` includes all dependencies, unlike the
|
||||||
|
:file:`kitty.app` built above and is built automatically by using the :file:`kitty` branch of
|
||||||
|
`build-calibre <https://github.com/kovidgoyal/build-calibre>`_ however, that
|
||||||
|
is designed to run on Linux and is not for the faint of heart.
|
||||||
|
|
||||||
|
|
||||||
Note for Linux/macOS packagers
|
Note for Linux/macOS packagers
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
||||||
@@ -85,7 +104,7 @@ without needing to install all of |kitty|.
|
|||||||
.. note::
|
.. note::
|
||||||
You need a couple of extra dependencies to build linux-package.
|
You need a couple of extra dependencies to build linux-package.
|
||||||
:file:`tic` to compile terminfo files, usually found in the
|
:file:`tic` to compile terminfo files, usually found in the
|
||||||
development package of :file:`ncurses`. Also, if you are building form
|
development package of :file:`ncurses`. Also, if you are building from
|
||||||
a git checkout instead of the released source code tarball, you will
|
a git checkout instead of the released source code tarball, you will
|
||||||
need :file:`sphinx-build` from the `Sphinx documentation generator
|
need :file:`sphinx-build` from the `Sphinx documentation generator
|
||||||
<http://www.sphinx-doc.org/>`_.
|
<http://www.sphinx-doc.org/>`_.
|
||||||
|
|||||||
@@ -3,6 +3,424 @@ Changelog
|
|||||||
|
|
||||||
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
||||||
|
|
||||||
|
0.13.3 [2019-01-19]
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
- icat kitten: Add a ``--stdin`` option to control if image data is read from
|
||||||
|
STDIN (:iss:`1308`)
|
||||||
|
|
||||||
|
- hints kitten: Start hints numbering at one instead of zero by default. Added
|
||||||
|
an option ``--hints-offset`` to control it. (:iss:`1289`)
|
||||||
|
|
||||||
|
- Fix a regression in the previous release that broke using ``background`` for
|
||||||
|
:opt:`cursor_text_color` (:iss:`1288`)
|
||||||
|
|
||||||
|
- macOS: Fix dragging kitty window tabs in traditional full screen mode causing
|
||||||
|
crashes (:iss:`1296`)
|
||||||
|
|
||||||
|
- macOS: Ensure that when running from a bundle, the bundle kitty exe is
|
||||||
|
preferred over any kitty in PATH (:iss:`1280`)
|
||||||
|
|
||||||
|
- macOS: Fix a regression that broke mapping of :kbd:`ctrl+tab` (:iss:`1304`)
|
||||||
|
|
||||||
|
- Add a list of user-created kittens to the docs
|
||||||
|
|
||||||
|
- Fix a regression that broke changing mouse wheel scroll direction with
|
||||||
|
negative :opt:`wheel_scroll_multiplier` values in full-screen applications
|
||||||
|
like vim (:iss:`1299`)
|
||||||
|
|
||||||
|
- Fix :opt:`background_opacity` not working with pure white backgrounds
|
||||||
|
(:iss:`1285`)
|
||||||
|
|
||||||
|
- macOS: Fix "New OS Window" dock action not working when kitty is not focused
|
||||||
|
(:iss:`1312`)
|
||||||
|
|
||||||
|
- macOS: Add aliases for close window and new tab actions that conform to common
|
||||||
|
Apple shortcuts for these actions (:iss:`1313`)
|
||||||
|
|
||||||
|
- macOS: Fix some kittens causing 100% CPU usage
|
||||||
|
|
||||||
|
|
||||||
|
0.13.2 [2019-01-04]
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
- Add a new option :opt:`tab_title_template` to control how tab titles
|
||||||
|
are formatted. In particular the template can be used to display
|
||||||
|
the tab number next to the title (:iss:`1223`)
|
||||||
|
|
||||||
|
- Report the current foreground processes as well as the original child process,
|
||||||
|
when using `kitty @ ls`
|
||||||
|
|
||||||
|
- Use the current working directory of the foreground process for the
|
||||||
|
`*_with_cwd` actions that open a new window with the current working
|
||||||
|
directory.
|
||||||
|
|
||||||
|
- Add a new ``copy_or_interrupt`` action that can be mapped to kbd:`ctrl+c`. It
|
||||||
|
will copy if there is a selection and interrupt otherwise (:iss:`1286`)
|
||||||
|
|
||||||
|
- Fix setting :opt:`background_opacity` causing window margins/padding to be slightly
|
||||||
|
different shade from background (:iss:`1221`)
|
||||||
|
|
||||||
|
- Handle keyboards with a "+" key (:iss:`1224`)
|
||||||
|
|
||||||
|
- Fix Private use Unicode area characters followed by spaces at the end of text
|
||||||
|
not being rendered correctly (:iss:`1210`)
|
||||||
|
|
||||||
|
- macOS: Add an entry to the dock menu to open a new OS window (:iss:`1242`)
|
||||||
|
|
||||||
|
- macOS: Fix scrolling very slowly with wheel mice not working (:iss:`1238`)
|
||||||
|
|
||||||
|
- Fix changing :opt:`cursor_text_color` via remote control not working
|
||||||
|
(:iss:`1229`)
|
||||||
|
|
||||||
|
- Add an action to resize windows that can be mapped to shortcuts in :file:`kitty.conf`
|
||||||
|
(:pull:`1245`)
|
||||||
|
|
||||||
|
- Fix using the ``new_tab !neighbor`` action changing the order of the
|
||||||
|
non-neighboring tabs (:iss:`1256`)
|
||||||
|
|
||||||
|
- macOS: Fix momentum scrolling continuing when changing the active window/tab
|
||||||
|
(:iss:`1267`)
|
||||||
|
|
||||||
|
|
||||||
|
0.13.1 [2018-12-06]
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
- Fix passing input via the pipe action to a program without a window not
|
||||||
|
working.
|
||||||
|
|
||||||
|
- Linux: Fix a regression in the previous release that caused automatic
|
||||||
|
selection of bold/italic fonts when using aliases such as "monospace" to not
|
||||||
|
work (:iss:`1209`)
|
||||||
|
|
||||||
|
- Fix resizing window smaller and then restoring causing some wrapped lines to not
|
||||||
|
be properly unwrapped (:iss:`1206`)
|
||||||
|
|
||||||
|
0.13.0 [2018-12-05]
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
- Add an option :opt:`scrollback_pager_history_size` to tell kitty to store
|
||||||
|
extended scrollback to use when viewing the scrollback buffer in a pager
|
||||||
|
(:iss:`970`)
|
||||||
|
|
||||||
|
- Modify the kittens sub-system to allow creating custom kittens without any
|
||||||
|
user interface. This is useful for creating more complex actions that can
|
||||||
|
be bound to key presses in :file:`kitty.conf`. See
|
||||||
|
doc:`kittens/custom`. (:iss:`870`)
|
||||||
|
|
||||||
|
- Add a new ``nth_window`` action that can be used to go to the nth window and
|
||||||
|
also previously active windows, using negative numbers. Similarly,
|
||||||
|
``goto_tab`` now accepts negative numbers to go to previously active tabs
|
||||||
|
(:iss:`1040`)
|
||||||
|
|
||||||
|
- Allow hiding the tab bar completely, by setting :opt:`tab_bar_style` to
|
||||||
|
``hidden``. (:iss:`1014`)
|
||||||
|
|
||||||
|
- Allow private use unicode characters to stretch over more than a single
|
||||||
|
neighboring space (:pull:`1036`)
|
||||||
|
|
||||||
|
- Add a new :opt:`touch_scroll_multiplier` option to modify the amount
|
||||||
|
scrolled by high precision scrolling devices such as touchpads (:pull:`1129`)
|
||||||
|
|
||||||
|
- icat kitten: Implement reading image data from STDIN, if STDIN is not
|
||||||
|
connected to a terminal (:iss:`1130`)
|
||||||
|
|
||||||
|
- hints kitten: Insert trailing spaces after matches when using the
|
||||||
|
``--multiple`` option. Also add a separate ``--add-trailing-space``
|
||||||
|
option to control this behavior (:pull:`1132`)
|
||||||
|
|
||||||
|
- Fix the ``*_with_cwd`` actions using the cwd of the overlay window rather
|
||||||
|
than the underlying window's cwd (:iss:`1045`)
|
||||||
|
|
||||||
|
- Fix incorrect key repeat rate on wayland (:pull:`1055`)
|
||||||
|
|
||||||
|
- macOS: Fix drag and drop of files not working on Mojave (:iss:`1058`)
|
||||||
|
|
||||||
|
- macOS: Fix IME input for East Asian languages (:iss:`910`)
|
||||||
|
|
||||||
|
- macOS: Fix rendering frames-per-second very low when processing
|
||||||
|
large amounts of input in small chunks (:pull:`1082`)
|
||||||
|
|
||||||
|
- macOS: Fix incorrect text sizes calculated when using an external display
|
||||||
|
that is set to mirror the main display (:iss:`1056`)
|
||||||
|
|
||||||
|
- macOS: Use the system default double click interval (:pull:`1090`)
|
||||||
|
|
||||||
|
- macOS: Fix touch scrolling sensitivity low on retina screens (:iss:`1112`)
|
||||||
|
|
||||||
|
- Linux: Fix incorrect rendering of some fonts when hinting is disabled at
|
||||||
|
small sizes (:iss:`1173`)
|
||||||
|
|
||||||
|
- Linux: Fix match rules used as aliases in Fontconfig configuration not being
|
||||||
|
respected (:iss:`1085`)
|
||||||
|
|
||||||
|
- Linux: Fix a crash when using the GNU Unifont as a fallback font
|
||||||
|
(:iss:`1087`)
|
||||||
|
|
||||||
|
- Wayland: Fix copying from hidden kitty windows hanging (:iss:`1051`)
|
||||||
|
|
||||||
|
- Wayland: Add support for the primary selection protocol
|
||||||
|
implemented by some compositors (:pull:`1095`)
|
||||||
|
|
||||||
|
- Fix expansion of env vars not working in the :opt:`env` directive
|
||||||
|
(:iss:`1075`)
|
||||||
|
|
||||||
|
- Fix :opt:`mouse_hide_wait` only taking effect after an event such as cursor
|
||||||
|
blink or key press (:iss:`1073`)
|
||||||
|
|
||||||
|
- Fix the ``set_background_opacity`` action not working correctly
|
||||||
|
(:pull:`1147`)
|
||||||
|
|
||||||
|
- Fix second cell of emoji created using variation selectors not having
|
||||||
|
the same attributes as the first cell (:iss:`1109`)
|
||||||
|
|
||||||
|
- Fix focusing neighboring windows in the grid layout with less than 4 windows
|
||||||
|
not working (:iss:`1115`)
|
||||||
|
|
||||||
|
- Fix :kbd:`ctrl+shift+special` key not working in normal and application keyboard
|
||||||
|
modes (:iss:`1114`)
|
||||||
|
|
||||||
|
- Add a terminfo entry for full keyboard mode.
|
||||||
|
|
||||||
|
- Fix incorrect text-antialiasing when using very low background opacity
|
||||||
|
(:iss:`1005`)
|
||||||
|
|
||||||
|
- When double or triple clicking ignore clicks if they are "far" from each
|
||||||
|
other (:iss:`1093`)
|
||||||
|
|
||||||
|
- Follow xterm's behavior for the menu key (:iss:`597`)
|
||||||
|
|
||||||
|
- Fix hover detection of URLs not working when hovering over the first colon
|
||||||
|
and slash characters in short URLs (:iss:`1201`)
|
||||||
|
|
||||||
|
0.12.3 [2018-09-29]
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
- macOS: Fix kitty window not being rendered on macOS Mojave until the window is
|
||||||
|
moved or resized at least once (:iss:`887`)
|
||||||
|
|
||||||
|
- Unicode input: Fix an error when searching for the string 'fir' (:iss:`1035`)
|
||||||
|
|
||||||
|
|
||||||
|
0.12.2 [2018-09-24]
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
- A new ``last_used_layout`` function that can be mapped to a shortcut to
|
||||||
|
switch to the previously used window layout (:iss:`870`)
|
||||||
|
|
||||||
|
- New ``neighboring_window`` and ``move_window`` functions to switch to
|
||||||
|
neighboring windows in the current layout, and move them around, similar to
|
||||||
|
window movement in vim (:iss:`916`)
|
||||||
|
|
||||||
|
- A new ``pipe`` function that can be used to pipe the contents of the screen
|
||||||
|
and scrollback buffer to any desired program running in a new window, tab or
|
||||||
|
overlay window. (:iss:`933`)
|
||||||
|
|
||||||
|
- Add a new :option:`kitty --start-as` command line flag to start kitty
|
||||||
|
full-screen/maximized/minimized. This replaces the ``--start-in-fullscreen``
|
||||||
|
flag introduced in the previous release (:iss:`935`)
|
||||||
|
|
||||||
|
- When mapping the ``new_tab`` action allow specifying that the tab should open
|
||||||
|
next to the current tab instead of at the end of the tabs list (:iss:`979`)
|
||||||
|
|
||||||
|
- macOS: Add a new :opt:`macos_thicken_font` to make text rendering
|
||||||
|
on macs thicker, which makes it similar to the result of
|
||||||
|
sub-pixel antialiasing (:pull:`950`)
|
||||||
|
|
||||||
|
- macOS: Add an option :opt:`macos_traditional_fullscreen` to make
|
||||||
|
full-screening of kitty windows much faster, but less pretty. (:iss:`911`)
|
||||||
|
|
||||||
|
- Fix a bug causing incorrect line ordering when viewing the scrollback buffer
|
||||||
|
if the scrollback buffer is full (:iss:`960`)
|
||||||
|
|
||||||
|
- Fix drag-scrolling not working when the mouse leaves the window confines
|
||||||
|
(:iss:`917`)
|
||||||
|
|
||||||
|
- Workaround for broken editors like nano that cannot handle newlines in pasted text
|
||||||
|
(:iss:`994`)
|
||||||
|
|
||||||
|
- Linux: Ensure that the python embedded in the kitty binary build uses
|
||||||
|
UTF-8 mode to process command-line arguments (:iss:`924`)
|
||||||
|
|
||||||
|
- Linux: Handle fonts that contain monochrome bitmaps (such as the Terminus TTF
|
||||||
|
font) (:pull:`934`)
|
||||||
|
|
||||||
|
- Have the :option:`kitty --title` flag apply to all windows created
|
||||||
|
using :option:`kitty --session` (:iss:`921`)
|
||||||
|
|
||||||
|
- Revert change for backspacing of wide characters in the previous release,
|
||||||
|
as it breaks backspacing in some wide character aware programs (:iss:`875`)
|
||||||
|
|
||||||
|
- Fix kitty @set-colors not working for tab backgrounds when using the `fade` tabbar style
|
||||||
|
(:iss:`937`)
|
||||||
|
|
||||||
|
- macOS: Fix resizing semi-transparent windows causing the windows to be
|
||||||
|
invisible during the resize (:iss:`941`)
|
||||||
|
|
||||||
|
- Linux: Fix window icon not set on X11 for the first OS window (:iss:`961`)
|
||||||
|
|
||||||
|
- macOS: Add an :opt:`macos_custom_beam_cursor` option to use a special
|
||||||
|
mouse cursor image that can be seen on both light and dark backgrounds
|
||||||
|
(:iss:`359`)
|
||||||
|
|
||||||
|
- Remote control: Fix the ``focus_window`` command not focusing the
|
||||||
|
top-level OS window of the specified kitty window (:iss:`1003`)
|
||||||
|
|
||||||
|
- Fix using :opt:`focus_follows_mouse` causing text selection with the
|
||||||
|
mouse to malfunction when using multiple kitty windows (:iss:`1002`)
|
||||||
|
|
||||||
|
0.12.1 [2018-09-08]
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
- Add a new ``--start-in-fullscreen`` command line flag to start
|
||||||
|
kitty in full screen mode (:iss:`856`)
|
||||||
|
|
||||||
|
- macOS: Fix a character that cannot be rendered in any font causing
|
||||||
|
font fallback for all subsequent characters that cannot be rendered in the
|
||||||
|
main font to fail (:iss:`799`)
|
||||||
|
|
||||||
|
- Linux: Do not enable IME input via ibus unless the ``GLFW_IM_MODULE=ibus``
|
||||||
|
environment variable is set. IME causes key processing latency and even
|
||||||
|
missed keystrokes for many people, so it is now off by default.
|
||||||
|
|
||||||
|
- Fix backspacing of wide characters in wide-character unaware programs not working (:iss:`875`)
|
||||||
|
|
||||||
|
- Linux: Fix number pad arrow keys not working when Numlock is off (:iss:`857`)
|
||||||
|
|
||||||
|
- Wayland: Implement support for clipboard copy/paste (:iss:`855`)
|
||||||
|
|
||||||
|
- Allow mapping shortcuts using the raw key code from the OS (:iss:`848`)
|
||||||
|
|
||||||
|
- Allow mapping of individual key-presses without modifiers as shortcuts
|
||||||
|
|
||||||
|
- Fix legacy invocation of icat as `kitty icat` not working (:iss:`850`)
|
||||||
|
|
||||||
|
- Improve rendering of wavy underline at small font sizes (:iss:`853`)
|
||||||
|
|
||||||
|
- Fix a regression in 0.12.0 that broke dynamic resizing of layouts (:iss:`860`)
|
||||||
|
|
||||||
|
- Wayland: Allow using the :option:`kitty --class` command line flag
|
||||||
|
to set the app id (:iss:`862`)
|
||||||
|
|
||||||
|
- Add completion of the kitty command for the fish shell (:pull:`829`)
|
||||||
|
|
||||||
|
- Linux: Fix XCompose rules with no defined symbol not working (:iss:`880`)
|
||||||
|
|
||||||
|
- Linux: Fix crash with some Nvidia drivers when creating tabs in the first
|
||||||
|
top level-window after creating a second top-level window. (:iss:`873`)
|
||||||
|
|
||||||
|
- macOS: Diff kitten: Fix syntax highlighting not working because of
|
||||||
|
a bug in the 0.12.0 macOS package
|
||||||
|
|
||||||
|
0.12.0 [2018-09-01]
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
- Preserve the mouse selection even when the contents of the screen are
|
||||||
|
scrolled or overwritten provided the new text does not intersect the
|
||||||
|
selected lines.
|
||||||
|
|
||||||
|
- Linux: Implement support for Input Method Extensions (multilingual input
|
||||||
|
using standard keyboards) via `IBus
|
||||||
|
<https://github.com/ibus/ibus/wiki/ReadMe>`_ (:iss:`469`)
|
||||||
|
|
||||||
|
- Implement completion for the kitty command in bash and zsh. See
|
||||||
|
:ref:`completion`.
|
||||||
|
|
||||||
|
- Render the text under the cursor in a fixed color, configurable via
|
||||||
|
the option :opt:`cursor_text_color` (:iss:`126`)
|
||||||
|
|
||||||
|
- Add an option :opt:`env` to set environment variables in child processes
|
||||||
|
from kitty.conf
|
||||||
|
|
||||||
|
- Add an action to the ``clear_terminal`` function to scroll the screen
|
||||||
|
contents into the scrollback buffer (:iss:`1113`)
|
||||||
|
|
||||||
|
- Implement high precision scrolling with the trackpad on platforms such as
|
||||||
|
macOS and Wayland that implement it. (:pull:`819`)
|
||||||
|
|
||||||
|
- macOS: Allow scrolling window contents using mouse wheel/trackpad even when the
|
||||||
|
window is not the active window (:iss:`729`)
|
||||||
|
|
||||||
|
- Remote control: Allow changing the current window layout with a new
|
||||||
|
:ref:`at_goto-layout` command (:iss:`845`)
|
||||||
|
|
||||||
|
- Remote control: Allow matching windows by the environment variables of their
|
||||||
|
child process as well
|
||||||
|
|
||||||
|
- Allow running kittens via the remote control system (:iss:`738`)
|
||||||
|
|
||||||
|
- Allow enabling remote control in only some kitty windows
|
||||||
|
|
||||||
|
- Add a keyboard shortcut to reset the terminal (:sc:`reset_terminal`). It
|
||||||
|
takes parameters so you can define your own shortcuts to clear the
|
||||||
|
screen/scrollback also (:iss:`747`)
|
||||||
|
|
||||||
|
- Fix one-pixel line appearing at window edges at some window sizes when
|
||||||
|
displaying images with background opacity enabled (:iss:`741`)
|
||||||
|
|
||||||
|
- diff kitten: Fix error when right hand side file is binary and left hand side
|
||||||
|
file is text (:pull:`752`)
|
||||||
|
|
||||||
|
- kitty @ new-window: Add a new option :option:`kitty @ new-window --window-type`
|
||||||
|
to create top-level OS windows (:iss:`770`)
|
||||||
|
|
||||||
|
- macOS: The :opt:`focus_follows_mouse` option now also works across top-level kitty OS windows
|
||||||
|
(:iss:`754`)
|
||||||
|
|
||||||
|
- Fix detection of URLs in HTML source code (URLs inside quotes) (:iss:`785`)
|
||||||
|
|
||||||
|
- Implement support for emoji skin tone modifiers (:iss:`787`)
|
||||||
|
|
||||||
|
- Round-trip the zwj unicode character. Rendering of sequences containing zwj
|
||||||
|
is still not implemented, since it can cause the collapse of an unbounded
|
||||||
|
number of characters into a single cell. However, kitty at least preserves
|
||||||
|
the zwj by storing it as a combining character.
|
||||||
|
|
||||||
|
- macOS: Disable the custom mouse cursor. Using a custom cursor fails on dual
|
||||||
|
GPU machines. I give up, Apple users will just have to live with the
|
||||||
|
limitations of their choice of OS. (:iss:`794`)
|
||||||
|
|
||||||
|
- macOS: Fix control+tab key combination not working (:iss:`801`)
|
||||||
|
|
||||||
|
- Linux: Fix slow startup on some systems caused by GLFW searching for
|
||||||
|
joysticks. Since kitty does not use joysticks, disable joystick support.
|
||||||
|
(:iss:`830`)
|
||||||
|
|
||||||
|
|
||||||
|
0.11.3 [2018-07-10]
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
- Draw only the minimum borders needed for inactive windows. That is only the borders
|
||||||
|
that separate the inactive window from a neighbor. Note that setting
|
||||||
|
a non-zero window margin overrides this and causes all borders to be drawn.
|
||||||
|
The old behavior of drawing all borders can be restored via the
|
||||||
|
:opt:`draw_minimal_borders` setting in kitty.conf. (:iss:`699`)
|
||||||
|
|
||||||
|
- macOS: Add an option :opt:`macos_window_resizable` to control if kitty
|
||||||
|
top-level windows are resizable using the mouse or not (:iss:`698`)
|
||||||
|
|
||||||
|
- macOS: Use a custom mouse cursor that shows up well on both light and dark backgrounds
|
||||||
|
(:iss:`359`)
|
||||||
|
|
||||||
|
- macOS: Workaround for switching from fullscreen to windowed mode with the
|
||||||
|
titlebar hidden causing window resizing to not work. (:iss:`711`)
|
||||||
|
|
||||||
|
- Fix triple-click to select line not working when the entire line is filled
|
||||||
|
(:iss:`703`)
|
||||||
|
|
||||||
|
- When dragging to select with the mouse "grab" the mouse so that if it strays
|
||||||
|
into neighboring windows, the selection is still updated (:pull:`624`)
|
||||||
|
|
||||||
|
- When clicking in the margin/border area of a window, map the click to the
|
||||||
|
nearest cell in the window. Avoids selection with the mouse failing when
|
||||||
|
starting the selection just outside the window.
|
||||||
|
|
||||||
|
- When drag-scrolling stop the scroll when the mouse button is released.
|
||||||
|
|
||||||
|
- Fix a regression in the previous release that caused pasting large amounts
|
||||||
|
of text to be duplicated (:iss:`709`)
|
||||||
|
|
||||||
|
|
||||||
0.11.2 [2018-07-01]
|
0.11.2 [2018-07-01]
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|
||||||
@@ -126,7 +544,7 @@ Changelog
|
|||||||
|
|
||||||
- Add a config option (:opt:`editor`) to set the EDITOR kitty uses (:iss:`580`)
|
- Add a config option (:opt:`editor`) to set the EDITOR kitty uses (:iss:`580`)
|
||||||
|
|
||||||
- Add a config option (:opt:`x11_hide_window_decorations`) to hide window
|
- Add a config option (``x11_hide_window_decorations``) to hide window
|
||||||
decorations under X11/Wayland (:iss:`607`)
|
decorations under X11/Wayland (:iss:`607`)
|
||||||
|
|
||||||
- Add an option to @set-window-title to make the title change non-permanent
|
- Add an option to @set-window-title to make the title change non-permanent
|
||||||
@@ -261,7 +679,7 @@ Changelog
|
|||||||
|
|
||||||
- When triple-clicking select all wrapped lines belonging to a single logical line.
|
- When triple-clicking select all wrapped lines belonging to a single logical line.
|
||||||
|
|
||||||
- hints kitten: Detect bracketed URLs and dont include the closing bracket in the URL.
|
- hints kitten: Detect bracketed URLs and don't include the closing bracket in the URL.
|
||||||
|
|
||||||
- When calling pass_selection_to_program use the current directory of the child
|
- When calling pass_selection_to_program use the current directory of the child
|
||||||
process as the cwd of the program.
|
process as the cwd of the program.
|
||||||
|
|||||||
63
docs/faq.rst
63
docs/faq.rst
@@ -6,7 +6,7 @@ Frequently Asked Questions
|
|||||||
.. contents::
|
.. contents::
|
||||||
|
|
||||||
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
|
||||||
@@ -25,24 +25,6 @@ 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.
|
||||||
|
|
||||||
|
|
||||||
How do I build kitty.app on macOS?
|
|
||||||
----------------------------------------
|
|
||||||
|
|
||||||
Install `imagemagick`, `optipng` and `librsvg` using `brew` or similar (needed
|
|
||||||
for the logo generation step). And run::
|
|
||||||
|
|
||||||
make app
|
|
||||||
|
|
||||||
This :file:`kitty.app` unlike the released one does not include its own copy of
|
|
||||||
python and the other dependencies. So if you ever un-install/upgrade those dependencies
|
|
||||||
you might have to rebuild the app.
|
|
||||||
|
|
||||||
Note that the released :file:`kitty.dmg` includes all dependencies, unlike the
|
|
||||||
:file:`kitty.app` built above and is built automatically by using the :file:`kitty` branch of
|
|
||||||
`build-calibre <https://github.com/kovidgoyal/build-calibre>`_ however, that
|
|
||||||
is designed to run on Linux and is not for the faint of heart.
|
|
||||||
|
|
||||||
|
|
||||||
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?
|
||||||
-----------------------------------------------------------------------
|
-----------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -66,15 +48,43 @@ terminfo files to the server::
|
|||||||
kitty +kitten ssh myserver
|
kitty +kitten ssh myserver
|
||||||
|
|
||||||
If for some reason that does not work (typically because the server is using a
|
If for some reason that does not work (typically because the server is using a
|
||||||
a non POSIX compliant shell), you can use the following one-liner instead (it
|
non POSIX compliant shell), you can use the following one-liner instead (it
|
||||||
is slower as it needs to ssh into the server twice, but will work with most
|
is slower as it needs to ssh into the server twice, but will work with most
|
||||||
servers)::
|
servers)::
|
||||||
|
|
||||||
infocmp xterm-kitty | ssh myserver tic -x -o \~/.terminfo /dev/stdin
|
infocmp xterm-kitty | ssh myserver tic -x -o \~/.terminfo /dev/stdin
|
||||||
|
|
||||||
|
If you are behind a proxy (like Balabit) that prevents this, you must redirect the
|
||||||
|
1st command to a file, copy that to the server and run ``tic`` manually. If you
|
||||||
|
connect to a server, embedded or Android system that doesn't have ``tic``, copy over
|
||||||
|
your local file terminfo to the other system as :file:`~/.terminfo/x/xterm-kitty`.
|
||||||
|
|
||||||
Really, the correct solution for this is to convince the OpenSSH maintainers to
|
Really, the correct solution for this is to convince the OpenSSH maintainers to
|
||||||
have ssh do this automatically when connecting to a server, so that all
|
have ssh do this automatically, if possible, when connecting to a server, so that
|
||||||
terminals work transparently.
|
all terminals work transparently.
|
||||||
|
|
||||||
|
|
||||||
|
Keys such as arrow keys, backspace, delete, home/end, etc. do not work when using su or sudo?
|
||||||
|
-------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Make sure the TERM environment variable, is ``xterm-kitty``. And either the
|
||||||
|
TERMINFO environment variable points to a directory containing :file:`x/xterm-kitty`
|
||||||
|
or that file is under :file:`~/.terminfo/x/`.
|
||||||
|
|
||||||
|
Note that ``sudo`` might remove TERMINFO. Then setting it at the shell prompt can
|
||||||
|
be too late, because command line editing may not be reinitialized. In that case
|
||||||
|
you can either ask ``sudo`` to set it or if that is not supported, insert an ``env``
|
||||||
|
command before starting the shell, or, if not possible, after sudo start another
|
||||||
|
Shell providing the right terminfo path::
|
||||||
|
|
||||||
|
sudo … TERMINFO=$HOME/.terminfo bash -i
|
||||||
|
sudo … env TERMINFO=$HOME/.terminfo bash -i
|
||||||
|
TERMINFO=/home/ORIGINALUSER/.terminfo exec bash -i
|
||||||
|
|
||||||
|
If you have double width characters in your prompt, you may also need to
|
||||||
|
explicitly set a UTF-8 locale, like::
|
||||||
|
|
||||||
|
export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
|
||||||
|
|
||||||
|
|
||||||
How do I change the colors in a running kitty instance?
|
How do I change the colors in a running kitty instance?
|
||||||
@@ -113,3 +123,12 @@ your font is not listed in ``kitty list-fonts`` it means that it is not
|
|||||||
monospace. On Linux you can list all monospace fonts with::
|
monospace. On Linux you can list all monospace fonts with::
|
||||||
|
|
||||||
fc-list : family spacing | grep spacing=100
|
fc-list : family spacing | grep spacing=100
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
manager/desktop environment. For ways to do it with kitty (or indeed any
|
||||||
|
terminal) in different environments,
|
||||||
|
see `here <https://github.com/kovidgoyal/kitty/issues/45>`_.
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ For some discussion regarding the design choices, see `#33
|
|||||||
|
|
||||||
To see a quick demo, inside a |kitty| terminal run::
|
To see a quick demo, inside a |kitty| terminal run::
|
||||||
|
|
||||||
kitty icat path/to/some/image.png
|
kitty +kitten icat path/to/some/image.png
|
||||||
|
|
||||||
You can also see a screenshot with more sophisticated features such as
|
You can also see a screenshot with more sophisticated features such as
|
||||||
alpha-blending and text over graphics.
|
alpha-blending and text over graphics.
|
||||||
@@ -75,6 +75,47 @@ You can also use the *CSI t* escape code to get the screen size. Send
|
|||||||
``<ESC>[4;<height>;<width>t`` where *height* and *width* are the window size in
|
``<ESC>[4;<height>;<width>t`` where *height* and *width* are the window size in
|
||||||
pixels. This escape code is supported in many terminals, not just kitty.
|
pixels. This escape code is supported in many terminals, not just kitty.
|
||||||
|
|
||||||
|
A minimal example
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Some minimal python code to display PNG images in kitty, using the most basic
|
||||||
|
features of the graphics protocol:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from base64 import standard_b64encode
|
||||||
|
|
||||||
|
def serialize_gr_command(cmd, payload=None):
|
||||||
|
cmd = ','.join('{}={}'.format(k, v) for k, v in cmd.items())
|
||||||
|
ans = []
|
||||||
|
w = ans.append
|
||||||
|
w(b'\033_G'), w(cmd.encode('ascii'))
|
||||||
|
if payload:
|
||||||
|
w(b';')
|
||||||
|
w(payload)
|
||||||
|
w(b'\033\\')
|
||||||
|
return b''.join(ans)
|
||||||
|
|
||||||
|
def write_chunked(cmd, data):
|
||||||
|
data = standard_b64encode(data)
|
||||||
|
while data:
|
||||||
|
chunk, data = data[:4096], data[4096:]
|
||||||
|
m = 1 if data else 0
|
||||||
|
cmd['m'] = m
|
||||||
|
sys.stdout.buffer.write(serialize_gr_command(cmd, chunk))
|
||||||
|
sys.stdout.flush()
|
||||||
|
cmd.clear()
|
||||||
|
|
||||||
|
write_chunked({'a': 'T', 'f': 100}, open(sys.argv[-1], 'rb').read())
|
||||||
|
|
||||||
|
|
||||||
|
Save this script as :file:`png.py`, then you can use it to display any PNG
|
||||||
|
file in kitty as::
|
||||||
|
|
||||||
|
python png.py file.png
|
||||||
|
|
||||||
|
|
||||||
The graphics escape code
|
The graphics escape code
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
@@ -160,7 +201,11 @@ Value of `t` Meaning
|
|||||||
================== ============
|
================== ============
|
||||||
``d`` Direct (the data is transmitted within the escape code itself)
|
``d`` Direct (the data is transmitted within the escape code itself)
|
||||||
``f`` A simple file
|
``f`` A simple file
|
||||||
``t`` A temporary file, the terminal emulator will delete the file after reading the pixel data
|
``t`` A temporary file, the terminal emulator will delete the file after reading the pixel data. For security reasons
|
||||||
|
the terminal emulator should only delete the file if it
|
||||||
|
is in a known temporary directory, such as :file:`/tmp`,
|
||||||
|
:file:`/dev/shm`, :file:`TMPDIR env var if present` and any platform
|
||||||
|
specific temporary directories.
|
||||||
``s`` A `POSIX shared memory object <http://man7.org/linux/man-pages/man7/shm_overview.7.html>`_.
|
``s`` A `POSIX shared memory object <http://man7.org/linux/man-pages/man7/shm_overview.7.html>`_.
|
||||||
The terminal emulator will delete it after reading the pixel data
|
The terminal emulator will delete it after reading the pixel data
|
||||||
================== ============
|
================== ============
|
||||||
@@ -372,9 +417,9 @@ Key Value Default Description
|
|||||||
**Keys for deleting images**
|
**Keys for deleting images**
|
||||||
-----------------------------------------------------------
|
-----------------------------------------------------------
|
||||||
``d`` Single character. ``a`` What to delete.
|
``d`` Single character. ``a`` What to delete.
|
||||||
``(a, A, c, C, p,
|
``(a, A, c, C, i,
|
||||||
P, q, Q, x, X, y,
|
I, p, P, q, Q, x, X,
|
||||||
Y, z, Z)``.
|
y, Y, z, Z)``.
|
||||||
======= ==================== ========= =================
|
======= ==================== ========= =================
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
118
docs/index.rst
118
docs/index.rst
@@ -51,7 +51,8 @@ Quickstart
|
|||||||
--------------
|
--------------
|
||||||
|
|
||||||
Pre-built binaries of |kitty| are available for both macOS and Linux.
|
Pre-built binaries of |kitty| are available for both macOS and Linux.
|
||||||
See the :doc:`binary install instructions </binary>`.
|
See the :doc:`binary install instructions </binary>`. You can also
|
||||||
|
:doc:`build from source </build>`.
|
||||||
|
|
||||||
If you are on Linux, you can also use your distribution's |kitty| package.
|
If you are on Linux, you can also use your distribution's |kitty| package.
|
||||||
|kitty| packages are available for:
|
|kitty| packages are available for:
|
||||||
@@ -60,7 +61,8 @@ If you are on Linux, you can also use your distribution's |kitty| package.
|
|||||||
`Arch Linux <https://www.archlinux.org/packages/community/x86_64/kitty/>`_,
|
`Arch Linux <https://www.archlinux.org/packages/community/x86_64/kitty/>`_,
|
||||||
`NixOS <https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/misc/kitty/default.nix>`_,
|
`NixOS <https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/misc/kitty/default.nix>`_,
|
||||||
`Gentoo <https://packages.gentoo.org/packages/x11-terms/kitty>`_,
|
`Gentoo <https://packages.gentoo.org/packages/x11-terms/kitty>`_,
|
||||||
`Fedora <https://copr.fedorainfracloud.org/coprs/oleastre/kitty-terminal/>`_.
|
`Fedora <https://copr.fedorainfracloud.org/coprs/gagbo/kitty-latest/>`_,
|
||||||
|
`Void Linux <https://github.com/void-linux/void-packages/blob/master/srcpkgs/kitty/template>`_.
|
||||||
|
|
||||||
See :doc:`Configuring kitty <conf>` for help on configuring |kitty| and
|
See :doc:`Configuring kitty <conf>` for help on configuring |kitty| and
|
||||||
:doc:`Invocation <invocation>` for the command line arguments |kitty| supports.
|
:doc:`Invocation <invocation>` for the command line arguments |kitty| supports.
|
||||||
@@ -120,10 +122,10 @@ Tabs
|
|||||||
======================== =======================
|
======================== =======================
|
||||||
Action Shortcut
|
Action Shortcut
|
||||||
======================== =======================
|
======================== =======================
|
||||||
New tab :sc:`new_tab`
|
New tab :sc:`new_tab` (also :kbd:`⌘+t` on macOS)
|
||||||
Close tab :sc:`close_tab`
|
Close tab :sc:`close_tab`
|
||||||
Next tab :sc:`next_tab`
|
Next tab :sc:`next_tab` (also :kbd:`control+tab` on macOS)
|
||||||
Previous tab :sc:`previous_tab`
|
Previous tab :sc:`previous_tab` (also :kbd:`control+shift+tab` on macOS)
|
||||||
Next layout :sc:`next_layout`
|
Next layout :sc:`next_layout`
|
||||||
Move tab forward :sc:`move_tab_forward`
|
Move tab forward :sc:`move_tab_forward`
|
||||||
Move tab backward :sc:`move_tab_backward`
|
Move tab backward :sc:`move_tab_backward`
|
||||||
@@ -139,7 +141,7 @@ Action Shortcut
|
|||||||
======================== =======================
|
======================== =======================
|
||||||
New window :sc:`new_window`
|
New window :sc:`new_window`
|
||||||
New OS window :sc:`new_os_window` (also :kbd:`⌘+n` on macOS)
|
New OS window :sc:`new_os_window` (also :kbd:`⌘+n` on macOS)
|
||||||
Close window :sc:`close_window`
|
Close window :sc:`close_window` (also :kbd:`⌘+w` on macOS)
|
||||||
Next window :sc:`next_window`
|
Next window :sc:`next_window`
|
||||||
Previous window :sc:`previous_window`
|
Previous window :sc:`previous_window`
|
||||||
Move window forward :sc:`move_window_forward`
|
Move window forward :sc:`move_window_forward`
|
||||||
@@ -149,6 +151,22 @@ Focus specific window :sc:`first_window`, :sc:`second_window` ... :sc:`ten
|
|||||||
(clockwise from the top-left)
|
(clockwise from the top-left)
|
||||||
======================== =======================
|
======================== =======================
|
||||||
|
|
||||||
|
Additionally, you can define shortcuts in :file:`kitty.conf` to focus neighboring
|
||||||
|
windows and move windows around (similar to window movement in vim)::
|
||||||
|
|
||||||
|
map ctrl+left neighboring_window left
|
||||||
|
map shift+left move_window right
|
||||||
|
map ctrl+down neighboring_window down
|
||||||
|
map shift+down move_window up
|
||||||
|
...
|
||||||
|
|
||||||
|
You can also define a shortcut to switch to the previously active window::
|
||||||
|
|
||||||
|
map ctrl+p nth_window -1
|
||||||
|
|
||||||
|
``nth_window`` will focus the nth window for positive numbers and the
|
||||||
|
previously active windows for negative numbers.
|
||||||
|
|
||||||
|
|
||||||
Other keyboard shortcuts
|
Other keyboard shortcuts
|
||||||
----------------------------------
|
----------------------------------
|
||||||
@@ -162,9 +180,11 @@ Paste from selection :sc:`paste_from_selection`
|
|||||||
Increase font size :sc:`increase_font_size`
|
Increase font size :sc:`increase_font_size`
|
||||||
Decrease font size :sc:`decrease_font_size`
|
Decrease font size :sc:`decrease_font_size`
|
||||||
Restore font size :sc:`reset_font_size`
|
Restore font size :sc:`reset_font_size`
|
||||||
Toggle fullscreen :sc:`toggle_fullscreen`
|
Toggle fullscreen :sc:`toggle_fullscreen` (also :kbd:`^⌘+f` on macOS)
|
||||||
|
on
|
||||||
Input unicode character :sc:`input_unicode_character`
|
Input unicode character :sc:`input_unicode_character`
|
||||||
Click URL using the keyboard :sc:`open_url`
|
Click URL using the keyboard :sc:`open_url`
|
||||||
|
Reset the terminal :sc:`reset_terminal`
|
||||||
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`
|
||||||
Open a |kitty| shell :sc:`kitty_shell`
|
Open a |kitty| shell :sc:`kitty_shell`
|
||||||
@@ -180,6 +200,9 @@ Reset background opacity :sc:`reset_background_opacity`
|
|||||||
Layouts
|
Layouts
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
A layout is an arrangement of multiple *windows*. You can create a new window
|
||||||
|
using the :sc:`new_window` key combination.
|
||||||
|
|
||||||
Currently, there are five layouts available,
|
Currently, there are five layouts available,
|
||||||
|
|
||||||
* **Stack** -- Only a single maximized window is shown at a time
|
* **Stack** -- Only a single maximized window is shown at a time
|
||||||
@@ -200,6 +223,19 @@ example, in the Tall layout you can make the first window wider/narrower, but
|
|||||||
not taller/shorter. Note that what you are resizing is actually not a window,
|
not taller/shorter. Note that what you are resizing is actually not a window,
|
||||||
but a row/column in the layout, all windows in that row/column will be resized.
|
but a row/column in the layout, all windows in that row/column will be resized.
|
||||||
|
|
||||||
|
You can also define shortcuts in :file:`kitty.conf` to make the active window
|
||||||
|
wider, narrower, taller, or shorter by mapping to the ``resize_window``
|
||||||
|
action, for example::
|
||||||
|
|
||||||
|
map ctrl+left resize_window narrower
|
||||||
|
map ctrl+right resize_window wider
|
||||||
|
map ctrl+up resize_window taller
|
||||||
|
map ctrl+down resize_window shorter 3
|
||||||
|
|
||||||
|
The ``resize_window`` action has a second, optional argument to control
|
||||||
|
the resizing increment (a positive integer that defaults to 1).
|
||||||
|
|
||||||
|
|
||||||
Some layouts take options to control their behavior. For example, the ``fat``
|
Some layouts take options to control their behavior. For example, the ``fat``
|
||||||
and ``tall`` layouts accept the ``bias`` option to control how the available
|
and ``tall`` layouts accept the ``bias`` option to control how the available
|
||||||
space is split up. To specify the option, in :opt:`kitty.conf <enabled_layouts>` use::
|
space is split up. To specify the option, in :opt:`kitty.conf <enabled_layouts>` use::
|
||||||
@@ -249,6 +285,8 @@ Some prominent kittens:
|
|||||||
:doc:`Clipboard <kittens/clipboard>`
|
:doc:`Clipboard <kittens/clipboard>`
|
||||||
Copy/paste to the clipboard from shell scripts, even over SSH.
|
Copy/paste to the clipboard from shell scripts, even over SSH.
|
||||||
|
|
||||||
|
You can also :doc:`Learn to create your own kittens <kittens/custom>`.
|
||||||
|
|
||||||
|
|
||||||
Configuring kitty
|
Configuring kitty
|
||||||
-------------------
|
-------------------
|
||||||
@@ -257,6 +295,16 @@ Configuring kitty
|
|||||||
painting frames-per-second. For details and a sample :file:`kitty.conf`,
|
painting frames-per-second. For details and a sample :file:`kitty.conf`,
|
||||||
see the :doc:`configuration docs <conf>`.
|
see the :doc:`configuration docs <conf>`.
|
||||||
|
|
||||||
|
|
||||||
|
Remote control
|
||||||
|
------------------
|
||||||
|
|
||||||
|
|kitty| has a very powerful system that allows you to control it from the
|
||||||
|
:doc:`shell prompt, even over SSH <remote-control>`. You can change colors,
|
||||||
|
fonts, open new windows, tabs, set their titles, change window layout, get text
|
||||||
|
from one window and send text to another, etc, etc. The possibilities are
|
||||||
|
endless. See the :doc:`tutorial <remote-control>` to get started.
|
||||||
|
|
||||||
.. _sessions:
|
.. _sessions:
|
||||||
|
|
||||||
Startup Sessions
|
Startup Sessions
|
||||||
@@ -303,6 +351,8 @@ Mouse features
|
|||||||
* You can also hold down :kbd:`ctrl+shift` and click on a URL to open it in a browser.
|
* You can also hold down :kbd:`ctrl+shift` and click on a URL to open it in a browser.
|
||||||
* You can double click to select a word and triple click to select a line.
|
* You can double click to select a word and triple click to select a line.
|
||||||
* You can right click to extend a previous selection
|
* You can right click to extend a previous selection
|
||||||
|
* You can hold down :kbd:`ctrl+alt` and drag with the mouse to select in
|
||||||
|
columns
|
||||||
|
|
||||||
|
|
||||||
Font control
|
Font control
|
||||||
@@ -332,12 +382,66 @@ scrollback buffer in your favorite pager program (which is ``less`` by default).
|
|||||||
Colors and text formatting are preserved. You can explore the scrollback buffer
|
Colors and text formatting are preserved. You can explore the scrollback buffer
|
||||||
comfortably within the pager.
|
comfortably within the pager.
|
||||||
|
|
||||||
|
Additionally, you can pipe the contents of the scrollback buffer to an
|
||||||
|
arbitrary, command running in a new window, tab or overlay, for example::
|
||||||
|
|
||||||
|
map f1 pipe @ansi window less +G -R
|
||||||
|
|
||||||
|
Would open the scrollback buffer in a new window when you press the :kbd:`F1`
|
||||||
|
key. See :sc:`show_scrollback` for details.
|
||||||
|
|
||||||
|
If you wish to store very large amounts of scrollback to view using the piping or
|
||||||
|
:sc:`show_scrollback` features, you can use the :opt:`scrollback_pager_history_size`
|
||||||
|
option.
|
||||||
|
|
||||||
Frequently Asked Questions
|
Frequently Asked Questions
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
The list of Frequently Asked Questions (*FAQ*) is :doc:`available here <faq>`.
|
The list of Frequently Asked Questions (*FAQ*) is :doc:`available here <faq>`.
|
||||||
|
|
||||||
|
.. _completion:
|
||||||
|
|
||||||
|
Completion for kitty
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
|kitty| comes with completion for the ``kitty`` command for popular shells.
|
||||||
|
|
||||||
|
|
||||||
|
bash
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
Add the following to your :file:`~/.bashrc`
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
source <(kitty + complete setup bash)
|
||||||
|
|
||||||
|
|
||||||
|
zsh
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
Add the following to your :file:`~/.zshrc`
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
autoload -Uz compinit
|
||||||
|
compinit
|
||||||
|
# Completion for kitty
|
||||||
|
kitty + complete setup zsh | source /dev/stdin
|
||||||
|
|
||||||
|
The important thing above is to make sure the call to |kitty| to load the zsh
|
||||||
|
completions happens after the call to :file:`compinit`.
|
||||||
|
|
||||||
|
|
||||||
|
fish
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
Add the following to your :file:`~/.config/fish/config.fish`
|
||||||
|
|
||||||
|
.. code-block:: sh
|
||||||
|
|
||||||
|
kitty + complete setup fish | source
|
||||||
|
|
||||||
|
|
||||||
Changelog
|
Changelog
|
||||||
------------------
|
------------------
|
||||||
|
|||||||
146
docs/kittens/custom.rst
Normal file
146
docs/kittens/custom.rst
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
Custom kittens
|
||||||
|
=================
|
||||||
|
|
||||||
|
You can easily create your own kittens to extend kitty. They are just
|
||||||
|
terminal programs written in Python. When launching a kitten, kitty will
|
||||||
|
open an overlay window over the current window and optionally pass the
|
||||||
|
contents of the current window/scrollback to the kitten over its :file:`STDIN`.
|
||||||
|
The kitten can then perform whatever actions it likes, just as a normal
|
||||||
|
terminal program. After execution of the kitten is complete, it has access
|
||||||
|
to the running kitty instance so it can perform arbitrary actions
|
||||||
|
such as closing windows, pasting text, etc.
|
||||||
|
|
||||||
|
Let's see a simple example of creating a kitten. It will ask the user for some
|
||||||
|
input and paste it into the terminal window.
|
||||||
|
|
||||||
|
Create a file in the kitty config folder, :file:`~/.config/kitty/mykitten.py`
|
||||||
|
(you might need to adjust the path to wherever the kitty config folder is on
|
||||||
|
your machine).
|
||||||
|
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
def main(args):
|
||||||
|
# this is the main entry point of the kitten, it will be executed in
|
||||||
|
# the overlay window when the kitten is launched
|
||||||
|
answer = input('Enter some text: ')
|
||||||
|
# whatever this function returns will be available in the
|
||||||
|
# handle_result() function
|
||||||
|
return answer
|
||||||
|
|
||||||
|
def handle_result(args, answer, target_window_id, boss):
|
||||||
|
# get the kitty window into which to paste answer
|
||||||
|
w = boss.window_id_map.get(target_window_id)
|
||||||
|
if w is not None:
|
||||||
|
w.paste(answer)
|
||||||
|
|
||||||
|
|
||||||
|
Now in :file:`kitty.conf` add the lines::
|
||||||
|
|
||||||
|
map ctrl+k kitten mykitten.py
|
||||||
|
|
||||||
|
|
||||||
|
Start kitty and press :kbd:`ctrl+k` and you should see the kitten running.
|
||||||
|
The best way to develop your own kittens is to modify one of the built in
|
||||||
|
kittens. Look in the kittens sub-directory of the kitty source code for those.
|
||||||
|
Or see below for a list of :ref:`third-party kittens <external_kittens>`,
|
||||||
|
that other kitty users have created.
|
||||||
|
|
||||||
|
|
||||||
|
Passing arguments to kittens
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
You can pass arguments to kittens by defining them in the map directive in
|
||||||
|
:file:`kitty.conf`. For example::
|
||||||
|
|
||||||
|
map ctrl+k kitten mykitten.py arg1 arg2
|
||||||
|
|
||||||
|
These will be available as the ``args`` parameter in the ``main()`` and
|
||||||
|
``handle_result()`` functions. Note also that the current working directory
|
||||||
|
of the kitten is set to the working directory of whatever program is
|
||||||
|
running in the active kitty window.
|
||||||
|
|
||||||
|
|
||||||
|
Passing the contents of the screen to the kitten
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
If you would like your kitten to have access to the contents of the screen
|
||||||
|
and/or the scrollback buffer, you just need to add an annotation to the ``handle_result()``
|
||||||
|
function, telling kitty what kind of input your kitten would like. For example:
|
||||||
|
|
||||||
|
.. code-block:: py
|
||||||
|
|
||||||
|
# in main, STDIN is for the kitten process and will contain
|
||||||
|
# the contents of the screen
|
||||||
|
def main(args):
|
||||||
|
return sys.stdin.read()
|
||||||
|
|
||||||
|
# in handle_result, STDIN is for the kitty process itself, rather
|
||||||
|
# than the kitten process and should not be read from.
|
||||||
|
def handle_result(args, stdin_data, target_window_id, boss):
|
||||||
|
pass
|
||||||
|
|
||||||
|
handle_result.type_of_input = 'text'
|
||||||
|
|
||||||
|
This will send the plain text of the active window to the kitten's
|
||||||
|
:file:`STDIN`. For text with formatting escape codes, use ``ansi``
|
||||||
|
instead. If you want line wrap markers as well, use ``screen-ansi``
|
||||||
|
or just ``screen``. For the scrollback buffer as well, use
|
||||||
|
``history``, ``ansi-history`` or ``screen-history``.
|
||||||
|
|
||||||
|
|
||||||
|
Using kittens to script kitty, without any terminal UI
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
If you would like your kitten to script kitty, without bothering to write a
|
||||||
|
terminal program, you can tell the kittens system to run the
|
||||||
|
``handle_result()`` function without first running the ``main()`` function.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Create a file in the kitty config folder, :file:`~/.config/kitty/zoom_toggle.py`
|
||||||
|
|
||||||
|
.. code-block:: py
|
||||||
|
|
||||||
|
def main(args):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def handle_result(args, answer, target_window_id, boss):
|
||||||
|
tab = boss.active_tab
|
||||||
|
if tab is not None:
|
||||||
|
if tab.current_layout.name == 'stack':
|
||||||
|
tab.last_used_layout()
|
||||||
|
else:
|
||||||
|
tab.goto_layout('stack')
|
||||||
|
|
||||||
|
handle_result.no_ui = True
|
||||||
|
|
||||||
|
|
||||||
|
Now in kitty.conf add::
|
||||||
|
|
||||||
|
map f11 kitten zoom_toggle.py
|
||||||
|
|
||||||
|
Pressing :kbd:`F11` will now act as a zoom toggle function. You can get even
|
||||||
|
more fancy, switching the kitty OS window to fullscreen as well as changing the
|
||||||
|
layout, by simply adding the line::
|
||||||
|
|
||||||
|
boss.toggle_fullscreen()
|
||||||
|
|
||||||
|
|
||||||
|
to the ``handle_result()`` function, above.
|
||||||
|
|
||||||
|
.. _external_kittens:
|
||||||
|
|
||||||
|
Kittens created by kitty users
|
||||||
|
---------------------------------------------
|
||||||
|
|
||||||
|
`vim-kitty-navigator <https://github.com/knubie/vim-kitty-navigator>`_
|
||||||
|
Allows you to navigate seamlessly between vim and kitty splits using a consistent set of hotkeys.
|
||||||
|
|
||||||
|
`smart-scroll <https://github.com/yurikhan/kitty-smart-scroll>`_
|
||||||
|
Makes the kitty scroll bindings work in full screen applications
|
||||||
|
|
||||||
|
`insert password <https://github.com/kovidgoyal/kitty/issues/1222>`_
|
||||||
|
Insert a password from a CLI password manager, taking care to only do it at
|
||||||
|
a password prompt.
|
||||||
@@ -19,7 +19,9 @@ terminal, very useful for picking files from the output of a ``git`` or ``ls`` c
|
|||||||
adding them to the command line for the next command.
|
adding them to the command line for the next command.
|
||||||
|
|
||||||
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.
|
options and modes of operation, see below. You can use these options to
|
||||||
|
create mappings in :file:`kitty.conf` to select various different text
|
||||||
|
snippets. See :sc:`insert_selected_path` for examples.
|
||||||
|
|
||||||
Command Line Interface
|
Command Line Interface
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|||||||
@@ -10,6 +10,12 @@ It supports all image types supported by `ImageMagick
|
|||||||
<https://www.imagemagick.org>`_. It even works over SSH. For details, see
|
<https://www.imagemagick.org>`_. It even works over SSH. For details, see
|
||||||
the :doc:`kitty graphics protocol </graphics-protocol>`.
|
the :doc:`kitty graphics protocol </graphics-protocol>`.
|
||||||
|
|
||||||
|
You might want to create an alias in your shell's configuration files::
|
||||||
|
|
||||||
|
alias icat="kitty +kitten icat"
|
||||||
|
|
||||||
|
Then you can simply use ``icat image.png`` to view images.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
`ImageMagick <https://www.imagemagick.org>`_ must be installed for ``icat`` to
|
`ImageMagick <https://www.imagemagick.org>`_ must be installed for ``icat`` to
|
||||||
|
|||||||
@@ -18,5 +18,5 @@ typing a leading period and then the two character index and pressing Enter.
|
|||||||
|
|
||||||
In :guilabel:`Name` mode you instead type words from the character name and use the arrow
|
In :guilabel:`Name` mode you instead type words from the character name and use the arrow
|
||||||
keys/tab to select the character from the displayed matches. You can also type
|
keys/tab to select the character from the displayed matches. You can also type
|
||||||
a leading period and the index for the match if you dont like to use arrow
|
a leading period and the index for the match if you don't like to use arrow
|
||||||
keys.
|
keys.
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ glyph in video RAM so that font rendering is not a bottleneck. Interaction
|
|||||||
with child programs takes place in a separate thread from rendering, to improve
|
with child programs takes place in a separate thread from rendering, to improve
|
||||||
smoothness.
|
smoothness.
|
||||||
|
|
||||||
There are two parameters you can tune to adjust the performance. ``repaint_delay``
|
There are two parameters you can tune to adjust the performance. :opt:`repaint_delay`
|
||||||
and ``input_delay``. These control the artificial delays introduced into the
|
and :opt:`input_delay`. These control the artificial delays introduced into the
|
||||||
render loop to reduce CPU usage. See :ref:`conf-kitty-performance` for details.
|
render loop to reduce CPU usage. See :ref:`conf-kitty-performance` for details.
|
||||||
See also the ``sync_to_monitor`` option to further decrease latency at the cost
|
See also the :opt:`sync_to_monitor` option to further decrease latency at the cost
|
||||||
of some `tearing <https://en.wikipedia.org/wiki/Screen_tearing>`_ while scrolling.
|
of some `tearing <https://en.wikipedia.org/wiki/Screen_tearing>`_ while scrolling.
|
||||||
|
|
||||||
You can generate detailed per-function performance data using
|
You can generate detailed per-function performance data using
|
||||||
|
|||||||
88
docs/pipe.rst
Normal file
88
docs/pipe.rst
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
Working with the screen and history buffer contents
|
||||||
|
======================================================
|
||||||
|
|
||||||
|
|
||||||
|
You can pipe the contents of the current screen and history buffer as
|
||||||
|
:file:`STDIN` to an arbitrary program using the ``pipe`` function. The program
|
||||||
|
can be displayed in a kitty window or overlay.
|
||||||
|
|
||||||
|
For example, the following in :file:`kitty.conf` will open the scrollback
|
||||||
|
buffer in less in an overlay window, when you press :kbd:`F1`::
|
||||||
|
|
||||||
|
map f1 pipe @ansi overlay less +G -R
|
||||||
|
|
||||||
|
The syntax of the ``pipe`` function is::
|
||||||
|
|
||||||
|
pipe <input placeholder> <destination window type> <command line to run>
|
||||||
|
|
||||||
|
|
||||||
|
The piping environment
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
The program to which the data is piped has a special environment variable
|
||||||
|
declared, ``KITTY_PIPE_DATA`` whose contents are::
|
||||||
|
|
||||||
|
KITTY_PIPE_DATA={scrolled_by}:{cursor_x},{cursor_y}:{lines},{columns}
|
||||||
|
|
||||||
|
where ``scrolled_by`` is the number of lines kitty is currently scrolled by,
|
||||||
|
``cursor_(x|y)`` is the position of the cursor on the screen with ``(1,1)``
|
||||||
|
being the top left corner and ``{lines},{columns}`` being the number of rows
|
||||||
|
and columns of the screen.
|
||||||
|
|
||||||
|
You can choose where to run the pipe program:
|
||||||
|
|
||||||
|
``overlay``
|
||||||
|
An overlay window over the current kitty window
|
||||||
|
|
||||||
|
``window``
|
||||||
|
A new kitty window
|
||||||
|
|
||||||
|
``os_window``
|
||||||
|
A new top-level window
|
||||||
|
|
||||||
|
``tab``
|
||||||
|
A new window in a new tab
|
||||||
|
|
||||||
|
``none``
|
||||||
|
Run it in the background
|
||||||
|
|
||||||
|
|
||||||
|
Input placeholders
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
There are various different kinds of placeholders
|
||||||
|
|
||||||
|
``@text``
|
||||||
|
Plain text, current screen + scrollback buffer
|
||||||
|
|
||||||
|
``@ansi``
|
||||||
|
Text with formatting, current screen + scrollback buffer
|
||||||
|
|
||||||
|
``@screen``
|
||||||
|
Plain text, only current screen
|
||||||
|
|
||||||
|
``@ansi_screen``
|
||||||
|
Text with formatting, only current screen
|
||||||
|
|
||||||
|
``@alternate``
|
||||||
|
Plain text, secondary screen. The secondary screen is the screen not currently displayed. For
|
||||||
|
example if you run a fullscreen terminal application, the secondary screen will
|
||||||
|
be the screen you return to when quitting the application.
|
||||||
|
|
||||||
|
``@ansi_alternate``
|
||||||
|
Text with formatting, secondary screen.
|
||||||
|
|
||||||
|
``@alternate_scrollback``
|
||||||
|
Plain text, secondary screen + scrollback, if any.
|
||||||
|
|
||||||
|
``@ansi_alternate_scrollback``
|
||||||
|
Text with formatting, secondary screen + scrollback, if any.
|
||||||
|
|
||||||
|
``none``
|
||||||
|
No input
|
||||||
|
|
||||||
|
|
||||||
|
You can also add the suffix ``_wrap`` to the placeholder, in which case kitty
|
||||||
|
will insert the carriage return at every line wrap location (where long lines
|
||||||
|
are wrapped at screen edges). This is useful if you want to pipe to program
|
||||||
|
that wants to duplicate the screen layout of the screen.
|
||||||
@@ -6,7 +6,7 @@ These are typically in the form of new or re-purposed escape codes. While these
|
|||||||
extensions are currently |kitty| specific, it would be nice to get some of them
|
extensions are currently |kitty| specific, it would be nice to get some of them
|
||||||
adopted more broadly, to push the state of terminal emulators forward.
|
adopted more broadly, to push the state of terminal emulators forward.
|
||||||
|
|
||||||
The goal of these extensions is to be as small an unobtrusive as possible,
|
The goal of these extensions is to be as small and unobtrusive as possible,
|
||||||
while filling in some gaps in the existing xterm protocol. In particular, one
|
while filling in some gaps in the existing xterm protocol. In particular, one
|
||||||
of the goals of this specification is explicitly not to "re-imagine" the tty.
|
of the goals of this specification is explicitly not to "re-imagine" the tty.
|
||||||
The tty should remain what it is -- a device for efficiently processing text
|
The tty should remain what it is -- a device for efficiently processing text
|
||||||
@@ -141,6 +141,8 @@ For example::
|
|||||||
This encoding means each key event is represented by 8 or 9 printable ascii
|
This encoding means each key event is represented by 8 or 9 printable ascii
|
||||||
only bytes, for maximum robustness.
|
only bytes, for maximum robustness.
|
||||||
|
|
||||||
|
Support for this mode is indicated by the ``fullkbd`` boolean capability
|
||||||
|
in the terminfo database, in case querying for it via DECQRM is inconvenient.
|
||||||
|
|
||||||
.. _ext_styles:
|
.. _ext_styles:
|
||||||
|
|
||||||
@@ -184,3 +186,25 @@ changes even after it exits. To avoid this, kitty introduces a new pair of
|
|||||||
These escape codes save/restore the so called *dynamic colors*, default
|
These escape codes save/restore the so called *dynamic colors*, default
|
||||||
background, default foreground, selection background, selection foreground and
|
background, default foreground, selection background, selection foreground and
|
||||||
cursor color.
|
cursor color.
|
||||||
|
|
||||||
|
|
||||||
|
Pasting to clipboard
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
|kitty| implements the OSC 52 escape code protocol to get/set the clipboard
|
||||||
|
contents (controlled via the :opt:`clipboard_control` setting). There is one
|
||||||
|
difference in kitty's implementation compared to some other terminal emulators.
|
||||||
|
|kitty| allows sending arbitrary amounts of text to the clipboard. It does so
|
||||||
|
by modifying the protocol slightly. Successive OSC 52 escape codes to set the
|
||||||
|
clipboard will concatenate, so::
|
||||||
|
|
||||||
|
<ESC>]52;c;<payload1><ESC>\
|
||||||
|
<ESC>]52;c;<payload2><ESC>\
|
||||||
|
|
||||||
|
will result in the clipboard having the contents ``payload1 + payload2``. To
|
||||||
|
send a new string to the clipboard send an OSC 52 sequence with an invalid payload
|
||||||
|
first, for example::
|
||||||
|
|
||||||
|
<ESC>]52;c;!<ESC>\
|
||||||
|
|
||||||
|
Here ``!`` is not valid base64 encoded text, so it clears the clipboard.
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ Let's walk through a few examples of controlling |kitty|.
|
|||||||
|
|
||||||
Start by running |kitty| as::
|
Start by running |kitty| as::
|
||||||
|
|
||||||
kitty -o allow_remote_control=yes --window-layout tall
|
kitty -o allow_remote_control=yes -o enabled_layouts=tall
|
||||||
|
|
||||||
In order for control to work, :opt:`allow_remote_control` must be enabled in
|
In order for control to work, :opt:`allow_remote_control` must be enabled in
|
||||||
:file:`kitty.conf`. Here we turn it on explicitly at the command line.
|
:file:`kitty.conf`. Here we turn it on explicitly at the command line.
|
||||||
@@ -103,7 +103,7 @@ instance of |kitty| using the :option:`kitty @ --to` command line argument to ``
|
|||||||
kitty @ --to unix:/tmp/mykitty ls
|
kitty @ --to unix:/tmp/mykitty ls
|
||||||
|
|
||||||
|
|
||||||
Note that is all you want to do is run a single |kitty| "daemon" and have subsequent
|
Note that if all you want to do is run a single |kitty| "daemon" and have subsequent
|
||||||
|kitty| invocations appear as new top-level windows, you can use the simpler :option:`kitty --single-instance`
|
|kitty| invocations appear as new top-level windows, you can use the simpler :option:`kitty --single-instance`
|
||||||
option, see ``kitty --help`` for that.
|
option, see ``kitty --help`` for that.
|
||||||
|
|
||||||
@@ -116,7 +116,23 @@ shell with completion for |kitty| command names and options.
|
|||||||
|
|
||||||
You can even open the |kitty| shell inside a running |kitty| using a simple
|
You can even open the |kitty| shell inside a running |kitty| using a simple
|
||||||
keyboard shortcut (:sc:`kitty_shell` by default). This has the added
|
keyboard shortcut (:sc:`kitty_shell` by default). This has the added
|
||||||
advantage that you dont need to use ``allow_remote_control`` to make it work.
|
advantage that you don't need to use ``allow_remote_control`` to make it work.
|
||||||
|
|
||||||
|
|
||||||
|
Allowing only some windows to control kitty
|
||||||
|
----------------------------------------------
|
||||||
|
|
||||||
|
If you do not want to allow all programs running in |kitty| to control it, you can selectively
|
||||||
|
enable remote control for only some |kitty| windows. Simply create a shortcut
|
||||||
|
such as::
|
||||||
|
|
||||||
|
map ctrl+k new_window @ some_program
|
||||||
|
|
||||||
|
Then programs running in windows created with that shortcut can use ``kitty @``
|
||||||
|
to control kitty. Note that any program with the right level of permissions can
|
||||||
|
still write to the pipes of any other program on the same computer and
|
||||||
|
therefore can control |kitty|. It can, however, be useful to block programs
|
||||||
|
running on other computers (for example, over ssh) or as other users.
|
||||||
|
|
||||||
|
|
||||||
.. include:: generated/cli-kitty-at.rst
|
.. include:: generated/cli-kitty-at.rst
|
||||||
|
|||||||
264
gen-apc-parsers.py
Executable file
264
gen-apc-parsers.py
Executable file
@@ -0,0 +1,264 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
# License: GPLv3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_keys(keymap):
|
||||||
|
ans = defaultdict(list)
|
||||||
|
for ch, (attr, atype) in keymap.items():
|
||||||
|
if atype not in ('int', 'uint'):
|
||||||
|
atype = 'flag'
|
||||||
|
ans[atype].append(ch)
|
||||||
|
return ans
|
||||||
|
|
||||||
|
|
||||||
|
def enum(keymap):
|
||||||
|
lines = []
|
||||||
|
for ch, (attr, atype) in keymap.items():
|
||||||
|
lines.append(f"{attr}='{ch}'")
|
||||||
|
return '''
|
||||||
|
enum KEYS {{
|
||||||
|
{}
|
||||||
|
}};
|
||||||
|
'''.format(',\n'.join(lines))
|
||||||
|
|
||||||
|
|
||||||
|
def parse_key(keymap):
|
||||||
|
lines = []
|
||||||
|
for attr, atype in keymap.values():
|
||||||
|
vs = atype.upper() if atype in ('uint', 'int') else 'FLAG'
|
||||||
|
lines.append(f'case {attr}: value_state = {vs}; break;')
|
||||||
|
return ' \n'.join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_flag(keymap, type_map, command_class):
|
||||||
|
lines = []
|
||||||
|
for ch in type_map['flag']:
|
||||||
|
attr, allowed_values = keymap[ch]
|
||||||
|
q = ' && '.join(f"g.{attr} != '{x}'" for x in allowed_values)
|
||||||
|
lines.append(f'''
|
||||||
|
case {attr}: {{
|
||||||
|
g.{attr} = screen->parser_buf[pos++] & 0xff;
|
||||||
|
if ({q}) {{
|
||||||
|
REPORT_ERROR("Malformed {command_class} control block, unknown flag value for {attr}: 0x%x", g.{attr});
|
||||||
|
return;
|
||||||
|
}};
|
||||||
|
}}
|
||||||
|
break;
|
||||||
|
''')
|
||||||
|
return ' \n'.join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_number(keymap):
|
||||||
|
int_keys = [f'I({attr})' for attr, atype in keymap.values() if atype == 'int']
|
||||||
|
uint_keys = [f'U({attr})' for attr, atype in keymap.values() if atype == 'uint']
|
||||||
|
return '; '.join(int_keys), '; '.join(uint_keys)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_for_report(report_name, keymap, type_map, payload_allowed):
|
||||||
|
def group(atype, conv):
|
||||||
|
flag_fmt, flag_attrs = [], []
|
||||||
|
cv = {'flag': 'c', 'int': 'i', 'uint': 'I'}[atype]
|
||||||
|
for ch in type_map[atype]:
|
||||||
|
flag_fmt.append('s' + cv)
|
||||||
|
attr = keymap[ch][0]
|
||||||
|
flag_attrs.append(f'"{attr}", {conv}g.{attr}')
|
||||||
|
return ' '.join(flag_fmt), ', '.join(flag_attrs)
|
||||||
|
|
||||||
|
flag_fmt, flag_attrs = group('flag', '')
|
||||||
|
int_fmt, int_attrs = group('int', '(int)')
|
||||||
|
uint_fmt, uint_attrs = group('uint', '(unsigned int)')
|
||||||
|
|
||||||
|
fmt = f'{flag_fmt} {uint_fmt} {int_fmt}'
|
||||||
|
if payload_allowed:
|
||||||
|
ans = [f'REPORT_VA_COMMAND("s {{{fmt} sI}} y#", "{report_name}",']
|
||||||
|
else:
|
||||||
|
ans = [f'REPORT_VA_COMMAND("s {{{fmt}}}", "{report_name}",']
|
||||||
|
ans.append(',\n '.join((flag_attrs, uint_attrs, int_attrs)))
|
||||||
|
if payload_allowed:
|
||||||
|
ans.append(', "payload_sz", g.payload_sz, payload, g.payload_sz')
|
||||||
|
ans.append(');')
|
||||||
|
return '\n'.join(ans)
|
||||||
|
|
||||||
|
|
||||||
|
def generate(function_name, callback_name, report_name, keymap, command_class, initial_key='a', payload_allowed=True):
|
||||||
|
type_map = resolve_keys(keymap)
|
||||||
|
keys_enum = enum(keymap)
|
||||||
|
handle_key = parse_key(keymap)
|
||||||
|
flag_keys = parse_flag(keymap, type_map, command_class)
|
||||||
|
int_keys, uint_keys = parse_number(keymap)
|
||||||
|
report_cmd = cmd_for_report(report_name, keymap, type_map, payload_allowed)
|
||||||
|
if payload_allowed:
|
||||||
|
payload_after_value = "case ';': state = PAYLOAD; break;"
|
||||||
|
payload = payload = ', PAYLOAD'
|
||||||
|
parr = 'static uint8_t payload[4096];'
|
||||||
|
payload_case = f'''
|
||||||
|
case PAYLOAD: {{
|
||||||
|
sz = screen->parser_buf_pos - pos;
|
||||||
|
const char *err = base64_decode(screen->parser_buf + pos, sz, payload, sizeof(payload), &g.payload_sz);
|
||||||
|
if (err != NULL) {{ REPORT_ERROR("Failed to parse {command_class} command payload with error: %s", err); return; }}
|
||||||
|
pos = screen->parser_buf_pos;
|
||||||
|
}}
|
||||||
|
break;
|
||||||
|
'''
|
||||||
|
callback = f'{callback_name}(screen, &g, payload)'
|
||||||
|
else:
|
||||||
|
payload_after_value = payload = parr = payload_case = ''
|
||||||
|
callback = f'{callback_name}(screen, &g)'
|
||||||
|
|
||||||
|
return f'''
|
||||||
|
static inline void
|
||||||
|
{function_name}(Screen *screen, PyObject UNUSED *dump_callback) {{
|
||||||
|
unsigned int pos = 1;
|
||||||
|
enum PARSER_STATES {{ KEY, EQUAL, UINT, INT, FLAG, AFTER_VALUE {payload} }};
|
||||||
|
enum PARSER_STATES state = KEY, value_state = FLAG;
|
||||||
|
static {command_class} g;
|
||||||
|
unsigned int i, code;
|
||||||
|
uint64_t lcode;
|
||||||
|
bool is_negative;
|
||||||
|
memset(&g, 0, sizeof(g));
|
||||||
|
size_t sz;
|
||||||
|
{parr}
|
||||||
|
{keys_enum}
|
||||||
|
enum KEYS key = '{initial_key}';
|
||||||
|
|
||||||
|
while (pos < screen->parser_buf_pos) {{
|
||||||
|
switch(state) {{
|
||||||
|
case KEY:
|
||||||
|
key = screen->parser_buf[pos++];
|
||||||
|
state = EQUAL;
|
||||||
|
switch(key) {{
|
||||||
|
{handle_key}
|
||||||
|
default:
|
||||||
|
REPORT_ERROR("Malformed {command_class} control block, invalid key character: 0x%x", key);
|
||||||
|
return;
|
||||||
|
}}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EQUAL:
|
||||||
|
if (screen->parser_buf[pos++] != '=') {{
|
||||||
|
REPORT_ERROR("Malformed {command_class} control block, no = after key, found: 0x%x instead", screen->parser_buf[pos-1]);
|
||||||
|
return;
|
||||||
|
}}
|
||||||
|
state = value_state;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FLAG:
|
||||||
|
switch(key) {{
|
||||||
|
{flag_keys}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}}
|
||||||
|
state = AFTER_VALUE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case INT:
|
||||||
|
#define READ_UINT \\
|
||||||
|
for (i = pos; i < MIN(screen->parser_buf_pos, pos + 10); i++) {{ \\
|
||||||
|
if (screen->parser_buf[i] < '0' || screen->parser_buf[i] > '9') break; \\
|
||||||
|
}} \\
|
||||||
|
if (i == pos) {{ REPORT_ERROR("Malformed {command_class} control block, expecting an integer value for key: %c", key & 0xFF); return; }} \\
|
||||||
|
lcode = utoi(screen->parser_buf + pos, i - pos); pos = i; \\
|
||||||
|
if (lcode > UINT32_MAX) {{ REPORT_ERROR("Malformed {command_class} control block, number is too large"); return; }} \\
|
||||||
|
code = lcode;
|
||||||
|
|
||||||
|
is_negative = false;
|
||||||
|
if(screen->parser_buf[pos] == '-') {{ is_negative = true; pos++; }}
|
||||||
|
#define I(x) case x: g.x = is_negative ? 0 - (int32_t)code : (int32_t)code; break
|
||||||
|
READ_UINT;
|
||||||
|
switch(key) {{
|
||||||
|
{int_keys};
|
||||||
|
default: break;
|
||||||
|
}}
|
||||||
|
state = AFTER_VALUE;
|
||||||
|
break;
|
||||||
|
#undef I
|
||||||
|
case UINT:
|
||||||
|
READ_UINT;
|
||||||
|
#define U(x) case x: g.x = code; break
|
||||||
|
switch(key) {{
|
||||||
|
{uint_keys};
|
||||||
|
default: break;
|
||||||
|
}}
|
||||||
|
state = AFTER_VALUE;
|
||||||
|
break;
|
||||||
|
#undef U
|
||||||
|
#undef READ_UINT
|
||||||
|
|
||||||
|
case AFTER_VALUE:
|
||||||
|
switch (screen->parser_buf[pos++]) {{
|
||||||
|
default:
|
||||||
|
REPORT_ERROR("Malformed {command_class} control block, expecting a comma or semi-colon after a value, found: 0x%x",
|
||||||
|
screen->parser_buf[pos - 1]);
|
||||||
|
return;
|
||||||
|
case ',':
|
||||||
|
state = KEY;
|
||||||
|
break;
|
||||||
|
{payload_after_value}
|
||||||
|
}}
|
||||||
|
break;
|
||||||
|
|
||||||
|
{payload_case}
|
||||||
|
|
||||||
|
}} // end switch
|
||||||
|
}} // end while
|
||||||
|
|
||||||
|
switch(state) {{
|
||||||
|
case EQUAL:
|
||||||
|
REPORT_ERROR("Malformed {command_class} control block, no = after key"); return;
|
||||||
|
case INT:
|
||||||
|
case UINT:
|
||||||
|
REPORT_ERROR("Malformed {command_class} control block, expecting an integer value"); return;
|
||||||
|
case FLAG:
|
||||||
|
REPORT_ERROR("Malformed {command_class} control block, expecting a flag value"); return;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}}
|
||||||
|
|
||||||
|
{report_cmd}
|
||||||
|
|
||||||
|
{callback};
|
||||||
|
}}
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
def write_header(text, path):
|
||||||
|
with open(path, 'w') as f:
|
||||||
|
print(f'// This file is generated by {__file__} do not edit!', file=f, end='\n\n')
|
||||||
|
print('#pragma once', file=f)
|
||||||
|
print(text, file=f)
|
||||||
|
subprocess.check_call(['clang-format', '-i', path])
|
||||||
|
|
||||||
|
|
||||||
|
def graphics_parser():
|
||||||
|
flag = frozenset
|
||||||
|
keymap = {
|
||||||
|
'a': ('action', flag('tTqpd')),
|
||||||
|
'd': ('delete_action', flag('aAiIcCpPqQxXyYzZ')),
|
||||||
|
't': ('transmission_type', flag('dfts')),
|
||||||
|
'o': ('compressed', flag('z')),
|
||||||
|
'f': ('format', 'uint'),
|
||||||
|
'm': ('more', 'uint'),
|
||||||
|
'i': ('id', 'uint'),
|
||||||
|
'w': ('width', 'uint'),
|
||||||
|
'h': ('height', 'uint'),
|
||||||
|
'x': ('x_offset', 'uint'),
|
||||||
|
'y': ('y_offset', 'uint'),
|
||||||
|
'v': ('data_height', 'uint'),
|
||||||
|
's': ('data_width', 'uint'),
|
||||||
|
'S': ('data_sz', 'uint'),
|
||||||
|
'O': ('data_offset', 'uint'),
|
||||||
|
'c': ('num_cells', 'uint'),
|
||||||
|
'r': ('num_lines', 'uint'),
|
||||||
|
'X': ('cell_x_offset', 'uint'),
|
||||||
|
'Y': ('cell_y_offset', 'uint'),
|
||||||
|
'z': ('z_index', 'int'),
|
||||||
|
}
|
||||||
|
text = generate('parse_graphics_code', 'screen_handle_graphics_command', 'graphics_command', keymap, 'GraphicsCommand')
|
||||||
|
write_header(text, 'kitty/parse-graphics-command.h')
|
||||||
|
|
||||||
|
|
||||||
|
graphics_parser()
|
||||||
@@ -20,6 +20,7 @@ non_characters |= frozenset(range(0xffff, 0x10ffff + 1, 0x10000))
|
|||||||
non_characters |= frozenset(range(0xfdd0, 0xfdf0))
|
non_characters |= frozenset(range(0xfdd0, 0xfdf0))
|
||||||
if len(non_characters) != 66:
|
if len(non_characters) != 66:
|
||||||
raise SystemExit('non_characters table incorrect')
|
raise SystemExit('non_characters table incorrect')
|
||||||
|
emoji_skin_tone_modifiers = frozenset(range(0x1f3fb, 0x1F3FF + 1))
|
||||||
|
|
||||||
|
|
||||||
def get_data(fname, folder='UCD'):
|
def get_data(fname, folder='UCD'):
|
||||||
@@ -41,7 +42,8 @@ def get_data(fname, folder='UCD'):
|
|||||||
class_maps = {}
|
class_maps = {}
|
||||||
name_map = {}
|
name_map = {}
|
||||||
word_search_map = defaultdict(set)
|
word_search_map = defaultdict(set)
|
||||||
marks = set()
|
zwj = 0x200d
|
||||||
|
marks = set(emoji_skin_tone_modifiers) | {zwj}
|
||||||
not_assigned = set(range(0, sys.maxunicode))
|
not_assigned = set(range(0, sys.maxunicode))
|
||||||
|
|
||||||
|
|
||||||
@@ -85,6 +87,11 @@ def parse_ucd():
|
|||||||
if category.startswith('M'):
|
if category.startswith('M'):
|
||||||
marks.add(codepoint)
|
marks.add(codepoint)
|
||||||
|
|
||||||
|
# Some common synonyms
|
||||||
|
word_search_map['bee'] |= word_search_map['honeybee']
|
||||||
|
word_search_map['lambda'] |= word_search_map['lamda']
|
||||||
|
word_search_map['lamda'] |= word_search_map['lambda']
|
||||||
|
|
||||||
|
|
||||||
def split_two(line):
|
def split_two(line):
|
||||||
spec, rest = line.split(';', 1)
|
spec, rest = line.split(';', 1)
|
||||||
@@ -195,12 +202,13 @@ def gen_emoji():
|
|||||||
p('\treturn false;\n}')
|
p('\treturn false;\n}')
|
||||||
|
|
||||||
|
|
||||||
def category_test(name, p, classes, comment, static=False, extra_chars=frozenset()):
|
def category_test(name, p, classes, comment, static=False, extra_chars=frozenset(), exclude=frozenset()):
|
||||||
static = 'static inline ' if static else ''
|
static = 'static inline ' if static else ''
|
||||||
chars = set()
|
chars = set()
|
||||||
for c in classes:
|
for c in classes:
|
||||||
chars |= class_maps[c]
|
chars |= class_maps[c]
|
||||||
chars |= extra_chars
|
chars |= extra_chars
|
||||||
|
chars -= exclude
|
||||||
p(f'{static}bool\n{name}(char_type code) {{')
|
p(f'{static}bool\n{name}(char_type code) {{')
|
||||||
p(f'\t// {comment} ({len(chars)} codepoints)' + ' {{' '{')
|
p(f'\t// {comment} ({len(chars)} codepoints)' + ' {{' '{')
|
||||||
p('\tswitch(code) {')
|
p('\tswitch(code) {')
|
||||||
@@ -251,8 +259,16 @@ def gen_ucd():
|
|||||||
cz = {c for c in class_maps if c[0] in 'CZ'}
|
cz = {c for c in class_maps if c[0] in 'CZ'}
|
||||||
with create_header('kitty/unicode-data.c') as p:
|
with create_header('kitty/unicode-data.c') as p:
|
||||||
p('#include "unicode-data.h"')
|
p('#include "unicode-data.h"')
|
||||||
category_test('is_combining_char', p, {c for c in class_maps if c.startswith('M')}, 'M category (marks)')
|
category_test(
|
||||||
category_test('is_ignored_char', p, 'Cc Cf Cs'.split(), 'Control characters and non-characters', extra_chars=non_characters)
|
'is_combining_char', p,
|
||||||
|
{c for c in class_maps if c.startswith('M')},
|
||||||
|
'M category (marks)',
|
||||||
|
# See https://github.com/harfbuzz/harfbuzz/issues/169
|
||||||
|
extra_chars=emoji_skin_tone_modifiers | {zwj}
|
||||||
|
)
|
||||||
|
category_test(
|
||||||
|
'is_ignored_char', p, 'Cc Cf Cs'.split(),
|
||||||
|
'Control characters and non-characters', extra_chars=non_characters, exclude={zwj})
|
||||||
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')
|
||||||
category_test('is_CZ_category', p, cz, 'C and Z categories')
|
category_test('is_CZ_category', p, cz, 'C and Z categories')
|
||||||
category_test('is_P_category', p, {c for c in class_maps if c[0] == 'P'}, 'P category (punctuation)')
|
category_test('is_P_category', p, {c for c in class_maps if c[0] == 'P'}, 'P category (punctuation)')
|
||||||
@@ -265,7 +281,7 @@ def gen_ucd():
|
|||||||
p('combining_type mark_for_codepoint(char_type c) {')
|
p('combining_type mark_for_codepoint(char_type c) {')
|
||||||
rmap = codepoint_to_mark_map(p, mark_map)
|
rmap = codepoint_to_mark_map(p, mark_map)
|
||||||
p('}\n')
|
p('}\n')
|
||||||
if rmap[0xfe0e] != 1280:
|
if rmap[0xfe0e] != 1281:
|
||||||
raise ValueError('The mark for 0xfe0e has changed, you have to update VS15 to {} and VS16 to {} in unicode-data.h'.format(
|
raise ValueError('The mark for 0xfe0e has changed, you have to update VS15 to {} and VS16 to {} in unicode-data.h'.format(
|
||||||
rmap[0xfe0e], rmap[0xfe0f]
|
rmap[0xfe0e], rmap[0xfe0f]
|
||||||
))
|
))
|
||||||
@@ -349,16 +365,15 @@ def gen_names():
|
|||||||
root = TrieNode()
|
root = TrieNode()
|
||||||
all_trie_nodes.append(root)
|
all_trie_nodes.append(root)
|
||||||
|
|
||||||
def add_word(word_idx):
|
def add_word(word_idx, word):
|
||||||
word = word_map[word_idx]
|
|
||||||
parent = root
|
parent = root
|
||||||
for letter in map(ord, word):
|
for letter in map(ord, word):
|
||||||
idx = parent.add_letter(letter)
|
idx = parent.add_letter(letter)
|
||||||
parent = all_trie_nodes[idx]
|
parent = all_trie_nodes[idx]
|
||||||
parent.match_offset = offsets_array[word_idx]
|
parent.match_offset = offsets_array[word_idx]
|
||||||
|
|
||||||
for i in range(len(word_map)):
|
for i, word in enumerate(word_map):
|
||||||
add_word(i)
|
add_word(i, word)
|
||||||
children_array = [0]
|
children_array = [0]
|
||||||
for node in all_trie_nodes:
|
for node in all_trie_nodes:
|
||||||
if node.children:
|
if node.children:
|
||||||
|
|||||||
@@ -43,8 +43,6 @@ def export():
|
|||||||
switch = ['glad_debug_{0} = glad_{0}'.format(f) for f in functions]
|
switch = ['glad_debug_{0} = glad_{0}'.format(f) for f in functions]
|
||||||
c = c.replace('<glad/glad.h>', '"gl-wrapper.h"', 1)
|
c = c.replace('<glad/glad.h>', '"gl-wrapper.h"', 1)
|
||||||
c = '#pragma GCC diagnostic ignored "-Wpedantic"\n' + c
|
c = '#pragma GCC diagnostic ignored "-Wpedantic"\n' + c
|
||||||
# See https://github.com/kovidgoyal/kitty/issues/654
|
|
||||||
c = c.replace('strncpy(local_str, gl_str_tmp, len+1)', 'memcpy(local_str, gl_str_tmp, len+1)', 1)
|
|
||||||
c += '''
|
c += '''
|
||||||
int
|
int
|
||||||
init_glad(GLADloadproc load, int debug) {
|
init_glad(GLADloadproc load, int debug) {
|
||||||
|
|||||||
314
glfw/backend_utils.c
vendored
Normal file
314
glfw/backend_utils.c
vendored
Normal file
@@ -0,0 +1,314 @@
|
|||||||
|
/*
|
||||||
|
* backend_utils.c
|
||||||
|
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
*
|
||||||
|
* Distributed under terms of the GPL3 license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#include "backend_utils.h"
|
||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <float.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#ifdef __NetBSD__
|
||||||
|
#define ppoll pollts
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static inline double
|
||||||
|
monotonic() {
|
||||||
|
struct timespec ts = {0};
|
||||||
|
#ifdef CLOCK_HIGHRES
|
||||||
|
clock_gettime(CLOCK_HIGHRES, &ts);
|
||||||
|
#elif CLOCK_MONOTONIC_RAW
|
||||||
|
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
|
||||||
|
#else
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||||
|
#endif
|
||||||
|
return (((double)ts.tv_nsec) / 1e9) + (double)ts.tv_sec;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
update_fds(EventLoopData *eld) {
|
||||||
|
for (nfds_t i = 0; i < eld->watches_count; i++) {
|
||||||
|
Watch *w = eld->watches + i;
|
||||||
|
eld->fds[i].fd = w->fd;
|
||||||
|
eld->fds[i].events = w->enabled ? w->events : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static id_type watch_counter = 0;
|
||||||
|
|
||||||
|
id_type
|
||||||
|
addWatch(EventLoopData *eld, const char* name, int fd, int events, int enabled, watch_callback_func cb, void *cb_data) {
|
||||||
|
if (eld->watches_count >= sizeof(eld->watches)/sizeof(eld->watches[0])) {
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR, "Too many watches added");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
Watch *w = eld->watches + eld->watches_count++;
|
||||||
|
w->name = name;
|
||||||
|
w->fd = fd; w->events = events; w->enabled = enabled;
|
||||||
|
w->callback = cb;
|
||||||
|
w->callback_data = cb_data;
|
||||||
|
w->id = ++watch_counter;
|
||||||
|
update_fds(eld);
|
||||||
|
return w->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define removeX(which, item_id, update_func) {\
|
||||||
|
for (nfds_t i = 0; i < eld->which##_count; i++) { \
|
||||||
|
if (eld->which[i].id == item_id) { \
|
||||||
|
eld->which##_count--; \
|
||||||
|
if (i < eld->which##_count) { \
|
||||||
|
memmove(eld->which + i, eld->which + i + 1, sizeof(eld->which[0]) * (eld->which##_count - i)); \
|
||||||
|
} \
|
||||||
|
update_func(eld); break; \
|
||||||
|
}}}
|
||||||
|
|
||||||
|
void
|
||||||
|
removeWatch(EventLoopData *eld, id_type watch_id) {
|
||||||
|
removeX(watches, watch_id, update_fds);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
toggleWatch(EventLoopData *eld, id_type watch_id, int enabled) {
|
||||||
|
for (nfds_t i = 0; i < eld->watches_count; i++) {
|
||||||
|
if (eld->watches[i].id == watch_id) {
|
||||||
|
if (eld->watches[i].enabled != enabled) {
|
||||||
|
eld->watches[i].enabled = enabled;
|
||||||
|
update_fds(eld);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static id_type timer_counter = 0;
|
||||||
|
|
||||||
|
static int
|
||||||
|
compare_timers(const void *a_, const void *b_) {
|
||||||
|
const Timer *a = (const Timer*)a_, *b = (const Timer*)b_;
|
||||||
|
return (a->trigger_at > b->trigger_at) ? 1 : (a->trigger_at < b->trigger_at) ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
update_timers(EventLoopData *eld) {
|
||||||
|
if (eld->timers_count > 1) qsort(eld->timers, eld->timers_count, sizeof(eld->timers[0]), compare_timers);
|
||||||
|
}
|
||||||
|
|
||||||
|
id_type
|
||||||
|
addTimer(EventLoopData *eld, const char *name, double interval, int enabled, timer_callback_func cb, void *cb_data) {
|
||||||
|
if (eld->timers_count >= sizeof(eld->timers)/sizeof(eld->timers[0])) {
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR, "Too many timers added");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
Timer *t = eld->timers + eld->timers_count++;
|
||||||
|
t->interval = interval;
|
||||||
|
t->name = name;
|
||||||
|
t->trigger_at = enabled ? monotonic() + interval : DBL_MAX;
|
||||||
|
t->callback = cb;
|
||||||
|
t->callback_data = cb_data;
|
||||||
|
t->id = ++timer_counter;
|
||||||
|
update_timers(eld);
|
||||||
|
return t->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
removeTimer(EventLoopData *eld, id_type timer_id) {
|
||||||
|
removeX(timers, timer_id, update_timers);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
toggleTimer(EventLoopData *eld, id_type timer_id, int enabled) {
|
||||||
|
for (nfds_t i = 0; i < eld->timers_count; i++) {
|
||||||
|
if (eld->timers[i].id == timer_id) {
|
||||||
|
double trigger_at = enabled ? (monotonic() + eld->timers[i].interval) : DBL_MAX;
|
||||||
|
if (trigger_at != eld->timers[i].trigger_at) {
|
||||||
|
eld->timers[i].trigger_at = trigger_at;
|
||||||
|
update_timers(eld);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
changeTimerInterval(EventLoopData *eld, id_type timer_id, double interval) {
|
||||||
|
for (nfds_t i = 0; i < eld->timers_count; i++) {
|
||||||
|
if (eld->timers[i].id == timer_id) {
|
||||||
|
eld->timers[i].interval = interval;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
double
|
||||||
|
prepareForPoll(EventLoopData *eld, double timeout) {
|
||||||
|
for (nfds_t i = 0; i < eld->watches_count; i++) eld->fds[i].revents = 0;
|
||||||
|
if (!eld->timers_count || eld->timers[0].trigger_at == DBL_MAX) return timeout;
|
||||||
|
double now = monotonic(), next_repeat_at = eld->timers[0].trigger_at;
|
||||||
|
if (timeout < 0 || now + timeout > next_repeat_at) {
|
||||||
|
timeout = next_repeat_at <= now ? 0 : next_repeat_at - now;
|
||||||
|
}
|
||||||
|
return timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
pollWithTimeout(struct pollfd *fds, nfds_t nfds, double timeout) {
|
||||||
|
const long seconds = (long) timeout;
|
||||||
|
const long nanoseconds = (long) ((timeout - seconds) * 1e9);
|
||||||
|
struct timespec tv = { seconds, nanoseconds };
|
||||||
|
return ppoll(fds, nfds, &tv, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
dispatchEvents(EventLoopData *eld) {
|
||||||
|
for (nfds_t i = 0; i < eld->watches_count; i++) {
|
||||||
|
Watch *ww = eld->watches + i;
|
||||||
|
struct pollfd *pfd = eld->fds + i;
|
||||||
|
if (pfd->revents & ww->events) {
|
||||||
|
ww->ready = 1;
|
||||||
|
if (ww->callback) ww->callback(ww->fd, pfd->revents, ww->callback_data);
|
||||||
|
} else ww->ready = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned
|
||||||
|
dispatchTimers(EventLoopData *eld) {
|
||||||
|
if (!eld->timers_count || eld->timers[0].trigger_at == DBL_MAX) return 0;
|
||||||
|
static struct { timer_callback_func func; id_type id; void* data; } dispatches[sizeof(eld->timers)/sizeof(eld->timers[0])];
|
||||||
|
unsigned num_dispatches = 0;
|
||||||
|
double now = monotonic();
|
||||||
|
for (nfds_t i = 0; i < eld->timers_count && eld->timers[i].trigger_at <= now; i++) {
|
||||||
|
eld->timers[i].trigger_at = now + eld->timers[i].interval;
|
||||||
|
dispatches[num_dispatches].func = eld->timers[i].callback;
|
||||||
|
dispatches[num_dispatches].id = eld->timers[i].id;
|
||||||
|
dispatches[num_dispatches].data = eld->timers[i].callback_data;
|
||||||
|
num_dispatches++;
|
||||||
|
}
|
||||||
|
// we dispatch separately so that the callbacks can modify timers
|
||||||
|
for (unsigned i = 0; i < num_dispatches; i++) {
|
||||||
|
dispatches[i].func(dispatches[i].id, dispatches[i].data);
|
||||||
|
}
|
||||||
|
if (num_dispatches) update_timers(eld);
|
||||||
|
return num_dispatches;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
drain_wakeup_fd(int fd, int events, void* data) {
|
||||||
|
static char drain_buf[64];
|
||||||
|
while(read(fd, drain_buf, sizeof(drain_buf)) < 0 && errno == EINTR);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
initPollData(EventLoopData *eld, int wakeup_fd, int display_fd) {
|
||||||
|
addWatch(eld, "display", display_fd, POLLIN, 1, NULL, NULL);
|
||||||
|
addWatch(eld, "wakeup", wakeup_fd, POLLIN, 1, drain_wakeup_fd, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int
|
||||||
|
pollForEvents(EventLoopData *eld, double timeout) {
|
||||||
|
int read_ok = 0;
|
||||||
|
timeout = prepareForPoll(eld, timeout);
|
||||||
|
int result;
|
||||||
|
double end_time = monotonic() + timeout;
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
if (timeout >= 0) {
|
||||||
|
errno = 0;
|
||||||
|
result = pollWithTimeout(eld->fds, eld->watches_count, timeout);
|
||||||
|
int saved_errno = errno;
|
||||||
|
dispatchTimers(eld);
|
||||||
|
if (result > 0) {
|
||||||
|
dispatchEvents(eld);
|
||||||
|
read_ok = eld->watches[0].ready;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
timeout = end_time - monotonic();
|
||||||
|
if (timeout <= 0) break;
|
||||||
|
if (result < 0 && (saved_errno == EINTR || saved_errno == EAGAIN)) continue;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
errno = 0;
|
||||||
|
result = poll(eld->fds, eld->watches_count, -1);
|
||||||
|
int saved_errno = errno;
|
||||||
|
dispatchTimers(eld);
|
||||||
|
if (result > 0) {
|
||||||
|
dispatchEvents(eld);
|
||||||
|
read_ok = eld->watches[0].ready;
|
||||||
|
}
|
||||||
|
if (result < 0 && (saved_errno == EINTR || saved_errno == EAGAIN)) continue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return read_ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
closeFds(int *fds, size_t count) {
|
||||||
|
while(count--) {
|
||||||
|
if (*fds > 0) {
|
||||||
|
close(*fds);
|
||||||
|
*fds = -1;
|
||||||
|
}
|
||||||
|
fds++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Splits and translates a text/uri-list into separate file paths
|
||||||
|
// NOTE: This function destroys the provided string
|
||||||
|
//
|
||||||
|
char** parseUriList(char* text, int* count)
|
||||||
|
{
|
||||||
|
const char* prefix = "file://";
|
||||||
|
char** paths = NULL;
|
||||||
|
char* line;
|
||||||
|
|
||||||
|
*count = 0;
|
||||||
|
|
||||||
|
while ((line = strtok(text, "\r\n")))
|
||||||
|
{
|
||||||
|
text = NULL;
|
||||||
|
|
||||||
|
if (line[0] == '#')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (strncmp(line, prefix, strlen(prefix)) == 0)
|
||||||
|
{
|
||||||
|
line += strlen(prefix);
|
||||||
|
// TODO: Validate hostname
|
||||||
|
while (*line != '/')
|
||||||
|
line++;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*count)++;
|
||||||
|
|
||||||
|
char* path = calloc(strlen(line) + 1, 1);
|
||||||
|
paths = realloc(paths, *count * sizeof(char*));
|
||||||
|
paths[*count - 1] = path;
|
||||||
|
|
||||||
|
while (*line)
|
||||||
|
{
|
||||||
|
if (line[0] == '%' && line[1] && line[2])
|
||||||
|
{
|
||||||
|
const char digits[3] = { line[1], line[2], '\0' };
|
||||||
|
*path = strtol(digits, NULL, 16);
|
||||||
|
line += 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
*path = *line;
|
||||||
|
|
||||||
|
path++;
|
||||||
|
line++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
72
glfw/backend_utils.h
vendored
72
glfw/backend_utils.h
vendored
@@ -25,42 +25,50 @@
|
|||||||
//========================================================================
|
//========================================================================
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <unistd.h>
|
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
#include <errno.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#ifdef __NetBSD__
|
typedef unsigned long long id_type;
|
||||||
#define ppoll pollts
|
typedef void(*watch_callback_func)(int, int, void*);
|
||||||
#endif
|
typedef void(*timer_callback_func)(id_type, void*);
|
||||||
|
|
||||||
static inline void
|
typedef struct {
|
||||||
drainFd(int fd) {
|
int fd, events, enabled, ready;
|
||||||
static char drain_buf[64];
|
watch_callback_func callback;
|
||||||
while(read(fd, drain_buf, sizeof(drain_buf)) < 0 && errno == EINTR);
|
void *callback_data;
|
||||||
}
|
id_type id;
|
||||||
|
const char *name;
|
||||||
|
} Watch;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
id_type id;
|
||||||
|
double interval, trigger_at;
|
||||||
|
timer_callback_func callback;
|
||||||
|
void *callback_data;
|
||||||
|
const char *name;
|
||||||
|
} Timer;
|
||||||
|
|
||||||
|
|
||||||
static inline int
|
typedef struct {
|
||||||
pollWithTimeout(struct pollfd *fds, nfds_t nfds, double timeout) {
|
struct pollfd fds[32];
|
||||||
const long seconds = (long) timeout;
|
int wakeupFds[2];
|
||||||
const long nanoseconds = (long) ((timeout - seconds) * 1e9);
|
nfds_t watches_count, timers_count;
|
||||||
struct timespec tv = { seconds, nanoseconds };
|
Watch watches[32];
|
||||||
return ppoll(fds, nfds, &tv, NULL);
|
Timer timers[128];
|
||||||
}
|
} EventLoopData;
|
||||||
|
|
||||||
static inline void
|
|
||||||
initPollData(struct pollfd *fds, int wakeup_fd, int display_fd) {
|
|
||||||
fds[0].fd = wakeup_fd; fds[1].fd = display_fd;
|
|
||||||
fds[0].events = POLLIN; fds[1].events = POLLIN;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void
|
id_type addWatch(EventLoopData *eld, const char *name, int fd, int events, int enabled, watch_callback_func cb, void *cb_data);
|
||||||
closeFds(int *fds, size_t count) {
|
void removeWatch(EventLoopData *eld, id_type watch_id);
|
||||||
while(count--) {
|
void toggleWatch(EventLoopData *eld, id_type watch_id, int enabled);
|
||||||
if (*fds > 0) {
|
id_type addTimer(EventLoopData *eld, const char *name, double interval, int enabled, timer_callback_func cb, void *cb_data);
|
||||||
close(*fds);
|
void removeTimer(EventLoopData *eld, id_type timer_id);
|
||||||
*fds = -1;
|
void toggleTimer(EventLoopData *eld, id_type timer_id, int enabled);
|
||||||
}
|
void changeTimerInterval(EventLoopData *eld, id_type timer_id, double interval);
|
||||||
fds++;
|
double prepareForPoll(EventLoopData *eld, double timeout);
|
||||||
}
|
int pollWithTimeout(struct pollfd *fds, nfds_t nfds, double timeout);
|
||||||
}
|
int pollForEvents(EventLoopData *eld, double timeout);
|
||||||
|
unsigned dispatchTimers(EventLoopData *eld);
|
||||||
|
void closeFds(int *fds, size_t count);
|
||||||
|
void initPollData(EventLoopData *eld, int wakeup_fd, int display_fd);
|
||||||
|
char** parseUriList(char* text, int* count);
|
||||||
|
|||||||
@@ -26,7 +26,11 @@
|
|||||||
|
|
||||||
#include "internal.h"
|
#include "internal.h"
|
||||||
#include <sys/param.h> // For MAXPATHLEN
|
#include <sys/param.h> // For MAXPATHLEN
|
||||||
|
#if MAC_OS_X_VERSION_MAX_ALLOWED < 101200
|
||||||
|
#define NSEventMaskKeyUp NSKeyUpMask
|
||||||
|
#define NSEventMaskKeyDown NSKeyDownMask
|
||||||
|
#define NSEventModifierFlagCommand NSCommandKeyMask
|
||||||
|
#endif
|
||||||
|
|
||||||
// Change to our application bundle's resources directory, if present
|
// Change to our application bundle's resources directory, if present
|
||||||
//
|
//
|
||||||
@@ -274,33 +278,83 @@ static GLFWbool initializeTIS(void)
|
|||||||
return updateUnicodeDataNS();
|
return updateUnicodeDataNS();
|
||||||
}
|
}
|
||||||
|
|
||||||
@interface GLFWLayoutListener : NSObject
|
@interface GLFWHelper : NSObject
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation GLFWLayoutListener
|
@implementation GLFWHelper
|
||||||
|
|
||||||
- (void)selectedKeyboardInputSourceChanged:(NSObject* )object
|
- (void)selectedKeyboardInputSourceChanged:(NSObject* )object
|
||||||
{
|
{
|
||||||
updateUnicodeDataNS();
|
updateUnicodeDataNS();
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
- (void)doNothing:(id)object
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@end // GLFWHelper
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
////// GLFW platform API //////
|
////// GLFW platform API //////
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
is_ctrl_tab(NSEvent *event) {
|
||||||
|
NSEventModifierFlags modifierFlags = [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
|
||||||
|
return event.keyCode == kVK_Tab && (modifierFlags == NSEventModifierFlagControl || modifierFlags == (
|
||||||
|
NSEventModifierFlagControl | NSEventModifierFlagShift));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int _glfwPlatformInit(void)
|
int _glfwPlatformInit(void)
|
||||||
{
|
{
|
||||||
_glfw.ns.autoreleasePool = [[NSAutoreleasePool alloc] init];
|
_glfw.ns.autoreleasePool = [[NSAutoreleasePool alloc] init];
|
||||||
|
_glfw.ns.helper = [[GLFWHelper alloc] init];
|
||||||
|
|
||||||
|
[NSThread detachNewThreadSelector:@selector(doNothing:)
|
||||||
|
toTarget:_glfw.ns.helper
|
||||||
|
withObject:nil];
|
||||||
|
|
||||||
|
[NSApplication sharedApplication];
|
||||||
|
|
||||||
|
NSEvent* (^keydown_block)(NSEvent*) = ^ NSEvent* (NSEvent* event)
|
||||||
|
{
|
||||||
|
if (is_ctrl_tab(event)) {
|
||||||
|
// Cocoa swallows Ctrl+Tab to cycle between views
|
||||||
|
[[NSApp keyWindow].contentView keyDown:event];
|
||||||
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
|
};
|
||||||
|
|
||||||
|
NSEvent* (^keyup_block)(NSEvent*) = ^ NSEvent* (NSEvent* event)
|
||||||
|
{
|
||||||
|
if ([event modifierFlags] & NSEventModifierFlagCommand) {
|
||||||
|
// From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost
|
||||||
|
// This works around an AppKit bug, where key up events while holding
|
||||||
|
// down the command key don't get sent to the key window.
|
||||||
|
[[NSApp keyWindow] sendEvent:event];
|
||||||
|
}
|
||||||
|
if (is_ctrl_tab(event)) {
|
||||||
|
// Cocoa swallows Ctrl+Tab to cycle between views
|
||||||
|
[[NSApp keyWindow].contentView keyUp:event];
|
||||||
|
}
|
||||||
|
|
||||||
|
return event;
|
||||||
|
};
|
||||||
|
|
||||||
|
_glfw.ns.keyUpMonitor =
|
||||||
|
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp
|
||||||
|
handler:keyup_block];
|
||||||
|
_glfw.ns.keyDownMonitor =
|
||||||
|
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyDown
|
||||||
|
handler:keydown_block];
|
||||||
if (_glfw.hints.init.ns.chdir)
|
if (_glfw.hints.init.ns.chdir)
|
||||||
changeToResourcesDirectory();
|
changeToResourcesDirectory();
|
||||||
|
|
||||||
_glfw.ns.listener = [[GLFWLayoutListener alloc] init];
|
|
||||||
[[NSNotificationCenter defaultCenter]
|
[[NSNotificationCenter defaultCenter]
|
||||||
addObserver:_glfw.ns.listener
|
addObserver:_glfw.ns.helper
|
||||||
selector:@selector(selectedKeyboardInputSourceChanged:)
|
selector:@selector(selectedKeyboardInputSourceChanged:)
|
||||||
name:NSTextInputContextKeyboardSelectionDidChangeNotification
|
name:NSTextInputContextKeyboardSelectionDidChangeNotification
|
||||||
object:nil];
|
object:nil];
|
||||||
@@ -345,17 +399,21 @@ void _glfwPlatformTerminate(void)
|
|||||||
_glfw.ns.delegate = nil;
|
_glfw.ns.delegate = nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_glfw.ns.listener)
|
if (_glfw.ns.helper)
|
||||||
{
|
{
|
||||||
[[NSNotificationCenter defaultCenter]
|
[[NSNotificationCenter defaultCenter]
|
||||||
removeObserver:_glfw.ns.listener
|
removeObserver:_glfw.ns.helper
|
||||||
name:NSTextInputContextKeyboardSelectionDidChangeNotification
|
name:NSTextInputContextKeyboardSelectionDidChangeNotification
|
||||||
object:nil];
|
object:nil];
|
||||||
[[NSNotificationCenter defaultCenter]
|
[[NSNotificationCenter defaultCenter]
|
||||||
removeObserver:_glfw.ns.listener];
|
removeObserver:_glfw.ns.helper];
|
||||||
[_glfw.ns.listener release];
|
[_glfw.ns.helper release];
|
||||||
_glfw.ns.listener = nil;
|
_glfw.ns.helper = nil;
|
||||||
}
|
}
|
||||||
|
if (_glfw.ns.keyUpMonitor)
|
||||||
|
[NSEvent removeMonitor:_glfw.ns.keyUpMonitor];
|
||||||
|
if (_glfw.ns.keyDownMonitor)
|
||||||
|
[NSEvent removeMonitor:_glfw.ns.keyDownMonitor];
|
||||||
|
|
||||||
free(_glfw.ns.clipboardString);
|
free(_glfw.ns.clipboardString);
|
||||||
|
|
||||||
|
|||||||
@@ -467,7 +467,7 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode *mode)
|
|||||||
CVDisplayLinkRelease(link);
|
CVDisplayLinkRelease(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||||
{
|
{
|
||||||
uint32_t i, size = CGDisplayGammaTableCapacity(monitor->ns.displayID);
|
uint32_t i, size = CGDisplayGammaTableCapacity(monitor->ns.displayID);
|
||||||
CGGammaValue* values = calloc(size * 3, sizeof(CGGammaValue));
|
CGGammaValue* values = calloc(size * 3, sizeof(CGGammaValue));
|
||||||
@@ -489,6 +489,7 @@ void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
|||||||
}
|
}
|
||||||
|
|
||||||
free(values);
|
free(values);
|
||||||
|
return GLFW_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
|
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
|
||||||
@@ -523,4 +524,3 @@ GLFWAPI CGDirectDisplayID glfwGetCocoaMonitor(GLFWmonitor* handle)
|
|||||||
_GLFW_REQUIRE_INIT_OR_RETURN(kCGNullDirectDisplay);
|
_GLFW_REQUIRE_INIT_OR_RETURN(kCGNullDirectDisplay);
|
||||||
return monitor->ns.displayID;
|
return monitor->ns.displayID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
11
glfw/cocoa_platform.h
vendored
11
glfw/cocoa_platform.h
vendored
@@ -27,18 +27,17 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
|
|
||||||
|
#include <Carbon/Carbon.h>
|
||||||
#if defined(__OBJC__)
|
#if defined(__OBJC__)
|
||||||
#import <Carbon/Carbon.h>
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#else
|
#else
|
||||||
#include <Carbon/Carbon.h>
|
|
||||||
#include <ApplicationServices/ApplicationServices.h>
|
|
||||||
typedef void* id;
|
typedef void* id;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef VkFlags VkMacOSSurfaceCreateFlagsMVK;
|
typedef VkFlags VkMacOSSurfaceCreateFlagsMVK;
|
||||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,int);
|
typedef int (* GLFWcocoatextinputfilterfun)(int,int,int);
|
||||||
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
|
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||||
|
typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
|
||||||
|
|
||||||
typedef struct VkMacOSSurfaceCreateInfoMVK
|
typedef struct VkMacOSSurfaceCreateInfoMVK
|
||||||
{
|
{
|
||||||
@@ -102,6 +101,8 @@ typedef struct _GLFWwindowNS
|
|||||||
|
|
||||||
// The text input filter callback
|
// The text input filter callback
|
||||||
GLFWcocoatextinputfilterfun textInputFilterCallback;
|
GLFWcocoatextinputfilterfun textInputFilterCallback;
|
||||||
|
// The toggle fullscreen intercept callback
|
||||||
|
GLFWcocoatogglefullscreenfun toggleFullscreenCallback;
|
||||||
// Dead key state
|
// Dead key state
|
||||||
UInt32 deadKeyState;
|
UInt32 deadKeyState;
|
||||||
} _GLFWwindowNS;
|
} _GLFWwindowNS;
|
||||||
@@ -117,7 +118,9 @@ typedef struct _GLFWlibraryNS
|
|||||||
TISInputSourceRef inputSource;
|
TISInputSourceRef inputSource;
|
||||||
IOHIDManagerRef hidManager;
|
IOHIDManagerRef hidManager;
|
||||||
id unicodeData;
|
id unicodeData;
|
||||||
id listener;
|
id helper;
|
||||||
|
id keyUpMonitor;
|
||||||
|
id keyDownMonitor;
|
||||||
|
|
||||||
char keyName[64];
|
char keyName[64];
|
||||||
char text[256];
|
char text[256];
|
||||||
|
|||||||
@@ -47,9 +47,13 @@
|
|||||||
#define NSEventModifierFlagDeviceIndependentFlagsMask NSDeviceIndependentModifierFlagsMask
|
#define NSEventModifierFlagDeviceIndependentFlagsMask NSDeviceIndependentModifierFlagsMask
|
||||||
#define NSEventMaskAny NSAnyEventMask
|
#define NSEventMaskAny NSAnyEventMask
|
||||||
#define NSEventTypeApplicationDefined NSApplicationDefined
|
#define NSEventTypeApplicationDefined NSApplicationDefined
|
||||||
#define NSEventTypeKeyUp NSKeyUp
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101400)
|
||||||
|
#define NSPasteboardTypeFileURL NSFilenamesPboardType
|
||||||
|
#define NSBitmapFormatAlphaNonpremultiplied NSAlphaNonpremultipliedBitmapFormat
|
||||||
|
#define NSPasteboardTypeString NSStringPboardType
|
||||||
|
#endif
|
||||||
|
|
||||||
// Returns the style mask corresponding to the window settings
|
// Returns the style mask corresponding to the window settings
|
||||||
//
|
//
|
||||||
@@ -307,6 +311,7 @@ static int translateKey(unsigned int key, GLFWbool apply_keymap)
|
|||||||
K(';', SEMICOLON);
|
K(';', SEMICOLON);
|
||||||
K('[', LEFT_BRACKET);
|
K('[', LEFT_BRACKET);
|
||||||
K(']', RIGHT_BRACKET);
|
K(']', RIGHT_BRACKET);
|
||||||
|
K('+', PLUS);
|
||||||
K('`', GRAVE_ACCENT);
|
K('`', GRAVE_ACCENT);
|
||||||
K('\\', BACKSLASH);
|
K('\\', BACKSLASH);
|
||||||
#undef K
|
#undef K
|
||||||
@@ -339,6 +344,8 @@ static NSUInteger translateKeyToModifierFlag(int key)
|
|||||||
case GLFW_KEY_LEFT_SUPER:
|
case GLFW_KEY_LEFT_SUPER:
|
||||||
case GLFW_KEY_RIGHT_SUPER:
|
case GLFW_KEY_RIGHT_SUPER:
|
||||||
return NSEventModifierFlagCommand;
|
return NSEventModifierFlagCommand;
|
||||||
|
case GLFW_KEY_CAPS_LOCK:
|
||||||
|
return NSEventModifierFlagCapsLock;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -450,6 +457,12 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
|||||||
|
|
||||||
_glfwInputWindowFocus(window, GLFW_TRUE);
|
_glfwInputWindowFocus(window, GLFW_TRUE);
|
||||||
updateCursorMode(window);
|
updateCursorMode(window);
|
||||||
|
if (_glfw.ns.disabledCursorWindow != window && cursorInClientArea(window))
|
||||||
|
{
|
||||||
|
double x = 0, y = 0;
|
||||||
|
_glfwPlatformGetCursorPos(window, &x, &y);
|
||||||
|
_glfwInputCursorPos(window, x, y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)windowDidResignKey:(NSNotification *)notification
|
- (void)windowDidResignKey:(NSNotification *)notification
|
||||||
@@ -549,8 +562,7 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
|
|||||||
markedText = [[NSMutableAttributedString alloc] init];
|
markedText = [[NSMutableAttributedString alloc] init];
|
||||||
|
|
||||||
[self updateTrackingAreas];
|
[self updateTrackingAreas];
|
||||||
[self registerForDraggedTypes:[NSArray arrayWithObjects:
|
[self registerForDraggedTypes:@[NSPasteboardTypeFileURL]];
|
||||||
NSFilenamesPboardType, nil]];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
@@ -563,6 +575,10 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
|
|||||||
[super dealloc];
|
[super dealloc];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (_GLFWwindow*)glfwWindow {
|
||||||
|
return window;
|
||||||
|
}
|
||||||
|
|
||||||
- (BOOL)isOpaque
|
- (BOOL)isOpaque
|
||||||
{
|
{
|
||||||
return [window->ns.object isOpaque];
|
return [window->ns.object isOpaque];
|
||||||
@@ -583,6 +599,21 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
|
|||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)updateLayer
|
||||||
|
{
|
||||||
|
if (window->context.client != GLFW_NO_API) {
|
||||||
|
@try {
|
||||||
|
[window->context.nsgl.object update];
|
||||||
|
} @catch (NSException *e) {
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||||
|
"Failed to update NSGL Context object with error: %s (%s)",
|
||||||
|
[[e name] UTF8String], [[e reason] UTF8String]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_glfwInputWindowDamage(window);
|
||||||
|
}
|
||||||
|
|
||||||
- (id)makeBackingLayer
|
- (id)makeBackingLayer
|
||||||
{
|
{
|
||||||
if (window->ns.layer)
|
if (window->ns.layer)
|
||||||
@@ -596,6 +627,11 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
|
|||||||
updateCursorImage(window);
|
updateCursorImage(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (BOOL)acceptsFirstMouse:(NSEvent *)event
|
||||||
|
{
|
||||||
|
return NO; // changed by Kovid, to follow cocoa platform conventions
|
||||||
|
}
|
||||||
|
|
||||||
- (void)mouseDown:(NSEvent *)event
|
- (void)mouseDown:(NSEvent *)event
|
||||||
{
|
{
|
||||||
_glfwInputMouseClick(window,
|
_glfwInputMouseClick(window,
|
||||||
@@ -739,7 +775,7 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
|
|||||||
}
|
}
|
||||||
|
|
||||||
const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited |
|
const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited |
|
||||||
NSTrackingActiveInKeyWindow |
|
NSTrackingActiveAlways |
|
||||||
NSTrackingEnabledDuringMouseDrag |
|
NSTrackingEnabledDuringMouseDrag |
|
||||||
NSTrackingCursorUpdate |
|
NSTrackingCursorUpdate |
|
||||||
NSTrackingInVisibleRect |
|
NSTrackingInVisibleRect |
|
||||||
@@ -824,28 +860,22 @@ is_ascii_control_char(char x) {
|
|||||||
window->ns.deadKeyState = 0;
|
window->ns.deadKeyState = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
debug_key(@"scancode: 0x%x (%s) %schar_count: %lu deadKeyState: %u ", scancode, safe_name_for_scancode(scancode), format_mods(mods), char_count, window->ns.deadKeyState);
|
||||||
if (process_text) {
|
if (process_text) {
|
||||||
// We check if cocoa wants to insert text, as UCKeyTranslate
|
// this will call insertText which will fill up _glfw.ns.text
|
||||||
// inserts text even when the cmd key is pressed. For instance,
|
|
||||||
// cmd+a will result in the text a.
|
|
||||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||||
debug_key(@"char_count: %lu cocoa text: %s\n", char_count, format_text(_glfw.ns.text));
|
|
||||||
GLFWbool cocoa_wants_to_insert_text = !is_ascii_control_char(_glfw.ns.text[0]);
|
|
||||||
_glfw.ns.text[0] = 0;
|
|
||||||
if (char_count && cocoa_wants_to_insert_text) convert_utf16_to_utf8(text, char_count, _glfw.ns.text, sizeof(_glfw.ns.text));
|
|
||||||
} else {
|
} else {
|
||||||
window->ns.deadKeyState = 0;
|
window->ns.deadKeyState = 0;
|
||||||
}
|
}
|
||||||
if (window->ns.deadKeyState && char_count == 0) {
|
if (window->ns.deadKeyState && (char_count == 0 || scancode == 0x75)) {
|
||||||
debug_key(@"Ignoring dead key. deadKeyState: 0x%x scancode: 0x%x (%s) %s\n",
|
// 0x75 is the delete key which needs to be ignored during a compose sequence
|
||||||
window->ns.deadKeyState, scancode, safe_name_for_scancode(scancode), format_mods(mods));
|
debug_key(@"Ignoring dead key.\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
debug_key(@"scancode: 0x%x (%s) %stext: %s glfw_key: %s\n",
|
if (is_ascii_control_char(_glfw.ns.text[0])) _glfw.ns.text[0] = 0; // don't send text for ascii control codes
|
||||||
scancode, safe_name_for_scancode(scancode), format_mods(mods),
|
debug_key(@"text: %s glfw_key: %s\n",
|
||||||
format_text(_glfw.ns.text), _glfwGetKeyName(key));
|
format_text(_glfw.ns.text), _glfwGetKeyName(key));
|
||||||
if (is_ascii_control_char(_glfw.ns.text[0])) _glfw.ns.text[0] = 0; // dont send text for ascii control codes
|
|
||||||
_glfwInputKeyboard(window, key, scancode, GLFW_PRESS, mods, _glfw.ns.text, 0);
|
_glfwInputKeyboard(window, key, scancode, GLFW_PRESS, mods, _glfw.ns.text, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -884,58 +914,65 @@ is_ascii_control_char(char x) {
|
|||||||
|
|
||||||
deltaX = [event scrollingDeltaX];
|
deltaX = [event scrollingDeltaX];
|
||||||
deltaY = [event scrollingDeltaY];
|
deltaY = [event scrollingDeltaY];
|
||||||
|
int flags = [event hasPreciseScrollingDeltas] ? 1 : 0;
|
||||||
if ([event hasPreciseScrollingDeltas])
|
if (flags) {
|
||||||
{
|
float xscale = 1, yscale = 1;
|
||||||
deltaX *= 0.1;
|
_glfwPlatformGetWindowContentScale(window, &xscale, &yscale);
|
||||||
deltaY *= 0.1;
|
if (xscale > 0) deltaX *= xscale;
|
||||||
|
if (yscale > 0) deltaY *= yscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fabs(deltaX) > 0.0 || fabs(deltaY) > 0.0)
|
switch([event momentumPhase]) {
|
||||||
_glfwInputScroll(window, deltaX, deltaY);
|
case NSEventPhaseBegan:
|
||||||
|
flags |= (1 << 1); break;
|
||||||
|
case NSEventPhaseStationary:
|
||||||
|
flags |= (2 << 1); break;
|
||||||
|
case NSEventPhaseChanged:
|
||||||
|
flags |= (3 << 1); break;
|
||||||
|
case NSEventPhaseEnded:
|
||||||
|
flags |= (4 << 1); break;
|
||||||
|
case NSEventPhaseCancelled:
|
||||||
|
flags |= (5 << 1); break;
|
||||||
|
case NSEventPhaseMayBegin:
|
||||||
|
flags |= (6 << 1); break;
|
||||||
|
case NSEventPhaseNone:
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
_glfwInputScroll(window, deltaX, deltaY, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
|
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
|
||||||
{
|
{
|
||||||
if ((NSDragOperationGeneric & [sender draggingSourceOperationMask])
|
// HACK: We don't know what to say here because we don't know what the
|
||||||
== NSDragOperationGeneric)
|
// application wants to do with the paths
|
||||||
{
|
return NSDragOperationGeneric;
|
||||||
[self setNeedsDisplay:YES];
|
|
||||||
return NSDragOperationGeneric;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NSDragOperationNone;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
|
|
||||||
{
|
|
||||||
[self setNeedsDisplay:YES];
|
|
||||||
return YES;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
|
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
|
||||||
{
|
{
|
||||||
NSPasteboard* pasteboard = [sender draggingPasteboard];
|
|
||||||
NSArray* files = [pasteboard propertyListForType:NSFilenamesPboardType];
|
|
||||||
|
|
||||||
const NSRect contentRect = [window->ns.view frame];
|
const NSRect contentRect = [window->ns.view frame];
|
||||||
_glfwInputCursorPos(window,
|
_glfwInputCursorPos(window,
|
||||||
[sender draggingLocation].x,
|
[sender draggingLocation].x,
|
||||||
contentRect.size.height - [sender draggingLocation].y);
|
contentRect.size.height - [sender draggingLocation].y);
|
||||||
|
NSPasteboard* pasteboard = [sender draggingPasteboard];
|
||||||
|
NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES};
|
||||||
|
NSArray* urls = [pasteboard readObjectsForClasses:@[[NSURL class]]
|
||||||
|
options:options];
|
||||||
|
if (!urls) return NO;
|
||||||
|
const NSUInteger count = [urls count];
|
||||||
|
|
||||||
const NSUInteger count = [files count];
|
|
||||||
if (count)
|
if (count)
|
||||||
{
|
{
|
||||||
NSEnumerator* e = [files objectEnumerator];
|
|
||||||
char** paths = calloc(count, sizeof(char*));
|
char** paths = calloc(count, sizeof(char*));
|
||||||
NSUInteger i;
|
|
||||||
|
|
||||||
for (i = 0; i < count; i++)
|
for (NSUInteger i = 0; i < count; i++)
|
||||||
paths[i] = _glfw_strdup([[e nextObject] UTF8String]);
|
paths[i] = _glfw_strdup([[urls objectAtIndex:i] fileSystemRepresentation]);
|
||||||
|
|
||||||
_glfwInputDrop(window, (int) count, (const char**) paths);
|
_glfwInputDrop(window, (int) count, (const char**) paths);
|
||||||
|
|
||||||
for (i = 0; i < count; i++)
|
for (NSUInteger i = 0; i < count; i++)
|
||||||
free(paths[i]);
|
free(paths[i]);
|
||||||
free(paths);
|
free(paths);
|
||||||
}
|
}
|
||||||
@@ -943,11 +980,6 @@ is_ascii_control_char(char x) {
|
|||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)concludeDragOperation:(id <NSDraggingInfo>)sender
|
|
||||||
{
|
|
||||||
[self setNeedsDisplay:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)hasMarkedText
|
- (BOOL)hasMarkedText
|
||||||
{
|
{
|
||||||
return [markedText length] > 0;
|
return [markedText length] > 0;
|
||||||
@@ -1045,49 +1077,21 @@ is_ascii_control_char(char x) {
|
|||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
- (void)toggleFullScreen:(nullable id)sender
|
||||||
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------
|
|
||||||
// GLFW application class
|
|
||||||
//------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@interface GLFWApplication : NSApplication
|
|
||||||
{
|
{
|
||||||
NSArray* nibObjects;
|
GLFWContentView *view = [self contentView];
|
||||||
}
|
if (view)
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation GLFWApplication
|
|
||||||
|
|
||||||
// From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost
|
|
||||||
// This works around an AppKit bug, where key up events while holding
|
|
||||||
// down the command key don't get sent to the key window.
|
|
||||||
- (void)sendEvent:(NSEvent *)event
|
|
||||||
{
|
|
||||||
if ([event type] == NSEventTypeKeyUp &&
|
|
||||||
([event modifierFlags] & NSEventModifierFlagCommand))
|
|
||||||
{
|
{
|
||||||
[[self keyWindow] sendEvent:event];
|
_GLFWwindow *window = [view glfwWindow];
|
||||||
|
if (window && window->ns.toggleFullscreenCallback && window->ns.toggleFullscreenCallback((GLFWwindow*)window) == 1)
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else
|
[super toggleFullScreen:sender];
|
||||||
[super sendEvent:event];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// No-op thread entry point
|
|
||||||
//
|
|
||||||
- (void)doNothing:(id)object
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)loadMainMenu
|
|
||||||
{ // removed by Kovid as it generated compiler warnings
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
// Set up the menu bar (manually)
|
// Set up the menu bar (manually)
|
||||||
// This is nasty, nasty stuff -- calls to undocumented semi-private APIs that
|
// This is nasty, nasty stuff -- calls to undocumented semi-private APIs that
|
||||||
// could go away at any moment, lots of stuff that really should be
|
// could go away at any moment, lots of stuff that really should be
|
||||||
@@ -1197,32 +1201,9 @@ static void createMenuBar(void)
|
|||||||
//
|
//
|
||||||
static GLFWbool initializeAppKit(void)
|
static GLFWbool initializeAppKit(void)
|
||||||
{
|
{
|
||||||
if (NSApp)
|
if (_glfw.ns.delegate)
|
||||||
return GLFW_TRUE;
|
return GLFW_TRUE;
|
||||||
|
|
||||||
// Implicitly create shared NSApplication instance
|
|
||||||
[GLFWApplication sharedApplication];
|
|
||||||
|
|
||||||
// Make Cocoa enter multi-threaded mode
|
|
||||||
[NSThread detachNewThreadSelector:@selector(doNothing:)
|
|
||||||
toTarget:NSApp
|
|
||||||
withObject:nil];
|
|
||||||
|
|
||||||
if (_glfw.hints.init.ns.menubar)
|
|
||||||
{
|
|
||||||
// In case we are unbundled, make us a proper UI application
|
|
||||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
|
||||||
|
|
||||||
// Menu bar setup must go between sharedApplication above and
|
|
||||||
// finishLaunching below, in order to properly emulate the behavior
|
|
||||||
// of NSApplicationMain
|
|
||||||
|
|
||||||
if ([[NSBundle mainBundle] pathForResource:@"MainMenu" ofType:@"nib"])
|
|
||||||
[NSApp loadMainMenu];
|
|
||||||
else
|
|
||||||
createMenuBar();
|
|
||||||
}
|
|
||||||
|
|
||||||
// There can only be one application delegate, but we allocate it the
|
// There can only be one application delegate, but we allocate it the
|
||||||
// first time a window is created to keep all window code in this file
|
// first time a window is created to keep all window code in this file
|
||||||
_glfw.ns.delegate = [[GLFWApplicationDelegate alloc] init];
|
_glfw.ns.delegate = [[GLFWApplicationDelegate alloc] init];
|
||||||
@@ -1232,15 +1213,29 @@ static GLFWbool initializeAppKit(void)
|
|||||||
"Cocoa: Failed to create application delegate");
|
"Cocoa: Failed to create application delegate");
|
||||||
return GLFW_FALSE;
|
return GLFW_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
[NSApp setDelegate:_glfw.ns.delegate];
|
[NSApp setDelegate:_glfw.ns.delegate];
|
||||||
|
|
||||||
|
if (_glfw.hints.init.ns.menubar)
|
||||||
|
{
|
||||||
|
// In case we are unbundled, make us a proper UI application
|
||||||
|
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||||
|
|
||||||
|
// Menu bar setup must go between sharedApplication above and
|
||||||
|
// finishLaunching below, in order to properly emulate the behavior
|
||||||
|
// of NSApplicationMain
|
||||||
|
|
||||||
|
// disabled by Kovid
|
||||||
|
/* if ([[NSBundle mainBundle] pathForResource:@"MainMenu" ofType:@"nib"]) */
|
||||||
|
/* [NSApp loadMainMenu]; */
|
||||||
|
/* else */
|
||||||
|
createMenuBar();
|
||||||
|
}
|
||||||
|
|
||||||
[NSApp run];
|
[NSApp run];
|
||||||
|
|
||||||
// Press and Hold prevents some keys from emitting repeated characters
|
// Press and Hold prevents some keys from emitting repeated characters
|
||||||
NSDictionary* defaults =
|
NSDictionary* defaults = @{@"ApplePressAndHoldEnabled":@NO};
|
||||||
[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO],
|
|
||||||
@"ApplePressAndHoldEnabled",
|
|
||||||
nil];
|
|
||||||
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
|
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
|
||||||
|
|
||||||
return GLFW_TRUE;
|
return GLFW_TRUE;
|
||||||
@@ -1537,6 +1532,11 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
|
|||||||
*yscale = (float) (pixels.size.height / points.size.height);
|
*yscale = (float) (pixels.size.height / points.size.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window)
|
||||||
|
{
|
||||||
|
return [NSEvent doubleClickInterval];
|
||||||
|
}
|
||||||
|
|
||||||
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
|
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
|
||||||
{
|
{
|
||||||
[window->ns.object miniaturize:nil];
|
[window->ns.object miniaturize:nil];
|
||||||
@@ -1747,9 +1747,6 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity)
|
|||||||
|
|
||||||
void _glfwPlatformPollEvents(void)
|
void _glfwPlatformPollEvents(void)
|
||||||
{
|
{
|
||||||
if (!initializeAppKit())
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
|
NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
|
||||||
@@ -1759,7 +1756,7 @@ void _glfwPlatformPollEvents(void)
|
|||||||
if (event == nil)
|
if (event == nil)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
[NSApp sendEvent:event];
|
if ([event type] != NSEventTypeApplicationDefined) [NSApp sendEvent:event];
|
||||||
}
|
}
|
||||||
|
|
||||||
[_glfw.ns.autoreleasePool drain];
|
[_glfw.ns.autoreleasePool drain];
|
||||||
@@ -1775,7 +1772,7 @@ void _glfwPlatformWaitEvents(void)
|
|||||||
untilDate:[NSDate distantFuture]
|
untilDate:[NSDate distantFuture]
|
||||||
inMode:NSDefaultRunLoopMode
|
inMode:NSDefaultRunLoopMode
|
||||||
dequeue:YES];
|
dequeue:YES];
|
||||||
[NSApp sendEvent:event];
|
if ([event type] != NSEventTypeApplicationDefined) [NSApp sendEvent:event];
|
||||||
|
|
||||||
_glfwPlatformPollEvents();
|
_glfwPlatformPollEvents();
|
||||||
}
|
}
|
||||||
@@ -1787,8 +1784,7 @@ void _glfwPlatformWaitEventsTimeout(double timeout)
|
|||||||
untilDate:date
|
untilDate:date
|
||||||
inMode:NSDefaultRunLoopMode
|
inMode:NSDefaultRunLoopMode
|
||||||
dequeue:YES];
|
dequeue:YES];
|
||||||
if (event)
|
if (event && [event type] != NSEventTypeApplicationDefined) [NSApp sendEvent:event];
|
||||||
[NSApp sendEvent:event];
|
|
||||||
|
|
||||||
_glfwPlatformPollEvents();
|
_glfwPlatformPollEvents();
|
||||||
}
|
}
|
||||||
@@ -1886,51 +1882,47 @@ int _glfwPlatformGetKeyScancode(int key)
|
|||||||
|
|
||||||
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||||
const GLFWimage* image,
|
const GLFWimage* image,
|
||||||
int xhot, int yhot)
|
int xhot, int yhot, int count)
|
||||||
{
|
{
|
||||||
NSImage* native;
|
NSImage* native;
|
||||||
NSBitmapImageRep* rep;
|
NSBitmapImageRep* rep;
|
||||||
|
|
||||||
if (!initializeAppKit())
|
|
||||||
return GLFW_FALSE;
|
|
||||||
|
|
||||||
rep = [[NSBitmapImageRep alloc]
|
|
||||||
initWithBitmapDataPlanes:NULL
|
|
||||||
pixelsWide:image->width
|
|
||||||
pixelsHigh:image->height
|
|
||||||
bitsPerSample:8
|
|
||||||
samplesPerPixel:4
|
|
||||||
hasAlpha:YES
|
|
||||||
isPlanar:NO
|
|
||||||
colorSpaceName:NSCalibratedRGBColorSpace
|
|
||||||
bitmapFormat:NSAlphaNonpremultipliedBitmapFormat
|
|
||||||
bytesPerRow:image->width * 4
|
|
||||||
bitsPerPixel:32];
|
|
||||||
|
|
||||||
if (rep == nil)
|
|
||||||
return GLFW_FALSE;
|
|
||||||
|
|
||||||
memcpy([rep bitmapData], image->pixels, image->width * image->height * 4);
|
|
||||||
|
|
||||||
native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)];
|
native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)];
|
||||||
[native addRepresentation:rep];
|
if (native == nil)
|
||||||
|
return GLFW_FALSE;
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
const GLFWimage *src = image + i;
|
||||||
|
rep = [[NSBitmapImageRep alloc]
|
||||||
|
initWithBitmapDataPlanes:NULL
|
||||||
|
pixelsWide:src->width
|
||||||
|
pixelsHigh:src->height
|
||||||
|
bitsPerSample:8
|
||||||
|
samplesPerPixel:4
|
||||||
|
hasAlpha:YES
|
||||||
|
isPlanar:NO
|
||||||
|
colorSpaceName:NSCalibratedRGBColorSpace
|
||||||
|
bitmapFormat:NSBitmapFormatAlphaNonpremultiplied
|
||||||
|
bytesPerRow:src->width * 4
|
||||||
|
bitsPerPixel:32];
|
||||||
|
if (rep == nil)
|
||||||
|
return GLFW_FALSE;
|
||||||
|
|
||||||
|
memcpy([rep bitmapData], src->pixels, src->width * src->height * 4);
|
||||||
|
[native addRepresentation:rep];
|
||||||
|
[rep release];
|
||||||
|
}
|
||||||
|
|
||||||
cursor->ns.object = [[NSCursor alloc] initWithImage:native
|
cursor->ns.object = [[NSCursor alloc] initWithImage:native
|
||||||
hotSpot:NSMakePoint(xhot, yhot)];
|
hotSpot:NSMakePoint(xhot, yhot)];
|
||||||
|
|
||||||
[native release];
|
[native release];
|
||||||
[rep release];
|
|
||||||
|
|
||||||
if (cursor->ns.object == nil)
|
if (cursor->ns.object == nil)
|
||||||
return GLFW_FALSE;
|
return GLFW_FALSE;
|
||||||
|
|
||||||
return GLFW_TRUE;
|
return GLFW_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
|
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
|
||||||
{
|
{
|
||||||
if (!initializeAppKit())
|
|
||||||
return GLFW_FALSE;
|
|
||||||
|
|
||||||
if (shape == GLFW_ARROW_CURSOR)
|
if (shape == GLFW_ARROW_CURSOR)
|
||||||
cursor->ns.object = [NSCursor arrowCursor];
|
cursor->ns.object = [NSCursor arrowCursor];
|
||||||
@@ -1970,26 +1962,24 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
|
|||||||
|
|
||||||
void _glfwPlatformSetClipboardString(const char* string)
|
void _glfwPlatformSetClipboardString(const char* string)
|
||||||
{
|
{
|
||||||
NSArray* types = [NSArray arrayWithObjects:NSStringPboardType, nil];
|
|
||||||
|
|
||||||
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
||||||
[pasteboard declareTypes:types owner:nil];
|
[pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil];
|
||||||
[pasteboard setString:[NSString stringWithUTF8String:string]
|
[pasteboard setString:[NSString stringWithUTF8String:string]
|
||||||
forType:NSStringPboardType];
|
forType:NSPasteboardTypeString];
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* _glfwPlatformGetClipboardString(void)
|
const char* _glfwPlatformGetClipboardString(void)
|
||||||
{
|
{
|
||||||
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
||||||
|
|
||||||
if (![[pasteboard types] containsObject:NSStringPboardType])
|
if (![[pasteboard types] containsObject:NSPasteboardTypeString])
|
||||||
{
|
{
|
||||||
_glfwInputError(GLFW_FORMAT_UNAVAILABLE,
|
_glfwInputError(GLFW_FORMAT_UNAVAILABLE,
|
||||||
"Cocoa: Failed to retrieve string from pasteboard");
|
"Cocoa: Failed to retrieve string from pasteboard");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
NSString* object = [pasteboard stringForType:NSStringPboardType];
|
NSString* object = [pasteboard stringForType:NSPasteboardTypeString];
|
||||||
if (!object)
|
if (!object)
|
||||||
{
|
{
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||||
@@ -2098,6 +2088,14 @@ GLFWAPI GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow *hand
|
|||||||
return previous;
|
return previous;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GLFWAPI GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *handle, GLFWcocoatogglefullscreenfun callback) {
|
||||||
|
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||||
|
_GLFW_REQUIRE_INIT_OR_RETURN(nil);
|
||||||
|
GLFWcocoatogglefullscreenfun previous = window->ns.toggleFullscreenCallback;
|
||||||
|
window->ns.toggleFullscreenCallback = callback;
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
|
|
||||||
GLFWAPI GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback) {
|
GLFWAPI GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback) {
|
||||||
GLFWapplicationshouldhandlereopenfun previous = handle_reopen_callback;
|
GLFWapplicationshouldhandlereopenfun previous = handle_reopen_callback;
|
||||||
handle_reopen_callback = callback;
|
handle_reopen_callback = callback;
|
||||||
@@ -2165,6 +2163,7 @@ GLFWAPI void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, unsigned sho
|
|||||||
K(';', SEMICOLON);
|
K(';', SEMICOLON);
|
||||||
K('[', LEFT_BRACKET);
|
K('[', LEFT_BRACKET);
|
||||||
K(']', RIGHT_BRACKET);
|
K(']', RIGHT_BRACKET);
|
||||||
|
K('+', PLUS);
|
||||||
K('`', GRAVE_ACCENT);
|
K('`', GRAVE_ACCENT);
|
||||||
K('\\', BACKSLASH);
|
K('\\', BACKSLASH);
|
||||||
|
|
||||||
|
|||||||
295
glfw/dbus_glfw.c
vendored
Normal file
295
glfw/dbus_glfw.c
vendored
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
//========================================================================
|
||||||
|
// GLFW 3.3 XKB - www.glfw.org
|
||||||
|
//------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2018 Kovid Goyal <kovid@kovidgoyal.net>
|
||||||
|
//
|
||||||
|
// This software is provided 'as-is', without any express or implied
|
||||||
|
// warranty. In no event will the authors be held liable for any damages
|
||||||
|
// arising from the use of this software.
|
||||||
|
//
|
||||||
|
// Permission is granted to anyone to use this software for any purpose,
|
||||||
|
// including commercial applications, and to alter it and redistribute it
|
||||||
|
// freely, subject to the following restrictions:
|
||||||
|
//
|
||||||
|
// 1. The origin of this software must not be misrepresented; you must not
|
||||||
|
// claim that you wrote the original software. If you use this software
|
||||||
|
// in a product, an acknowledgment in the product documentation would
|
||||||
|
// be appreciated but is not required.
|
||||||
|
//
|
||||||
|
// 2. Altered source versions must be plainly marked as such, and must not
|
||||||
|
// be misrepresented as being the original software.
|
||||||
|
//
|
||||||
|
// 3. This notice may not be removed or altered from any source
|
||||||
|
// distribution.
|
||||||
|
//
|
||||||
|
//========================================================================
|
||||||
|
|
||||||
|
|
||||||
|
#include "internal.h"
|
||||||
|
#include "dbus_glfw.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
report_error(DBusError *err, const char *fmt, ...) {
|
||||||
|
static char buf[1024];
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
int n = vsnprintf(buf, sizeof(buf), fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
snprintf(buf + n, sizeof(buf), ". DBUS error: %s", err->message);
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR, "%s", buf);
|
||||||
|
dbus_error_free(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
static _GLFWDBUSData *dbus_data = NULL;
|
||||||
|
|
||||||
|
GLFWbool
|
||||||
|
glfw_dbus_init(_GLFWDBUSData *dbus, EventLoopData *eld) {
|
||||||
|
dbus->eld = eld;
|
||||||
|
dbus_data = dbus;
|
||||||
|
return GLFW_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_dbus_watch_ready(int fd, int events, void *data) {
|
||||||
|
DBusWatch *watch = (DBusWatch*)data;
|
||||||
|
unsigned int flags = 0;
|
||||||
|
if (events & POLLERR) flags |= DBUS_WATCH_ERROR;
|
||||||
|
if (events & POLLHUP) flags |= DBUS_WATCH_HANGUP;
|
||||||
|
if (events & POLLIN) flags |= DBUS_WATCH_READABLE;
|
||||||
|
if (events & POLLOUT) flags |= DBUS_WATCH_WRITABLE;
|
||||||
|
dbus_watch_handle(watch, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
events_for_watch(DBusWatch *watch) {
|
||||||
|
int events = 0;
|
||||||
|
unsigned int flags = dbus_watch_get_flags(watch);
|
||||||
|
if (flags & DBUS_WATCH_READABLE) events |= POLLIN;
|
||||||
|
if (flags & DBUS_WATCH_WRITABLE) events |= POLLOUT;
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
static dbus_bool_t
|
||||||
|
add_dbus_watch(DBusWatch *watch, void *data) {
|
||||||
|
id_type watch_id = addWatch(dbus_data->eld, data, dbus_watch_get_unix_fd(watch), events_for_watch(watch), dbus_watch_get_enabled(watch), on_dbus_watch_ready, watch);
|
||||||
|
if (!watch_id) return FALSE;
|
||||||
|
id_type *idp = malloc(sizeof(id_type));
|
||||||
|
if (!idp) return FALSE;
|
||||||
|
*idp = watch_id;
|
||||||
|
dbus_watch_set_data(watch, idp, free);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
remove_dbus_watch(DBusWatch *watch, void *data) {
|
||||||
|
id_type *idp = dbus_watch_get_data(watch);
|
||||||
|
if (idp) removeWatch(dbus_data->eld, *idp);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
toggle_dbus_watch(DBusWatch *watch, void *data) {
|
||||||
|
id_type *idp = dbus_watch_get_data(watch);
|
||||||
|
if (idp) toggleWatch(dbus_data->eld, *idp, dbus_watch_get_enabled(watch));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_dbus_timer_ready(id_type timer_id, void *data) {
|
||||||
|
if (data) {
|
||||||
|
DBusTimeout *t = (DBusTimeout*)data;
|
||||||
|
dbus_timeout_handle(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static dbus_bool_t
|
||||||
|
add_dbus_timeout(DBusTimeout *timeout, void *data) {
|
||||||
|
int enabled = dbus_timeout_get_enabled(timeout) ? 1 : 0;
|
||||||
|
double interval = ((double)dbus_timeout_get_interval(timeout)) / 1000.0;
|
||||||
|
if (interval < 0) return FALSE;
|
||||||
|
id_type timer_id = addTimer(dbus_data->eld, data, interval, enabled, on_dbus_timer_ready, timeout);
|
||||||
|
if (!timer_id) return FALSE;
|
||||||
|
id_type *idp = malloc(sizeof(id_type));
|
||||||
|
if (!idp) {
|
||||||
|
removeTimer(dbus_data->eld, timer_id);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
*idp = timer_id;
|
||||||
|
dbus_timeout_set_data(timeout, idp, free);
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
remove_dbus_timeout(DBusTimeout *timeout, void *data) {
|
||||||
|
id_type *idp = dbus_timeout_get_data(timeout);
|
||||||
|
if (idp) removeTimer(dbus_data->eld, *idp);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
toggle_dbus_timeout(DBusTimeout *timeout, void *data) {
|
||||||
|
id_type *idp = dbus_timeout_get_data(timeout);
|
||||||
|
if (idp) toggleTimer(dbus_data->eld, *idp, dbus_timeout_get_enabled(timeout));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DBusConnection*
|
||||||
|
glfw_dbus_connect_to(const char *path, const char* err_msg, const char *name, GLFWbool register_on_bus) {
|
||||||
|
DBusError err;
|
||||||
|
dbus_error_init(&err);
|
||||||
|
DBusConnection *ans = dbus_connection_open_private(path, &err);
|
||||||
|
if (!ans) {
|
||||||
|
report_error(&err, err_msg);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
dbus_connection_set_exit_on_disconnect(ans, FALSE);
|
||||||
|
dbus_error_free(&err);
|
||||||
|
if (register_on_bus) {
|
||||||
|
if (!dbus_bus_register(ans, &err)) {
|
||||||
|
report_error(&err, err_msg);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!dbus_connection_set_watch_functions(ans, add_dbus_watch, remove_dbus_watch, toggle_dbus_watch, (void*)name, NULL)) {
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to set DBUS watches on connection to: %s", path);
|
||||||
|
dbus_connection_close(ans);
|
||||||
|
dbus_connection_unref(ans);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (!dbus_connection_set_timeout_functions(ans, add_dbus_timeout, remove_dbus_timeout, toggle_dbus_timeout, (void*)name, NULL)) {
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to set DBUS timeout functions on connection to: %s", path);
|
||||||
|
dbus_connection_close(ans);
|
||||||
|
dbus_connection_unref(ans);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
glfw_dbus_dispatch(DBusConnection *conn) {
|
||||||
|
while(dbus_connection_dispatch(conn) == DBUS_DISPATCH_DATA_REMAINS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
glfw_dbus_terminate(_GLFWDBUSData *dbus) {
|
||||||
|
if (dbus_data) {
|
||||||
|
dbus_data->eld = NULL;
|
||||||
|
dbus_data = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
glfw_dbus_close_connection(DBusConnection *conn) {
|
||||||
|
dbus_connection_close(conn);
|
||||||
|
dbus_connection_unref(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
GLFWbool
|
||||||
|
glfw_dbus_get_args(DBusMessage *msg, const char *failmsg, ...) {
|
||||||
|
DBusError err;
|
||||||
|
dbus_error_init(&err);
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, failmsg);
|
||||||
|
int firstarg = va_arg(ap, int);
|
||||||
|
GLFWbool ret = dbus_message_get_args_valist(msg, &err, firstarg, ap) ? GLFW_TRUE : GLFW_FALSE;
|
||||||
|
va_end(ap);
|
||||||
|
if (!ret) report_error(&err, failmsg);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
dbus_pending_callback callback;
|
||||||
|
void *user_data;
|
||||||
|
} MethodResponse;
|
||||||
|
|
||||||
|
static const char*
|
||||||
|
format_message_error(DBusError *err) {
|
||||||
|
static char buf[1024];
|
||||||
|
snprintf(buf, sizeof(buf), "[%s] %s", err->name ? err->name : "", err->message);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
method_reply_received(DBusPendingCall *pending, void *user_data) {
|
||||||
|
MethodResponse *res = (MethodResponse*)user_data;
|
||||||
|
DBusMessage *msg = dbus_pending_call_steal_reply(pending);
|
||||||
|
if (msg) {
|
||||||
|
DBusError err;
|
||||||
|
dbus_error_init(&err);
|
||||||
|
if (dbus_set_error_from_message(&err, msg)) res->callback(NULL, format_message_error(&err), res->user_data);
|
||||||
|
else res->callback(msg, NULL, res->user_data);
|
||||||
|
dbus_message_unref(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static GLFWbool
|
||||||
|
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 GLFW_FALSE;
|
||||||
|
DBusMessage *msg = dbus_message_new_method_call(node, path, interface, method);
|
||||||
|
if (!msg) return GLFW_FALSE;
|
||||||
|
GLFWbool retval = GLFW_FALSE;
|
||||||
|
MethodResponse *res = malloc(sizeof(MethodResponse));
|
||||||
|
if (!res) { dbus_message_unref(msg); return GLFW_FALSE; }
|
||||||
|
res->callback = callback;
|
||||||
|
res->user_data = user_data;
|
||||||
|
|
||||||
|
int firstarg = va_arg(ap, int);
|
||||||
|
if ((firstarg == DBUS_TYPE_INVALID) || dbus_message_append_args_valist(msg, firstarg, ap)) {
|
||||||
|
if (callback) {
|
||||||
|
DBusPendingCall *pending = NULL;
|
||||||
|
if (dbus_connection_send_with_reply(conn, msg, &pending, timeout)) {
|
||||||
|
dbus_pending_call_set_notify(pending, method_reply_received, res, free);
|
||||||
|
retval = GLFW_TRUE;
|
||||||
|
} else {
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: %s on node: %s and interface: %s out of memory", method, node, interface);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (dbus_connection_send(conn, msg, NULL)) {
|
||||||
|
retval = GLFW_TRUE;
|
||||||
|
} else {
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: %s on node: %s and interface: %s out of memory", method, node, interface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: %s on node: %s and interface: %s could not add arguments", method, node, interface);
|
||||||
|
}
|
||||||
|
dbus_message_unref(msg);
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLFWbool
|
||||||
|
glfw_dbus_call_method_with_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout, dbus_pending_callback callback, void* user_data, ...) {
|
||||||
|
GLFWbool retval;
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, user_data);
|
||||||
|
retval = call_method(conn, node, path, interface, method, timeout, callback, user_data, ap);
|
||||||
|
va_end(ap);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLFWbool
|
||||||
|
glfw_dbus_call_method_no_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...) {
|
||||||
|
GLFWbool retval;
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, method);
|
||||||
|
retval = call_method(conn, node, path, interface, method, DBUS_TIMEOUT_USE_DEFAULT, NULL, NULL, ap);
|
||||||
|
va_end(ap);
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
glfw_dbus_match_signal(DBusMessage *msg, const char *interface, ...) {
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, interface);
|
||||||
|
int ans = -1, num = -1;
|
||||||
|
while(1) {
|
||||||
|
num++;
|
||||||
|
const char *name = va_arg(ap, const char*);
|
||||||
|
if (!name) break;
|
||||||
|
if (dbus_message_is_signal(msg, interface, name)) { ans = num; break; }
|
||||||
|
}
|
||||||
|
va_end(ap);
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
50
glfw/dbus_glfw.h
vendored
Normal file
50
glfw/dbus_glfw.h
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
//========================================================================
|
||||||
|
// GLFW 3.3 XKB - www.glfw.org
|
||||||
|
//------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2018 Kovid Goyal <kovid@kovidgoyal.net>
|
||||||
|
//
|
||||||
|
// This software is provided 'as-is', without any express or implied
|
||||||
|
// warranty. In no event will the authors be held liable for any damages
|
||||||
|
// arising from the use of this software.
|
||||||
|
//
|
||||||
|
// Permission is granted to anyone to use this software for any purpose,
|
||||||
|
// including commercial applications, and to alter it and redistribute it
|
||||||
|
// freely, subject to the following restrictions:
|
||||||
|
//
|
||||||
|
// 1. The origin of this software must not be misrepresented; you must not
|
||||||
|
// claim that you wrote the original software. If you use this software
|
||||||
|
// in a product, an acknowledgment in the product documentation would
|
||||||
|
// be appreciated but is not required.
|
||||||
|
//
|
||||||
|
// 2. Altered source versions must be plainly marked as such, and must not
|
||||||
|
// be misrepresented as being the original software.
|
||||||
|
//
|
||||||
|
// 3. This notice may not be removed or altered from any source
|
||||||
|
// distribution.
|
||||||
|
//
|
||||||
|
//========================================================================
|
||||||
|
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <dbus/dbus.h>
|
||||||
|
#include "backend_utils.h"
|
||||||
|
|
||||||
|
typedef void(*dbus_pending_callback)(DBusMessage *msg, const char* err, void* data);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
EventLoopData* eld;
|
||||||
|
} _GLFWDBUSData;
|
||||||
|
|
||||||
|
|
||||||
|
GLFWbool glfw_dbus_init(_GLFWDBUSData *dbus, EventLoopData *eld);
|
||||||
|
void glfw_dbus_terminate(_GLFWDBUSData *dbus);
|
||||||
|
DBusConnection* glfw_dbus_connect_to(const char *path, const char* err_msg, const char* name, GLFWbool register_on_bus);
|
||||||
|
void glfw_dbus_close_connection(DBusConnection *conn);
|
||||||
|
GLFWbool
|
||||||
|
glfw_dbus_call_method_no_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...);
|
||||||
|
GLFWbool
|
||||||
|
glfw_dbus_call_method_with_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout_ms, dbus_pending_callback callback, void *user_data, ...);
|
||||||
|
void glfw_dbus_dispatch(DBusConnection *);
|
||||||
|
GLFWbool glfw_dbus_get_args(DBusMessage *msg, const char *failmsg, ...);
|
||||||
|
int glfw_dbus_match_signal(DBusMessage *msg, const char *interface, ...);
|
||||||
5
glfw/egl_context.h
vendored
5
glfw/egl_context.h
vendored
@@ -43,10 +43,6 @@ typedef Window EGLNativeWindowType;
|
|||||||
#define EGLAPIENTRY
|
#define EGLAPIENTRY
|
||||||
typedef struct wl_display* EGLNativeDisplayType;
|
typedef struct wl_display* EGLNativeDisplayType;
|
||||||
typedef struct wl_egl_window* EGLNativeWindowType;
|
typedef struct wl_egl_window* EGLNativeWindowType;
|
||||||
#elif defined(_GLFW_MIR)
|
|
||||||
#define EGLAPIENTRY
|
|
||||||
typedef MirEGLNativeDisplayType EGLNativeDisplayType;
|
|
||||||
typedef MirEGLNativeWindowType EGLNativeWindowType;
|
|
||||||
#else
|
#else
|
||||||
#error "No supported EGL platform selected"
|
#error "No supported EGL platform selected"
|
||||||
#endif
|
#endif
|
||||||
@@ -216,4 +212,3 @@ GLFWbool _glfwChooseVisualEGL(const _GLFWwndconfig* wndconfig,
|
|||||||
const _GLFWfbconfig* fbconfig,
|
const _GLFWfbconfig* fbconfig,
|
||||||
Visual** visual, int* depth);
|
Visual** visual, int* depth);
|
||||||
#endif /*_GLFW_X11*/
|
#endif /*_GLFW_X11*/
|
||||||
|
|
||||||
|
|||||||
79
glfw/glfw.py
79
glfw/glfw.py
@@ -5,7 +5,6 @@
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
_plat = sys.platform.lower()
|
_plat = sys.platform.lower()
|
||||||
@@ -22,7 +21,7 @@ def wayland_protocol_file_name(base, ext='c'):
|
|||||||
return 'wayland-{}-client-protocol.{}'.format(base, ext)
|
return 'wayland-{}-client-protocol.{}'.format(base, ext)
|
||||||
|
|
||||||
|
|
||||||
def init_env(env, pkg_config, at_least_version, module='x11'):
|
def init_env(env, pkg_config, at_least_version, test_compile, module='x11'):
|
||||||
ans = env.copy()
|
ans = env.copy()
|
||||||
ans.cflags = [
|
ans.cflags = [
|
||||||
x for x in ans.cflags
|
x for x in ans.cflags
|
||||||
@@ -32,10 +31,11 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
|
|||||||
ans.cflags.append('-pthread')
|
ans.cflags.append('-pthread')
|
||||||
ans.ldpaths.append('-pthread')
|
ans.ldpaths.append('-pthread')
|
||||||
ans.cflags.append('-fpic')
|
ans.cflags.append('-fpic')
|
||||||
ans.cflags.append('-D_GLFW_' + module.upper())
|
ans.cppflags.append('-D_GLFW_' + module.upper())
|
||||||
ans.cflags.append('-D_GLFW_BUILD_DLL')
|
ans.cppflags.append('-D_GLFW_BUILD_DLL')
|
||||||
|
|
||||||
if is_macos:
|
if is_macos:
|
||||||
|
ans.cppflags.append('-DGL_SILENCE_DEPRECATION')
|
||||||
ans.ldpaths.extend(
|
ans.ldpaths.extend(
|
||||||
"-framework Cocoa -framework IOKit -framework CoreFoundation -framework CoreVideo".
|
"-framework Cocoa -framework IOKit -framework CoreFoundation -framework CoreVideo".
|
||||||
split()
|
split()
|
||||||
@@ -55,7 +55,7 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
|
|||||||
at_least_version('xkbcommon', 0, 5)
|
at_least_version('xkbcommon', 0, 5)
|
||||||
|
|
||||||
if module == 'x11':
|
if module == 'x11':
|
||||||
for dep in 'x11 xrandr xinerama xcursor xkbcommon xkbcommon-x11 x11-xcb'.split():
|
for dep in 'x11 xrandr xinerama xcursor xkbcommon xkbcommon-x11 x11-xcb dbus-1'.split():
|
||||||
ans.cflags.extend(pkg_config(dep, '--cflags-only-I'))
|
ans.cflags.extend(pkg_config(dep, '--cflags-only-I'))
|
||||||
ans.ldpaths.extend(pkg_config(dep, '--libs'))
|
ans.ldpaths.extend(pkg_config(dep, '--libs'))
|
||||||
|
|
||||||
@@ -67,13 +67,23 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
|
|||||||
at_least_version('wayland-protocols', *sinfo['wayland_protocols'])
|
at_least_version('wayland-protocols', *sinfo['wayland_protocols'])
|
||||||
ans.wayland_packagedir = os.path.abspath(pkg_config('wayland-protocols', '--variable=pkgdatadir')[0])
|
ans.wayland_packagedir = os.path.abspath(pkg_config('wayland-protocols', '--variable=pkgdatadir')[0])
|
||||||
ans.wayland_scanner = os.path.abspath(pkg_config('wayland-scanner', '--variable=wayland_scanner')[0])
|
ans.wayland_scanner = os.path.abspath(pkg_config('wayland-scanner', '--variable=wayland_scanner')[0])
|
||||||
|
scanner_version = tuple(map(int, pkg_config('wayland-scanner', '--modversion')[0].strip().split('.')))
|
||||||
|
ans.wayland_scanner_code = 'private-code' if scanner_version >= (1, 14, 91) else 'code'
|
||||||
ans.wayland_protocols = tuple(sinfo[module]['protocols'])
|
ans.wayland_protocols = tuple(sinfo[module]['protocols'])
|
||||||
for p in ans.wayland_protocols:
|
for p in ans.wayland_protocols:
|
||||||
ans.sources.append(wayland_protocol_file_name(p))
|
ans.sources.append(wayland_protocol_file_name(p))
|
||||||
ans.all_headers.append(wayland_protocol_file_name(p, 'h'))
|
ans.all_headers.append(wayland_protocol_file_name(p, 'h'))
|
||||||
for dep in 'wayland-egl wayland-client wayland-cursor xkbcommon'.split():
|
for dep in 'wayland-egl wayland-client wayland-cursor xkbcommon dbus-1'.split():
|
||||||
ans.cflags.extend(pkg_config(dep, '--cflags-only-I'))
|
ans.cflags.extend(pkg_config(dep, '--cflags-only-I'))
|
||||||
ans.ldpaths.extend(pkg_config(dep, '--libs'))
|
ans.ldpaths.extend(pkg_config(dep, '--libs'))
|
||||||
|
has_memfd_create = test_compile(env.cc, '-Werror', src='''#define _GNU_SOURCE
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/syscall.h>
|
||||||
|
int main(void) {
|
||||||
|
return syscall(__NR_memfd_create, "test", 0);
|
||||||
|
}''')
|
||||||
|
if has_memfd_create:
|
||||||
|
ans.cppflags.append('-DHAS_MEMFD_CREATE')
|
||||||
|
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
@@ -87,7 +97,7 @@ def build_wayland_protocols(env, run_tool, emphasis, newer, dest_dir):
|
|||||||
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 'code'
|
q = 'client-header' if ext == 'h' else env.wayland_scanner_code
|
||||||
run_tool([env.wayland_scanner, q, src, dest],
|
run_tool([env.wayland_scanner, q, src, dest],
|
||||||
desc='Generating {} ...'.format(emphasis(os.path.basename(dest))))
|
desc='Generating {} ...'.format(emphasis(os.path.basename(dest))))
|
||||||
|
|
||||||
@@ -126,16 +136,6 @@ def collect_source_information():
|
|||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
def patch_in_file(path, pfunc):
|
|
||||||
with open(path, 'r+') as f:
|
|
||||||
raw = f.read()
|
|
||||||
nraw = pfunc(raw)
|
|
||||||
if raw == nraw:
|
|
||||||
raise SystemExit('Patching of {} failed'.format(path))
|
|
||||||
f.seek(0), f.truncate()
|
|
||||||
f.write(nraw)
|
|
||||||
|
|
||||||
|
|
||||||
class Arg:
|
class Arg:
|
||||||
|
|
||||||
def __init__(self, decl):
|
def __init__(self, decl):
|
||||||
@@ -187,7 +187,7 @@ class Function:
|
|||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
def generate_wrappers(glfw_header, glfw_native_header):
|
def generate_wrappers(glfw_header):
|
||||||
src = open(glfw_header).read()
|
src = open(glfw_header).read()
|
||||||
functions = []
|
functions = []
|
||||||
first = None
|
first = None
|
||||||
@@ -200,15 +200,18 @@ def generate_wrappers(glfw_header, glfw_native_header):
|
|||||||
functions.append(Function(decl))
|
functions.append(Function(decl))
|
||||||
for line in '''\
|
for line in '''\
|
||||||
void* glfwGetCocoaWindow(GLFWwindow* window)
|
void* glfwGetCocoaWindow(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)
|
||||||
|
GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *window, GLFWcocoatogglefullscreenfun callback)
|
||||||
GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback)
|
GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback)
|
||||||
void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, void* cocoa_key, void* cocoa_mods)
|
void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, void* cocoa_key, void* cocoa_mods)
|
||||||
void* glfwGetX11Display(void)
|
void* glfwGetX11Display(void)
|
||||||
int32_t glfwGetX11Window(GLFWwindow* window)
|
int32_t glfwGetX11Window(GLFWwindow* window)
|
||||||
void glfwSetX11SelectionString(const char* string)
|
void glfwSetPrimarySelectionString(GLFWwindow* window, const char* string)
|
||||||
const char* glfwGetX11SelectionString(void)
|
const char* glfwGetPrimarySelectionString(GLFWwindow* window, void)
|
||||||
int glfwGetXKBScancode(const char* key_name, int case_sensitive)
|
int glfwGetXKBScancode(const char* key_name, int case_sensitive)
|
||||||
|
void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long id, GLFWwaylandframecallbackfunc callback)
|
||||||
'''.splitlines():
|
'''.splitlines():
|
||||||
if line:
|
if line:
|
||||||
functions.append(Function(line.strip(), check_fail=False))
|
functions.append(Function(line.strip(), check_fail=False))
|
||||||
@@ -221,11 +224,13 @@ def generate_wrappers(glfw_header, glfw_native_header):
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int);
|
|
||||||
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
|
|
||||||
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int);
|
||||||
|
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||||
|
typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
|
||||||
|
typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id);
|
||||||
{}
|
{}
|
||||||
|
|
||||||
const char* load_glfw(const char* path);
|
const char* load_glfw(const char* path);
|
||||||
@@ -264,34 +269,8 @@ unload_glfw() {
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
os.chdir(sys.argv[-1])
|
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||||
sinfo = collect_source_information()
|
generate_wrappers('glfw3.h')
|
||||||
files_to_copy = set()
|
|
||||||
for x in sinfo.values():
|
|
||||||
if isinstance(x, dict):
|
|
||||||
headers, sources = x['headers'], x['sources']
|
|
||||||
for name in headers + sources:
|
|
||||||
files_to_copy.add(os.path.abspath(os.path.join('src', name)))
|
|
||||||
glfw_header = os.path.abspath('include/GLFW/glfw3.h')
|
|
||||||
glfw_native_header = os.path.abspath('include/GLFW/glfw3native.h')
|
|
||||||
os.chdir(base)
|
|
||||||
for x in os.listdir('.'):
|
|
||||||
if x.rpartition('.') in ('c', 'h'):
|
|
||||||
os.unlink(x)
|
|
||||||
for src in files_to_copy:
|
|
||||||
shutil.copy2(src, '.')
|
|
||||||
shutil.copy2(glfw_header, '.')
|
|
||||||
patch_in_file('internal.h', lambda x: x.replace('../include/GLFW/', ''))
|
|
||||||
patch_in_file('cocoa_window.m', lambda x: re.sub(
|
|
||||||
r'[(]void[)]loadMainMenu.+?}', '(void)loadMainMenu\n{ // removed by Kovid as it generated compiler warnings \n}\n', x, flags=re.DOTALL))
|
|
||||||
json.dump(
|
|
||||||
sinfo,
|
|
||||||
open('source-info.json', 'w'),
|
|
||||||
indent=2,
|
|
||||||
ensure_ascii=False,
|
|
||||||
sort_keys=True
|
|
||||||
)
|
|
||||||
generate_wrappers(glfw_header, glfw_native_header)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
86
glfw/glfw3.h
vendored
86
glfw/glfw3.h
vendored
@@ -407,6 +407,7 @@ extern "C" {
|
|||||||
#define GLFW_KEY_GRAVE_ACCENT 96 /* ` */
|
#define GLFW_KEY_GRAVE_ACCENT 96 /* ` */
|
||||||
#define GLFW_KEY_WORLD_1 161 /* non-US #1 */
|
#define GLFW_KEY_WORLD_1 161 /* non-US #1 */
|
||||||
#define GLFW_KEY_WORLD_2 162 /* non-US #2 */
|
#define GLFW_KEY_WORLD_2 162 /* non-US #2 */
|
||||||
|
#define GLFW_KEY_PLUS 163
|
||||||
|
|
||||||
/* Function keys */
|
/* Function keys */
|
||||||
#define GLFW_KEY_ESCAPE 256
|
#define GLFW_KEY_ESCAPE 256
|
||||||
@@ -973,6 +974,10 @@ extern "C" {
|
|||||||
* [attribute](@ref GLFW_CLIENT_API_attrib).
|
* [attribute](@ref GLFW_CLIENT_API_attrib).
|
||||||
*/
|
*/
|
||||||
#define GLFW_CONTEXT_CREATION_API 0x0002200B
|
#define GLFW_CONTEXT_CREATION_API 0x0002200B
|
||||||
|
/*! @brief Window content area scaling window
|
||||||
|
* [window hint](@ref GLFW_SCALE_TO_MONITOR).
|
||||||
|
*/
|
||||||
|
#define GLFW_SCALE_TO_MONITOR 0x0002200C
|
||||||
|
|
||||||
#define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001
|
#define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001
|
||||||
#define GLFW_COCOA_FRAME_NAME 0x00023002
|
#define GLFW_COCOA_FRAME_NAME 0x00023002
|
||||||
@@ -980,6 +985,8 @@ extern "C" {
|
|||||||
|
|
||||||
#define GLFW_X11_CLASS_NAME 0x00024001
|
#define GLFW_X11_CLASS_NAME 0x00024001
|
||||||
#define GLFW_X11_INSTANCE_NAME 0x00024002
|
#define GLFW_X11_INSTANCE_NAME 0x00024002
|
||||||
|
|
||||||
|
#define GLFW_WAYLAND_APP_ID 0x00025001
|
||||||
/*! @} */
|
/*! @} */
|
||||||
|
|
||||||
#define GLFW_NO_API 0
|
#define GLFW_NO_API 0
|
||||||
@@ -1058,6 +1065,7 @@ extern "C" {
|
|||||||
* @{ */
|
* @{ */
|
||||||
#define GLFW_JOYSTICK_HAT_BUTTONS 0x00050001
|
#define GLFW_JOYSTICK_HAT_BUTTONS 0x00050001
|
||||||
#define GLFW_DEBUG_KEYBOARD 0x00050002
|
#define GLFW_DEBUG_KEYBOARD 0x00050002
|
||||||
|
#define GLFW_ENABLE_JOYSTICKS 0x00050003
|
||||||
|
|
||||||
#define GLFW_COCOA_CHDIR_RESOURCES 0x00051001
|
#define GLFW_COCOA_CHDIR_RESOURCES 0x00051001
|
||||||
#define GLFW_COCOA_MENUBAR 0x00051002
|
#define GLFW_COCOA_MENUBAR 0x00051002
|
||||||
@@ -1372,15 +1380,23 @@ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int);
|
|||||||
* @param[in] window The window that received the event.
|
* @param[in] window The window that received the event.
|
||||||
* @param[in] xoffset The scroll offset along the x-axis.
|
* @param[in] xoffset The scroll offset along the x-axis.
|
||||||
* @param[in] yoffset The scroll offset along the y-axis.
|
* @param[in] yoffset The scroll offset along the y-axis.
|
||||||
|
* @param[in] flags A bit-mask providing extra data about the event.
|
||||||
|
* flags & 1 will be true if and only if the offset values are "high-precision".
|
||||||
|
* Typically pixel values. Otherwise the offset values are number of lines.
|
||||||
|
* (flags >> 1) & 7 will have value 1 for the start of momentum scrolling,
|
||||||
|
* value 2 for stationary momentum scrolling, value 3 for momentum scrolling
|
||||||
|
* in progress, value 4 for momentum scrolling ended, value 5 for momentum
|
||||||
|
* scrolling cancelled and value 6 if scrolling may begin soon.
|
||||||
*
|
*
|
||||||
* @sa @ref scrolling
|
* @sa @ref scrolling
|
||||||
* @sa @ref glfwSetScrollCallback
|
* @sa @ref glfwSetScrollCallback
|
||||||
*
|
*
|
||||||
* @since Added in version 3.0. Replaces `GLFWmousewheelfun`.
|
* @since Added in version 3.0. Replaces `GLFWmousewheelfun`.
|
||||||
|
* @since Changed in version 4.0. Added `flags` parameter.
|
||||||
*
|
*
|
||||||
* @ingroup input
|
* @ingroup input
|
||||||
*/
|
*/
|
||||||
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double);
|
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int);
|
||||||
|
|
||||||
/*! @brief The function signature for key callbacks.
|
/*! @brief The function signature for key callbacks.
|
||||||
*
|
*
|
||||||
@@ -1399,7 +1415,9 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double);
|
|||||||
* @param[in] mods Bit field describing which [modifier keys](@ref mods) were
|
* @param[in] mods Bit field describing which [modifier keys](@ref mods) were
|
||||||
* held down.
|
* held down.
|
||||||
* @param[in] text UTF-8 encoded text generated by this key event or empty string.
|
* @param[in] text UTF-8 encoded text generated by this key event or empty string.
|
||||||
* @param[in] reserved Reserved for future use.
|
* @param[in] Used for Input Method events. Zero for normal key events.
|
||||||
|
* A value of 1 means the pre-edit text for the input event has been changed.
|
||||||
|
* A value of 2 means the text should be committed.
|
||||||
*
|
*
|
||||||
* @note On X11/Wayland if a modifier other than the modifiers GLFW reports
|
* @note On X11/Wayland if a modifier other than the modifiers GLFW reports
|
||||||
* (ctrl/shift/alt/super) is used, GLFW will report the shifted key rather
|
* (ctrl/shift/alt/super) is used, GLFW will report the shifted key rather
|
||||||
@@ -2110,9 +2128,9 @@ GLFWAPI const GLFWvidmode* glfwGetVideoMode(GLFWmonitor* monitor);
|
|||||||
|
|
||||||
/*! @brief Generates a gamma ramp and sets it for the specified monitor.
|
/*! @brief Generates a gamma ramp and sets it for the specified monitor.
|
||||||
*
|
*
|
||||||
* This function generates a 256-element gamma ramp from the specified exponent
|
* This function generates an appropriately sized gamma ramp from the
|
||||||
* and then calls @ref glfwSetGammaRamp with it. The value must be a finite
|
* specified exponent and then calls @ref glfwSetGammaRamp with it. The value
|
||||||
* number greater than zero.
|
* must be a finite number greater than zero.
|
||||||
*
|
*
|
||||||
* The software controlled gamma ramp is applied _in addition_ to the hardware
|
* The software controlled gamma ramp is applied _in addition_ to the hardware
|
||||||
* gamma correction, which today is usually an approximation of sRGB gamma.
|
* gamma correction, which today is usually an approximation of sRGB gamma.
|
||||||
@@ -2191,8 +2209,8 @@ GLFWAPI const GLFWgammaramp* glfwGetGammaRamp(GLFWmonitor* monitor);
|
|||||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
||||||
* GLFW_PLATFORM_ERROR.
|
* GLFW_PLATFORM_ERROR.
|
||||||
*
|
*
|
||||||
* @remark Gamma ramp sizes other than 256 are not supported by all platforms
|
* @remark The size of the specified gamma ramp should match the size of the
|
||||||
* or graphics hardware.
|
* current ramp for that monitor.
|
||||||
*
|
*
|
||||||
* @remark @win32 The gamma ramp size must be 256.
|
* @remark @win32 The gamma ramp size must be 256.
|
||||||
*
|
*
|
||||||
@@ -2919,6 +2937,32 @@ GLFWAPI void glfwGetWindowFrameSize(GLFWwindow* window, int* left, int* top, int
|
|||||||
*/
|
*/
|
||||||
GLFWAPI void glfwGetWindowContentScale(GLFWwindow* window, float* xscale, float* yscale);
|
GLFWAPI void glfwGetWindowContentScale(GLFWwindow* window, float* xscale, float* yscale);
|
||||||
|
|
||||||
|
/*! @brief Returns the double click time interval.
|
||||||
|
*
|
||||||
|
* This function returns the maximum time between clicks to count as a
|
||||||
|
* double click.
|
||||||
|
*
|
||||||
|
* The double click interval is a positive finite number greater than zero,
|
||||||
|
* where zero means that no click is ever recognized as a double click. If the
|
||||||
|
* system does not support a double click interval, this function always returns one half.
|
||||||
|
*
|
||||||
|
* @return The double click interval.
|
||||||
|
*
|
||||||
|
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
||||||
|
* GLFW_PLATFORM_ERROR.
|
||||||
|
*
|
||||||
|
* @thread_safety This function must only be called from the main thread.
|
||||||
|
*
|
||||||
|
* @sa @ref double_click
|
||||||
|
* @sa @ref click_interval
|
||||||
|
* @sa @ref double_click_interval
|
||||||
|
*
|
||||||
|
* @since Added in version 3.3.
|
||||||
|
*
|
||||||
|
* @ingroup window
|
||||||
|
*/
|
||||||
|
GLFWAPI double glfwGetDoubleClickInterval(GLFWwindow* window);
|
||||||
|
|
||||||
/*! @brief Returns the opacity of the whole window.
|
/*! @brief Returns the opacity of the whole window.
|
||||||
*
|
*
|
||||||
* This function returns the opacity of the window, including any decorations.
|
* This function returns the opacity of the window, including any decorations.
|
||||||
@@ -4007,8 +4051,8 @@ GLFWAPI int glfwGetKey(GLFWwindow* window, int key);
|
|||||||
* `GLFW_RELEASE`.
|
* `GLFW_RELEASE`.
|
||||||
*
|
*
|
||||||
* If the @ref GLFW_STICKY_MOUSE_BUTTONS input mode is enabled, this function
|
* If the @ref GLFW_STICKY_MOUSE_BUTTONS input mode is enabled, this function
|
||||||
* `GLFW_PRESS` the first time you call it for a mouse button that was pressed,
|
* returns `GLFW_PRESS` the first time you call it for a mouse button that was
|
||||||
* even if that mouse button has already been released.
|
* pressed, even if that mouse button has already been released.
|
||||||
*
|
*
|
||||||
* @param[in] window The desired window.
|
* @param[in] window The desired window.
|
||||||
* @param[in] button The desired [mouse button](@ref buttons).
|
* @param[in] button The desired [mouse button](@ref buttons).
|
||||||
@@ -4123,6 +4167,7 @@ GLFWAPI void glfwSetCursorPos(GLFWwindow* window, double xpos, double ypos);
|
|||||||
* @param[in] image The desired cursor image.
|
* @param[in] image The desired cursor image.
|
||||||
* @param[in] xhot The desired x-coordinate, in pixels, of the cursor hotspot.
|
* @param[in] xhot The desired x-coordinate, in pixels, of the cursor hotspot.
|
||||||
* @param[in] yhot The desired y-coordinate, in pixels, of the cursor hotspot.
|
* @param[in] yhot The desired y-coordinate, in pixels, of the cursor hotspot.
|
||||||
|
* @param[in] count The number of images. Used on Cocoa for retina cursors. The first image should be the 1:1 scale image.
|
||||||
* @return The handle of the created cursor, or `NULL` if an
|
* @return The handle of the created cursor, or `NULL` if an
|
||||||
* [error](@ref error_handling) occurred.
|
* [error](@ref error_handling) occurred.
|
||||||
*
|
*
|
||||||
@@ -4138,11 +4183,11 @@ GLFWAPI void glfwSetCursorPos(GLFWwindow* window, double xpos, double ypos);
|
|||||||
* @sa @ref glfwDestroyCursor
|
* @sa @ref glfwDestroyCursor
|
||||||
* @sa @ref glfwCreateStandardCursor
|
* @sa @ref glfwCreateStandardCursor
|
||||||
*
|
*
|
||||||
* @since Added in version 3.1.
|
* @since Added in version 3.1. Changed in 4.0 to add the count parameter.
|
||||||
*
|
*
|
||||||
* @ingroup input
|
* @ingroup input
|
||||||
*/
|
*/
|
||||||
GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot);
|
GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot, int count);
|
||||||
|
|
||||||
/*! @brief Creates a cursor with a standard shape.
|
/*! @brief Creates a cursor with a standard shape.
|
||||||
*
|
*
|
||||||
@@ -4227,6 +4272,23 @@ GLFWAPI void glfwSetCursor(GLFWwindow* window, GLFWcursor* cursor);
|
|||||||
*/
|
*/
|
||||||
GLFWAPI GLFWkeyboardfun glfwSetKeyboardCallback(GLFWwindow* window, GLFWkeyboardfun cbfun);
|
GLFWAPI GLFWkeyboardfun glfwSetKeyboardCallback(GLFWwindow* window, GLFWkeyboardfun cbfun);
|
||||||
|
|
||||||
|
/*! @brief Notifies the OS Input Method Event system of changes to application input state
|
||||||
|
*
|
||||||
|
* Used to notify the IME system of changes in state such as focus gained/lost
|
||||||
|
* and text cursor position.
|
||||||
|
*
|
||||||
|
* @param which: What data to notify. 1 means focus and 2 means cursor position.
|
||||||
|
* @param a, b, c, d: Interpreted based on the value of which. When which is 1
|
||||||
|
* a is interpreted as a boolean indicating focus gained/lost. When which is 2
|
||||||
|
* a, b, c, d are the cursor x, y, width and height values (in the window co-ordinate
|
||||||
|
* system).
|
||||||
|
*
|
||||||
|
* @ingroup input
|
||||||
|
* @since Added in version 4.0
|
||||||
|
*/
|
||||||
|
GLFWAPI void glfwUpdateIMEState(GLFWwindow* window, int which, int a, int b, int c, int d);
|
||||||
|
|
||||||
|
|
||||||
/*! @brief Sets the mouse button callback.
|
/*! @brief Sets the mouse button callback.
|
||||||
*
|
*
|
||||||
* This function sets the mouse button callback of the specified window, which
|
* This function sets the mouse button callback of the specified window, which
|
||||||
@@ -4787,6 +4849,8 @@ GLFWAPI const char* glfwGetGamepadName(int jid);
|
|||||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
||||||
* GLFW_INVALID_ENUM.
|
* GLFW_INVALID_ENUM.
|
||||||
*
|
*
|
||||||
|
* @thread_safety This function must only be called from the main thread.
|
||||||
|
*
|
||||||
* @sa @ref gamepad
|
* @sa @ref gamepad
|
||||||
* @sa @ref glfwUpdateGamepadMappings
|
* @sa @ref glfwUpdateGamepadMappings
|
||||||
* @sa @ref glfwJoystickIsGamepad
|
* @sa @ref glfwJoystickIsGamepad
|
||||||
|
|||||||
426
glfw/ibus_glfw.c
vendored
Normal file
426
glfw/ibus_glfw.c
vendored
Normal file
@@ -0,0 +1,426 @@
|
|||||||
|
//========================================================================
|
||||||
|
// GLFW 3.3 XKB - www.glfw.org
|
||||||
|
//------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2018 Kovid Goyal <kovid@kovidgoyal.net>
|
||||||
|
//
|
||||||
|
// This software is provided 'as-is', without any express or implied
|
||||||
|
// warranty. In no event will the authors be held liable for any damages
|
||||||
|
// arising from the use of this software.
|
||||||
|
//
|
||||||
|
// Permission is granted to anyone to use this software for any purpose,
|
||||||
|
// including commercial applications, and to alter it and redistribute it
|
||||||
|
// freely, subject to the following restrictions:
|
||||||
|
//
|
||||||
|
// 1. The origin of this software must not be misrepresented; you must not
|
||||||
|
// claim that you wrote the original software. If you use this software
|
||||||
|
// in a product, an acknowledgment in the product documentation would
|
||||||
|
// be appreciated but is not required.
|
||||||
|
//
|
||||||
|
// 2. Altered source versions must be plainly marked as such, and must not
|
||||||
|
// be misrepresented as being the original software.
|
||||||
|
//
|
||||||
|
// 3. This notice may not be removed or altered from any source
|
||||||
|
// distribution.
|
||||||
|
//
|
||||||
|
//========================================================================
|
||||||
|
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
#include "internal.h"
|
||||||
|
#include "ibus_glfw.h"
|
||||||
|
|
||||||
|
#define debug(...) if (_glfw.hints.init.debugKeyboard) printf(__VA_ARGS__);
|
||||||
|
static const char IBUS_SERVICE[] = "org.freedesktop.IBus";
|
||||||
|
static const char IBUS_PATH[] = "/org/freedesktop/IBus";
|
||||||
|
static const char IBUS_INTERFACE[] = "org.freedesktop.IBus";
|
||||||
|
static const char IBUS_INPUT_INTERFACE[] = "org.freedesktop.IBus.InputContext";
|
||||||
|
enum Capabilities {
|
||||||
|
IBUS_CAP_PREEDIT_TEXT = 1 << 0,
|
||||||
|
IBUS_CAP_AUXILIARY_TEXT = 1 << 1,
|
||||||
|
IBUS_CAP_LOOKUP_TABLE = 1 << 2,
|
||||||
|
IBUS_CAP_FOCUS = 1 << 3,
|
||||||
|
IBUS_CAP_PROPERTY = 1 << 4,
|
||||||
|
IBUS_CAP_SURROUNDING_TEXT = 1 << 5
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static inline GLFWbool
|
||||||
|
test_env_var(const char *name, const char *val) {
|
||||||
|
const char *q = getenv(name);
|
||||||
|
return (q && strcmp(q, val) == 0) ? GLFW_TRUE : GLFW_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline GLFWbool
|
||||||
|
MIN(size_t a, size_t b) {
|
||||||
|
return a < b ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char*
|
||||||
|
get_ibus_text_from_message(DBusMessage *msg) {
|
||||||
|
/* The message structure is (from dbus-monitor)
|
||||||
|
variant struct {
|
||||||
|
string "IBusText"
|
||||||
|
array [
|
||||||
|
]
|
||||||
|
string "ash "
|
||||||
|
variant struct {
|
||||||
|
string "IBusAttrList"
|
||||||
|
array [
|
||||||
|
]
|
||||||
|
array [
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
const char *text = NULL;
|
||||||
|
const char *struct_id = NULL;
|
||||||
|
DBusMessageIter iter, sub1, sub2;
|
||||||
|
dbus_message_iter_init(msg, &iter);
|
||||||
|
|
||||||
|
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) return NULL;
|
||||||
|
|
||||||
|
dbus_message_iter_recurse(&iter, &sub1);
|
||||||
|
|
||||||
|
if (dbus_message_iter_get_arg_type(&sub1) != DBUS_TYPE_STRUCT) return NULL;
|
||||||
|
|
||||||
|
dbus_message_iter_recurse(&sub1, &sub2);
|
||||||
|
|
||||||
|
if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_STRING) return NULL;
|
||||||
|
|
||||||
|
dbus_message_iter_get_basic(&sub2, &struct_id);
|
||||||
|
if (!struct_id || strncmp(struct_id, "IBusText", sizeof("IBusText")) != 0) return NULL;
|
||||||
|
|
||||||
|
dbus_message_iter_next(&sub2);
|
||||||
|
dbus_message_iter_next(&sub2);
|
||||||
|
|
||||||
|
if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_STRING) return NULL;
|
||||||
|
|
||||||
|
dbus_message_iter_get_basic(&sub2, &text);
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
send_text(const char *text, int state) {
|
||||||
|
_GLFWwindow *w = _glfwFocusedWindow();
|
||||||
|
if (w && w->callbacks.keyboard) {
|
||||||
|
w->callbacks.keyboard((GLFWwindow*) w, GLFW_KEY_UNKNOWN, 0, GLFW_PRESS, 0, text, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connection handling {{{
|
||||||
|
|
||||||
|
static DBusHandlerResult
|
||||||
|
message_handler(DBusConnection *conn, DBusMessage *msg, void *user_data) {
|
||||||
|
// To monitor signals from IBUS, use
|
||||||
|
// dbus-monitor --address `ibus address` "type='signal',interface='org.freedesktop.IBus.InputContext'"
|
||||||
|
_GLFWIBUSData *ibus = (_GLFWIBUSData*)user_data;
|
||||||
|
(void)ibus;
|
||||||
|
const char *text;
|
||||||
|
switch(glfw_dbus_match_signal(msg, IBUS_INPUT_INTERFACE, "CommitText", "UpdatePreeditText", "HidePreeditText", "ShowPreeditText", NULL)) {
|
||||||
|
case 0:
|
||||||
|
text = get_ibus_text_from_message(msg);
|
||||||
|
debug("IBUS: CommitText: '%s'\n", text ? text : "(nil)");
|
||||||
|
send_text(text, 2);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
text = get_ibus_text_from_message(msg);
|
||||||
|
send_text(text, 1);
|
||||||
|
debug("IBUS: UpdatePreeditText: '%s'\n", text ? text : "(nil)");
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
debug("IBUS: HidePreeditText\n");
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
debug("IBUS: ShowPreeditText\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline const char*
|
||||||
|
get_ibus_address_file_name(void) {
|
||||||
|
const char *addr;
|
||||||
|
static char ans[PATH_MAX];
|
||||||
|
addr = getenv("IBUS_ADDRESS");
|
||||||
|
int offset = 0;
|
||||||
|
if (addr && addr[0]) {
|
||||||
|
memcpy(ans, addr, MIN(strlen(addr), sizeof(ans)));
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *de = getenv("DISPLAY");
|
||||||
|
if (!de || !de[0]) de = ":0.0";
|
||||||
|
char *display = _glfw_strdup(de);
|
||||||
|
const char *host = display;
|
||||||
|
char *disp_num = strrchr(display, ':');
|
||||||
|
char *screen_num = strrchr(display, '.');
|
||||||
|
|
||||||
|
if (!disp_num) {
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR, "Could not get IBUS address file name as DISPLAY env var has no colon");
|
||||||
|
free(display);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
*disp_num = 0;
|
||||||
|
disp_num++;
|
||||||
|
if (screen_num) *screen_num = 0;
|
||||||
|
if (!*host) host = "unix";
|
||||||
|
|
||||||
|
memset(ans, 0, sizeof(ans));
|
||||||
|
const char *conf_env = getenv("XDG_CONFIG_HOME");
|
||||||
|
if (conf_env && conf_env[0]) {
|
||||||
|
offset = snprintf(ans, sizeof(ans), "%s", conf_env);
|
||||||
|
} else {
|
||||||
|
conf_env = getenv("HOME");
|
||||||
|
if (!conf_env || !conf_env[0]) {
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR, "Could not get IBUS address file name as no HOME env var is set");
|
||||||
|
free(display);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
offset = snprintf(ans, sizeof(ans), "%s/.config", conf_env);
|
||||||
|
}
|
||||||
|
char *key = dbus_get_local_machine_id();
|
||||||
|
snprintf(ans + offset, sizeof(ans) - offset, "/ibus/bus/%s-%s-%s", key, host, disp_num);
|
||||||
|
dbus_free(key);
|
||||||
|
free(display);
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static inline GLFWbool
|
||||||
|
read_ibus_address(_GLFWIBUSData *ibus) {
|
||||||
|
static char buf[1024];
|
||||||
|
struct stat s;
|
||||||
|
FILE *addr_file = fopen(ibus->address_file_name, "r");
|
||||||
|
if (!addr_file) {
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to open IBUS address file: %s with error: %s", ibus->address_file_name, strerror(errno));
|
||||||
|
return GLFW_FALSE;
|
||||||
|
}
|
||||||
|
int stat_result = fstat(fileno(addr_file), &s);
|
||||||
|
GLFWbool found = GLFW_FALSE;
|
||||||
|
while (fgets(buf, sizeof(buf), addr_file)) {
|
||||||
|
if (strncmp(buf, "IBUS_ADDRESS=", sizeof("IBUS_ADDRESS=")-1) == 0) {
|
||||||
|
size_t sz = strlen(buf);
|
||||||
|
if (buf[sz-1] == '\n') buf[sz-1] = 0;
|
||||||
|
if (buf[sz-2] == '\r') buf[sz-2] = 0;
|
||||||
|
found = GLFW_TRUE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose(addr_file); addr_file = NULL;
|
||||||
|
if (stat_result != 0) {
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to stat IBUS address file: %s with error: %s", ibus->address_file_name, strerror(errno));
|
||||||
|
return GLFW_FALSE;
|
||||||
|
}
|
||||||
|
ibus->address_file_mtime = s.st_mtime;
|
||||||
|
if (found) {
|
||||||
|
free((void*)ibus->address);
|
||||||
|
ibus->address = _glfw_strdup(buf + sizeof("IBUS_ADDRESS=") - 1);
|
||||||
|
return GLFW_TRUE;
|
||||||
|
}
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR, "Could not find IBUS_ADDRESS in %s", ibus->address_file_name);
|
||||||
|
return GLFW_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
input_context_created(DBusMessage *msg, const char* errmsg, void *data) {
|
||||||
|
if (errmsg) {
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR, "IBUS: Failed to create input context with error: %s", errmsg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const char *path = NULL;
|
||||||
|
if (!glfw_dbus_get_args(msg, "Failed to get IBUS context path from reply", DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) return;
|
||||||
|
_GLFWIBUSData *ibus = (_GLFWIBUSData*)data;
|
||||||
|
free((void*)ibus->input_ctx_path);
|
||||||
|
ibus->input_ctx_path = _glfw_strdup(path);
|
||||||
|
if (!ibus->input_ctx_path) return;
|
||||||
|
dbus_bus_add_match(ibus->conn, "type='signal',interface='org.freedesktop.IBus.InputContext'", NULL);
|
||||||
|
DBusObjectPathVTable ibus_vtable = {.message_function = message_handler};
|
||||||
|
dbus_connection_try_register_object_path(ibus->conn, ibus->input_ctx_path, &ibus_vtable, ibus, NULL);
|
||||||
|
enum Capabilities caps = IBUS_CAP_FOCUS | IBUS_CAP_PREEDIT_TEXT;
|
||||||
|
if (!glfw_dbus_call_method_no_reply(ibus->conn, IBUS_SERVICE, ibus->input_ctx_path, IBUS_INPUT_INTERFACE, "SetCapabilities", DBUS_TYPE_UINT32, &caps, DBUS_TYPE_INVALID)) return;
|
||||||
|
ibus->ok = GLFW_TRUE;
|
||||||
|
glfw_ibus_set_focused(ibus, GLFW_FALSE);
|
||||||
|
glfw_ibus_set_cursor_geometry(ibus, 0, 0, 0, 0);
|
||||||
|
debug("Connected to IBUS daemon for IME input management\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
GLFWbool
|
||||||
|
setup_connection(_GLFWIBUSData *ibus) {
|
||||||
|
const char *client_name = "GLFW_Application";
|
||||||
|
const char *address_file_name = get_ibus_address_file_name();
|
||||||
|
ibus->ok = GLFW_FALSE;
|
||||||
|
if (!address_file_name) return GLFW_FALSE;
|
||||||
|
free((void*)ibus->address_file_name);
|
||||||
|
ibus->address_file_name = _glfw_strdup(address_file_name);
|
||||||
|
if (!read_ibus_address(ibus)) return GLFW_FALSE;
|
||||||
|
if (ibus->conn) {
|
||||||
|
glfw_dbus_close_connection(ibus->conn);
|
||||||
|
ibus->conn = NULL;
|
||||||
|
}
|
||||||
|
debug("Connecting to IBUS daemon @ %s for IME input management\n", ibus->address);
|
||||||
|
ibus->conn = glfw_dbus_connect_to(ibus->address, "Failed to connect to the IBUS daemon, with error", "ibus", GLFW_FALSE);
|
||||||
|
if (!ibus->conn) return GLFW_FALSE;
|
||||||
|
free((void*)ibus->input_ctx_path); ibus->input_ctx_path = NULL;
|
||||||
|
if (!glfw_dbus_call_method_with_reply(
|
||||||
|
ibus->conn, IBUS_SERVICE, IBUS_PATH, IBUS_INTERFACE, "CreateInputContext", DBUS_TIMEOUT_USE_DEFAULT, input_context_created, ibus,
|
||||||
|
DBUS_TYPE_STRING, &client_name, DBUS_TYPE_INVALID)) {
|
||||||
|
return GLFW_FALSE;
|
||||||
|
}
|
||||||
|
return GLFW_TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
glfw_connect_to_ibus(_GLFWIBUSData *ibus) {
|
||||||
|
if (ibus->inited) return;
|
||||||
|
if (!test_env_var("GLFW_IM_MODULE", "ibus")) return;
|
||||||
|
ibus->inited = GLFW_TRUE;
|
||||||
|
setup_connection(ibus);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
glfw_ibus_terminate(_GLFWIBUSData *ibus) {
|
||||||
|
if (ibus->conn) {
|
||||||
|
glfw_dbus_close_connection(ibus->conn);
|
||||||
|
ibus->conn = NULL;
|
||||||
|
}
|
||||||
|
#define F(x) if (ibus->x) { free((void*)ibus->x); ibus->x = NULL; }
|
||||||
|
F(input_ctx_path);
|
||||||
|
F(address);
|
||||||
|
F(address_file_name);
|
||||||
|
#undef F
|
||||||
|
|
||||||
|
ibus->ok = GLFW_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GLFWbool
|
||||||
|
check_connection(_GLFWIBUSData *ibus) {
|
||||||
|
if (!ibus->inited) return GLFW_FALSE;
|
||||||
|
if (ibus->conn && dbus_connection_get_is_connected(ibus->conn)) {
|
||||||
|
return ibus->ok;
|
||||||
|
}
|
||||||
|
struct stat s;
|
||||||
|
if (stat(ibus->address_file_name, &s) != 0 || s.st_mtime != ibus->address_file_mtime) {
|
||||||
|
if (!read_ibus_address(ibus)) return GLFW_FALSE;
|
||||||
|
setup_connection(ibus);
|
||||||
|
}
|
||||||
|
return GLFW_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
glfw_ibus_dispatch(_GLFWIBUSData *ibus) {
|
||||||
|
if (ibus->conn) glfw_dbus_dispatch(ibus->conn);
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
static void
|
||||||
|
simple_message(_GLFWIBUSData *ibus, const char *method) {
|
||||||
|
if (check_connection(ibus)) {
|
||||||
|
glfw_dbus_call_method_no_reply(ibus->conn, IBUS_SERVICE, ibus->input_ctx_path, IBUS_INPUT_INTERFACE, method, DBUS_TYPE_INVALID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
glfw_ibus_set_focused(_GLFWIBUSData *ibus, GLFWbool focused) {
|
||||||
|
simple_message(ibus, focused ? "FocusIn" : "FocusOut");
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
glfw_ibus_set_cursor_geometry(_GLFWIBUSData *ibus, int x, int y, int w, int h) {
|
||||||
|
if (check_connection(ibus)) {
|
||||||
|
glfw_dbus_call_method_no_reply(ibus->conn, IBUS_SERVICE, ibus->input_ctx_path, IBUS_INPUT_INTERFACE, "SetCursorLocation",
|
||||||
|
DBUS_TYPE_INT32, &x, DBUS_TYPE_INT32, &y, DBUS_TYPE_INT32, &w, DBUS_TYPE_INT32, &h, DBUS_TYPE_INVALID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
IBUS_SHIFT_MASK = 1 << 0,
|
||||||
|
IBUS_LOCK_MASK = 1 << 1,
|
||||||
|
IBUS_CONTROL_MASK = 1 << 2,
|
||||||
|
IBUS_MOD1_MASK = 1 << 3,
|
||||||
|
IBUS_MOD2_MASK = 1 << 4,
|
||||||
|
IBUS_MOD3_MASK = 1 << 5,
|
||||||
|
IBUS_MOD4_MASK = 1 << 6,
|
||||||
|
IBUS_MOD5_MASK = 1 << 7,
|
||||||
|
IBUS_BUTTON1_MASK = 1 << 8,
|
||||||
|
IBUS_BUTTON2_MASK = 1 << 9,
|
||||||
|
IBUS_BUTTON3_MASK = 1 << 10,
|
||||||
|
IBUS_BUTTON4_MASK = 1 << 11,
|
||||||
|
IBUS_BUTTON5_MASK = 1 << 12,
|
||||||
|
|
||||||
|
/* The next few modifiers are used by XKB, so we skip to the end.
|
||||||
|
* Bits 15 - 23 are currently unused. Bit 29 is used internally.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ibus mask */
|
||||||
|
IBUS_HANDLED_MASK = 1 << 24,
|
||||||
|
IBUS_FORWARD_MASK = 1 << 25,
|
||||||
|
IBUS_IGNORED_MASK = IBUS_FORWARD_MASK,
|
||||||
|
|
||||||
|
IBUS_SUPER_MASK = 1 << 26,
|
||||||
|
IBUS_HYPER_MASK = 1 << 27,
|
||||||
|
IBUS_META_MASK = 1 << 28,
|
||||||
|
|
||||||
|
IBUS_RELEASE_MASK = 1 << 30,
|
||||||
|
|
||||||
|
IBUS_MODIFIER_MASK = 0x5f001fff
|
||||||
|
} IBusModifierType;
|
||||||
|
|
||||||
|
|
||||||
|
static inline uint32_t
|
||||||
|
ibus_key_state(unsigned int glfw_modifiers, int action) {
|
||||||
|
uint32_t ans = action == GLFW_RELEASE ? IBUS_RELEASE_MASK : 0;
|
||||||
|
#define M(g, i) if(glfw_modifiers & GLFW_MOD_##g) ans |= i
|
||||||
|
M(SHIFT, IBUS_SHIFT_MASK);
|
||||||
|
M(CAPS_LOCK, IBUS_LOCK_MASK);
|
||||||
|
M(CONTROL, IBUS_CONTROL_MASK);
|
||||||
|
M(ALT, IBUS_MOD1_MASK);
|
||||||
|
M(NUM_LOCK, IBUS_MOD2_MASK);
|
||||||
|
M(SUPER, IBUS_MOD4_MASK);
|
||||||
|
#undef M
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
key_event_processed(DBusMessage *msg, const char* errmsg, void *data) {
|
||||||
|
uint32_t handled = 0;
|
||||||
|
KeyEvent *ev = (KeyEvent*)data;
|
||||||
|
GLFWbool is_release = ev->action == GLFW_RELEASE;
|
||||||
|
GLFWbool failed = GLFW_FALSE;
|
||||||
|
if (errmsg) {
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR, "IBUS: Failed to process key with error: %s", errmsg);
|
||||||
|
failed = GLFW_TRUE;
|
||||||
|
} else {
|
||||||
|
glfw_dbus_get_args(msg, "Failed to get IBUS handled key from reply", DBUS_TYPE_BOOLEAN, &handled, DBUS_TYPE_INVALID);
|
||||||
|
debug("IBUS processed scancode: 0x%x release: %d handled: %u\n", ev->keycode, is_release, handled);
|
||||||
|
}
|
||||||
|
glfw_xkb_key_from_ime(ev, handled ? GLFW_TRUE : GLFW_FALSE, failed);
|
||||||
|
free(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
GLFWbool
|
||||||
|
ibus_process_key(const KeyEvent *ev_, _GLFWIBUSData *ibus) {
|
||||||
|
if (!check_connection(ibus)) return GLFW_FALSE;
|
||||||
|
KeyEvent *ev = malloc(sizeof(KeyEvent));
|
||||||
|
if (!ev) return GLFW_FALSE;
|
||||||
|
memcpy(ev, ev_, sizeof(KeyEvent));
|
||||||
|
uint32_t state = ibus_key_state(ev->glfw_modifiers, ev->action);
|
||||||
|
if (!glfw_dbus_call_method_with_reply(
|
||||||
|
ibus->conn, IBUS_SERVICE, ibus->input_ctx_path, IBUS_INPUT_INTERFACE, "ProcessKeyEvent",
|
||||||
|
3000, key_event_processed, ev,
|
||||||
|
DBUS_TYPE_UINT32, &ev->ibus_sym, DBUS_TYPE_UINT32, &ev->ibus_keycode, DBUS_TYPE_UINT32,
|
||||||
|
&state, DBUS_TYPE_INVALID)) {
|
||||||
|
free(ev);
|
||||||
|
return GLFW_FALSE;
|
||||||
|
}
|
||||||
|
return GLFW_TRUE;
|
||||||
|
}
|
||||||
55
glfw/ibus_glfw.h
vendored
Normal file
55
glfw/ibus_glfw.h
vendored
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
//========================================================================
|
||||||
|
// GLFW 3.3 XKB - www.glfw.org
|
||||||
|
//------------------------------------------------------------------------
|
||||||
|
// Copyright (c) 2018 Kovid Goyal <kovid@kovidgoyal.net>
|
||||||
|
//
|
||||||
|
// This software is provided 'as-is', without any express or implied
|
||||||
|
// warranty. In no event will the authors be held liable for any damages
|
||||||
|
// arising from the use of this software.
|
||||||
|
//
|
||||||
|
// Permission is granted to anyone to use this software for any purpose,
|
||||||
|
// including commercial applications, and to alter it and redistribute it
|
||||||
|
// freely, subject to the following restrictions:
|
||||||
|
//
|
||||||
|
// 1. The origin of this software must not be misrepresented; you must not
|
||||||
|
// claim that you wrote the original software. If you use this software
|
||||||
|
// in a product, an acknowledgment in the product documentation would
|
||||||
|
// be appreciated but is not required.
|
||||||
|
//
|
||||||
|
// 2. Altered source versions must be plainly marked as such, and must not
|
||||||
|
// be misrepresented as being the original software.
|
||||||
|
//
|
||||||
|
// 3. This notice may not be removed or altered from any source
|
||||||
|
// distribution.
|
||||||
|
//
|
||||||
|
//========================================================================
|
||||||
|
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "dbus_glfw.h"
|
||||||
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
GLFWbool ok, inited;
|
||||||
|
time_t address_file_mtime;
|
||||||
|
DBusConnection *conn;
|
||||||
|
const char *input_ctx_path, *address_file_name, *address;
|
||||||
|
} _GLFWIBUSData;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
xkb_keycode_t keycode, ibus_keycode;
|
||||||
|
xkb_keysym_t keysym, ibus_sym;
|
||||||
|
unsigned int glfw_modifiers;
|
||||||
|
int action;
|
||||||
|
GLFWid window_id;
|
||||||
|
int glfw_keycode;
|
||||||
|
char text[64];
|
||||||
|
} KeyEvent;
|
||||||
|
|
||||||
|
void glfw_connect_to_ibus(_GLFWIBUSData *ibus);
|
||||||
|
void glfw_ibus_terminate(_GLFWIBUSData *ibus);
|
||||||
|
void glfw_ibus_set_focused(_GLFWIBUSData *ibus, GLFWbool focused);
|
||||||
|
void glfw_ibus_dispatch(_GLFWIBUSData *ibus);
|
||||||
|
GLFWbool ibus_process_key(const KeyEvent *ev_, _GLFWIBUSData *ibus);
|
||||||
|
void glfw_ibus_set_cursor_geometry(_GLFWIBUSData *ibus, int x, int y, int w, int h);
|
||||||
9
glfw/init.c
vendored
9
glfw/init.c
vendored
@@ -52,6 +52,7 @@ static _GLFWinitconfig _glfwInitHints =
|
|||||||
{
|
{
|
||||||
GLFW_TRUE, // hat buttons
|
GLFW_TRUE, // hat buttons
|
||||||
GLFW_FALSE, // debug keyboard
|
GLFW_FALSE, // debug keyboard
|
||||||
|
GLFW_TRUE, // enable joystick
|
||||||
{
|
{
|
||||||
GLFW_TRUE, // macOS menu bar
|
GLFW_TRUE, // macOS menu bar
|
||||||
GLFW_TRUE // macOS bundle chdir
|
GLFW_TRUE // macOS bundle chdir
|
||||||
@@ -115,8 +116,9 @@ static void terminate(void)
|
|||||||
char* _glfw_strdup(const char* source)
|
char* _glfw_strdup(const char* source)
|
||||||
{
|
{
|
||||||
const size_t length = strlen(source);
|
const size_t length = strlen(source);
|
||||||
char* result = calloc(length + 1, 1);
|
char* result = malloc(length + 1);
|
||||||
strcpy(result, source);
|
memcpy(result, source, length);
|
||||||
|
result[length] = 0;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,6 +255,9 @@ GLFWAPI void glfwInitHint(int hint, int value)
|
|||||||
{
|
{
|
||||||
switch (hint)
|
switch (hint)
|
||||||
{
|
{
|
||||||
|
case GLFW_ENABLE_JOYSTICKS:
|
||||||
|
_glfwInitHints.enableJoysticks = value;
|
||||||
|
return;
|
||||||
case GLFW_JOYSTICK_HAT_BUTTONS:
|
case GLFW_JOYSTICK_HAT_BUTTONS:
|
||||||
_glfwInitHints.hatButtons = value;
|
_glfwInitHints.hatButtons = value;
|
||||||
return;
|
return;
|
||||||
|
|||||||
38
glfw/input.c
vendored
38
glfw/input.c
vendored
@@ -286,10 +286,10 @@ void _glfwInputKeyboard(_GLFWwindow* window, int key, int scancode, int action,
|
|||||||
|
|
||||||
// Notifies shared code of a scroll event
|
// Notifies shared code of a scroll event
|
||||||
//
|
//
|
||||||
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset)
|
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags)
|
||||||
{
|
{
|
||||||
if (window->callbacks.scroll)
|
if (window->callbacks.scroll)
|
||||||
window->callbacks.scroll((GLFWwindow*) window, xoffset, yoffset);
|
window->callbacks.scroll((GLFWwindow*) window, xoffset, yoffset, flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notifies shared code of a mouse button click event
|
// Notifies shared code of a mouse button click event
|
||||||
@@ -487,6 +487,7 @@ const char* _glfwGetKeyName(int key)
|
|||||||
case GLFW_KEY_SLASH: return "SLASH";
|
case GLFW_KEY_SLASH: return "SLASH";
|
||||||
case GLFW_KEY_WORLD_1: return "WORLD 1";
|
case GLFW_KEY_WORLD_1: return "WORLD 1";
|
||||||
case GLFW_KEY_WORLD_2: return "WORLD 2";
|
case GLFW_KEY_WORLD_2: return "WORLD 2";
|
||||||
|
case GLFW_KEY_PLUS: return "PLUS";
|
||||||
|
|
||||||
// Function keys
|
// Function keys
|
||||||
case GLFW_KEY_ESCAPE: return "ESCAPE";
|
case GLFW_KEY_ESCAPE: return "ESCAPE";
|
||||||
@@ -799,11 +800,12 @@ GLFWAPI void glfwSetCursorPos(GLFWwindow* handle, double xpos, double ypos)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot)
|
GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot, int count)
|
||||||
{
|
{
|
||||||
_GLFWcursor* cursor;
|
_GLFWcursor* cursor;
|
||||||
|
|
||||||
assert(image != NULL);
|
assert(image != NULL);
|
||||||
|
assert(count > 0);
|
||||||
|
|
||||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||||
|
|
||||||
@@ -811,7 +813,7 @@ GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot)
|
|||||||
cursor->next = _glfw.cursorListHead;
|
cursor->next = _glfw.cursorListHead;
|
||||||
_glfw.cursorListHead = cursor;
|
_glfw.cursorListHead = cursor;
|
||||||
|
|
||||||
if (!_glfwPlatformCreateCursor(cursor, image, xhot, yhot))
|
if (!_glfwPlatformCreateCursor(cursor, image, xhot, yhot, count))
|
||||||
{
|
{
|
||||||
glfwDestroyCursor((GLFWcursor*) cursor);
|
glfwDestroyCursor((GLFWcursor*) cursor);
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -908,6 +910,18 @@ GLFWAPI GLFWkeyboardfun glfwSetKeyboardCallback(GLFWwindow* handle, GLFWkeyboard
|
|||||||
return cbfun;
|
return cbfun;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GLFWAPI void glfwUpdateIMEState(GLFWwindow* handle, int which, int a, int b, int c, int d) {
|
||||||
|
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||||
|
assert(window != NULL);
|
||||||
|
|
||||||
|
_GLFW_REQUIRE_INIT();
|
||||||
|
#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND)
|
||||||
|
_glfwPlatformUpdateIMEState(window, which, a, b, c, d);
|
||||||
|
#else
|
||||||
|
(void)window; (void)which; (void)a; (void)b; (void)c; (void)d;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
GLFWAPI GLFWmousebuttonfun glfwSetMouseButtonCallback(GLFWwindow* handle,
|
GLFWAPI GLFWmousebuttonfun glfwSetMouseButtonCallback(GLFWwindow* handle,
|
||||||
GLFWmousebuttonfun cbfun)
|
GLFWmousebuttonfun cbfun)
|
||||||
{
|
{
|
||||||
@@ -1362,6 +1376,22 @@ GLFWAPI const char* glfwGetClipboardString(GLFWwindow* handle)
|
|||||||
return _glfwPlatformGetClipboardString();
|
return _glfwPlatformGetClipboardString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND)
|
||||||
|
GLFWAPI void glfwSetPrimarySelectionString(GLFWwindow* handle, const char* string)
|
||||||
|
{
|
||||||
|
assert(string != NULL);
|
||||||
|
|
||||||
|
_GLFW_REQUIRE_INIT();
|
||||||
|
_glfwPlatformSetPrimarySelectionString(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
GLFWAPI const char* glfwGetPrimarySelectionString(GLFWwindow* handle)
|
||||||
|
{
|
||||||
|
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||||
|
return _glfwPlatformGetPrimarySelectionString();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
GLFWAPI double glfwGetTime(void)
|
GLFWAPI double glfwGetTime(void)
|
||||||
{
|
{
|
||||||
_GLFW_REQUIRE_INIT_OR_RETURN(0.0);
|
_GLFW_REQUIRE_INIT_OR_RETURN(0.0);
|
||||||
|
|||||||
29
glfw/internal.h
vendored
29
glfw/internal.h
vendored
@@ -31,6 +31,8 @@
|
|||||||
#include "glfw_config.h"
|
#include "glfw_config.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define arraysz(x) (sizeof(x)/sizeof(x[0]))
|
||||||
|
|
||||||
#if defined(GLFW_INCLUDE_GLCOREARB) || \
|
#if defined(GLFW_INCLUDE_GLCOREARB) || \
|
||||||
defined(GLFW_INCLUDE_ES1) || \
|
defined(GLFW_INCLUDE_ES1) || \
|
||||||
defined(GLFW_INCLUDE_ES2) || \
|
defined(GLFW_INCLUDE_ES2) || \
|
||||||
@@ -59,6 +61,7 @@
|
|||||||
#define _GLFW_MESSAGE_SIZE 1024
|
#define _GLFW_MESSAGE_SIZE 1024
|
||||||
|
|
||||||
typedef int GLFWbool;
|
typedef int GLFWbool;
|
||||||
|
typedef unsigned long long GLFWid;
|
||||||
|
|
||||||
typedef struct _GLFWerror _GLFWerror;
|
typedef struct _GLFWerror _GLFWerror;
|
||||||
typedef struct _GLFWinitconfig _GLFWinitconfig;
|
typedef struct _GLFWinitconfig _GLFWinitconfig;
|
||||||
@@ -126,7 +129,6 @@ typedef enum VkStructureType
|
|||||||
VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR = 1000004000,
|
VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR = 1000004000,
|
||||||
VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR = 1000005000,
|
VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR = 1000005000,
|
||||||
VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR = 1000006000,
|
VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR = 1000006000,
|
||||||
VK_STRUCTURE_TYPE_MIR_SURFACE_CREATE_INFO_KHR = 1000007000,
|
|
||||||
VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR = 1000009000,
|
VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR = 1000009000,
|
||||||
VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK = 1000123000,
|
VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK = 1000123000,
|
||||||
VK_STRUCTURE_TYPE_MAX_ENUM = 0x7FFFFFFF
|
VK_STRUCTURE_TYPE_MAX_ENUM = 0x7FFFFFFF
|
||||||
@@ -188,8 +190,6 @@ typedef void (APIENTRY * PFN_vkVoidFunction)(void);
|
|||||||
#include "x11_platform.h"
|
#include "x11_platform.h"
|
||||||
#elif defined(_GLFW_WAYLAND)
|
#elif defined(_GLFW_WAYLAND)
|
||||||
#include "wl_platform.h"
|
#include "wl_platform.h"
|
||||||
#elif defined(_GLFW_MIR)
|
|
||||||
#include "mir_platform.h"
|
|
||||||
#elif defined(_GLFW_OSMESA)
|
#elif defined(_GLFW_OSMESA)
|
||||||
#include "null_platform.h"
|
#include "null_platform.h"
|
||||||
#else
|
#else
|
||||||
@@ -243,6 +243,7 @@ struct _GLFWinitconfig
|
|||||||
{
|
{
|
||||||
GLFWbool hatButtons;
|
GLFWbool hatButtons;
|
||||||
GLFWbool debugKeyboard;
|
GLFWbool debugKeyboard;
|
||||||
|
GLFWbool enableJoysticks;
|
||||||
struct {
|
struct {
|
||||||
GLFWbool menubar;
|
GLFWbool menubar;
|
||||||
GLFWbool chdir;
|
GLFWbool chdir;
|
||||||
@@ -269,6 +270,7 @@ struct _GLFWwndconfig
|
|||||||
GLFWbool maximized;
|
GLFWbool maximized;
|
||||||
GLFWbool centerCursor;
|
GLFWbool centerCursor;
|
||||||
GLFWbool focusOnShow;
|
GLFWbool focusOnShow;
|
||||||
|
GLFWbool scaleToMonitor;
|
||||||
struct {
|
struct {
|
||||||
GLFWbool retina;
|
GLFWbool retina;
|
||||||
char frameName[256];
|
char frameName[256];
|
||||||
@@ -277,6 +279,9 @@ struct _GLFWwndconfig
|
|||||||
char className[256];
|
char className[256];
|
||||||
char instanceName[256];
|
char instanceName[256];
|
||||||
} x11;
|
} x11;
|
||||||
|
struct {
|
||||||
|
char appId[256];
|
||||||
|
} wl;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Context configuration
|
// Context configuration
|
||||||
@@ -377,6 +382,7 @@ struct _GLFWwindow
|
|||||||
GLFWbool focusOnShow;
|
GLFWbool focusOnShow;
|
||||||
GLFWbool shouldClose;
|
GLFWbool shouldClose;
|
||||||
void* userPointer;
|
void* userPointer;
|
||||||
|
GLFWid id;
|
||||||
GLFWvidmode videoMode;
|
GLFWvidmode videoMode;
|
||||||
_GLFWmonitor* monitor;
|
_GLFWmonitor* monitor;
|
||||||
_GLFWcursor* cursor;
|
_GLFWcursor* cursor;
|
||||||
@@ -525,6 +531,7 @@ struct _GLFWlibrary
|
|||||||
_GLFWerror* errorListHead;
|
_GLFWerror* errorListHead;
|
||||||
_GLFWcursor* cursorListHead;
|
_GLFWcursor* cursorListHead;
|
||||||
_GLFWwindow* windowListHead;
|
_GLFWwindow* windowListHead;
|
||||||
|
GLFWid focusedWindowId;
|
||||||
|
|
||||||
_GLFWmonitor** monitors;
|
_GLFWmonitor** monitors;
|
||||||
int monitorCount;
|
int monitorCount;
|
||||||
@@ -561,8 +568,6 @@ struct _GLFWlibrary
|
|||||||
GLFWbool KHR_xcb_surface;
|
GLFWbool KHR_xcb_surface;
|
||||||
#elif defined(_GLFW_WAYLAND)
|
#elif defined(_GLFW_WAYLAND)
|
||||||
GLFWbool KHR_wayland_surface;
|
GLFWbool KHR_wayland_surface;
|
||||||
#elif defined(_GLFW_MIR)
|
|
||||||
GLFWbool KHR_mir_surface;
|
|
||||||
#endif
|
#endif
|
||||||
} vk;
|
} vk;
|
||||||
|
|
||||||
@@ -600,7 +605,7 @@ void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos);
|
|||||||
void _glfwPlatformSetCursorPos(_GLFWwindow* window, double xpos, double ypos);
|
void _glfwPlatformSetCursorPos(_GLFWwindow* window, double xpos, double ypos);
|
||||||
void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode);
|
void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode);
|
||||||
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||||
const GLFWimage* image, int xhot, int yhot);
|
const GLFWimage* image, int xhot, int yhot, int count);
|
||||||
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape);
|
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape);
|
||||||
void _glfwPlatformDestroyCursor(_GLFWcursor* cursor);
|
void _glfwPlatformDestroyCursor(_GLFWcursor* cursor);
|
||||||
void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor);
|
void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor);
|
||||||
@@ -614,11 +619,15 @@ void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor,
|
|||||||
float* xscale, float* yscale);
|
float* xscale, float* yscale);
|
||||||
GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count);
|
GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count);
|
||||||
void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode);
|
void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode);
|
||||||
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp);
|
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp);
|
||||||
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp);
|
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp);
|
||||||
|
|
||||||
void _glfwPlatformSetClipboardString(const char* string);
|
void _glfwPlatformSetClipboardString(const char* string);
|
||||||
const char* _glfwPlatformGetClipboardString(void);
|
const char* _glfwPlatformGetClipboardString(void);
|
||||||
|
#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND)
|
||||||
|
void _glfwPlatformSetPrimarySelectionString(const char* string);
|
||||||
|
const char* _glfwPlatformGetPrimarySelectionString(void);
|
||||||
|
#endif
|
||||||
|
|
||||||
int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode);
|
int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode);
|
||||||
void _glfwPlatformUpdateGamepadGUID(char* guid);
|
void _glfwPlatformUpdateGamepadGUID(char* guid);
|
||||||
@@ -648,6 +657,7 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
|||||||
int* right, int* bottom);
|
int* right, int* bottom);
|
||||||
void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
|
void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
|
||||||
float* xscale, float* yscale);
|
float* xscale, float* yscale);
|
||||||
|
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window);
|
||||||
void _glfwPlatformIconifyWindow(_GLFWwindow* window);
|
void _glfwPlatformIconifyWindow(_GLFWwindow* window);
|
||||||
void _glfwPlatformRestoreWindow(_GLFWwindow* window);
|
void _glfwPlatformRestoreWindow(_GLFWwindow* window);
|
||||||
void _glfwPlatformMaximizeWindow(_GLFWwindow* window);
|
void _glfwPlatformMaximizeWindow(_GLFWwindow* window);
|
||||||
@@ -670,6 +680,7 @@ void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled);
|
|||||||
void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled);
|
void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled);
|
||||||
void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled);
|
void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled);
|
||||||
void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity);
|
void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity);
|
||||||
|
void _glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int d);
|
||||||
|
|
||||||
void _glfwPlatformPollEvents(void);
|
void _glfwPlatformPollEvents(void);
|
||||||
void _glfwPlatformWaitEvents(void);
|
void _glfwPlatformWaitEvents(void);
|
||||||
@@ -713,7 +724,7 @@ void _glfwInputWindowCloseRequest(_GLFWwindow* window);
|
|||||||
void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor);
|
void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor);
|
||||||
|
|
||||||
void _glfwInputKeyboard(_GLFWwindow* window, int key, int scancode, int action, int mods, const char* text, int state);
|
void _glfwInputKeyboard(_GLFWwindow* window, int key, int scancode, int action, int mods, const char* text, int state);
|
||||||
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset);
|
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags);
|
||||||
void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods);
|
void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods);
|
||||||
void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos);
|
void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos);
|
||||||
void _glfwInputCursorEnter(_GLFWwindow* window, GLFWbool entered);
|
void _glfwInputCursorEnter(_GLFWwindow* window, GLFWbool entered);
|
||||||
@@ -766,5 +777,7 @@ const char* _glfwGetKeyName(int key);
|
|||||||
GLFWbool _glfwInitVulkan(int mode);
|
GLFWbool _glfwInitVulkan(int mode);
|
||||||
void _glfwTerminateVulkan(void);
|
void _glfwTerminateVulkan(void);
|
||||||
const char* _glfwGetVulkanResultString(VkResult result);
|
const char* _glfwGetVulkanResultString(VkResult result);
|
||||||
|
_GLFWwindow* _glfwFocusedWindow();
|
||||||
|
_GLFWwindow* _glfwWindowForId(GLFWid id);
|
||||||
|
|
||||||
char* _glfw_strdup(const char* source);
|
char* _glfw_strdup(const char* source);
|
||||||
|
|||||||
3
glfw/linux_joystick.c
vendored
3
glfw/linux_joystick.c
vendored
@@ -228,7 +228,7 @@ static GLFWbool openJoystickDevice(const char* path)
|
|||||||
return GLFW_FALSE;
|
return GLFW_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
strncpy(linjs.path, path, sizeof(linjs.path));
|
strncpy(linjs.path, path, sizeof(linjs.path) - 1);
|
||||||
memcpy(&js->linjs, &linjs, sizeof(linjs));
|
memcpy(&js->linjs, &linjs, sizeof(linjs));
|
||||||
|
|
||||||
pollAbsState(js);
|
pollAbsState(js);
|
||||||
@@ -431,4 +431,3 @@ int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode)
|
|||||||
void _glfwPlatformUpdateGamepadGUID(char* guid)
|
void _glfwPlatformUpdateGamepadGUID(char* guid)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
54
glfw/memfd.h
vendored
Normal file
54
glfw/memfd.h
vendored
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
*
|
||||||
|
* Distributed under terms of the GPL3 license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef HAS_MEMFD_CREATE
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/syscall.h>
|
||||||
|
static inline int memfd_create(const char *name, unsigned int flags) {
|
||||||
|
return syscall(__NR_memfd_create, name, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef F_LINUX_SPECIFIC_BASE
|
||||||
|
#define F_LINUX_SPECIFIC_BASE 1024
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef F_ADD_SEALS
|
||||||
|
#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9)
|
||||||
|
#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10)
|
||||||
|
|
||||||
|
#define F_SEAL_SEAL 0x0001 /* prevent further seals from being set */
|
||||||
|
#define F_SEAL_SHRINK 0x0002 /* prevent file from shrinking */
|
||||||
|
#define F_SEAL_GROW 0x0004 /* prevent file from growing */
|
||||||
|
#define F_SEAL_WRITE 0x0008 /* prevent writes */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef MFD_CLOEXEC
|
||||||
|
#define MFD_CLOEXEC 0x0001U
|
||||||
|
#define MFD_ALLOW_SEALING 0x0002U
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
createTmpfileCloexec(char* tmpname)
|
||||||
|
{
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
fd = mkostemp(tmpname, O_CLOEXEC);
|
||||||
|
if (fd >= 0)
|
||||||
|
unlink(tmpname);
|
||||||
|
|
||||||
|
return fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
27
glfw/monitor.c
vendored
27
glfw/monitor.c
vendored
@@ -427,11 +427,11 @@ GLFWAPI const GLFWvidmode* glfwGetVideoMode(GLFWmonitor* handle)
|
|||||||
|
|
||||||
GLFWAPI void glfwSetGamma(GLFWmonitor* handle, float gamma)
|
GLFWAPI void glfwSetGamma(GLFWmonitor* handle, float gamma)
|
||||||
{
|
{
|
||||||
int i;
|
unsigned int i;
|
||||||
unsigned short values[256];
|
unsigned short* values;
|
||||||
GLFWgammaramp ramp;
|
GLFWgammaramp ramp;
|
||||||
|
const GLFWgammaramp* original;
|
||||||
assert(handle != NULL);
|
assert(handle != NULL);
|
||||||
assert(gamma == gamma);
|
|
||||||
assert(gamma >= 0.f);
|
assert(gamma >= 0.f);
|
||||||
assert(gamma <= FLT_MAX);
|
assert(gamma <= FLT_MAX);
|
||||||
|
|
||||||
@@ -442,13 +442,18 @@ GLFWAPI void glfwSetGamma(GLFWmonitor* handle, float gamma)
|
|||||||
_glfwInputError(GLFW_INVALID_VALUE, "Invalid gamma value %f", gamma);
|
_glfwInputError(GLFW_INVALID_VALUE, "Invalid gamma value %f", gamma);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
original = glfwGetGammaRamp(handle);
|
||||||
|
if (!original)
|
||||||
|
return;
|
||||||
|
|
||||||
for (i = 0; i < 256; i++)
|
values = calloc(original->size, sizeof(unsigned short));
|
||||||
|
|
||||||
|
for (i = 0; i < original->size; i++)
|
||||||
{
|
{
|
||||||
float value;
|
float value;
|
||||||
|
|
||||||
// Calculate intensity
|
// Calculate intensity
|
||||||
value = i / 255.f;
|
value = i / (float) (original->size - 1);
|
||||||
// Apply gamma curve
|
// Apply gamma curve
|
||||||
value = powf(value, 1.f / gamma) * 65535.f + 0.5f;
|
value = powf(value, 1.f / gamma) * 65535.f + 0.5f;
|
||||||
|
|
||||||
@@ -462,9 +467,10 @@ GLFWAPI void glfwSetGamma(GLFWmonitor* handle, float gamma)
|
|||||||
ramp.red = values;
|
ramp.red = values;
|
||||||
ramp.green = values;
|
ramp.green = values;
|
||||||
ramp.blue = values;
|
ramp.blue = values;
|
||||||
ramp.size = 256;
|
ramp.size = original->size;
|
||||||
|
|
||||||
glfwSetGammaRamp(handle, &ramp);
|
glfwSetGammaRamp(handle, &ramp);
|
||||||
|
free(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
GLFWAPI const GLFWgammaramp* glfwGetGammaRamp(GLFWmonitor* handle)
|
GLFWAPI const GLFWgammaramp* glfwGetGammaRamp(GLFWmonitor* handle)
|
||||||
@@ -475,7 +481,8 @@ GLFWAPI const GLFWgammaramp* glfwGetGammaRamp(GLFWmonitor* handle)
|
|||||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||||
|
|
||||||
_glfwFreeGammaArrays(&monitor->currentRamp);
|
_glfwFreeGammaArrays(&monitor->currentRamp);
|
||||||
_glfwPlatformGetGammaRamp(monitor, &monitor->currentRamp);
|
if (!_glfwPlatformGetGammaRamp(monitor, &monitor->currentRamp))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
return &monitor->currentRamp;
|
return &monitor->currentRamp;
|
||||||
}
|
}
|
||||||
@@ -501,8 +508,10 @@ GLFWAPI void glfwSetGammaRamp(GLFWmonitor* handle, const GLFWgammaramp* ramp)
|
|||||||
_GLFW_REQUIRE_INIT();
|
_GLFW_REQUIRE_INIT();
|
||||||
|
|
||||||
if (!monitor->originalRamp.size)
|
if (!monitor->originalRamp.size)
|
||||||
_glfwPlatformGetGammaRamp(monitor, &monitor->originalRamp);
|
{
|
||||||
|
if (!_glfwPlatformGetGammaRamp(monitor, &monitor->originalRamp))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_glfwPlatformSetGammaRamp(monitor, ramp);
|
_glfwPlatformSetGammaRamp(monitor, ramp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,11 @@
|
|||||||
#include "internal.h"
|
#include "internal.h"
|
||||||
|
|
||||||
|
|
||||||
|
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101400)
|
||||||
|
#define NSOpenGLContextParameterSwapInterval NSOpenGLCPSwapInterval
|
||||||
|
#define NSOpenGLContextParameterSurfaceOpacity NSOpenGLCPSurfaceOpacity
|
||||||
|
#endif
|
||||||
|
|
||||||
static void makeContextCurrentNSGL(_GLFWwindow* window)
|
static void makeContextCurrentNSGL(_GLFWwindow* window)
|
||||||
{
|
{
|
||||||
if (window)
|
if (window)
|
||||||
@@ -49,7 +54,7 @@ static void swapIntervalNSGL(int interval)
|
|||||||
|
|
||||||
GLint sync = interval;
|
GLint sync = interval;
|
||||||
[window->context.nsgl.object setValues:&sync
|
[window->context.nsgl.object setValues:&sync
|
||||||
forParameter:NSOpenGLCPSwapInterval];
|
forParameter:NSOpenGLContextParameterSwapInterval];
|
||||||
}
|
}
|
||||||
|
|
||||||
static int extensionSupportedNSGL(const char* extension)
|
static int extensionSupportedNSGL(const char* extension)
|
||||||
@@ -299,7 +304,7 @@ GLFWbool _glfwCreateContextNSGL(_GLFWwindow* window,
|
|||||||
if (fbconfig->transparent)
|
if (fbconfig->transparent)
|
||||||
{
|
{
|
||||||
GLint opaque = 0;
|
GLint opaque = 0;
|
||||||
[window->context.nsgl.object setValues:&opaque forParameter:NSOpenGLCPSurfaceOpacity];
|
[window->context.nsgl.object setValues:&opaque forParameter:NSOpenGLContextParameterSurfaceOpacity];
|
||||||
}
|
}
|
||||||
|
|
||||||
[window->context.nsgl.object setView:window->ns.view];
|
[window->context.nsgl.object setView:window->ns.view];
|
||||||
@@ -332,4 +337,3 @@ GLFWAPI id glfwGetNSGLContext(GLFWwindow* handle)
|
|||||||
|
|
||||||
return window->context.nsgl.object;
|
return window->context.nsgl.object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
4
glfw/null_monitor.c
vendored
4
glfw/null_monitor.c
vendored
@@ -58,11 +58,11 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode)
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||||
{
|
{
|
||||||
|
return GLFW_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
|
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
7
glfw/null_window.c
vendored
7
glfw/null_window.c
vendored
@@ -148,6 +148,11 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
|
|||||||
*yscale = 1.f;
|
*yscale = 1.f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window)
|
||||||
|
{
|
||||||
|
return 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
|
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -267,7 +272,7 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
|
|||||||
|
|
||||||
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||||
const GLFWimage* image,
|
const GLFWimage* image,
|
||||||
int xhot, int yhot)
|
int xhot, int yhot, int count)
|
||||||
{
|
{
|
||||||
return GLFW_TRUE;
|
return GLFW_TRUE;
|
||||||
}
|
}
|
||||||
|
|||||||
3
glfw/osmesa_context.c
vendored
3
glfw/osmesa_context.c
vendored
@@ -240,7 +240,7 @@ GLFWbool _glfwCreateContextOSMesa(_GLFWwindow* window,
|
|||||||
if (ctxconfig->forward)
|
if (ctxconfig->forward)
|
||||||
{
|
{
|
||||||
_glfwInputError(GLFW_VERSION_UNAVAILABLE,
|
_glfwInputError(GLFW_VERSION_UNAVAILABLE,
|
||||||
"OSMesa: Foward-compatible contexts not supported");
|
"OSMesa: Forward-compatible contexts not supported");
|
||||||
return GLFW_FALSE;
|
return GLFW_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,4 +367,3 @@ GLFWAPI OSMesaContext glfwGetOSMesaContext(GLFWwindow* handle)
|
|||||||
|
|
||||||
return window->context.osmesa.handle;
|
return window->context.osmesa.handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,8 @@
|
|||||||
"posix_time.h",
|
"posix_time.h",
|
||||||
"posix_thread.h",
|
"posix_thread.h",
|
||||||
"xkb_glfw.h",
|
"xkb_glfw.h",
|
||||||
|
"dbus_glfw.h",
|
||||||
|
"ibus_glfw.h",
|
||||||
"backend_utils.h",
|
"backend_utils.h",
|
||||||
"egl_context.h",
|
"egl_context.h",
|
||||||
"osmesa_context.h",
|
"osmesa_context.h",
|
||||||
@@ -69,7 +71,9 @@
|
|||||||
"stable/viewporter/viewporter.xml",
|
"stable/viewporter/viewporter.xml",
|
||||||
"unstable/relative-pointer/relative-pointer-unstable-v1.xml",
|
"unstable/relative-pointer/relative-pointer-unstable-v1.xml",
|
||||||
"unstable/pointer-constraints/pointer-constraints-unstable-v1.xml",
|
"unstable/pointer-constraints/pointer-constraints-unstable-v1.xml",
|
||||||
"unstable/idle-inhibit/idle-inhibit-unstable-v1.xml"
|
"unstable/idle-inhibit/idle-inhibit-unstable-v1.xml",
|
||||||
|
"unstable/xdg-decoration/xdg-decoration-unstable-v1.xml",
|
||||||
|
"unstable/primary-selection/primary-selection-unstable-v1.xml"
|
||||||
],
|
],
|
||||||
"sources": [
|
"sources": [
|
||||||
"wl_init.c",
|
"wl_init.c",
|
||||||
@@ -78,15 +82,18 @@
|
|||||||
"posix_time.c",
|
"posix_time.c",
|
||||||
"posix_thread.c",
|
"posix_thread.c",
|
||||||
"xkb_glfw.c",
|
"xkb_glfw.c",
|
||||||
|
"dbus_glfw.c",
|
||||||
|
"ibus_glfw.c",
|
||||||
"egl_context.c",
|
"egl_context.c",
|
||||||
"osmesa_context.c",
|
"osmesa_context.c",
|
||||||
|
"backend_utils.c",
|
||||||
"linux_joystick.c",
|
"linux_joystick.c",
|
||||||
"null_joystick.c"
|
"null_joystick.c"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"wayland_protocols": [
|
"wayland_protocols": [
|
||||||
1,
|
1,
|
||||||
12
|
17
|
||||||
],
|
],
|
||||||
"win32": {
|
"win32": {
|
||||||
"headers": [
|
"headers": [
|
||||||
@@ -112,6 +119,8 @@
|
|||||||
"headers": [
|
"headers": [
|
||||||
"x11_platform.h",
|
"x11_platform.h",
|
||||||
"xkb_glfw.h",
|
"xkb_glfw.h",
|
||||||
|
"dbus_glfw.h",
|
||||||
|
"ibus_glfw.h",
|
||||||
"backend_utils.h",
|
"backend_utils.h",
|
||||||
"posix_time.h",
|
"posix_time.h",
|
||||||
"posix_thread.h",
|
"posix_thread.h",
|
||||||
@@ -126,11 +135,14 @@
|
|||||||
"x11_monitor.c",
|
"x11_monitor.c",
|
||||||
"x11_window.c",
|
"x11_window.c",
|
||||||
"xkb_glfw.c",
|
"xkb_glfw.c",
|
||||||
|
"dbus_glfw.c",
|
||||||
|
"ibus_glfw.c",
|
||||||
"posix_time.c",
|
"posix_time.c",
|
||||||
"posix_thread.c",
|
"posix_thread.c",
|
||||||
"glx_context.c",
|
"glx_context.c",
|
||||||
"egl_context.c",
|
"egl_context.c",
|
||||||
"osmesa_context.c",
|
"osmesa_context.c",
|
||||||
|
"backend_utils.c",
|
||||||
"linux_joystick.c",
|
"linux_joystick.c",
|
||||||
"null_joystick.c"
|
"null_joystick.c"
|
||||||
]
|
]
|
||||||
|
|||||||
4
glfw/vulkan.c
vendored
4
glfw/vulkan.c
vendored
@@ -136,9 +136,6 @@ GLFWbool _glfwInitVulkan(int mode)
|
|||||||
#elif defined(_GLFW_WAYLAND)
|
#elif defined(_GLFW_WAYLAND)
|
||||||
else if (strcmp(ep[i].extensionName, "VK_KHR_wayland_surface") == 0)
|
else if (strcmp(ep[i].extensionName, "VK_KHR_wayland_surface") == 0)
|
||||||
_glfw.vk.KHR_wayland_surface = GLFW_TRUE;
|
_glfw.vk.KHR_wayland_surface = GLFW_TRUE;
|
||||||
#elif defined(_GLFW_MIR)
|
|
||||||
else if (strcmp(ep[i].extensionName, "VK_KHR_mir_surface") == 0)
|
|
||||||
_glfw.vk.KHR_mir_surface = GLFW_TRUE;
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -326,4 +323,3 @@ GLFWAPI VkResult glfwCreateWindowSurface(VkInstance instance,
|
|||||||
|
|
||||||
return _glfwPlatformCreateWindowSurface(instance, window, allocator, surface);
|
return _glfwPlatformCreateWindowSurface(instance, window, allocator, surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
34
glfw/wgl_context.c
vendored
34
glfw/wgl_context.c
vendored
@@ -256,12 +256,20 @@ static void makeContextCurrentWGL(_GLFWwindow* window)
|
|||||||
|
|
||||||
static void swapBuffersWGL(_GLFWwindow* window)
|
static void swapBuffersWGL(_GLFWwindow* window)
|
||||||
{
|
{
|
||||||
// HACK: Use DwmFlush when desktop composition is enabled
|
if (!window->monitor)
|
||||||
if (_glfwIsCompositionEnabledWin32() && !window->monitor)
|
|
||||||
{
|
{
|
||||||
int count = abs(window->context.wgl.interval);
|
if (IsWindowsVistaOrGreater())
|
||||||
while (count--)
|
{
|
||||||
DwmFlush();
|
BOOL enabled;
|
||||||
|
|
||||||
|
// HACK: Use DwmFlush when desktop composition is enabled
|
||||||
|
if (SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled)
|
||||||
|
{
|
||||||
|
int count = abs(window->context.wgl.interval);
|
||||||
|
while (count--)
|
||||||
|
DwmFlush();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SwapBuffers(window->context.wgl.dc);
|
SwapBuffers(window->context.wgl.dc);
|
||||||
@@ -273,10 +281,18 @@ static void swapIntervalWGL(int interval)
|
|||||||
|
|
||||||
window->context.wgl.interval = interval;
|
window->context.wgl.interval = interval;
|
||||||
|
|
||||||
// HACK: Disable WGL swap interval when desktop composition is enabled to
|
if (!window->monitor)
|
||||||
// avoid interfering with DWM vsync
|
{
|
||||||
if (_glfwIsCompositionEnabledWin32() && !window->monitor)
|
if (IsWindowsVistaOrGreater())
|
||||||
interval = 0;
|
{
|
||||||
|
BOOL enabled;
|
||||||
|
|
||||||
|
// HACK: Disable WGL swap interval when desktop composition is enabled to
|
||||||
|
// avoid interfering with DWM vsync
|
||||||
|
if (SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled)
|
||||||
|
interval = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_glfw.wgl.EXT_swap_control)
|
if (_glfw.wgl.EXT_swap_control)
|
||||||
_glfw.wgl.SwapIntervalEXT(interval);
|
_glfw.wgl.SwapIntervalEXT(interval);
|
||||||
|
|||||||
68
glfw/win32_init.c
vendored
68
glfw/win32_init.c
vendored
@@ -62,17 +62,6 @@ BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved)
|
|||||||
|
|
||||||
#endif // _GLFW_BUILD_DLL
|
#endif // _GLFW_BUILD_DLL
|
||||||
|
|
||||||
// HACK: Define versionhelpers.h functions manually as MinGW lacks the header
|
|
||||||
BOOL IsWindowsVersionOrGreater(WORD major, WORD minor, WORD sp)
|
|
||||||
{
|
|
||||||
OSVERSIONINFOEXW osvi = { sizeof(osvi), major, minor, 0, 0, {0}, sp };
|
|
||||||
DWORD mask = VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR;
|
|
||||||
ULONGLONG cond = VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL);
|
|
||||||
cond = VerSetConditionMask(cond, VER_MINORVERSION, VER_GREATER_EQUAL);
|
|
||||||
cond = VerSetConditionMask(cond, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
|
|
||||||
return VerifyVersionInfoW(&osvi, mask, cond);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load necessary libraries (DLLs)
|
// Load necessary libraries (DLLs)
|
||||||
//
|
//
|
||||||
static GLFWbool loadLibraries(void)
|
static GLFWbool loadLibraries(void)
|
||||||
@@ -100,6 +89,14 @@ static GLFWbool loadLibraries(void)
|
|||||||
GetProcAddress(_glfw.win32.user32.instance, "SetProcessDPIAware");
|
GetProcAddress(_glfw.win32.user32.instance, "SetProcessDPIAware");
|
||||||
_glfw.win32.user32.ChangeWindowMessageFilterEx_ = (PFN_ChangeWindowMessageFilterEx)
|
_glfw.win32.user32.ChangeWindowMessageFilterEx_ = (PFN_ChangeWindowMessageFilterEx)
|
||||||
GetProcAddress(_glfw.win32.user32.instance, "ChangeWindowMessageFilterEx");
|
GetProcAddress(_glfw.win32.user32.instance, "ChangeWindowMessageFilterEx");
|
||||||
|
_glfw.win32.user32.EnableNonClientDpiScaling_ = (PFN_EnableNonClientDpiScaling)
|
||||||
|
GetProcAddress(_glfw.win32.user32.instance, "EnableNonClientDpiScaling");
|
||||||
|
_glfw.win32.user32.SetProcessDpiAwarenessContext_ = (PFN_SetProcessDpiAwarenessContext)
|
||||||
|
GetProcAddress(_glfw.win32.user32.instance, "SetProcessDpiAwarenessContext");
|
||||||
|
_glfw.win32.user32.GetDpiForWindow_ = (PFN_GetDpiForWindow)
|
||||||
|
GetProcAddress(_glfw.win32.user32.instance, "GetDpiForWindow");
|
||||||
|
_glfw.win32.user32.AdjustWindowRectExForDpi_ = (PFN_AdjustWindowRectExForDpi)
|
||||||
|
GetProcAddress(_glfw.win32.user32.instance, "AdjustWindowRectExForDpi");
|
||||||
|
|
||||||
_glfw.win32.dinput8.instance = LoadLibraryA("dinput8.dll");
|
_glfw.win32.dinput8.instance = LoadLibraryA("dinput8.dll");
|
||||||
if (_glfw.win32.dinput8.instance)
|
if (_glfw.win32.dinput8.instance)
|
||||||
@@ -155,6 +152,13 @@ static GLFWbool loadLibraries(void)
|
|||||||
GetProcAddress(_glfw.win32.shcore.instance, "GetDpiForMonitor");
|
GetProcAddress(_glfw.win32.shcore.instance, "GetDpiForMonitor");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_glfw.win32.ntdll.instance = LoadLibraryA("ntdll.dll");
|
||||||
|
if (_glfw.win32.ntdll.instance)
|
||||||
|
{
|
||||||
|
_glfw.win32.ntdll.RtlVerifyVersionInfo_ = (PFN_RtlVerifyVersionInfo)
|
||||||
|
GetProcAddress(_glfw.win32.ntdll.instance, "RtlVerifyVersionInfo");
|
||||||
|
}
|
||||||
|
|
||||||
return GLFW_TRUE;
|
return GLFW_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,6 +183,9 @@ static void freeLibraries(void)
|
|||||||
|
|
||||||
if (_glfw.win32.shcore.instance)
|
if (_glfw.win32.shcore.instance)
|
||||||
FreeLibrary(_glfw.win32.shcore.instance);
|
FreeLibrary(_glfw.win32.shcore.instance);
|
||||||
|
|
||||||
|
if (_glfw.win32.ntdll.instance)
|
||||||
|
FreeLibrary(_glfw.win32.ntdll.instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create key code translation tables
|
// Create key code translation tables
|
||||||
@@ -309,6 +316,7 @@ static void createKeyTables(void)
|
|||||||
_glfw.win32.keycodes[0x053] = GLFW_KEY_KP_DECIMAL;
|
_glfw.win32.keycodes[0x053] = GLFW_KEY_KP_DECIMAL;
|
||||||
_glfw.win32.keycodes[0x135] = GLFW_KEY_KP_DIVIDE;
|
_glfw.win32.keycodes[0x135] = GLFW_KEY_KP_DIVIDE;
|
||||||
_glfw.win32.keycodes[0x11C] = GLFW_KEY_KP_ENTER;
|
_glfw.win32.keycodes[0x11C] = GLFW_KEY_KP_ENTER;
|
||||||
|
_glfw.win32.keycodes[0x059] = GLFW_KEY_KP_EQUAL;
|
||||||
_glfw.win32.keycodes[0x037] = GLFW_KEY_KP_MULTIPLY;
|
_glfw.win32.keycodes[0x037] = GLFW_KEY_KP_MULTIPLY;
|
||||||
_glfw.win32.keycodes[0x04A] = GLFW_KEY_KP_SUBTRACT;
|
_glfw.win32.keycodes[0x04A] = GLFW_KEY_KP_SUBTRACT;
|
||||||
|
|
||||||
@@ -339,8 +347,8 @@ static HWND createHelperWindow(void)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// HACK: The first call to ShowWindow is ignored if the parent process
|
// HACK: The command to the first ShowWindow call is ignored if the parent
|
||||||
// passed along a STARTUPINFO, so clear that flag with a no-op call
|
// process passed along a STARTUPINFO, so clear that with a no-op call
|
||||||
ShowWindow(window, SW_HIDE);
|
ShowWindow(window, SW_HIDE);
|
||||||
|
|
||||||
// Register for HID device notifications
|
// Register for HID device notifications
|
||||||
@@ -502,6 +510,36 @@ void _glfwUpdateKeyNamesWin32(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replacement for IsWindowsVersionOrGreater as MinGW lacks versionhelpers.h
|
||||||
|
//
|
||||||
|
BOOL _glfwIsWindowsVersionOrGreaterWin32(WORD major, WORD minor, WORD sp)
|
||||||
|
{
|
||||||
|
OSVERSIONINFOEXW osvi = { sizeof(osvi), major, minor, 0, 0, {0}, sp };
|
||||||
|
DWORD mask = VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR;
|
||||||
|
ULONGLONG cond = VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL);
|
||||||
|
cond = VerSetConditionMask(cond, VER_MINORVERSION, VER_GREATER_EQUAL);
|
||||||
|
cond = VerSetConditionMask(cond, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
|
||||||
|
// HACK: Use RtlVerifyVersionInfo instead of VerifyVersionInfoW as the
|
||||||
|
// latter lies unless the user knew to embedd a non-default manifest
|
||||||
|
// announcing support for Windows 10 via supportedOS GUID
|
||||||
|
return RtlVerifyVersionInfo(&osvi, mask, cond) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks whether we are on at least the specified build of Windows 10
|
||||||
|
//
|
||||||
|
BOOL _glfwIsWindows10BuildOrGreaterWin32(WORD build)
|
||||||
|
{
|
||||||
|
OSVERSIONINFOEXW osvi = { sizeof(osvi), 10, 0, build };
|
||||||
|
DWORD mask = VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER;
|
||||||
|
ULONGLONG cond = VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL);
|
||||||
|
cond = VerSetConditionMask(cond, VER_MINORVERSION, VER_GREATER_EQUAL);
|
||||||
|
cond = VerSetConditionMask(cond, VER_BUILDNUMBER, VER_GREATER_EQUAL);
|
||||||
|
// HACK: Use RtlVerifyVersionInfo instead of VerifyVersionInfoW as the
|
||||||
|
// latter lies unless the user knew to embedd a non-default manifest
|
||||||
|
// announcing support for Windows 10 via supportedOS GUID
|
||||||
|
return RtlVerifyVersionInfo(&osvi, mask, cond) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
////// GLFW platform API //////
|
////// GLFW platform API //////
|
||||||
@@ -523,7 +561,9 @@ int _glfwPlatformInit(void)
|
|||||||
createKeyTables();
|
createKeyTables();
|
||||||
_glfwUpdateKeyNamesWin32();
|
_glfwUpdateKeyNamesWin32();
|
||||||
|
|
||||||
if (IsWindows8Point1OrGreater())
|
if (_glfwIsWindows10CreatorsUpdateOrGreaterWin32())
|
||||||
|
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||||
|
else if (IsWindows8Point1OrGreater())
|
||||||
SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
|
SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
|
||||||
else if (IsWindowsVistaOrGreater())
|
else if (IsWindowsVistaOrGreater())
|
||||||
SetProcessDPIAware();
|
SetProcessDPIAware();
|
||||||
|
|||||||
9
glfw/win32_monitor.c
vendored
9
glfw/win32_monitor.c
vendored
@@ -324,9 +324,9 @@ void _glfwGetMonitorContentScaleWin32(HMONITOR handle, float* xscale, float* ysc
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (xscale)
|
if (xscale)
|
||||||
*xscale = xdpi / 96.f;
|
*xscale = xdpi / (float) USER_DEFAULT_SCREEN_DPI;
|
||||||
if (yscale)
|
if (yscale)
|
||||||
*yscale = ydpi / 96.f;
|
*yscale = ydpi / (float) USER_DEFAULT_SCREEN_DPI;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -455,7 +455,7 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode)
|
|||||||
&mode->blueBits);
|
&mode->blueBits);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||||
{
|
{
|
||||||
HDC dc;
|
HDC dc;
|
||||||
WORD values[768];
|
WORD values[768];
|
||||||
@@ -469,6 +469,8 @@ void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
|||||||
memcpy(ramp->red, values + 0, 256 * sizeof(unsigned short));
|
memcpy(ramp->red, values + 0, 256 * sizeof(unsigned short));
|
||||||
memcpy(ramp->green, values + 256, 256 * sizeof(unsigned short));
|
memcpy(ramp->green, values + 256, 256 * sizeof(unsigned short));
|
||||||
memcpy(ramp->blue, values + 512, 256 * sizeof(unsigned short));
|
memcpy(ramp->blue, values + 512, 256 * sizeof(unsigned short));
|
||||||
|
|
||||||
|
return GLFW_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
|
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
|
||||||
@@ -510,4 +512,3 @@ GLFWAPI const char* glfwGetWin32Monitor(GLFWmonitor* handle)
|
|||||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||||
return monitor->win32.publicDisplayName;
|
return monitor->win32.publicDisplayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
71
glfw/win32_platform.h
vendored
71
glfw/win32_platform.h
vendored
@@ -98,6 +98,12 @@
|
|||||||
#ifndef _WIN32_WINNT_WINBLUE
|
#ifndef _WIN32_WINNT_WINBLUE
|
||||||
#define _WIN32_WINNT_WINBLUE 0x0602
|
#define _WIN32_WINNT_WINBLUE 0x0602
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef WM_GETDPISCALEDSIZE
|
||||||
|
#define WM_GETDPISCALEDSIZE 0x02e4
|
||||||
|
#endif
|
||||||
|
#ifndef USER_DEFAULT_SCREEN_DPI
|
||||||
|
#define USER_DEFAULT_SCREEN_DPI 96
|
||||||
|
#endif
|
||||||
|
|
||||||
#if WINVER < 0x0601
|
#if WINVER < 0x0601
|
||||||
typedef struct
|
typedef struct
|
||||||
@@ -140,20 +146,32 @@ typedef enum
|
|||||||
} MONITOR_DPI_TYPE;
|
} MONITOR_DPI_TYPE;
|
||||||
#endif /*DPI_ENUMS_DECLARED*/
|
#endif /*DPI_ENUMS_DECLARED*/
|
||||||
|
|
||||||
|
#ifndef DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
|
||||||
|
DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
|
||||||
|
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT) -4)
|
||||||
|
#endif /*DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2*/
|
||||||
|
|
||||||
// HACK: Define versionhelpers.h functions manually as MinGW lacks the header
|
// HACK: Define versionhelpers.h functions manually as MinGW lacks the header
|
||||||
BOOL IsWindowsVersionOrGreater(WORD major, WORD minor, WORD sp);
|
#define IsWindowsXPOrGreater() \
|
||||||
#define IsWindowsVistaOrGreater() \
|
_glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_WINXP), \
|
||||||
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), \
|
LOBYTE(_WIN32_WINNT_WINXP), 0)
|
||||||
LOBYTE(_WIN32_WINNT_VISTA), 0)
|
#define IsWindowsVistaOrGreater() \
|
||||||
#define IsWindows7OrGreater() \
|
_glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_VISTA), \
|
||||||
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN7), \
|
LOBYTE(_WIN32_WINNT_VISTA), 0)
|
||||||
LOBYTE(_WIN32_WINNT_WIN7), 0)
|
#define IsWindows7OrGreater() \
|
||||||
#define IsWindows8OrGreater() \
|
_glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_WIN7), \
|
||||||
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN8), \
|
LOBYTE(_WIN32_WINNT_WIN7), 0)
|
||||||
LOBYTE(_WIN32_WINNT_WIN8), 0)
|
#define IsWindows8OrGreater() \
|
||||||
#define IsWindows8Point1OrGreater() \
|
_glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_WIN8), \
|
||||||
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINBLUE), \
|
LOBYTE(_WIN32_WINNT_WIN8), 0)
|
||||||
LOBYTE(_WIN32_WINNT_WINBLUE), 0)
|
#define IsWindows8Point1OrGreater() \
|
||||||
|
_glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_WINBLUE), \
|
||||||
|
LOBYTE(_WIN32_WINNT_WINBLUE), 0)
|
||||||
|
|
||||||
|
#define _glfwIsWindows10AnniversaryUpdateOrGreaterWin32() \
|
||||||
|
_glfwIsWindows10BuildOrGreaterWin32(14393)
|
||||||
|
#define _glfwIsWindows10CreatorsUpdateOrGreaterWin32() \
|
||||||
|
_glfwIsWindows10BuildOrGreaterWin32(15063)
|
||||||
|
|
||||||
// HACK: Define macros that some xinput.h variants don't
|
// HACK: Define macros that some xinput.h variants don't
|
||||||
#ifndef XINPUT_CAPS_WIRELESS
|
#ifndef XINPUT_CAPS_WIRELESS
|
||||||
@@ -206,8 +224,16 @@ typedef HRESULT (WINAPI * PFN_DirectInput8Create)(HINSTANCE,DWORD,REFIID,LPVOID*
|
|||||||
// user32.dll function pointer typedefs
|
// user32.dll function pointer typedefs
|
||||||
typedef BOOL (WINAPI * PFN_SetProcessDPIAware)(void);
|
typedef BOOL (WINAPI * PFN_SetProcessDPIAware)(void);
|
||||||
typedef BOOL (WINAPI * PFN_ChangeWindowMessageFilterEx)(HWND,UINT,DWORD,CHANGEFILTERSTRUCT*);
|
typedef BOOL (WINAPI * PFN_ChangeWindowMessageFilterEx)(HWND,UINT,DWORD,CHANGEFILTERSTRUCT*);
|
||||||
|
typedef BOOL (WINAPI * PFN_EnableNonClientDpiScaling)(HWND);
|
||||||
|
typedef BOOL (WINAPI * PFN_SetProcessDpiAwarenessContext)(DPI_AWARENESS_CONTEXT);
|
||||||
|
typedef UINT (WINAPI * PFN_GetDpiForWindow)(HWND);
|
||||||
|
typedef BOOL (WINAPI * PFN_AdjustWindowRectExForDpi)(LPRECT,DWORD,BOOL,DWORD,UINT);
|
||||||
#define SetProcessDPIAware _glfw.win32.user32.SetProcessDPIAware_
|
#define SetProcessDPIAware _glfw.win32.user32.SetProcessDPIAware_
|
||||||
#define ChangeWindowMessageFilterEx _glfw.win32.user32.ChangeWindowMessageFilterEx_
|
#define ChangeWindowMessageFilterEx _glfw.win32.user32.ChangeWindowMessageFilterEx_
|
||||||
|
#define EnableNonClientDpiScaling _glfw.win32.user32.EnableNonClientDpiScaling_
|
||||||
|
#define SetProcessDpiAwarenessContext _glfw.win32.user32.SetProcessDpiAwarenessContext_
|
||||||
|
#define GetDpiForWindow _glfw.win32.user32.GetDpiForWindow_
|
||||||
|
#define AdjustWindowRectExForDpi _glfw.win32.user32.AdjustWindowRectExForDpi_
|
||||||
|
|
||||||
// dwmapi.dll function pointer typedefs
|
// dwmapi.dll function pointer typedefs
|
||||||
typedef HRESULT (WINAPI * PFN_DwmIsCompositionEnabled)(BOOL*);
|
typedef HRESULT (WINAPI * PFN_DwmIsCompositionEnabled)(BOOL*);
|
||||||
@@ -223,6 +249,10 @@ typedef HRESULT (WINAPI * PFN_GetDpiForMonitor)(HMONITOR,MONITOR_DPI_TYPE,UINT*,
|
|||||||
#define SetProcessDpiAwareness _glfw.win32.shcore.SetProcessDpiAwareness_
|
#define SetProcessDpiAwareness _glfw.win32.shcore.SetProcessDpiAwareness_
|
||||||
#define GetDpiForMonitor _glfw.win32.shcore.GetDpiForMonitor_
|
#define GetDpiForMonitor _glfw.win32.shcore.GetDpiForMonitor_
|
||||||
|
|
||||||
|
// ntdll.dll function pointer typedefs
|
||||||
|
typedef LONG (WINAPI * PFN_RtlVerifyVersionInfo)(OSVERSIONINFOEXW*,ULONG,ULONGLONG);
|
||||||
|
#define RtlVerifyVersionInfo _glfw.win32.ntdll.RtlVerifyVersionInfo_
|
||||||
|
|
||||||
typedef VkFlags VkWin32SurfaceCreateFlagsKHR;
|
typedef VkFlags VkWin32SurfaceCreateFlagsKHR;
|
||||||
|
|
||||||
typedef struct VkWin32SurfaceCreateInfoKHR
|
typedef struct VkWin32SurfaceCreateInfoKHR
|
||||||
@@ -276,6 +306,7 @@ typedef struct _GLFWwindowWin32
|
|||||||
GLFWbool maximized;
|
GLFWbool maximized;
|
||||||
// Whether to enable framebuffer transparency on DWM
|
// Whether to enable framebuffer transparency on DWM
|
||||||
GLFWbool transparent;
|
GLFWbool transparent;
|
||||||
|
GLFWbool scaleToMonitor;
|
||||||
|
|
||||||
// The last received cursor position, regardless of source
|
// The last received cursor position, regardless of source
|
||||||
int lastCursorPosX, lastCursorPosY;
|
int lastCursorPosX, lastCursorPosY;
|
||||||
@@ -300,6 +331,7 @@ typedef struct _GLFWlibraryWin32
|
|||||||
_GLFWwindow* disabledCursorWindow;
|
_GLFWwindow* disabledCursorWindow;
|
||||||
RAWINPUT* rawInput;
|
RAWINPUT* rawInput;
|
||||||
int rawInputSize;
|
int rawInputSize;
|
||||||
|
UINT mouseTrailSize;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
HINSTANCE instance;
|
HINSTANCE instance;
|
||||||
@@ -322,6 +354,10 @@ typedef struct _GLFWlibraryWin32
|
|||||||
HINSTANCE instance;
|
HINSTANCE instance;
|
||||||
PFN_SetProcessDPIAware SetProcessDPIAware_;
|
PFN_SetProcessDPIAware SetProcessDPIAware_;
|
||||||
PFN_ChangeWindowMessageFilterEx ChangeWindowMessageFilterEx_;
|
PFN_ChangeWindowMessageFilterEx ChangeWindowMessageFilterEx_;
|
||||||
|
PFN_EnableNonClientDpiScaling EnableNonClientDpiScaling_;
|
||||||
|
PFN_SetProcessDpiAwarenessContext SetProcessDpiAwarenessContext_;
|
||||||
|
PFN_GetDpiForWindow GetDpiForWindow_;
|
||||||
|
PFN_AdjustWindowRectExForDpi AdjustWindowRectExForDpi_;
|
||||||
} user32;
|
} user32;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
@@ -337,6 +373,11 @@ typedef struct _GLFWlibraryWin32
|
|||||||
PFN_GetDpiForMonitor GetDpiForMonitor_;
|
PFN_GetDpiForMonitor GetDpiForMonitor_;
|
||||||
} shcore;
|
} shcore;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
HINSTANCE instance;
|
||||||
|
PFN_RtlVerifyVersionInfo RtlVerifyVersionInfo_;
|
||||||
|
} ntdll;
|
||||||
|
|
||||||
} _GLFWlibraryWin32;
|
} _GLFWlibraryWin32;
|
||||||
|
|
||||||
// Win32-specific per-monitor data
|
// Win32-specific per-monitor data
|
||||||
@@ -392,10 +433,11 @@ typedef struct _GLFWmutexWin32
|
|||||||
|
|
||||||
GLFWbool _glfwRegisterWindowClassWin32(void);
|
GLFWbool _glfwRegisterWindowClassWin32(void);
|
||||||
void _glfwUnregisterWindowClassWin32(void);
|
void _glfwUnregisterWindowClassWin32(void);
|
||||||
GLFWbool _glfwIsCompositionEnabledWin32(void);
|
|
||||||
|
|
||||||
WCHAR* _glfwCreateWideStringFromUTF8Win32(const char* source);
|
WCHAR* _glfwCreateWideStringFromUTF8Win32(const char* source);
|
||||||
char* _glfwCreateUTF8FromWideStringWin32(const WCHAR* source);
|
char* _glfwCreateUTF8FromWideStringWin32(const WCHAR* source);
|
||||||
|
BOOL _glfwIsWindowsVersionOrGreaterWin32(WORD major, WORD minor, WORD sp);
|
||||||
|
BOOL _glfwIsWindows10BuildOrGreaterWin32(WORD build);
|
||||||
void _glfwInputErrorWin32(int error, const char* description);
|
void _glfwInputErrorWin32(int error, const char* description);
|
||||||
void _glfwUpdateKeyNamesWin32(void);
|
void _glfwUpdateKeyNamesWin32(void);
|
||||||
|
|
||||||
@@ -405,4 +447,3 @@ void _glfwPollMonitorsWin32(void);
|
|||||||
void _glfwSetVideoModeWin32(_GLFWmonitor* monitor, const GLFWvidmode* desired);
|
void _glfwSetVideoModeWin32(_GLFWmonitor* monitor, const GLFWvidmode* desired);
|
||||||
void _glfwRestoreVideoModeWin32(_GLFWmonitor* monitor);
|
void _glfwRestoreVideoModeWin32(_GLFWmonitor* monitor);
|
||||||
void _glfwGetMonitorContentScaleWin32(HMONITOR handle, float* xscale, float* yscale);
|
void _glfwGetMonitorContentScaleWin32(HMONITOR handle, float* xscale, float* yscale);
|
||||||
|
|
||||||
|
|||||||
289
glfw/win32_window.c
vendored
289
glfw/win32_window.c
vendored
@@ -186,14 +186,20 @@ static HICON createIcon(const GLFWimage* image,
|
|||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translate client window size to full window size according to styles
|
// Translate client window size to full window size according to styles and DPI
|
||||||
//
|
//
|
||||||
static void getFullWindowSize(DWORD style, DWORD exStyle,
|
static void getFullWindowSize(DWORD style, DWORD exStyle,
|
||||||
int clientWidth, int clientHeight,
|
int clientWidth, int clientHeight,
|
||||||
int* fullWidth, int* fullHeight)
|
int* fullWidth, int* fullHeight,
|
||||||
|
UINT dpi)
|
||||||
{
|
{
|
||||||
RECT rect = { 0, 0, clientWidth, clientHeight };
|
RECT rect = { 0, 0, clientWidth, clientHeight };
|
||||||
AdjustWindowRectEx(&rect, style, FALSE, exStyle);
|
|
||||||
|
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||||
|
AdjustWindowRectExForDpi(&rect, style, FALSE, exStyle, dpi);
|
||||||
|
else
|
||||||
|
AdjustWindowRectEx(&rect, style, FALSE, exStyle);
|
||||||
|
|
||||||
*fullWidth = rect.right - rect.left;
|
*fullWidth = rect.right - rect.left;
|
||||||
*fullHeight = rect.bottom - rect.top;
|
*fullHeight = rect.bottom - rect.top;
|
||||||
}
|
}
|
||||||
@@ -206,7 +212,8 @@ static void applyAspectRatio(_GLFWwindow* window, int edge, RECT* area)
|
|||||||
const float ratio = (float) window->numer / (float) window->denom;
|
const float ratio = (float) window->numer / (float) window->denom;
|
||||||
|
|
||||||
getFullWindowSize(getWindowStyle(window), getWindowExStyle(window),
|
getFullWindowSize(getWindowStyle(window), getWindowExStyle(window),
|
||||||
0, 0, &xoff, &yoff);
|
0, 0, &xoff, &yoff,
|
||||||
|
GetDpiForWindow(window->win32.handle));
|
||||||
|
|
||||||
if (edge == WMSZ_LEFT || edge == WMSZ_BOTTOMLEFT ||
|
if (edge == WMSZ_LEFT || edge == WMSZ_BOTTOMLEFT ||
|
||||||
edge == WMSZ_RIGHT || edge == WMSZ_BOTTOMRIGHT)
|
edge == WMSZ_RIGHT || edge == WMSZ_BOTTOMRIGHT)
|
||||||
@@ -337,7 +344,16 @@ static void updateWindowStyles(const _GLFWwindow* window)
|
|||||||
style |= getWindowStyle(window);
|
style |= getWindowStyle(window);
|
||||||
|
|
||||||
GetClientRect(window->win32.handle, &rect);
|
GetClientRect(window->win32.handle, &rect);
|
||||||
AdjustWindowRectEx(&rect, style, FALSE, getWindowExStyle(window));
|
|
||||||
|
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||||
|
{
|
||||||
|
AdjustWindowRectExForDpi(&rect, style, FALSE,
|
||||||
|
getWindowExStyle(window),
|
||||||
|
GetDpiForWindow(window->win32.handle));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
AdjustWindowRectEx(&rect, style, FALSE, getWindowExStyle(window));
|
||||||
|
|
||||||
ClientToScreen(window->win32.handle, (POINT*) &rect.left);
|
ClientToScreen(window->win32.handle, (POINT*) &rect.left);
|
||||||
ClientToScreen(window->win32.handle, (POINT*) &rect.right);
|
ClientToScreen(window->win32.handle, (POINT*) &rect.right);
|
||||||
SetWindowLongW(window->win32.handle, GWL_STYLE, style);
|
SetWindowLongW(window->win32.handle, GWL_STYLE, style);
|
||||||
@@ -351,10 +367,12 @@ static void updateWindowStyles(const _GLFWwindow* window)
|
|||||||
//
|
//
|
||||||
static void updateFramebufferTransparency(const _GLFWwindow* window)
|
static void updateFramebufferTransparency(const _GLFWwindow* window)
|
||||||
{
|
{
|
||||||
|
BOOL enabled;
|
||||||
|
|
||||||
if (!IsWindowsVistaOrGreater())
|
if (!IsWindowsVistaOrGreater())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_glfwIsCompositionEnabledWin32())
|
if (SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled)
|
||||||
{
|
{
|
||||||
HRGN region = CreateRectRgn(0, 0, -1, -1);
|
HRGN region = CreateRectRgn(0, 0, -1, -1);
|
||||||
DWM_BLURBEHIND bb = {0};
|
DWM_BLURBEHIND bb = {0};
|
||||||
@@ -396,29 +414,6 @@ static void updateFramebufferTransparency(const _GLFWwindow* window)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translates a GLFW standard cursor to a resource ID
|
|
||||||
//
|
|
||||||
static LPWSTR translateCursorShape(int shape)
|
|
||||||
{
|
|
||||||
switch (shape)
|
|
||||||
{
|
|
||||||
case GLFW_ARROW_CURSOR:
|
|
||||||
return IDC_ARROW;
|
|
||||||
case GLFW_IBEAM_CURSOR:
|
|
||||||
return IDC_IBEAM;
|
|
||||||
case GLFW_CROSSHAIR_CURSOR:
|
|
||||||
return IDC_CROSS;
|
|
||||||
case GLFW_HAND_CURSOR:
|
|
||||||
return IDC_HAND;
|
|
||||||
case GLFW_HRESIZE_CURSOR:
|
|
||||||
return IDC_SIZEWE;
|
|
||||||
case GLFW_VRESIZE_CURSOR:
|
|
||||||
return IDC_SIZENS;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieves and translates modifier keys
|
// Retrieves and translates modifier keys
|
||||||
//
|
//
|
||||||
static int getKeyMods(void)
|
static int getKeyMods(void)
|
||||||
@@ -529,7 +524,18 @@ static void fitToMonitor(_GLFWwindow* window)
|
|||||||
static void acquireMonitor(_GLFWwindow* window)
|
static void acquireMonitor(_GLFWwindow* window)
|
||||||
{
|
{
|
||||||
if (!_glfw.win32.acquiredMonitorCount)
|
if (!_glfw.win32.acquiredMonitorCount)
|
||||||
|
{
|
||||||
SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED);
|
SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED);
|
||||||
|
|
||||||
|
// HACK: When mouse trails are enabled the cursor becomes invisible when
|
||||||
|
// the OpenGL ICD switches to page flipping
|
||||||
|
if (IsWindowsXPOrGreater())
|
||||||
|
{
|
||||||
|
SystemParametersInfo(SPI_GETMOUSETRAILS, 0, &_glfw.win32.mouseTrailSize, 0);
|
||||||
|
SystemParametersInfo(SPI_SETMOUSETRAILS, 0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!window->monitor->window)
|
if (!window->monitor->window)
|
||||||
_glfw.win32.acquiredMonitorCount++;
|
_glfw.win32.acquiredMonitorCount++;
|
||||||
|
|
||||||
@@ -546,8 +552,14 @@ static void releaseMonitor(_GLFWwindow* window)
|
|||||||
|
|
||||||
_glfw.win32.acquiredMonitorCount--;
|
_glfw.win32.acquiredMonitorCount--;
|
||||||
if (!_glfw.win32.acquiredMonitorCount)
|
if (!_glfw.win32.acquiredMonitorCount)
|
||||||
|
{
|
||||||
SetThreadExecutionState(ES_CONTINUOUS);
|
SetThreadExecutionState(ES_CONTINUOUS);
|
||||||
|
|
||||||
|
// HACK: Restore mouse trail length saved in acquireMonitor
|
||||||
|
if (IsWindowsXPOrGreater())
|
||||||
|
SystemParametersInfo(SPI_SETMOUSETRAILS, _glfw.win32.mouseTrailSize, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
_glfwInputMonitorWindow(window->monitor, NULL);
|
_glfwInputMonitorWindow(window->monitor, NULL);
|
||||||
_glfwRestoreVideoModeWin32(window->monitor);
|
_glfwRestoreVideoModeWin32(window->monitor);
|
||||||
}
|
}
|
||||||
@@ -561,9 +573,18 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
|
|||||||
if (!window)
|
if (!window)
|
||||||
{
|
{
|
||||||
// This is the message handling for the hidden helper window
|
// This is the message handling for the hidden helper window
|
||||||
|
// and for a regular window during its initial creation
|
||||||
|
|
||||||
switch (uMsg)
|
switch (uMsg)
|
||||||
{
|
{
|
||||||
|
case WM_NCCREATE:
|
||||||
|
{
|
||||||
|
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||||
|
EnableNonClientDpiScaling(hWnd);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case WM_DISPLAYCHANGE:
|
case WM_DISPLAYCHANGE:
|
||||||
_glfwPollMonitorsWin32();
|
_glfwPollMonitorsWin32();
|
||||||
break;
|
break;
|
||||||
@@ -986,7 +1007,8 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
getFullWindowSize(getWindowStyle(window), getWindowExStyle(window),
|
getFullWindowSize(getWindowStyle(window), getWindowExStyle(window),
|
||||||
0, 0, &xoff, &yoff);
|
0, 0, &xoff, &yoff,
|
||||||
|
GetDpiForWindow(window->win32.handle));
|
||||||
|
|
||||||
if (window->minwidth != GLFW_DONT_CARE &&
|
if (window->minwidth != GLFW_DONT_CARE &&
|
||||||
window->minheight != GLFW_DONT_CARE)
|
window->minheight != GLFW_DONT_CARE)
|
||||||
@@ -1039,10 +1061,52 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case WM_GETDPISCALEDSIZE:
|
||||||
|
{
|
||||||
|
if (window->win32.scaleToMonitor)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Adjust the window size to keep the client area size constant
|
||||||
|
if (_glfwIsWindows10CreatorsUpdateOrGreaterWin32())
|
||||||
|
{
|
||||||
|
RECT source = {0}, target = {0};
|
||||||
|
SIZE* size = (SIZE*) lParam;
|
||||||
|
|
||||||
|
AdjustWindowRectExForDpi(&source, getWindowStyle(window),
|
||||||
|
FALSE, getWindowExStyle(window),
|
||||||
|
GetDpiForWindow(window->win32.handle));
|
||||||
|
AdjustWindowRectExForDpi(&target, getWindowStyle(window),
|
||||||
|
FALSE, getWindowExStyle(window),
|
||||||
|
LOWORD(wParam));
|
||||||
|
|
||||||
|
size->cx += (target.right - target.left) -
|
||||||
|
(source.right - source.left);
|
||||||
|
size->cy += (target.bottom - target.top) -
|
||||||
|
(source.bottom - source.top);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case WM_DPICHANGED:
|
case WM_DPICHANGED:
|
||||||
{
|
{
|
||||||
const float xscale = HIWORD(wParam) / 96.f;
|
const float xscale = HIWORD(wParam) / (float) USER_DEFAULT_SCREEN_DPI;
|
||||||
const float yscale = LOWORD(wParam) / 96.f;
|
const float yscale = LOWORD(wParam) / (float) USER_DEFAULT_SCREEN_DPI;
|
||||||
|
|
||||||
|
// Only apply the suggested size if the OS is new enough to have
|
||||||
|
// sent a WM_GETDPISCALEDSIZE before this
|
||||||
|
if (_glfwIsWindows10CreatorsUpdateOrGreaterWin32())
|
||||||
|
{
|
||||||
|
RECT* suggested = (RECT*) lParam;
|
||||||
|
SetWindowPos(window->win32.handle, HWND_TOP,
|
||||||
|
suggested->left,
|
||||||
|
suggested->top,
|
||||||
|
suggested->right - suggested->left,
|
||||||
|
suggested->bottom - suggested->top,
|
||||||
|
SWP_NOACTIVATE | SWP_NOZORDER);
|
||||||
|
}
|
||||||
|
|
||||||
_glfwInputWindowContentScale(window, xscale, yscale);
|
_glfwInputWindowContentScale(window, xscale, yscale);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1129,7 +1193,8 @@ static int createNativeWindow(_GLFWwindow* window,
|
|||||||
|
|
||||||
getFullWindowSize(style, exStyle,
|
getFullWindowSize(style, exStyle,
|
||||||
wndconfig->width, wndconfig->height,
|
wndconfig->width, wndconfig->height,
|
||||||
&fullWidth, &fullHeight);
|
&fullWidth, &fullHeight,
|
||||||
|
USER_DEFAULT_SCREEN_DPI);
|
||||||
}
|
}
|
||||||
|
|
||||||
wideTitle = _glfwCreateWideStringFromUTF8Win32(wndconfig->title);
|
wideTitle = _glfwCreateWideStringFromUTF8Win32(wndconfig->title);
|
||||||
@@ -1168,6 +1233,40 @@ static int createNativeWindow(_GLFWwindow* window,
|
|||||||
WM_COPYGLOBALDATA, MSGFLT_ALLOW, NULL);
|
WM_COPYGLOBALDATA, MSGFLT_ALLOW, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window->win32.scaleToMonitor = wndconfig->scaleToMonitor;
|
||||||
|
|
||||||
|
// Adjust window size to account for DPI scaling of the window frame and
|
||||||
|
// optionally DPI scaling of the client area
|
||||||
|
// This cannot be done until we know what monitor it was placed on
|
||||||
|
if (!window->monitor)
|
||||||
|
{
|
||||||
|
RECT rect = { 0, 0, wndconfig->width, wndconfig->height };
|
||||||
|
|
||||||
|
if (wndconfig->scaleToMonitor)
|
||||||
|
{
|
||||||
|
float xscale, yscale;
|
||||||
|
_glfwPlatformGetWindowContentScale(window, &xscale, &yscale);
|
||||||
|
rect.right = (int) (rect.right * xscale);
|
||||||
|
rect.bottom = (int) (rect.bottom * yscale);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientToScreen(window->win32.handle, (POINT*) &rect.left);
|
||||||
|
ClientToScreen(window->win32.handle, (POINT*) &rect.right);
|
||||||
|
|
||||||
|
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||||
|
{
|
||||||
|
AdjustWindowRectExForDpi(&rect, style, FALSE, exStyle,
|
||||||
|
GetDpiForWindow(window->win32.handle));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
AdjustWindowRectEx(&rect, style, FALSE, exStyle);
|
||||||
|
|
||||||
|
SetWindowPos(window->win32.handle, NULL,
|
||||||
|
rect.left, rect.top,
|
||||||
|
rect.right - rect.left, rect.bottom - rect.top,
|
||||||
|
SWP_NOACTIVATE | SWP_NOZORDER);
|
||||||
|
}
|
||||||
|
|
||||||
DragAcceptFiles(window->win32.handle, TRUE);
|
DragAcceptFiles(window->win32.handle, TRUE);
|
||||||
|
|
||||||
if (fbconfig->transparent)
|
if (fbconfig->transparent)
|
||||||
@@ -1227,20 +1326,6 @@ void _glfwUnregisterWindowClassWin32(void)
|
|||||||
UnregisterClassW(_GLFW_WNDCLASSNAME, GetModuleHandleW(NULL));
|
UnregisterClassW(_GLFW_WNDCLASSNAME, GetModuleHandleW(NULL));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns whether desktop compositing is enabled
|
|
||||||
//
|
|
||||||
GLFWbool _glfwIsCompositionEnabledWin32(void)
|
|
||||||
{
|
|
||||||
if (IsWindowsVistaOrGreater())
|
|
||||||
{
|
|
||||||
BOOL enabled;
|
|
||||||
if (SUCCEEDED(DwmIsCompositionEnabled(&enabled)))
|
|
||||||
return enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
////// GLFW platform API //////
|
////// GLFW platform API //////
|
||||||
@@ -1378,8 +1463,19 @@ void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos)
|
|||||||
void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos)
|
void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos)
|
||||||
{
|
{
|
||||||
RECT rect = { xpos, ypos, xpos, ypos };
|
RECT rect = { xpos, ypos, xpos, ypos };
|
||||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
|
||||||
FALSE, getWindowExStyle(window));
|
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||||
|
{
|
||||||
|
AdjustWindowRectExForDpi(&rect, getWindowStyle(window),
|
||||||
|
FALSE, getWindowExStyle(window),
|
||||||
|
GetDpiForWindow(window->win32.handle));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||||
|
FALSE, getWindowExStyle(window));
|
||||||
|
}
|
||||||
|
|
||||||
SetWindowPos(window->win32.handle, NULL, rect.left, rect.top, 0, 0,
|
SetWindowPos(window->win32.handle, NULL, rect.left, rect.top, 0, 0,
|
||||||
SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE);
|
SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE);
|
||||||
}
|
}
|
||||||
@@ -1408,8 +1504,19 @@ void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
RECT rect = { 0, 0, width, height };
|
RECT rect = { 0, 0, width, height };
|
||||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
|
||||||
FALSE, getWindowExStyle(window));
|
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||||
|
{
|
||||||
|
AdjustWindowRectExForDpi(&rect, getWindowStyle(window),
|
||||||
|
FALSE, getWindowExStyle(window),
|
||||||
|
GetDpiForWindow(window->win32.handle));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||||
|
FALSE, getWindowExStyle(window));
|
||||||
|
}
|
||||||
|
|
||||||
SetWindowPos(window->win32.handle, HWND_TOP,
|
SetWindowPos(window->win32.handle, HWND_TOP,
|
||||||
0, 0, rect.right - rect.left, rect.bottom - rect.top,
|
0, 0, rect.right - rect.left, rect.bottom - rect.top,
|
||||||
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOZORDER);
|
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOZORDER);
|
||||||
@@ -1464,8 +1571,18 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
|||||||
|
|
||||||
_glfwPlatformGetWindowSize(window, &width, &height);
|
_glfwPlatformGetWindowSize(window, &width, &height);
|
||||||
SetRect(&rect, 0, 0, width, height);
|
SetRect(&rect, 0, 0, width, height);
|
||||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
|
||||||
FALSE, getWindowExStyle(window));
|
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||||
|
{
|
||||||
|
AdjustWindowRectExForDpi(&rect, getWindowStyle(window),
|
||||||
|
FALSE, getWindowExStyle(window),
|
||||||
|
GetDpiForWindow(window->win32.handle));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||||
|
FALSE, getWindowExStyle(window));
|
||||||
|
}
|
||||||
|
|
||||||
if (left)
|
if (left)
|
||||||
*left = -rect.left;
|
*left = -rect.left;
|
||||||
@@ -1485,6 +1602,11 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
|
|||||||
_glfwGetMonitorContentScaleWin32(handle, xscale, yscale);
|
_glfwGetMonitorContentScaleWin32(handle, xscale, yscale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window)
|
||||||
|
{
|
||||||
|
return 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
|
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
|
||||||
{
|
{
|
||||||
ShowWindow(window->win32.handle, SW_MINIMIZE);
|
ShowWindow(window->win32.handle, SW_MINIMIZE);
|
||||||
@@ -1546,8 +1668,19 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
RECT rect = { xpos, ypos, xpos + width, ypos + height };
|
RECT rect = { xpos, ypos, xpos + width, ypos + height };
|
||||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
|
||||||
FALSE, getWindowExStyle(window));
|
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||||
|
{
|
||||||
|
AdjustWindowRectExForDpi(&rect, getWindowStyle(window),
|
||||||
|
FALSE, getWindowExStyle(window),
|
||||||
|
GetDpiForWindow(window->win32.handle));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||||
|
FALSE, getWindowExStyle(window));
|
||||||
|
}
|
||||||
|
|
||||||
SetWindowPos(window->win32.handle, HWND_TOP,
|
SetWindowPos(window->win32.handle, HWND_TOP,
|
||||||
rect.left, rect.top,
|
rect.left, rect.top,
|
||||||
rect.right - rect.left, rect.bottom - rect.top,
|
rect.right - rect.left, rect.bottom - rect.top,
|
||||||
@@ -1607,8 +1740,18 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
|
|||||||
else
|
else
|
||||||
after = HWND_NOTOPMOST;
|
after = HWND_NOTOPMOST;
|
||||||
|
|
||||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||||
FALSE, getWindowExStyle(window));
|
{
|
||||||
|
AdjustWindowRectExForDpi(&rect, getWindowStyle(window),
|
||||||
|
FALSE, getWindowExStyle(window),
|
||||||
|
GetDpiForWindow(window->win32.handle));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||||
|
FALSE, getWindowExStyle(window));
|
||||||
|
}
|
||||||
|
|
||||||
SetWindowPos(window->win32.handle, after,
|
SetWindowPos(window->win32.handle, after,
|
||||||
rect.left, rect.top,
|
rect.left, rect.top,
|
||||||
rect.right - rect.left, rect.bottom - rect.top,
|
rect.right - rect.left, rect.bottom - rect.top,
|
||||||
@@ -1643,7 +1786,15 @@ int _glfwPlatformWindowHovered(_GLFWwindow* window)
|
|||||||
|
|
||||||
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
|
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
|
||||||
{
|
{
|
||||||
return window->win32.transparent && _glfwIsCompositionEnabledWin32();
|
BOOL enabled;
|
||||||
|
|
||||||
|
if (!window->win32.transparent)
|
||||||
|
return GLFW_FALSE;
|
||||||
|
|
||||||
|
if (!IsWindowsVistaOrGreater())
|
||||||
|
return GLFW_FALSE;
|
||||||
|
|
||||||
|
return SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled)
|
void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled)
|
||||||
@@ -1839,7 +1990,7 @@ int _glfwPlatformGetKeyScancode(int key)
|
|||||||
|
|
||||||
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||||
const GLFWimage* image,
|
const GLFWimage* image,
|
||||||
int xhot, int yhot)
|
int xhot, int yhot, int count)
|
||||||
{
|
{
|
||||||
cursor->win32.handle = (HCURSOR) createIcon(image, xhot, yhot, GLFW_FALSE);
|
cursor->win32.handle = (HCURSOR) createIcon(image, xhot, yhot, GLFW_FALSE);
|
||||||
if (!cursor->win32.handle)
|
if (!cursor->win32.handle)
|
||||||
@@ -1850,8 +2001,24 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
|||||||
|
|
||||||
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
|
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
|
||||||
{
|
{
|
||||||
cursor->win32.handle =
|
LPCWSTR name = NULL;
|
||||||
CopyCursor(LoadCursorW(NULL, translateCursorShape(shape)));
|
|
||||||
|
if (shape == GLFW_ARROW_CURSOR)
|
||||||
|
name = IDC_ARROW;
|
||||||
|
else if (shape == GLFW_IBEAM_CURSOR)
|
||||||
|
name = IDC_IBEAM;
|
||||||
|
else if (shape == GLFW_CROSSHAIR_CURSOR)
|
||||||
|
name = IDC_CROSS;
|
||||||
|
else if (shape == GLFW_HAND_CURSOR)
|
||||||
|
name = IDC_HAND;
|
||||||
|
else if (shape == GLFW_HRESIZE_CURSOR)
|
||||||
|
name = IDC_SIZEWE;
|
||||||
|
else if (shape == GLFW_VRESIZE_CURSOR)
|
||||||
|
name = IDC_SIZENS;
|
||||||
|
else
|
||||||
|
return GLFW_FALSE;
|
||||||
|
|
||||||
|
cursor->win32.handle = CopyCursor(LoadCursorW(NULL, name));
|
||||||
if (!cursor->win32.handle)
|
if (!cursor->win32.handle)
|
||||||
{
|
{
|
||||||
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
|
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
|
||||||
|
|||||||
40
glfw/window.c
vendored
40
glfw/window.c
vendored
@@ -48,6 +48,7 @@ void _glfwInputWindowFocus(_GLFWwindow* window, GLFWbool focused)
|
|||||||
if (!focused)
|
if (!focused)
|
||||||
{
|
{
|
||||||
int key, button;
|
int key, button;
|
||||||
|
_glfw.focusedWindowId = 0;
|
||||||
|
|
||||||
for (key = 0; key <= GLFW_KEY_LAST; key++)
|
for (key = 0; key <= GLFW_KEY_LAST; key++)
|
||||||
{
|
{
|
||||||
@@ -63,7 +64,28 @@ void _glfwInputWindowFocus(_GLFWwindow* window, GLFWbool focused)
|
|||||||
if (window->mouseButtons[button] == GLFW_PRESS)
|
if (window->mouseButtons[button] == GLFW_PRESS)
|
||||||
_glfwInputMouseClick(window, button, GLFW_RELEASE, 0);
|
_glfwInputMouseClick(window, button, GLFW_RELEASE, 0);
|
||||||
}
|
}
|
||||||
|
} else
|
||||||
|
_glfw.focusedWindowId = window->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
_GLFWwindow* _glfwFocusedWindow() {
|
||||||
|
if (_glfw.focusedWindowId) {
|
||||||
|
_GLFWwindow *w = _glfw.windowListHead;
|
||||||
|
while (w) {
|
||||||
|
if (w->id == _glfw.focusedWindowId) return w;
|
||||||
|
w = w->next;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
_GLFWwindow* _glfwWindowForId(GLFWid id) {
|
||||||
|
_GLFWwindow *w = _glfw.windowListHead;
|
||||||
|
while (w) {
|
||||||
|
if (w->id == _glfw.focusedWindowId) return w;
|
||||||
|
w = w->next;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notifies shared code that a window has moved
|
// Notifies shared code that a window has moved
|
||||||
@@ -185,8 +207,10 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
|
|||||||
if (!_glfwIsValidContextConfig(&ctxconfig))
|
if (!_glfwIsValidContextConfig(&ctxconfig))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
static GLFWid windowIdCounter = 0;
|
||||||
window = calloc(1, sizeof(_GLFWwindow));
|
window = calloc(1, sizeof(_GLFWwindow));
|
||||||
window->next = _glfw.windowListHead;
|
window->next = _glfw.windowListHead;
|
||||||
|
window->id = ++windowIdCounter;
|
||||||
_glfw.windowListHead = window;
|
_glfw.windowListHead = window;
|
||||||
|
|
||||||
window->videoMode.width = width;
|
window->videoMode.width = width;
|
||||||
@@ -369,6 +393,9 @@ GLFWAPI void glfwWindowHint(int hint, int value)
|
|||||||
case GLFW_COCOA_GRAPHICS_SWITCHING:
|
case GLFW_COCOA_GRAPHICS_SWITCHING:
|
||||||
_glfw.hints.context.nsgl.offline = value ? GLFW_TRUE : GLFW_FALSE;
|
_glfw.hints.context.nsgl.offline = value ? GLFW_TRUE : GLFW_FALSE;
|
||||||
return;
|
return;
|
||||||
|
case GLFW_SCALE_TO_MONITOR:
|
||||||
|
_glfw.hints.window.scaleToMonitor = value ? GLFW_TRUE : GLFW_FALSE;
|
||||||
|
return;
|
||||||
case GLFW_CENTER_CURSOR:
|
case GLFW_CENTER_CURSOR:
|
||||||
_glfw.hints.window.centerCursor = value ? GLFW_TRUE : GLFW_FALSE;
|
_glfw.hints.window.centerCursor = value ? GLFW_TRUE : GLFW_FALSE;
|
||||||
return;
|
return;
|
||||||
@@ -433,6 +460,10 @@ GLFWAPI void glfwWindowHintString(int hint, const char* value)
|
|||||||
strncpy(_glfw.hints.window.x11.instanceName, value,
|
strncpy(_glfw.hints.window.x11.instanceName, value,
|
||||||
sizeof(_glfw.hints.window.x11.instanceName) - 1);
|
sizeof(_glfw.hints.window.x11.instanceName) - 1);
|
||||||
return;
|
return;
|
||||||
|
case GLFW_WAYLAND_APP_ID:
|
||||||
|
strncpy(_glfw.hints.window.wl.appId, value,
|
||||||
|
sizeof(_glfw.hints.window.wl.appId) - 1);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_glfwInputError(GLFW_INVALID_ENUM, "Invalid window hint string 0x%08X", hint);
|
_glfwInputError(GLFW_INVALID_ENUM, "Invalid window hint string 0x%08X", hint);
|
||||||
@@ -690,6 +721,15 @@ GLFWAPI void glfwGetWindowContentScale(GLFWwindow* handle,
|
|||||||
_glfwPlatformGetWindowContentScale(window, xscale, yscale);
|
_glfwPlatformGetWindowContentScale(window, xscale, yscale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GLFWAPI double glfwGetDoubleClickInterval(GLFWwindow* handle)
|
||||||
|
{
|
||||||
|
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||||
|
assert(window != NULL);
|
||||||
|
|
||||||
|
_GLFW_REQUIRE_INIT_OR_RETURN(0.5f);
|
||||||
|
return _glfwPlatformGetDoubleClickInterval(window);
|
||||||
|
}
|
||||||
|
|
||||||
GLFWAPI float glfwGetWindowOpacity(GLFWwindow* handle)
|
GLFWAPI float glfwGetWindowOpacity(GLFWwindow* handle)
|
||||||
{
|
{
|
||||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||||
|
|||||||
112
glfw/wl_init.c
vendored
112
glfw/wl_init.c
vendored
@@ -29,6 +29,8 @@
|
|||||||
#include "backend_utils.h"
|
#include "backend_utils.h"
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <limits.h>
|
||||||
#include <linux/input.h>
|
#include <linux/input.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -139,6 +141,7 @@ static void setCursor(const char* name)
|
|||||||
"Wayland: Standard cursor not found");
|
"Wayland: Standard cursor not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// TODO: handle animated cursors too.
|
||||||
image = cursor->images[0];
|
image = cursor->images[0];
|
||||||
|
|
||||||
if (!image)
|
if (!image)
|
||||||
@@ -308,7 +311,7 @@ static void pointerHandleButton(void* data,
|
|||||||
state == WL_POINTER_BUTTON_STATE_PRESSED
|
state == WL_POINTER_BUTTON_STATE_PRESSED
|
||||||
? GLFW_PRESS
|
? GLFW_PRESS
|
||||||
: GLFW_RELEASE,
|
: GLFW_RELEASE,
|
||||||
_glfw.wl.xkb.modifiers);
|
_glfw.wl.xkb.states.modifiers);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pointerHandleAxis(void* data,
|
static void pointerHandleAxis(void* data,
|
||||||
@@ -319,11 +322,6 @@ static void pointerHandleAxis(void* data,
|
|||||||
{
|
{
|
||||||
_GLFWwindow* window = _glfw.wl.pointerFocus;
|
_GLFWwindow* window = _glfw.wl.pointerFocus;
|
||||||
double x = 0.0, y = 0.0;
|
double x = 0.0, y = 0.0;
|
||||||
// Wayland scroll events are in pointer motion coordinate space (think two
|
|
||||||
// finger scroll). The factor 10 is commonly used to convert to "scroll
|
|
||||||
// step means 1.0.
|
|
||||||
const double scrollFactor = 1.0 / 10.0;
|
|
||||||
|
|
||||||
if (!window)
|
if (!window)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -331,11 +329,11 @@ static void pointerHandleAxis(void* data,
|
|||||||
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) * scrollFactor;
|
x = wl_fixed_to_double(value) * -1;
|
||||||
else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
|
else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
|
||||||
y = wl_fixed_to_double(value) * scrollFactor;
|
y = wl_fixed_to_double(value) * -1;
|
||||||
|
|
||||||
_glfwInputScroll(window, x, y);
|
_glfwInputScroll(window, x, y, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct wl_pointer_listener pointerListener = {
|
static const struct wl_pointer_listener pointerListener = {
|
||||||
@@ -407,6 +405,15 @@ static void keyboardHandleLeave(void* data,
|
|||||||
_glfwInputWindowFocus(window, GLFW_FALSE);
|
_glfwInputWindowFocus(window, GLFW_FALSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
dispatchPendingKeyRepeats(id_type timer_id, void *data) {
|
||||||
|
if (_glfw.wl.keyRepeatInfo.keyboardFocus != _glfw.wl.keyboardFocus || _glfw.wl.keyboardRepeatRate == 0) return;
|
||||||
|
glfw_xkb_handle_key_event(_glfw.wl.keyRepeatInfo.keyboardFocus, &_glfw.wl.xkb, _glfw.wl.keyRepeatInfo.key, GLFW_REPEAT);
|
||||||
|
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, (1.0 / (double)_glfw.wl.keyboardRepeatRate));
|
||||||
|
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void keyboardHandleKey(void* data,
|
static void keyboardHandleKey(void* data,
|
||||||
struct wl_keyboard* keyboard,
|
struct wl_keyboard* keyboard,
|
||||||
uint32_t serial,
|
uint32_t serial,
|
||||||
@@ -419,14 +426,18 @@ static void keyboardHandleKey(void* data,
|
|||||||
return;
|
return;
|
||||||
int action = state == WL_KEYBOARD_KEY_STATE_PRESSED ? GLFW_PRESS : GLFW_RELEASE;
|
int action = state == WL_KEYBOARD_KEY_STATE_PRESSED ? GLFW_PRESS : GLFW_RELEASE;
|
||||||
glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, key, action);
|
glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, key, action);
|
||||||
_glfw.wl.keyRepeatInfo.nextRepeatAt = 0;
|
GLFWbool repeatable = GLFW_FALSE;
|
||||||
|
|
||||||
if (action == GLFW_PRESS && _glfw.wl.keyboardRepeatRate > 0 && glfw_xkb_should_repeat(&_glfw.wl.xkb, key))
|
if (action == GLFW_PRESS && _glfw.wl.keyboardRepeatRate > 0 && glfw_xkb_should_repeat(&_glfw.wl.xkb, key))
|
||||||
{
|
{
|
||||||
_glfw.wl.keyRepeatInfo.key = key;
|
_glfw.wl.keyRepeatInfo.key = key;
|
||||||
_glfw.wl.keyRepeatInfo.nextRepeatAt = glfwGetTime() + (double)(_glfw.wl.keyboardRepeatDelay) / 1000.0;
|
repeatable = GLFW_TRUE;
|
||||||
_glfw.wl.keyRepeatInfo.keyboardFocus = window;
|
_glfw.wl.keyRepeatInfo.keyboardFocus = window;
|
||||||
}
|
}
|
||||||
|
if (repeatable) {
|
||||||
|
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, (double)(_glfw.wl.keyboardRepeatDelay) / 1000.0);
|
||||||
|
}
|
||||||
|
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, repeatable ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void keyboardHandleModifiers(void* data,
|
static void keyboardHandleModifiers(void* data,
|
||||||
@@ -552,6 +563,12 @@ static void registryHandleGlobal(void* data,
|
|||||||
_glfw.wl.seatVersion);
|
_glfw.wl.seatVersion);
|
||||||
wl_seat_add_listener(_glfw.wl.seat, &seatListener, NULL);
|
wl_seat_add_listener(_glfw.wl.seat, &seatListener, NULL);
|
||||||
}
|
}
|
||||||
|
if (_glfw.wl.seat) {
|
||||||
|
if (_glfw.wl.dataDeviceManager && !_glfw.wl.dataDevice) _glfwSetupWaylandDataDevice();
|
||||||
|
if (_glfw.wl.primarySelectionDeviceManager && !_glfw.wl.primarySelectionDevice) {
|
||||||
|
_glfwSetupWaylandPrimarySelectionDevice();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (strcmp(interface, "xdg_wm_base") == 0)
|
else if (strcmp(interface, "xdg_wm_base") == 0)
|
||||||
{
|
{
|
||||||
@@ -559,6 +576,12 @@ static void registryHandleGlobal(void* data,
|
|||||||
wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
|
wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
|
||||||
xdg_wm_base_add_listener(_glfw.wl.wmBase, &wmBaseListener, NULL);
|
xdg_wm_base_add_listener(_glfw.wl.wmBase, &wmBaseListener, NULL);
|
||||||
}
|
}
|
||||||
|
else if (strcmp(interface, "zxdg_decoration_manager_v1") == 0)
|
||||||
|
{
|
||||||
|
_glfw.wl.decorationManager =
|
||||||
|
wl_registry_bind(registry, name,
|
||||||
|
&zxdg_decoration_manager_v1_interface, 1);
|
||||||
|
}
|
||||||
else if (strcmp(interface, "wp_viewporter") == 0)
|
else if (strcmp(interface, "wp_viewporter") == 0)
|
||||||
{
|
{
|
||||||
_glfw.wl.viewporter =
|
_glfw.wl.viewporter =
|
||||||
@@ -585,6 +608,28 @@ static void registryHandleGlobal(void* data,
|
|||||||
&zwp_idle_inhibit_manager_v1_interface,
|
&zwp_idle_inhibit_manager_v1_interface,
|
||||||
1);
|
1);
|
||||||
}
|
}
|
||||||
|
else if (strcmp(interface, "wl_data_device_manager") == 0)
|
||||||
|
{
|
||||||
|
_glfw.wl.dataDeviceManager =
|
||||||
|
wl_registry_bind(registry, name,
|
||||||
|
&wl_data_device_manager_interface,
|
||||||
|
1);
|
||||||
|
if (_glfw.wl.seat && _glfw.wl.dataDeviceManager && !_glfw.wl.dataDevice) {
|
||||||
|
_glfwSetupWaylandDataDevice();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (strcmp(interface, "zwp_primary_selection_device_manager_v1") == 0 ||
|
||||||
|
strcmp(interface, "gtk_primary_selection_device_manager") == 0)
|
||||||
|
{
|
||||||
|
_glfw.wl.primarySelectionDeviceManager =
|
||||||
|
wl_registry_bind(registry, name,
|
||||||
|
&zwp_primary_selection_device_manager_v1_interface,
|
||||||
|
1);
|
||||||
|
if (_glfw.wl.seat && _glfw.wl.primarySelectionDeviceManager && !_glfw.wl.primarySelectionDevice) {
|
||||||
|
_glfwSetupWaylandPrimarySelectionDevice();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void registryHandleGlobalRemove(void *data,
|
static void registryHandleGlobalRemove(void *data,
|
||||||
@@ -664,7 +709,10 @@ int _glfwPlatformInit(void)
|
|||||||
"Wayland: Failed to connect to display");
|
"Wayland: Failed to connect to display");
|
||||||
return GLFW_FALSE;
|
return GLFW_FALSE;
|
||||||
}
|
}
|
||||||
initPollData(_glfw.wl.eventLoopData.fds, _glfw.wl.eventLoopData.wakeupFds[0], wl_display_get_fd(_glfw.wl.display));
|
initPollData(&_glfw.wl.eventLoopData, _glfw.wl.eventLoopData.wakeupFds[0], wl_display_get_fd(_glfw.wl.display));
|
||||||
|
glfw_dbus_init(&_glfw.wl.dbus, &_glfw.wl.eventLoopData);
|
||||||
|
_glfw.wl.keyRepeatInfo.keyRepeatTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-key-repeat", 0.5, 0, dispatchPendingKeyRepeats, NULL);
|
||||||
|
_glfw.wl.cursorAnimationTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-cursor-animation", 0.5, 0, animateCursorImage, NULL);
|
||||||
|
|
||||||
_glfw.wl.registry = wl_display_get_registry(_glfw.wl.display);
|
_glfw.wl.registry = wl_display_get_registry(_glfw.wl.display);
|
||||||
wl_registry_add_listener(_glfw.wl.registry, ®istryListener, NULL);
|
wl_registry_add_listener(_glfw.wl.registry, ®istryListener, NULL);
|
||||||
@@ -678,15 +726,27 @@ int _glfwPlatformInit(void)
|
|||||||
wl_display_roundtrip(_glfw.wl.display);
|
wl_display_roundtrip(_glfw.wl.display);
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
if (!_glfwInitJoysticksLinux())
|
if (_glfw.hints.init.enableJoysticks) {
|
||||||
return GLFW_FALSE;
|
if (!_glfwInitJoysticksLinux())
|
||||||
|
return GLFW_FALSE;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
_glfwInitTimerPOSIX();
|
_glfwInitTimerPOSIX();
|
||||||
|
|
||||||
if (_glfw.wl.pointer && _glfw.wl.shm)
|
if (_glfw.wl.pointer && _glfw.wl.shm)
|
||||||
{
|
{
|
||||||
_glfw.wl.cursorTheme = wl_cursor_theme_load(NULL, 32, _glfw.wl.shm);
|
const char *cursorTheme = getenv("XCURSOR_THEME"), *cursorSizeStr = getenv("XCURSOR_SIZE");
|
||||||
|
char *cursorSizeEnd;
|
||||||
|
int cursorSize = 32;
|
||||||
|
if (cursorSizeStr)
|
||||||
|
{
|
||||||
|
errno = 0;
|
||||||
|
long cursorSizeLong = strtol(cursorSizeStr, &cursorSizeEnd, 10);
|
||||||
|
if (!*cursorSizeEnd && !errno && cursorSizeLong > 0 && cursorSizeLong <= INT_MAX)
|
||||||
|
cursorSize = (int)cursorSizeLong;
|
||||||
|
}
|
||||||
|
_glfw.wl.cursorTheme = wl_cursor_theme_load(cursorTheme, cursorSize, _glfw.wl.shm);
|
||||||
if (!_glfw.wl.cursorTheme)
|
if (!_glfw.wl.cursorTheme)
|
||||||
{
|
{
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||||
@@ -713,6 +773,7 @@ void _glfwPlatformTerminate(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
glfw_xkb_release(&_glfw.wl.xkb);
|
glfw_xkb_release(&_glfw.wl.xkb);
|
||||||
|
glfw_dbus_terminate(&_glfw.wl.dbus);
|
||||||
|
|
||||||
if (_glfw.wl.cursorTheme)
|
if (_glfw.wl.cursorTheme)
|
||||||
wl_cursor_theme_destroy(_glfw.wl.cursorTheme);
|
wl_cursor_theme_destroy(_glfw.wl.cursorTheme);
|
||||||
@@ -734,6 +795,8 @@ void _glfwPlatformTerminate(void)
|
|||||||
wl_shell_destroy(_glfw.wl.shell);
|
wl_shell_destroy(_glfw.wl.shell);
|
||||||
if (_glfw.wl.viewporter)
|
if (_glfw.wl.viewporter)
|
||||||
wp_viewporter_destroy(_glfw.wl.viewporter);
|
wp_viewporter_destroy(_glfw.wl.viewporter);
|
||||||
|
if (_glfw.wl.decorationManager)
|
||||||
|
zxdg_decoration_manager_v1_destroy(_glfw.wl.decorationManager);
|
||||||
if (_glfw.wl.wmBase)
|
if (_glfw.wl.wmBase)
|
||||||
xdg_wm_base_destroy(_glfw.wl.wmBase);
|
xdg_wm_base_destroy(_glfw.wl.wmBase);
|
||||||
if (_glfw.wl.pointer)
|
if (_glfw.wl.pointer)
|
||||||
@@ -748,6 +811,21 @@ void _glfwPlatformTerminate(void)
|
|||||||
zwp_pointer_constraints_v1_destroy(_glfw.wl.pointerConstraints);
|
zwp_pointer_constraints_v1_destroy(_glfw.wl.pointerConstraints);
|
||||||
if (_glfw.wl.idleInhibitManager)
|
if (_glfw.wl.idleInhibitManager)
|
||||||
zwp_idle_inhibit_manager_v1_destroy(_glfw.wl.idleInhibitManager);
|
zwp_idle_inhibit_manager_v1_destroy(_glfw.wl.idleInhibitManager);
|
||||||
|
if (_glfw.wl.dataSourceForClipboard)
|
||||||
|
wl_data_source_destroy(_glfw.wl.dataSourceForClipboard);
|
||||||
|
for (size_t doi=0; doi < arraysz(_glfw.wl.dataOffers); doi++) {
|
||||||
|
if (_glfw.wl.dataOffers[doi].id) {
|
||||||
|
wl_data_offer_destroy(_glfw.wl.dataOffers[doi].id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_glfw.wl.dataDevice)
|
||||||
|
wl_data_device_destroy(_glfw.wl.dataDevice);
|
||||||
|
if (_glfw.wl.dataDeviceManager)
|
||||||
|
wl_data_device_manager_destroy(_glfw.wl.dataDeviceManager);
|
||||||
|
if (_glfw.wl.primarySelectionDevice)
|
||||||
|
zwp_primary_selection_device_v1_destroy(_glfw.wl.primarySelectionDevice);
|
||||||
|
if (_glfw.wl.primarySelectionDeviceManager)
|
||||||
|
zwp_primary_selection_device_manager_v1_destroy(_glfw.wl.primarySelectionDeviceManager);
|
||||||
if (_glfw.wl.registry)
|
if (_glfw.wl.registry)
|
||||||
wl_registry_destroy(_glfw.wl.registry);
|
wl_registry_destroy(_glfw.wl.registry);
|
||||||
if (_glfw.wl.display)
|
if (_glfw.wl.display)
|
||||||
@@ -756,6 +834,10 @@ void _glfwPlatformTerminate(void)
|
|||||||
wl_display_disconnect(_glfw.wl.display);
|
wl_display_disconnect(_glfw.wl.display);
|
||||||
}
|
}
|
||||||
closeFds(_glfw.wl.eventLoopData.wakeupFds, sizeof(_glfw.wl.eventLoopData.wakeupFds)/sizeof(_glfw.wl.eventLoopData.wakeupFds[0]));
|
closeFds(_glfw.wl.eventLoopData.wakeupFds, sizeof(_glfw.wl.eventLoopData.wakeupFds)/sizeof(_glfw.wl.eventLoopData.wakeupFds[0]));
|
||||||
|
free(_glfw.wl.clipboardString); _glfw.wl.clipboardString = NULL;
|
||||||
|
free(_glfw.wl.primarySelectionString); _glfw.wl.primarySelectionString = NULL;
|
||||||
|
free(_glfw.wl.pasteString); _glfw.wl.pasteString = NULL;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* _glfwPlatformGetVersionString(void)
|
const char* _glfwPlatformGetVersionString(void)
|
||||||
|
|||||||
58
glfw/wl_monitor.c
vendored
58
glfw/wl_monitor.c
vendored
@@ -32,16 +32,16 @@
|
|||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
|
|
||||||
static void geometry(void* data,
|
static void outputHandleGeometry(void* data,
|
||||||
struct wl_output* output,
|
struct wl_output* output,
|
||||||
int32_t x,
|
int32_t x,
|
||||||
int32_t y,
|
int32_t y,
|
||||||
int32_t physicalWidth,
|
int32_t physicalWidth,
|
||||||
int32_t physicalHeight,
|
int32_t physicalHeight,
|
||||||
int32_t subpixel,
|
int32_t subpixel,
|
||||||
const char* make,
|
const char* make,
|
||||||
const char* model,
|
const char* model,
|
||||||
int32_t transform)
|
int32_t transform)
|
||||||
{
|
{
|
||||||
struct _GLFWmonitor *monitor = data;
|
struct _GLFWmonitor *monitor = data;
|
||||||
char name[1024];
|
char name[1024];
|
||||||
@@ -55,12 +55,12 @@ static void geometry(void* data,
|
|||||||
monitor->name = _glfw_strdup(name);
|
monitor->name = _glfw_strdup(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void mode(void* data,
|
static void outputHandleMode(void* data,
|
||||||
struct wl_output* output,
|
struct wl_output* output,
|
||||||
uint32_t flags,
|
uint32_t flags,
|
||||||
int32_t width,
|
int32_t width,
|
||||||
int32_t height,
|
int32_t height,
|
||||||
int32_t refresh)
|
int32_t refresh)
|
||||||
{
|
{
|
||||||
struct _GLFWmonitor *monitor = data;
|
struct _GLFWmonitor *monitor = data;
|
||||||
GLFWvidmode mode;
|
GLFWvidmode mode;
|
||||||
@@ -81,16 +81,16 @@ static void mode(void* data,
|
|||||||
monitor->wl.currentMode = monitor->modeCount - 1;
|
monitor->wl.currentMode = monitor->modeCount - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void done(void* data, struct wl_output* output)
|
static void outputHandleDone(void* data, struct wl_output* output)
|
||||||
{
|
{
|
||||||
struct _GLFWmonitor *monitor = data;
|
struct _GLFWmonitor *monitor = data;
|
||||||
|
|
||||||
_glfwInputMonitor(monitor, GLFW_CONNECTED, _GLFW_INSERT_LAST);
|
_glfwInputMonitor(monitor, GLFW_CONNECTED, _GLFW_INSERT_LAST);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void scale(void* data,
|
static void outputHandleScale(void* data,
|
||||||
struct wl_output* output,
|
struct wl_output* output,
|
||||||
int32_t factor)
|
int32_t factor)
|
||||||
{
|
{
|
||||||
struct _GLFWmonitor *monitor = data;
|
struct _GLFWmonitor *monitor = data;
|
||||||
|
|
||||||
@@ -98,10 +98,10 @@ static void scale(void* data,
|
|||||||
}
|
}
|
||||||
|
|
||||||
static const struct wl_output_listener outputListener = {
|
static const struct wl_output_listener outputListener = {
|
||||||
geometry,
|
outputHandleGeometry,
|
||||||
mode,
|
outputHandleMode,
|
||||||
done,
|
outputHandleDone,
|
||||||
scale,
|
outputHandleScale,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -180,18 +180,17 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode)
|
|||||||
*mode = monitor->modes[monitor->wl.currentMode];
|
*mode = monitor->modes[monitor->wl.currentMode];
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||||
{
|
{
|
||||||
// TODO
|
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||||
"Wayland: Gamma ramp getting not supported yet");
|
"Wayland: Gamma ramp access is not available");
|
||||||
|
return GLFW_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
|
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
|
||||||
{
|
{
|
||||||
// TODO
|
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||||
"Wayland: Gamma ramp setting not supported yet");
|
"Wayland: Gamma ramp access is not available");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -205,4 +204,3 @@ GLFWAPI struct wl_output* glfwGetWaylandMonitor(GLFWmonitor* handle)
|
|||||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||||
return monitor->wl.output;
|
return monitor->wl.output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
75
glfw/wl_platform.h
vendored
75
glfw/wl_platform.h
vendored
@@ -49,15 +49,18 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
|
|||||||
#else
|
#else
|
||||||
#include "null_joystick.h"
|
#include "null_joystick.h"
|
||||||
#endif
|
#endif
|
||||||
|
#include "backend_utils.h"
|
||||||
#include "xkb_glfw.h"
|
#include "xkb_glfw.h"
|
||||||
#include "egl_context.h"
|
#include "egl_context.h"
|
||||||
#include "osmesa_context.h"
|
#include "osmesa_context.h"
|
||||||
|
|
||||||
#include "wayland-xdg-shell-client-protocol.h"
|
#include "wayland-xdg-shell-client-protocol.h"
|
||||||
#include "wayland-viewporter-client-protocol.h"
|
#include "wayland-viewporter-client-protocol.h"
|
||||||
|
#include "wayland-xdg-decoration-unstable-v1-client-protocol.h"
|
||||||
#include "wayland-relative-pointer-unstable-v1-client-protocol.h"
|
#include "wayland-relative-pointer-unstable-v1-client-protocol.h"
|
||||||
#include "wayland-pointer-constraints-unstable-v1-client-protocol.h"
|
#include "wayland-pointer-constraints-unstable-v1-client-protocol.h"
|
||||||
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
|
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
|
||||||
|
#include "wayland-primary-selection-unstable-v1-client-protocol.h"
|
||||||
|
|
||||||
#define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL)
|
#define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL)
|
||||||
#define _glfw_dlclose(handle) dlclose(handle)
|
#define _glfw_dlclose(handle) dlclose(handle)
|
||||||
@@ -142,12 +145,14 @@ typedef struct _GLFWwindowWayland
|
|||||||
struct {
|
struct {
|
||||||
struct xdg_surface* surface;
|
struct xdg_surface* surface;
|
||||||
struct xdg_toplevel* toplevel;
|
struct xdg_toplevel* toplevel;
|
||||||
|
struct zxdg_toplevel_decoration_v1* decoration;
|
||||||
} xdg;
|
} xdg;
|
||||||
|
|
||||||
_GLFWcursor* currentCursor;
|
_GLFWcursor* currentCursor;
|
||||||
double cursorPosX, cursorPosY;
|
double cursorPosX, cursorPosY;
|
||||||
|
|
||||||
char* title;
|
char* title;
|
||||||
|
char appId[256];
|
||||||
|
|
||||||
// We need to track the monitors the window spans on to calculate the
|
// We need to track the monitors the window spans on to calculate the
|
||||||
// optimal scaling factor.
|
// optimal scaling factor.
|
||||||
@@ -167,13 +172,53 @@ typedef struct _GLFWwindowWayland
|
|||||||
GLFWbool justCreated;
|
GLFWbool justCreated;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
GLFWbool serverSide;
|
||||||
struct wl_buffer* buffer;
|
struct wl_buffer* buffer;
|
||||||
_GLFWdecorationWayland top, left, right, bottom;
|
_GLFWdecorationWayland top, left, right, bottom;
|
||||||
int focus;
|
int focus;
|
||||||
} decorations;
|
} decorations;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
unsigned long long id;
|
||||||
|
void(*callback)(unsigned long long id);
|
||||||
|
struct wl_callback *current_wl_callback;
|
||||||
|
} frameCallbackData;
|
||||||
|
|
||||||
|
|
||||||
} _GLFWwindowWayland;
|
} _GLFWwindowWayland;
|
||||||
|
|
||||||
|
typedef enum _GLFWWaylandOfferType
|
||||||
|
{
|
||||||
|
EXPIRED,
|
||||||
|
CLIPBOARD,
|
||||||
|
DRAG_AND_DROP,
|
||||||
|
PRIMARY_SELECTION
|
||||||
|
}_GLFWWaylandOfferType ;
|
||||||
|
|
||||||
|
typedef struct _GLFWWaylandDataOffer
|
||||||
|
{
|
||||||
|
struct wl_data_offer *id;
|
||||||
|
const char *mime;
|
||||||
|
_GLFWWaylandOfferType offer_type;
|
||||||
|
size_t idx;
|
||||||
|
int is_self_offer;
|
||||||
|
int has_uri_list;
|
||||||
|
uint32_t source_actions;
|
||||||
|
uint32_t dnd_action;
|
||||||
|
struct wl_surface *surface;
|
||||||
|
} _GLFWWaylandDataOffer;
|
||||||
|
|
||||||
|
typedef struct _GLFWWaylandPrimaryOffer
|
||||||
|
{
|
||||||
|
struct zwp_primary_selection_offer_v1 *id;
|
||||||
|
const char *mime;
|
||||||
|
_GLFWWaylandOfferType offer_type;
|
||||||
|
size_t idx;
|
||||||
|
int is_self_offer;
|
||||||
|
int has_uri_list;
|
||||||
|
struct wl_surface *surface;
|
||||||
|
} _GLFWWaylandPrimaryOffer;
|
||||||
|
|
||||||
// Wayland-specific global data
|
// Wayland-specific global data
|
||||||
//
|
//
|
||||||
typedef struct _GLFWlibraryWayland
|
typedef struct _GLFWlibraryWayland
|
||||||
@@ -188,10 +233,17 @@ typedef struct _GLFWlibraryWayland
|
|||||||
struct wl_pointer* pointer;
|
struct wl_pointer* pointer;
|
||||||
struct wl_keyboard* keyboard;
|
struct wl_keyboard* keyboard;
|
||||||
struct xdg_wm_base* wmBase;
|
struct xdg_wm_base* wmBase;
|
||||||
|
struct zxdg_decoration_manager_v1* decorationManager;
|
||||||
struct wp_viewporter* viewporter;
|
struct wp_viewporter* viewporter;
|
||||||
struct zwp_relative_pointer_manager_v1* relativePointerManager;
|
struct zwp_relative_pointer_manager_v1* relativePointerManager;
|
||||||
struct zwp_pointer_constraints_v1* pointerConstraints;
|
struct zwp_pointer_constraints_v1* pointerConstraints;
|
||||||
struct zwp_idle_inhibit_manager_v1* idleInhibitManager;
|
struct zwp_idle_inhibit_manager_v1* idleInhibitManager;
|
||||||
|
struct wl_data_device_manager* dataDeviceManager;
|
||||||
|
struct wl_data_device* dataDevice;
|
||||||
|
struct wl_data_source* dataSourceForClipboard;
|
||||||
|
struct zwp_primary_selection_device_manager_v1* primarySelectionDeviceManager;
|
||||||
|
struct zwp_primary_selection_device_v1* primarySelectionDevice;
|
||||||
|
struct zwp_primary_selection_source_v1* dataSourceForPrimarySelection;
|
||||||
|
|
||||||
int compositorVersion;
|
int compositorVersion;
|
||||||
int seatVersion;
|
int seatVersion;
|
||||||
@@ -204,10 +256,12 @@ typedef struct _GLFWlibraryWayland
|
|||||||
int32_t keyboardRepeatDelay;
|
int32_t keyboardRepeatDelay;
|
||||||
struct {
|
struct {
|
||||||
uint32_t key;
|
uint32_t key;
|
||||||
double nextRepeatAt;
|
id_type keyRepeatTimer;
|
||||||
_GLFWwindow* keyboardFocus;
|
_GLFWwindow* keyboardFocus;
|
||||||
} keyRepeatInfo;
|
} keyRepeatInfo;
|
||||||
|
id_type cursorAnimationTimer;
|
||||||
_GLFWXKBData xkb;
|
_GLFWXKBData xkb;
|
||||||
|
_GLFWDBUSData dbus;
|
||||||
|
|
||||||
_GLFWwindow* pointerFocus;
|
_GLFWwindow* pointerFocus;
|
||||||
_GLFWwindow* keyboardFocus;
|
_GLFWwindow* keyboardFocus;
|
||||||
@@ -229,11 +283,14 @@ typedef struct _GLFWlibraryWayland
|
|||||||
PFN_wl_egl_window_resize window_resize;
|
PFN_wl_egl_window_resize window_resize;
|
||||||
} egl;
|
} egl;
|
||||||
|
|
||||||
struct {
|
EventLoopData eventLoopData;
|
||||||
struct pollfd fds[2];
|
char* pasteString;
|
||||||
int wakeupFds[2];
|
char* clipboardString;
|
||||||
} eventLoopData;
|
size_t dataOffersCounter;
|
||||||
|
_GLFWWaylandDataOffer dataOffers[8];
|
||||||
|
char* primarySelectionString;
|
||||||
|
size_t primarySelectionOffersCounter;
|
||||||
|
_GLFWWaylandPrimaryOffer primarySelectionOffers[8];
|
||||||
} _GLFWlibraryWayland;
|
} _GLFWlibraryWayland;
|
||||||
|
|
||||||
// Wayland-specific per-monitor data
|
// Wayland-specific per-monitor data
|
||||||
@@ -254,11 +311,15 @@ typedef struct _GLFWmonitorWayland
|
|||||||
//
|
//
|
||||||
typedef struct _GLFWcursorWayland
|
typedef struct _GLFWcursorWayland
|
||||||
{
|
{
|
||||||
struct wl_cursor_image* image;
|
struct wl_cursor* cursor;
|
||||||
struct wl_buffer* buffer;
|
struct wl_buffer* buffer;
|
||||||
int width, height;
|
int width, height;
|
||||||
int xhot, yhot;
|
int xhot, yhot;
|
||||||
|
int currentImage;
|
||||||
} _GLFWcursorWayland;
|
} _GLFWcursorWayland;
|
||||||
|
|
||||||
|
|
||||||
void _glfwAddOutputWayland(uint32_t name, uint32_t version);
|
void _glfwAddOutputWayland(uint32_t name, uint32_t version);
|
||||||
|
void _glfwSetupWaylandDataDevice();
|
||||||
|
void _glfwSetupWaylandPrimarySelectionDevice();
|
||||||
|
void animateCursorImage(id_type timer_id, void *data);
|
||||||
|
|||||||
763
glfw/wl_window.c
vendored
763
glfw/wl_window.c
vendored
File diff suppressed because it is too large
Load Diff
14
glfw/x11_init.c
vendored
14
glfw/x11_init.c
vendored
@@ -37,6 +37,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <locale.h>
|
#include <locale.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
|
||||||
// Check whether the specified atom is supported
|
// Check whether the specified atom is supported
|
||||||
@@ -640,8 +641,8 @@ int _glfwPlatformInit(void)
|
|||||||
return GLFW_FALSE;
|
return GLFW_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
initPollData(_glfw.x11.eventLoopData.fds, _glfw.x11.eventLoopData.wakeupFds[0], ConnectionNumber(_glfw.x11.display));
|
initPollData(&_glfw.x11.eventLoopData, _glfw.x11.eventLoopData.wakeupFds[0], ConnectionNumber(_glfw.x11.display));
|
||||||
_glfw.x11.eventLoopData.fds[2].events = POLLIN;
|
glfw_dbus_init(&_glfw.x11.dbus, &_glfw.x11.eventLoopData);
|
||||||
|
|
||||||
_glfw.x11.screen = DefaultScreen(_glfw.x11.display);
|
_glfw.x11.screen = DefaultScreen(_glfw.x11.display);
|
||||||
_glfw.x11.root = RootWindow(_glfw.x11.display, _glfw.x11.screen);
|
_glfw.x11.root = RootWindow(_glfw.x11.display, _glfw.x11.screen);
|
||||||
@@ -658,8 +659,12 @@ int _glfwPlatformInit(void)
|
|||||||
_glfw.x11.hiddenCursorHandle = createHiddenCursor();
|
_glfw.x11.hiddenCursorHandle = createHiddenCursor();
|
||||||
|
|
||||||
#if defined(__linux__)
|
#if defined(__linux__)
|
||||||
if (!_glfwInitJoysticksLinux())
|
if (_glfw.hints.init.enableJoysticks) {
|
||||||
return GLFW_FALSE;
|
if (!_glfwInitJoysticksLinux())
|
||||||
|
return GLFW_FALSE;
|
||||||
|
if (_glfw.linjs.inotify > 0)
|
||||||
|
addWatch(&_glfw.x11.eventLoopData, "joystick", _glfw.linjs.inotify, POLLIN, 1, NULL, NULL);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
_glfwInitTimerPOSIX();
|
_glfwInitTimerPOSIX();
|
||||||
@@ -689,6 +694,7 @@ void _glfwPlatformTerminate(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
glfw_xkb_release(&_glfw.x11.xkb);
|
glfw_xkb_release(&_glfw.x11.xkb);
|
||||||
|
glfw_dbus_terminate(&_glfw.x11.dbus);
|
||||||
free(_glfw.x11.primarySelectionString);
|
free(_glfw.x11.primarySelectionString);
|
||||||
free(_glfw.x11.clipboardString);
|
free(_glfw.x11.clipboardString);
|
||||||
|
|
||||||
|
|||||||
42
glfw/x11_monitor.c
vendored
42
glfw/x11_monitor.c
vendored
@@ -152,6 +152,11 @@ void _glfwPollMonitorsX11(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
ci = XRRGetCrtcInfo(_glfw.x11.display, sr, oi->crtc);
|
ci = XRRGetCrtcInfo(_glfw.x11.display, sr, oi->crtc);
|
||||||
|
if (!ci)
|
||||||
|
{
|
||||||
|
XRRFreeOutputInfo(oi);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (ci->rotation == RR_Rotate_90 || ci->rotation == RR_Rotate_270)
|
if (ci->rotation == RR_Rotate_90 || ci->rotation == RR_Rotate_270)
|
||||||
{
|
{
|
||||||
widthMM = oi->mm_height;
|
widthMM = oi->mm_height;
|
||||||
@@ -321,13 +326,15 @@ void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos)
|
|||||||
|
|
||||||
sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
||||||
ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
|
ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
|
||||||
|
if (ci)
|
||||||
|
{
|
||||||
|
if (xpos)
|
||||||
|
*xpos = ci->x;
|
||||||
|
if (ypos)
|
||||||
|
*ypos = ci->y;
|
||||||
|
|
||||||
if (xpos)
|
XRRFreeCrtcInfo(ci);
|
||||||
*xpos = ci->x;
|
}
|
||||||
if (ypos)
|
|
||||||
*ypos = ci->y;
|
|
||||||
|
|
||||||
XRRFreeCrtcInfo(ci);
|
|
||||||
XRRFreeScreenResources(sr);
|
XRRFreeScreenResources(sr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -406,9 +413,13 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode)
|
|||||||
sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
||||||
ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
|
ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
|
||||||
|
|
||||||
*mode = vidmodeFromModeInfo(getModeInfo(sr, ci->mode), ci);
|
if (ci) {
|
||||||
|
const XRRModeInfo* mi = getModeInfo(sr, ci->mode);
|
||||||
|
if (mi) // mi can be NULL if the monitor has been disconnected
|
||||||
|
*mode = vidmodeFromModeInfo(mi, ci);
|
||||||
|
|
||||||
XRRFreeCrtcInfo(ci);
|
XRRFreeCrtcInfo(ci);
|
||||||
|
}
|
||||||
XRRFreeScreenResources(sr);
|
XRRFreeScreenResources(sr);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -422,7 +433,7 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||||
{
|
{
|
||||||
if (_glfw.x11.randr.available && !_glfw.x11.randr.gammaBroken)
|
if (_glfw.x11.randr.available && !_glfw.x11.randr.gammaBroken)
|
||||||
{
|
{
|
||||||
@@ -438,6 +449,7 @@ void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
|||||||
memcpy(ramp->blue, gamma->blue, size * sizeof(unsigned short));
|
memcpy(ramp->blue, gamma->blue, size * sizeof(unsigned short));
|
||||||
|
|
||||||
XRRFreeGamma(gamma);
|
XRRFreeGamma(gamma);
|
||||||
|
return GLFW_TRUE;
|
||||||
}
|
}
|
||||||
else if (_glfw.x11.vidmode.available)
|
else if (_glfw.x11.vidmode.available)
|
||||||
{
|
{
|
||||||
@@ -449,6 +461,13 @@ void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
|||||||
XF86VidModeGetGammaRamp(_glfw.x11.display,
|
XF86VidModeGetGammaRamp(_glfw.x11.display,
|
||||||
_glfw.x11.screen,
|
_glfw.x11.screen,
|
||||||
ramp->size, ramp->red, ramp->green, ramp->blue);
|
ramp->size, ramp->red, ramp->green, ramp->blue);
|
||||||
|
return GLFW_TRUE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||||
|
"X11: Gamma ramp access not supported by server");
|
||||||
|
return GLFW_FALSE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,6 +500,10 @@ void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
|
|||||||
(unsigned short*) ramp->green,
|
(unsigned short*) ramp->green,
|
||||||
(unsigned short*) ramp->blue);
|
(unsigned short*) ramp->blue);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||||
|
"X11: Gamma ramp access not supported by server");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -501,4 +524,3 @@ GLFWAPI RROutput glfwGetX11Monitor(GLFWmonitor* handle)
|
|||||||
_GLFW_REQUIRE_INIT_OR_RETURN(None);
|
_GLFW_REQUIRE_INIT_OR_RETURN(None);
|
||||||
return monitor->x11.output;
|
return monitor->x11.output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
12
glfw/x11_platform.h
vendored
12
glfw/x11_platform.h
vendored
@@ -53,6 +53,7 @@
|
|||||||
|
|
||||||
// The libxkb library is used for improved keyboard support
|
// The libxkb library is used for improved keyboard support
|
||||||
#include "xkb_glfw.h"
|
#include "xkb_glfw.h"
|
||||||
|
#include "backend_utils.h"
|
||||||
|
|
||||||
typedef XRRCrtcGamma* (* PFN_XRRAllocGamma)(int);
|
typedef XRRCrtcGamma* (* PFN_XRRAllocGamma)(int);
|
||||||
typedef void (* PFN_XRRFreeCrtcInfo)(XRRCrtcInfo*);
|
typedef void (* PFN_XRRFreeCrtcInfo)(XRRCrtcInfo*);
|
||||||
@@ -103,8 +104,6 @@ typedef XineramaScreenInfo* (* PFN_XineramaQueryScreens)(Display*,int*);
|
|||||||
#define XineramaQueryExtension _glfw.x11.xinerama.QueryExtension
|
#define XineramaQueryExtension _glfw.x11.xinerama.QueryExtension
|
||||||
#define XineramaQueryScreens _glfw.x11.xinerama.QueryScreens
|
#define XineramaQueryScreens _glfw.x11.xinerama.QueryScreens
|
||||||
|
|
||||||
typedef struct xcb_connection_t xcb_connection_t;
|
|
||||||
|
|
||||||
typedef Bool (* PFN_XF86VidModeQueryExtension)(Display*,int*,int*);
|
typedef Bool (* PFN_XF86VidModeQueryExtension)(Display*,int*,int*);
|
||||||
typedef Bool (* PFN_XF86VidModeGetGammaRamp)(Display*,int,int,unsigned short*,unsigned short*,unsigned short*);
|
typedef Bool (* PFN_XF86VidModeGetGammaRamp)(Display*,int,int,unsigned short*,unsigned short*,unsigned short*);
|
||||||
typedef Bool (* PFN_XF86VidModeSetGammaRamp)(Display*,int,int,unsigned short*,unsigned short*,unsigned short*);
|
typedef Bool (* PFN_XF86VidModeSetGammaRamp)(Display*,int,int,unsigned short*,unsigned short*,unsigned short*);
|
||||||
@@ -199,9 +198,6 @@ typedef struct _GLFWwindowX11
|
|||||||
// The last position the cursor was warped to by GLFW
|
// The last position the cursor was warped to by GLFW
|
||||||
int warpCursorPosX, warpCursorPosY;
|
int warpCursorPosX, warpCursorPosY;
|
||||||
|
|
||||||
// The time of the last KeyPress event
|
|
||||||
Time lastKeyTime;
|
|
||||||
|
|
||||||
} _GLFWwindowX11;
|
} _GLFWwindowX11;
|
||||||
|
|
||||||
// X11-specific global data
|
// X11-specific global data
|
||||||
@@ -315,6 +311,7 @@ typedef struct _GLFWlibraryX11
|
|||||||
} randr;
|
} randr;
|
||||||
|
|
||||||
_GLFWXKBData xkb;
|
_GLFWXKBData xkb;
|
||||||
|
_GLFWDBUSData dbus;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
int count;
|
int count;
|
||||||
@@ -382,10 +379,7 @@ typedef struct _GLFWlibraryX11
|
|||||||
PFN_XRenderFindVisualFormat FindVisualFormat;
|
PFN_XRenderFindVisualFormat FindVisualFormat;
|
||||||
} xrender;
|
} xrender;
|
||||||
|
|
||||||
struct {
|
EventLoopData eventLoopData;
|
||||||
struct pollfd fds[3];
|
|
||||||
int wakeupFds[2];
|
|
||||||
} eventLoopData;
|
|
||||||
|
|
||||||
} _GLFWlibraryX11;
|
} _GLFWlibraryX11;
|
||||||
|
|
||||||
|
|||||||
322
glfw/x11_window.c
vendored
322
glfw/x11_window.c
vendored
@@ -54,47 +54,32 @@
|
|||||||
// This avoids blocking other threads via the per-display Xlib lock that also
|
// This avoids blocking other threads via the per-display Xlib lock that also
|
||||||
// covers GLX functions
|
// covers GLX functions
|
||||||
//
|
//
|
||||||
static GLFWbool waitForEvent(double* timeout)
|
GLFWbool _glfwDispatchX11Events(void);
|
||||||
{
|
|
||||||
nfds_t count = 2;
|
|
||||||
|
|
||||||
#if defined(__linux__)
|
static void
|
||||||
if (_glfw.linjs.inotify > 0)
|
handleEvents(double timeout) {
|
||||||
{
|
int display_read_ok = pollForEvents(&_glfw.x11.eventLoopData, timeout);
|
||||||
count = 3;
|
if (display_read_ok) _glfwDispatchX11Events();
|
||||||
_glfw.x11.eventLoopData.fds[2].fd = _glfw.linjs.inotify;
|
glfw_ibus_dispatch(&_glfw.x11.xkb.ibus);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
for (nfds_t i = 0; i < count; i++) _glfw.x11.eventLoopData.fds[i].revents = 0;
|
|
||||||
if (timeout)
|
|
||||||
{
|
|
||||||
const uint64_t base = _glfwPlatformGetTimerValue();
|
|
||||||
const int result = pollWithTimeout(_glfw.x11.eventLoopData.fds, count, *timeout);
|
|
||||||
*timeout -= (_glfwPlatformGetTimerValue() - base) /
|
|
||||||
(double) _glfwPlatformGetTimerFrequency();
|
|
||||||
|
|
||||||
if (result > 0)
|
static GLFWbool
|
||||||
{
|
waitForX11Event(double timeout) {
|
||||||
if (_glfw.x11.eventLoopData.fds[0].revents & POLLIN) drainFd(_glfw.x11.eventLoopData.fds[0].fd);
|
// returns true iff there is X11 data waiting to be read, does not run watches and timers
|
||||||
return GLFW_TRUE;
|
double end_time = glfwGetTime() + timeout;
|
||||||
}
|
while(GLFW_TRUE) {
|
||||||
if (result == 0)
|
if (timeout >= 0) {
|
||||||
return GLFW_FALSE;
|
const int result = pollWithTimeout(_glfw.x11.eventLoopData.fds, 1, timeout);
|
||||||
if (*timeout > 0 && (errno == EINTR || errno == EAGAIN)) continue;
|
if (result > 0) return GLFW_TRUE;
|
||||||
|
timeout = end_time - glfwGetTime();
|
||||||
|
if (timeout <= 0) return GLFW_FALSE;
|
||||||
|
if (result < 0 && (errno == EINTR || errno == EAGAIN)) continue;
|
||||||
|
return GLFW_FALSE;
|
||||||
|
} else {
|
||||||
|
const int result = poll(_glfw.x11.eventLoopData.fds, 1, -1);
|
||||||
|
if (result > 0) return GLFW_TRUE;
|
||||||
|
if (result < 0 && (errno == EINTR || errno == EAGAIN)) continue;
|
||||||
return GLFW_FALSE;
|
return GLFW_FALSE;
|
||||||
}
|
|
||||||
else {
|
|
||||||
const int result = poll(_glfw.x11.eventLoopData.fds, count, -1);
|
|
||||||
if (result > 0)
|
|
||||||
{
|
|
||||||
if (_glfw.x11.eventLoopData.fds[0].revents & POLLIN) drainFd(_glfw.x11.eventLoopData.fds[0].fd);
|
|
||||||
return GLFW_TRUE;
|
|
||||||
}
|
|
||||||
if (result == 0)
|
|
||||||
return GLFW_FALSE;
|
|
||||||
if (errno != EINTR && errno != EAGAIN) return GLFW_FALSE;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,14 +90,13 @@ static GLFWbool waitForEvent(double* timeout)
|
|||||||
static GLFWbool waitForVisibilityNotify(_GLFWwindow* window)
|
static GLFWbool waitForVisibilityNotify(_GLFWwindow* window)
|
||||||
{
|
{
|
||||||
XEvent dummy;
|
XEvent dummy;
|
||||||
double timeout = 0.1;
|
|
||||||
|
|
||||||
while (!XCheckTypedWindowEvent(_glfw.x11.display,
|
while (!XCheckTypedWindowEvent(_glfw.x11.display,
|
||||||
window->x11.handle,
|
window->x11.handle,
|
||||||
VisibilityNotify,
|
VisibilityNotify,
|
||||||
&dummy))
|
&dummy))
|
||||||
{
|
{
|
||||||
if (!waitForEvent(&timeout))
|
if (!waitForX11Event(0.1))
|
||||||
return GLFW_FALSE;
|
return GLFW_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,29 +161,6 @@ static Bool isSelPropNewValueNotify(Display* display, XEvent* event, XPointer po
|
|||||||
event->xproperty.atom == notification->xselection.property;
|
event->xproperty.atom == notification->xselection.property;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translates a GLFW standard cursor to a font cursor shape
|
|
||||||
//
|
|
||||||
static int translateCursorShape(int shape)
|
|
||||||
{
|
|
||||||
switch (shape)
|
|
||||||
{
|
|
||||||
case GLFW_ARROW_CURSOR:
|
|
||||||
return XC_left_ptr;
|
|
||||||
case GLFW_IBEAM_CURSOR:
|
|
||||||
return XC_xterm;
|
|
||||||
case GLFW_CROSSHAIR_CURSOR:
|
|
||||||
return XC_crosshair;
|
|
||||||
case GLFW_HAND_CURSOR:
|
|
||||||
return XC_hand1;
|
|
||||||
case GLFW_HRESIZE_CURSOR:
|
|
||||||
return XC_sb_h_double_arrow;
|
|
||||||
case GLFW_VRESIZE_CURSOR:
|
|
||||||
return XC_sb_v_double_arrow;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Translates an X event modifier state mask
|
// Translates an X event modifier state mask
|
||||||
//
|
//
|
||||||
static int translateState(int state)
|
static int translateState(int state)
|
||||||
@@ -222,23 +183,6 @@ static int translateState(int state)
|
|||||||
return mods;
|
return mods;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the GLFW window corresponding to the specified X11 window
|
|
||||||
//
|
|
||||||
static _GLFWwindow* findWindowByHandle(Window handle)
|
|
||||||
{
|
|
||||||
_GLFWwindow* window;
|
|
||||||
|
|
||||||
if (XFindContext(_glfw.x11.display,
|
|
||||||
handle,
|
|
||||||
_glfw.x11.context,
|
|
||||||
(XPointer*) &window) != 0)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return window;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sends an EWMH or ICCCM event to the window manager
|
// Sends an EWMH or ICCCM event to the window manager
|
||||||
//
|
//
|
||||||
static void sendEventToWM(_GLFWwindow* window, Atom type,
|
static void sendEventToWM(_GLFWwindow* window, Atom type,
|
||||||
@@ -406,56 +350,6 @@ static void updateWindowMode(_GLFWwindow* window)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Splits and translates a text/uri-list into separate file paths
|
|
||||||
// NOTE: This function destroys the provided string
|
|
||||||
//
|
|
||||||
static char** parseUriList(char* text, int* count)
|
|
||||||
{
|
|
||||||
const char* prefix = "file://";
|
|
||||||
char** paths = NULL;
|
|
||||||
char* line;
|
|
||||||
|
|
||||||
*count = 0;
|
|
||||||
|
|
||||||
while ((line = strtok(text, "\r\n")))
|
|
||||||
{
|
|
||||||
text = NULL;
|
|
||||||
|
|
||||||
if (line[0] == '#')
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (strncmp(line, prefix, strlen(prefix)) == 0)
|
|
||||||
{
|
|
||||||
line += strlen(prefix);
|
|
||||||
// TODO: Validate hostname
|
|
||||||
while (*line != '/')
|
|
||||||
line++;
|
|
||||||
}
|
|
||||||
|
|
||||||
(*count)++;
|
|
||||||
|
|
||||||
char* path = calloc(strlen(line) + 1, 1);
|
|
||||||
paths = realloc(paths, *count * sizeof(char*));
|
|
||||||
paths[*count - 1] = path;
|
|
||||||
|
|
||||||
while (*line)
|
|
||||||
{
|
|
||||||
if (line[0] == '%' && line[1] && line[2])
|
|
||||||
{
|
|
||||||
const char digits[3] = { line[1], line[2], '\0' };
|
|
||||||
*path = strtol(digits, NULL, 16);
|
|
||||||
line += 2;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
*path = *line;
|
|
||||||
|
|
||||||
path++;
|
|
||||||
line++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return paths;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode a Unicode code point to a UTF-8 stream
|
// Encode a Unicode code point to a UTF-8 stream
|
||||||
// Based on cutef8 by Jeff Bezanson (Public Domain)
|
// Based on cutef8 by Jeff Bezanson (Public Domain)
|
||||||
@@ -598,6 +492,15 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
|
|||||||
const _GLFWwndconfig* wndconfig,
|
const _GLFWwndconfig* wndconfig,
|
||||||
Visual* visual, int depth)
|
Visual* visual, int depth)
|
||||||
{
|
{
|
||||||
|
int width = wndconfig->width;
|
||||||
|
int height = wndconfig->height;
|
||||||
|
|
||||||
|
if (wndconfig->scaleToMonitor)
|
||||||
|
{
|
||||||
|
width *= _glfw.x11.contentScaleX;
|
||||||
|
height *= _glfw.x11.contentScaleY;
|
||||||
|
}
|
||||||
|
|
||||||
// Create a colormap based on the visual used by the current context
|
// Create a colormap based on the visual used by the current context
|
||||||
window->x11.colormap = XCreateColormap(_glfw.x11.display,
|
window->x11.colormap = XCreateColormap(_glfw.x11.display,
|
||||||
_glfw.x11.root,
|
_glfw.x11.root,
|
||||||
@@ -623,7 +526,7 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
|
|||||||
window->x11.handle = XCreateWindow(_glfw.x11.display,
|
window->x11.handle = XCreateWindow(_glfw.x11.display,
|
||||||
_glfw.x11.root,
|
_glfw.x11.root,
|
||||||
0, 0,
|
0, 0,
|
||||||
wndconfig->width, wndconfig->height,
|
width, height,
|
||||||
0, // Border width
|
0, // Border width
|
||||||
depth, // Color depth
|
depth, // Color depth
|
||||||
InputOutput,
|
InputOutput,
|
||||||
@@ -726,7 +629,7 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
|
|||||||
XFree(hints);
|
XFree(hints);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateNormalHints(window, wndconfig->width, wndconfig->height);
|
updateNormalHints(window, width, height);
|
||||||
|
|
||||||
// Set ICCCM WM_CLASS property
|
// Set ICCCM WM_CLASS property
|
||||||
{
|
{
|
||||||
@@ -970,6 +873,7 @@ static const char* getSelectionString(Atom selection)
|
|||||||
Atom actualType;
|
Atom actualType;
|
||||||
int actualFormat;
|
int actualFormat;
|
||||||
unsigned long itemCount, bytesAfter;
|
unsigned long itemCount, bytesAfter;
|
||||||
|
double start = glfwGetTime();
|
||||||
XEvent notification, dummy;
|
XEvent notification, dummy;
|
||||||
|
|
||||||
XConvertSelection(_glfw.x11.display,
|
XConvertSelection(_glfw.x11.display,
|
||||||
@@ -984,7 +888,10 @@ static const char* getSelectionString(Atom selection)
|
|||||||
SelectionNotify,
|
SelectionNotify,
|
||||||
¬ification))
|
¬ification))
|
||||||
{
|
{
|
||||||
waitForEvent(NULL);
|
double time = glfwGetTime();
|
||||||
|
if (time - start > 2)
|
||||||
|
return "";
|
||||||
|
waitForX11Event(2.0 - (time - start));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notification.xselection.property == None)
|
if (notification.xselection.property == None)
|
||||||
@@ -1015,12 +922,16 @@ static const char* getSelectionString(Atom selection)
|
|||||||
|
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
|
start = glfwGetTime();
|
||||||
while (!XCheckIfEvent(_glfw.x11.display,
|
while (!XCheckIfEvent(_glfw.x11.display,
|
||||||
&dummy,
|
&dummy,
|
||||||
isSelPropNewValueNotify,
|
isSelPropNewValueNotify,
|
||||||
(XPointer) ¬ification))
|
(XPointer) ¬ification))
|
||||||
{
|
{
|
||||||
waitForEvent(NULL);
|
double time = glfwGetTime();
|
||||||
|
if (time - start > 2)
|
||||||
|
return "";
|
||||||
|
waitForX11Event(2.0 - (time - start));
|
||||||
}
|
}
|
||||||
|
|
||||||
XFree(data);
|
XFree(data);
|
||||||
@@ -1259,8 +1170,10 @@ static void processEvent(XEvent *event)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window = findWindowByHandle(event->xany.window);
|
if (XFindContext(_glfw.x11.display,
|
||||||
if (window == NULL)
|
event->xany.window,
|
||||||
|
_glfw.x11.context,
|
||||||
|
(XPointer*) &window) != 0)
|
||||||
{
|
{
|
||||||
// This is an event for a window that has already been destroyed
|
// This is an event for a window that has already been destroyed
|
||||||
return;
|
return;
|
||||||
@@ -1325,13 +1238,13 @@ static void processEvent(XEvent *event)
|
|||||||
|
|
||||||
// Modern X provides scroll events as mouse button presses
|
// Modern X provides scroll events as mouse button presses
|
||||||
else if (event->xbutton.button == Button4)
|
else if (event->xbutton.button == Button4)
|
||||||
_glfwInputScroll(window, 0.0, 1.0);
|
_glfwInputScroll(window, 0.0, 1.0, 0);
|
||||||
else if (event->xbutton.button == Button5)
|
else if (event->xbutton.button == Button5)
|
||||||
_glfwInputScroll(window, 0.0, -1.0);
|
_glfwInputScroll(window, 0.0, -1.0, 0);
|
||||||
else if (event->xbutton.button == Button6)
|
else if (event->xbutton.button == Button6)
|
||||||
_glfwInputScroll(window, 1.0, 0.0);
|
_glfwInputScroll(window, 1.0, 0.0, 0);
|
||||||
else if (event->xbutton.button == Button7)
|
else if (event->xbutton.button == Button7)
|
||||||
_glfwInputScroll(window, -1.0, 0.0);
|
_glfwInputScroll(window, -1.0, 0.0, 0);
|
||||||
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1386,12 +1299,20 @@ static void processEvent(XEvent *event)
|
|||||||
|
|
||||||
case EnterNotify:
|
case EnterNotify:
|
||||||
{
|
{
|
||||||
|
// XEnterWindowEvent is XCrossingEvent
|
||||||
|
const int x = event->xcrossing.x;
|
||||||
|
const int y = event->xcrossing.y;
|
||||||
|
|
||||||
// HACK: This is a workaround for WMs (KWM, Fluxbox) that otherwise
|
// HACK: This is a workaround for WMs (KWM, Fluxbox) that otherwise
|
||||||
// ignore the defined cursor for hidden cursor mode
|
// ignore the defined cursor for hidden cursor mode
|
||||||
if (window->cursorMode == GLFW_CURSOR_HIDDEN)
|
if (window->cursorMode == GLFW_CURSOR_HIDDEN)
|
||||||
updateCursorImage(window);
|
updateCursorImage(window);
|
||||||
|
|
||||||
_glfwInputCursorEnter(window, GLFW_TRUE);
|
_glfwInputCursorEnter(window, GLFW_TRUE);
|
||||||
|
_glfwInputCursorPos(window, x, y);
|
||||||
|
|
||||||
|
window->x11.lastCursorPosX = x;
|
||||||
|
window->x11.lastCursorPosY = y;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1852,7 +1773,7 @@ void _glfwPushSelectionToManagerX11(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForEvent(NULL);
|
waitForX11Event(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2142,7 +2063,6 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
|||||||
_glfw.x11.NET_REQUEST_FRAME_EXTENTS)
|
_glfw.x11.NET_REQUEST_FRAME_EXTENTS)
|
||||||
{
|
{
|
||||||
XEvent event;
|
XEvent event;
|
||||||
double timeout = 0.5;
|
|
||||||
|
|
||||||
// Ensure _NET_FRAME_EXTENTS is set, allowing glfwGetWindowFrameSize to
|
// Ensure _NET_FRAME_EXTENTS is set, allowing glfwGetWindowFrameSize to
|
||||||
// function before the window is mapped
|
// function before the window is mapped
|
||||||
@@ -2159,7 +2079,7 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
|||||||
isFrameExtentsEvent,
|
isFrameExtentsEvent,
|
||||||
(XPointer) window))
|
(XPointer) window))
|
||||||
{
|
{
|
||||||
if (!waitForEvent(&timeout))
|
if (!waitForX11Event(0.5))
|
||||||
{
|
{
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||||
"X11: The window manager has a broken _NET_REQUEST_FRAME_EXTENTS implementation; please report this issue");
|
"X11: The window manager has a broken _NET_REQUEST_FRAME_EXTENTS implementation; please report this issue");
|
||||||
@@ -2196,6 +2116,11 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
|
|||||||
*yscale = _glfw.x11.contentScaleY;
|
*yscale = _glfw.x11.contentScaleY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window)
|
||||||
|
{
|
||||||
|
return 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
|
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
|
||||||
{
|
{
|
||||||
if (window->x11.overrideRedirect)
|
if (window->x11.overrideRedirect)
|
||||||
@@ -2319,10 +2244,14 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (!window->resizable)
|
||||||
|
updateNormalHints(window, width, height);
|
||||||
|
|
||||||
XMoveResizeWindow(_glfw.x11.display, window->x11.handle,
|
XMoveResizeWindow(_glfw.x11.display, window->x11.handle,
|
||||||
xpos, ypos, width, height);
|
xpos, ypos, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
XFlush(_glfw.x11.display);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2374,6 +2303,10 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
|
|||||||
Atom* states;
|
Atom* states;
|
||||||
unsigned long i;
|
unsigned long i;
|
||||||
GLFWbool maximized = GLFW_FALSE;
|
GLFWbool maximized = GLFW_FALSE;
|
||||||
|
if (!_glfw.x11.NET_WM_STATE ||
|
||||||
|
!_glfw.x11.NET_WM_STATE_MAXIMIZED_VERT ||
|
||||||
|
!_glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ)
|
||||||
|
return maximized;
|
||||||
const unsigned long count =
|
const unsigned long count =
|
||||||
_glfwGetWindowPropertyX11(window->x11.handle,
|
_glfwGetWindowPropertyX11(window->x11.handle,
|
||||||
_glfw.x11.NET_WM_STATE,
|
_glfw.x11.NET_WM_STATE,
|
||||||
@@ -2560,9 +2493,10 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity)
|
|||||||
PropModeReplace, (unsigned char*) &value, 1);
|
PropModeReplace, (unsigned char*) &value, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformPollEvents(void)
|
GLFWbool
|
||||||
{
|
_glfwDispatchX11Events(void) {
|
||||||
_GLFWwindow* window;
|
_GLFWwindow* window;
|
||||||
|
GLFWbool dispatched = GLFW_FALSE;
|
||||||
|
|
||||||
#if defined(__linux__)
|
#if defined(__linux__)
|
||||||
_glfwDetectJoystickConnectionLinux();
|
_glfwDetectJoystickConnectionLinux();
|
||||||
@@ -2574,6 +2508,7 @@ void _glfwPlatformPollEvents(void)
|
|||||||
XEvent event;
|
XEvent event;
|
||||||
XNextEvent(_glfw.x11.display, &event);
|
XNextEvent(_glfw.x11.display, &event);
|
||||||
processEvent(&event);
|
processEvent(&event);
|
||||||
|
dispatched = GLFW_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
window = _glfw.x11.disabledCursorWindow;
|
window = _glfw.x11.disabledCursorWindow;
|
||||||
@@ -2592,25 +2527,25 @@ void _glfwPlatformPollEvents(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
XFlush(_glfw.x11.display);
|
XFlush(_glfw.x11.display);
|
||||||
|
return dispatched;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _glfwPlatformPollEvents(void)
|
||||||
|
{
|
||||||
|
_glfwDispatchX11Events();
|
||||||
|
handleEvents(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformWaitEvents(void)
|
void _glfwPlatformWaitEvents(void)
|
||||||
{
|
{
|
||||||
while (!XPending(_glfw.x11.display))
|
double timeout = _glfwDispatchX11Events() ? 0 : -1;
|
||||||
waitForEvent(NULL);
|
handleEvents(timeout);
|
||||||
|
|
||||||
_glfwPlatformPollEvents();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformWaitEventsTimeout(double timeout)
|
void _glfwPlatformWaitEventsTimeout(double timeout)
|
||||||
{
|
{
|
||||||
while (!XPending(_glfw.x11.display))
|
if (_glfwDispatchX11Events()) timeout = 0;
|
||||||
{
|
handleEvents(timeout);
|
||||||
if (!waitForEvent(&timeout))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
_glfwPlatformPollEvents();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformPostEmptyEvent(void)
|
void _glfwPlatformPostEmptyEvent(void)
|
||||||
@@ -2684,7 +2619,7 @@ int _glfwPlatformGetKeyScancode(int key)
|
|||||||
|
|
||||||
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||||
const GLFWimage* image,
|
const GLFWimage* image,
|
||||||
int xhot, int yhot)
|
int xhot, int yhot, int count)
|
||||||
{
|
{
|
||||||
cursor->x11.handle = _glfwCreateCursorX11(image, xhot, yhot);
|
cursor->x11.handle = _glfwCreateCursorX11(image, xhot, yhot);
|
||||||
if (!cursor->x11.handle)
|
if (!cursor->x11.handle)
|
||||||
@@ -2695,8 +2630,24 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
|||||||
|
|
||||||
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
|
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
|
||||||
{
|
{
|
||||||
cursor->x11.handle = XCreateFontCursor(_glfw.x11.display,
|
int native = 0;
|
||||||
translateCursorShape(shape));
|
|
||||||
|
if (shape == GLFW_ARROW_CURSOR)
|
||||||
|
native = XC_left_ptr;
|
||||||
|
else if (shape == GLFW_IBEAM_CURSOR)
|
||||||
|
native = XC_xterm;
|
||||||
|
else if (shape == GLFW_CROSSHAIR_CURSOR)
|
||||||
|
native = XC_crosshair;
|
||||||
|
else if (shape == GLFW_HAND_CURSOR)
|
||||||
|
native = XC_hand1;
|
||||||
|
else if (shape == GLFW_HRESIZE_CURSOR)
|
||||||
|
native = XC_sb_h_double_arrow;
|
||||||
|
else if (shape == GLFW_VRESIZE_CURSOR)
|
||||||
|
native = XC_sb_v_double_arrow;
|
||||||
|
else
|
||||||
|
return GLFW_FALSE;
|
||||||
|
|
||||||
|
cursor->x11.handle = XCreateFontCursor(_glfw.x11.display, native);
|
||||||
if (!cursor->x11.handle)
|
if (!cursor->x11.handle)
|
||||||
{
|
{
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||||
@@ -2745,6 +2696,29 @@ const char* _glfwPlatformGetClipboardString(void)
|
|||||||
return getSelectionString(_glfw.x11.CLIPBOARD);
|
return getSelectionString(_glfw.x11.CLIPBOARD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _glfwPlatformSetPrimarySelectionString(const char* string)
|
||||||
|
{
|
||||||
|
free(_glfw.x11.primarySelectionString);
|
||||||
|
_glfw.x11.primarySelectionString = _glfw_strdup(string);
|
||||||
|
|
||||||
|
XSetSelectionOwner(_glfw.x11.display,
|
||||||
|
_glfw.x11.PRIMARY,
|
||||||
|
_glfw.x11.helperWindowHandle,
|
||||||
|
CurrentTime);
|
||||||
|
|
||||||
|
if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.PRIMARY) !=
|
||||||
|
_glfw.x11.helperWindowHandle)
|
||||||
|
{
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||||
|
"X11: Failed to become owner of primary selection");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* _glfwPlatformGetPrimarySelectionString(void)
|
||||||
|
{
|
||||||
|
return getSelectionString(_glfw.x11.PRIMARY);
|
||||||
|
}
|
||||||
|
|
||||||
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
|
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
|
||||||
{
|
{
|
||||||
if (!_glfw.vk.KHR_surface)
|
if (!_glfw.vk.KHR_surface)
|
||||||
@@ -2894,6 +2868,10 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
_glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int d) {
|
||||||
|
glfw_xkb_update_ime_state(w, &_glfw.x11.xkb, which, a, b, c, d);
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
////// GLFW native API //////
|
////// GLFW native API //////
|
||||||
@@ -2912,32 +2890,6 @@ GLFWAPI Window glfwGetX11Window(GLFWwindow* handle)
|
|||||||
return window->x11.handle;
|
return window->x11.handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
GLFWAPI void glfwSetX11SelectionString(const char* string)
|
|
||||||
{
|
|
||||||
_GLFW_REQUIRE_INIT();
|
|
||||||
|
|
||||||
free(_glfw.x11.primarySelectionString);
|
|
||||||
_glfw.x11.primarySelectionString = _glfw_strdup(string);
|
|
||||||
|
|
||||||
XSetSelectionOwner(_glfw.x11.display,
|
|
||||||
_glfw.x11.PRIMARY,
|
|
||||||
_glfw.x11.helperWindowHandle,
|
|
||||||
CurrentTime);
|
|
||||||
|
|
||||||
if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.PRIMARY) !=
|
|
||||||
_glfw.x11.helperWindowHandle)
|
|
||||||
{
|
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
|
||||||
"X11: Failed to become owner of primary selection");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GLFWAPI const char* glfwGetX11SelectionString(void)
|
|
||||||
{
|
|
||||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
|
||||||
return getSelectionString(_glfw.x11.PRIMARY);
|
|
||||||
}
|
|
||||||
|
|
||||||
GLFWAPI int glfwGetXKBScancode(const char* keyName, GLFWbool caseSensitive) {
|
GLFWAPI int glfwGetXKBScancode(const char* keyName, GLFWbool caseSensitive) {
|
||||||
return glfw_xkb_keysym_from_name(keyName, caseSensitive);
|
return glfw_xkb_keysym_from_name(keyName, caseSensitive);
|
||||||
}
|
}
|
||||||
|
|||||||
334
glfw/xkb_glfw.c
vendored
334
glfw/xkb_glfw.c
vendored
@@ -32,7 +32,8 @@
|
|||||||
|
|
||||||
#define debug(...) if (_glfw.hints.init.debugKeyboard) printf(__VA_ARGS__);
|
#define debug(...) if (_glfw.hints.init.debugKeyboard) printf(__VA_ARGS__);
|
||||||
|
|
||||||
#define map_key(key) { \
|
|
||||||
|
#define map_key(key) \
|
||||||
switch(key) { \
|
switch(key) { \
|
||||||
S(space, SPACE); \
|
S(space, SPACE); \
|
||||||
S(apostrophe, APOSTROPHE); \
|
S(apostrophe, APOSTROPHE); \
|
||||||
@@ -45,6 +46,7 @@
|
|||||||
S(bracketleft, LEFT_BRACKET); \
|
S(bracketleft, LEFT_BRACKET); \
|
||||||
S(backslash, BACKSLASH); \
|
S(backslash, BACKSLASH); \
|
||||||
S(bracketright, RIGHT_BRACKET); \
|
S(bracketright, RIGHT_BRACKET); \
|
||||||
|
S(plus, PLUS); \
|
||||||
S(grave, GRAVE_ACCENT); \
|
S(grave, GRAVE_ACCENT); \
|
||||||
S(Escape, ESCAPE); \
|
S(Escape, ESCAPE); \
|
||||||
S(Return, ENTER); \
|
S(Return, ENTER); \
|
||||||
@@ -92,10 +94,6 @@
|
|||||||
D(A, Z, A, Z); \
|
D(A, Z, A, Z); \
|
||||||
R(F1, F25, F1, F25); \
|
R(F1, F25, F1, F25); \
|
||||||
R(KP_0, KP_9, KP_0, KP_9); \
|
R(KP_0, KP_9, KP_0, KP_9); \
|
||||||
default: \
|
|
||||||
break; \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
glfw_key_for_sym(xkb_keysym_t key) {
|
glfw_key_for_sym(xkb_keysym_t key) {
|
||||||
@@ -104,6 +102,13 @@ glfw_key_for_sym(xkb_keysym_t key) {
|
|||||||
#define R(s, e, gs, ...) case XKB_KEY_##s ... XKB_KEY_##e: return GLFW_KEY_##gs + key - XKB_KEY_##s
|
#define R(s, e, gs, ...) case XKB_KEY_##s ... XKB_KEY_##e: return GLFW_KEY_##gs + key - XKB_KEY_##s
|
||||||
#define D(s, e, gs, ...) R(s, e, gs)
|
#define D(s, e, gs, ...) R(s, e, gs)
|
||||||
map_key(key)
|
map_key(key)
|
||||||
|
S(KP_Up, UP);
|
||||||
|
S(KP_Down, DOWN);
|
||||||
|
S(KP_Left, LEFT);
|
||||||
|
S(KP_Right, RIGHT);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
return GLFW_KEY_UNKNOWN;
|
return GLFW_KEY_UNKNOWN;
|
||||||
#undef F
|
#undef F
|
||||||
#undef D
|
#undef D
|
||||||
@@ -118,6 +123,9 @@ glfw_xkb_sym_for_key(int key) {
|
|||||||
#define R(s, e, gs, ge) case GLFW_KEY_##gs ... GLFW_KEY_##ge: return XKB_KEY_##s + key - GLFW_KEY_##gs
|
#define R(s, e, gs, ge) case GLFW_KEY_##gs ... GLFW_KEY_##ge: return XKB_KEY_##s + key - GLFW_KEY_##gs
|
||||||
#define D(...)
|
#define D(...)
|
||||||
map_key(key)
|
map_key(key)
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
return GLFW_KEY_UNKNOWN;
|
return GLFW_KEY_UNKNOWN;
|
||||||
#undef F
|
#undef F
|
||||||
#undef D
|
#undef D
|
||||||
@@ -170,36 +178,29 @@ glfw_xkb_update_x11_keyboard_id(_GLFWXKBData *xkb) {
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
static void
|
||||||
|
release_keyboard_data(_GLFWXKBData *xkb) {
|
||||||
|
#define US(group, state, unref) if (xkb->group.state) { unref(xkb->group.state); xkb->group.state = NULL; }
|
||||||
|
#define UK(keymap) if(xkb->keymap) { xkb_keymap_unref(xkb->keymap); xkb->keymap = NULL; }
|
||||||
|
US(states, composeState, xkb_compose_state_unref);
|
||||||
|
UK(keymap);
|
||||||
|
UK(default_keymap);
|
||||||
|
US(states, state, xkb_state_unref);
|
||||||
|
US(states, clean_state, xkb_state_unref);
|
||||||
|
US(states, default_state, xkb_state_unref);
|
||||||
|
#undef US
|
||||||
|
#undef UK
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
glfw_xkb_release(_GLFWXKBData *xkb) {
|
glfw_xkb_release(_GLFWXKBData *xkb) {
|
||||||
if (xkb->composeState) {
|
release_keyboard_data(xkb);
|
||||||
xkb_compose_state_unref(xkb->composeState);
|
|
||||||
xkb->composeState = NULL;
|
|
||||||
}
|
|
||||||
if (xkb->keymap) {
|
|
||||||
xkb_keymap_unref(xkb->keymap);
|
|
||||||
xkb->keymap = NULL;
|
|
||||||
}
|
|
||||||
if (xkb->default_keymap) {
|
|
||||||
xkb_keymap_unref(xkb->default_keymap);
|
|
||||||
xkb->default_keymap = NULL;
|
|
||||||
}
|
|
||||||
if (xkb->state) {
|
|
||||||
xkb_state_unref(xkb->state);
|
|
||||||
xkb->state = NULL;
|
|
||||||
}
|
|
||||||
if (xkb->clean_state) {
|
|
||||||
xkb_state_unref(xkb->clean_state);
|
|
||||||
xkb->clean_state = NULL;
|
|
||||||
}
|
|
||||||
if (xkb->default_state) {
|
|
||||||
xkb_state_unref(xkb->default_state);
|
|
||||||
xkb->default_state = NULL;
|
|
||||||
}
|
|
||||||
if (xkb->context) {
|
if (xkb->context) {
|
||||||
xkb_context_unref(xkb->context);
|
xkb_context_unref(xkb->context);
|
||||||
xkb->context = NULL;
|
xkb->context = NULL;
|
||||||
}
|
}
|
||||||
|
glfw_ibus_terminate(&xkb->ibus);
|
||||||
}
|
}
|
||||||
|
|
||||||
GLFWbool
|
GLFWbool
|
||||||
@@ -211,115 +212,117 @@ glfw_xkb_create_context(_GLFWXKBData *xkb) {
|
|||||||
"Failed to initialize XKB context");
|
"Failed to initialize XKB context");
|
||||||
return GLFW_FALSE;
|
return GLFW_FALSE;
|
||||||
}
|
}
|
||||||
|
glfw_connect_to_ibus(&xkb->ibus);
|
||||||
return GLFW_TRUE;
|
return GLFW_TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char*
|
||||||
|
load_keymaps(_GLFWXKBData *xkb, const char *map_str) {
|
||||||
|
(void)(map_str); // not needed on X11
|
||||||
|
xkb_glfw_load_keymap(xkb->keymap, map_str);
|
||||||
|
if (!xkb->keymap) return "Failed to compile XKB keymap";
|
||||||
|
// The system default keymap, can be overridden by the XKB_DEFAULT_RULES
|
||||||
|
// env var, see
|
||||||
|
// https://xkbcommon.org/doc/current/structxkb__rule__names.html
|
||||||
|
static struct xkb_rule_names default_rule_names = {0};
|
||||||
|
xkb->default_keymap = xkb_keymap_new_from_names(xkb->context, &default_rule_names, XKB_KEYMAP_COMPILE_NO_FLAGS);
|
||||||
|
if (!xkb->default_keymap) return "Failed to create default XKB keymap";
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char*
|
||||||
|
load_states(_GLFWXKBData *xkb) {
|
||||||
|
xkb_glfw_load_state(xkb->keymap, xkb->states.state);
|
||||||
|
xkb->states.clean_state = xkb_state_new(xkb->keymap);
|
||||||
|
xkb->states.default_state = xkb_state_new(xkb->default_keymap);
|
||||||
|
if (!xkb->states.state || !xkb->states.clean_state || !xkb->states.default_state) return "Failed to create XKB state";
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
load_compose_tables(_GLFWXKBData *xkb) {
|
||||||
|
/* Look up the preferred locale, falling back to "C" as default. */
|
||||||
|
struct xkb_compose_table* compose_table = NULL;
|
||||||
|
const char *locale = getenv("LC_ALL");
|
||||||
|
if (!locale) locale = getenv("LC_CTYPE");
|
||||||
|
if (!locale) locale = getenv("LANG");
|
||||||
|
if (!locale) locale = "C";
|
||||||
|
compose_table = xkb_compose_table_new_from_locale(xkb->context, locale, XKB_COMPOSE_COMPILE_NO_FLAGS);
|
||||||
|
if (!compose_table) {
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB compose table for locale %s", locale);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
xkb->states.composeState = xkb_compose_state_new(compose_table, XKB_COMPOSE_STATE_NO_FLAGS);
|
||||||
|
if (!xkb->states.composeState) {
|
||||||
|
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB compose state");
|
||||||
|
}
|
||||||
|
xkb_compose_table_unref(compose_table);
|
||||||
|
}
|
||||||
|
|
||||||
GLFWbool
|
GLFWbool
|
||||||
glfw_xkb_compile_keymap(_GLFWXKBData *xkb, const char *map_str) {
|
glfw_xkb_compile_keymap(_GLFWXKBData *xkb, const char *map_str) {
|
||||||
const char* locale = NULL;
|
const char *err;
|
||||||
struct xkb_state* state = NULL, *clean_state = NULL;
|
release_keyboard_data(xkb);
|
||||||
struct xkb_keymap* keymap = NULL;
|
err = load_keymaps(xkb, map_str);
|
||||||
struct xkb_compose_table* compose_table = NULL;
|
if (err) {
|
||||||
struct xkb_compose_state* compose_state = NULL;
|
_glfwInputError(GLFW_PLATFORM_ERROR, "%s", err);
|
||||||
(void)(map_str); // not needed on X11
|
release_keyboard_data(xkb);
|
||||||
GLFWbool ok = GLFW_FALSE;
|
return GLFW_FALSE;
|
||||||
|
|
||||||
xkb_glfw_load_keymap(keymap, map_str);
|
|
||||||
if (!keymap) _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to compile XKB keymap");
|
|
||||||
else {
|
|
||||||
xkb_glfw_load_state(keymap, state);
|
|
||||||
clean_state = xkb_state_new(keymap);
|
|
||||||
if (!state || !clean_state) {
|
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB state");
|
|
||||||
xkb_keymap_unref(keymap); keymap = NULL;
|
|
||||||
} else {
|
|
||||||
ok = GLFW_TRUE;
|
|
||||||
/* Look up the preferred locale, falling back to "C" as default. */
|
|
||||||
locale = getenv("LC_ALL");
|
|
||||||
if (!locale) locale = getenv("LC_CTYPE");
|
|
||||||
if (!locale) locale = getenv("LANG");
|
|
||||||
if (!locale) locale = "C";
|
|
||||||
compose_table = xkb_compose_table_new_from_locale(xkb->context, locale, XKB_COMPOSE_COMPILE_NO_FLAGS);
|
|
||||||
if (!compose_table) {
|
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB compose table");
|
|
||||||
} else {
|
|
||||||
compose_state = xkb_compose_state_new(compose_table, XKB_COMPOSE_STATE_NO_FLAGS);
|
|
||||||
xkb_compose_table_unref(compose_table); compose_table = NULL;
|
|
||||||
if (!compose_state) {
|
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB compose state");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (keymap && state && clean_state) {
|
err = load_states(xkb);
|
||||||
if (xkb->composeState) xkb_compose_state_unref(xkb->composeState);
|
if (err) {
|
||||||
xkb->composeState = compose_state;
|
_glfwInputError(GLFW_PLATFORM_ERROR, "%s", err);
|
||||||
if (xkb->keymap) xkb_keymap_unref(xkb->keymap);
|
release_keyboard_data(xkb);
|
||||||
xkb->keymap = keymap;
|
return GLFW_FALSE;
|
||||||
if (xkb->state) xkb_state_unref(xkb->state);
|
|
||||||
xkb->state = state;
|
|
||||||
if (xkb->clean_state) xkb_state_unref(xkb->clean_state);
|
|
||||||
xkb->clean_state = clean_state;
|
|
||||||
}
|
}
|
||||||
if (xkb->keymap) {
|
load_compose_tables(xkb);
|
||||||
#define S(a, n) xkb->a##Idx = xkb_keymap_mod_get_index(xkb->keymap, n); xkb->a##Mask = 1 << xkb->a##Idx;
|
#define S(a, n) xkb->a##Idx = xkb_keymap_mod_get_index(xkb->keymap, n); xkb->a##Mask = 1 << xkb->a##Idx;
|
||||||
S(control, XKB_MOD_NAME_CTRL);
|
S(control, XKB_MOD_NAME_CTRL);
|
||||||
S(alt, XKB_MOD_NAME_ALT);
|
S(alt, XKB_MOD_NAME_ALT);
|
||||||
S(shift, XKB_MOD_NAME_SHIFT);
|
S(shift, XKB_MOD_NAME_SHIFT);
|
||||||
S(super, XKB_MOD_NAME_LOGO);
|
S(super, XKB_MOD_NAME_LOGO);
|
||||||
S(capsLock, XKB_MOD_NAME_CAPS);
|
S(capsLock, XKB_MOD_NAME_CAPS);
|
||||||
S(numLock, XKB_MOD_NAME_NUM);
|
S(numLock, XKB_MOD_NAME_NUM);
|
||||||
#undef S
|
#undef S
|
||||||
size_t capacity = sizeof(xkb->unknownModifiers)/sizeof(xkb->unknownModifiers[0]), j = 0;
|
size_t capacity = sizeof(xkb->unknownModifiers)/sizeof(xkb->unknownModifiers[0]), j = 0;
|
||||||
for (xkb_mod_index_t i = 0; i < capacity; i++) xkb->unknownModifiers[i] = XKB_MOD_INVALID;
|
for (xkb_mod_index_t i = 0; i < capacity; i++) xkb->unknownModifiers[i] = XKB_MOD_INVALID;
|
||||||
for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(xkb->keymap) && j < capacity - 1; i++) {
|
for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(xkb->keymap) && j < capacity - 1; i++) {
|
||||||
if (i != xkb->controlIdx && i != xkb->altIdx && i != xkb->shiftIdx && i != xkb->superIdx && i != xkb->capsLockIdx && i != xkb->numLockIdx) xkb->unknownModifiers[j++] = i;
|
if (i != xkb->controlIdx && i != xkb->altIdx && i != xkb->shiftIdx && i != xkb->superIdx && i != xkb->capsLockIdx && i != xkb->numLockIdx) xkb->unknownModifiers[j++] = i;
|
||||||
}
|
|
||||||
xkb->modifiers = 0;
|
|
||||||
xkb->activeUnknownModifiers = 0;
|
|
||||||
}
|
}
|
||||||
if (!xkb->default_keymap && xkb->context) {
|
xkb->states.modifiers = 0;
|
||||||
// The system default keymap, can be overridden by the XKB_DEFAULT_RULES
|
xkb->states.activeUnknownModifiers = 0;
|
||||||
// env var, see
|
return GLFW_TRUE;
|
||||||
// https://xkbcommon.org/doc/current/structxkb__rule__names.html
|
|
||||||
static struct xkb_rule_names default_rule_names = {0};
|
|
||||||
xkb->default_keymap = xkb_keymap_new_from_names(xkb->context, &default_rule_names, XKB_KEYMAP_COMPILE_NO_FLAGS);
|
|
||||||
if (xkb->default_keymap) {
|
|
||||||
xkb->default_state = xkb_state_new(keymap);
|
|
||||||
if (!xkb->default_state) {
|
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create default XKB state, fallback key processing is unavailable");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create default XKB keymap, fallback key processing is unavailable");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline xkb_mod_mask_t
|
static inline xkb_mod_mask_t
|
||||||
active_unknown_modifiers(_GLFWXKBData *xkb) {
|
active_unknown_modifiers(_GLFWXKBData *xkb, struct xkb_state *state) {
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
xkb_mod_mask_t ans = 0;
|
xkb_mod_mask_t ans = 0;
|
||||||
while (xkb->unknownModifiers[i] != XKB_MOD_INVALID) {
|
while (xkb->unknownModifiers[i] != XKB_MOD_INVALID) {
|
||||||
if (xkb_state_mod_index_is_active(xkb->state, xkb->unknownModifiers[i], XKB_STATE_MODS_EFFECTIVE)) ans |= (1 << xkb->unknownModifiers[i]);
|
if (xkb_state_mod_index_is_active(state, xkb->unknownModifiers[i], XKB_STATE_MODS_EFFECTIVE)) ans |= (1 << xkb->unknownModifiers[i]);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
return ans;
|
return ans;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
update_modifiers(_GLFWXKBData *xkb, XKBStateGroup *group) {
|
||||||
|
#define S(attr, name) if (xkb_state_mod_index_is_active(group->state, xkb->attr##Idx, XKB_STATE_MODS_EFFECTIVE)) group->modifiers |= GLFW_MOD_##name
|
||||||
|
S(control, CONTROL); S(alt, ALT); S(shift, SHIFT); S(super, SUPER); S(capsLock, CAPS_LOCK); S(numLock, NUM_LOCK);
|
||||||
|
#undef S
|
||||||
|
xkb->states.activeUnknownModifiers = active_unknown_modifiers(xkb, xkb->states.state);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
glfw_xkb_update_modifiers(_GLFWXKBData *xkb, xkb_mod_mask_t depressed, xkb_mod_mask_t latched, xkb_mod_mask_t locked, xkb_layout_index_t base_group, xkb_layout_index_t latched_group, xkb_layout_index_t locked_group) {
|
glfw_xkb_update_modifiers(_GLFWXKBData *xkb, xkb_mod_mask_t depressed, xkb_mod_mask_t latched, xkb_mod_mask_t locked, xkb_layout_index_t base_group, xkb_layout_index_t latched_group, xkb_layout_index_t locked_group) {
|
||||||
if (!xkb->keymap) return;
|
if (!xkb->keymap) return;
|
||||||
xkb->modifiers = 0;
|
xkb->states.modifiers = 0;
|
||||||
xkb_state_update_mask(xkb->state, depressed, latched, locked, base_group, latched_group, locked_group);
|
xkb_state_update_mask(xkb->states.state, depressed, latched, locked, base_group, latched_group, locked_group);
|
||||||
// We have to update the groups in clean_state, as they change for
|
// We have to update the groups in clean_state, as they change for
|
||||||
// different keyboard layouts, see https://github.com/kovidgoyal/kitty/issues/488
|
// different keyboard layouts, see https://github.com/kovidgoyal/kitty/issues/488
|
||||||
xkb_state_update_mask(xkb->clean_state, 0, 0, 0, base_group, latched_group, locked_group);
|
xkb_state_update_mask(xkb->states.clean_state, 0, 0, 0, base_group, latched_group, locked_group);
|
||||||
#define S(attr, name) if (xkb_state_mod_index_is_active(xkb->state, xkb->attr##Idx, XKB_STATE_MODS_EFFECTIVE)) xkb->modifiers |= GLFW_MOD_##name
|
update_modifiers(xkb, &xkb->states);
|
||||||
S(control, CONTROL); S(alt, ALT); S(shift, SHIFT); S(super, SUPER); S(capsLock, CAPS_LOCK); S(numLock, NUM_LOCK);
|
|
||||||
#undef S
|
|
||||||
xkb->activeUnknownModifiers = active_unknown_modifiers(xkb);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GLFWbool
|
GLFWbool
|
||||||
@@ -331,16 +334,18 @@ glfw_xkb_should_repeat(_GLFWXKBData *xkb, xkb_keycode_t scancode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static char text[256];
|
static KeyEvent key_event = {};
|
||||||
|
|
||||||
static inline xkb_keysym_t
|
static inline xkb_keysym_t
|
||||||
compose_symbol(_GLFWXKBData *xkb, xkb_keysym_t sym) {
|
compose_symbol(struct xkb_compose_state *composeState, xkb_keysym_t sym, int *compose_completed) {
|
||||||
if (sym == XKB_KEY_NoSymbol || !xkb->composeState) return sym;
|
*compose_completed = 0;
|
||||||
if (xkb_compose_state_feed(xkb->composeState, sym) != XKB_COMPOSE_FEED_ACCEPTED) return sym;
|
if (sym == XKB_KEY_NoSymbol || !composeState) return sym;
|
||||||
switch (xkb_compose_state_get_status(xkb->composeState)) {
|
if (xkb_compose_state_feed(composeState, sym) != XKB_COMPOSE_FEED_ACCEPTED) return sym;
|
||||||
|
switch (xkb_compose_state_get_status(composeState)) {
|
||||||
case XKB_COMPOSE_COMPOSED:
|
case XKB_COMPOSE_COMPOSED:
|
||||||
xkb_compose_state_get_utf8(xkb->composeState, text, sizeof(text));
|
xkb_compose_state_get_utf8(composeState, key_event.text, sizeof(key_event.text));
|
||||||
return xkb_compose_state_get_one_sym(xkb->composeState);
|
*compose_completed = 1;
|
||||||
|
return xkb_compose_state_get_one_sym(composeState);
|
||||||
case XKB_COMPOSE_COMPOSING:
|
case XKB_COMPOSE_COMPOSING:
|
||||||
case XKB_COMPOSE_CANCELLED:
|
case XKB_COMPOSE_CANCELLED:
|
||||||
return XKB_KEY_NoSymbol;
|
return XKB_KEY_NoSymbol;
|
||||||
@@ -402,30 +407,77 @@ format_xkb_mods(_GLFWXKBData *xkb, const char* name, xkb_mod_mask_t mods) {
|
|||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
glfw_xkb_update_ime_state(_GLFWwindow *w, _GLFWXKBData *xkb, int which, int a, int b, int c, int d) {
|
||||||
|
int x = 0, y = 0;
|
||||||
|
switch(which) {
|
||||||
|
case 1:
|
||||||
|
glfw_ibus_set_focused(&xkb->ibus, a ? GLFW_TRUE : GLFW_FALSE);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
_glfwPlatformGetWindowPos(w, &x, &y);
|
||||||
|
x += a; y += b;
|
||||||
|
glfw_ibus_set_cursor_geometry(&xkb->ibus, x, y, c, d);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
glfw_xkb_key_from_ime(KeyEvent *ev, GLFWbool handled_by_ime, GLFWbool failed) {
|
||||||
|
_GLFWwindow *window = _glfwWindowForId(ev->window_id);
|
||||||
|
if (failed && window && window->callbacks.keyboard) {
|
||||||
|
// notify application to remove any existing pre-edit text
|
||||||
|
window->callbacks.keyboard((GLFWwindow*) window, GLFW_KEY_UNKNOWN, 0, GLFW_PRESS, 0, "", 1);
|
||||||
|
}
|
||||||
|
static xkb_keycode_t last_handled_press_keycode = 0;
|
||||||
|
// We filter out release events that correspond to the last press event
|
||||||
|
// handled by the IME system. This wont fix the case of multiple key
|
||||||
|
// presses before a release, but is better than nothing. For that case
|
||||||
|
// you'd need to implement a ring buffer to store pending key presses.
|
||||||
|
xkb_keycode_t prev_handled_press = last_handled_press_keycode;
|
||||||
|
last_handled_press_keycode = 0;
|
||||||
|
GLFWbool is_release = ev->action == GLFW_RELEASE;
|
||||||
|
debug("From IBUS: scancode: 0x%x name: %s is_release: %d\n", ev->keycode, glfw_xkb_keysym_name(ev->keysym), is_release);
|
||||||
|
if (window && !handled_by_ime && !(is_release && ev->keycode == prev_handled_press)) {
|
||||||
|
debug("↳ to application: glfw_keycode: 0x%x (%s) keysym: 0x%x (%s) action: %s %s text: %s\n",
|
||||||
|
ev->glfw_keycode, _glfwGetKeyName(ev->glfw_keycode), ev->keysym, glfw_xkb_keysym_name(ev->keysym),
|
||||||
|
(ev->action == GLFW_RELEASE ? "RELEASE" : (ev->action == GLFW_PRESS ? "PRESS" : "REPEAT")),
|
||||||
|
format_mods(ev->glfw_modifiers), ev->text
|
||||||
|
);
|
||||||
|
_glfwInputKeyboard(window, ev->glfw_keycode, ev->keysym, ev->action, ev->glfw_modifiers, ev->text, 0);
|
||||||
|
} else debug("↳ discarded\n");
|
||||||
|
if (!is_release && handled_by_ime) last_handled_press_keycode = ev->keycode;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t scancode, int action) {
|
glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t scancode, int action) {
|
||||||
const xkb_keysym_t *syms, *clean_syms, *default_syms;
|
const xkb_keysym_t *syms, *clean_syms, *default_syms;
|
||||||
xkb_keysym_t glfw_sym;
|
xkb_keysym_t glfw_sym;
|
||||||
xkb_keycode_t code_for_sym = scancode;
|
xkb_keycode_t code_for_sym = scancode;
|
||||||
|
key_event.ibus_keycode = scancode;
|
||||||
#ifdef _GLFW_WAYLAND
|
#ifdef _GLFW_WAYLAND
|
||||||
code_for_sym += 8;
|
code_for_sym += 8;
|
||||||
|
#else
|
||||||
|
key_event.ibus_keycode -= 8;
|
||||||
#endif
|
#endif
|
||||||
debug("scancode: 0x%x release: %d ", scancode, action == GLFW_RELEASE);
|
debug("%s scancode: 0x%x ", action == GLFW_RELEASE ? "Release" : "Press", scancode);
|
||||||
int num_syms = xkb_state_key_get_syms(xkb->state, code_for_sym, &syms);
|
XKBStateGroup *sg = &xkb->states;
|
||||||
int num_clean_syms = xkb_state_key_get_syms(xkb->clean_state, code_for_sym, &clean_syms);
|
int num_syms = xkb_state_key_get_syms(sg->state, code_for_sym, &syms);
|
||||||
text[0] = 0;
|
int num_clean_syms = xkb_state_key_get_syms(sg->clean_state, code_for_sym, &clean_syms);
|
||||||
|
key_event.text[0] = 0;
|
||||||
// According to the documentation of xkb_compose_state_feed it does not
|
// According to the documentation of xkb_compose_state_feed it does not
|
||||||
// support multi-sym events, so we ignore them
|
// support multi-sym events, so we ignore them
|
||||||
if (num_syms != 1 || num_clean_syms != 1) {
|
if (num_syms != 1 || num_clean_syms != 1) {
|
||||||
debug("scancode: 0x%x num_syms: %d num_clean_syms: %d ignoring event\n", scancode, num_syms, num_clean_syms);
|
debug("num_syms: %d num_clean_syms: %d ignoring event\n", num_syms, num_clean_syms);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
glfw_sym = clean_syms[0];
|
glfw_sym = clean_syms[0];
|
||||||
debug("clean_sym: %s ", glfw_xkb_keysym_name(clean_syms[0]));
|
debug("clean_sym: %s ", glfw_xkb_keysym_name(clean_syms[0]));
|
||||||
if (action == GLFW_PRESS || action == GLFW_REPEAT) {
|
if (action == GLFW_PRESS || action == GLFW_REPEAT) {
|
||||||
const char *text_type = "composed_text";
|
const char *text_type = "composed_text";
|
||||||
glfw_sym = compose_symbol(xkb, syms[0]);
|
int compose_completed;
|
||||||
if (glfw_sym == XKB_KEY_NoSymbol) {
|
glfw_sym = compose_symbol(sg->composeState, syms[0], &compose_completed);
|
||||||
|
if (glfw_sym == XKB_KEY_NoSymbol && !compose_completed) {
|
||||||
debug("compose not complete, ignoring.\n");
|
debug("compose not complete, ignoring.\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -435,21 +487,21 @@ glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t
|
|||||||
// are active (for example if ISO_Shift_Level_* mods are active
|
// are active (for example if ISO_Shift_Level_* mods are active
|
||||||
// they are not reported by GLFW so the key should be the shifted
|
// they are not reported by GLFW so the key should be the shifted
|
||||||
// key). See https://github.com/kovidgoyal/kitty/issues/171#issuecomment-377557053
|
// key). See https://github.com/kovidgoyal/kitty/issues/171#issuecomment-377557053
|
||||||
xkb_mod_mask_t consumed_unknown_mods = xkb_state_key_get_consumed_mods(xkb->state, code_for_sym) & xkb->activeUnknownModifiers;
|
xkb_mod_mask_t consumed_unknown_mods = xkb_state_key_get_consumed_mods(sg->state, code_for_sym) & sg->activeUnknownModifiers;
|
||||||
if (xkb->activeUnknownModifiers) debug("%s", format_xkb_mods(xkb, "active_unknown_mods", xkb->activeUnknownModifiers));
|
if (sg->activeUnknownModifiers) debug("%s", format_xkb_mods(xkb, "active_unknown_mods", sg->activeUnknownModifiers));
|
||||||
if (consumed_unknown_mods) { debug("%s", format_xkb_mods(xkb, "consumed_unknown_mods", consumed_unknown_mods)); }
|
if (consumed_unknown_mods) { debug("%s", format_xkb_mods(xkb, "consumed_unknown_mods", consumed_unknown_mods)); }
|
||||||
else glfw_sym = clean_syms[0];
|
else glfw_sym = clean_syms[0];
|
||||||
// xkb returns text even if alt and/or super are pressed
|
// xkb returns text even if alt and/or super are pressed
|
||||||
if ( ((GLFW_MOD_CONTROL | GLFW_MOD_ALT | GLFW_MOD_SUPER) & xkb->modifiers) == 0) xkb_state_key_get_utf8(xkb->state, code_for_sym, text, sizeof(text));
|
if ( ((GLFW_MOD_CONTROL | GLFW_MOD_ALT | GLFW_MOD_SUPER) & sg->modifiers) == 0) xkb_state_key_get_utf8(sg->state, code_for_sym, key_event.text, sizeof(key_event.text));
|
||||||
text_type = "text";
|
text_type = "text";
|
||||||
}
|
}
|
||||||
if ((1 <= text[0] && text[0] <= 31) || text[0] == 127) text[0] = 0; // dont send text for ascii control codes
|
if ((1 <= key_event.text[0] && key_event.text[0] <= 31) || key_event.text[0] == 127) key_event.text[0] = 0; // don't send text for ascii control codes
|
||||||
if (text[0]) { debug("%s: %s ", text_type, text); }
|
if (key_event.text[0]) { debug("%s: %s ", text_type, key_event.text); }
|
||||||
}
|
}
|
||||||
int glfw_keycode = glfw_key_for_sym(glfw_sym);
|
int glfw_keycode = glfw_key_for_sym(glfw_sym);
|
||||||
GLFWbool is_fallback = GLFW_FALSE;
|
GLFWbool is_fallback = GLFW_FALSE;
|
||||||
if (glfw_keycode == GLFW_KEY_UNKNOWN && !text[0]) {
|
if (glfw_keycode == GLFW_KEY_UNKNOWN && !key_event.text[0]) {
|
||||||
int num_default_syms = xkb_state_key_get_syms(xkb->default_state, code_for_sym, &default_syms);
|
int num_default_syms = xkb_state_key_get_syms(sg->default_state, code_for_sym, &default_syms);
|
||||||
if (num_default_syms > 0) {
|
if (num_default_syms > 0) {
|
||||||
glfw_sym = default_syms[0];
|
glfw_sym = default_syms[0];
|
||||||
glfw_keycode = glfw_key_for_sym(glfw_sym);
|
glfw_keycode = glfw_key_for_sym(glfw_sym);
|
||||||
@@ -457,10 +509,18 @@ glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
debug(
|
debug(
|
||||||
"%s%s: %s xkb_key_name: %s\n",
|
"%s%s: %d (%s) xkb_key: %d (%s)\n",
|
||||||
format_mods(xkb->modifiers),
|
format_mods(sg->modifiers),
|
||||||
is_fallback ? "glfw_fallback_key" : "glfw_key", _glfwGetKeyName(glfw_keycode),
|
is_fallback ? "glfw_fallback_key" : "glfw_key", glfw_keycode, _glfwGetKeyName(glfw_keycode),
|
||||||
glfw_xkb_keysym_name(glfw_sym)
|
glfw_sym, glfw_xkb_keysym_name(glfw_sym)
|
||||||
);
|
);
|
||||||
_glfwInputKeyboard(window, glfw_keycode, glfw_sym, action, xkb->modifiers, text, 0);
|
key_event.action = action; key_event.glfw_modifiers = sg->modifiers;
|
||||||
|
key_event.keycode = scancode; key_event.keysym = glfw_sym;
|
||||||
|
key_event.window_id = window->id; key_event.glfw_keycode = glfw_keycode;
|
||||||
|
key_event.ibus_sym = syms[0];
|
||||||
|
if (ibus_process_key(&key_event, &xkb->ibus)) {
|
||||||
|
debug("↳ to IBUS: keycode: 0x%x keysym: 0x%x (%s) %s\n", key_event.ibus_keycode, key_event.ibus_sym, glfw_xkb_keysym_name(key_event.ibus_sym), format_mods(key_event.glfw_modifiers));
|
||||||
|
} else {
|
||||||
|
_glfwInputKeyboard(window, glfw_keycode, glfw_sym, action, sg->modifiers, key_event.text, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
glfw/xkb_glfw.h
vendored
20
glfw/xkb_glfw.h
vendored
@@ -32,14 +32,23 @@
|
|||||||
#include <xkbcommon/xkbcommon-x11.h>
|
#include <xkbcommon/xkbcommon-x11.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "ibus_glfw.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
struct xkb_context* context;
|
|
||||||
struct xkb_keymap* keymap;
|
|
||||||
struct xkb_keymap* default_keymap;
|
|
||||||
struct xkb_state* state;
|
struct xkb_state* state;
|
||||||
struct xkb_state* clean_state;
|
struct xkb_state* clean_state;
|
||||||
struct xkb_state* default_state;
|
struct xkb_state* default_state;
|
||||||
struct xkb_compose_state* composeState;
|
struct xkb_compose_state* composeState;
|
||||||
|
xkb_mod_mask_t activeUnknownModifiers;
|
||||||
|
unsigned int modifiers;
|
||||||
|
} XKBStateGroup;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
struct xkb_context* context;
|
||||||
|
struct xkb_keymap* keymap;
|
||||||
|
struct xkb_keymap* default_keymap;
|
||||||
|
XKBStateGroup states;
|
||||||
|
|
||||||
xkb_mod_index_t controlIdx;
|
xkb_mod_index_t controlIdx;
|
||||||
xkb_mod_index_t altIdx;
|
xkb_mod_index_t altIdx;
|
||||||
@@ -53,9 +62,8 @@ typedef struct {
|
|||||||
xkb_mod_mask_t superMask;
|
xkb_mod_mask_t superMask;
|
||||||
xkb_mod_mask_t capsLockMask;
|
xkb_mod_mask_t capsLockMask;
|
||||||
xkb_mod_mask_t numLockMask;
|
xkb_mod_mask_t numLockMask;
|
||||||
xkb_mod_mask_t activeUnknownModifiers;
|
|
||||||
unsigned int modifiers;
|
|
||||||
xkb_mod_index_t unknownModifiers[256];
|
xkb_mod_index_t unknownModifiers[256];
|
||||||
|
_GLFWIBUSData ibus;
|
||||||
|
|
||||||
#ifdef _GLFW_X11
|
#ifdef _GLFW_X11
|
||||||
int32_t keyboard_device_id;
|
int32_t keyboard_device_id;
|
||||||
@@ -84,3 +92,5 @@ const char* glfw_xkb_keysym_name(xkb_keysym_t sym);
|
|||||||
xkb_keysym_t glfw_xkb_sym_for_key(int key);
|
xkb_keysym_t glfw_xkb_sym_for_key(int key);
|
||||||
void glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t scancode, int action);
|
void glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t scancode, int action);
|
||||||
int glfw_xkb_keysym_from_name(const char *name, GLFWbool case_sensitive);
|
int glfw_xkb_keysym_from_name(const char *name, GLFWbool case_sensitive);
|
||||||
|
void glfw_xkb_update_ime_state(_GLFWwindow *w, _GLFWXKBData *xkb, int which, int a, int b, int c, int d);
|
||||||
|
void glfw_xkb_key_from_ime(KeyEvent *ev, GLFWbool handled_by_ime, GLFWbool failed);
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
# vim:fileencoding=utf-8
|
# vim:fileencoding=utf-8
|
||||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from kitty.cli import parse_args
|
from kitty.cli import parse_args
|
||||||
|
|
||||||
from ..tui.handler import Handler
|
from ..tui.handler import Handler
|
||||||
@@ -13,7 +15,6 @@ class Clipboard(Handler):
|
|||||||
|
|
||||||
def __init__(self, data_to_send, args):
|
def __init__(self, data_to_send, args):
|
||||||
self.args = args
|
self.args = args
|
||||||
self.print_on_fail = None
|
|
||||||
self.clipboard_contents = None
|
self.clipboard_contents = None
|
||||||
self.data_to_send = data_to_send
|
self.data_to_send = data_to_send
|
||||||
|
|
||||||
@@ -63,16 +64,13 @@ def main(args):
|
|||||||
data = None
|
data = None
|
||||||
if not sys.stdin.isatty():
|
if not sys.stdin.isatty():
|
||||||
data = sys.stdin.buffer.read()
|
data = sys.stdin.buffer.read()
|
||||||
sys.stdin = open('/dev/tty', 'r')
|
sys.stdin = open(os.ctermid(), 'r')
|
||||||
loop = Loop()
|
loop = Loop()
|
||||||
handler = Clipboard(data, args)
|
handler = Clipboard(data, args)
|
||||||
loop.loop(handler)
|
loop.loop(handler)
|
||||||
if loop.return_code == 0 and handler.clipboard_contents:
|
if loop.return_code == 0 and handler.clipboard_contents:
|
||||||
sys.stdout.write(handler.clipboard_contents)
|
sys.stdout.write(handler.clipboard_contents)
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
if handler.print_on_fail:
|
|
||||||
print(handler.print_on_fail, file=sys.stderr)
|
|
||||||
input('Press Enter to quit')
|
|
||||||
raise SystemExit(loop.return_code)
|
raise SystemExit(loop.return_code)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ def highlight_collection(collection, aliases=None):
|
|||||||
jobs = {}
|
jobs = {}
|
||||||
ans = {}
|
ans = {}
|
||||||
with concurrent.futures.ProcessPoolExecutor(max_workers=os.cpu_count()) as executor:
|
with concurrent.futures.ProcessPoolExecutor(max_workers=os.cpu_count()) as executor:
|
||||||
|
highlight_collection.processes = executor._processes
|
||||||
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):
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import signal
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
@@ -24,14 +25,14 @@ from .collect import (
|
|||||||
set_highlight_data
|
set_highlight_data
|
||||||
)
|
)
|
||||||
from .config import init_config
|
from .config import init_config
|
||||||
from .patch import Differ, set_diff_command
|
from .patch import Differ, set_diff_command, worker_processes
|
||||||
from .render import ImageSupportWarning, LineRef, render_diff
|
from .render import ImageSupportWarning, LineRef, render_diff
|
||||||
from .search import BadRegex, Search
|
from .search import BadRegex, Search
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .highlight import initialize_highlighter, highlight_collection
|
from .highlight import initialize_highlighter, highlight_collection
|
||||||
except ImportError:
|
except ImportError:
|
||||||
initialize_highlighter = None
|
initialize_highlighter = highlight_collection = None
|
||||||
|
|
||||||
|
|
||||||
INITIALIZING, COLLECTED, DIFFED, COMMAND, MESSAGE = range(5)
|
INITIALIZING, COLLECTED, DIFFED, COMMAND, MESSAGE = range(5)
|
||||||
@@ -42,7 +43,7 @@ def generate_diff(collection, context):
|
|||||||
|
|
||||||
for path, item_type, changed_path in collection:
|
for path, item_type, changed_path in collection:
|
||||||
if item_type == 'diff':
|
if item_type == 'diff':
|
||||||
is_binary = isinstance(data_for_path(path), bytes)
|
is_binary = isinstance(data_for_path(path), bytes) or isinstance(data_for_path(changed_path), bytes)
|
||||||
if not is_binary:
|
if not is_binary:
|
||||||
d.add_diff(path, changed_path)
|
d.add_diff(path, changed_path)
|
||||||
|
|
||||||
@@ -106,10 +107,67 @@ class DiffHandler(Handler):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def create_collection(self):
|
def create_collection(self):
|
||||||
self.start_job('collect', create_collection, self.left, self.right)
|
|
||||||
|
def collect_done(collection):
|
||||||
|
self.collection = collection
|
||||||
|
self.state = COLLECTED
|
||||||
|
self.generate_diff()
|
||||||
|
|
||||||
|
def collect(left, right):
|
||||||
|
collection = create_collection(left, right)
|
||||||
|
self.asyncio_loop.call_soon_threadsafe(collect_done, collection)
|
||||||
|
|
||||||
|
self.asyncio_loop.run_in_executor(None, collect, self.left, self.right)
|
||||||
|
|
||||||
def generate_diff(self):
|
def generate_diff(self):
|
||||||
self.start_job('diff', generate_diff, self.collection, self.current_context_count)
|
|
||||||
|
def diff_done(diff_map):
|
||||||
|
if isinstance(diff_map, str):
|
||||||
|
self.report_traceback_on_exit = diff_map
|
||||||
|
self.quit_loop(1)
|
||||||
|
return
|
||||||
|
self.state = DIFFED
|
||||||
|
self.diff_map = diff_map
|
||||||
|
self.calculate_statistics()
|
||||||
|
self.render_diff()
|
||||||
|
self.scroll_pos = 0
|
||||||
|
if self.restore_position is not None:
|
||||||
|
self.current_position = self.restore_position
|
||||||
|
self.restore_position = None
|
||||||
|
self.draw_screen()
|
||||||
|
if initialize_highlighter is not None and not self.highlighting_done:
|
||||||
|
from .highlight import StyleNotFound
|
||||||
|
self.highlighting_done = True
|
||||||
|
try:
|
||||||
|
initialize_highlighter(self.opts.pygments_style)
|
||||||
|
except StyleNotFound as e:
|
||||||
|
self.report_traceback_on_exit = str(e)
|
||||||
|
self.quit_loop(1)
|
||||||
|
return
|
||||||
|
self.syntax_highlight()
|
||||||
|
|
||||||
|
def diff(collection, current_context_count):
|
||||||
|
diff_map = generate_diff(collection, current_context_count)
|
||||||
|
self.asyncio_loop.call_soon_threadsafe(diff_done, diff_map)
|
||||||
|
|
||||||
|
self.asyncio_loop.run_in_executor(None, diff, self.collection, self.current_context_count)
|
||||||
|
|
||||||
|
def syntax_highlight(self):
|
||||||
|
|
||||||
|
def highlighting_done(hdata):
|
||||||
|
if isinstance(hdata, str):
|
||||||
|
self.report_traceback_on_exit = hdata
|
||||||
|
self.quit_loop(1)
|
||||||
|
return
|
||||||
|
set_highlight_data(hdata)
|
||||||
|
self.render_diff()
|
||||||
|
self.draw_screen()
|
||||||
|
|
||||||
|
def highlight(*a):
|
||||||
|
result = highlight_collection(*a)
|
||||||
|
self.asyncio_loop.call_soon_threadsafe(highlighting_done, result)
|
||||||
|
|
||||||
|
self.asyncio_loop.run_in_executor(None, highlight, self.collection, self.opts.syntax_aliases)
|
||||||
|
|
||||||
def calculate_statistics(self):
|
def calculate_statistics(self):
|
||||||
self.added_count = self.collection.added_count
|
self.added_count = self.collection.added_count
|
||||||
@@ -150,7 +208,7 @@ class DiffHandler(Handler):
|
|||||||
break
|
break
|
||||||
|
|
||||||
if num is not None:
|
if num is not None:
|
||||||
self.scroll_pos = min(num, self.max_scroll_pos)
|
self.scroll_pos = max(0, min(num, self.max_scroll_pos))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def num_lines(self):
|
def num_lines(self):
|
||||||
@@ -413,50 +471,6 @@ class DiffHandler(Handler):
|
|||||||
self.render_diff()
|
self.render_diff()
|
||||||
self.draw_screen()
|
self.draw_screen()
|
||||||
|
|
||||||
def on_job_done(self, job_id, job_result):
|
|
||||||
if 'tb' in job_result:
|
|
||||||
self.report_traceback_on_exit = job_result['tb']
|
|
||||||
self.quit_loop(1)
|
|
||||||
return
|
|
||||||
if job_id == 'collect':
|
|
||||||
self.collection = job_result['result']
|
|
||||||
self.state = COLLECTED
|
|
||||||
self.generate_diff()
|
|
||||||
elif job_id == 'diff':
|
|
||||||
diff_map = job_result['result']
|
|
||||||
if isinstance(diff_map, str):
|
|
||||||
self.report_traceback_on_exit = diff_map
|
|
||||||
self.quit_loop(1)
|
|
||||||
return
|
|
||||||
self.state = DIFFED
|
|
||||||
self.diff_map = diff_map
|
|
||||||
self.calculate_statistics()
|
|
||||||
self.render_diff()
|
|
||||||
self.scroll_pos = 0
|
|
||||||
if self.restore_position is not None:
|
|
||||||
self.current_position = self.restore_position
|
|
||||||
self.restore_position = None
|
|
||||||
self.draw_screen()
|
|
||||||
if initialize_highlighter is not None and not self.highlighting_done:
|
|
||||||
from .highlight import StyleNotFound
|
|
||||||
self.highlighting_done = True
|
|
||||||
try:
|
|
||||||
initialize_highlighter(self.opts.pygments_style)
|
|
||||||
except StyleNotFound as e:
|
|
||||||
self.report_traceback_on_exit = str(e)
|
|
||||||
self.quit_loop(1)
|
|
||||||
return
|
|
||||||
self.start_job('highlight', highlight_collection, self.collection, self.opts.syntax_aliases)
|
|
||||||
elif job_id == 'highlight':
|
|
||||||
hdata = job_result['result']
|
|
||||||
if isinstance(hdata, str):
|
|
||||||
self.report_traceback_on_exit = diff_map
|
|
||||||
self.quit_loop(1)
|
|
||||||
return
|
|
||||||
set_highlight_data(hdata)
|
|
||||||
self.render_diff()
|
|
||||||
self.draw_screen()
|
|
||||||
|
|
||||||
def on_interrupt(self):
|
def on_interrupt(self):
|
||||||
self.quit_loop(1)
|
self.quit_loop(1)
|
||||||
|
|
||||||
@@ -495,6 +509,14 @@ help_text = 'Show a side-by-side diff of the specified files/directories'
|
|||||||
usage = 'file_or_directory_left file_or_directory_right'
|
usage = 'file_or_directory_left file_or_directory_right'
|
||||||
|
|
||||||
|
|
||||||
|
def terminate_processes(processes):
|
||||||
|
for pid in processes:
|
||||||
|
try:
|
||||||
|
os.kill(pid, signal.SIGKILL)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def main(args):
|
def main(args):
|
||||||
warnings.showwarning = showwarning
|
warnings.showwarning = showwarning
|
||||||
args, items = parse_args(args[1:], OPTIONS, usage, help_text, 'kitty +kitten diff')
|
args, items = parse_args(args[1:], OPTIONS, usage, help_text, 'kitty +kitten diff')
|
||||||
@@ -517,6 +539,9 @@ def main(args):
|
|||||||
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', ())
|
||||||
|
terminate_processes(tuple(highlight_processes))
|
||||||
|
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)
|
||||||
|
|||||||
@@ -14,10 +14,11 @@ from .diff_speedup import changed_center
|
|||||||
left_lines = right_lines = None
|
left_lines = right_lines = None
|
||||||
GIT_DIFF = 'git diff --no-color --no-ext-diff --exit-code -U_CONTEXT_ --no-index --'
|
GIT_DIFF = 'git diff --no-color --no-ext-diff --exit-code -U_CONTEXT_ --no-index --'
|
||||||
DIFF_DIFF = 'diff -p -U _CONTEXT_ --'
|
DIFF_DIFF = 'diff -p -U _CONTEXT_ --'
|
||||||
|
worker_processes = []
|
||||||
|
|
||||||
|
|
||||||
def find_differ():
|
def find_differ():
|
||||||
if shutil.which('git'):
|
if shutil.which('git') and subprocess.Popen(['git', '--help'], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL).wait() == 0:
|
||||||
return GIT_DIFF
|
return GIT_DIFF
|
||||||
if shutil.which('diff'):
|
if shutil.which('diff'):
|
||||||
return DIFF_DIFF
|
return DIFF_DIFF
|
||||||
@@ -43,8 +44,10 @@ def run_diff(file1, file2, context=3):
|
|||||||
p = subprocess.Popen(
|
p = subprocess.Popen(
|
||||||
cmd + [path1, path2],
|
cmd + [path1, path2],
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL)
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL)
|
||||||
|
worker_processes.append(p.pid)
|
||||||
stdout, stderr = p.communicate()
|
stdout, stderr = p.communicate()
|
||||||
returncode = p.wait()
|
returncode = p.wait()
|
||||||
|
worker_processes.remove(p.pid)
|
||||||
if returncode in (0, 1):
|
if returncode in (0, 1):
|
||||||
return True, returncode == 1, stdout.decode('utf-8')
|
return True, returncode == 1, stdout.decode('utf-8')
|
||||||
return False, returncode, stderr.decode('utf-8')
|
return False, returncode, stderr.decode('utf-8')
|
||||||
@@ -205,9 +208,13 @@ def parse_patch(raw):
|
|||||||
|
|
||||||
class Differ:
|
class Differ:
|
||||||
|
|
||||||
|
diff_executor = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.jmap = {}
|
self.jmap = {}
|
||||||
self.jobs = []
|
self.jobs = []
|
||||||
|
if Differ.diff_executor is None:
|
||||||
|
Differ.diff_executor = self.diff_executor = concurrent.futures.ThreadPoolExecutor(max_workers=os.cpu_count())
|
||||||
|
|
||||||
def add_diff(self, file1, file2):
|
def add_diff(self, file1, file2):
|
||||||
self.jmap[file1] = file2
|
self.jmap[file1] = file2
|
||||||
@@ -216,26 +223,26 @@ class Differ:
|
|||||||
def __call__(self, context=3):
|
def __call__(self, context=3):
|
||||||
global left_lines, right_lines
|
global left_lines, right_lines
|
||||||
ans = {}
|
ans = {}
|
||||||
with concurrent.futures.ThreadPoolExecutor(max_workers=os.cpu_count()) as executor:
|
executor = Differ.diff_executor
|
||||||
jobs = {executor.submit(run_diff, key, self.jmap[key], context): key for key in self.jobs}
|
jobs = {executor.submit(run_diff, key, self.jmap[key], context): key for key in self.jobs}
|
||||||
for future in concurrent.futures.as_completed(jobs):
|
for future in concurrent.futures.as_completed(jobs):
|
||||||
key = jobs[future]
|
key = jobs[future]
|
||||||
left_path, right_path = key, self.jmap[key]
|
left_path, right_path = key, self.jmap[key]
|
||||||
try:
|
try:
|
||||||
ok, returncode, output = future.result()
|
ok, returncode, output = future.result()
|
||||||
except FileNotFoundError as err:
|
except FileNotFoundError as err:
|
||||||
return 'Could not find the {} executable. Is it in your PATH?'.format(err.filename)
|
return 'Could not find the {} executable. Is it in your PATH?'.format(err.filename)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return 'Running git diff for {} vs. {} generated an exception: {}'.format(left_path, right_path, e)
|
return 'Running git diff for {} vs. {} generated an exception: {}'.format(left_path, right_path, e)
|
||||||
if not ok:
|
if not ok:
|
||||||
return output + '\nRunning git diff for {} vs. {} failed'.format(left_path, right_path)
|
return output + '\nRunning git diff for {} vs. {} failed'.format(left_path, right_path)
|
||||||
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() + '\nParsing diff for {} vs. {} failed'.format(left_path, right_path)
|
return traceback.format_exc() + '\nParsing diff for {} vs. {} failed'.format(left_path, right_path)
|
||||||
else:
|
else:
|
||||||
ans[key] = patch
|
ans[key] = patch
|
||||||
return ans
|
return ans
|
||||||
|
|||||||
@@ -453,7 +453,9 @@ def render_diff(collection, diff_map, args, columns, image_manager):
|
|||||||
for i, (path, item_type, other_path) in enumerate(collection):
|
for i, (path, item_type, other_path) in enumerate(collection):
|
||||||
item_ref = Reference(path)
|
item_ref = Reference(path)
|
||||||
is_binary = isinstance(data_for_path(path), bytes)
|
is_binary = isinstance(data_for_path(path), bytes)
|
||||||
is_img = is_binary and is_image(path) and images_supported()
|
if not is_binary and item_type == 'diff' and isinstance(data_for_path(other_path), bytes):
|
||||||
|
is_binary = True
|
||||||
|
is_img = is_binary and (is_image(path) or is_image(other_path)) and images_supported()
|
||||||
yield from yield_lines_from(title_lines(path, other_path, args, columns, margin_size), item_ref, False)
|
yield from yield_lines_from(title_lines(path, other_path, args, columns, margin_size), item_ref, False)
|
||||||
if item_type == 'diff':
|
if item_type == 'diff':
|
||||||
if is_binary:
|
if is_binary:
|
||||||
|
|||||||
@@ -84,9 +84,17 @@ class Hints(Handler):
|
|||||||
self.args = args
|
self.args = args
|
||||||
self.window_title = _('Choose URL') if args.type == 'url' else _('Choose text')
|
self.window_title = _('Choose URL') if args.type == 'url' else _('Choose text')
|
||||||
self.multiple = args.multiple
|
self.multiple = args.multiple
|
||||||
|
self.match_suffix = self.get_match_suffix(args)
|
||||||
self.chosen = []
|
self.chosen = []
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
|
def get_match_suffix(self, args):
|
||||||
|
if args.add_trailing_space == 'always':
|
||||||
|
return ' '
|
||||||
|
if args.add_trailing_space == 'never':
|
||||||
|
return ''
|
||||||
|
return ' ' if args.multiple else ''
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.current_input = ''
|
self.current_input = ''
|
||||||
self.current_text = None
|
self.current_text = None
|
||||||
@@ -112,7 +120,7 @@ class Hints(Handler):
|
|||||||
if encode_hint(idx).startswith(self.current_input)
|
if encode_hint(idx).startswith(self.current_input)
|
||||||
]
|
]
|
||||||
if len(matches) == 1:
|
if len(matches) == 1:
|
||||||
self.chosen.append(matches[0].text)
|
self.chosen.append(matches[0].text + self.match_suffix)
|
||||||
if self.multiple:
|
if self.multiple:
|
||||||
self.ignore_mark_indices.add(matches[0].index)
|
self.ignore_mark_indices.add(matches[0].index)
|
||||||
self.reset()
|
self.reset()
|
||||||
@@ -130,7 +138,7 @@ class Hints(Handler):
|
|||||||
elif key_event is enter_key and self.current_input:
|
elif key_event is enter_key and self.current_input:
|
||||||
try:
|
try:
|
||||||
idx = decode_hint(self.current_input)
|
idx = decode_hint(self.current_input)
|
||||||
self.chosen.append(self.index_map[idx].text)
|
self.chosen.append(self.index_map[idx].text + self.match_suffix)
|
||||||
self.ignore_mark_indices.add(idx)
|
self.ignore_mark_indices.add(idx)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.current_input = ''
|
self.current_input = ''
|
||||||
@@ -152,7 +160,6 @@ class Hints(Handler):
|
|||||||
self.quit_loop(1)
|
self.quit_loop(1)
|
||||||
|
|
||||||
def on_resize(self, new_size):
|
def on_resize(self, new_size):
|
||||||
Handler.on_resize(self, new_size)
|
|
||||||
self.draw_screen()
|
self.draw_screen()
|
||||||
|
|
||||||
def draw_screen(self):
|
def draw_screen(self):
|
||||||
@@ -171,7 +178,7 @@ def regex_finditer(pat, minimum_match_length, text):
|
|||||||
yield s, e
|
yield s, e
|
||||||
|
|
||||||
|
|
||||||
closing_bracket_map = {'(': ')', '[': ']', '{': '}', '<': '>'}
|
closing_bracket_map = {'(': ')', '[': ']', '{': '}', '<': '>', '*': '*', '"': '"', "'": "'"}
|
||||||
opening_brackets = ''.join(closing_bracket_map)
|
opening_brackets = ''.join(closing_bracket_map)
|
||||||
postprocessor_map = {}
|
postprocessor_map = {}
|
||||||
|
|
||||||
@@ -190,15 +197,15 @@ def url(text, s, e):
|
|||||||
e -= len(url) - idx
|
e -= len(url) - idx
|
||||||
while text[e - 1] in '.,?!' and e > 1: # remove trailing punctuation
|
while text[e - 1] in '.,?!' and e > 1: # remove trailing punctuation
|
||||||
e -= 1
|
e -= 1
|
||||||
|
# truncate url at closing bracket/quote
|
||||||
|
if s > 0 and e <= len(text) and text[s-1] in opening_brackets:
|
||||||
|
q = closing_bracket_map[text[s-1]]
|
||||||
|
idx = text.find(q, s)
|
||||||
|
if idx > s:
|
||||||
|
e = idx
|
||||||
# Restructured Text URLs
|
# Restructured Text URLs
|
||||||
if e > 3 and text[e-2:e] == '`_':
|
if e > 3 and text[e-2:e] == '`_':
|
||||||
e -= 2
|
e -= 2
|
||||||
# Remove trailing bracket if matched by leading bracket
|
|
||||||
if s > 0 and e < len(text) and text[s-1] in opening_brackets and text[e-1] == closing_bracket_map[text[s-1]]:
|
|
||||||
e -= 1
|
|
||||||
# Remove trailing quote if matched by leading quote
|
|
||||||
if s > 0 and e < len(text) and text[s-1] in '\'"' and text[e-1] == text[s-1]:
|
|
||||||
e -= 1
|
|
||||||
|
|
||||||
return s, e
|
return s, e
|
||||||
|
|
||||||
@@ -256,7 +263,7 @@ def functions_for(args):
|
|||||||
)
|
)
|
||||||
post_processors.append(url)
|
post_processors.append(url)
|
||||||
elif args.type == 'path':
|
elif args.type == 'path':
|
||||||
pattern = r'(?:\S*/\S+)|(?:\S+[.][a-zA-Z0-9]{2,5})'
|
pattern = r'(?:\S*/\S+)|(?:\S+[.][a-zA-Z0-9]{2,7})'
|
||||||
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]*$'
|
||||||
@@ -308,8 +315,9 @@ def run(args, text):
|
|||||||
return
|
return
|
||||||
|
|
||||||
largest_index = all_marks[-1].index
|
largest_index = all_marks[-1].index
|
||||||
|
offset = max(0, args.hints_offset)
|
||||||
for m in all_marks:
|
for m in all_marks:
|
||||||
m.index = largest_index - m.index
|
m.index = largest_index - m.index + offset
|
||||||
index_map = {m.index: m for m in all_marks}
|
index_map = {m.index: m for m in all_marks}
|
||||||
except Exception:
|
except Exception:
|
||||||
import traceback
|
import traceback
|
||||||
@@ -364,6 +372,20 @@ The minimum number of characters to consider a match.
|
|||||||
type=bool-set
|
type=bool-set
|
||||||
Select multiple matches and perform the action on all of them together at the end.
|
Select multiple matches and perform the action on all of them together at the end.
|
||||||
In this mode, press :kbd:`Esc` to finish selecting.
|
In this mode, press :kbd:`Esc` to finish selecting.
|
||||||
|
|
||||||
|
|
||||||
|
--add-trailing-space
|
||||||
|
default=auto
|
||||||
|
choices=auto,always,never
|
||||||
|
Add trailing space after matched text. Defaults to auto, which adds the space
|
||||||
|
when used together with --multiple.
|
||||||
|
|
||||||
|
|
||||||
|
--hints-offset
|
||||||
|
default=1
|
||||||
|
type=int
|
||||||
|
The offset (from zero) at which to start hint numbering. Note that only numbers
|
||||||
|
greater than zero are respected.
|
||||||
'''.format(','.join(sorted(URL_PREFIXES))).format
|
'''.format(','.join(sorted(URL_PREFIXES))).format
|
||||||
help_text = 'Select text from the screen using the keyboard. Defaults to searching for URLs.'
|
help_text = 'Select text from the screen using the keyboard. Defaults to searching for URLs.'
|
||||||
usage = ''
|
usage = ''
|
||||||
@@ -382,7 +404,7 @@ def main(args):
|
|||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
text = sys.stdin.buffer.read().decode('utf-8')
|
text = sys.stdin.buffer.read().decode('utf-8')
|
||||||
sys.stdin = open('/dev/tty')
|
sys.stdin = open(os.ctermid())
|
||||||
try:
|
try:
|
||||||
args, items = parse_hints_args(args[1:])
|
args, items = parse_hints_args(args[1:])
|
||||||
except SystemExit as e:
|
except SystemExit as e:
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ Horizontal alignment for the displayed image.
|
|||||||
--place
|
--place
|
||||||
Choose where on the screen to display the image. The image will
|
Choose where on the screen to display the image. The image will
|
||||||
be scaled to fit into the specified rectangle. The syntax for
|
be scaled to fit into the specified rectangle. The syntax for
|
||||||
specifying rectanges is <:italic:`width`>x<:italic:`height`>@<:italic:`left`>x<:italic:`top`>. All measurements
|
specifying rectangles is <:italic:`width`>x<:italic:`height`>@<:italic:`left`>x<:italic:`top`>.
|
||||||
are in cells (i.e. cursor positions) with the origin :italic:`(0, 0)` at
|
All measurements are in cells (i.e. cursor positions) with the
|
||||||
the top-left corner of the screen.
|
origin :italic:`(0, 0)` at the top-left corner of the screen.
|
||||||
|
|
||||||
|
|
||||||
--scale-up
|
--scale-up
|
||||||
@@ -81,6 +81,14 @@ 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 kitty icat from a
|
||||||
scripting language that cannot make termios calls.
|
scripting language that cannot make termios calls.
|
||||||
|
|
||||||
|
|
||||||
|
--stdin
|
||||||
|
type=choices
|
||||||
|
choices=detect,yes,no
|
||||||
|
default=detect
|
||||||
|
Read image data from stdin. The default is to do it automatically, when STDIN is not a terminal,
|
||||||
|
but you can turn it off or on explicitly, if needed.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
@@ -174,7 +182,7 @@ def show(outfile, width, height, fmt, transmit_mode='t', align='center', place=N
|
|||||||
write_chunked(cmd, data)
|
write_chunked(cmd, data)
|
||||||
|
|
||||||
|
|
||||||
def process(path, args):
|
def process(path, args, is_tempfile):
|
||||||
m = identify(path)
|
m = identify(path)
|
||||||
ss = screen_size()
|
ss = screen_size()
|
||||||
available_width = args.place.width * (ss.width / ss.cols) if args.place else ss.width
|
available_width = args.place.width * (ss.width / ss.cols) if args.place else ss.width
|
||||||
@@ -183,7 +191,7 @@ def process(path, args):
|
|||||||
needs_scaling = needs_scaling or args.scale_up
|
needs_scaling = needs_scaling or args.scale_up
|
||||||
if m.fmt == 'png' and not needs_scaling:
|
if m.fmt == 'png' and not needs_scaling:
|
||||||
outfile = path
|
outfile = path
|
||||||
transmit_mode = 'f'
|
transmit_mode = 't' if is_tempfile else 'f'
|
||||||
fmt = 100
|
fmt = 100
|
||||||
width, height = m.width, m.height
|
width, height = m.width, m.height
|
||||||
else:
|
else:
|
||||||
@@ -249,7 +257,8 @@ def parse_place(raw):
|
|||||||
help_text = (
|
help_text = (
|
||||||
'A cat like utility to display images in the terminal.'
|
'A cat like utility to display images in the terminal.'
|
||||||
' You can specify multiple image files and/or directories.'
|
' You can specify multiple image files and/or directories.'
|
||||||
' Directories are scanned recursively for image files.'
|
' Directories are scanned recursively for image files. If STDIN'
|
||||||
|
' is not a terminal, image data will be read from it as well.'
|
||||||
)
|
)
|
||||||
usage = 'image-file ...'
|
usage = 'image-file ...'
|
||||||
|
|
||||||
@@ -260,13 +269,21 @@ def main(args=sys.argv):
|
|||||||
|
|
||||||
if args.print_window_size:
|
if args.print_window_size:
|
||||||
screen_size_function.ans = None
|
screen_size_function.ans = None
|
||||||
with open('/dev/tty') as tty:
|
with open(os.ctermid()) as tty:
|
||||||
ss = screen_size_function(tty)()
|
ss = screen_size_function(tty)()
|
||||||
print('{}x{}'.format(ss.width, ss.height), end='')
|
print('{}x{}'.format(ss.width, ss.height), end='')
|
||||||
raise SystemExit(0)
|
raise SystemExit(0)
|
||||||
|
|
||||||
if not sys.stdout.isatty():
|
if not sys.stdout.isatty():
|
||||||
sys.stdout = open('/dev/tty', 'w')
|
sys.stdout = open(os.ctermid(), 'w')
|
||||||
|
stdin_data = None
|
||||||
|
if args.stdin == 'yes' or (not sys.stdin.isatty() and args.stdin == 'detect'):
|
||||||
|
stdin_data = sys.stdin.buffer.read()
|
||||||
|
if stdin_data:
|
||||||
|
items.insert(0, stdin_data)
|
||||||
|
sys.stdin.close()
|
||||||
|
sys.stdin = open(os.ctermid(), 'r')
|
||||||
|
|
||||||
screen_size = screen_size_function()
|
screen_size = screen_size_function()
|
||||||
signal.signal(signal.SIGWINCH, lambda signum, frame: setattr(screen_size, 'changed', True))
|
signal.signal(signal.SIGWINCH, lambda signum, frame: setattr(screen_size, 'changed', True))
|
||||||
if screen_size().width == 0:
|
if screen_size().width == 0:
|
||||||
@@ -298,16 +315,22 @@ def main(args=sys.argv):
|
|||||||
if not items:
|
if not items:
|
||||||
raise SystemExit('You must specify at least one file to cat')
|
raise SystemExit('You must specify at least one file to cat')
|
||||||
if args.place:
|
if args.place:
|
||||||
if len(items) > 1 or os.path.isdir(items[0]):
|
if len(items) > 1 or (isinstance(items[0], str) and os.path.isdir(items[0])):
|
||||||
raise SystemExit('The --place option can only be used with a single image')
|
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
|
||||||
for item in items:
|
for item in items:
|
||||||
|
is_tempfile = False
|
||||||
try:
|
try:
|
||||||
|
if isinstance(item, bytes):
|
||||||
|
tf = NamedTemporaryFile(prefix='stdin-image-data-', delete=False)
|
||||||
|
tf.write(item), tf.close()
|
||||||
|
item = tf.name
|
||||||
|
is_tempfile = True
|
||||||
if os.path.isdir(item):
|
if os.path.isdir(item):
|
||||||
for x in scan(item):
|
for x in scan(item):
|
||||||
process(item, args)
|
process(item, args)
|
||||||
else:
|
else:
|
||||||
process(item, args)
|
process(item, args, is_tempfile)
|
||||||
except NoImageMagick as e:
|
except NoImageMagick as e:
|
||||||
raise SystemExit(str(e))
|
raise SystemExit(str(e))
|
||||||
except ConvertFailed as e:
|
except ConvertFailed as e:
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
# vim:fileencoding=utf-8
|
# vim:fileencoding=utf-8
|
||||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from kitty.constants import is_macos, is_wayland
|
|
||||||
from kitty.cli import parse_args
|
from kitty.cli import parse_args
|
||||||
|
from kitty.constants import is_macos, is_wayland
|
||||||
|
|
||||||
OPTIONS = r'''
|
OPTIONS = r'''
|
||||||
--lines
|
--lines
|
||||||
@@ -123,7 +123,8 @@ def main(sys_args):
|
|||||||
global args
|
global args
|
||||||
if is_macos or is_wayland:
|
if is_macos or is_wayland:
|
||||||
raise SystemExit('Currently the panel kitten is supported only on X11 desktops')
|
raise SystemExit('Currently the panel kitten is supported only on X11 desktops')
|
||||||
call_xprop('-version', silent=True) # ensure xprop is available
|
if not shutil.which('xprop'):
|
||||||
|
raise SystemExit('The xprop program is required for the panel kitten')
|
||||||
args, items = parse_panel_args(sys_args[1:])
|
args, items = parse_panel_args(sys_args[1:])
|
||||||
if not items:
|
if not items:
|
||||||
raise SystemExit('You must specify the program to run')
|
raise SystemExit('You must specify the program to run')
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class Resize(Handler):
|
|||||||
cmdline = [resize_window.name, '--self', '--increment={}'.format(increment), '--axis=' + axis]
|
cmdline = [resize_window.name, '--self', '--increment={}'.format(increment), '--axis=' + axis]
|
||||||
opts, items = parse_subcommand_cli(resize_window, cmdline)
|
opts, items = parse_subcommand_cli(resize_window, cmdline)
|
||||||
payload = resize_window(global_opts, opts, items)
|
payload = resize_window(global_opts, opts, items)
|
||||||
send = {'cmd': resize_window.name, 'version': version, 'payload': payload}
|
send = {'cmd': resize_window.name, 'version': version, 'payload': payload, 'no_response': False}
|
||||||
self.write(encode_send(send))
|
self.write(encode_send(send))
|
||||||
|
|
||||||
def on_kitty_cmd_response(self, response):
|
def on_kitty_cmd_response(self, response):
|
||||||
@@ -74,7 +74,6 @@ class Resize(Handler):
|
|||||||
self.do_window_resize(is_decrease=key_event.key in (N, S), is_horizontal=key_event.key in (W, N), multiplier=2)
|
self.do_window_resize(is_decrease=key_event.key in (N, S), is_horizontal=key_event.key in (W, N), multiplier=2)
|
||||||
|
|
||||||
def on_resize(self, new_size):
|
def on_resize(self, new_size):
|
||||||
Handler.on_resize(self, new_size)
|
|
||||||
self.draw_screen()
|
self.draw_screen()
|
||||||
|
|
||||||
def draw_screen(self):
|
def draw_screen(self):
|
||||||
|
|||||||
@@ -17,23 +17,27 @@ def resolved_kitten(k):
|
|||||||
|
|
||||||
def import_kitten_main_module(config_dir, kitten):
|
def import_kitten_main_module(config_dir, kitten):
|
||||||
if kitten.endswith('.py'):
|
if kitten.endswith('.py'):
|
||||||
|
path_modified = False
|
||||||
path = os.path.expanduser(kitten)
|
path = os.path.expanduser(kitten)
|
||||||
if not os.path.isabs(path):
|
if not os.path.isabs(path):
|
||||||
path = os.path.join(config_dir, path)
|
path = os.path.join(config_dir, path)
|
||||||
path = os.path.abspath(path)
|
path = os.path.abspath(path)
|
||||||
if os.path.dirname(path):
|
if os.path.dirname(path):
|
||||||
sys.path.insert(0, os.path.dirname(path))
|
sys.path.insert(0, os.path.dirname(path))
|
||||||
|
path_modified = True
|
||||||
with open(path) as f:
|
with open(path) as f:
|
||||||
src = f.read()
|
src = f.read()
|
||||||
code = compile(src, path, 'exec')
|
code = compile(src, path, 'exec')
|
||||||
g = {'__name__': 'kitten'}
|
g = {'__name__': 'kitten'}
|
||||||
exec(code, g)
|
exec(code, g)
|
||||||
hr = g.get('handle_result', lambda *a, **kw: None)
|
hr = g.get('handle_result', lambda *a, **kw: None)
|
||||||
|
if path_modified:
|
||||||
|
del sys.path[0]
|
||||||
return {'start': g['main'], 'end': hr}
|
return {'start': g['main'], 'end': hr}
|
||||||
else:
|
|
||||||
kitten = resolved_kitten(kitten)
|
kitten = resolved_kitten(kitten)
|
||||||
m = importlib.import_module('kittens.{}.main'.format(kitten))
|
m = importlib.import_module('kittens.{}.main'.format(kitten))
|
||||||
return {'start': m.main, 'end': getattr(m, 'handle_result', lambda *a, **k: None)}
|
return {'start': m.main, 'end': getattr(m, 'handle_result', lambda *a, **k: None)}
|
||||||
|
|
||||||
|
|
||||||
def create_kitten_handler(kitten, orig_args):
|
def create_kitten_handler(kitten, orig_args):
|
||||||
@@ -42,6 +46,7 @@ def create_kitten_handler(kitten, orig_args):
|
|||||||
m = import_kitten_main_module(config_dir, kitten)
|
m = import_kitten_main_module(config_dir, kitten)
|
||||||
ans = partial(m['end'], [kitten] + orig_args)
|
ans = partial(m['end'], [kitten] + orig_args)
|
||||||
ans.type_of_input = getattr(m['end'], 'type_of_input', None)
|
ans.type_of_input = getattr(m['end'], 'type_of_input', None)
|
||||||
|
ans.no_ui = getattr(m['end'], 'no_ui', False)
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
# vim:fileencoding=utf-8
|
# vim:fileencoding=utf-8
|
||||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from kitty.cli import parse_args
|
from kitty.cli import parse_args
|
||||||
from ..tui.operations import styled
|
|
||||||
|
|
||||||
|
from ..tui.operations import styled
|
||||||
|
|
||||||
OPTIONS = '''\
|
OPTIONS = '''\
|
||||||
--title
|
--title
|
||||||
@@ -17,7 +18,7 @@ The title for the error message.
|
|||||||
|
|
||||||
def real_main(args):
|
def real_main(args):
|
||||||
error_message = sys.stdin.buffer.read().decode('utf-8')
|
error_message = sys.stdin.buffer.read().decode('utf-8')
|
||||||
sys.stdin = open('/dev/tty')
|
sys.stdin = open(os.ctermid())
|
||||||
msg = 'Show an error message'
|
msg = 'Show an error message'
|
||||||
args, items = parse_args(args, OPTIONS, '', msg, 'hints')
|
args, items = parse_args(args, OPTIONS, '', msg, 'hints')
|
||||||
print(styled(args.title, fg_intense=True, fg='red', bold=True))
|
print(styled(args.title, fg_intense=True, fg='red', bold=True))
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ def main(args):
|
|||||||
sh_script = SHELL_SCRIPT.replace('TERMINFO', terminfo, 1)
|
sh_script = SHELL_SCRIPT.replace('TERMINFO', terminfo, 1)
|
||||||
if len(server_args) > 1:
|
if len(server_args) > 1:
|
||||||
command_to_execute = ["'{}'".format(c.replace("'", """'"'"'""")) for c in server_args[1:]]
|
command_to_execute = ["'{}'".format(c.replace("'", """'"'"'""")) for c in server_args[1:]]
|
||||||
command_to_execute = 'cmd=({}); exec "$cmd"'.format(' '.join(command_to_execute))
|
command_to_execute = 'exec ' + ' '.join(command_to_execute)
|
||||||
else:
|
else:
|
||||||
command_to_execute = ''
|
command_to_execute = ''
|
||||||
sh_script = sh_script.replace('EXEC_CMD', command_to_execute)
|
sh_script = sh_script.replace('EXEC_CMD', command_to_execute)
|
||||||
|
|||||||
@@ -10,14 +10,19 @@ class Handler:
|
|||||||
|
|
||||||
image_manager_class = None
|
image_manager_class = None
|
||||||
|
|
||||||
def _initialize(self, screen_size, quit_loop, wakeup, start_job, debug, image_manager=None):
|
def _initialize(self, screen_size, term_manager, schedule_write, tui_loop, debug, image_manager=None):
|
||||||
self.screen_size, self.quit_loop = screen_size, quit_loop
|
self.screen_size = screen_size
|
||||||
self.wakeup = wakeup
|
self._term_manager = term_manager
|
||||||
|
self._tui_loop = tui_loop
|
||||||
|
self._schedule_write = schedule_write
|
||||||
self.debug = debug
|
self.debug = debug
|
||||||
self.start_job = start_job
|
|
||||||
self.cmd = commander(self)
|
self.cmd = commander(self)
|
||||||
self.image_manager = image_manager
|
self.image_manager = image_manager
|
||||||
|
|
||||||
|
@property
|
||||||
|
def asyncio_loop(self):
|
||||||
|
return self._tui_loop.asycio_loop
|
||||||
|
|
||||||
def add_shortcut(self, action, key, mods=None, is_text=False):
|
def add_shortcut(self, action, key, mods=None, is_text=False):
|
||||||
if not hasattr(self, '_text_shortcuts'):
|
if not hasattr(self, '_text_shortcuts'):
|
||||||
self._text_shortcuts, self._key_shortcuts = {}, {}
|
self._text_shortcuts, self._key_shortcuts = {}, {}
|
||||||
@@ -38,7 +43,6 @@ class Handler:
|
|||||||
self.initialize()
|
self.initialize()
|
||||||
|
|
||||||
def __exit__(self, *a):
|
def __exit__(self, *a):
|
||||||
del self.write_buf[:]
|
|
||||||
del self.debug.fobj
|
del self.debug.fobj
|
||||||
self.finalize()
|
self.finalize()
|
||||||
if self.image_manager is not None:
|
if self.image_manager is not None:
|
||||||
@@ -53,8 +57,11 @@ class Handler:
|
|||||||
def on_resize(self, screen_size):
|
def on_resize(self, screen_size):
|
||||||
self.screen_size = screen_size
|
self.screen_size = screen_size
|
||||||
|
|
||||||
|
def quit_loop(self, return_code=None):
|
||||||
|
self._tui_loop.quit(return_code)
|
||||||
|
|
||||||
def on_term(self):
|
def on_term(self):
|
||||||
self.quit_loop(1)
|
self._tui_loop.quit(1)
|
||||||
|
|
||||||
def on_text(self, text, in_bracketed_paste=False):
|
def on_text(self, text, in_bracketed_paste=False):
|
||||||
pass
|
pass
|
||||||
@@ -71,12 +78,6 @@ class Handler:
|
|||||||
def on_eot(self):
|
def on_eot(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def on_wakeup(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def on_job_done(self, job_id, job_result):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def on_kitty_cmd_response(self, response):
|
def on_kitty_cmd_response(self, response):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -86,7 +87,7 @@ class Handler:
|
|||||||
def write(self, data):
|
def write(self, data):
|
||||||
if isinstance(data, str):
|
if isinstance(data, str):
|
||||||
data = data.encode('utf-8')
|
data = data.encode('utf-8')
|
||||||
self.write_buf.append(data)
|
self._schedule_write(data)
|
||||||
|
|
||||||
def print(self, *args, sep=' ', end='\r\n'):
|
def print(self, *args, sep=' ', end='\r\n'):
|
||||||
data = sep.join(map(str, args)) + end
|
data = sep.join(map(str, args)) + end
|
||||||
|
|||||||
@@ -71,13 +71,15 @@ def convert(path, m, available_width, available_height, scale_up, tdir=None):
|
|||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
width, height = m.width, m.height
|
width, height = m.width, m.height
|
||||||
cmd = ['convert', '-background', 'none', path]
|
cmd = ['convert', '-background', 'none', path]
|
||||||
|
scaled = False
|
||||||
if scale_up:
|
if scale_up:
|
||||||
if width < available_width:
|
if width < available_width:
|
||||||
r = available_width / width
|
r = available_width / width
|
||||||
width, height = available_width, int(height * r)
|
width, height = available_width, int(height * r)
|
||||||
if width > available_width or height > available_height:
|
scaled = True
|
||||||
|
if scaled or width > available_width or height > available_height:
|
||||||
width, height = fit_image(width, height, available_width, available_height)
|
width, height = fit_image(width, height, available_width, available_height)
|
||||||
cmd += ['-resize', '{}x{}'.format(width, height)]
|
cmd += ['-resize', '{}x{}!'.format(width, height)]
|
||||||
with NamedTemporaryFile(prefix='icat-', suffix='.' + m.mode, delete=False, dir=tdir) as outfile:
|
with NamedTemporaryFile(prefix='icat-', suffix='.' + m.mode, delete=False, dir=tdir) as outfile:
|
||||||
run_imagemagick(path, cmd + [outfile.name])
|
run_imagemagick(path, cmd + [outfile.name])
|
||||||
# ImageMagick sometimes generated rgba images smaller than the specified
|
# ImageMagick sometimes generated rgba images smaller than the specified
|
||||||
@@ -88,7 +90,10 @@ def convert(path, m, available_width, available_height, scale_up, tdir=None):
|
|||||||
if sz < expected_size:
|
if sz < expected_size:
|
||||||
missing = expected_size - sz
|
missing = expected_size - sz
|
||||||
if missing % (bytes_per_pixel * width) != 0:
|
if missing % (bytes_per_pixel * width) != 0:
|
||||||
raise ConvertFailed(path, 'ImageMagick failed to convert {} correctly, it generated {} < {} of data'.format(path, sz, expected_size))
|
raise ConvertFailed(
|
||||||
|
path, 'ImageMagick failed to convert {} correctly,'
|
||||||
|
' it generated {} < {} of data (w={}, h={}, bpp={})'.format(
|
||||||
|
path, sz, expected_size, width, height, bytes_per_pixel))
|
||||||
height -= missing // (bytes_per_pixel * width)
|
height -= missing // (bytes_per_pixel * width)
|
||||||
|
|
||||||
return outfile.name, width, height
|
return outfile.name, width, height
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
# vim:fileencoding=utf-8
|
# vim:fileencoding=utf-8
|
||||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import asyncio
|
||||||
import codecs
|
import codecs
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
@@ -10,13 +11,12 @@ import selectors
|
|||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from contextlib import closing, contextmanager
|
from contextlib import contextmanager
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from queue import Empty, Queue
|
|
||||||
|
|
||||||
|
from kitty.constants import is_macos
|
||||||
from kitty.fast_data_types import (
|
from kitty.fast_data_types import (
|
||||||
close_tty, normal_tty, open_tty, parse_input_from_terminal, raw_tty,
|
close_tty, normal_tty, open_tty, parse_input_from_terminal, raw_tty
|
||||||
safe_pipe
|
|
||||||
)
|
)
|
||||||
from kitty.key_encoding import (
|
from kitty.key_encoding import (
|
||||||
ALT, CTRL, PRESS, RELEASE, REPEAT, SHIFT, C, D, backspace_key,
|
ALT, CTRL, PRESS, RELEASE, REPEAT, SHIFT, C, D, backspace_key,
|
||||||
@@ -74,29 +74,6 @@ class TermManager:
|
|||||||
del self.tty_fd, self.original_termios
|
del self.tty_fd, self.original_termios
|
||||||
|
|
||||||
|
|
||||||
class SignalManager:
|
|
||||||
|
|
||||||
def __init__(self, on_sigwinch, on_sigterm, on_sigint):
|
|
||||||
self.on_sigwinch = on_sigwinch
|
|
||||||
self.on_sigterm = on_sigterm
|
|
||||||
self.on_sigint = on_sigint
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
for x in ('winch', 'term', 'int'):
|
|
||||||
attr = 'on_sig' + x
|
|
||||||
handler = getattr(self, attr)
|
|
||||||
old_handler = signal.signal(getattr(signal, 'SIG' + x.upper()), handler)
|
|
||||||
setattr(self, attr, old_handler)
|
|
||||||
|
|
||||||
def __exit__(self, *a):
|
|
||||||
for x in ('winch', 'term', 'int'):
|
|
||||||
attr = 'on_sig' + x
|
|
||||||
val = getattr(self, attr)
|
|
||||||
if val is None:
|
|
||||||
val = signal.SIG_DFL
|
|
||||||
signal.signal(getattr(signal, 'SIG' + x.upper()), val)
|
|
||||||
|
|
||||||
|
|
||||||
LEFT, MIDDLE, RIGHT, FOURTH, FIFTH = 1, 2, 4, 8, 16
|
LEFT, MIDDLE, RIGHT, FOURTH, FIFTH = 1, 2, 4, 8, 16
|
||||||
DRAG = REPEAT
|
DRAG = REPEAT
|
||||||
MouseEvent = namedtuple('MouseEvent', 'x y type buttons mods')
|
MouseEvent = namedtuple('MouseEvent', 'x y type buttons mods')
|
||||||
@@ -150,22 +127,39 @@ class UnhandledException(Handler):
|
|||||||
|
|
||||||
def on_interrupt(self):
|
def on_interrupt(self):
|
||||||
self.quit_loop(1)
|
self.quit_loop(1)
|
||||||
|
on_eot = on_term = on_interrupt
|
||||||
|
|
||||||
def on_eot(self):
|
|
||||||
self.quit_loop(1)
|
class SignalManager:
|
||||||
|
|
||||||
|
def __init__(self, loop, on_winch, on_interrupt, on_term):
|
||||||
|
self.asycio_loop = loop
|
||||||
|
self.on_winch, self.on_interrupt, self.on_term = on_winch, on_interrupt, on_term
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
tuple(map(lambda x: self.asycio_loop.add_signal_handler(*x), (
|
||||||
|
(signal.SIGWINCH, self.on_winch),
|
||||||
|
(signal.SIGINT, self.on_interrupt),
|
||||||
|
(signal.SIGTERM, self.on_term)
|
||||||
|
)))
|
||||||
|
|
||||||
|
def __exit__(self, *a):
|
||||||
|
tuple(map(self.asycio_loop.remove_signal_handler, (
|
||||||
|
signal.SIGWINCH, signal.SIGINT, signal.SIGTERM)))
|
||||||
|
|
||||||
|
|
||||||
class Loop:
|
class Loop:
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
sanitize_bracketed_paste='[\x03\x04\x0e\x0f\r\x07\x7f\x8d\x8e\x8f\x90\x9b\x9d\x9e\x9f]'):
|
sanitize_bracketed_paste='[\x03\x04\x0e\x0f\r\x07\x7f\x8d\x8e\x8f\x90\x9b\x9d\x9e\x9f]'):
|
||||||
self.wakeup_read_fd, self.wakeup_write_fd = safe_pipe()
|
if is_macos:
|
||||||
# For some reason on macOS the DefaultSelector fails when tty_fd is
|
# On macOS PTY devices are not supported by the KqueueSelector and
|
||||||
# open('/dev/tty')
|
# the PollSelector is broken, causes 100% CPU usage
|
||||||
self.sel = s = selectors.PollSelector()
|
self.asycio_loop = asyncio.SelectorEventLoop(selectors.SelectSelector())
|
||||||
s.register(self.wakeup_read_fd, selectors.EVENT_READ)
|
asyncio.set_event_loop(self.asycio_loop)
|
||||||
|
else:
|
||||||
|
self.asycio_loop = asyncio.get_event_loop()
|
||||||
self.return_code = 0
|
self.return_code = 0
|
||||||
self.read_allowed = True
|
|
||||||
self.read_buf = ''
|
self.read_buf = ''
|
||||||
self.decoder = codecs.getincrementaldecoder('utf-8')('ignore')
|
self.decoder = codecs.getincrementaldecoder('utf-8')('ignore')
|
||||||
try:
|
try:
|
||||||
@@ -178,28 +172,8 @@ class Loop:
|
|||||||
self.sanitize_bracketed_paste = bool(sanitize_bracketed_paste)
|
self.sanitize_bracketed_paste = bool(sanitize_bracketed_paste)
|
||||||
if self.sanitize_bracketed_paste:
|
if self.sanitize_bracketed_paste:
|
||||||
self.sanitize_ibp_pat = re.compile(sanitize_bracketed_paste)
|
self.sanitize_ibp_pat = re.compile(sanitize_bracketed_paste)
|
||||||
self.jobs_queue = Queue()
|
|
||||||
|
|
||||||
def start_job(self, job_id, func, *args, **kw):
|
|
||||||
from threading import Thread
|
|
||||||
t = Thread(target=partial(self._run_job, job_id, func), args=args, kwargs=kw, name='LoopJob')
|
|
||||||
t.daemon = True
|
|
||||||
t.start()
|
|
||||||
|
|
||||||
def _run_job(self, job_id, func, *args, **kw):
|
|
||||||
try:
|
|
||||||
result = func(*args, **kw)
|
|
||||||
except Exception as err:
|
|
||||||
import traceback
|
|
||||||
entry = {'id': job_id, 'exception': err, 'tb': traceback.format_exc()}
|
|
||||||
else:
|
|
||||||
entry = {'id': job_id, 'result': result}
|
|
||||||
self.jobs_queue.put(entry)
|
|
||||||
self._wakeup_write(b'j')
|
|
||||||
|
|
||||||
def _read_ready(self, handler, fd):
|
def _read_ready(self, handler, fd):
|
||||||
if not self.read_allowed:
|
|
||||||
return
|
|
||||||
try:
|
try:
|
||||||
data = os.read(fd, io.DEFAULT_BUFFER_SIZE)
|
data = os.read(fd, io.DEFAULT_BUFFER_SIZE)
|
||||||
except BlockingIOError:
|
except BlockingIOError:
|
||||||
@@ -219,6 +193,7 @@ class Loop:
|
|||||||
finally:
|
finally:
|
||||||
del self.handler
|
del self.handler
|
||||||
|
|
||||||
|
# terminal input callbacks {{{
|
||||||
def _on_text(self, text):
|
def _on_text(self, text):
|
||||||
if self.in_bracketed_paste and self.sanitize_bracketed_paste:
|
if self.in_bracketed_paste and self.sanitize_bracketed_paste:
|
||||||
text = self.sanitize_ibp_pat.sub('', text)
|
text = self.sanitize_ibp_pat.sub('', text)
|
||||||
@@ -292,127 +267,99 @@ class Loop:
|
|||||||
elif apc.startswith('G'):
|
elif apc.startswith('G'):
|
||||||
if self.handler.image_manager is not None:
|
if self.handler.image_manager is not None:
|
||||||
self.handler.image_manager.handle_response(apc)
|
self.handler.image_manager.handle_response(apc)
|
||||||
|
# }}}
|
||||||
|
|
||||||
def _write_ready(self, handler, fd):
|
def _write_ready(self, handler, fd):
|
||||||
if len(handler.write_buf) > self.iov_limit:
|
if len(self.write_buf) > self.iov_limit:
|
||||||
handler.write_buf[self.iov_limit - 1] = b''.join(handler.write_buf[self.iov_limit - 1:])
|
self.write_buf[self.iov_limit - 1] = b''.join(self.write_buf[self.iov_limit - 1:])
|
||||||
del handler.write_buf[self.iov_limit:]
|
del self.write_buf[self.iov_limit:]
|
||||||
sizes = tuple(map(len, handler.write_buf))
|
sizes = tuple(map(len, self.write_buf))
|
||||||
try:
|
try:
|
||||||
written = os.writev(fd, handler.write_buf)
|
written = os.writev(fd, self.write_buf)
|
||||||
except BlockingIOError:
|
except BlockingIOError:
|
||||||
return
|
return
|
||||||
if not written:
|
if not written:
|
||||||
raise EOFError('The output stream is closed')
|
raise EOFError('The output stream is closed')
|
||||||
if written >= sum(sizes):
|
if written >= sum(sizes):
|
||||||
handler.write_buf = []
|
self.write_buf = []
|
||||||
|
self.asycio_loop.remove_writer(fd)
|
||||||
|
self.waiting_for_writes = False
|
||||||
else:
|
else:
|
||||||
consumed = 0
|
consumed = 0
|
||||||
for i, buf in enumerate(handler.write_buf):
|
for i, buf in enumerate(self.write_buf):
|
||||||
if not written:
|
if not written:
|
||||||
break
|
break
|
||||||
if len(buf) <= written:
|
if len(buf) <= written:
|
||||||
written -= len(buf)
|
written -= len(buf)
|
||||||
consumed += 1
|
consumed += 1
|
||||||
continue
|
continue
|
||||||
handler.write_buf[i] = buf[written:]
|
self.write_buf[i] = buf[written:]
|
||||||
break
|
break
|
||||||
del handler.write_buf[:consumed]
|
del self.write_buf[:consumed]
|
||||||
|
|
||||||
def _wakeup_ready(self, handler, fd):
|
|
||||||
data = os.read(fd, 1024)
|
|
||||||
if b'r' in data:
|
|
||||||
self._get_screen_size.changed = True
|
|
||||||
handler.on_resize(self._get_screen_size())
|
|
||||||
if b't' in data:
|
|
||||||
handler.on_term()
|
|
||||||
if b'i' in data:
|
|
||||||
handler.on_interrupt()
|
|
||||||
if b'j' in data:
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
entry = self.jobs_queue.get_nowait()
|
|
||||||
except Empty:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
job_id = entry.pop('id')
|
|
||||||
handler.on_job_done(job_id, entry)
|
|
||||||
if b'1' in data:
|
|
||||||
handler.on_wakeup()
|
|
||||||
|
|
||||||
def _wakeup_write(self, val):
|
|
||||||
while not os.write(self.wakeup_write_fd, val):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _on_sigwinch(self, signum, frame):
|
|
||||||
self._wakeup_write(b'r')
|
|
||||||
|
|
||||||
def _on_sigterm(self, signum, frame):
|
|
||||||
self._wakeup_write(b't')
|
|
||||||
|
|
||||||
def _on_sigint(self, signum, frame):
|
|
||||||
self._wakeup_write(b'i')
|
|
||||||
|
|
||||||
def quit(self, return_code=None):
|
def quit(self, return_code=None):
|
||||||
self.read_allowed = False
|
|
||||||
if return_code is not None:
|
if return_code is not None:
|
||||||
self.return_code = return_code
|
self.return_code = return_code
|
||||||
|
self.asycio_loop.stop()
|
||||||
|
|
||||||
def wakeup(self):
|
def loop_impl(self, handler, term_manager, image_manager=None):
|
||||||
self._wakeup_write(b'1')
|
self.write_buf = []
|
||||||
|
tty_fd = term_manager.tty_fd
|
||||||
|
tb = None
|
||||||
|
self.waiting_for_writes = True
|
||||||
|
|
||||||
def _modify_output_selector(self, tty_fd, waiting_for_write):
|
def schedule_write(data):
|
||||||
events = selectors.EVENT_READ
|
self.write_buf.append(data)
|
||||||
if waiting_for_write:
|
if not self.waiting_for_writes:
|
||||||
events |= selectors.EVENT_WRITE
|
self.asycio_loop.add_writer(tty_fd, self._write_ready, handler, tty_fd)
|
||||||
self.sel.modify(tty_fd, events)
|
self.waiting_for_writes = True
|
||||||
|
|
||||||
def loop_impl(self, handler, tty_fd, image_manager=None, waiting_for_write=True):
|
def handle_exception(loop, context):
|
||||||
read_ready, write_ready, wakeup_ready = self._read_ready, self._write_ready, self._wakeup_ready
|
nonlocal tb
|
||||||
select = self.sel.select
|
loop.stop()
|
||||||
handler._initialize(self._get_screen_size(), self.quit, self.wakeup, self.start_job, debug, image_manager)
|
tb = context['message']
|
||||||
|
exc = context.get('exception')
|
||||||
|
if exc is not None:
|
||||||
|
import traceback
|
||||||
|
tb += '\n' + ''.join(traceback.format_exception(exc.__class__, exc, exc.__traceback__))
|
||||||
|
|
||||||
|
self.asycio_loop.set_exception_handler(handle_exception)
|
||||||
|
handler._initialize(self._get_screen_size(), term_manager, schedule_write, self, debug, image_manager)
|
||||||
with handler:
|
with handler:
|
||||||
while True:
|
self.asycio_loop.add_reader(
|
||||||
has_data_to_write = bool(handler.write_buf)
|
tty_fd, self._read_ready, handler, tty_fd)
|
||||||
if not has_data_to_write and not self.read_allowed:
|
self.asycio_loop.add_writer(
|
||||||
break
|
tty_fd, self._write_ready, handler, tty_fd)
|
||||||
if has_data_to_write != waiting_for_write:
|
self.asycio_loop.run_forever()
|
||||||
waiting_for_write = has_data_to_write
|
self.asycio_loop.remove_reader(tty_fd)
|
||||||
self._modify_output_selector(tty_fd, waiting_for_write)
|
if self.waiting_for_writes:
|
||||||
for key, mask in select():
|
self.asycio_loop.remove_writer(tty_fd)
|
||||||
fd = key.fd
|
return tb
|
||||||
if fd == tty_fd:
|
|
||||||
if mask & selectors.EVENT_READ:
|
|
||||||
read_ready(handler, fd)
|
|
||||||
if mask & selectors.EVENT_WRITE:
|
|
||||||
write_ready(handler, fd)
|
|
||||||
else:
|
|
||||||
wakeup_ready(handler, fd)
|
|
||||||
|
|
||||||
def loop(self, handler):
|
def loop(self, handler):
|
||||||
tb = None
|
tb = None
|
||||||
signal_manager = SignalManager(self._on_sigwinch, self._on_sigterm, self._on_sigint)
|
|
||||||
with closing(self.sel), TermManager() as term_manager, signal_manager:
|
def _on_sigwinch():
|
||||||
self.sel.register(term_manager.tty_fd, selectors.EVENT_READ | selectors.EVENT_WRITE)
|
self._get_screen_size.changed = True
|
||||||
|
handler.screen_size = self._get_screen_size()
|
||||||
|
handler.on_resize(handler.screen_size)
|
||||||
|
|
||||||
|
signal_manager = SignalManager(self.asycio_loop, _on_sigwinch, handler.on_interrupt, handler.on_term)
|
||||||
|
with TermManager() as term_manager, signal_manager:
|
||||||
self._get_screen_size = screen_size_function(term_manager.tty_fd)
|
self._get_screen_size = screen_size_function(term_manager.tty_fd)
|
||||||
handler.write_buf = []
|
|
||||||
handler._term_manager = term_manager
|
|
||||||
image_manager = None
|
image_manager = None
|
||||||
if handler.image_manager_class is not None:
|
if handler.image_manager_class is not None:
|
||||||
image_manager = handler.image_manager_class(handler)
|
image_manager = handler.image_manager_class(handler)
|
||||||
try:
|
try:
|
||||||
self.loop_impl(handler, term_manager.tty_fd, image_manager)
|
tb = self.loop_impl(handler, term_manager, image_manager)
|
||||||
except Exception:
|
except Exception:
|
||||||
import traceback
|
import traceback
|
||||||
tb = traceback.format_exc()
|
tb = traceback.format_exc()
|
||||||
self.return_code = 1
|
|
||||||
|
|
||||||
term_manager.extra_finalize = b''.join(handler.write_buf).decode('utf-8')
|
term_manager.extra_finalize = b''.join(self.write_buf).decode('utf-8')
|
||||||
if tb is not None:
|
if tb is not None:
|
||||||
|
self.return_code = 1
|
||||||
self._report_error_loop(tb, term_manager)
|
self._report_error_loop(tb, term_manager)
|
||||||
|
|
||||||
def _report_error_loop(self, tb, term_manager):
|
def _report_error_loop(self, tb, term_manager):
|
||||||
handler = UnhandledException(tb)
|
self.loop_impl(UnhandledException(tb), term_manager)
|
||||||
handler.write_buf = []
|
|
||||||
handler._term_manager = term_manager
|
|
||||||
self.loop_impl(handler, term_manager.tty_fd, waiting_for_write=False)
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from ..tui.line_edit import LineEdit
|
|||||||
from ..tui.handler import Handler
|
from ..tui.handler import Handler
|
||||||
from ..tui.loop import Loop
|
from ..tui.loop import Loop
|
||||||
from ..tui.operations import (
|
from ..tui.operations import (
|
||||||
clear_screen, color_code, colored, cursor, faint, set_line_wrapping,
|
clear_screen, colored, cursor, faint, set_line_wrapping,
|
||||||
set_window_title, sgr, styled
|
set_window_title, sgr, styled
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -159,30 +159,32 @@ class Table:
|
|||||||
self.last_cols, self.last_rows = cols, rows
|
self.last_cols, self.last_rows = cols, rows
|
||||||
self.layout_dirty = False
|
self.layout_dirty = False
|
||||||
|
|
||||||
|
def safe_chr(codepoint):
|
||||||
|
return chr(codepoint).encode('utf-8', 'replace').decode('utf-8')
|
||||||
|
|
||||||
if self.mode is NAME:
|
if self.mode is NAME:
|
||||||
def as_parts(i, codepoint):
|
def as_parts(i, codepoint):
|
||||||
return encode_hint(i).ljust(idx_size), chr(codepoint), name(codepoint)
|
return encode_hint(i).ljust(idx_size), safe_chr(codepoint), name(codepoint)
|
||||||
|
|
||||||
def cell(i, idx, c, desc):
|
def cell(i, idx, c, desc):
|
||||||
is_current = i == self.current_idx
|
is_current = i == self.current_idx
|
||||||
if is_current:
|
text = colored(idx, 'green') + ' ' + sgr('49') + c + ' '
|
||||||
yield sgr(color_code('gray', base=40))
|
|
||||||
yield colored(idx, 'green') + ' '
|
|
||||||
yield colored(c, 'black' if is_current else 'gray', True) + ' '
|
|
||||||
w = wcswidth(c)
|
w = wcswidth(c)
|
||||||
if w < 2:
|
if w < 2:
|
||||||
yield ' ' * (2 - w)
|
text += ' ' * (2 - w)
|
||||||
if len(desc) > space_for_desc:
|
if len(desc) > space_for_desc:
|
||||||
desc = desc[:space_for_desc - 1] + '…'
|
text += desc[:space_for_desc - 1] + '…'
|
||||||
yield faint(desc)
|
else:
|
||||||
|
text += desc
|
||||||
extra = space_for_desc - len(desc)
|
extra = space_for_desc - len(desc)
|
||||||
if extra > 0:
|
if extra > 0:
|
||||||
yield ' ' * extra
|
text += ' ' * extra
|
||||||
if is_current:
|
|
||||||
yield sgr('49')
|
yield styled(text, reverse=True if is_current else None)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
def as_parts(i, codepoint):
|
def as_parts(i, codepoint):
|
||||||
return encode_hint(i).ljust(idx_size), chr(codepoint), ''
|
return encode_hint(i).ljust(idx_size), safe_chr(codepoint), ''
|
||||||
|
|
||||||
def cell(i, idx, c, desc):
|
def cell(i, idx, c, desc):
|
||||||
yield colored(idx, 'green') + ' '
|
yield colored(idx, 'green') + ' '
|
||||||
@@ -455,7 +457,6 @@ class UnicodeInput(Handler):
|
|||||||
self.quit_loop(1)
|
self.quit_loop(1)
|
||||||
|
|
||||||
def on_resize(self, new_size):
|
def on_resize(self, new_size):
|
||||||
Handler.on_resize(self, new_size)
|
|
||||||
self.refresh()
|
self.refresh()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
30346
kittens/unicode_input/names.h
generated
30346
kittens/unicode_input/names.h
generated
File diff suppressed because one or more lines are too long
@@ -4,5 +4,5 @@ in vec3 color;
|
|||||||
out vec4 final_color;
|
out vec4 final_color;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
final_color = vec4(color, background_opacity);
|
final_color = vec4(color * background_opacity, background_opacity);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
# vim:fileencoding=utf-8
|
# vim:fileencoding=utf-8
|
||||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
from functools import partial
|
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
from .fast_data_types import (
|
from .fast_data_types import (
|
||||||
@@ -10,6 +9,16 @@ from .fast_data_types import (
|
|||||||
)
|
)
|
||||||
from .utils import load_shaders
|
from .utils import load_shaders
|
||||||
|
|
||||||
|
try:
|
||||||
|
from enum import IntFlag
|
||||||
|
except Exception:
|
||||||
|
from enum import IntEnum as IntFlag
|
||||||
|
|
||||||
|
|
||||||
|
class BorderColor(IntFlag):
|
||||||
|
# See the border vertex shader for how these flags become actual colors
|
||||||
|
default_bg, active, inactive, window_bg, bell = ((1 << i) for i in range(5))
|
||||||
|
|
||||||
|
|
||||||
def vertical_edge(os_window_id, tab_id, color, width, top, bottom, left):
|
def vertical_edge(os_window_id, tab_id, color, width, top, bottom, left):
|
||||||
add_borders_rect(os_window_id, tab_id, left, top, left + width, bottom, color)
|
add_borders_rect(os_window_id, tab_id, left, top, left + width, bottom, color)
|
||||||
@@ -19,15 +28,15 @@ def horizontal_edge(os_window_id, tab_id, color, height, left, right, top):
|
|||||||
add_borders_rect(os_window_id, tab_id, left, top, right, top + height, color)
|
add_borders_rect(os_window_id, tab_id, left, top, right, top + height, color)
|
||||||
|
|
||||||
|
|
||||||
def edge(func, os_window_id, tab_id, color, sz, a, b):
|
def draw_edges(os_window_id, tab_id, colors, width, geometry, base_width=0):
|
||||||
return partial(func, os_window_id, tab_id, color, sz, a, b)
|
left = geometry.left - (width + base_width)
|
||||||
|
top = geometry.top - (width + base_width)
|
||||||
|
right = geometry.right + (width + base_width)
|
||||||
def border(os_window_id, tab_id, color, sz, left, top, right, bottom):
|
bottom = geometry.bottom + (width + base_width)
|
||||||
horz = edge(horizontal_edge, os_window_id, tab_id, color, sz, left, right)
|
horizontal_edge(os_window_id, tab_id, colors[1], width, left, right, top)
|
||||||
horz(top), horz(bottom - sz) # top, bottom edges
|
horizontal_edge(os_window_id, tab_id, colors[3], width, left, right, geometry.bottom + base_width)
|
||||||
vert = edge(vertical_edge, os_window_id, tab_id, color, sz, top, bottom)
|
vertical_edge(os_window_id, tab_id, colors[0], width, top, bottom, left)
|
||||||
vert(left), vert(right - sz) # left, right edges
|
vertical_edge(os_window_id, tab_id, colors[2], width, top, bottom, geometry.right + base_width)
|
||||||
|
|
||||||
|
|
||||||
def load_borders_program():
|
def load_borders_program():
|
||||||
@@ -52,28 +61,31 @@ class Borders:
|
|||||||
extra_blank_rects,
|
extra_blank_rects,
|
||||||
draw_window_borders=True
|
draw_window_borders=True
|
||||||
):
|
):
|
||||||
add_borders_rect(self.os_window_id, self.tab_id, 0, 0, 0, 0, 1)
|
add_borders_rect(self.os_window_id, self.tab_id, 0, 0, 0, 0, BorderColor.default_bg)
|
||||||
for br in chain(current_layout.blank_rects, extra_blank_rects):
|
for br in chain(current_layout.blank_rects, extra_blank_rects):
|
||||||
add_borders_rect(self.os_window_id, self.tab_id, *br, 1)
|
add_borders_rect(self.os_window_id, self.tab_id, *br, BorderColor.default_bg)
|
||||||
bw, pw = self.border_width, self.padding_width
|
bw, pw = self.border_width, self.padding_width
|
||||||
fw = bw + pw
|
if bw + pw <= 0:
|
||||||
|
return
|
||||||
|
draw_borders = bw > 0 and draw_window_borders and len(windows) > 1
|
||||||
|
if draw_borders:
|
||||||
|
border_data = current_layout.resolve_borders(windows, active_window)
|
||||||
|
|
||||||
if fw > 0:
|
for i, w in enumerate(windows):
|
||||||
for w in windows:
|
g = w.geometry
|
||||||
g = w.geometry
|
window_bg = w.screen.color_profile.default_bg
|
||||||
if bw > 0 and draw_window_borders:
|
window_bg = (window_bg << 8) | BorderColor.window_bg
|
||||||
# Draw the border rectangles
|
if draw_borders:
|
||||||
color = 2 if w is active_window else (16 if w.needs_attention else 4)
|
# Draw the border rectangles
|
||||||
border(
|
if w is active_window:
|
||||||
self.os_window_id, self.tab_id,
|
color = BorderColor.active
|
||||||
color, bw, g.left - fw, g.top - fw, g.right + fw,
|
else:
|
||||||
g.bottom + fw
|
color = BorderColor.bell if w.needs_attention else BorderColor.inactive
|
||||||
)
|
colors = tuple(color if needed else window_bg for needed in next(border_data))
|
||||||
if pw > 0:
|
draw_edges(
|
||||||
# Draw the background rectangles over the padding region
|
self.os_window_id, self.tab_id, colors, bw, g, base_width=pw)
|
||||||
color = w.screen.color_profile.default_bg
|
if pw > 0:
|
||||||
border(
|
# Draw the background rectangles over the padding region
|
||||||
self.os_window_id, self.tab_id,
|
colors = (window_bg, window_bg, window_bg, window_bg)
|
||||||
(color << 8) | 8, pw, g.left - pw, g.top - pw, g.right + pw,
|
draw_edges(
|
||||||
g.bottom + pw
|
self.os_window_id, self.tab_id, colors, pw, g)
|
||||||
)
|
|
||||||
|
|||||||
280
kitty/boss.py
280
kitty/boss.py
@@ -10,6 +10,7 @@ from functools import partial
|
|||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
from weakref import WeakValueDictionary
|
from weakref import WeakValueDictionary
|
||||||
|
|
||||||
|
from .child import cached_process_data
|
||||||
from .cli import create_opts, parse_args
|
from .cli import create_opts, parse_args
|
||||||
from .conf.utils import to_cmdline
|
from .conf.utils import to_cmdline
|
||||||
from .config import initial_window_size_func, prepare_config_file_for_editing
|
from .config import initial_window_size_func, prepare_config_file_for_editing
|
||||||
@@ -19,20 +20,22 @@ from .constants import (
|
|||||||
)
|
)
|
||||||
from .fast_data_types import (
|
from .fast_data_types import (
|
||||||
ChildMonitor, background_opacity_of, change_background_opacity,
|
ChildMonitor, background_opacity_of, change_background_opacity,
|
||||||
create_os_window, current_os_window, destroy_global_data,
|
change_os_window_state, create_os_window, current_os_window,
|
||||||
get_clipboard_string, glfw_post_empty_event, global_font_size,
|
destroy_global_data, get_clipboard_string, glfw_post_empty_event,
|
||||||
mark_os_window_for_close, os_window_font_size, patch_global_colors,
|
global_font_size, mark_os_window_for_close, os_window_font_size,
|
||||||
set_clipboard_string, set_in_sequence_mode, toggle_fullscreen
|
patch_global_colors, set_clipboard_string, set_in_sequence_mode,
|
||||||
|
toggle_fullscreen
|
||||||
)
|
)
|
||||||
from .keys import get_shortcut, shortcut_matches
|
from .keys import get_shortcut, shortcut_matches
|
||||||
|
from .layout import set_draw_minimal_borders
|
||||||
from .remote_control import handle_cmd
|
from .remote_control import handle_cmd
|
||||||
from .rgb import Color, color_from_int
|
from .rgb import Color, color_from_int
|
||||||
from .session import create_session
|
from .session import create_session
|
||||||
from .tabs import SpecialWindow, SpecialWindowInstance, TabManager
|
from .tabs import SpecialWindow, SpecialWindowInstance, TabManager
|
||||||
from .utils import (
|
from .utils import (
|
||||||
get_editor, get_primary_selection, log_error, open_url, parse_address_spec,
|
get_editor, get_primary_selection, is_path_in_temp_dir, log_error,
|
||||||
remove_socket_file, safe_print, set_primary_selection, single_instance,
|
open_url, parse_address_spec, remove_socket_file, safe_print,
|
||||||
startup_notification_handler
|
set_primary_selection, single_instance, startup_notification_handler
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -46,6 +49,31 @@ def listen_on(spec):
|
|||||||
return s.fileno()
|
return s.fileno()
|
||||||
|
|
||||||
|
|
||||||
|
def data_for_at(w, arg, add_wrap_markers=False):
|
||||||
|
def as_text(**kw):
|
||||||
|
kw['add_wrap_markers'] = add_wrap_markers
|
||||||
|
return w.as_text(**kw)
|
||||||
|
|
||||||
|
if arg == '@selection':
|
||||||
|
return w.text_for_selection()
|
||||||
|
if arg == '@ansi':
|
||||||
|
return as_text(as_ansi=True, add_history=True)
|
||||||
|
if arg == '@text':
|
||||||
|
return as_text(add_history=True)
|
||||||
|
if arg == '@screen':
|
||||||
|
return as_text()
|
||||||
|
if arg == '@ansi_screen':
|
||||||
|
return as_text(as_ansi=True)
|
||||||
|
if arg == '@alternate':
|
||||||
|
return as_text(alternate_screen=True)
|
||||||
|
if arg == '@alternate_scrollback':
|
||||||
|
return as_text(alternate_screen=True, add_history=True)
|
||||||
|
if arg == '@ansi_alternate':
|
||||||
|
return as_text(as_ansi=True, alternate_screen=True)
|
||||||
|
if arg == '@ansi_alternate_scrollback':
|
||||||
|
return as_text(as_ansi=True, alternate_screen=True, add_history=True)
|
||||||
|
|
||||||
|
|
||||||
class DumpCommands: # {{{
|
class DumpCommands: # {{{
|
||||||
|
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
@@ -76,8 +104,10 @@ class DumpCommands: # {{{
|
|||||||
class Boss:
|
class Boss:
|
||||||
|
|
||||||
def __init__(self, os_window_id, opts, args, cached_values, new_os_window_trigger):
|
def __init__(self, os_window_id, opts, args, cached_values, new_os_window_trigger):
|
||||||
|
set_draw_minimal_borders(opts)
|
||||||
self.window_id_map = WeakValueDictionary()
|
self.window_id_map = WeakValueDictionary()
|
||||||
self.startup_colors = {k: opts[k] for k in opts if isinstance(opts[k], Color)}
|
self.startup_colors = {k: opts[k] for k in opts if isinstance(opts[k], Color)}
|
||||||
|
self.startup_cursor_text_color = opts.cursor_text_color
|
||||||
self.pending_sequences = None
|
self.pending_sequences = None
|
||||||
self.cached_values = cached_values
|
self.cached_values = cached_values
|
||||||
self.os_window_map = {}
|
self.os_window_map = {}
|
||||||
@@ -101,6 +131,11 @@ class Boss:
|
|||||||
if new_os_window_trigger is not None:
|
if new_os_window_trigger is not None:
|
||||||
self.keymap.pop(new_os_window_trigger, None)
|
self.keymap.pop(new_os_window_trigger, None)
|
||||||
self.add_os_window(startup_session, os_window_id=os_window_id)
|
self.add_os_window(startup_session, os_window_id=os_window_id)
|
||||||
|
if args.start_as != 'normal':
|
||||||
|
if args.start_as == 'fullscreen':
|
||||||
|
self.toggle_fullscreen()
|
||||||
|
else:
|
||||||
|
change_os_window_state(args.start_as)
|
||||||
|
|
||||||
def add_os_window(self, startup_session, os_window_id=None, wclass=None, wname=None, opts_for_size=None, startup_id=None):
|
def add_os_window(self, startup_session, os_window_id=None, wclass=None, wname=None, opts_for_size=None, startup_id=None):
|
||||||
if os_window_id is None:
|
if os_window_id is None:
|
||||||
@@ -116,11 +151,15 @@ class Boss:
|
|||||||
return os_window_id
|
return os_window_id
|
||||||
|
|
||||||
def list_os_windows(self):
|
def list_os_windows(self):
|
||||||
for os_window_id, tm in self.os_window_map.items():
|
with cached_process_data():
|
||||||
yield {
|
active_tab, active_window = self.active_tab, self.active_window
|
||||||
'id': os_window_id,
|
active_tab_manager = self.active_tab_manager
|
||||||
'tabs': list(tm.list_tabs()),
|
for os_window_id, tm in self.os_window_map.items():
|
||||||
}
|
yield {
|
||||||
|
'id': os_window_id,
|
||||||
|
'is_focused': tm is active_tab_manager,
|
||||||
|
'tabs': list(tm.list_tabs(active_tab, active_window)),
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def all_tab_managers(self):
|
def all_tab_managers(self):
|
||||||
@@ -150,11 +189,18 @@ class Boss:
|
|||||||
return
|
return
|
||||||
if w is not None:
|
if w is not None:
|
||||||
yield w
|
yield w
|
||||||
|
return
|
||||||
|
if field == 'env':
|
||||||
|
kp, vp = exp.partition('=')[::2]
|
||||||
|
if vp:
|
||||||
|
pat = tuple(map(re.compile, (kp, vp)))
|
||||||
|
else:
|
||||||
|
pat = re.compile(kp), None
|
||||||
else:
|
else:
|
||||||
pat = re.compile(exp)
|
pat = re.compile(exp)
|
||||||
for window in self.all_windows:
|
for window in self.all_windows:
|
||||||
if window.matches(field, pat):
|
if window.matches(field, pat):
|
||||||
yield window
|
yield window
|
||||||
|
|
||||||
def tab_for_window(self, window):
|
def tab_for_window(self, window):
|
||||||
for tab in self.all_tabs:
|
for tab in self.all_tabs:
|
||||||
@@ -181,26 +227,36 @@ class Boss:
|
|||||||
yield tab
|
yield tab
|
||||||
|
|
||||||
def set_active_window(self, window):
|
def set_active_window(self, window):
|
||||||
for tm in self.os_window_map.values():
|
for os_window_id, tm in self.os_window_map.items():
|
||||||
for tab in tm:
|
for tab in tm:
|
||||||
for w in tab:
|
for w in tab:
|
||||||
if w.id == window.id:
|
if w.id == window.id:
|
||||||
if tab is not self.active_tab:
|
if tab is not self.active_tab:
|
||||||
tm.set_active_tab(tab)
|
tm.set_active_tab(tab)
|
||||||
tab.set_active_window(w)
|
tab.set_active_window(w)
|
||||||
return
|
return os_window_id
|
||||||
|
|
||||||
def _new_os_window(self, args, cwd_from=None):
|
def _new_os_window(self, args, cwd_from=None):
|
||||||
sw = self.args_to_special_window(args, cwd_from) if args else None
|
if isinstance(args, SpecialWindowInstance):
|
||||||
|
sw = args
|
||||||
|
else:
|
||||||
|
sw = self.args_to_special_window(args, cwd_from) if args else None
|
||||||
startup_session = create_session(self.opts, special_window=sw, cwd_from=cwd_from)
|
startup_session = create_session(self.opts, special_window=sw, cwd_from=cwd_from)
|
||||||
return self.add_os_window(startup_session)
|
return self.add_os_window(startup_session)
|
||||||
|
|
||||||
def new_os_window(self, *args):
|
def new_os_window(self, *args):
|
||||||
self._new_os_window(args)
|
self._new_os_window(args)
|
||||||
|
|
||||||
def new_os_window_with_cwd(self, *args):
|
@property
|
||||||
|
def active_window_for_cwd(self):
|
||||||
w = self.active_window
|
w = self.active_window
|
||||||
cwd_from = w.child.pid if w is not None else None
|
if w is not None and w.overlay_for is not None and w.overlay_for in self.window_id_map:
|
||||||
|
w = self.window_id_map[w.overlay_for]
|
||||||
|
return w
|
||||||
|
|
||||||
|
def new_os_window_with_cwd(self, *args):
|
||||||
|
w = self.active_window_for_cwd
|
||||||
|
cwd_from = w.child.pid_for_cwd if w is not None else None
|
||||||
self._new_os_window(args, cwd_from)
|
self._new_os_window(args, cwd_from)
|
||||||
|
|
||||||
def add_child(self, window):
|
def add_child(self, window):
|
||||||
@@ -313,6 +369,26 @@ class Boss:
|
|||||||
if tm is not None:
|
if tm is not None:
|
||||||
tm.resize()
|
tm.resize()
|
||||||
|
|
||||||
|
def clear_terminal(self, action, only_active):
|
||||||
|
if only_active:
|
||||||
|
windows = []
|
||||||
|
w = self.active_window
|
||||||
|
if w is not None:
|
||||||
|
windows.append(w)
|
||||||
|
else:
|
||||||
|
windows = self.all_windows
|
||||||
|
reset = action == 'reset'
|
||||||
|
how = 3 if action == 'scrollback' else 2
|
||||||
|
for w in windows:
|
||||||
|
if action == 'scroll':
|
||||||
|
w.screen.scroll_until_cursor()
|
||||||
|
continue
|
||||||
|
w.screen.cursor.x = w.screen.cursor.y = 0
|
||||||
|
if reset:
|
||||||
|
w.screen.reset()
|
||||||
|
else:
|
||||||
|
w.screen.erase_in_display(how, False)
|
||||||
|
|
||||||
def increase_font_size(self): # legacy
|
def increase_font_size(self): # legacy
|
||||||
cfs = global_font_size()
|
cfs = global_font_size()
|
||||||
self.set_font_size(min(self.opts.font_size * 5, cfs + 2.0))
|
self.set_font_size(min(self.opts.font_size * 5, cfs + 2.0))
|
||||||
@@ -390,10 +466,10 @@ class Boss:
|
|||||||
_('You must set the dynamic_background_opacity option in kitty.conf to be able to change background opacity'))
|
_('You must set the dynamic_background_opacity option in kitty.conf to be able to change background opacity'))
|
||||||
os_window_id = window.os_window_id
|
os_window_id = window.os_window_id
|
||||||
if opacity[0] in '+-':
|
if opacity[0] in '+-':
|
||||||
opacity = background_opacity_of(os_window_id)
|
old_opacity = background_opacity_of(os_window_id)
|
||||||
if opacity is None:
|
if old_opacity is None:
|
||||||
return
|
return
|
||||||
opacity += float(opacity)
|
opacity = old_opacity + float(opacity)
|
||||||
elif opacity == 'default':
|
elif opacity == 'default':
|
||||||
opacity = self.opts.background_opacity
|
opacity = self.opts.background_opacity
|
||||||
else:
|
else:
|
||||||
@@ -476,6 +552,8 @@ class Boss:
|
|||||||
if w is not None:
|
if w is not None:
|
||||||
tm = self.os_window_map.get(w.os_window_id)
|
tm = self.os_window_map.get(w.os_window_id)
|
||||||
if tm is not None:
|
if tm is not None:
|
||||||
|
tm.update_tab_bar_data()
|
||||||
|
tm.mark_tab_bar_dirty()
|
||||||
t = tm.tab_for_id(w.tab_id)
|
t = tm.tab_for_id(w.tab_id)
|
||||||
if t is not None:
|
if t is not None:
|
||||||
t.relayout_borders()
|
t.relayout_borders()
|
||||||
@@ -484,6 +562,8 @@ class Boss:
|
|||||||
if key_action is not None:
|
if key_action is not None:
|
||||||
f = getattr(self, key_action.func, None)
|
f = getattr(self, key_action.func, None)
|
||||||
if f is not None:
|
if f is not None:
|
||||||
|
if self.args.debug_keyboard:
|
||||||
|
print('Keypress matched action:', f.__name__)
|
||||||
passthrough = f(*key_action.args)
|
passthrough = f(*key_action.args)
|
||||||
if passthrough is not True:
|
if passthrough is not True:
|
||||||
return True
|
return True
|
||||||
@@ -497,6 +577,8 @@ class Boss:
|
|||||||
f = getattr(tab, key_action.func, getattr(window, key_action.func, None))
|
f = getattr(tab, key_action.func, getattr(window, key_action.func, None))
|
||||||
if f is not None:
|
if f is not None:
|
||||||
passthrough = f(*key_action.args)
|
passthrough = f(*key_action.args)
|
||||||
|
if self.args.debug_keyboard:
|
||||||
|
print('Keypress matched action:', f.__name__)
|
||||||
if passthrough is not True:
|
if passthrough is not True:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@@ -573,13 +655,21 @@ class Boss:
|
|||||||
output += str(s.linebuf.line(i))
|
output += str(s.linebuf.line(i))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def _run_kitten(self, kitten, args=(), input_data=None):
|
def _run_kitten(self, kitten, args=(), input_data=None, window=None):
|
||||||
w = self.active_window
|
orig_args, args = list(args), list(args)
|
||||||
tab = self.active_tab
|
from kittens.runner import create_kitten_handler
|
||||||
|
end_kitten = create_kitten_handler(kitten, orig_args)
|
||||||
|
if window is None:
|
||||||
|
w = self.active_window
|
||||||
|
tab = self.active_tab
|
||||||
|
else:
|
||||||
|
w = window
|
||||||
|
tab = w.tabref()
|
||||||
|
if end_kitten.no_ui:
|
||||||
|
end_kitten(None, getattr(w, 'id', None), self)
|
||||||
|
return
|
||||||
|
|
||||||
if w is not None and tab is not None and w.overlay_for is None:
|
if w is not None and tab is not None and w.overlay_for is None:
|
||||||
orig_args, args = list(args), list(args)
|
|
||||||
from kittens.runner import create_kitten_handler
|
|
||||||
end_kitten = create_kitten_handler(kitten, orig_args)
|
|
||||||
args[0:0] = [config_dir, kitten]
|
args[0:0] = [config_dir, kitten]
|
||||||
if input_data is None:
|
if input_data is None:
|
||||||
type_of_input = end_kitten.type_of_input
|
type_of_input = end_kitten.type_of_input
|
||||||
@@ -604,11 +694,14 @@ class Boss:
|
|||||||
stdin=data,
|
stdin=data,
|
||||||
env={
|
env={
|
||||||
'KITTY_COMMON_OPTS': json.dumps(copts),
|
'KITTY_COMMON_OPTS': json.dumps(copts),
|
||||||
|
'KITTY_CHILD_PID': w.child.pid,
|
||||||
'PYTHONWARNINGS': 'ignore',
|
'PYTHONWARNINGS': 'ignore',
|
||||||
'OVERLAID_WINDOW_LINES': str(w.screen.lines),
|
'OVERLAID_WINDOW_LINES': str(w.screen.lines),
|
||||||
'OVERLAID_WINDOW_COLS': str(w.screen.columns),
|
'OVERLAID_WINDOW_COLS': str(w.screen.columns),
|
||||||
},
|
},
|
||||||
overlay_for=w.id))
|
cwd=w.cwd_of_child,
|
||||||
|
overlay_for=w.id
|
||||||
|
))
|
||||||
overlay_window.action_on_close = partial(self.on_kitten_finish, w.id, end_kitten)
|
overlay_window.action_on_close = partial(self.on_kitten_finish, w.id, end_kitten)
|
||||||
return overlay_window
|
return overlay_window
|
||||||
|
|
||||||
@@ -647,23 +740,19 @@ class Boss:
|
|||||||
break
|
break
|
||||||
|
|
||||||
def kitty_shell(self, window_type):
|
def kitty_shell(self, window_type):
|
||||||
cmd = [kitty_exe(), '@']
|
cmd = ['@', kitty_exe(), '@']
|
||||||
if window_type == 'tab':
|
if window_type == 'tab':
|
||||||
window = self._new_tab(cmd).active_window
|
self._new_tab(cmd).active_window
|
||||||
elif window_type == 'os_window':
|
elif window_type == 'os_window':
|
||||||
os_window_id = self._new_os_window(cmd)
|
os_window_id = self._new_os_window(cmd)
|
||||||
window = self.os_window_map[os_window_id].active_window
|
self.os_window_map[os_window_id].active_window
|
||||||
elif window_type == 'overlay':
|
elif window_type == 'overlay':
|
||||||
w = self.active_window
|
w = self.active_window
|
||||||
tab = self.active_tab
|
tab = self.active_tab
|
||||||
if w is not None and tab is not None and w.overlay_for is None:
|
if w is not None and tab is not None and w.overlay_for is None:
|
||||||
window = tab.new_special_window(SpecialWindow(cmd, overlay_for=w.id))
|
tab.new_special_window(SpecialWindow(cmd, overlay_for=w.id))
|
||||||
else:
|
|
||||||
window = None
|
|
||||||
else:
|
else:
|
||||||
window = self._new_window(cmd)
|
self._new_window(cmd)
|
||||||
if window is not None:
|
|
||||||
window.allow_remote_control = True
|
|
||||||
|
|
||||||
def switch_focus_to(self, window_idx):
|
def switch_focus_to(self, window_idx):
|
||||||
tab = self.active_tab
|
tab = self.active_tab
|
||||||
@@ -730,25 +819,74 @@ class Boss:
|
|||||||
if tm is not None:
|
if tm is not None:
|
||||||
tm.next_tab(-1)
|
tm.next_tab(-1)
|
||||||
|
|
||||||
|
prev_tab = previous_tab
|
||||||
|
|
||||||
|
def process_stdin_source(self, window=None, stdin=None):
|
||||||
|
w = window or self.active_window
|
||||||
|
env = None
|
||||||
|
if stdin:
|
||||||
|
add_wrap_markers = stdin.endswith('_wrap')
|
||||||
|
if add_wrap_markers:
|
||||||
|
stdin = stdin[:-len('_wrap')]
|
||||||
|
stdin = data_for_at(w, stdin, add_wrap_markers=add_wrap_markers)
|
||||||
|
if stdin is not None:
|
||||||
|
pipe_data = w.pipe_data(stdin, has_wrap_markers=add_wrap_markers) if w else {}
|
||||||
|
if pipe_data:
|
||||||
|
env = {
|
||||||
|
'KITTY_PIPE_DATA':
|
||||||
|
'{scrolled_by}:{cursor_x},{cursor_y}:{lines},{columns}'.format(**pipe_data)
|
||||||
|
}
|
||||||
|
stdin = stdin.encode('utf-8')
|
||||||
|
return env, stdin
|
||||||
|
|
||||||
|
def special_window_for_cmd(self, cmd, window=None, stdin=None, cwd_from=None, as_overlay=False):
|
||||||
|
w = window or self.active_window
|
||||||
|
env, stdin = self.process_stdin_source(w, stdin)
|
||||||
|
cmdline = []
|
||||||
|
for arg in cmd:
|
||||||
|
if arg == '@selection':
|
||||||
|
arg = data_for_at(w, arg)
|
||||||
|
if not arg:
|
||||||
|
continue
|
||||||
|
cmdline.append(arg)
|
||||||
|
overlay_for = w.id if as_overlay and w.overlay_for is None else None
|
||||||
|
return SpecialWindow(cmd, stdin, cwd_from=cwd_from, overlay_for=overlay_for, env=env)
|
||||||
|
|
||||||
|
def pipe(self, source, dest, exe, *args):
|
||||||
|
cmd = [exe] + list(args)
|
||||||
|
window = self.active_window
|
||||||
|
cwd_from = window.child.pid_for_cwd if window else None
|
||||||
|
|
||||||
|
def create_window():
|
||||||
|
return self.special_window_for_cmd(
|
||||||
|
cmd, stdin=source, as_overlay=dest == 'overlay', cwd_from=cwd_from)
|
||||||
|
|
||||||
|
if dest == 'overlay' or dest == 'window':
|
||||||
|
tab = self.active_tab
|
||||||
|
if tab is not None:
|
||||||
|
return tab.new_special_window(create_window())
|
||||||
|
elif dest == 'tab':
|
||||||
|
tm = self.active_tab_manager
|
||||||
|
if tm is not None:
|
||||||
|
tm.new_tab(special_window=create_window(), cwd_from=cwd_from)
|
||||||
|
elif dest == 'os_window':
|
||||||
|
self._new_os_window(create_window(), cwd_from=cwd_from)
|
||||||
|
else:
|
||||||
|
import subprocess
|
||||||
|
env, stdin = self.process_stdin_source(stdin=source, window=window)
|
||||||
|
if stdin:
|
||||||
|
p = subprocess.Popen(cmd, env=env, stdin=subprocess.PIPE)
|
||||||
|
p.communicate(stdin)
|
||||||
|
else:
|
||||||
|
subprocess.Popen(cmd)
|
||||||
|
|
||||||
def args_to_special_window(self, args, cwd_from=None):
|
def args_to_special_window(self, args, cwd_from=None):
|
||||||
args = list(args)
|
args = list(args)
|
||||||
stdin = None
|
stdin = None
|
||||||
w = self.active_window
|
w = self.active_window
|
||||||
|
|
||||||
def data_for_at(arg):
|
if args[0].startswith('@') and args[0] != '@':
|
||||||
if arg == '@selection':
|
stdin = data_for_at(w, args[0]) or None
|
||||||
return w.text_for_selection()
|
|
||||||
if arg == '@ansi':
|
|
||||||
return w.as_text(as_ansi=True, add_history=True)
|
|
||||||
if arg == '@text':
|
|
||||||
return w.as_text(add_history=True)
|
|
||||||
if arg == '@screen':
|
|
||||||
return w.as_text()
|
|
||||||
if arg == '@ansi_screen':
|
|
||||||
return w.as_text(as_ansi=True)
|
|
||||||
|
|
||||||
if args[0].startswith('@'):
|
|
||||||
stdin = data_for_at(args[0]) or None
|
|
||||||
if stdin is not None:
|
if stdin is not None:
|
||||||
stdin = stdin.encode('utf-8')
|
stdin = stdin.encode('utf-8')
|
||||||
del args[0]
|
del args[0]
|
||||||
@@ -756,13 +894,13 @@ class Boss:
|
|||||||
cmd = []
|
cmd = []
|
||||||
for arg in args:
|
for arg in args:
|
||||||
if arg == '@selection':
|
if arg == '@selection':
|
||||||
arg = data_for_at(arg)
|
arg = data_for_at(w, arg)
|
||||||
if not arg:
|
if not arg:
|
||||||
continue
|
continue
|
||||||
cmd.append(arg)
|
cmd.append(arg)
|
||||||
return SpecialWindow(cmd, stdin, cwd_from=cwd_from)
|
return SpecialWindow(cmd, stdin, cwd_from=cwd_from)
|
||||||
|
|
||||||
def _new_tab(self, args, cwd_from=None):
|
def _new_tab(self, args, cwd_from=None, as_neighbor=False):
|
||||||
special_window = None
|
special_window = None
|
||||||
if args:
|
if args:
|
||||||
if isinstance(args, SpecialWindowInstance):
|
if isinstance(args, SpecialWindowInstance):
|
||||||
@@ -771,15 +909,22 @@ class Boss:
|
|||||||
special_window = self.args_to_special_window(args, cwd_from=cwd_from)
|
special_window = self.args_to_special_window(args, cwd_from=cwd_from)
|
||||||
tm = self.active_tab_manager
|
tm = self.active_tab_manager
|
||||||
if tm is not None:
|
if tm is not None:
|
||||||
return tm.new_tab(special_window=special_window, cwd_from=cwd_from)
|
return tm.new_tab(special_window=special_window, cwd_from=cwd_from, as_neighbor=as_neighbor)
|
||||||
|
|
||||||
|
def _create_tab(self, args, cwd_from=None):
|
||||||
|
as_neighbor = False
|
||||||
|
if args and args[0].startswith('!'):
|
||||||
|
as_neighbor = 'neighbor' in args[0][1:].split(',')
|
||||||
|
args = args[1:]
|
||||||
|
self._new_tab(args, as_neighbor=as_neighbor, cwd_from=cwd_from)
|
||||||
|
|
||||||
def new_tab(self, *args):
|
def new_tab(self, *args):
|
||||||
self._new_tab(args)
|
self._create_tab(args)
|
||||||
|
|
||||||
def new_tab_with_cwd(self, *args):
|
def new_tab_with_cwd(self, *args):
|
||||||
w = self.active_window
|
w = self.active_window_for_cwd
|
||||||
cwd_from = w.child.pid if w is not None else None
|
cwd_from = w.child.pid_for_cwd if w is not None else None
|
||||||
self._new_tab(args, cwd_from=cwd_from)
|
self._create_tab(args, cwd_from=cwd_from)
|
||||||
|
|
||||||
def _new_window(self, args, cwd_from=None):
|
def _new_window(self, args, cwd_from=None):
|
||||||
tab = self.active_tab
|
tab = self.active_tab
|
||||||
@@ -793,10 +938,10 @@ class Boss:
|
|||||||
self._new_window(args)
|
self._new_window(args)
|
||||||
|
|
||||||
def new_window_with_cwd(self, *args):
|
def new_window_with_cwd(self, *args):
|
||||||
w = self.active_window
|
w = self.active_window_for_cwd
|
||||||
if w is None:
|
if w is None:
|
||||||
return self.new_window(*args)
|
return self.new_window(*args)
|
||||||
cwd_from = w.child.pid if w is not None else None
|
cwd_from = w.child.pid_for_cwd if w is not None else None
|
||||||
self._new_window(args, cwd_from=cwd_from)
|
self._new_window(args, cwd_from=cwd_from)
|
||||||
|
|
||||||
def move_tab_forward(self):
|
def move_tab_forward(self):
|
||||||
@@ -809,11 +954,20 @@ class Boss:
|
|||||||
if tm is not None:
|
if tm is not None:
|
||||||
tm.move_tab(-1)
|
tm.move_tab(-1)
|
||||||
|
|
||||||
def patch_colors(self, spec, configured=False):
|
def patch_colors(self, spec, cursor_text_color, configured=False):
|
||||||
if configured:
|
if configured:
|
||||||
for k, v in spec.items():
|
for k, v in spec.items():
|
||||||
if hasattr(self.opts, k):
|
if hasattr(self.opts, k):
|
||||||
setattr(self.opts, k, color_from_int(v))
|
setattr(self.opts, k, color_from_int(v))
|
||||||
|
if cursor_text_color is not False:
|
||||||
|
self.opts.cursor_text_color = cursor_text_color
|
||||||
for tm in self.all_tab_managers:
|
for tm in self.all_tab_managers:
|
||||||
tm.tab_bar.patch_colors(spec)
|
tm.tab_bar.patch_colors(spec)
|
||||||
patch_global_colors(spec, configured)
|
patch_global_colors(spec, configured)
|
||||||
|
|
||||||
|
def safe_delete_temp_file(self, path):
|
||||||
|
if is_path_in_temp_dir(path):
|
||||||
|
try:
|
||||||
|
os.remove(path)
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|||||||
@@ -22,8 +22,10 @@ uniform sampler2DArray sprites;
|
|||||||
in float effective_text_alpha;
|
in float effective_text_alpha;
|
||||||
in vec3 sprite_pos;
|
in vec3 sprite_pos;
|
||||||
in vec3 underline_pos;
|
in vec3 underline_pos;
|
||||||
|
in vec3 cursor_pos;
|
||||||
in vec3 strike_pos;
|
in vec3 strike_pos;
|
||||||
in vec3 foreground;
|
in vec3 foreground;
|
||||||
|
in vec4 cursor_color_vec;
|
||||||
in vec3 decoration_fg;
|
in vec3 decoration_fg;
|
||||||
in float colored_sprite;
|
in float colored_sprite;
|
||||||
#endif
|
#endif
|
||||||
@@ -58,17 +60,23 @@ vec4 calculate_foreground() {
|
|||||||
float text_alpha = text_fg.a;
|
float text_alpha = text_fg.a;
|
||||||
float underline_alpha = texture(sprites, underline_pos).a;
|
float underline_alpha = texture(sprites, underline_pos).a;
|
||||||
float strike_alpha = texture(sprites, strike_pos).a;
|
float strike_alpha = texture(sprites, strike_pos).a;
|
||||||
|
float cursor_alpha = texture(sprites, cursor_pos).a;
|
||||||
// Since strike and text are the same color, we simply add the alpha values
|
// Since strike and text are the same color, we simply add the alpha values
|
||||||
float combined_alpha = min(text_alpha + strike_alpha, 1.0f);
|
float combined_alpha = min(text_alpha + strike_alpha, 1.0f);
|
||||||
// Underline color might be different, so alpha blend
|
// Underline color might be different, so alpha blend
|
||||||
return alpha_blend(decoration_fg, underline_alpha * effective_text_alpha, fg, combined_alpha * effective_text_alpha);
|
vec4 ans = alpha_blend(fg, combined_alpha * effective_text_alpha, decoration_fg, underline_alpha * effective_text_alpha);
|
||||||
|
return mix(ans, cursor_color_vec, cursor_alpha);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
#ifdef BACKGROUND
|
#ifdef BACKGROUND
|
||||||
#ifdef TRANSPARENT
|
#ifdef TRANSPARENT
|
||||||
final_color = vec4(background.rgb * bg_alpha, bg_alpha);
|
// bg_alpha is doubled to match rendering in the SIMPLE case
|
||||||
|
// and also rendering of the margin/padding, see https://github.com/kovidgoyal/kitty/pull/1291
|
||||||
|
// to test use background_opacity, window_margin_width and icat to display
|
||||||
|
// an image.
|
||||||
|
final_color = vec4(background.rgb * bg_alpha * bg_alpha, bg_alpha);
|
||||||
#else
|
#else
|
||||||
final_color = vec4(background.rgb, 1.0f);
|
final_color = vec4(background.rgb, 1.0f);
|
||||||
#endif
|
#endif
|
||||||
@@ -98,11 +106,11 @@ void main() {
|
|||||||
// SIMPLE
|
// SIMPLE
|
||||||
#ifdef TRANSPARENT
|
#ifdef TRANSPARENT
|
||||||
final_color = alpha_blend_premul(fg.rgb, fg.a, background * bg_alpha, bg_alpha);
|
final_color = alpha_blend_premul(fg.rgb, fg.a, background * bg_alpha, bg_alpha);
|
||||||
final_color = vec4(final_color.rgb / final_color.a, final_color.a);
|
final_color = vec4(final_color.rgb, final_color.a);
|
||||||
#else
|
#else
|
||||||
// since background alpha is 1.0, it is effectively pre-multiplied
|
// since background alpha is 1.0, it is effectively pre-multiplied
|
||||||
final_color = vec4(premul_blend(fg.rgb, fg.a, background), 1.0f);
|
final_color = vec4(premul_blend(fg.rgb, fg.a, background), 1.0f);
|
||||||
final_color = vec4(final_color.rgb / final_color.a, final_color.a);
|
final_color = vec4(final_color.rgb, final_color.a);
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,12 @@
|
|||||||
|
|
||||||
// Inputs {{{
|
// Inputs {{{
|
||||||
layout(std140) uniform CellRenderData {
|
layout(std140) uniform CellRenderData {
|
||||||
float xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity;
|
float xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity, cursor_text_uses_bg;
|
||||||
|
|
||||||
uint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, url_color, url_style, inverted;
|
uint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, cursor_text_color, url_color, url_style, inverted;
|
||||||
|
|
||||||
uint xnum, ynum, cursor_x, cursor_y, cursor_w;
|
uint xnum, ynum, cursor_fg_sprite_idx;
|
||||||
|
float cursor_x, cursor_y, cursor_w;
|
||||||
|
|
||||||
uint color_table[256];
|
uint color_table[256];
|
||||||
};
|
};
|
||||||
@@ -53,6 +54,8 @@ uniform float inactive_text_alpha;
|
|||||||
uniform float dim_opacity;
|
uniform float dim_opacity;
|
||||||
out vec3 sprite_pos;
|
out vec3 sprite_pos;
|
||||||
out vec3 underline_pos;
|
out vec3 underline_pos;
|
||||||
|
out vec3 cursor_pos;
|
||||||
|
out vec4 cursor_color_vec;
|
||||||
out vec3 strike_pos;
|
out vec3 strike_pos;
|
||||||
out vec3 foreground;
|
out vec3 foreground;
|
||||||
out vec3 decoration_fg;
|
out vec3 decoration_fg;
|
||||||
@@ -110,9 +113,18 @@ vec3 choose_color(float q, vec3 a, vec3 b) {
|
|||||||
return mix(b, a, q);
|
return mix(b, a, q);
|
||||||
}
|
}
|
||||||
|
|
||||||
float is_cursor(uint x, uint y) {
|
float are_integers_equal(float a, float b) { // return 1 iff equal otherwise 0
|
||||||
if (y == cursor_y && (x == cursor_x || x == cursor_w)) return 1.0;
|
float delta = abs(a - b); // delta can be 0, 1 or larger
|
||||||
return 0.0;
|
return step(delta, 0.5); // 0 if 0.5 < delta else 1
|
||||||
|
}
|
||||||
|
|
||||||
|
float is_cursor(uint xi, uint y) {
|
||||||
|
float x = float(xi);
|
||||||
|
float y_equal = are_integers_equal(float(y), cursor_y);
|
||||||
|
float x1_equal = are_integers_equal(x, cursor_x);
|
||||||
|
float x2_equal = are_integers_equal(x, cursor_w);
|
||||||
|
float x_equal = step(0.5, x1_equal + x2_equal);
|
||||||
|
return step(2.0, x_equal + y_equal);
|
||||||
}
|
}
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
@@ -141,7 +153,9 @@ void main() {
|
|||||||
uint is_inverted = ((text_attrs >> REVERSE_SHIFT) & ONE) + inverted;
|
uint is_inverted = ((text_attrs >> REVERSE_SHIFT) & ONE) + inverted;
|
||||||
int fg_index = fg_index_map[is_inverted];
|
int fg_index = fg_index_map[is_inverted];
|
||||||
int bg_index = 1 - fg_index;
|
int bg_index = 1 - fg_index;
|
||||||
float cursor = is_cursor(c, r);
|
float cell_has_cursor = is_cursor(c, r);
|
||||||
|
float is_block_cursor = step(float(cursor_fg_sprite_idx), 0.5);
|
||||||
|
float cell_has_block_cursor = cell_has_cursor * is_block_cursor;
|
||||||
vec3 bg = to_color(colors[bg_index], default_colors[bg_index]);
|
vec3 bg = to_color(colors[bg_index], default_colors[bg_index]);
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
@@ -166,8 +180,11 @@ void main() {
|
|||||||
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) * FOUR, ZERO, ZERO);
|
||||||
|
|
||||||
// Cursor
|
// Cursor
|
||||||
foreground = choose_color(cursor, bg, foreground);
|
cursor_color_vec = vec4(color_to_vec(cursor_color), 1.0);
|
||||||
decoration_fg = choose_color(cursor, bg, decoration_fg);
|
vec3 final_cursor_text_color = mix(color_to_vec(cursor_text_color), bg, cursor_text_uses_bg);
|
||||||
|
foreground = choose_color(cell_has_block_cursor, final_cursor_text_color, foreground);
|
||||||
|
decoration_fg = choose_color(cell_has_block_cursor, final_cursor_text_color, decoration_fg);
|
||||||
|
cursor_pos = to_sprite_pos(pos, cursor_fg_sprite_idx * uint(cell_has_cursor), ZERO, ZERO);
|
||||||
#endif
|
#endif
|
||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
@@ -182,17 +199,17 @@ void main() {
|
|||||||
// If the background color is default, set its opacity to background_opacity, otherwise it should be opaque
|
// If the background color is default, set its opacity to background_opacity, otherwise it should be opaque
|
||||||
bg_alpha = step(0.5, float(colors[bg_index] & BYTE_MASK));
|
bg_alpha = step(0.5, float(colors[bg_index] & BYTE_MASK));
|
||||||
// Cursor must not be affected by background_opacity
|
// Cursor must not be affected by background_opacity
|
||||||
bg_alpha = mix(bg_alpha, 1.0, cursor);
|
bg_alpha = mix(bg_alpha, 1.0, cell_has_block_cursor);
|
||||||
bg_alpha = bg_alpha + (1.0f - bg_alpha) * background_opacity;
|
bg_alpha = bg_alpha + (1.0f - bg_alpha) * background_opacity;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(SPECIAL) || defined(SIMPLE)
|
#if defined(SPECIAL) || defined(SIMPLE)
|
||||||
// Selection and cursor
|
// Selection and cursor
|
||||||
bg = choose_color(float(is_selected & ONE), color_to_vec(highlight_bg), bg);
|
bg = choose_color(float(is_selected & ONE), color_to_vec(highlight_bg), bg);
|
||||||
background = choose_color(cursor, color_to_vec(cursor_color), bg);
|
background = choose_color(cell_has_block_cursor, color_to_vec(cursor_color), bg);
|
||||||
#ifdef SPECIAL
|
#ifdef SPECIAL
|
||||||
// bg_alpha should be 1 if cursor/selection otherwise 0
|
// bg_alpha should be 1 if cursor/selection otherwise 0
|
||||||
bg_alpha = mix(0.0, 1.0, step(0.5, float(is_selected & ONE) + cursor));
|
bg_alpha = mix(0.0, 1.0, step(0.5, float(is_selected & ONE) + cell_has_block_cursor));
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ extern PyTypeObject Screen_Type;
|
|||||||
#define MSG_NOSIGNAL 0
|
#define MSG_NOSIGNAL 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static void (*parse_func)(Screen*, PyObject*);
|
static void (*parse_func)(Screen*, PyObject*, double);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char *data;
|
char *data;
|
||||||
@@ -246,9 +246,18 @@ add_child(ChildMonitor *self, PyObject *args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
schedule_write_to_child(unsigned long id, const char *data, size_t sz) {
|
schedule_write_to_child(unsigned long id, unsigned int num, ...) {
|
||||||
ChildMonitor *self = the_monitor;
|
ChildMonitor *self = the_monitor;
|
||||||
bool found = false;
|
bool found = false;
|
||||||
|
const char *data;
|
||||||
|
size_t sz = 0;
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, num);
|
||||||
|
for (unsigned int i = 0; i < num; i++) {
|
||||||
|
data = va_arg(ap, const char*);
|
||||||
|
sz += va_arg(ap, size_t);
|
||||||
|
}
|
||||||
|
va_end(ap);
|
||||||
children_mutex(lock);
|
children_mutex(lock);
|
||||||
for (size_t i = 0; i < self->count; i++) {
|
for (size_t i = 0; i < self->count; i++) {
|
||||||
if (children[i].id == id) {
|
if (children[i].id == id) {
|
||||||
@@ -266,8 +275,14 @@ schedule_write_to_child(unsigned long id, const char *data, size_t sz) {
|
|||||||
screen->write_buf = PyMem_RawRealloc(screen->write_buf, screen->write_buf_sz);
|
screen->write_buf = PyMem_RawRealloc(screen->write_buf, screen->write_buf_sz);
|
||||||
if (screen->write_buf == NULL) { fatal("Out of memory."); }
|
if (screen->write_buf == NULL) { fatal("Out of memory."); }
|
||||||
}
|
}
|
||||||
memcpy(screen->write_buf + screen->write_buf_used, data, sz);
|
va_start(ap, num);
|
||||||
screen->write_buf_used += sz;
|
for (unsigned int i = 0; i < num; i++) {
|
||||||
|
data = va_arg(ap, const char*);
|
||||||
|
size_t dsz = va_arg(ap, size_t);
|
||||||
|
memcpy(screen->write_buf + screen->write_buf_used, data, dsz);
|
||||||
|
screen->write_buf_used += dsz;
|
||||||
|
}
|
||||||
|
va_end(ap);
|
||||||
if (screen->write_buf_sz > BUFSIZ && screen->write_buf_used < BUFSIZ) {
|
if (screen->write_buf_sz > BUFSIZ && screen->write_buf_used < BUFSIZ) {
|
||||||
screen->write_buf_sz = BUFSIZ;
|
screen->write_buf_sz = BUFSIZ;
|
||||||
screen->write_buf = PyMem_RawRealloc(screen->write_buf, screen->write_buf_sz);
|
screen->write_buf = PyMem_RawRealloc(screen->write_buf, screen->write_buf_sz);
|
||||||
@@ -288,7 +303,7 @@ needs_write(ChildMonitor UNUSED *self, PyObject *args) {
|
|||||||
unsigned long id, sz;
|
unsigned long id, sz;
|
||||||
const char *data;
|
const char *data;
|
||||||
if (!PyArg_ParseTuple(args, "ks#", &id, &data, &sz)) return NULL;
|
if (!PyArg_ParseTuple(args, "ks#", &id, &data, &sz)) return NULL;
|
||||||
if (schedule_write_to_child(id, data, sz)) { Py_RETURN_TRUE; }
|
if (schedule_write_to_child(id, 1, data, (size_t)sz)) { Py_RETURN_TRUE; }
|
||||||
Py_RETURN_FALSE;
|
Py_RETURN_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,13 +329,17 @@ shutdown_monitor(ChildMonitor *self, PyObject *a UNUSED) {
|
|||||||
static inline void
|
static inline void
|
||||||
do_parse(ChildMonitor *self, Screen *screen, double now) {
|
do_parse(ChildMonitor *self, Screen *screen, double now) {
|
||||||
screen_mutex(lock, read);
|
screen_mutex(lock, read);
|
||||||
if (screen->read_buf_sz) {
|
if (screen->read_buf_sz || screen->pending_mode.used) {
|
||||||
double time_since_new_input = now - screen->new_input_at;
|
double time_since_new_input = now - screen->new_input_at;
|
||||||
if (time_since_new_input >= OPT(input_delay)) {
|
if (time_since_new_input >= OPT(input_delay)) {
|
||||||
parse_func(screen, self->dump_callback);
|
bool read_buf_full = screen->read_buf_sz >= READ_BUF_SZ;
|
||||||
if (screen->read_buf_sz >= READ_BUF_SZ) wakeup_io_loop(false); // Ensure the read fd has POLLIN set
|
parse_func(screen, self->dump_callback, now);
|
||||||
screen->read_buf_sz = 0;
|
if (read_buf_full) wakeup_io_loop(false); // Ensure the read fd has POLLIN set
|
||||||
screen->new_input_at = 0;
|
screen->new_input_at = 0;
|
||||||
|
if (screen->pending_mode.activated_at) {
|
||||||
|
double time_since_pending = MAX(0, now - screen->pending_mode.activated_at);
|
||||||
|
set_maximum_wait(screen->pending_mode.wait_time - time_since_pending);
|
||||||
|
}
|
||||||
} else set_maximum_wait(OPT(input_delay) - time_since_new_input);
|
} else set_maximum_wait(OPT(input_delay) - time_since_new_input);
|
||||||
}
|
}
|
||||||
screen_mutex(unlock, read);
|
screen_mutex(unlock, read);
|
||||||
@@ -353,7 +372,7 @@ parse_input(ChildMonitor *self) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (UNLIKELY(kill_signal_received)) {
|
if (UNLIKELY(kill_signal_received)) {
|
||||||
global_state.close_all_windows = true;
|
global_state.terminate = true;
|
||||||
} else {
|
} else {
|
||||||
count = self->count;
|
count = self->count;
|
||||||
for (size_t i = 0; i < count; i++) {
|
for (size_t i = 0; i < count; i++) {
|
||||||
@@ -491,14 +510,6 @@ pyset_iutf8(ChildMonitor *self, PyObject *args) {
|
|||||||
|
|
||||||
static double last_render_at = -DBL_MAX;
|
static double last_render_at = -DBL_MAX;
|
||||||
|
|
||||||
static inline double
|
|
||||||
cursor_width(double w, bool vert, OSWindow *os_window) {
|
|
||||||
double dpi = vert ? os_window->fonts_data->logical_dpi_x : os_window->fonts_data->logical_dpi_y;
|
|
||||||
double ans = w * dpi / 72.0; // as pixels
|
|
||||||
double factor = 2.0 / (vert ? os_window->viewport_width : os_window->viewport_height);
|
|
||||||
return ans * factor;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern void cocoa_update_title(PyObject*);
|
extern void cocoa_update_title(PyObject*);
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
@@ -526,23 +537,6 @@ collect_cursor_info(CursorRenderInfo *ans, Window *w, double now, OSWindow *os_w
|
|||||||
ans->shape = cursor->shape ? cursor->shape : OPT(cursor_shape);
|
ans->shape = cursor->shape ? cursor->shape : OPT(cursor_shape);
|
||||||
ans->color = colorprofile_to_color(cp, cp->overridden.cursor_color, cp->configured.cursor_color);
|
ans->color = colorprofile_to_color(cp, cp->overridden.cursor_color, cp->configured.cursor_color);
|
||||||
ans->is_focused = os_window->is_focused;
|
ans->is_focused = os_window->is_focused;
|
||||||
if (ans->shape == CURSOR_BLOCK && ans->is_focused) return;
|
|
||||||
double left = rd->xstart + cursor->x * rd->dx;
|
|
||||||
double top = rd->ystart - cursor->y * rd->dy;
|
|
||||||
unsigned long mult = MAX(1, screen_current_char_width(rd->screen));
|
|
||||||
double right = left + (ans->shape == CURSOR_BEAM ? cursor_width(1.5, true, os_window) : rd->dx * mult);
|
|
||||||
double bottom = top - rd->dy;
|
|
||||||
switch (ans->shape) {
|
|
||||||
case CURSOR_UNDERLINE:
|
|
||||||
top = bottom + cursor_width(2.0, false, os_window);
|
|
||||||
break;
|
|
||||||
case CURSOR_BLOCK:
|
|
||||||
top -= 2.0 / os_window->viewport_height; // 1px adjustment for width of line
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ans->left = left; ans->right = right; ans->top = top; ans->bottom = bottom;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
@@ -571,7 +565,10 @@ prepare_to_render_os_window(OSWindow *os_window, double now, unsigned int *activ
|
|||||||
}
|
}
|
||||||
if (send_cell_data_to_gpu(TD.vao_idx, 0, TD.xstart, TD.ystart, TD.dx, TD.dy, TD.screen, os_window)) needs_render = true;
|
if (send_cell_data_to_gpu(TD.vao_idx, 0, TD.xstart, TD.ystart, TD.dx, TD.dy, TD.screen, os_window)) needs_render = true;
|
||||||
}
|
}
|
||||||
if (OPT(mouse_hide_wait) > 0 && now - os_window->last_mouse_activity_at > OPT(mouse_hide_wait)) hide_mouse(os_window);
|
if (OPT(mouse_hide_wait) > 0 && !is_mouse_hidden(os_window)) {
|
||||||
|
if (now - os_window->last_mouse_activity_at >= OPT(mouse_hide_wait)) hide_mouse(os_window);
|
||||||
|
else set_maximum_wait(OPT(mouse_hide_wait) - now + os_window->last_mouse_activity_at);
|
||||||
|
}
|
||||||
Tab *tab = os_window->tabs + os_window->active_tab;
|
Tab *tab = os_window->tabs + os_window->active_tab;
|
||||||
*active_window_bg = OPT(background);
|
*active_window_bg = OPT(background);
|
||||||
for (unsigned int i = 0; i < tab->num_windows; i++) {
|
for (unsigned int i = 0; i < tab->num_windows; i++) {
|
||||||
@@ -605,6 +602,8 @@ prepare_to_render_os_window(OSWindow *os_window, double now, unsigned int *activ
|
|||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
render_os_window(OSWindow *os_window, double now, unsigned int active_window_id, color_type active_window_bg, unsigned int num_visible_windows) {
|
render_os_window(OSWindow *os_window, double now, unsigned int active_window_id, color_type active_window_bg, unsigned int num_visible_windows) {
|
||||||
|
// ensure all pixels are cleared to background color at least once in every buffer
|
||||||
|
if (os_window->clear_count++ < 3) blank_os_window(os_window);
|
||||||
Tab *tab = os_window->tabs + os_window->active_tab;
|
Tab *tab = os_window->tabs + os_window->active_tab;
|
||||||
BorderRects *br = &tab->border_rects;
|
BorderRects *br = &tab->border_rects;
|
||||||
draw_borders(br->vao_idx, br->num_border_rects, br->rect_buf, br->is_dirty, os_window->viewport_width, os_window->viewport_height, active_window_bg, num_visible_windows, os_window);
|
draw_borders(br->vao_idx, br->num_border_rects, br->rect_buf, br->is_dirty, os_window->viewport_width, os_window->viewport_height, active_window_bg, num_visible_windows, os_window);
|
||||||
@@ -614,9 +613,6 @@ render_os_window(OSWindow *os_window, double now, unsigned int active_window_id,
|
|||||||
if (w->visible && WD.screen) {
|
if (w->visible && WD.screen) {
|
||||||
bool is_active_window = i == tab->active_window;
|
bool is_active_window = i == tab->active_window;
|
||||||
draw_cells(WD.vao_idx, WD.gvao_idx, WD.xstart, WD.ystart, WD.dx, WD.dy, WD.screen, os_window, is_active_window, true);
|
draw_cells(WD.vao_idx, WD.gvao_idx, WD.xstart, WD.ystart, WD.dx, WD.dy, WD.screen, os_window, is_active_window, true);
|
||||||
if (is_active_window && WD.screen->cursor_render_info.is_visible && (!WD.screen->cursor_render_info.is_focused || WD.screen->cursor_render_info.shape != CURSOR_BLOCK)) {
|
|
||||||
draw_cursor(&WD.screen->cursor_render_info, os_window->is_focused);
|
|
||||||
}
|
|
||||||
if (WD.screen->start_visual_bell_at != 0) {
|
if (WD.screen->start_visual_bell_at != 0) {
|
||||||
double bell_left = global_state.opts.visual_bell_duration - (now - WD.screen->start_visual_bell_at);
|
double bell_left = global_state.opts.visual_bell_duration - (now - WD.screen->start_visual_bell_at);
|
||||||
set_maximum_wait(bell_left);
|
set_maximum_wait(bell_left);
|
||||||
@@ -629,6 +625,7 @@ render_os_window(OSWindow *os_window, double now, unsigned int active_window_id,
|
|||||||
os_window->last_active_tab = os_window->active_tab; os_window->last_num_tabs = os_window->num_tabs; os_window->last_active_window_id = active_window_id;
|
os_window->last_active_tab = os_window->active_tab; os_window->last_num_tabs = os_window->num_tabs; os_window->last_active_window_id = active_window_id;
|
||||||
os_window->focused_at_last_render = os_window->is_focused;
|
os_window->focused_at_last_render = os_window->is_focused;
|
||||||
os_window->is_damaged = false;
|
os_window->is_damaged = false;
|
||||||
|
if (global_state.is_wayland && OPT(sync_to_monitor)) wayland_request_frame_render(os_window);
|
||||||
#undef WD
|
#undef WD
|
||||||
#undef TD
|
#undef TD
|
||||||
}
|
}
|
||||||
@@ -644,6 +641,10 @@ render(double now) {
|
|||||||
for (size_t i = 0; i < global_state.num_os_windows; i++) {
|
for (size_t i = 0; i < global_state.num_os_windows; i++) {
|
||||||
OSWindow *w = global_state.os_windows + i;
|
OSWindow *w = global_state.os_windows + i;
|
||||||
if (!w->num_tabs || !should_os_window_be_rendered(w)) continue;
|
if (!w->num_tabs || !should_os_window_be_rendered(w)) continue;
|
||||||
|
if (global_state.is_wayland && w->wayland_render_state != RENDER_FRAME_READY && OPT(sync_to_monitor)) {
|
||||||
|
if (w->wayland_render_state == RENDER_FRAME_NOT_REQUESTED) wayland_request_frame_render(w);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
bool needs_render = w->is_damaged;
|
bool needs_render = w->is_damaged;
|
||||||
make_os_window_context_current(w);
|
make_os_window_context_current(w);
|
||||||
if (w->viewport_size_dirty) {
|
if (w->viewport_size_dirty) {
|
||||||
@@ -736,7 +737,6 @@ process_pending_resizes(double now) {
|
|||||||
static inline void
|
static inline void
|
||||||
close_all_windows() {
|
close_all_windows() {
|
||||||
for (size_t w = 0; w < global_state.num_os_windows; w++) mark_os_window_for_close(&global_state.os_windows[w], true);
|
for (size_t w = 0; w < global_state.num_os_windows; w++) mark_os_window_for_close(&global_state.os_windows[w], true);
|
||||||
global_state.close_all_windows = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
@@ -771,6 +771,9 @@ static unsigned int cocoa_pending_actions = 0;
|
|||||||
void
|
void
|
||||||
set_cocoa_pending_action(CocoaPendingAction action) {
|
set_cocoa_pending_action(CocoaPendingAction action) {
|
||||||
cocoa_pending_actions |= action;
|
cocoa_pending_actions |= action;
|
||||||
|
// The main loop may be blocking on the event queue, if e.g. unfocused.
|
||||||
|
// Unjam it so the pending action is processed right now.
|
||||||
|
unjam_event_loop();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -792,7 +795,13 @@ main_loop(ChildMonitor *self, PyObject *a UNUSED) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
parse_input(self);
|
parse_input(self);
|
||||||
if (global_state.close_all_windows) close_all_windows();
|
if (global_state.terminate) {
|
||||||
|
global_state.terminate = false;
|
||||||
|
close_all_windows();
|
||||||
|
#ifdef __APPLE__
|
||||||
|
request_application_quit();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
has_open_windows = process_pending_closes(self);
|
has_open_windows = process_pending_closes(self);
|
||||||
}
|
}
|
||||||
if (PyErr_Occurred()) return NULL;
|
if (PyErr_Occurred()) return NULL;
|
||||||
@@ -873,7 +882,7 @@ read_bytes(int fd, Screen *screen) {
|
|||||||
while(true) {
|
while(true) {
|
||||||
len = read(fd, screen->read_buf + orig_sz, available_buffer_space);
|
len = read(fd, screen->read_buf + orig_sz, available_buffer_space);
|
||||||
if (len < 0) {
|
if (len < 0) {
|
||||||
if (errno == EINTR) continue;
|
if (errno == EINTR || errno == EAGAIN) continue;
|
||||||
if (errno != EIO) perror("Call to read() from child fd failed");
|
if (errno != EIO) perror("Call to read() from child fd failed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -976,7 +985,12 @@ write_to_child(int fd, Screen *screen) {
|
|||||||
written = screen->write_buf_used;
|
written = screen->write_buf_used;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
screen->write_buf_used -= written;
|
if (written) {
|
||||||
|
screen->write_buf_used -= written;
|
||||||
|
if (screen->write_buf_used) {
|
||||||
|
memmove(screen->write_buf, screen->write_buf + written, screen->write_buf_used);
|
||||||
|
}
|
||||||
|
}
|
||||||
screen_mutex(unlock, write);
|
screen_mutex(unlock, write);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -985,7 +999,8 @@ io_loop(void *data) {
|
|||||||
// The I/O thread loop
|
// The I/O thread loop
|
||||||
size_t i;
|
size_t i;
|
||||||
int ret;
|
int ret;
|
||||||
bool has_more, data_received;
|
bool has_more, data_received, has_pending_wakeups = false;
|
||||||
|
double last_main_loop_wakeup_at = -1, now = -1;
|
||||||
Screen *screen;
|
Screen *screen;
|
||||||
ChildMonitor *self = (ChildMonitor*)data;
|
ChildMonitor *self = (ChildMonitor*)data;
|
||||||
set_thread_name("KittyChildMon");
|
set_thread_name("KittyChildMon");
|
||||||
@@ -1004,7 +1019,14 @@ io_loop(void *data) {
|
|||||||
fds[EXTRA_FDS + i].events = (screen->read_buf_sz < READ_BUF_SZ ? POLLIN : 0) | (screen->write_buf_used ? POLLOUT : 0);
|
fds[EXTRA_FDS + i].events = (screen->read_buf_sz < READ_BUF_SZ ? POLLIN : 0) | (screen->write_buf_used ? POLLOUT : 0);
|
||||||
screen_mutex(unlock, read); screen_mutex(unlock, write);
|
screen_mutex(unlock, read); screen_mutex(unlock, write);
|
||||||
}
|
}
|
||||||
ret = poll(fds, self->count + EXTRA_FDS, -1);
|
if (has_pending_wakeups) {
|
||||||
|
now = monotonic();
|
||||||
|
double time_delta = OPT(input_delay) - (now - last_main_loop_wakeup_at);
|
||||||
|
if (time_delta >= 0) ret = poll(fds, self->count + EXTRA_FDS, (int)ceil(1000 * time_delta));
|
||||||
|
else ret = 0;
|
||||||
|
} else {
|
||||||
|
ret = poll(fds, self->count + EXTRA_FDS, -1);
|
||||||
|
}
|
||||||
if (ret > 0) {
|
if (ret > 0) {
|
||||||
if (fds[0].revents && POLLIN) drain_fd(fds[0].fd); // wakeup
|
if (fds[0].revents && POLLIN) drain_fd(fds[0].fd); // wakeup
|
||||||
if (fds[1].revents && POLLIN) {
|
if (fds[1].revents && POLLIN) {
|
||||||
@@ -1048,8 +1070,17 @@ io_loop(void *data) {
|
|||||||
perror("Call to poll() failed");
|
perror("Call to poll() failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (data_received) wakeup_main_loop();
|
#define WAKEUP { wakeup_main_loop(); last_main_loop_wakeup_at = now; has_pending_wakeups = false; }
|
||||||
|
// we only wakeup the main loop after input_delay as wakeup is an expensive operation
|
||||||
|
// on some platforms, such as cocoa
|
||||||
|
if (data_received) {
|
||||||
|
if ((now = monotonic()) - last_main_loop_wakeup_at > OPT(input_delay)) WAKEUP
|
||||||
|
else has_pending_wakeups = true;
|
||||||
|
} else {
|
||||||
|
if (has_pending_wakeups && (now = monotonic()) - last_main_loop_wakeup_at > OPT(input_delay)) WAKEUP
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
#undef WAKEUP
|
||||||
children_mutex(lock);
|
children_mutex(lock);
|
||||||
for (i = 0; i < self->count; i++) children[i].needs_removal = true;
|
for (i = 0; i < self->count; i++) children[i].needs_removal = true;
|
||||||
remove_children(self);
|
remove_children(self);
|
||||||
@@ -1060,9 +1091,6 @@ io_loop(void *data) {
|
|||||||
|
|
||||||
// {{{ Talk thread functions
|
// {{{ Talk thread functions
|
||||||
|
|
||||||
#define MAX_PEERS 256
|
|
||||||
#define MAX_LISTENERS 2
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char *data;
|
char *data;
|
||||||
size_t capacity, used;
|
size_t capacity, used;
|
||||||
@@ -1081,15 +1109,18 @@ static PeerWriteData empty_pwd = {.fd = -1, 0};
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
size_t num_listen_fds, num_talk_fds, num_reads, num_writes, num_queued_writes;
|
size_t num_listen_fds, num_talk_fds, num_reads, num_writes, num_queued_writes;
|
||||||
struct pollfd fds[MAX_PEERS + MAX_LISTENERS + 1];
|
size_t fds_capacity, reads_capacity, writes_capacity, queued_writes_capacity;
|
||||||
PeerReadData reads[MAX_LISTENERS];
|
struct pollfd *fds;
|
||||||
PeerWriteData writes[MAX_LISTENERS];
|
PeerReadData *reads;
|
||||||
PeerWriteData queued_writes[MAX_LISTENERS];
|
PeerWriteData *writes;
|
||||||
|
PeerWriteData *queued_writes;
|
||||||
int wakeup_fds[2];
|
int wakeup_fds[2];
|
||||||
pthread_mutex_t peer_lock;
|
pthread_mutex_t peer_lock;
|
||||||
} TalkData;
|
} TalkData;
|
||||||
|
|
||||||
static TalkData talk_data = {0};
|
static TalkData talk_data = {0};
|
||||||
|
typedef struct pollfd PollFD;
|
||||||
|
#define PEER_LIMIT 256
|
||||||
#define nuke_socket(s) { shutdown(s, SHUT_RDWR); close(s); }
|
#define nuke_socket(s) { shutdown(s, SHUT_RDWR); close(s); }
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
@@ -1101,11 +1132,16 @@ accept_peer(int listen_fd, bool shutting_down) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
size_t fd_idx = talk_data.num_listen_fds + talk_data.num_talk_fds;
|
size_t fd_idx = talk_data.num_listen_fds + talk_data.num_talk_fds;
|
||||||
if (fd_idx < arraysz(talk_data.fds) && talk_data.num_reads < arraysz(talk_data.reads)) {
|
if (fd_idx < PEER_LIMIT && talk_data.reads_capacity < PEER_LIMIT) {
|
||||||
|
ensure_space_for(&talk_data, fds, PollFD, fd_idx + 1, fds_capacity, 8, false);
|
||||||
talk_data.fds[fd_idx].fd = peer; talk_data.fds[fd_idx].events = POLLIN;
|
talk_data.fds[fd_idx].fd = peer; talk_data.fds[fd_idx].events = POLLIN;
|
||||||
|
ensure_space_for(&talk_data, reads, PeerReadData, talk_data.num_reads + 1, reads_capacity, 8, false);
|
||||||
talk_data.reads[talk_data.num_reads] = empty_prd; talk_data.reads[talk_data.num_reads++].fd = peer;
|
talk_data.reads[talk_data.num_reads] = empty_prd; talk_data.reads[talk_data.num_reads++].fd = peer;
|
||||||
talk_data.num_talk_fds++;
|
talk_data.num_talk_fds++;
|
||||||
} else nuke_socket(peer);
|
} else {
|
||||||
|
log_error("Too many peers want to talk, ignoring one.");
|
||||||
|
nuke_socket(peer);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1234,8 +1270,10 @@ move_queued_writes() {
|
|||||||
while (talk_data.num_queued_writes) {
|
while (talk_data.num_queued_writes) {
|
||||||
PeerWriteData *src = talk_data.queued_writes + --talk_data.num_queued_writes;
|
PeerWriteData *src = talk_data.queued_writes + --talk_data.num_queued_writes;
|
||||||
size_t fd_idx = talk_data.num_listen_fds + talk_data.num_talk_fds;
|
size_t fd_idx = talk_data.num_listen_fds + talk_data.num_talk_fds;
|
||||||
if (fd_idx < arraysz(talk_data.fds) && talk_data.num_writes < arraysz(talk_data.writes)) {
|
if (fd_idx < PEER_LIMIT && talk_data.num_writes < PEER_LIMIT) {
|
||||||
|
ensure_space_for(&talk_data, fds, PollFD, fd_idx + 1, fds_capacity, 8, false);
|
||||||
talk_data.fds[fd_idx].fd = src->fd; talk_data.fds[fd_idx].events = POLLOUT;
|
talk_data.fds[fd_idx].fd = src->fd; talk_data.fds[fd_idx].events = POLLOUT;
|
||||||
|
ensure_space_for(&talk_data, writes, PeerWriteData, talk_data.num_writes + 1, writes_capacity, 8, false);
|
||||||
talk_data.writes[talk_data.num_writes++] = *src;
|
talk_data.writes[talk_data.num_writes++] = *src;
|
||||||
talk_data.num_talk_fds++;
|
talk_data.num_talk_fds++;
|
||||||
} else {
|
} else {
|
||||||
@@ -1254,6 +1292,7 @@ talk_loop(void *data) {
|
|||||||
set_thread_name("KittyPeerMon");
|
set_thread_name("KittyPeerMon");
|
||||||
if ((pthread_mutex_init(&talk_data.peer_lock, NULL)) != 0) { perror("Failed to create peer mutex"); return 0; }
|
if ((pthread_mutex_init(&talk_data.peer_lock, NULL)) != 0) { perror("Failed to create peer mutex"); return 0; }
|
||||||
if (!self_pipe(talk_data.wakeup_fds)) { perror("Failed to create wakeup fds for talk thread"); return 0; }
|
if (!self_pipe(talk_data.wakeup_fds)) { perror("Failed to create wakeup fds for talk thread"); return 0; }
|
||||||
|
ensure_space_for(&talk_data, fds, PollFD, 8, fds_capacity, 8, false);
|
||||||
#define add_listener(which) \
|
#define add_listener(which) \
|
||||||
if (self->which > -1) { \
|
if (self->which > -1) { \
|
||||||
talk_data.fds[talk_data.num_listen_fds].fd = self->which; talk_data.fds[talk_data.num_listen_fds++].events = POLLIN; \
|
talk_data.fds[talk_data.num_listen_fds].fd = self->which; talk_data.fds[talk_data.num_listen_fds++].events = POLLIN; \
|
||||||
@@ -1284,6 +1323,7 @@ talk_loop(void *data) {
|
|||||||
}
|
}
|
||||||
end:
|
end:
|
||||||
close(talk_data.wakeup_fds[0]); close(talk_data.wakeup_fds[1]);
|
close(talk_data.wakeup_fds[0]); close(talk_data.wakeup_fds[1]);
|
||||||
|
free(talk_data.fds); free(talk_data.reads); free(talk_data.writes); free(talk_data.queued_writes);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1291,7 +1331,8 @@ static inline bool
|
|||||||
add_peer_writer(int fd, const char* msg, size_t msg_sz) {
|
add_peer_writer(int fd, const char* msg, size_t msg_sz) {
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
peer_mutex(lock);
|
peer_mutex(lock);
|
||||||
if (talk_data.num_queued_writes < arraysz(talk_data.queued_writes)) {
|
if (talk_data.num_queued_writes < PEER_LIMIT) {
|
||||||
|
ensure_space_for(&talk_data, queued_writes, PeerWriteData, talk_data.num_queued_writes + 1, queued_writes_capacity, 8, false);
|
||||||
talk_data.queued_writes[talk_data.num_queued_writes] = empty_pwd;
|
talk_data.queued_writes[talk_data.num_queued_writes] = empty_pwd;
|
||||||
talk_data.queued_writes[talk_data.num_queued_writes].data = malloc(msg_sz);
|
talk_data.queued_writes[talk_data.num_queued_writes].data = malloc(msg_sz);
|
||||||
if (talk_data.queued_writes[talk_data.num_queued_writes].data) {
|
if (talk_data.queued_writes[talk_data.num_queued_writes].data) {
|
||||||
|
|||||||
@@ -11,10 +11,12 @@
|
|||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
static inline char**
|
static inline char**
|
||||||
serialize_string_tuple(PyObject *src) {
|
serialize_string_tuple(PyObject *src) {
|
||||||
Py_ssize_t sz = PyTuple_GET_SIZE(src);
|
Py_ssize_t sz = PyTuple_GET_SIZE(src);
|
||||||
|
|
||||||
char **ans = calloc(sz + 1, sizeof(char*));
|
char **ans = calloc(sz + 1, sizeof(char*));
|
||||||
if (!ans) fatal("Out of memory");
|
if (!ans) fatal("Out of memory");
|
||||||
for (Py_ssize_t i = 0; i < sz; i++) {
|
for (Py_ssize_t i = 0; i < sz; i++) {
|
||||||
@@ -27,6 +29,13 @@ serialize_string_tuple(PyObject *src) {
|
|||||||
return ans;
|
return ans;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
free_string_tuple(char** data) {
|
||||||
|
size_t i = 0;
|
||||||
|
while(data[i]) free(data[i++]);
|
||||||
|
free(data);
|
||||||
|
}
|
||||||
|
|
||||||
extern char **environ;
|
extern char **environ;
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
@@ -44,18 +53,29 @@ write_to_stderr(const char *text) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define exit_on_err(m) { write_to_stderr(m); write_to_stderr(": "); write_to_stderr(strerror(errno)); exit(EXIT_FAILURE); }
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
wait_for_terminal_ready(int fd) {
|
||||||
|
char data;
|
||||||
|
while(1) {
|
||||||
|
int ret = read(fd, &data, 1);
|
||||||
|
if (ret == -1 && (errno == EINTR || errno == EAGAIN)) continue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
spawn(PyObject *self UNUSED, PyObject *args) {
|
spawn(PyObject *self UNUSED, PyObject *args) {
|
||||||
PyObject *argv_p, *env_p;
|
PyObject *argv_p, *env_p;
|
||||||
int master, slave, stdin_read_fd, stdin_write_fd;
|
int master, slave, stdin_read_fd, stdin_write_fd, ready_read_fd, ready_write_fd;
|
||||||
char *cwd, *exe;
|
char *cwd, *exe;
|
||||||
if (!PyArg_ParseTuple(args, "ssO!O!iiii", &exe, &cwd, &PyTuple_Type, &argv_p, &PyTuple_Type, &env_p, &master, &slave, &stdin_read_fd, &stdin_write_fd)) return NULL;
|
if (!PyArg_ParseTuple(args, "ssO!O!iiiiii", &exe, &cwd, &PyTuple_Type, &argv_p, &PyTuple_Type, &env_p, &master, &slave, &stdin_read_fd, &stdin_write_fd, &ready_read_fd, &ready_write_fd)) return NULL;
|
||||||
char name[2048] = {0};
|
char name[2048] = {0};
|
||||||
if (ttyname_r(slave, name, sizeof(name) - 1) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
if (ttyname_r(slave, name, sizeof(name) - 1) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
||||||
char **argv = serialize_string_tuple(argv_p);
|
char **argv = serialize_string_tuple(argv_p);
|
||||||
char **env = serialize_string_tuple(env_p);
|
char **env = serialize_string_tuple(env_p);
|
||||||
|
|
||||||
#define exit_on_err(m) { write_to_stderr(m); write_to_stderr(": "); write_to_stderr(strerror(errno)); exit(EXIT_FAILURE); }
|
|
||||||
pid_t pid = fork();
|
pid_t pid = fork();
|
||||||
switch(pid) {
|
switch(pid) {
|
||||||
case 0:
|
case 0:
|
||||||
@@ -63,6 +83,17 @@ spawn(PyObject *self UNUSED, PyObject *args) {
|
|||||||
// Use only signal-safe functions (man 7 signal-safety)
|
// Use only signal-safe functions (man 7 signal-safety)
|
||||||
if (chdir(cwd) != 0) { if (chdir("/") != 0) {} }; // ignore failure to chdir to /
|
if (chdir(cwd) != 0) { if (chdir("/") != 0) {} }; // ignore failure to chdir to /
|
||||||
if (setsid() == -1) exit_on_err("setsid() in child process failed");
|
if (setsid() == -1) exit_on_err("setsid() in child process failed");
|
||||||
|
|
||||||
|
// Establish the controlling terminal (see man 7 credentials)
|
||||||
|
int tfd = open(name, O_RDWR);
|
||||||
|
if (tfd == -1) exit_on_err("Failed to open controlling terminal");
|
||||||
|
#ifdef TIOCSTTY
|
||||||
|
// On BSD open() does not establish the controlling terminal
|
||||||
|
if (ioctl(tfd, TIOCSCTTY, 0) == -1) exit_on_err("Failed to set controlling terminal with TIOCSCTTY");
|
||||||
|
#endif
|
||||||
|
close(tfd);
|
||||||
|
|
||||||
|
// Redirect stdin/stdout/stderr to the pty
|
||||||
if (dup2(slave, 1) == -1) exit_on_err("dup2() failed for fd number 1");
|
if (dup2(slave, 1) == -1) exit_on_err("dup2() failed for fd number 1");
|
||||||
if (dup2(slave, 2) == -1) exit_on_err("dup2() failed for fd number 2");
|
if (dup2(slave, 2) == -1) exit_on_err("dup2() failed for fd number 2");
|
||||||
if (stdin_read_fd > -1) {
|
if (stdin_read_fd > -1) {
|
||||||
@@ -74,14 +105,19 @@ spawn(PyObject *self UNUSED, PyObject *args) {
|
|||||||
}
|
}
|
||||||
close(slave);
|
close(slave);
|
||||||
close(master);
|
close(master);
|
||||||
|
|
||||||
|
// Wait for READY_SIGNAL which indicates kitty has setup the screen object
|
||||||
|
close(ready_write_fd);
|
||||||
|
wait_for_terminal_ready(ready_read_fd);
|
||||||
|
close(ready_read_fd);
|
||||||
|
|
||||||
|
// Close any extra fds inherited from parent
|
||||||
for (int c = 3; c < 201; c++) close(c);
|
for (int c = 3; c < 201; c++) close(c);
|
||||||
|
|
||||||
// Establish the controlling terminal (see man 7 credentials)
|
|
||||||
int tfd = open(name, O_RDWR);
|
|
||||||
if (tfd == -1) exit_on_err("Failed to open controlling terminal");
|
|
||||||
close(tfd);
|
|
||||||
|
|
||||||
environ = env;
|
environ = env;
|
||||||
|
// for some reason SIGPIPE is set to SIG_IGN, so reset it, needed by bash,
|
||||||
|
// which does not reset signal handlers on its own
|
||||||
|
signal(SIGPIPE, SIG_DFL);
|
||||||
execvp(exe, argv);
|
execvp(exe, argv);
|
||||||
// Report the failure and exec a shell instead, so that we are not left
|
// Report the failure and exec a shell instead, so that we are not left
|
||||||
// with a forked but not exec'ed process
|
// with a forked but not exec'ed process
|
||||||
@@ -100,8 +136,8 @@ spawn(PyObject *self UNUSED, PyObject *args) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
#undef exit_on_err
|
#undef exit_on_err
|
||||||
free(argv);
|
free_string_tuple(argv);
|
||||||
free(env);
|
free_string_tuple(env);
|
||||||
if (PyErr_Occurred()) return NULL;
|
if (PyErr_Occurred()) return NULL;
|
||||||
return PyLong_FromLong(pid);
|
return PyLong_FromLong(pid);
|
||||||
}
|
}
|
||||||
|
|||||||
180
kitty/child.py
180
kitty/child.py
@@ -4,38 +4,137 @@
|
|||||||
|
|
||||||
import fcntl
|
import fcntl
|
||||||
import os
|
import os
|
||||||
|
from collections import defaultdict
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
import kitty.fast_data_types as fast_data_types
|
import kitty.fast_data_types as fast_data_types
|
||||||
|
|
||||||
from .constants import is_macos, shell_path, terminfo_dir
|
from .constants import is_macos, shell_path, terminfo_dir
|
||||||
|
|
||||||
|
if is_macos:
|
||||||
|
from kitty.fast_data_types import (
|
||||||
|
cmdline_of_process, cwd_of_process as _cwd, environ_of_process as _environ_of_process,
|
||||||
|
process_group_map as _process_group_map
|
||||||
|
)
|
||||||
|
|
||||||
def cwd_of_process(pid):
|
def cwd_of_process(pid):
|
||||||
if is_macos:
|
return os.path.realpath(_cwd(pid))
|
||||||
from kitty.fast_data_types import cwd_of_process
|
|
||||||
ans = cwd_of_process(pid)
|
def process_group_map():
|
||||||
else:
|
ans = defaultdict(list)
|
||||||
|
for pid, pgid in _process_group_map():
|
||||||
|
ans[pgid].append(pid)
|
||||||
|
return ans
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
def cmdline_of_process(pid):
|
||||||
|
return list(filter(None, open('/proc/{}/cmdline'.format(pid), 'rb').read().decode('utf-8').split('\0')))
|
||||||
|
|
||||||
|
def cwd_of_process(pid):
|
||||||
ans = '/proc/{}/cwd'.format(pid)
|
ans = '/proc/{}/cwd'.format(pid)
|
||||||
return os.path.realpath(ans)
|
return os.path.realpath(ans)
|
||||||
|
|
||||||
|
def _environ_of_process(pid):
|
||||||
|
return open('/proc/{}/environ'.format(pid), 'rb').read().decode('utf-8')
|
||||||
|
|
||||||
|
def process_group_map():
|
||||||
|
ans = defaultdict(list)
|
||||||
|
for x in os.listdir('/proc'):
|
||||||
|
try:
|
||||||
|
pid = int(x)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
raw = open('/proc/' + x + '/stat', 'rb').read().decode('utf-8')
|
||||||
|
except EnvironmentError:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
q = int(raw.split(' ', 5)[4])
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
ans[q].append(pid)
|
||||||
|
return ans
|
||||||
|
|
||||||
|
|
||||||
def cmdline_of_process(pid):
|
def processes_in_group(grp):
|
||||||
if is_macos:
|
gmap = getattr(process_group_map, 'cached_map', None)
|
||||||
# TODO: macOS implementation, see DarwinProcess.c in htop for inspiration
|
if gmap is None:
|
||||||
raise NotImplementedError()
|
try:
|
||||||
return list(filter(None, open('/proc/{}/cmdline'.format(pid), 'rb').read().decode('utf-8').split('\0')))
|
gmap = process_group_map()
|
||||||
|
except Exception:
|
||||||
|
gmap = {}
|
||||||
|
return gmap.get(grp, [])
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def cached_process_data():
|
||||||
|
try:
|
||||||
|
process_group_map.cached_map = process_group_map()
|
||||||
|
except Exception:
|
||||||
|
process_group_map.cached_map = {}
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
process_group_map.cached_map = None
|
||||||
|
|
||||||
|
|
||||||
|
def parse_environ_block(data):
|
||||||
|
"""Parse a C environ block of environment variables into a dictionary."""
|
||||||
|
# The block is usually raw data from the target process. It might contain
|
||||||
|
# trailing garbage and lines that do not look like assignments.
|
||||||
|
ret = {}
|
||||||
|
pos = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
next_pos = data.find("\0", pos)
|
||||||
|
# nul byte at the beginning or double nul byte means finish
|
||||||
|
if next_pos <= pos:
|
||||||
|
break
|
||||||
|
# there might not be an equals sign
|
||||||
|
equal_pos = data.find("=", pos, next_pos)
|
||||||
|
if equal_pos > pos:
|
||||||
|
key = data[pos:equal_pos]
|
||||||
|
value = data[equal_pos + 1:next_pos]
|
||||||
|
ret[key] = value
|
||||||
|
pos = next_pos + 1
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def environ_of_process(pid):
|
||||||
|
return parse_environ_block(_environ_of_process(pid))
|
||||||
|
|
||||||
|
|
||||||
def remove_cloexec(fd):
|
def remove_cloexec(fd):
|
||||||
fcntl.fcntl(fd, fcntl.F_SETFD, fcntl.fcntl(fd, fcntl.F_GETFD) & ~fcntl.FD_CLOEXEC)
|
fcntl.fcntl(fd, fcntl.F_SETFD, fcntl.fcntl(fd, fcntl.F_GETFD) & ~fcntl.FD_CLOEXEC)
|
||||||
|
|
||||||
|
|
||||||
|
def default_env():
|
||||||
|
try:
|
||||||
|
return default_env.env
|
||||||
|
except AttributeError:
|
||||||
|
return os.environ
|
||||||
|
|
||||||
|
|
||||||
|
def set_default_env(val=None):
|
||||||
|
env = os.environ.copy()
|
||||||
|
if val:
|
||||||
|
env.update(val)
|
||||||
|
default_env.env = env
|
||||||
|
|
||||||
|
|
||||||
class Child:
|
class Child:
|
||||||
|
|
||||||
child_fd = pid = None
|
child_fd = pid = None
|
||||||
forked = False
|
forked = False
|
||||||
|
|
||||||
def __init__(self, argv, cwd, opts, stdin=None, env=None, cwd_from=None):
|
def __init__(self, argv, cwd, opts, stdin=None, env=None, cwd_from=None):
|
||||||
|
self.allow_remote_control = False
|
||||||
|
if argv and argv[0] == '@':
|
||||||
|
self.allow_remote_control = True
|
||||||
|
if len(argv) > 1:
|
||||||
|
argv = argv[1:]
|
||||||
self.argv = argv
|
self.argv = argv
|
||||||
if cwd_from is not None:
|
if cwd_from is not None:
|
||||||
try:
|
try:
|
||||||
@@ -58,12 +157,14 @@ class Child:
|
|||||||
remove_cloexec(slave)
|
remove_cloexec(slave)
|
||||||
fast_data_types.set_iutf8(master, True)
|
fast_data_types.set_iutf8(master, True)
|
||||||
stdin, self.stdin = self.stdin, None
|
stdin, self.stdin = self.stdin, None
|
||||||
|
ready_read_fd, ready_write_fd = os.pipe()
|
||||||
|
remove_cloexec(ready_read_fd)
|
||||||
if stdin is not None:
|
if stdin is not None:
|
||||||
stdin_read_fd, stdin_write_fd = os.pipe()
|
stdin_read_fd, stdin_write_fd = os.pipe()
|
||||||
remove_cloexec(stdin_read_fd)
|
remove_cloexec(stdin_read_fd)
|
||||||
else:
|
else:
|
||||||
stdin_read_fd = stdin_write_fd = -1
|
stdin_read_fd = stdin_write_fd = -1
|
||||||
env = os.environ.copy()
|
env = default_env().copy()
|
||||||
env.update(self.env)
|
env.update(self.env)
|
||||||
env['TERM'] = self.opts.term
|
env['TERM'] = self.opts.term
|
||||||
env['COLORTERM'] = 'truecolor'
|
env['COLORTERM'] = 'truecolor'
|
||||||
@@ -76,16 +177,44 @@ class Child:
|
|||||||
# Some macOS machines need the shell to have argv[0] prefixed by
|
# Some macOS machines need the shell to have argv[0] prefixed by
|
||||||
# hyphen, see https://github.com/kovidgoyal/kitty/issues/247
|
# hyphen, see https://github.com/kovidgoyal/kitty/issues/247
|
||||||
argv[0] = ('-' + exe.split('/')[-1])
|
argv[0] = ('-' + exe.split('/')[-1])
|
||||||
pid = fast_data_types.spawn(exe, self.cwd, tuple(argv), env, master, slave, stdin_read_fd, stdin_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)
|
||||||
self.pid = pid
|
self.pid = pid
|
||||||
self.child_fd = master
|
self.child_fd = master
|
||||||
if stdin is not None:
|
if stdin is not None:
|
||||||
os.close(stdin_read_fd)
|
os.close(stdin_read_fd)
|
||||||
fast_data_types.thread_write(stdin_write_fd, stdin)
|
fast_data_types.thread_write(stdin_write_fd, stdin)
|
||||||
|
os.close(ready_read_fd)
|
||||||
|
self.terminal_ready_fd = ready_write_fd
|
||||||
fcntl.fcntl(self.child_fd, fcntl.F_SETFL, fcntl.fcntl(self.child_fd, fcntl.F_GETFL) | os.O_NONBLOCK)
|
fcntl.fcntl(self.child_fd, fcntl.F_SETFL, fcntl.fcntl(self.child_fd, fcntl.F_GETFL) | os.O_NONBLOCK)
|
||||||
return pid
|
return pid
|
||||||
|
|
||||||
|
def mark_terminal_ready(self):
|
||||||
|
os.close(self.terminal_ready_fd)
|
||||||
|
self.terminal_ready_fd = -1
|
||||||
|
|
||||||
|
@property
|
||||||
|
def foreground_processes(self):
|
||||||
|
try:
|
||||||
|
pgrp = os.tcgetpgrp(self.child_fd)
|
||||||
|
foreground_processes = processes_in_group(pgrp) if pgrp >= 0 else []
|
||||||
|
|
||||||
|
def process_desc(pid):
|
||||||
|
ans = {'pid': pid}
|
||||||
|
try:
|
||||||
|
ans['cmdline'] = cmdline_of_process(pid)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
ans['cwd'] = cwd_of_process(pid) or None
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return ans
|
||||||
|
|
||||||
|
return list(map(process_desc, foreground_processes))
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cmdline(self):
|
def cmdline(self):
|
||||||
try:
|
try:
|
||||||
@@ -93,9 +222,34 @@ class Child:
|
|||||||
except Exception:
|
except Exception:
|
||||||
return list(self.argv)
|
return list(self.argv)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def environ(self):
|
||||||
|
try:
|
||||||
|
return environ_of_process(self.pid)
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def current_cwd(self):
|
def current_cwd(self):
|
||||||
try:
|
try:
|
||||||
return cwd_of_process(self.pid)
|
return cwd_of_process(self.pid)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pid_for_cwd(self):
|
||||||
|
try:
|
||||||
|
pgrp = os.tcgetpgrp(self.child_fd)
|
||||||
|
foreground_processes = processes_in_group(pgrp) if pgrp >= 0 else []
|
||||||
|
if len(foreground_processes) == 1:
|
||||||
|
return foreground_processes[0]
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return self.pid
|
||||||
|
|
||||||
|
@property
|
||||||
|
def foreground_cwd(self):
|
||||||
|
try:
|
||||||
|
return cwd_of_process(self.pid_for_cwd) or None
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|||||||
302
kitty/cli.py
302
kitty/cli.py
@@ -8,9 +8,7 @@ import sys
|
|||||||
from collections import deque
|
from collections import deque
|
||||||
|
|
||||||
from .conf.utils import resolve_config
|
from .conf.utils import resolve_config
|
||||||
from .config import defaults, load_config
|
|
||||||
from .constants import appname, defconf, is_macos, is_wayland, str_version
|
from .constants import appname, defconf, is_macos, is_wayland, str_version
|
||||||
from .fast_data_types import GLFW_KEY_UNKNOWN, glfw_get_key_name
|
|
||||||
|
|
||||||
CONFIG_HELP = '''\
|
CONFIG_HELP = '''\
|
||||||
Specify a path to the configuration file(s) to use. All configuration files are
|
Specify a path to the configuration file(s) to use. All configuration files are
|
||||||
@@ -30,140 +28,10 @@ directory is always used and the above searching does not happen.
|
|||||||
If :file:`/etc/xdg/{appname}/{conf_name}.conf` exists it is merged before (i.e. with lower
|
If :file:`/etc/xdg/{appname}/{conf_name}.conf` exists it is merged before (i.e. with lower
|
||||||
priority) than any user config files. It can be used to specify system-wide
|
priority) than any user config files. It can be used to specify system-wide
|
||||||
defaults for all users.
|
defaults for all users.
|
||||||
'''.replace('{macos_confpath}', ':file:`~/Library/Preferences/{appname}/{conf_name}.conf`,' if is_macos else '')
|
'''.replace(
|
||||||
|
'{macos_confpath}',
|
||||||
OPTIONS = '''
|
(':file:`~/Library/Preferences/{appname}/{conf_name}.conf`,' if is_macos else ''), 1
|
||||||
--class
|
)
|
||||||
dest=cls
|
|
||||||
default={appname}
|
|
||||||
condition=not is_macos
|
|
||||||
Set the class part of the :italic:`WM_CLASS` window property
|
|
||||||
|
|
||||||
|
|
||||||
--name
|
|
||||||
condition=not is_macos
|
|
||||||
Set the name part of the :italic:`WM_CLASS` property (defaults to using the value from :option:`{appname} --class`)
|
|
||||||
|
|
||||||
|
|
||||||
--title -T
|
|
||||||
Set the window title. This will override any title set by the program running inside kitty. So
|
|
||||||
only use this if you are running a program that does not set titles.
|
|
||||||
|
|
||||||
|
|
||||||
--config -c
|
|
||||||
type=list
|
|
||||||
{config_help}
|
|
||||||
|
|
||||||
|
|
||||||
--override -o
|
|
||||||
type=list
|
|
||||||
Override individual configuration options, can be specified multiple times.
|
|
||||||
Syntax: :italic:`name=value`. For example: :option:`kitty -o` font_size=20
|
|
||||||
|
|
||||||
|
|
||||||
--directory -d
|
|
||||||
default=.
|
|
||||||
Change to the specified directory when launching
|
|
||||||
|
|
||||||
|
|
||||||
--detach
|
|
||||||
type=bool-set
|
|
||||||
condition=not is_macos
|
|
||||||
Detach from the controlling terminal, if any
|
|
||||||
|
|
||||||
|
|
||||||
--session
|
|
||||||
Path to a file containing the startup :italic:`session` (tabs, windows, layout, programs).
|
|
||||||
See the README file for details and an example.
|
|
||||||
|
|
||||||
|
|
||||||
--hold
|
|
||||||
type=bool-set
|
|
||||||
Remain open after child process exits. Note that this only affects the first
|
|
||||||
window. You can quit by either using the close window shortcut or :kbd:`Ctrl+d`.
|
|
||||||
|
|
||||||
|
|
||||||
--single-instance -1
|
|
||||||
type=bool-set
|
|
||||||
If specified only a single instance of :italic:`{appname}` will run. New invocations will
|
|
||||||
instead create a new top-level window in the existing :italic:`{appname}` instance. This
|
|
||||||
allows :italic:`{appname}` to share a single sprite cache on the GPU and also reduces
|
|
||||||
startup time. You can also have separate groups of :italic:`{appname}` instances by using the
|
|
||||||
:option:`kitty --instance-group` option
|
|
||||||
|
|
||||||
|
|
||||||
--instance-group
|
|
||||||
Used in combination with the :option:`kitty --single-instance` option. All :italic:`{appname}` invocations
|
|
||||||
with the same :option:`kitty --instance-group` will result in new windows being created
|
|
||||||
in the first :italic:`{appname}` instance within that group
|
|
||||||
|
|
||||||
|
|
||||||
--wait-for-single-instance-window-close
|
|
||||||
type=bool-set
|
|
||||||
Normally, when using :option:`--single-instance`, :italic:`{appname}` will open a new window in an existing
|
|
||||||
instance and quit immediately. With this option, it will not quit till the newly opened
|
|
||||||
window is closed. Note that if no previous instance is found, then :italic:`{appname}` will wait anyway,
|
|
||||||
regardless of this option.
|
|
||||||
|
|
||||||
|
|
||||||
--listen-on
|
|
||||||
Tell kitty to listen on the specified address for control
|
|
||||||
messages. For example, :option:`{appname} --listen-on`=unix:/tmp/mykitty or
|
|
||||||
:option:`{appname} --listen-on`=tcp:localhost:12345. On Linux systems, you can also use abstract
|
|
||||||
UNIX sockets, not associated with a file, like this: :option:`{appname} --listen-on`=unix:@mykitty.
|
|
||||||
To control kitty, you can send it commands with :italic:`kitty @` using the :option:`kitty @ --to` option
|
|
||||||
to specify this address. Note that this option will be ignored, unless you set
|
|
||||||
:opt:`allow_remote_control` to yes in :file:`kitty.conf`.
|
|
||||||
|
|
||||||
|
|
||||||
# Debugging options
|
|
||||||
|
|
||||||
--version -v
|
|
||||||
type=bool-set
|
|
||||||
The current {appname} version
|
|
||||||
|
|
||||||
|
|
||||||
--dump-commands
|
|
||||||
type=bool-set
|
|
||||||
Output commands received from child process to stdout
|
|
||||||
|
|
||||||
|
|
||||||
--replay-commands
|
|
||||||
Replay previously dumped commands. Specify the path to a dump file previously created by --dump-commands. You
|
|
||||||
can open a new kitty window to replay the commands with::
|
|
||||||
|
|
||||||
kitty sh -c "kitty --replay-commands /path/to/dump/file; read"
|
|
||||||
|
|
||||||
|
|
||||||
--dump-bytes
|
|
||||||
Path to file in which to store the raw bytes received from the child process
|
|
||||||
|
|
||||||
|
|
||||||
--debug-gl
|
|
||||||
type=bool-set
|
|
||||||
Debug OpenGL commands. This will cause all OpenGL calls to check for errors
|
|
||||||
instead of ignoring them. Useful when debugging rendering problems
|
|
||||||
|
|
||||||
|
|
||||||
--debug-keyboard
|
|
||||||
type=bool-set
|
|
||||||
This option will cause kitty to print out key events as they are received
|
|
||||||
|
|
||||||
|
|
||||||
--debug-font-fallback
|
|
||||||
type=bool-set
|
|
||||||
Print out information about the selection of fallback fonts for characters not present in the main font.
|
|
||||||
|
|
||||||
|
|
||||||
--debug-config
|
|
||||||
type=bool-set
|
|
||||||
Print out information about the system and kitty configuration.
|
|
||||||
|
|
||||||
|
|
||||||
--execute -e
|
|
||||||
type=bool-set
|
|
||||||
!
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
def surround(x, start, end):
|
def surround(x, start, end):
|
||||||
@@ -232,7 +100,9 @@ def file(x):
|
|||||||
return italic(x)
|
return italic(x)
|
||||||
|
|
||||||
|
|
||||||
def parse_option_spec(spec=OPTIONS):
|
def parse_option_spec(spec=None):
|
||||||
|
if spec is None:
|
||||||
|
spec = options_spec()
|
||||||
NORMAL, METADATA, HELP = 'NORMAL', 'METADATA', 'HELP'
|
NORMAL, METADATA, HELP = 'NORMAL', 'METADATA', 'HELP'
|
||||||
state = NORMAL
|
state = NORMAL
|
||||||
lines = spec.splitlines()
|
lines = spec.splitlines()
|
||||||
@@ -405,7 +275,10 @@ def print_help_for_seq(seq, usage, message, appname):
|
|||||||
if print_help_for_seq.allow_pager and sys.stdout.isatty():
|
if print_help_for_seq.allow_pager and sys.stdout.isatty():
|
||||||
import subprocess
|
import subprocess
|
||||||
p = subprocess.Popen(['less', '-isRXF'], stdin=subprocess.PIPE)
|
p = subprocess.Popen(['less', '-isRXF'], stdin=subprocess.PIPE)
|
||||||
p.communicate(text.encode('utf-8'))
|
try:
|
||||||
|
p.communicate(text.encode('utf-8'))
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
raise SystemExit(1)
|
||||||
raise SystemExit(p.wait())
|
raise SystemExit(p.wait())
|
||||||
else:
|
else:
|
||||||
print(text)
|
print(text)
|
||||||
@@ -454,7 +327,7 @@ def seq_as_rst(seq, usage, message, appname, heading_char='-'):
|
|||||||
if defval is not None:
|
if defval is not None:
|
||||||
a(textwrap.indent('Default: :code:`{}`'.format(defval), ' ' * 4))
|
a(textwrap.indent('Default: :code:`{}`'.format(defval), ' ' * 4))
|
||||||
if 'choices' in opt:
|
if 'choices' in opt:
|
||||||
a(textwrap.indent('Choices: :code:`{}`'.format(', '.join(opt['choices'])), ' ' * 4))
|
a(textwrap.indent('Choices: :code:`{}`'.format(', '.join(sorted(opt['choices']))), ' ' * 4))
|
||||||
a('')
|
a('')
|
||||||
|
|
||||||
text = '\n'.join(blocks)
|
text = '\n'.join(blocks)
|
||||||
@@ -589,6 +462,150 @@ def parse_cmdline(oc, disabled, args=None):
|
|||||||
|
|
||||||
def options_spec():
|
def options_spec():
|
||||||
if not hasattr(options_spec, 'ans'):
|
if not hasattr(options_spec, 'ans'):
|
||||||
|
OPTIONS = '''
|
||||||
|
--class
|
||||||
|
dest=cls
|
||||||
|
default={appname}
|
||||||
|
condition=not is_macos
|
||||||
|
Set the class part of the :italic:`WM_CLASS` window property. On Wayland, it sets the app id.
|
||||||
|
|
||||||
|
|
||||||
|
--name
|
||||||
|
condition=not is_macos
|
||||||
|
Set the name part of the :italic:`WM_CLASS` property (defaults to using the value from :option:`{appname} --class`)
|
||||||
|
|
||||||
|
|
||||||
|
--title -T
|
||||||
|
Set the window title. This will override any title set by the program running inside kitty. So
|
||||||
|
only use this if you are running a program that does not set titles. If combined
|
||||||
|
with :option:`{appname} --session` the title will be used for all windows created by the
|
||||||
|
session, that do not set their own titles.
|
||||||
|
|
||||||
|
|
||||||
|
--config -c
|
||||||
|
type=list
|
||||||
|
{config_help}
|
||||||
|
|
||||||
|
|
||||||
|
--override -o
|
||||||
|
type=list
|
||||||
|
Override individual configuration options, can be specified multiple times.
|
||||||
|
Syntax: :italic:`name=value`. For example: :option:`kitty -o` font_size=20
|
||||||
|
|
||||||
|
|
||||||
|
--directory -d
|
||||||
|
default=.
|
||||||
|
Change to the specified directory when launching
|
||||||
|
|
||||||
|
|
||||||
|
--detach
|
||||||
|
type=bool-set
|
||||||
|
condition=not is_macos
|
||||||
|
Detach from the controlling terminal, if any
|
||||||
|
|
||||||
|
|
||||||
|
--session
|
||||||
|
Path to a file containing the startup :italic:`session` (tabs, windows, layout, programs).
|
||||||
|
See the README file for details and an example.
|
||||||
|
|
||||||
|
|
||||||
|
--hold
|
||||||
|
type=bool-set
|
||||||
|
Remain open after child process exits. Note that this only affects the first
|
||||||
|
window. You can quit by either using the close window shortcut or :kbd:`Ctrl+d`.
|
||||||
|
|
||||||
|
|
||||||
|
--single-instance -1
|
||||||
|
type=bool-set
|
||||||
|
If specified only a single instance of :italic:`{appname}` will run. New invocations will
|
||||||
|
instead create a new top-level window in the existing :italic:`{appname}` instance. This
|
||||||
|
allows :italic:`{appname}` to share a single sprite cache on the GPU and also reduces
|
||||||
|
startup time. You can also have separate groups of :italic:`{appname}` instances by using the
|
||||||
|
:option:`kitty --instance-group` option
|
||||||
|
|
||||||
|
|
||||||
|
--instance-group
|
||||||
|
Used in combination with the :option:`kitty --single-instance` option. All :italic:`{appname}` invocations
|
||||||
|
with the same :option:`kitty --instance-group` will result in new windows being created
|
||||||
|
in the first :italic:`{appname}` instance within that group
|
||||||
|
|
||||||
|
|
||||||
|
--wait-for-single-instance-window-close
|
||||||
|
type=bool-set
|
||||||
|
Normally, when using :option:`--single-instance`, :italic:`{appname}` will open a new window in an existing
|
||||||
|
instance and quit immediately. With this option, it will not quit till the newly opened
|
||||||
|
window is closed. Note that if no previous instance is found, then :italic:`{appname}` will wait anyway,
|
||||||
|
regardless of this option.
|
||||||
|
|
||||||
|
|
||||||
|
--listen-on
|
||||||
|
Tell kitty to listen on the specified address for control
|
||||||
|
messages. For example, :option:`{appname} --listen-on`=unix:/tmp/mykitty or
|
||||||
|
:option:`{appname} --listen-on`=tcp:localhost:12345. On Linux systems, you can
|
||||||
|
also use abstract UNIX sockets, not associated with a file, like this:
|
||||||
|
:option:`{appname} --listen-on`=unix:@mykitty. To control kitty, you can send
|
||||||
|
it commands with :italic:`kitty @` using the :option:`kitty @ --to` option to
|
||||||
|
specify this address. This option will be ignored, unless you set
|
||||||
|
:opt:`allow_remote_control` to yes in :file:`kitty.conf`. Note that if you run
|
||||||
|
:italic:`kitty @` within a kitty window, there is no need to specify the :italic:`--to`
|
||||||
|
option as it is read automatically from the environment.
|
||||||
|
|
||||||
|
|
||||||
|
--start-as
|
||||||
|
type=choices
|
||||||
|
default=normal
|
||||||
|
choices=normal,fullscreen,maximized,minimized
|
||||||
|
Control how the initial kitty window is created.
|
||||||
|
|
||||||
|
|
||||||
|
# Debugging options
|
||||||
|
|
||||||
|
--version -v
|
||||||
|
type=bool-set
|
||||||
|
The current {appname} version
|
||||||
|
|
||||||
|
|
||||||
|
--dump-commands
|
||||||
|
type=bool-set
|
||||||
|
Output commands received from child process to stdout
|
||||||
|
|
||||||
|
|
||||||
|
--replay-commands
|
||||||
|
Replay previously dumped commands. Specify the path to a dump file previously created by --dump-commands. You
|
||||||
|
can open a new kitty window to replay the commands with::
|
||||||
|
|
||||||
|
kitty sh -c "kitty --replay-commands /path/to/dump/file; read"
|
||||||
|
|
||||||
|
|
||||||
|
--dump-bytes
|
||||||
|
Path to file in which to store the raw bytes received from the child process
|
||||||
|
|
||||||
|
|
||||||
|
--debug-gl
|
||||||
|
type=bool-set
|
||||||
|
Debug OpenGL commands. This will cause all OpenGL calls to check for errors
|
||||||
|
instead of ignoring them. Useful when debugging rendering problems
|
||||||
|
|
||||||
|
|
||||||
|
--debug-keyboard
|
||||||
|
type=bool-set
|
||||||
|
This option will cause kitty to print out key events as they are received
|
||||||
|
|
||||||
|
|
||||||
|
--debug-font-fallback
|
||||||
|
type=bool-set
|
||||||
|
Print out information about the selection of fallback fonts for characters not present in the main font.
|
||||||
|
|
||||||
|
|
||||||
|
--debug-config
|
||||||
|
type=bool-set
|
||||||
|
Print out information about the system and kitty configuration.
|
||||||
|
|
||||||
|
|
||||||
|
--execute -e
|
||||||
|
type=bool-set
|
||||||
|
!
|
||||||
|
'''
|
||||||
options_spec.ans = OPTIONS.format(
|
options_spec.ans = OPTIONS.format(
|
||||||
appname=appname, config_help=CONFIG_HELP.format(appname=appname, conf_name=appname)
|
appname=appname, config_help=CONFIG_HELP.format(appname=appname, conf_name=appname)
|
||||||
|
|
||||||
@@ -596,6 +613,10 @@ def options_spec():
|
|||||||
return options_spec.ans
|
return options_spec.ans
|
||||||
|
|
||||||
|
|
||||||
|
def options_for_completion():
|
||||||
|
return parse_option_spec(options_spec())[0]
|
||||||
|
|
||||||
|
|
||||||
def option_spec_as_rst(ospec=options_spec, usage=None, message=None, appname=None, heading_char='-'):
|
def option_spec_as_rst(ospec=options_spec, usage=None, message=None, appname=None, heading_char='-'):
|
||||||
options = parse_option_spec(ospec())
|
options = parse_option_spec(ospec())
|
||||||
seq, disabled = options
|
seq, disabled = options
|
||||||
@@ -631,6 +652,7 @@ def print_shortcut(key_sequence, action):
|
|||||||
names.append(name)
|
names.append(name)
|
||||||
if key:
|
if key:
|
||||||
if is_native:
|
if is_native:
|
||||||
|
from .fast_data_types import GLFW_KEY_UNKNOWN, glfw_get_key_name
|
||||||
names.append(glfw_get_key_name(GLFW_KEY_UNKNOWN, key))
|
names.append(glfw_get_key_name(GLFW_KEY_UNKNOWN, key))
|
||||||
else:
|
else:
|
||||||
names.append(krmap[key])
|
names.append(krmap[key])
|
||||||
@@ -665,6 +687,7 @@ def flatten_sequence_map(m):
|
|||||||
|
|
||||||
|
|
||||||
def compare_opts(opts):
|
def compare_opts(opts):
|
||||||
|
from .config import defaults, load_config
|
||||||
print('\nConfig options different from defaults:')
|
print('\nConfig options different from defaults:')
|
||||||
default_opts = load_config()
|
default_opts = load_config()
|
||||||
changed_opts = [
|
changed_opts = [
|
||||||
@@ -686,6 +709,7 @@ def compare_opts(opts):
|
|||||||
|
|
||||||
|
|
||||||
def create_opts(args, debug_config=False):
|
def create_opts(args, debug_config=False):
|
||||||
|
from .config import load_config
|
||||||
config = tuple(resolve_config(SYSTEM_CONF, defconf, args.config))
|
config = tuple(resolve_config(SYSTEM_CONF, defconf, args.config))
|
||||||
if debug_config:
|
if debug_config:
|
||||||
print(version(add_rev=True))
|
print(version(add_rev=True))
|
||||||
|
|||||||
@@ -122,6 +122,10 @@ def draw(*a):
|
|||||||
write(' '.join(a))
|
write(' '.join(a))
|
||||||
|
|
||||||
|
|
||||||
|
def screen_manipulate_title_stack(op, which):
|
||||||
|
write(CSI + '%d;%dt' % (op, which))
|
||||||
|
|
||||||
|
|
||||||
def report_device_attributes(mode, char):
|
def report_device_attributes(mode, char):
|
||||||
x = CSI
|
x = CSI
|
||||||
if char:
|
if char:
|
||||||
|
|||||||
205
kitty/cmds.py
205
kitty/cmds.py
@@ -9,6 +9,7 @@ import sys
|
|||||||
from .cli import parse_args
|
from .cli import parse_args
|
||||||
from .config import parse_config, parse_send_text_bytes
|
from .config import parse_config, parse_send_text_bytes
|
||||||
from .constants import appname
|
from .constants import appname
|
||||||
|
from .fast_data_types import focus_os_window
|
||||||
from .tabs import SpecialWindow
|
from .tabs import SpecialWindow
|
||||||
from .utils import natsort_ints
|
from .utils import natsort_ints
|
||||||
|
|
||||||
@@ -26,6 +27,14 @@ class OpacityError(ValueError):
|
|||||||
hide_traceback = True
|
hide_traceback = True
|
||||||
|
|
||||||
|
|
||||||
|
class UnknownLayout(ValueError):
|
||||||
|
|
||||||
|
hide_traceback = True
|
||||||
|
|
||||||
|
|
||||||
|
cmap = {}
|
||||||
|
|
||||||
|
|
||||||
def cmd(short_desc, desc=None, options_spec=None, no_response=False, argspec='...', string_return_is_error=False, args_count=None):
|
def cmd(short_desc, desc=None, options_spec=None, no_response=False, argspec='...', string_return_is_error=False, args_count=None):
|
||||||
|
|
||||||
def w(func):
|
def w(func):
|
||||||
@@ -39,6 +48,7 @@ def cmd(short_desc, desc=None, options_spec=None, no_response=False, argspec='..
|
|||||||
func.no_response = no_response
|
func.no_response = no_response
|
||||||
func.string_return_is_error = string_return_is_error
|
func.string_return_is_error = string_return_is_error
|
||||||
func.args_count = 0 if not argspec else args_count
|
func.args_count = 0 if not argspec else args_count
|
||||||
|
cmap[func.name] = func
|
||||||
return func
|
return func
|
||||||
return w
|
return w
|
||||||
|
|
||||||
@@ -46,18 +56,20 @@ def cmd(short_desc, desc=None, options_spec=None, no_response=False, argspec='..
|
|||||||
MATCH_WINDOW_OPTION = '''\
|
MATCH_WINDOW_OPTION = '''\
|
||||||
--match -m
|
--match -m
|
||||||
The window to match. Match specifications are of the form:
|
The window to match. Match specifications are of the form:
|
||||||
:italic:`field:regexp`. Where field can be one of: id, title, pid, cwd, cmdline, num.
|
:italic:`field:regexp`. Where field can be one of: id, title, pid, cwd, cmdline, num, env.
|
||||||
You can use the :italic:`ls` command to get a list of windows. Note that for
|
You can use the :italic:`ls` command to get a list of windows. Note that for
|
||||||
numeric fields such as id, pid and num the expression is interpreted as a number,
|
numeric fields such as id, pid and num the expression is interpreted as a number,
|
||||||
not a regular expression. The field num refers to the window position in the current tab,
|
not a regular expression. The field num refers to the window position in the current tab,
|
||||||
starting from zero and counting clockwise (this is the same as the order in which the
|
starting from zero and counting clockwise (this is the same as the order in which the
|
||||||
windows are reported by the :italic:`ls` command). The window id of the current window
|
windows are reported by the :italic:`ls` command). The window id of the current window
|
||||||
is available as the KITTY_WINDOW_ID environment variable.
|
is available as the KITTY_WINDOW_ID environment variable. When using the :italic:`env` field
|
||||||
|
to match on environment variables you can specify only the environment variable name or a name
|
||||||
|
and value, for example, :italic:`env:MY_ENV_VAR=2`
|
||||||
'''
|
'''
|
||||||
MATCH_TAB_OPTION = '''\
|
MATCH_TAB_OPTION = '''\
|
||||||
--match -m
|
--match -m
|
||||||
The tab to match. Match specifications are of the form:
|
The tab to match. Match specifications are of the form:
|
||||||
:italic:`field:regexp`. Where field can be one of: id, title, pid, cwd, cmdline.
|
:italic:`field:regexp`. Where field can be one of: id, title, pid, cwd, env, cmdline.
|
||||||
You can use the :italic:`ls` command to get a list of tabs. Note that for
|
You can use the :italic:`ls` command to get a list of tabs. Note that for
|
||||||
numeric fields such as id and pid the expression is interpreted as a number,
|
numeric fields such as id and pid the expression is interpreted as a number,
|
||||||
not a regular expression. When using title or id, first a matching tab is
|
not a regular expression. When using title or id, first a matching tab is
|
||||||
@@ -72,8 +84,8 @@ for that window is used.
|
|||||||
'List all windows. The list is returned as JSON tree. The top-level is a list of'
|
'List all windows. The list is returned as JSON tree. The top-level is a list of'
|
||||||
' operating system {appname} windows. Each OS window has an :italic:`id` and a list'
|
' operating system {appname} windows. Each OS window has an :italic:`id` and a list'
|
||||||
' of :italic:`tabs`. Each tab has its own :italic:`id`, a :italic:`title` and a list of :italic:`windows`.'
|
' of :italic:`tabs`. Each tab has its own :italic:`id`, a :italic:`title` and a list of :italic:`windows`.'
|
||||||
' Each window has an :italic:`id`, :italic:`title`, :italic:`current working directory`, :italic:`process id (PID)` and'
|
' Each window has an :italic:`id`, :italic:`title`, :italic:`current working directory`, :italic:`process id (PID)`, '
|
||||||
' :italic:`command-line` of the process running in the window.\n\n'
|
' :italic:`command-line` and :italic:`environment` of the process running in the window.\n\n'
|
||||||
'You can use these criteria to select windows/tabs for the other commands.'.format(appname=appname),
|
'You can use these criteria to select windows/tabs for the other commands.'.format(appname=appname),
|
||||||
argspec=''
|
argspec=''
|
||||||
)
|
)
|
||||||
@@ -114,7 +126,7 @@ def set_font_size(boss, window, payload):
|
|||||||
' and :italic:`\\u21fa` to send unicode characters. If you use the :option:`kitty @ send-text --match` option'
|
' and :italic:`\\u21fa` to send unicode characters. If you use the :option:`kitty @ send-text --match` option'
|
||||||
' the text will be sent to all matched windows. By default, text is sent to'
|
' the text will be sent to all matched windows. By default, text is sent to'
|
||||||
' only the currently active window.',
|
' only the currently active window.',
|
||||||
options_spec=MATCH_WINDOW_OPTION + '''\n
|
options_spec=MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t') + '''\n
|
||||||
--stdin
|
--stdin
|
||||||
type=bool-set
|
type=bool-set
|
||||||
Read the text to be sent from :italic:`stdin`. Note that in this case the text is sent as is,
|
Read the text to be sent from :italic:`stdin`. Note that in this case the text is sent as is,
|
||||||
@@ -130,7 +142,7 @@ are sent as is, not interpreted for escapes.
|
|||||||
)
|
)
|
||||||
def cmd_send_text(global_opts, opts, args):
|
def cmd_send_text(global_opts, opts, args):
|
||||||
limit = 1024
|
limit = 1024
|
||||||
ret = {'match': opts.match, 'is_binary': False}
|
ret = {'match': opts.match, 'is_binary': False, 'match_tab': opts.match_tab}
|
||||||
|
|
||||||
def pipe():
|
def pipe():
|
||||||
ret['is_binary'] = True
|
ret['is_binary'] = True
|
||||||
@@ -197,6 +209,13 @@ def send_text(boss, window, payload):
|
|||||||
match = payload['match']
|
match = payload['match']
|
||||||
if match:
|
if match:
|
||||||
windows = tuple(boss.match_windows(match))
|
windows = tuple(boss.match_windows(match))
|
||||||
|
if payload['match_tab']:
|
||||||
|
windows = []
|
||||||
|
tabs = tuple(boss.match_tabs(payload['match_tab']))
|
||||||
|
if not tabs:
|
||||||
|
raise MatchError(payload['match_tab'], 'tabs')
|
||||||
|
for tab in tabs:
|
||||||
|
windows += tuple(tab)
|
||||||
data = payload['text'].encode('utf-8') if payload['is_binary'] else parse_send_text_bytes(payload['text'])
|
data = payload['text'].encode('utf-8') if payload['is_binary'] else parse_send_text_bytes(payload['text'])
|
||||||
for window in windows:
|
for window in windows:
|
||||||
if window is not None:
|
if window is not None:
|
||||||
@@ -270,6 +289,69 @@ def set_tab_title(boss, window, payload):
|
|||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
|
# goto_layout {{{
|
||||||
|
@cmd(
|
||||||
|
'Set the window layout',
|
||||||
|
'Set the window layout in the specified tab (or the active tab if not specified).'
|
||||||
|
' You can use special match value :italic:`all` to set the layout in all tabs.',
|
||||||
|
options_spec=MATCH_TAB_OPTION,
|
||||||
|
argspec='LAYOUT_NAME'
|
||||||
|
)
|
||||||
|
def cmd_goto_layout(global_opts, opts, args):
|
||||||
|
try:
|
||||||
|
return {'layout': args[0], 'match': opts.match}
|
||||||
|
except IndexError:
|
||||||
|
raise SystemExit('No layout specified')
|
||||||
|
|
||||||
|
|
||||||
|
def goto_layout(boss, window, payload):
|
||||||
|
match = payload['match']
|
||||||
|
if match:
|
||||||
|
if match == 'all':
|
||||||
|
tabs = tuple(boss.all_tabs)
|
||||||
|
else:
|
||||||
|
tabs = tuple(boss.match_tabs(match))
|
||||||
|
if not tabs:
|
||||||
|
raise MatchError(match, 'tabs')
|
||||||
|
else:
|
||||||
|
tabs = [boss.tab_for_window(window) if window else boss.active_tab]
|
||||||
|
for tab in tabs:
|
||||||
|
if tab:
|
||||||
|
try:
|
||||||
|
tab.goto_layout(payload['layout'], raise_exception=True)
|
||||||
|
except ValueError:
|
||||||
|
raise UnknownLayout('The layout {} is unknown or disabled'.format(payload['layout']))
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
|
# last_used_layout {{{
|
||||||
|
@cmd(
|
||||||
|
'Switch to the last used layout',
|
||||||
|
'Switch to the last used window layout in the specified tab (or the active tab if not specified).'
|
||||||
|
' You can use special match value :italic:`all` to set the layout in all tabs.',
|
||||||
|
options_spec=MATCH_TAB_OPTION,
|
||||||
|
)
|
||||||
|
def cmd_last_used_layout(global_opts, opts, args):
|
||||||
|
return {'match': opts.match}
|
||||||
|
|
||||||
|
|
||||||
|
def last_used_layout(boss, window, payload):
|
||||||
|
match = payload['match']
|
||||||
|
if match:
|
||||||
|
if match == 'all':
|
||||||
|
tabs = tuple(boss.all_tabs)
|
||||||
|
else:
|
||||||
|
tabs = tuple(boss.match_tabs(match))
|
||||||
|
if not tabs:
|
||||||
|
raise MatchError(match, 'tabs')
|
||||||
|
else:
|
||||||
|
tabs = [boss.tab_for_window(window) if window else boss.active_tab]
|
||||||
|
for tab in tabs:
|
||||||
|
if tab:
|
||||||
|
tab.last_used_layout()
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
# close_window {{{
|
# close_window {{{
|
||||||
@cmd(
|
@cmd(
|
||||||
'Close the specified window(s)',
|
'Close the specified window(s)',
|
||||||
@@ -320,7 +402,7 @@ reset the layout to its default configuration.
|
|||||||
|
|
||||||
--self
|
--self
|
||||||
type=bool-set
|
type=bool-set
|
||||||
If specified close the window this command is run in, rather than the active window.
|
If specified resize the window this command is run in, rather than the active window.
|
||||||
''',
|
''',
|
||||||
argspec='',
|
argspec='',
|
||||||
string_return_is_error=True
|
string_return_is_error=True
|
||||||
@@ -381,7 +463,7 @@ def close_tab(boss, window, payload):
|
|||||||
'Open new window',
|
'Open new window',
|
||||||
'Open a new window in the specified tab. If you use the :option:`kitty @ new-window --match` option'
|
'Open a new window in the specified tab. If you use the :option:`kitty @ new-window --match` option'
|
||||||
' the first matching tab is used. Otherwise the currently active tab is used.'
|
' the first matching tab is used. Otherwise the currently active tab is used.'
|
||||||
' Prints out the id of the newly opened window. Any command line arguments'
|
' Prints out the id of the newly opened window (unless :option:`--no-response` is used). Any command line arguments'
|
||||||
' are assumed to be the command line used to run in the new window, if none'
|
' are assumed to be the command line used to run in the new window, if none'
|
||||||
' are provided, the default shell is run. For example:\n'
|
' are provided, the default shell is run. For example:\n'
|
||||||
':italic:`kitty @ new-window --title Email mutt`',
|
':italic:`kitty @ new-window --title Email mutt`',
|
||||||
@@ -400,6 +482,12 @@ type=bool-set
|
|||||||
Keep the current window focused instead of switching to the newly opened window
|
Keep the current window focused instead of switching to the newly opened window
|
||||||
|
|
||||||
|
|
||||||
|
--window-type
|
||||||
|
default=kitty
|
||||||
|
choices=kitty,os
|
||||||
|
What kind of window to open. A kitty window or a top-level OS window.
|
||||||
|
|
||||||
|
|
||||||
--new-tab
|
--new-tab
|
||||||
type=bool-set
|
type=bool-set
|
||||||
Open a new tab
|
Open a new tab
|
||||||
@@ -407,12 +495,23 @@ Open a new tab
|
|||||||
|
|
||||||
--tab-title
|
--tab-title
|
||||||
When using --new-tab set the title of the tab.
|
When using --new-tab set the title of the tab.
|
||||||
|
|
||||||
|
|
||||||
|
--no-response
|
||||||
|
type=bool-set
|
||||||
|
default=false
|
||||||
|
Don't wait for a response giving the id of the newly opened window. Note that
|
||||||
|
using this option means that you will not be notified of failures and that
|
||||||
|
the id of the new window will not be printed out.
|
||||||
''',
|
''',
|
||||||
argspec='[CMD ...]'
|
argspec='[CMD ...]'
|
||||||
)
|
)
|
||||||
def cmd_new_window(global_opts, opts, args):
|
def cmd_new_window(global_opts, opts, args):
|
||||||
|
if opts.no_response:
|
||||||
|
global_opts.no_command_response = True
|
||||||
return {'match': opts.match, 'title': opts.title, 'cwd': opts.cwd,
|
return {'match': opts.match, 'title': opts.title, 'cwd': opts.cwd,
|
||||||
'new_tab': opts.new_tab, 'tab_title': opts.tab_title,
|
'new_tab': opts.new_tab, 'tab_title': opts.tab_title,
|
||||||
|
'window_type': opts.window_type, 'no_response': opts.no_response,
|
||||||
'keep_focus': opts.keep_focus, 'args': args or []}
|
'keep_focus': opts.keep_focus, 'args': args or []}
|
||||||
|
|
||||||
|
|
||||||
@@ -427,7 +526,16 @@ def new_window(boss, window, payload):
|
|||||||
wid = boss.active_window.id
|
wid = boss.active_window.id
|
||||||
if payload['keep_focus'] and old_window:
|
if payload['keep_focus'] and old_window:
|
||||||
boss.set_active_window(old_window)
|
boss.set_active_window(old_window)
|
||||||
return str(wid)
|
return None if payload['no_response'] else str(wid)
|
||||||
|
|
||||||
|
if payload['window_type'] == 'os':
|
||||||
|
boss._new_os_window(w)
|
||||||
|
wid = boss.active_window.id
|
||||||
|
if payload['keep_focus'] and old_window:
|
||||||
|
os_window_id = boss.set_active_window(old_window)
|
||||||
|
if os_window_id:
|
||||||
|
focus_os_window(os_window_id)
|
||||||
|
return None if payload['no_response'] else str(wid)
|
||||||
|
|
||||||
match = payload['match']
|
match = payload['match']
|
||||||
if match:
|
if match:
|
||||||
@@ -440,7 +548,7 @@ def new_window(boss, window, payload):
|
|||||||
w = tab.new_special_window(w)
|
w = tab.new_special_window(w)
|
||||||
if payload['keep_focus'] and old_window:
|
if payload['keep_focus'] and old_window:
|
||||||
boss.set_active_window(old_window)
|
boss.set_active_window(old_window)
|
||||||
return str(w.id)
|
return None if payload['no_response'] else str(w.id)
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
@@ -448,11 +556,19 @@ def new_window(boss, window, payload):
|
|||||||
@cmd(
|
@cmd(
|
||||||
'Focus the specified window',
|
'Focus the specified window',
|
||||||
'Focus the specified window, if no window is specified, focus the window this command is run inside.',
|
'Focus the specified window, if no window is specified, focus the window this command is run inside.',
|
||||||
options_spec=MATCH_WINDOW_OPTION,
|
|
||||||
argspec='',
|
argspec='',
|
||||||
|
options_spec=MATCH_WINDOW_OPTION + '''\n\n
|
||||||
|
--no-response
|
||||||
|
type=bool-set
|
||||||
|
default=false
|
||||||
|
Don't wait for a response from kitty. This means that even if no matching window is found,
|
||||||
|
the command will exit with a success code.
|
||||||
|
'''
|
||||||
)
|
)
|
||||||
def cmd_focus_window(global_opts, opts, args):
|
def cmd_focus_window(global_opts, opts, args):
|
||||||
return {'match': opts.match}
|
if opts.no_response:
|
||||||
|
global_opts.no_command_response = True
|
||||||
|
return {'match': opts.match, 'no_response': opts.no_response}
|
||||||
|
|
||||||
|
|
||||||
def focus_window(boss, window, payload):
|
def focus_window(boss, window, payload):
|
||||||
@@ -464,7 +580,9 @@ def focus_window(boss, window, payload):
|
|||||||
raise MatchError(match)
|
raise MatchError(match)
|
||||||
for window in windows:
|
for window in windows:
|
||||||
if window:
|
if window:
|
||||||
boss.set_active_window(window)
|
os_window_id = boss.set_active_window(window)
|
||||||
|
if os_window_id:
|
||||||
|
focus_os_window(os_window_id, True)
|
||||||
break
|
break
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
@@ -473,10 +591,20 @@ def focus_window(boss, window, payload):
|
|||||||
@cmd(
|
@cmd(
|
||||||
'Focus the specified tab',
|
'Focus the specified tab',
|
||||||
'The active window in the specified tab will be focused.',
|
'The active window in the specified tab will be focused.',
|
||||||
options_spec=MATCH_TAB_OPTION,
|
options_spec=MATCH_TAB_OPTION + '''
|
||||||
|
|
||||||
|
--no-response
|
||||||
|
type=bool-set
|
||||||
|
default=false
|
||||||
|
Don't wait for a response indicating the success of the action. Note that
|
||||||
|
using this option means that you will not be notified of failures.
|
||||||
|
''',
|
||||||
argspec='',
|
argspec='',
|
||||||
|
no_response=True,
|
||||||
)
|
)
|
||||||
def cmd_focus_tab(global_opts, opts, args):
|
def cmd_focus_tab(global_opts, opts, args):
|
||||||
|
if opts.no_response:
|
||||||
|
global_opts.no_command_response = True
|
||||||
return {'match': opts.match}
|
return {'match': opts.match}
|
||||||
|
|
||||||
|
|
||||||
@@ -563,7 +691,7 @@ this option, any color arguments are ignored and --configured and --all are impl
|
|||||||
)
|
)
|
||||||
def cmd_set_colors(global_opts, opts, args):
|
def cmd_set_colors(global_opts, opts, args):
|
||||||
from .rgb import color_as_int, Color
|
from .rgb import color_as_int, Color
|
||||||
colors = {}
|
colors, cursor_text_color = {}, False
|
||||||
if not opts.reset:
|
if not opts.reset:
|
||||||
for spec in args:
|
for spec in args:
|
||||||
if '=' in spec:
|
if '=' in spec:
|
||||||
@@ -571,15 +699,17 @@ def cmd_set_colors(global_opts, opts, args):
|
|||||||
else:
|
else:
|
||||||
with open(os.path.expanduser(spec), encoding='utf-8', errors='replace') as f:
|
with open(os.path.expanduser(spec), encoding='utf-8', errors='replace') as f:
|
||||||
colors.update(parse_config(f))
|
colors.update(parse_config(f))
|
||||||
|
cursor_text_color = colors.pop('cursor_text_color', False)
|
||||||
colors = {k: color_as_int(v) for k, v in colors.items() if isinstance(v, Color)}
|
colors = {k: color_as_int(v) for k, v in colors.items() if isinstance(v, Color)}
|
||||||
return {
|
return {
|
||||||
'title': ' '.join(args), 'match_window': opts.match, 'match_tab': opts.match_tab,
|
'title': ' '.join(args), 'match_window': opts.match, 'match_tab': opts.match_tab,
|
||||||
'all': opts.all or opts.reset, 'configured': opts.configured or opts.reset, 'colors': colors, 'reset': opts.reset
|
'all': opts.all or opts.reset, 'configured': opts.configured or opts.reset,
|
||||||
|
'colors': colors, 'reset': opts.reset, 'cursor_text_color': cursor_text_color
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def set_colors(boss, window, payload):
|
def set_colors(boss, window, payload):
|
||||||
from .rgb import color_as_int
|
from .rgb import color_as_int, Color
|
||||||
if payload['all']:
|
if payload['all']:
|
||||||
windows = tuple(boss.all_windows)
|
windows = tuple(boss.all_windows)
|
||||||
else:
|
else:
|
||||||
@@ -596,10 +726,14 @@ def set_colors(boss, window, payload):
|
|||||||
windows += tuple(tab)
|
windows += tuple(tab)
|
||||||
if payload['reset']:
|
if payload['reset']:
|
||||||
payload['colors'] = {k: color_as_int(v) for k, v in boss.startup_colors.items()}
|
payload['colors'] = {k: color_as_int(v) for k, v in boss.startup_colors.items()}
|
||||||
|
payload['cursor_text_color'] = boss.startup_cursor_text_color
|
||||||
profiles = tuple(w.screen.color_profile for w in windows)
|
profiles = tuple(w.screen.color_profile for w in windows)
|
||||||
from .fast_data_types import patch_color_profiles
|
from .fast_data_types import patch_color_profiles
|
||||||
patch_color_profiles(payload['colors'], profiles, payload['configured'])
|
cursor_text_color = payload.get('cursor_text_color', False)
|
||||||
boss.patch_colors(payload['colors'], payload['configured'])
|
if isinstance(cursor_text_color, (tuple, list, Color)):
|
||||||
|
cursor_text_color = color_as_int(Color(*cursor_text_color))
|
||||||
|
patch_color_profiles(payload['colors'], cursor_text_color, profiles, payload['configured'])
|
||||||
|
boss.patch_colors(payload['colors'], cursor_text_color, payload['configured'])
|
||||||
default_bg_changed = 'background' in payload['colors']
|
default_bg_changed = 'background' in payload['colors']
|
||||||
for w in windows:
|
for w in windows:
|
||||||
if default_bg_changed:
|
if default_bg_changed:
|
||||||
@@ -680,7 +814,34 @@ def set_background_opacity(boss, window, payload):
|
|||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
cmap = {v.name: v for v in globals().values() if hasattr(v, 'is_cmd')}
|
# kitten {{{
|
||||||
|
@cmd(
|
||||||
|
'Run a kitten',
|
||||||
|
'Run a kitten over the specified window (active window by default).'
|
||||||
|
' The :italic:`kitten_name` can be either the name of a builtin kitten'
|
||||||
|
' or the path to a python file containing a custom kitten. If a relative path'
|
||||||
|
' is used it is searched for in the kitty config directory.',
|
||||||
|
options_spec=MATCH_WINDOW_OPTION,
|
||||||
|
argspec='kitten_name',
|
||||||
|
)
|
||||||
|
def cmd_kitten(global_opts, opts, args):
|
||||||
|
if len(args) < 1:
|
||||||
|
raise SystemExit('Must specify kitten name')
|
||||||
|
return {'match': opts.match, 'args': list(args)[1:], 'kitten': args[0]}
|
||||||
|
|
||||||
|
|
||||||
|
def kitten(boss, window, payload):
|
||||||
|
windows = [window or boss.active_window]
|
||||||
|
match = payload['match']
|
||||||
|
if match:
|
||||||
|
windows = tuple(boss.match_windows(match))
|
||||||
|
if not windows:
|
||||||
|
raise MatchError(match)
|
||||||
|
for window in windows:
|
||||||
|
if window:
|
||||||
|
boss._run_kitten(payload['kitten'], args=tuple(payload['args']), window=window)
|
||||||
|
break
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
def cli_params_for(func):
|
def cli_params_for(func):
|
||||||
|
|||||||
@@ -12,8 +12,7 @@
|
|||||||
#include <AvailabilityMacros.h>
|
#include <AvailabilityMacros.h>
|
||||||
// Needed for _NSGetProgname
|
// Needed for _NSGetProgname
|
||||||
#include <crt_externs.h>
|
#include <crt_externs.h>
|
||||||
typedef void* rusage_info_t; // needed for libproc.h
|
#include <objc/runtime.h>
|
||||||
#include <libproc.h>
|
|
||||||
|
|
||||||
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200)
|
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200)
|
||||||
#define NSWindowStyleMaskResizable NSResizableWindowMask
|
#define NSWindowStyleMaskResizable NSResizableWindowMask
|
||||||
@@ -101,6 +100,21 @@ cocoa_set_new_window_trigger(PyObject *self UNUSED, PyObject *args) {
|
|||||||
Py_RETURN_FALSE;
|
Py_RETURN_FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implementation of applicationDockMenu: for the app delegate
|
||||||
|
static NSMenu *dockMenu = nil;
|
||||||
|
static NSMenu *
|
||||||
|
get_dock_menu(id self UNUSED, SEL _cmd UNUSED, NSApplication *sender UNUSED) {
|
||||||
|
if (!dockMenu) {
|
||||||
|
GlobalMenuTarget *global_menu_target = [GlobalMenuTarget shared_instance];
|
||||||
|
dockMenu = [[NSMenu alloc] init];
|
||||||
|
NSMenuItem *newWindowItem = [dockMenu addItemWithTitle:@"New OS window"
|
||||||
|
action:@selector(new_os_window:)
|
||||||
|
keyEquivalent:@""];
|
||||||
|
[newWindowItem setTarget:global_menu_target];
|
||||||
|
}
|
||||||
|
return dockMenu;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
cocoa_create_global_menu(void) {
|
cocoa_create_global_menu(void) {
|
||||||
NSString* app_name = find_app_name();
|
NSString* app_name = find_app_name();
|
||||||
@@ -185,6 +199,12 @@ cocoa_create_global_menu(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[bar release];
|
[bar release];
|
||||||
|
|
||||||
|
class_addMethod(
|
||||||
|
object_getClass([NSApp delegate]),
|
||||||
|
@selector(applicationDockMenu:),
|
||||||
|
(IMP)get_dock_menu,
|
||||||
|
"@@:@");
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -202,19 +222,53 @@ cocoa_update_title(PyObject *pytitle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
cocoa_make_window_resizable(void *w) {
|
cocoa_make_window_resizable(void *w, bool resizable) {
|
||||||
NSWindow *window = (NSWindow*)w;
|
NSWindow *window = (NSWindow*)w;
|
||||||
|
|
||||||
@try {
|
@try {
|
||||||
[window setStyleMask:
|
if (resizable) {
|
||||||
[window styleMask] | NSWindowStyleMaskResizable];
|
[window setStyleMask:
|
||||||
|
[window styleMask] | NSWindowStyleMaskResizable];
|
||||||
|
} else {
|
||||||
|
[window setStyleMask:
|
||||||
|
[window styleMask] & ~NSWindowStyleMaskResizable];
|
||||||
|
}
|
||||||
} @catch (NSException *e) {
|
} @catch (NSException *e) {
|
||||||
return PyErr_Format(PyExc_ValueError, "Failed to set style mask: %s: %s", [[e name] UTF8String], [[e reason] UTF8String]);
|
log_error("Failed to set style mask: %s: %s", [[e name] UTF8String], [[e reason] UTF8String]);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
cocoa_focus_window(void *w) {
|
||||||
|
NSWindow *window = (NSWindow*)w;
|
||||||
|
[window makeKeyWindow];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
cocoa_toggle_fullscreen(void *w, bool traditional) {
|
||||||
|
NSWindow *window = (NSWindow*)w;
|
||||||
|
bool made_fullscreen = true;
|
||||||
|
NSWindowStyleMask sm = [window styleMask];
|
||||||
|
bool in_fullscreen = sm & NSWindowStyleMaskFullScreen;
|
||||||
|
if (traditional) {
|
||||||
|
if (!(in_fullscreen)) {
|
||||||
|
sm |= NSWindowStyleMaskBorderless | NSWindowStyleMaskFullScreen;
|
||||||
|
[[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock];
|
||||||
|
} else {
|
||||||
|
made_fullscreen = false;
|
||||||
|
sm &= ~(NSWindowStyleMaskBorderless | NSWindowStyleMaskFullScreen);
|
||||||
|
[[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationDefault];
|
||||||
|
}
|
||||||
|
[window setStyleMask: sm];
|
||||||
|
} else {
|
||||||
|
if (in_fullscreen) made_fullscreen = false;
|
||||||
|
[window toggleFullScreen: nil];
|
||||||
|
}
|
||||||
|
return made_fullscreen;
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
cocoa_get_lang(PyObject UNUSED *self) {
|
cocoa_get_lang(PyObject UNUSED *self) {
|
||||||
NSString* locale = nil;
|
NSString* locale = nil;
|
||||||
@@ -229,15 +283,6 @@ cocoa_get_lang(PyObject UNUSED *self) {
|
|||||||
return Py_BuildValue("s", [locale UTF8String]);
|
return Py_BuildValue("s", [locale UTF8String]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject*
|
|
||||||
cwd_of_process(PyObject *self UNUSED, PyObject *pid_) {
|
|
||||||
long pid = PyLong_AsLong(pid_);
|
|
||||||
struct proc_vnodepathinfo vpi;
|
|
||||||
int ret = proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi));
|
|
||||||
if (ret < 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
|
||||||
return PyUnicode_FromString(vpi.pvi_cdir.vip_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
cocoa_set_hide_from_tasks(void) {
|
cocoa_set_hide_from_tasks(void) {
|
||||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
|
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
|
||||||
@@ -269,9 +314,14 @@ cocoa_set_titlebar_color(void *w, color_type titlebar_color)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
cleanup() {
|
||||||
|
if (dockMenu) [dockMenu release];
|
||||||
|
dockMenu = nil;
|
||||||
|
}
|
||||||
|
|
||||||
static PyMethodDef module_methods[] = {
|
static PyMethodDef module_methods[] = {
|
||||||
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
|
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
|
||||||
{"cwd_of_process", (PyCFunction)cwd_of_process, METH_O, ""},
|
|
||||||
{"cocoa_set_new_window_trigger", (PyCFunction)cocoa_set_new_window_trigger, METH_VARARGS, ""},
|
{"cocoa_set_new_window_trigger", (PyCFunction)cocoa_set_new_window_trigger, METH_VARARGS, ""},
|
||||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
@@ -279,5 +329,9 @@ static PyMethodDef module_methods[] = {
|
|||||||
bool
|
bool
|
||||||
init_cocoa(PyObject *module) {
|
init_cocoa(PyObject *module) {
|
||||||
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
|
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
|
||||||
|
if (Py_AtExit(cleanup) != 0) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "Failed to register the cocoa_window at exit handler");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,8 +100,8 @@ update_ansi_color_table(ColorProfile *self, PyObject *val) {
|
|||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
patch_color_profiles(PyObject *module UNUSED, PyObject *args) {
|
patch_color_profiles(PyObject *module UNUSED, PyObject *args) {
|
||||||
PyObject *spec, *profiles, *v; ColorProfile *self; int change_configured;
|
PyObject *spec, *profiles, *v; ColorProfile *self; int change_configured; PyObject *cursor_text_color;
|
||||||
if (!PyArg_ParseTuple(args, "O!O!p", &PyDict_Type, &spec, &PyTuple_Type, &profiles, &change_configured)) return NULL;
|
if (!PyArg_ParseTuple(args, "O!OO!p", &PyDict_Type, &spec, &cursor_text_color, &PyTuple_Type, &profiles, &change_configured)) return NULL;
|
||||||
char key[32] = {0};
|
char key[32] = {0};
|
||||||
for (size_t i = 0; i < arraysz(FG_BG_256); i++) {
|
for (size_t i = 0; i < arraysz(FG_BG_256); i++) {
|
||||||
snprintf(key, sizeof(key) - 1, "color%zu", i);
|
snprintf(key, sizeof(key) - 1, "color%zu", i);
|
||||||
@@ -131,6 +131,22 @@ patch_color_profiles(PyObject *module UNUSED, PyObject *args) {
|
|||||||
S(foreground, default_fg); S(background, default_bg); S(cursor, cursor_color);
|
S(foreground, default_fg); S(background, default_bg); S(cursor, cursor_color);
|
||||||
S(selection_foreground, highlight_fg); S(selection_background, highlight_bg);
|
S(selection_foreground, highlight_fg); S(selection_background, highlight_bg);
|
||||||
#undef S
|
#undef S
|
||||||
|
if (cursor_text_color != Py_False) {
|
||||||
|
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(profiles); i++) {
|
||||||
|
self = (ColorProfile*)PyTuple_GET_ITEM(profiles, i);
|
||||||
|
self->overridden.cursor_text_color = 0x111111;
|
||||||
|
self->overridden.cursor_text_uses_bg = 3;
|
||||||
|
if (cursor_text_color != Py_None) {
|
||||||
|
self->overridden.cursor_text_color = (PyLong_AsUnsignedLong(cursor_text_color) << 8) | 2;
|
||||||
|
self->overridden.cursor_text_uses_bg = 1;
|
||||||
|
}
|
||||||
|
if (change_configured) {
|
||||||
|
self->configured.cursor_text_color = self->overridden.cursor_text_color;
|
||||||
|
self->configured.cursor_text_uses_bg = self->overridden.cursor_text_uses_bg;
|
||||||
|
}
|
||||||
|
self->dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
@@ -149,6 +165,14 @@ colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float
|
||||||
|
cursor_text_as_bg(ColorProfile *self) {
|
||||||
|
if (self->overridden.cursor_text_uses_bg & 1) {
|
||||||
|
return self->overridden.cursor_text_uses_bg & 2 ? 1.f : 0.f;
|
||||||
|
}
|
||||||
|
return self->configured.cursor_text_uses_bg & 2 ? 1.f : 0.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
as_dict(ColorProfile *self, PyObject *args UNUSED) {
|
as_dict(ColorProfile *self, PyObject *args UNUSED) {
|
||||||
@@ -174,7 +198,7 @@ as_dict(ColorProfile *self, PyObject *args UNUSED) {
|
|||||||
if (ret != 0) { Py_CLEAR(ans); return NULL; } \
|
if (ret != 0) { Py_CLEAR(ans); return NULL; } \
|
||||||
}}
|
}}
|
||||||
D(default_fg, foreground); D(default_bg, background);
|
D(default_fg, foreground); D(default_bg, background);
|
||||||
D(cursor_color, cursor); D(highlight_fg, selection_foreground);
|
D(cursor_color, cursor); D(cursor_text_color, cursor_text); D(highlight_fg, selection_foreground);
|
||||||
D(highlight_bg, selection_background);
|
D(highlight_bg, selection_background);
|
||||||
|
|
||||||
#undef D
|
#undef D
|
||||||
@@ -236,7 +260,11 @@ set_color(ColorProfile *self, PyObject *args) {
|
|||||||
static PyObject*
|
static PyObject*
|
||||||
set_configured_colors(ColorProfile *self, PyObject *args) {
|
set_configured_colors(ColorProfile *self, PyObject *args) {
|
||||||
#define set_configured_colors_doc "Set the configured colors"
|
#define set_configured_colors_doc "Set the configured colors"
|
||||||
if (!PyArg_ParseTuple(args, "II|III", &(self->configured.default_fg), &(self->configured.default_bg), &(self->configured.cursor_color), &(self->configured.highlight_fg), &(self->configured.highlight_bg))) return NULL;
|
if (!PyArg_ParseTuple(
|
||||||
|
args, "II|IIIII",
|
||||||
|
&(self->configured.default_fg), &(self->configured.default_bg),
|
||||||
|
&(self->configured.cursor_color), &(self->configured.cursor_text_color), &(self->configured.cursor_text_uses_bg),
|
||||||
|
&(self->configured.highlight_fg), &(self->configured.highlight_bg))) return NULL;
|
||||||
self->dirty = true;
|
self->dirty = true;
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
@@ -286,6 +314,7 @@ default_color_table(PyObject *self UNUSED, PyObject *args UNUSED) {
|
|||||||
CGETSET(default_fg)
|
CGETSET(default_fg)
|
||||||
CGETSET(default_bg)
|
CGETSET(default_bg)
|
||||||
CGETSET(cursor_color)
|
CGETSET(cursor_color)
|
||||||
|
CGETSET(cursor_text_color)
|
||||||
CGETSET(highlight_fg)
|
CGETSET(highlight_fg)
|
||||||
CGETSET(highlight_bg)
|
CGETSET(highlight_bg)
|
||||||
|
|
||||||
@@ -293,6 +322,7 @@ static PyGetSetDef getsetters[] = {
|
|||||||
GETSET(default_fg)
|
GETSET(default_fg)
|
||||||
GETSET(default_bg)
|
GETSET(default_bg)
|
||||||
GETSET(cursor_color)
|
GETSET(cursor_color)
|
||||||
|
GETSET(cursor_text_color)
|
||||||
GETSET(highlight_fg)
|
GETSET(highlight_fg)
|
||||||
GETSET(highlight_bg)
|
GETSET(highlight_bg)
|
||||||
{NULL} /* Sentinel */
|
{NULL} /* Sentinel */
|
||||||
|
|||||||
384
kitty/complete.py
Normal file
384
kitty/complete.py
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
# License: GPLv3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shlex
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from kittens.runner import get_kitten_cli_docs, all_kitten_names
|
||||||
|
|
||||||
|
from .cli import options_for_completion, parse_option_spec
|
||||||
|
from .cmds import cmap
|
||||||
|
from .shell import options_for_cmd
|
||||||
|
|
||||||
|
parsers, serializers = {}, {}
|
||||||
|
|
||||||
|
|
||||||
|
def debug(*a, **kw):
|
||||||
|
kw['file'] = sys.stderr
|
||||||
|
print(*a, **kw)
|
||||||
|
|
||||||
|
|
||||||
|
class Completions:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.match_groups = {}
|
||||||
|
self.no_space_groups = set()
|
||||||
|
self.files_groups = set()
|
||||||
|
|
||||||
|
|
||||||
|
# Shell specific code {{{
|
||||||
|
|
||||||
|
|
||||||
|
completion_scripts = {
|
||||||
|
'zsh': '''
|
||||||
|
_kitty() {
|
||||||
|
local src
|
||||||
|
# Send all words up to the word the cursor is currently on
|
||||||
|
src=$(printf "%s\n" "${(@)words[1,$CURRENT]}" | kitty +complete zsh)
|
||||||
|
if [[ $? == 0 ]]; then
|
||||||
|
eval ${src}
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
compdef _kitty kitty
|
||||||
|
''',
|
||||||
|
'bash': '''
|
||||||
|
_kitty_completions() {
|
||||||
|
local src
|
||||||
|
local limit
|
||||||
|
# Send all words up to the word the cursor is currently on
|
||||||
|
let limit=1+$COMP_CWORD
|
||||||
|
src=$(printf "%s\n" "${COMP_WORDS[@]: 0:$limit}" | kitty +complete bash)
|
||||||
|
if [[ $? == 0 ]]; then
|
||||||
|
eval ${src}
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
complete -o nospace -F _kitty_completions kitty
|
||||||
|
''',
|
||||||
|
'fish': '''
|
||||||
|
function __kitty_completions
|
||||||
|
# Send all words up to the one before the cursor
|
||||||
|
commandline -cop | kitty +complete fish
|
||||||
|
end
|
||||||
|
|
||||||
|
complete -f -c kitty -a "(__kitty_completions)"
|
||||||
|
''',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def input_parser(func):
|
||||||
|
name = func.__name__.split('_')[0]
|
||||||
|
parsers[name] = func
|
||||||
|
return func
|
||||||
|
|
||||||
|
|
||||||
|
def output_serializer(func):
|
||||||
|
name = func.__name__.split('_')[0]
|
||||||
|
serializers[name] = func
|
||||||
|
return func
|
||||||
|
|
||||||
|
|
||||||
|
@input_parser
|
||||||
|
def zsh_input_parser(data):
|
||||||
|
new_word = data.endswith('\n\n')
|
||||||
|
words = data.rstrip().splitlines()
|
||||||
|
return words, new_word
|
||||||
|
|
||||||
|
|
||||||
|
@input_parser
|
||||||
|
def bash_input_parser(data):
|
||||||
|
new_word = data.endswith('\n\n')
|
||||||
|
words = data.rstrip().splitlines()
|
||||||
|
return words, new_word
|
||||||
|
|
||||||
|
|
||||||
|
@input_parser
|
||||||
|
def fish_input_parser(data):
|
||||||
|
return data.rstrip().splitlines(), True
|
||||||
|
|
||||||
|
|
||||||
|
@output_serializer
|
||||||
|
def zsh_output_serializer(ans):
|
||||||
|
lines = []
|
||||||
|
for description, matches in ans.match_groups.items():
|
||||||
|
cmd = ['compadd', '-U', '-J', shlex.quote(description), '-X', shlex.quote(description)]
|
||||||
|
if description in ans.no_space_groups:
|
||||||
|
cmd += ['-S', '""']
|
||||||
|
if description in ans.files_groups:
|
||||||
|
cmd.append('-f')
|
||||||
|
common_prefix = os.path.commonprefix(tuple(matches))
|
||||||
|
if common_prefix:
|
||||||
|
cmd.extend(('-p', shlex.quote(common_prefix)))
|
||||||
|
matches = {k[len(common_prefix):]: v for k, v in matches.items()}
|
||||||
|
cmd.append('--')
|
||||||
|
for word, description in matches.items():
|
||||||
|
cmd.append(shlex.quote(word))
|
||||||
|
lines.append(' '.join(cmd) + ';')
|
||||||
|
# debug('\n'.join(lines))
|
||||||
|
return '\n'.join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
@output_serializer
|
||||||
|
def bash_output_serializer(ans):
|
||||||
|
lines = []
|
||||||
|
for description, matches in ans.match_groups.items():
|
||||||
|
needs_space = description not in ans.no_space_groups
|
||||||
|
for word in matches:
|
||||||
|
if needs_space:
|
||||||
|
word += ' '
|
||||||
|
lines.append('COMPREPLY+=({})'.format(shlex.quote(word)))
|
||||||
|
# debug('\n'.join(lines))
|
||||||
|
return '\n'.join(lines)
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
|
@output_serializer
|
||||||
|
def fish_output_serializer(ans):
|
||||||
|
lines = []
|
||||||
|
for matches in ans.match_groups.values():
|
||||||
|
for word in matches:
|
||||||
|
lines.append(shlex.quote(word))
|
||||||
|
# debug('\n'.join(lines))
|
||||||
|
return '\n'.join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def completions_for_first_word(ans, prefix, entry_points, namespaced_entry_points):
|
||||||
|
cmds = ['@' + c for c in cmap]
|
||||||
|
ans.match_groups['Entry points'] = {
|
||||||
|
k: None for k in
|
||||||
|
list(entry_points) + cmds + ['+' + k for k in namespaced_entry_points]
|
||||||
|
if not prefix or k.startswith(prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def kitty_cli_opts(ans, prefix=None):
|
||||||
|
matches = {}
|
||||||
|
for opt in options_for_completion():
|
||||||
|
if isinstance(opt, str):
|
||||||
|
continue
|
||||||
|
aliases = frozenset(x for x in opt['aliases'] if x.startswith(prefix)) if prefix else opt['aliases']
|
||||||
|
for alias in aliases:
|
||||||
|
matches[alias] = opt['help'].strip()
|
||||||
|
ans.match_groups['Options'] = matches
|
||||||
|
|
||||||
|
|
||||||
|
def complete_kitty_cli_arg(ans, opt, prefix):
|
||||||
|
prefix = prefix or ''
|
||||||
|
if opt and opt['dest'] == 'override':
|
||||||
|
from kitty.config import option_names_for_completion
|
||||||
|
k = 'Config directives'
|
||||||
|
ans.match_groups[k] = {k+'=': None for k in option_names_for_completion() if k.startswith(prefix)}
|
||||||
|
ans.no_space_groups.add(k)
|
||||||
|
|
||||||
|
|
||||||
|
def complete_alias_map(ans, words, new_word, option_map, complete_args=None):
|
||||||
|
expecting_arg = False
|
||||||
|
opt = None
|
||||||
|
last_word = words[-1] if words else ''
|
||||||
|
for w in words:
|
||||||
|
if expecting_arg:
|
||||||
|
if w is last_word and not new_word:
|
||||||
|
if opt is not None:
|
||||||
|
complete_args(ans, opt, w)
|
||||||
|
return
|
||||||
|
expecting_arg = False
|
||||||
|
continue
|
||||||
|
opt = option_map.get(w)
|
||||||
|
if w is last_word and not new_word:
|
||||||
|
if w.startswith('-'):
|
||||||
|
ans.match_groups['Options'] = {k: opt['help'] for k, opt in option_map.items() if k.startswith(last_word)}
|
||||||
|
else:
|
||||||
|
if complete_args is not None:
|
||||||
|
complete_args(ans, None, last_word)
|
||||||
|
return
|
||||||
|
if opt is None:
|
||||||
|
if complete_args is not None:
|
||||||
|
complete_args(ans, None, '' if new_word else last_word)
|
||||||
|
return # some non-option word encountered
|
||||||
|
expecting_arg = not opt.get('type', '').startswith('bool-')
|
||||||
|
if expecting_arg:
|
||||||
|
if opt is not None and complete_args is not None:
|
||||||
|
complete_args(ans, opt, '' if new_word else last_word)
|
||||||
|
else:
|
||||||
|
prefix = '' if new_word else last_word
|
||||||
|
if complete_args is not None:
|
||||||
|
complete_args(ans, None, prefix)
|
||||||
|
ans.match_groups['Options'] = {k: opt['help'] for k, opt in option_map.items() if k.startswith(prefix)}
|
||||||
|
|
||||||
|
|
||||||
|
def complete_cli(ans, words, new_word, seq, complete_args=lambda *a: None):
|
||||||
|
option_map = {}
|
||||||
|
for opt in seq:
|
||||||
|
if not isinstance(opt, str):
|
||||||
|
for alias in opt['aliases']:
|
||||||
|
option_map[alias] = opt
|
||||||
|
complete_alias_map(ans, words, new_word, option_map, complete_args)
|
||||||
|
|
||||||
|
|
||||||
|
def complete_remote_command(ans, cmd_name, words, new_word):
|
||||||
|
aliases, alias_map = options_for_cmd(cmd_name)
|
||||||
|
if not alias_map:
|
||||||
|
return
|
||||||
|
complete_alias_map(ans, words, new_word, alias_map)
|
||||||
|
|
||||||
|
|
||||||
|
def path_completion(prefix=''):
|
||||||
|
prefix = prefix.replace(r'\ ', ' ')
|
||||||
|
dirs, files = [], []
|
||||||
|
base = '.'
|
||||||
|
if prefix.endswith('/'):
|
||||||
|
base = prefix
|
||||||
|
elif '/' in prefix:
|
||||||
|
base = os.path.dirname(prefix)
|
||||||
|
src = os.path.expandvars(os.path.expanduser(base))
|
||||||
|
src_prefix = os.path.abspath(os.path.expandvars(os.path.expanduser(prefix))) if prefix else ''
|
||||||
|
try:
|
||||||
|
items = os.scandir(src)
|
||||||
|
except FileNotFoundError:
|
||||||
|
items = ()
|
||||||
|
for x in items:
|
||||||
|
abspath = os.path.abspath(x.path)
|
||||||
|
if prefix and not abspath.startswith(src_prefix):
|
||||||
|
continue
|
||||||
|
if prefix:
|
||||||
|
q = prefix + abspath[len(src_prefix):].lstrip(os.sep)
|
||||||
|
q = os.path.expandvars(os.path.expanduser(q))
|
||||||
|
else:
|
||||||
|
q = os.path.relpath(abspath)
|
||||||
|
if x.is_dir():
|
||||||
|
dirs.append(q.rstrip(os.sep) + os.sep)
|
||||||
|
else:
|
||||||
|
files.append(q)
|
||||||
|
return dirs, files
|
||||||
|
|
||||||
|
|
||||||
|
def complete_files_and_dirs(ans, prefix, files_group_name='Files', predicate=None):
|
||||||
|
dirs, files = path_completion(prefix or '')
|
||||||
|
files = filter(predicate, files)
|
||||||
|
|
||||||
|
if dirs:
|
||||||
|
ans.match_groups['Directories'] = dict.fromkeys(dirs)
|
||||||
|
ans.files_groups.add('Directories'), ans.no_space_groups.add('Directories')
|
||||||
|
if files:
|
||||||
|
ans.match_groups[files_group_name] = dict.fromkeys(files)
|
||||||
|
ans.files_groups.add(files_group_name)
|
||||||
|
|
||||||
|
|
||||||
|
def complete_icat_args(ans, opt, prefix):
|
||||||
|
from mimetypes import guess_type
|
||||||
|
|
||||||
|
def icat_file_predicate(filename):
|
||||||
|
mt = guess_type(filename)[0]
|
||||||
|
if mt and mt.startswith('image/'):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if opt is None:
|
||||||
|
complete_files_and_dirs(ans, prefix, 'Images', icat_file_predicate)
|
||||||
|
|
||||||
|
|
||||||
|
def config_file_predicate(filename):
|
||||||
|
return filename.endswith('.conf')
|
||||||
|
|
||||||
|
|
||||||
|
def complete_diff_args(ans, opt, prefix):
|
||||||
|
if opt is None:
|
||||||
|
complete_files_and_dirs(ans, prefix, 'Files')
|
||||||
|
elif opt['dest'] == 'config':
|
||||||
|
complete_files_and_dirs(ans, prefix, 'Config Files', config_file_predicate)
|
||||||
|
|
||||||
|
|
||||||
|
def complete_kitten(ans, kitten, words, new_word):
|
||||||
|
try:
|
||||||
|
cd = get_kitten_cli_docs(kitten)
|
||||||
|
except SystemExit:
|
||||||
|
cd = None
|
||||||
|
if cd is None:
|
||||||
|
return
|
||||||
|
options = cd['options']()
|
||||||
|
seq = parse_option_spec(options)[0]
|
||||||
|
option_map = {}
|
||||||
|
for opt in seq:
|
||||||
|
if not isinstance(opt, str):
|
||||||
|
for alias in opt['aliases']:
|
||||||
|
option_map[alias] = opt
|
||||||
|
complete_alias_map(ans, words, new_word, option_map, {
|
||||||
|
'icat': complete_icat_args,
|
||||||
|
'diff': complete_diff_args,
|
||||||
|
}.get(kitten))
|
||||||
|
|
||||||
|
|
||||||
|
def find_completions(words, new_word, entry_points, namespaced_entry_points):
|
||||||
|
ans = Completions()
|
||||||
|
if not words or words[0] != 'kitty':
|
||||||
|
return ans
|
||||||
|
words = words[1:]
|
||||||
|
if not words or (len(words) == 1 and not new_word):
|
||||||
|
prefix = words[0] if words else ''
|
||||||
|
completions_for_first_word(ans, prefix, entry_points, namespaced_entry_points)
|
||||||
|
kitty_cli_opts(ans, prefix)
|
||||||
|
return ans
|
||||||
|
if words[0] == '@':
|
||||||
|
if len(words) == 1 or (len(words) == 2 and not new_word):
|
||||||
|
prefix = words[1] if len(words) > 1 else ''
|
||||||
|
ans.match_groups['Remote control commands'] = {c: None for c in cmap if c.startswith(prefix)}
|
||||||
|
else:
|
||||||
|
complete_remote_command(ans, words[1], words[2:], new_word)
|
||||||
|
return ans
|
||||||
|
if words[0].startswith('@'):
|
||||||
|
if len(words) == 1 and not new_word:
|
||||||
|
prefix = words[0]
|
||||||
|
ans.match_groups['Remote control commands'] = {'@' + c: None for c in cmap if c.startswith(prefix)}
|
||||||
|
else:
|
||||||
|
complete_remote_command(ans, words[0][1:], words[1:], new_word)
|
||||||
|
if words[0] == '+':
|
||||||
|
if len(words) == 1 or (len(words) == 2 and not new_word):
|
||||||
|
prefix = words[1] if len(words) > 1 else ''
|
||||||
|
ans.match_groups['Entry points'] = {c: None for c in namespaced_entry_points if c.startswith(prefix)}
|
||||||
|
else:
|
||||||
|
if words[1] == 'kitten':
|
||||||
|
if len(words) == 2 or (len(words) == 3 and not new_word):
|
||||||
|
ans.match_groups['Kittens'] = dict.fromkeys(k for k in all_kitten_names() if k.startswith('' if len(words) == 2 else words[2]))
|
||||||
|
else:
|
||||||
|
complete_kitten(ans, words[2], words[3:], new_word)
|
||||||
|
return ans
|
||||||
|
if words[0].startswith('+'):
|
||||||
|
if len(words) == 1:
|
||||||
|
if new_word:
|
||||||
|
if words[0] == '+kitten':
|
||||||
|
ans.match_groups['Kittens'] = dict.fromkeys(all_kitten_names())
|
||||||
|
else:
|
||||||
|
prefix = words[0]
|
||||||
|
ans.match_groups['Entry points'] = {c: None for c in namespaced_entry_points if c.startswith(prefix)}
|
||||||
|
else:
|
||||||
|
if len(words) == 2 and not new_word:
|
||||||
|
ans.match_groups['Kittens'] = dict.fromkeys(k for k in all_kitten_names() if k.startswith(words[1]))
|
||||||
|
else:
|
||||||
|
if words[0] == '+kitten':
|
||||||
|
complete_kitten(ans, words[1], words[2:], new_word)
|
||||||
|
else:
|
||||||
|
complete_cli(ans, words, new_word, options_for_completion(), complete_kitty_cli_arg)
|
||||||
|
|
||||||
|
return ans
|
||||||
|
|
||||||
|
|
||||||
|
def setup(cstyle):
|
||||||
|
print(completion_scripts[cstyle])
|
||||||
|
|
||||||
|
|
||||||
|
def main(args, entry_points, namespaced_entry_points):
|
||||||
|
if not args:
|
||||||
|
raise SystemExit('Must specify completion style')
|
||||||
|
cstyle = args[0]
|
||||||
|
if cstyle == 'setup':
|
||||||
|
return setup(args[1])
|
||||||
|
data = sys.stdin.read()
|
||||||
|
try:
|
||||||
|
parser = parsers[cstyle]
|
||||||
|
serializer = serializers[cstyle]
|
||||||
|
except KeyError:
|
||||||
|
raise SystemExit('Unknown completion style: {}'.format(cstyle))
|
||||||
|
words, new_word = parser(data)
|
||||||
|
ans = find_completions(words, new_word, entry_points, namespaced_entry_points)
|
||||||
|
print(serializer(ans), end='')
|
||||||
@@ -119,7 +119,16 @@ def merged_opts(all_options, opt, i):
|
|||||||
|
|
||||||
|
|
||||||
def remove_markup(text):
|
def remove_markup(text):
|
||||||
return re.sub(r':([a-zA-Z0-9]+):`(.+?)`', r'\2', text, flags=re.DOTALL)
|
|
||||||
|
def sub(m):
|
||||||
|
if m.group(1) == 'ref':
|
||||||
|
return {
|
||||||
|
'layouts': 'https://sw.kovidgoyal.net/kitty/index.html#layouts',
|
||||||
|
'sessions': 'https://sw.kovidgoyal.net/kitty/index.html#sessions',
|
||||||
|
}[m.group(2)]
|
||||||
|
return m.group(2)
|
||||||
|
|
||||||
|
return re.sub(r':([a-zA-Z0-9]+):`(.+?)`', sub, text, flags=re.DOTALL)
|
||||||
|
|
||||||
|
|
||||||
def iter_blocks(lines):
|
def iter_blocks(lines):
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
# vim:fileencoding=utf-8
|
# vim:fileencoding=utf-8
|
||||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
import ast
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
@@ -40,6 +39,7 @@ def to_cmdline(x):
|
|||||||
|
|
||||||
|
|
||||||
def python_string(text):
|
def python_string(text):
|
||||||
|
import ast
|
||||||
return ast.literal_eval("'''" + text.replace("'''", "'\\''") + "'''")
|
return ast.literal_eval("'''" + text.replace("'''", "'\\''") + "'''")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
195
kitty/config.py
195
kitty/config.py
@@ -16,7 +16,7 @@ from .conf.utils import (
|
|||||||
parse_config_base, python_string, to_bool, to_cmdline
|
parse_config_base, python_string, to_bool, to_cmdline
|
||||||
)
|
)
|
||||||
from .config_data import all_options, parse_mods, type_map
|
from .config_data import all_options, parse_mods, type_map
|
||||||
from .constants import cache_dir, defconf
|
from .constants import cache_dir, defconf, is_macos
|
||||||
from .utils import log_error
|
from .utils import log_error
|
||||||
|
|
||||||
named_keys = {
|
named_keys = {
|
||||||
@@ -35,14 +35,22 @@ named_keys = {
|
|||||||
|
|
||||||
def parse_shortcut(sc):
|
def parse_shortcut(sc):
|
||||||
parts = sc.split('+')
|
parts = sc.split('+')
|
||||||
mods = parse_mods(parts[:-1], sc)
|
mods = 0
|
||||||
if mods is None:
|
if len(parts) > 1:
|
||||||
return None, None, None
|
mods = parse_mods(parts[:-1], sc)
|
||||||
|
if mods is None:
|
||||||
|
return None, None, None
|
||||||
key = parts[-1].upper()
|
key = parts[-1].upper()
|
||||||
key = getattr(defines, 'GLFW_KEY_' + named_keys.get(key, key), None)
|
key = getattr(defines, 'GLFW_KEY_' + named_keys.get(key, key), None)
|
||||||
is_native = False
|
is_native = False
|
||||||
if key is None:
|
if key is None:
|
||||||
key = defines.key_for_native_key_name(parts[-1])
|
if parts[-1].startswith('0x'):
|
||||||
|
try:
|
||||||
|
key = int(parts[-1], 16)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
key = defines.key_for_native_key_name(parts[-1])
|
||||||
is_native = key is not None
|
is_native = key is not None
|
||||||
return mods, is_native, key
|
return mods, is_native, key
|
||||||
|
|
||||||
@@ -107,7 +115,7 @@ def float_parse(func, rest):
|
|||||||
|
|
||||||
@func_with_args('change_font_size')
|
@func_with_args('change_font_size')
|
||||||
def parse_change_font_size(func, rest):
|
def parse_change_font_size(func, rest):
|
||||||
vals = rest.split(' ', 1)
|
vals = rest.strip().split(' ', 1)
|
||||||
if len(vals) != 2:
|
if len(vals) != 2:
|
||||||
log_error('Invalid change_font_size specification: {}, treating it as default'.format(rest))
|
log_error('Invalid change_font_size specification: {}, treating it as default'.format(rest))
|
||||||
args = [True, None, 0]
|
args = [True, None, 0]
|
||||||
@@ -117,12 +125,87 @@ def parse_change_font_size(func, rest):
|
|||||||
if amt[0] in '+-':
|
if amt[0] in '+-':
|
||||||
args[1] = amt[0]
|
args[1] = amt[0]
|
||||||
amt = amt[1:]
|
amt = amt[1:]
|
||||||
args[2] = float(amt)
|
args[2] = float(amt.strip())
|
||||||
return func, args
|
return func, args
|
||||||
|
|
||||||
|
|
||||||
|
@func_with_args('clear_terminal')
|
||||||
|
def clear_terminal(func, rest):
|
||||||
|
vals = rest.strip().split(' ', 1)
|
||||||
|
if len(vals) != 2:
|
||||||
|
log_error('clear_terminal needs two arguments, using defaults')
|
||||||
|
args = ['reset', 'active']
|
||||||
|
else:
|
||||||
|
args = [vals[0].lower(), vals[1].lower() == 'active']
|
||||||
|
return func, args
|
||||||
|
|
||||||
|
|
||||||
|
@func_with_args('neighboring_window')
|
||||||
|
def neighboring_window(func, rest):
|
||||||
|
rest = rest.lower()
|
||||||
|
rest = {'up': 'top', 'down': 'bottom'}.get(rest, rest)
|
||||||
|
if rest not in ('left', 'right', 'top', 'bottom'):
|
||||||
|
log_error('Invalid neighbor specification: {}'.format(rest))
|
||||||
|
rest = 'right'
|
||||||
|
return func, [rest]
|
||||||
|
|
||||||
|
|
||||||
|
@func_with_args('resize_window')
|
||||||
|
def resize_window(func, rest):
|
||||||
|
vals = rest.strip().split(' ', 1)
|
||||||
|
if len(vals) > 2:
|
||||||
|
log_error('resize_window needs one or two arguments, using defaults')
|
||||||
|
args = ['wider', 1]
|
||||||
|
else:
|
||||||
|
quality = vals[0].lower()
|
||||||
|
if quality not in ('taller', 'shorter', 'wider', 'narrower'):
|
||||||
|
log_error('Invalid quality specification: {}'.format(quality))
|
||||||
|
quality = 'wider'
|
||||||
|
increment = 1
|
||||||
|
if len(vals) == 2:
|
||||||
|
try:
|
||||||
|
increment = int(vals[1])
|
||||||
|
except Exception:
|
||||||
|
log_error('Invalid increment specification: {}'.format(vals[1]))
|
||||||
|
args = [quality, increment]
|
||||||
|
return func, args
|
||||||
|
|
||||||
|
|
||||||
|
@func_with_args('move_window')
|
||||||
|
def move_window(func, rest):
|
||||||
|
rest = rest.lower()
|
||||||
|
rest = {'up': 'top', 'down': 'bottom'}.get(rest, rest)
|
||||||
|
try:
|
||||||
|
rest = int(rest)
|
||||||
|
except Exception:
|
||||||
|
if rest not in ('left', 'right', 'top', 'bottom'):
|
||||||
|
log_error('Invalid move_window specification: {}'.format(rest))
|
||||||
|
rest = 0
|
||||||
|
return func, [rest]
|
||||||
|
|
||||||
|
|
||||||
|
@func_with_args('pipe')
|
||||||
|
def pipe(func, rest):
|
||||||
|
import shlex
|
||||||
|
rest = shlex.split(rest)
|
||||||
|
if len(rest) < 3:
|
||||||
|
log_error('Too few arguments to pipe function')
|
||||||
|
rest = ['none', 'none', 'true']
|
||||||
|
return func, rest
|
||||||
|
|
||||||
|
|
||||||
|
@func_with_args('nth_window')
|
||||||
|
def nth_window(func, rest):
|
||||||
|
try:
|
||||||
|
num = int(rest)
|
||||||
|
except Exception:
|
||||||
|
log_error('Invalid nth_window number: {}'.format(rest))
|
||||||
|
num = 1
|
||||||
|
return func, [num]
|
||||||
|
|
||||||
|
|
||||||
def parse_key_action(action):
|
def parse_key_action(action):
|
||||||
parts = action.split(' ', 1)
|
parts = action.strip().split(' ', 1)
|
||||||
func = parts[0]
|
func = parts[0]
|
||||||
if len(parts) == 1:
|
if len(parts) == 1:
|
||||||
return KeyAction(func, ())
|
return KeyAction(func, ())
|
||||||
@@ -133,7 +216,8 @@ def parse_key_action(action):
|
|||||||
func, args = parser(func, rest)
|
func, args = parser(func, rest)
|
||||||
except Exception:
|
except Exception:
|
||||||
log_error('Ignoring invalid key action: {}'.format(action))
|
log_error('Ignoring invalid key action: {}'.format(action))
|
||||||
return KeyAction(func, args)
|
else:
|
||||||
|
return KeyAction(func, args)
|
||||||
|
|
||||||
|
|
||||||
all_key_actions = set()
|
all_key_actions = set()
|
||||||
@@ -245,28 +329,91 @@ def parse_send_text(val, key_definitions):
|
|||||||
return parse_key(key_str, key_definitions)
|
return parse_key(key_str, key_definitions)
|
||||||
|
|
||||||
|
|
||||||
|
special_handlers = {}
|
||||||
|
|
||||||
|
|
||||||
|
def special_handler(func):
|
||||||
|
special_handlers[func.__name__.partition('_')[2]] = func
|
||||||
|
return func
|
||||||
|
|
||||||
|
|
||||||
|
def deprecated_handler(*names):
|
||||||
|
def special_handler(func):
|
||||||
|
for name in names:
|
||||||
|
special_handlers[name] = func
|
||||||
|
return func
|
||||||
|
return special_handler
|
||||||
|
|
||||||
|
|
||||||
|
@special_handler
|
||||||
|
def handle_map(key, val, ans):
|
||||||
|
parse_key(val, ans['key_definitions'])
|
||||||
|
|
||||||
|
|
||||||
|
@special_handler
|
||||||
|
def handle_symbol_map(key, val, ans):
|
||||||
|
ans['symbol_map'].update(parse_symbol_map(val))
|
||||||
|
|
||||||
|
|
||||||
|
@special_handler
|
||||||
|
def handle_send_text(key, val, ans):
|
||||||
|
# For legacy compatibility
|
||||||
|
parse_send_text(val, ans['key_definitions'])
|
||||||
|
|
||||||
|
|
||||||
|
@special_handler
|
||||||
|
def handle_clear_all_shortcuts(key, val, ans):
|
||||||
|
if to_bool(val):
|
||||||
|
ans['key_definitions'] = [None]
|
||||||
|
|
||||||
|
|
||||||
|
@deprecated_handler('x11_hide_window_decorations', 'macos_hide_titlebar')
|
||||||
|
def handle_deprecated_hide_window_decorations_aliases(key, val, ans):
|
||||||
|
if not hasattr(handle_deprecated_hide_window_decorations_aliases, key):
|
||||||
|
handle_deprecated_hide_window_decorations_aliases.key = True
|
||||||
|
log_error('The option {} is deprecated. Use hide_window_decorations instead.'.format(key))
|
||||||
|
if to_bool(val):
|
||||||
|
ans['hide_window_decorations'] = True
|
||||||
|
|
||||||
|
|
||||||
|
def expandvars(val, env):
|
||||||
|
|
||||||
|
def sub(m):
|
||||||
|
key = m.group(1)
|
||||||
|
result = env.get(key)
|
||||||
|
if result is None:
|
||||||
|
result = os.environ.get(key)
|
||||||
|
if result is None:
|
||||||
|
result = m.group()
|
||||||
|
return result
|
||||||
|
|
||||||
|
return re.sub(r'\$\{(\S+?)\}', sub, val)
|
||||||
|
|
||||||
|
|
||||||
|
@special_handler
|
||||||
|
def handle_env(key, val, ans):
|
||||||
|
key, val = val.partition('=')[::2]
|
||||||
|
key, val = key.strip(), val.strip()
|
||||||
|
ans['env'][key] = expandvars(val, ans['env'])
|
||||||
|
|
||||||
|
|
||||||
def special_handling(key, val, ans):
|
def special_handling(key, val, ans):
|
||||||
if key == 'map':
|
func = special_handlers.get(key)
|
||||||
parse_key(val, ans['key_definitions'])
|
if func is not None:
|
||||||
|
func(key, val, ans)
|
||||||
return True
|
return True
|
||||||
if key == 'symbol_map':
|
|
||||||
ans['symbol_map'].update(parse_symbol_map(val))
|
|
||||||
return True
|
|
||||||
if key == 'send_text':
|
|
||||||
# For legacy compatibility
|
|
||||||
parse_send_text(val, ans['key_definitions'])
|
|
||||||
return True
|
|
||||||
if key == 'clear_all_shortcuts':
|
|
||||||
if to_bool(val):
|
|
||||||
ans['key_definitions'] = [None]
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
defaults = None
|
defaults = None
|
||||||
|
|
||||||
|
|
||||||
|
def option_names_for_completion():
|
||||||
|
yield from defaults
|
||||||
|
yield from special_handlers
|
||||||
|
|
||||||
|
|
||||||
def parse_config(lines, check_keys=True):
|
def parse_config(lines, check_keys=True):
|
||||||
ans = {'symbol_map': {}, 'keymap': {}, 'sequence_map': {}, 'key_definitions': []}
|
ans = {'symbol_map': {}, 'keymap': {}, 'sequence_map': {}, 'key_definitions': [], 'env': {}}
|
||||||
parse_config_base(
|
parse_config_base(
|
||||||
lines,
|
lines,
|
||||||
defaults,
|
defaults,
|
||||||
@@ -445,4 +592,6 @@ def load_config(*paths, overrides=None):
|
|||||||
if opts.background_opacity < 1.0 and opts.macos_titlebar_color:
|
if opts.background_opacity < 1.0 and opts.macos_titlebar_color:
|
||||||
log_error('Cannot use both macos_titlebar_color and background_opacity')
|
log_error('Cannot use both macos_titlebar_color and background_opacity')
|
||||||
opts.macos_titlebar_color = 0
|
opts.macos_titlebar_color = 0
|
||||||
|
if (is_macos and getattr(opts, 'macos_hide_titlebar', False)) or (not is_macos and getattr(opts, 'x11_hide_window_decorations', False)):
|
||||||
|
opts.hide_window_decorations = True
|
||||||
return opts
|
return opts
|
||||||
|
|||||||
@@ -95,6 +95,21 @@ for a list of key names. The name to use is the part after the :code:`XKB_KEY_`
|
|||||||
prefix. Note that you should only use an XKB key name for keys that are not present
|
prefix. Note that you should only use an XKB key name for keys that are not present
|
||||||
in the list of GLFW keys.
|
in the list of GLFW keys.
|
||||||
|
|
||||||
|
Finally, you can use raw system key codes to map keys. To see the system key code
|
||||||
|
for a key, start kitty with the :option:`kitty --debug-keyboard` option. Then kitty will
|
||||||
|
output some debug text for every key event. In that text look for ``native_code``
|
||||||
|
the value of that becomes the key name in the shortcut. For example:
|
||||||
|
|
||||||
|
.. code-block:: none
|
||||||
|
|
||||||
|
on_key_input: glfw key: 65 native_code: 0x61 action: PRESS mods: 0x0 text: 'a'
|
||||||
|
|
||||||
|
Here, the key name for the :kbd:`A` key is :kbd:`0x61` and you can use it with::
|
||||||
|
|
||||||
|
map ctrl+0x61 something
|
||||||
|
|
||||||
|
to map :kbd:`ctrl+a` to something.
|
||||||
|
|
||||||
You can use the special action :code:`no_op` to unmap a keyboard shortcut that is
|
You can use the special action :code:`no_op` to unmap a keyboard shortcut that is
|
||||||
assigned in the default configuration.
|
assigned in the default configuration.
|
||||||
|
|
||||||
@@ -125,13 +140,19 @@ For example::
|
|||||||
'shortcuts.tab': [
|
'shortcuts.tab': [
|
||||||
_('Tab management'), '',
|
_('Tab management'), '',
|
||||||
_('''\
|
_('''\
|
||||||
You can also create shortcuts to go to specific tabs, with 1 being the first tab::
|
You can also create shortcuts to go to specific tabs, with 1 being the first
|
||||||
|
tab, 2 the second tab and -1 being the previously active tab::
|
||||||
|
|
||||||
map ctrl+alt+1 goto_tab 1
|
map ctrl+alt+1 goto_tab 1
|
||||||
map ctrl+alt+2 goto_tab 2
|
map ctrl+alt+2 goto_tab 2
|
||||||
|
|
||||||
Just as with :code:`new_window` above, you can also pass the name of arbitrary
|
Just as with :code:`new_window` above, you can also pass the name of arbitrary
|
||||||
commands to run when using new_tab and use :code:`new_tab_with_cwd`.
|
commands to run when using new_tab and use :code:`new_tab_with_cwd`. Finally,
|
||||||
|
if you want the new tab to open next to the current tab rather than at the
|
||||||
|
end of the tabs list, use::
|
||||||
|
|
||||||
|
map ctrl+t new_tab !neighbor [optional cmd to run]
|
||||||
|
|
||||||
''')],
|
''')],
|
||||||
'shortcuts.layout': [
|
'shortcuts.layout': [
|
||||||
_('Layout management'), '',
|
_('Layout management'), '',
|
||||||
@@ -140,17 +161,22 @@ You can also create shortcuts to switch to specific layouts::
|
|||||||
|
|
||||||
map ctrl+alt+t goto_layout tall
|
map ctrl+alt+t goto_layout tall
|
||||||
map ctrl+alt+s goto_layout stack
|
map ctrl+alt+s goto_layout stack
|
||||||
|
|
||||||
|
Similarly, to switch back to the previous layout::
|
||||||
|
|
||||||
|
map ctrl+alt+p last_used_layout
|
||||||
|
|
||||||
''')],
|
''')],
|
||||||
'shortcuts.fonts': [
|
'shortcuts.fonts': [
|
||||||
_('Font sizes'), _('''\
|
_('Font sizes'), _('''\
|
||||||
You can change the font size for all top-level kitty windows at a time
|
You can change the font size for all top-level kitty OS windows at a time
|
||||||
or only the current one.
|
or only the current one.
|
||||||
'''), _('''\
|
'''), _('''\
|
||||||
To setup shortcuts for specific font sizes::
|
To setup shortcuts for specific font sizes::
|
||||||
|
|
||||||
map kitty_mod+f6 change_font_size all 10.0
|
map kitty_mod+f6 change_font_size all 10.0
|
||||||
|
|
||||||
To setup shortcuts to change only the current window's font size::
|
To setup shortcuts to change only the current OS window's font size::
|
||||||
|
|
||||||
map kitty_mod+f6 change_font_size current 10.0
|
map kitty_mod+f6 change_font_size current 10.0
|
||||||
''')],
|
''')],
|
||||||
@@ -266,7 +292,16 @@ def to_cursor_shape(x):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def cursor_text_color(x):
|
||||||
|
if x.lower() == 'background':
|
||||||
|
return
|
||||||
|
return to_color(x)
|
||||||
|
|
||||||
|
|
||||||
o('cursor', '#cccccc', _('Default cursor color'), option_type=to_color)
|
o('cursor', '#cccccc', _('Default cursor color'), option_type=to_color)
|
||||||
|
o('cursor_text_color', '#111111', option_type=cursor_text_color, long_text=_('''
|
||||||
|
Choose the color of text under the cursor. If you want it rendered with the
|
||||||
|
background color of the cell underneath instead, use the special keyword: background'''))
|
||||||
o('cursor_shape', 'block', option_type=to_cursor_shape, long_text=_(
|
o('cursor_shape', 'block', option_type=to_cursor_shape, long_text=_(
|
||||||
'The cursor shape can be one of (block, beam, underline)'))
|
'The cursor shape can be one of (block, beam, underline)'))
|
||||||
o('cursor_blink_interval', 0.5, option_type=positive_float, long_text=_('''
|
o('cursor_blink_interval', 0.5, option_type=positive_float, long_text=_('''
|
||||||
@@ -281,9 +316,24 @@ o('cursor_stop_blinking_after', 15.0, option_type=positive_float)
|
|||||||
|
|
||||||
g('scrollback') # {{{
|
g('scrollback') # {{{
|
||||||
|
|
||||||
o('scrollback_lines', 2000, option_type=positive_int, long_text=_('''
|
|
||||||
|
def scrollback_lines(x):
|
||||||
|
x = int(x)
|
||||||
|
if x < 0:
|
||||||
|
x = 2 ** 32 - 1
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
def scrollback_pager_history_size(x):
|
||||||
|
ans = int(max(0, float(x)) * 1024 * 1024)
|
||||||
|
return min(ans, 4096 * 1024 * 1024 - 1)
|
||||||
|
|
||||||
|
|
||||||
|
o('scrollback_lines', 2000, option_type=scrollback_lines, long_text=_('''
|
||||||
Number of lines of history to keep in memory for scrolling back. Memory is allocated
|
Number of lines of history to keep in memory for scrolling back. Memory is allocated
|
||||||
on demand.'''))
|
on demand. Negative numbers are (effectively) infinite scrollback. Note that using
|
||||||
|
very large scrollback is not recommended as it can slow down resizing of the terminal
|
||||||
|
and also use large amounts of RAM.'''))
|
||||||
|
|
||||||
o('scrollback_pager', 'less --chop-long-lines --RAW-CONTROL-CHARS +INPUT_LINE_NUMBER', option_type=to_cmdline, long_text=_('''
|
o('scrollback_pager', 'less --chop-long-lines --RAW-CONTROL-CHARS +INPUT_LINE_NUMBER', option_type=to_cmdline, long_text=_('''
|
||||||
Program with which to view scrollback in a new window. The scrollback buffer is
|
Program with which to view scrollback in a new window. The scrollback buffer is
|
||||||
@@ -292,9 +342,26 @@ use can handle ANSI escape sequences for colors and text formatting.
|
|||||||
INPUT_LINE_NUMBER in the command line above will be replaced by an integer
|
INPUT_LINE_NUMBER in the command line above will be replaced by an integer
|
||||||
representing which line should be at the top of the screen.'''))
|
representing which line should be at the top of the screen.'''))
|
||||||
|
|
||||||
|
o('scrollback_pager_history_size', 0, option_type=scrollback_pager_history_size, long_text=_('''
|
||||||
|
Separate scrollback history size, used only for browsing the scrollback buffer (in MB).
|
||||||
|
This separate buffer is not available for interactive scrolling but will be
|
||||||
|
piped to the pager program when viewing scrollback buffer in a separate window.
|
||||||
|
The current implementation stores one character in 4 bytes, so approximatively
|
||||||
|
2500 lines per megabyte at 100 chars per line. A value of zero or less disables
|
||||||
|
this feature. The maximum allowed size is 4GB.'''))
|
||||||
|
|
||||||
o('wheel_scroll_multiplier', 5.0, long_text=_('''
|
o('wheel_scroll_multiplier', 5.0, long_text=_('''
|
||||||
Modify the amount scrolled by the mouse wheel or touchpad. Use
|
Modify the amount scrolled by the mouse wheel. Note this is only used for low
|
||||||
negative numbers to change scroll direction.'''))
|
precision scrolling devices, not for high precision scrolling on platforms such
|
||||||
|
as macOS and Wayland. Use negative numbers to change scroll direction.'''))
|
||||||
|
|
||||||
|
o('touch_scroll_multiplier', 1.0, long_text=_('''
|
||||||
|
Modify the amount scrolled by a touchpad. Note this is only used for high
|
||||||
|
precision scrolling devices on platforms such as macOS and Wayland.
|
||||||
|
Use negative numbers to change scroll direction.'''))
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
g('mouse') # {{{
|
g('mouse') # {{{
|
||||||
@@ -327,7 +394,7 @@ operating system's default URL handler.'''))
|
|||||||
o('copy_on_select', False, long_text=_('''
|
o('copy_on_select', False, long_text=_('''
|
||||||
Copy to clipboard on select. With this enabled, simply selecting text with
|
Copy to clipboard on select. With this enabled, simply selecting text with
|
||||||
the mouse will cause the text to be copied to clipboard. Useful on platforms
|
the mouse will cause the text to be copied to clipboard. Useful on platforms
|
||||||
such as macOS/Wayland that do not have the concept of primary selections. Note
|
such as macOS that do not have the concept of primary selections. Note
|
||||||
that this is a security risk, as all programs, including websites open in your
|
that this is a security risk, as all programs, including websites open in your
|
||||||
browser can read the contents of the clipboard.'''))
|
browser can read the contents of the clipboard.'''))
|
||||||
|
|
||||||
@@ -340,9 +407,9 @@ Characters considered part of a word when double clicking. In addition to these
|
|||||||
any character that is marked as an alpha-numeric character in the unicode
|
any character that is marked as an alpha-numeric character in the unicode
|
||||||
database will be matched.'''))
|
database will be matched.'''))
|
||||||
|
|
||||||
o('click_interval', 0.5, option_type=positive_float, long_text=_('''
|
o('click_interval', -1.0, option_type=float, long_text=_('''
|
||||||
The interval between successive clicks to detect
|
The interval between successive clicks to detect double/triple clicks (in seconds).
|
||||||
double/triple clicks (in seconds)'''))
|
Negative numbers will use the system default instead, if available, or fallback to 0.5.'''))
|
||||||
|
|
||||||
o('mouse_hide_wait', 3.0, option_type=positive_float, long_text=_('''
|
o('mouse_hide_wait', 3.0, option_type=positive_float, long_text=_('''
|
||||||
Hide mouse cursor after the specified number of seconds
|
Hide mouse cursor after the specified number of seconds
|
||||||
@@ -422,7 +489,7 @@ def to_layout_names(raw):
|
|||||||
parts = [x.strip().lower() for x in raw.split(',')]
|
parts = [x.strip().lower() for x in raw.split(',')]
|
||||||
ans = []
|
ans = []
|
||||||
for p in parts:
|
for p in parts:
|
||||||
if p == '*':
|
if p in ('*', 'all'):
|
||||||
ans.extend(sorted(all_layouts))
|
ans.extend(sorted(all_layouts))
|
||||||
continue
|
continue
|
||||||
name = p.partition(':')[0]
|
name = p.partition(':')[0]
|
||||||
@@ -434,7 +501,7 @@ def to_layout_names(raw):
|
|||||||
|
|
||||||
o('enabled_layouts', '*', option_type=to_layout_names, long_text=_('''
|
o('enabled_layouts', '*', option_type=to_layout_names, long_text=_('''
|
||||||
The enabled window layouts. A comma separated list of layout names. The special
|
The enabled window layouts. A comma separated list of layout names. The special
|
||||||
value :code:`*` means all layouts. The first listed layout will be used as the
|
value :code:`all` means all layouts. The first listed layout will be used as the
|
||||||
startup layout. For a list of available layouts, see the :ref:`layouts`.
|
startup layout. For a list of available layouts, see the :ref:`layouts`.
|
||||||
'''))
|
'''))
|
||||||
|
|
||||||
@@ -449,6 +516,13 @@ o('window_border_width', 1.0, option_type=positive_float, long_text=_('''
|
|||||||
The width (in pts) of window borders. Will be rounded to the nearest number of pixels based on screen resolution.
|
The width (in pts) of window borders. Will be rounded to the nearest number of pixels based on screen resolution.
|
||||||
Note that borders are displayed only when more than one window is visible. They are meant to separate multiple windows.'''))
|
Note that borders are displayed only when more than one window is visible. They are meant to separate multiple windows.'''))
|
||||||
|
|
||||||
|
o('draw_minimal_borders', True, long_text=_('''
|
||||||
|
Draw only the minimum borders needed. This means that only the minimum
|
||||||
|
needed borders for inactive windows are drawn. That is only the borders
|
||||||
|
that separate the inactive window from a neighbor. Note that setting
|
||||||
|
a non-zero window margin overrides this and causes all borders to be drawn.
|
||||||
|
'''))
|
||||||
|
|
||||||
o('window_margin_width', 0.0, option_type=positive_float, long_text=_('''
|
o('window_margin_width', 0.0, option_type=positive_float, long_text=_('''
|
||||||
The window margin (in pts) (blank area outside the border)'''))
|
The window margin (in pts) (blank area outside the border)'''))
|
||||||
|
|
||||||
@@ -472,6 +546,12 @@ o('inactive_text_alpha', 1.0, option_type=unit_float, long_text=_('''
|
|||||||
Fade the text in inactive windows by the specified amount (a number between
|
Fade the text in inactive windows by the specified amount (a number between
|
||||||
zero and one, with zero being fully faded).
|
zero and one, with zero being fully faded).
|
||||||
'''))
|
'''))
|
||||||
|
|
||||||
|
o('hide_window_decorations', False, long_text=_('''
|
||||||
|
Hide the window decorations (title-bar and window borders).
|
||||||
|
Whether this works and exactly what effect it has depends on the
|
||||||
|
window manager/operating system.
|
||||||
|
'''))
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
g('tabbar') # {{{
|
g('tabbar') # {{{
|
||||||
@@ -506,8 +586,8 @@ Which edge to show the tab bar on, top or bottom'''))
|
|||||||
o('tab_bar_margin_width', 0.0, option_type=positive_float, long_text=_('''
|
o('tab_bar_margin_width', 0.0, option_type=positive_float, long_text=_('''
|
||||||
The margin to the left and right of the tab bar (in pts)'''))
|
The margin to the left and right of the tab bar (in pts)'''))
|
||||||
|
|
||||||
o('tab_bar_style', 'fade', option_type=choices('fade', 'separator'), long_text=_('''
|
o('tab_bar_style', 'fade', option_type=choices('fade', 'separator', 'hidden'), long_text=_('''
|
||||||
The tab bar style, can be one of: :code:`fade` or :code:`separator`. In the fade style,
|
The tab bar style, can be one of: :code:`fade`, :code:`separator` or :code:`hidden`. In the fade style,
|
||||||
each tab's edges fade into the background color, in the separator style, tabs are
|
each tab's edges fade into the background color, in the separator style, tabs are
|
||||||
separated by a configurable separator.
|
separated by a configurable separator.
|
||||||
'''))
|
'''))
|
||||||
@@ -528,6 +608,13 @@ entries to this list.
|
|||||||
o('tab_separator', '"{}"'.format(default_tab_separator), option_type=tab_separator, long_text=_('''
|
o('tab_separator', '"{}"'.format(default_tab_separator), option_type=tab_separator, long_text=_('''
|
||||||
The separator between tabs in the tab bar when using :code:`separator` as the :opt:`tab_bar_style`.'''))
|
The separator between tabs in the tab bar when using :code:`separator` as the :opt:`tab_bar_style`.'''))
|
||||||
|
|
||||||
|
o('tab_title_template', '{title}', long_text=_('''
|
||||||
|
A template to render the tab title. The default just renders
|
||||||
|
the title. If you wish to include the tab-index as well,
|
||||||
|
use something like: :code:`{index}: {title}`. Useful
|
||||||
|
if you have shortcuts mapped for :code:`goto_tab N`.
|
||||||
|
'''))
|
||||||
|
|
||||||
o('active_tab_foreground', '#000', option_type=to_color, long_text=_('''
|
o('active_tab_foreground', '#000', option_type=to_color, long_text=_('''
|
||||||
Tab bar colors and styles'''))
|
Tab bar colors and styles'''))
|
||||||
o('active_tab_background', '#eee', option_type=to_color)
|
o('active_tab_background', '#eee', option_type=to_color)
|
||||||
@@ -630,6 +717,19 @@ opening new windows, closing windows, reading the content of windows, etc.
|
|||||||
Note that this even works over ssh connections.
|
Note that this even works over ssh connections.
|
||||||
'''))
|
'''))
|
||||||
|
|
||||||
|
o(
|
||||||
|
'+env', '',
|
||||||
|
add_to_default=False,
|
||||||
|
long_text=_('''
|
||||||
|
Specify environment variables to set in all child processes. Note that
|
||||||
|
environment variables are expanded recursively, so if you use::
|
||||||
|
|
||||||
|
env MYVAR1=a
|
||||||
|
env MYVAR2=${MYVAR1}/${HOME}/b
|
||||||
|
|
||||||
|
The value of MYVAR2 will be :code:`a/<path to home directory>/b`.
|
||||||
|
'''))
|
||||||
|
|
||||||
|
|
||||||
def startup_session(x):
|
def startup_session(x):
|
||||||
if x.lower() == 'none':
|
if x.lower() == 'none':
|
||||||
@@ -659,9 +759,13 @@ program, even one running on a remote server via SSH can read your clipboard.
|
|||||||
'''))
|
'''))
|
||||||
|
|
||||||
o('term', 'xterm-kitty', long_text=_('''
|
o('term', 'xterm-kitty', long_text=_('''
|
||||||
The value of the TERM environment variable to set. Changing this can break
|
The value of the TERM environment variable to set. Changing this can break many
|
||||||
many terminal programs, only change it if you know what you are doing, not
|
terminal programs, only change it if you know what you are doing, not because
|
||||||
because you read some advice on Stack Overflow to change it.
|
you read some advice on Stack Overflow to change it. The TERM variable if used
|
||||||
|
by various programs to get information about the capabilities and behavior of
|
||||||
|
the terminal. If you change it, depending on what programs you run, and how
|
||||||
|
different the terminal you are changing it to is, various things from
|
||||||
|
key-presses, to colors, to various advanced features may not work.
|
||||||
'''))
|
'''))
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
@@ -686,18 +790,9 @@ an arbitrary color, such as :code:`#12af59` or :code:`red`. WARNING: This option
|
|||||||
using a hack, as there is no proper Cocoa API for it. It sets the background
|
using a hack, as there is no proper Cocoa API for it. It sets the background
|
||||||
color of the entire window and makes the titlebar transparent. As such it is
|
color of the entire window and makes the titlebar transparent. As such it is
|
||||||
incompatible with :opt:`background_opacity`. If you want to use both, you are
|
incompatible with :opt:`background_opacity`. If you want to use both, you are
|
||||||
probably better off just hiding the titlebar with :opt:`macos_hide_titlebar`.
|
probably better off just hiding the titlebar with :opt:`hide_window_decorations`.
|
||||||
'''))
|
'''))
|
||||||
|
|
||||||
o('macos_hide_titlebar', False, long_text=_('''
|
|
||||||
Hide the kitty window's title bar on macOS.'''))
|
|
||||||
|
|
||||||
o('x11_hide_window_decorations', False, long_text=_('''
|
|
||||||
Hide the window decorations (title bar and window borders) on X11 and Wayland.
|
|
||||||
Whether this works and exactly what effect it has depends on the window
|
|
||||||
manager, as it is the job of the window manager/compositor to draw window
|
|
||||||
decorations.'''))
|
|
||||||
|
|
||||||
o('macos_option_as_alt', True, long_text=_('''
|
o('macos_option_as_alt', True, long_text=_('''
|
||||||
Use the option key as an alt key. With this set to no, kitty will use
|
Use the option key as an alt key. With this set to no, kitty will use
|
||||||
the macOS native :kbd:`Option+Key` = unicode character behavior. This will
|
the macOS native :kbd:`Option+Key` = unicode character behavior. This will
|
||||||
@@ -715,7 +810,27 @@ kitty will stay running, even with no open windows, as is the expected
|
|||||||
behavior on macOS.
|
behavior on macOS.
|
||||||
'''))
|
'''))
|
||||||
|
|
||||||
|
o('macos_window_resizable', True, long_text=_('''
|
||||||
|
Disable this if you want kitty top-level (OS) windows to not be resizable
|
||||||
|
on macOS.
|
||||||
|
'''))
|
||||||
|
|
||||||
|
o('macos_thicken_font', 0, option_type=positive_float, long_text=_('''
|
||||||
|
Draw an extra border around the font with the given width, to increase
|
||||||
|
legibility at small font sizes. For example, a value of 0.75 will
|
||||||
|
result in rendering that looks similar to sub-pixel antialiasing at
|
||||||
|
common font sizes.
|
||||||
|
'''))
|
||||||
|
|
||||||
|
o('macos_traditional_fullscreen', False, long_text=_('''
|
||||||
|
Use the traditional full-screen transition, that is faster, but less pretty.
|
||||||
|
'''))
|
||||||
|
|
||||||
|
# Disabled by default because of https://github.com/kovidgoyal/kitty/issues/794
|
||||||
|
o('macos_custom_beam_cursor', False, long_text=_('''
|
||||||
|
Enable/disable custom mouse cursor for macOS that is easier to see on both
|
||||||
|
light and dark backgrounds. WARNING: this might make your mouse cursor
|
||||||
|
invisible on dual GPU machines.'''))
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
g('shortcuts') # {{{
|
g('shortcuts') # {{{
|
||||||
@@ -733,7 +848,9 @@ g('shortcuts.clipboard') # {{{
|
|||||||
if is_macos:
|
if is_macos:
|
||||||
k('copy_to_clipboard', 'cmd+c', 'copy_to_clipboard', _('Copy to clipboard'), add_to_docs=False)
|
k('copy_to_clipboard', 'cmd+c', 'copy_to_clipboard', _('Copy to clipboard'), add_to_docs=False)
|
||||||
k('paste_from_clipboard', 'cmd+v', 'paste_from_clipboard', _('Paste from clipboard'), add_to_docs=False)
|
k('paste_from_clipboard', 'cmd+v', 'paste_from_clipboard', _('Paste from clipboard'), add_to_docs=False)
|
||||||
k('copy_to_clipboard', 'kitty_mod+c', 'copy_to_clipboard', _('Copy to clipboard'))
|
k('copy_to_clipboard', 'kitty_mod+c', 'copy_to_clipboard', _('Copy to clipboard'), long_text=_('''
|
||||||
|
There is also a :code:`copy_or_interrupt` action that can be optionally mapped to :kbd:`Ctrl+c`.
|
||||||
|
It will copy only if there is a selection and send an interrupt otherwise.'''))
|
||||||
k('paste_from_clipboard', 'kitty_mod+v', 'paste_from_clipboard', _('Paste from clipboard'))
|
k('paste_from_clipboard', 'kitty_mod+v', 'paste_from_clipboard', _('Paste from clipboard'))
|
||||||
k('paste_from_selection', 'kitty_mod+s', 'paste_from_selection', _('Paste from selection'))
|
k('paste_from_selection', 'kitty_mod+s', 'paste_from_selection', _('Paste from selection'))
|
||||||
k('paste_from_selection', 'shift+insert', 'paste_from_selection', _('Paste from selection'))
|
k('paste_from_selection', 'shift+insert', 'paste_from_selection', _('Paste from selection'))
|
||||||
@@ -762,12 +879,15 @@ k('scroll_page_down', 'kitty_mod+page_down', 'scroll_page_down', _('Scroll page
|
|||||||
k('scroll_home', 'kitty_mod+home', 'scroll_home', _('Scroll to top'))
|
k('scroll_home', 'kitty_mod+home', 'scroll_home', _('Scroll to top'))
|
||||||
k('scroll_end', 'kitty_mod+end', 'scroll_end', _('Scroll to bottom'))
|
k('scroll_end', 'kitty_mod+end', 'scroll_end', _('Scroll to bottom'))
|
||||||
k('show_scrollback', 'kitty_mod+h', 'show_scrollback', _('Browse scrollback buffer in less'), long_text=_('''
|
k('show_scrollback', 'kitty_mod+h', 'show_scrollback', _('Browse scrollback buffer in less'), long_text=_('''
|
||||||
You can send the contents of the current screen + history buffer as stdin to an arbitrary program using
|
|
||||||
the placeholders @text (which is the plain text) and @ansi (which includes text styling escape codes).
|
|
||||||
For only the current screen, use @screen or @ansi_screen.
|
|
||||||
For example, the following command opens the scrollback buffer in less in a new window::
|
|
||||||
|
|
||||||
map kitty_mod+y new_window @ansi less +G -R
|
You can pipe the contents of the current screen + history buffer as
|
||||||
|
:file:`STDIN` to an arbitrary program using the ``pipe`` function. For example,
|
||||||
|
the following opens the scrollback buffer in less in an overlay window::
|
||||||
|
|
||||||
|
map f1 pipe @ansi overlay less +G -R
|
||||||
|
|
||||||
|
For more details on piping screen and buffer contents to external programs,
|
||||||
|
see :doc:`pipe`.
|
||||||
'''))
|
'''))
|
||||||
|
|
||||||
|
|
||||||
@@ -783,11 +903,20 @@ You can open a new window with the current working directory set to the
|
|||||||
working directory of the current window using::
|
working directory of the current window using::
|
||||||
|
|
||||||
map ctrl+alt+enter new_window_with_cwd
|
map ctrl+alt+enter new_window_with_cwd
|
||||||
|
|
||||||
|
You can open a new window that is allowed to control kitty via
|
||||||
|
the kitty remote control facility by prefixing the command line with @.
|
||||||
|
Any programs running in that window will be allowed to control kitty.
|
||||||
|
For example::
|
||||||
|
|
||||||
|
map ctrl+enter new_window @ some_program
|
||||||
'''))
|
'''))
|
||||||
if is_macos:
|
if is_macos:
|
||||||
k('new_os_window', 'cmd+n', 'new_os_window', _('New OS window'))
|
k('new_os_window', 'cmd+n', 'new_os_window', _('New OS window'))
|
||||||
k('new_os_window', 'kitty_mod+n', 'new_os_window', _('New OS window'))
|
k('new_os_window', 'kitty_mod+n', 'new_os_window', _('New OS window'))
|
||||||
k('close_window', 'kitty_mod+w', 'close_window', _('Close window'))
|
k('close_window', 'kitty_mod+w', 'close_window', _('Close window'))
|
||||||
|
if is_macos:
|
||||||
|
k('close_window', 'cmd+w', 'close_window', _('Close window'), add_to_docs=False)
|
||||||
k('next_window', 'kitty_mod+]', 'next_window', _('Next window'))
|
k('next_window', 'kitty_mod+]', 'next_window', _('Next window'))
|
||||||
k('previous_window', 'kitty_mod+[', 'previous_window', _('Previous window'))
|
k('previous_window', 'kitty_mod+[', 'previous_window', _('Previous window'))
|
||||||
k('move_window_forward', 'kitty_mod+f', 'move_window_forward', _('Move window forward'))
|
k('move_window_forward', 'kitty_mod+f', 'move_window_forward', _('Move window forward'))
|
||||||
@@ -807,9 +936,15 @@ k('tenth_window', 'kitty_mod+0', 'tenth_window', _('Tenth window'))
|
|||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
g('shortcuts.tab') # {{{
|
g('shortcuts.tab') # {{{
|
||||||
|
if is_macos:
|
||||||
|
k('next_tab', 'ctrl+tab', 'next_tab', _('Next tab'), add_to_docs=False)
|
||||||
k('next_tab', 'kitty_mod+right', 'next_tab', _('Next tab'))
|
k('next_tab', 'kitty_mod+right', 'next_tab', _('Next tab'))
|
||||||
|
if is_macos:
|
||||||
|
k('previous_tab', 'ctrl+shift+tab', 'previous_tab', _('Previous tab'), add_to_docs=False)
|
||||||
k('previous_tab', 'kitty_mod+left', 'previous_tab', _('Previous tab'))
|
k('previous_tab', 'kitty_mod+left', 'previous_tab', _('Previous tab'))
|
||||||
k('new_tab', 'kitty_mod+t', 'new_tab', _('New tab'))
|
k('new_tab', 'kitty_mod+t', 'new_tab', _('New tab'))
|
||||||
|
if is_macos:
|
||||||
|
k('new_tab', 'cmd+t', 'new_tab', _('New tab'), add_to_docs=False)
|
||||||
k('close_tab', 'kitty_mod+q', 'close_tab', _('Close tab'))
|
k('close_tab', 'kitty_mod+q', 'close_tab', _('Close tab'))
|
||||||
k('move_tab_forward', 'kitty_mod+.', 'move_tab_forward', _('Move tab forward'))
|
k('move_tab_forward', 'kitty_mod+.', 'move_tab_forward', _('Move tab forward'))
|
||||||
k('move_tab_backward', 'kitty_mod+,', 'move_tab_backward', _('Move tab backward'))
|
k('move_tab_backward', 'kitty_mod+,', 'move_tab_backward', _('Move tab backward'))
|
||||||
@@ -853,7 +988,7 @@ Useful with git, which uses sha1 hashes to identify commits'''))
|
|||||||
|
|
||||||
g('shortcuts.misc') # {{{
|
g('shortcuts.misc') # {{{
|
||||||
k('toggle_fullscreen', 'kitty_mod+f11', 'toggle_fullscreen', _('Toggle fullscreen'))
|
k('toggle_fullscreen', 'kitty_mod+f11', 'toggle_fullscreen', _('Toggle fullscreen'))
|
||||||
k('input_unicode_character', 'kitty_mod+u', 'input_unicode_character', _('Unicode input'))
|
k('input_unicode_character', 'kitty_mod+u', 'kitten unicode_input', _('Unicode input'))
|
||||||
k('edit_config_file', 'kitty_mod+f2', 'edit_config_file', _('Edit config file'))
|
k('edit_config_file', 'kitty_mod+f2', 'edit_config_file', _('Edit config file'))
|
||||||
k('kitty_shell', 'kitty_mod+escape', 'kitty_shell window', _('Open the kitty command shell'), long_text=_('''
|
k('kitty_shell', 'kitty_mod+escape', 'kitty_shell window', _('Open the kitty command shell'), long_text=_('''
|
||||||
Open the kitty shell in a new window/tab/overlay/os_window to control kitty using commands.'''))
|
Open the kitty shell in a new window/tab/overlay/os_window to control kitty using commands.'''))
|
||||||
@@ -861,6 +996,26 @@ k('increase_background_opacity', 'kitty_mod+a>m', 'set_background_opacity +0.1',
|
|||||||
k('decrease_background_opacity', 'kitty_mod+a>l', 'set_background_opacity -0.1', _('Decrease background opacity'))
|
k('decrease_background_opacity', 'kitty_mod+a>l', 'set_background_opacity -0.1', _('Decrease background opacity'))
|
||||||
k('full_background_opacity', 'kitty_mod+a>1', 'set_background_opacity 1', _('Make background fully opaque'))
|
k('full_background_opacity', 'kitty_mod+a>1', 'set_background_opacity 1', _('Make background fully opaque'))
|
||||||
k('reset_background_opacity', 'kitty_mod+a>d', 'set_background_opacity default', _('Reset background opacity'))
|
k('reset_background_opacity', 'kitty_mod+a>d', 'set_background_opacity default', _('Reset background opacity'))
|
||||||
|
k('reset_terminal', 'kitty_mod+delete', 'clear_terminal reset active', _('Reset the terminal'),
|
||||||
|
long_text=_('''
|
||||||
|
You can create shortcuts to clear/reset the terminal. For example::
|
||||||
|
|
||||||
|
# Reset the terminal
|
||||||
|
map kitty_mod+f9 clear_terminal reset active
|
||||||
|
# Clear the terminal screen by erasing all contents
|
||||||
|
map kitty_mod+f10 clear_terminal clear active
|
||||||
|
# Clear the terminal scrollback by erasing it
|
||||||
|
map kitty_mod+f11 clear_terminal scrollback active
|
||||||
|
# Scroll the contents of the screen into the scrollback
|
||||||
|
map kitty_mod+f12 clear_terminal scroll active
|
||||||
|
|
||||||
|
If you want to operate on all windows instead of just the current one, use :italic:`all` instead of :italic`active`.
|
||||||
|
|
||||||
|
It is also possible to remap Ctrl+L to both scroll the current screen contents into the scrollback buffer
|
||||||
|
and clear the screen, instead of just clearing the screen::
|
||||||
|
|
||||||
|
map ctrl+l combine : clear_terminal scroll active : send_text normal,application \x0c
|
||||||
|
'''))
|
||||||
k('send_text', 'ctrl+shift+alt+h', 'send_text all Hello World', _('Send arbitrary text on key presses'),
|
k('send_text', 'ctrl+shift+alt+h', 'send_text all Hello World', _('Send arbitrary text on key presses'),
|
||||||
add_to_default=False, long_text=_('''
|
add_to_default=False, long_text=_('''
|
||||||
You can tell kitty to send arbitrary (UTF-8) encoded text to
|
You can tell kitty to send arbitrary (UTF-8) encoded text to
|
||||||
|
|||||||
@@ -7,10 +7,8 @@ import pwd
|
|||||||
import sys
|
import sys
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
from .fast_data_types import set_boss as set_c_boss
|
|
||||||
|
|
||||||
appname = 'kitty'
|
appname = 'kitty'
|
||||||
version = (0, 11, 2)
|
version = (0, 13, 3)
|
||||||
str_version = '.'.join(map(str, version))
|
str_version = '.'.join(map(str, version))
|
||||||
_plat = sys.platform.lower()
|
_plat = sys.platform.lower()
|
||||||
is_macos = 'darwin' in _plat
|
is_macos = 'darwin' in _plat
|
||||||
@@ -58,7 +56,22 @@ def _get_config_dir():
|
|||||||
|
|
||||||
candidate = os.path.abspath(os.path.expanduser(os.environ.get('XDG_CONFIG_HOME') or '~/.config'))
|
candidate = os.path.abspath(os.path.expanduser(os.environ.get('XDG_CONFIG_HOME') or '~/.config'))
|
||||||
ans = os.path.join(candidate, appname)
|
ans = os.path.join(candidate, appname)
|
||||||
os.makedirs(ans, exist_ok=True)
|
try:
|
||||||
|
os.makedirs(ans, exist_ok=True)
|
||||||
|
except FileExistsError:
|
||||||
|
raise SystemExit('A file {} already exists. It must be a directory, not a file.'.format(ans))
|
||||||
|
except PermissionError:
|
||||||
|
import tempfile
|
||||||
|
import atexit
|
||||||
|
ans = tempfile.mkdtemp(prefix='kitty-conf-')
|
||||||
|
|
||||||
|
def cleanup():
|
||||||
|
import shutil
|
||||||
|
try:
|
||||||
|
shutil.rmtree(ans)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
atexit.register(cleanup)
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
@@ -91,6 +104,7 @@ def get_boss():
|
|||||||
|
|
||||||
|
|
||||||
def set_boss(m):
|
def set_boss(m):
|
||||||
|
from .fast_data_types import set_boss as set_c_boss
|
||||||
get_boss.boss = m
|
get_boss.boss = m
|
||||||
set_c_boss(m)
|
set_c_boss(m)
|
||||||
|
|
||||||
@@ -102,6 +116,7 @@ def wakeup():
|
|||||||
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
terminfo_dir = os.path.join(base_dir, 'terminfo')
|
terminfo_dir = os.path.join(base_dir, 'terminfo')
|
||||||
logo_data_file = os.path.join(base_dir, 'logo', 'kitty.rgba')
|
logo_data_file = os.path.join(base_dir, 'logo', 'kitty.rgba')
|
||||||
|
beam_cursor_data_file = os.path.join(base_dir, 'logo', 'beam-cursor.png')
|
||||||
try:
|
try:
|
||||||
shell_path = pwd.getpwuid(os.geteuid()).pw_shell or '/bin/sh'
|
shell_path = pwd.getpwuid(os.geteuid()).pw_shell or '/bin/sh'
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -121,4 +136,4 @@ if os.environ.get('WAYLAND_DISPLAY') and 'KITTY_ENABLE_WAYLAND' in os.environ an
|
|||||||
is_wayland = True
|
is_wayland = True
|
||||||
|
|
||||||
|
|
||||||
supports_primary_selection = not is_macos and not is_wayland
|
supports_primary_selection = not is_macos
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user