mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-04-06 07:15:30 -04:00
Compare commits
1112 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46a9cb8aab | ||
|
|
037a2f4fa2 | ||
|
|
2fb7bb0ea4 | ||
|
|
5708a63c9a | ||
|
|
ef15414b30 | ||
|
|
370adaf697 | ||
|
|
6f1157395b | ||
|
|
eed2d668ec | ||
|
|
657db18a4e | ||
|
|
4fd682b4e8 | ||
|
|
f49fdf7ca3 | ||
|
|
c05bd55029 | ||
|
|
ebacb14be8 | ||
|
|
bee76db20c | ||
|
|
739cf115e6 | ||
|
|
3bbaa64cae | ||
|
|
c9c5c55745 | ||
|
|
e48178bec3 | ||
|
|
046898f1b8 | ||
|
|
dea10e2e48 | ||
|
|
dc0c8550c3 | ||
|
|
21485fa66d | ||
|
|
fbf4304731 | ||
|
|
1f31b43db7 | ||
|
|
e24334a8df | ||
|
|
c291194a4e | ||
|
|
0bf193ef81 | ||
|
|
6fbb9b7d3b | ||
|
|
3a2eb80d83 | ||
|
|
e2a989785a | ||
|
|
b78cc92322 | ||
|
|
42e04c5c87 | ||
|
|
53e8fbbdec | ||
|
|
bb6968c284 | ||
|
|
41679e64a8 | ||
|
|
b3cb180e44 | ||
|
|
ee682abac8 | ||
|
|
6ab2e2d9eb | ||
|
|
bbebe0f330 | ||
|
|
cb1e152d99 | ||
|
|
ca278398b1 | ||
|
|
4cb17f5ae6 | ||
|
|
aa26676c43 | ||
|
|
1caba0d993 | ||
|
|
cf2b390f6e | ||
|
|
4e96780eef | ||
|
|
15ebc433ba | ||
|
|
6e533231b0 | ||
|
|
ac6d7660dd | ||
|
|
65bd79b77d | ||
|
|
55f8388694 | ||
|
|
be19ca2b20 | ||
|
|
fc9625678f | ||
|
|
c9abab0807 | ||
|
|
5cb8ff2e9c | ||
|
|
1fce0e69f5 | ||
|
|
9728ada028 | ||
|
|
143f220527 | ||
|
|
5ae4955e83 | ||
|
|
71de0c45bc | ||
|
|
19466a21d8 | ||
|
|
5587604469 | ||
|
|
82e75851e4 | ||
|
|
e114a5f02f | ||
|
|
b44a62724c | ||
|
|
dc5a921d2c | ||
|
|
612adda384 | ||
|
|
dbf18ba444 | ||
|
|
96605bf52f | ||
|
|
7ed36c1033 | ||
|
|
371837ef7b | ||
|
|
e308a4733e | ||
|
|
fd88c6c61c | ||
|
|
299186a654 | ||
|
|
bb314425ef | ||
|
|
e43ea3676f | ||
|
|
bd994eda1c | ||
|
|
fac3994154 | ||
|
|
80951ab7a6 | ||
|
|
1dfa86c93a | ||
|
|
44a674edb8 | ||
|
|
c34f063307 | ||
|
|
363477fa0d | ||
|
|
fa0fd2f50f | ||
|
|
f715f3b55f | ||
|
|
efc39097e5 | ||
|
|
65528f455d | ||
|
|
1d9ac3f611 | ||
|
|
298196365c | ||
|
|
f0e36e35cb | ||
|
|
ed7652db50 | ||
|
|
72d9a13c0c | ||
|
|
b13a8f12d2 | ||
|
|
70d99a8051 | ||
|
|
b1b2162416 | ||
|
|
3b8d59f476 | ||
|
|
6eedc88d70 | ||
|
|
7636f264a8 | ||
|
|
83303bd2a4 | ||
|
|
f873aa904d | ||
|
|
86d63f08ba | ||
|
|
8814b5f080 | ||
|
|
43620935a1 | ||
|
|
95e8b18c12 | ||
|
|
5a01dbc234 | ||
|
|
fcde74a181 | ||
|
|
42be74214a | ||
|
|
21db6a6cdc | ||
|
|
7ab43ebf74 | ||
|
|
57ae3bb89c | ||
|
|
01387f9593 | ||
|
|
cc290fa9b0 | ||
|
|
692b22cbbb | ||
|
|
9b6a9db98a | ||
|
|
d62bff1440 | ||
|
|
e72e08625d | ||
|
|
693aefa96a | ||
|
|
aa579acd6e | ||
|
|
968bc05c32 | ||
|
|
499f019dea | ||
|
|
d9675a7140 | ||
|
|
33eefa7b45 | ||
|
|
7347f4beb1 | ||
|
|
eeaecba723 | ||
|
|
5a84f8d841 | ||
|
|
5621829bb0 | ||
|
|
664cdcc65c | ||
|
|
d266599881 | ||
|
|
456ac5d79f | ||
|
|
3e1e3ea38c | ||
|
|
8bd39b32cd | ||
|
|
ebd1614316 | ||
|
|
9b0d5e7c96 | ||
|
|
073b637d45 | ||
|
|
c037836bbd | ||
|
|
c6db0bed42 | ||
|
|
970e13db8d | ||
|
|
7354b94f73 | ||
|
|
5080e271c2 | ||
|
|
237db6e771 | ||
|
|
0ea572dc63 | ||
|
|
183fd96aba | ||
|
|
85c81042d2 | ||
|
|
acea863fbe | ||
|
|
2a8948a3f3 | ||
|
|
7ced397089 | ||
|
|
9ff0151055 | ||
|
|
e5a0755451 | ||
|
|
b07ce56321 | ||
|
|
1dc8354534 | ||
|
|
99954534e1 | ||
|
|
513e91c33a | ||
|
|
fc293bad5e | ||
|
|
172f67a8df | ||
|
|
ce424e0990 | ||
|
|
1ea20b1b70 | ||
|
|
eb79a27900 | ||
|
|
d7b48d3924 | ||
|
|
97910a5cba | ||
|
|
d20fbc6807 | ||
|
|
4d70bb7b42 | ||
|
|
8273514d3c | ||
|
|
d3e45791bd | ||
|
|
6a1c3b89c2 | ||
|
|
1dc14a3001 | ||
|
|
537092e643 | ||
|
|
bc5b716668 | ||
|
|
10e7f29149 | ||
|
|
6bc91b5e28 | ||
|
|
1423babc35 | ||
|
|
01c43f1644 | ||
|
|
b24a9a59b9 | ||
|
|
5406ae3355 | ||
|
|
624c383a1f | ||
|
|
a7276d9dff | ||
|
|
79f6b4b1de | ||
|
|
fea9f5579f | ||
|
|
c85d5d5096 | ||
|
|
89bfac00e7 | ||
|
|
9d0b048cf9 | ||
|
|
0020ef12b4 | ||
|
|
cb1b7ba0c5 | ||
|
|
d43326d2b5 | ||
|
|
1fec0cf5ea | ||
|
|
1b15cc5f3d | ||
|
|
70b324b24c | ||
|
|
b2dfd339e4 | ||
|
|
bc8d6d1ff3 | ||
|
|
8bded8ce8c | ||
|
|
ef4a680ae8 | ||
|
|
cb2a64c585 | ||
|
|
1a2e5f4932 | ||
|
|
1bf9156628 | ||
|
|
179e14e0a1 | ||
|
|
155c7c96b7 | ||
|
|
30aafce82d | ||
|
|
6e5a602f67 | ||
|
|
9799c4ddcd | ||
|
|
95f7b71058 | ||
|
|
2c8214f6ea | ||
|
|
b46a9aa6d7 | ||
|
|
9ba8caf30b | ||
|
|
f7807c0f4c | ||
|
|
d2d4f53861 | ||
|
|
bacfba135d | ||
|
|
5f83278afd | ||
|
|
9a6227acb3 | ||
|
|
b93d2f042c | ||
|
|
bc2e0a29bb | ||
|
|
23431e3ecf | ||
|
|
091aa90f1a | ||
|
|
34d3f4664b | ||
|
|
bdf65672c0 | ||
|
|
1760cb6ab8 | ||
|
|
a50f78c599 | ||
|
|
0d8c7db962 | ||
|
|
a43614f098 | ||
|
|
dc99cf7358 | ||
|
|
58910856c8 | ||
|
|
c8470f40c1 | ||
|
|
9b776f2d6d | ||
|
|
6922ab2b8e | ||
|
|
663c9082db | ||
|
|
5f72f51ae8 | ||
|
|
cc99db5bc4 | ||
|
|
a02c0c8d4d | ||
|
|
878e07da59 | ||
|
|
6813b321f5 | ||
|
|
c2a9fd5dfa | ||
|
|
68f5eab0b0 | ||
|
|
49d2c08912 | ||
|
|
df32cd0504 | ||
|
|
3eef3ec877 | ||
|
|
d2ede697f9 | ||
|
|
7dbfdc73b6 | ||
|
|
cd4ee8ae49 | ||
|
|
eb9357709b | ||
|
|
d48a1c53f5 | ||
|
|
a79fd6a7cf | ||
|
|
16c384b707 | ||
|
|
7f11ba59ef | ||
|
|
cfa178ab25 | ||
|
|
edbfdd5150 | ||
|
|
7976975a8a | ||
|
|
04fcc5f5b5 | ||
|
|
8d2627b1ef | ||
|
|
605694bc93 | ||
|
|
7b6efcf19a | ||
|
|
a80b32d006 | ||
|
|
ccf625b991 | ||
|
|
6d39f66eb7 | ||
|
|
9e6d334bd8 | ||
|
|
882f4b2468 | ||
|
|
4f11d6086f | ||
|
|
c41008da31 | ||
|
|
101bc28698 | ||
|
|
9f9ffa9434 | ||
|
|
ba5f4abdd4 | ||
|
|
d66a00678d | ||
|
|
76503fb86a | ||
|
|
4abbaf1345 | ||
|
|
d7a4f9e99e | ||
|
|
888a6770da | ||
|
|
7445471238 | ||
|
|
8b63869f57 | ||
|
|
eced7cf1d6 | ||
|
|
6a181c9f72 | ||
|
|
3d66db63cc | ||
|
|
c63202ee0e | ||
|
|
970d95c5a1 | ||
|
|
fce755aafe | ||
|
|
26acf41d13 | ||
|
|
98db965813 | ||
|
|
4e5ad6e013 | ||
|
|
325086291b | ||
|
|
d84b0d4c6a | ||
|
|
8d20b82721 | ||
|
|
a3a404a257 | ||
|
|
d7e8f29ee2 | ||
|
|
9840204097 | ||
|
|
0f93766614 | ||
|
|
ed35a238d6 | ||
|
|
3998f8570c | ||
|
|
1260004330 | ||
|
|
d3f692990e | ||
|
|
7a8d2b5e01 | ||
|
|
1111f7e918 | ||
|
|
e248e73753 | ||
|
|
1181f74d19 | ||
|
|
88dcde3ed8 | ||
|
|
dd01783f88 | ||
|
|
9d8021de47 | ||
|
|
9e12f791c5 | ||
|
|
b1f16c84e0 | ||
|
|
0a32dc3820 | ||
|
|
a984531ce5 | ||
|
|
31f536ff8c | ||
|
|
cf4324e6c6 | ||
|
|
2f902c1f5b | ||
|
|
9d0f5cbd2a | ||
|
|
051cd6ecfc | ||
|
|
f718cb3fb0 | ||
|
|
ab4426f987 | ||
|
|
130b05f02b | ||
|
|
d41b28bd02 | ||
|
|
e76d8dd7af | ||
|
|
05207fcde3 | ||
|
|
6d344f82ee | ||
|
|
031382f428 | ||
|
|
859b4c8921 | ||
|
|
1c15ee940d | ||
|
|
32919b1049 | ||
|
|
aae794e9bd | ||
|
|
9443ac7e29 | ||
|
|
8de378963b | ||
|
|
2fe72effa9 | ||
|
|
6f9129fa3a | ||
|
|
70aa033d79 | ||
|
|
fcfdbeebcf | ||
|
|
230d8b6f70 | ||
|
|
9c882cfdab | ||
|
|
dc4e9fc25b | ||
|
|
51128a3484 | ||
|
|
bee17a95b8 | ||
|
|
d24f700256 | ||
|
|
5286808b6c | ||
|
|
6e2bdd663a | ||
|
|
c2c8d29272 | ||
|
|
8b408f0039 | ||
|
|
1c7c9f6c16 | ||
|
|
8d6f0d0583 | ||
|
|
7a5353d18a | ||
|
|
fda9638edd | ||
|
|
43206e6601 | ||
|
|
846271e8d3 | ||
|
|
97385b007f | ||
|
|
f39b75f296 | ||
|
|
787e886ff0 | ||
|
|
bfabc5450b | ||
|
|
736328ab6b | ||
|
|
ba7ecc4669 | ||
|
|
f301f6eccc | ||
|
|
6ca1a2c2dc | ||
|
|
f62a5ed1ff | ||
|
|
5e65f3f07e | ||
|
|
09f718878f | ||
|
|
786037791c | ||
|
|
2ff38e86a7 | ||
|
|
22e1b1610f | ||
|
|
10c4e94e1b | ||
|
|
bb60618941 | ||
|
|
e1d9b57f83 | ||
|
|
771af699f0 | ||
|
|
15d9b08307 | ||
|
|
077177e8a9 | ||
|
|
9361596d02 | ||
|
|
14cd128992 | ||
|
|
b808eb5162 | ||
|
|
c5529808c4 | ||
|
|
39061e0422 | ||
|
|
de3becef96 | ||
|
|
ab5a168dbf | ||
|
|
881eb28134 | ||
|
|
ad1e2d7d05 | ||
|
|
ba81480ebb | ||
|
|
354ba8dad8 | ||
|
|
a62194caee | ||
|
|
87ef869767 | ||
|
|
80ef366bde | ||
|
|
f7031a2161 | ||
|
|
2784ae8793 | ||
|
|
fc154872c0 | ||
|
|
c854f35579 | ||
|
|
45e5a4b024 | ||
|
|
aeb28e33fa | ||
|
|
2a07a2e6b9 | ||
|
|
7e7fd0468d | ||
|
|
3b96de2aa4 | ||
|
|
b5835cbd58 | ||
|
|
feb4dd102b | ||
|
|
2c309227f1 | ||
|
|
e136abf1ef | ||
|
|
20466e7fa4 | ||
|
|
301101e7d9 | ||
|
|
c2add346ad | ||
|
|
f0253633d3 | ||
|
|
25cd5dae26 | ||
|
|
bcc176cdf1 | ||
|
|
d9bd9b7ffa | ||
|
|
06a32d553e | ||
|
|
8e425c4e97 | ||
|
|
42f78b7f9c | ||
|
|
6999968ee5 | ||
|
|
a2960aa457 | ||
|
|
169471cf23 | ||
|
|
77e30c8b45 | ||
|
|
9667fe2b26 | ||
|
|
8414966013 | ||
|
|
bdc821c488 | ||
|
|
b0e6645395 | ||
|
|
4ed0361b97 | ||
|
|
b66a076bf1 | ||
|
|
f5c10a2452 | ||
|
|
9cde179034 | ||
|
|
9f5be55d1c | ||
|
|
7c1cee0373 | ||
|
|
56d2c3e990 | ||
|
|
c7ab7b3539 | ||
|
|
f8ebe985a8 | ||
|
|
1dc922e5a4 | ||
|
|
5048cb0c33 | ||
|
|
9b1c31ebcb | ||
|
|
ded55b7276 | ||
|
|
e851d44ac9 | ||
|
|
e38ec79be1 | ||
|
|
67bd5dd460 | ||
|
|
3b0c2a3543 | ||
|
|
d523e7a676 | ||
|
|
9a1b59adae | ||
|
|
768f254286 | ||
|
|
5034209087 | ||
|
|
ca13c7b4f5 | ||
|
|
7e88e0bfdc | ||
|
|
de305a7e58 | ||
|
|
256749c6d0 | ||
|
|
b43f19cb50 | ||
|
|
d3cd4ad933 | ||
|
|
f3e443ea47 | ||
|
|
ab3af2af37 | ||
|
|
4aae5222fe | ||
|
|
689549bb1f | ||
|
|
813b514f63 | ||
|
|
8edf273f6e | ||
|
|
e1469ed057 | ||
|
|
cb91fbb4b6 | ||
|
|
6d11e93e2f | ||
|
|
d68da27a7f | ||
|
|
996e5fa630 | ||
|
|
511aad419b | ||
|
|
e891abdd6a | ||
|
|
ce38f5b413 | ||
|
|
4afb94687c | ||
|
|
6ad84dab2d | ||
|
|
49ed8b5e21 | ||
|
|
798b44934f | ||
|
|
a9fefcf58b | ||
|
|
046d959657 | ||
|
|
e2aeb7f336 | ||
|
|
297cb370aa | ||
|
|
33f3818520 | ||
|
|
76b58b5663 | ||
|
|
9151685d04 | ||
|
|
7984f08925 | ||
|
|
c6208a98c8 | ||
|
|
f5caa2d265 | ||
|
|
97ec375c67 | ||
|
|
3f8a1fc85b | ||
|
|
b34137dde3 | ||
|
|
0cb07027f2 | ||
|
|
28a1c67dd5 | ||
|
|
5ef69fc591 | ||
|
|
d4a1283797 | ||
|
|
31f88e636c | ||
|
|
1925593a37 | ||
|
|
8a4bbbf5cb | ||
|
|
c8185aec1d | ||
|
|
11ff9ba7ec | ||
|
|
a916a6a8ca | ||
|
|
c41f55c3a0 | ||
|
|
c5bb1fb2ed | ||
|
|
c15ebbfa2e | ||
|
|
b47a4dd255 | ||
|
|
01fa59b6b7 | ||
|
|
8f9f3dbd9d | ||
|
|
7dd204fd31 | ||
|
|
be13788a4f | ||
|
|
b3fd994fd3 | ||
|
|
1969717527 | ||
|
|
a2fc2a986e | ||
|
|
96c30cd410 | ||
|
|
dc5ff7db28 | ||
|
|
4cd65f0e70 | ||
|
|
ce9c9f6be6 | ||
|
|
447b02b530 | ||
|
|
54c1660a1e | ||
|
|
2896c18981 | ||
|
|
eb185bfa47 | ||
|
|
cee0c5423a | ||
|
|
6d351ffc43 | ||
|
|
7e1894978f | ||
|
|
ee9c76ded0 | ||
|
|
481ce82d66 | ||
|
|
bef613e656 | ||
|
|
7ec9ca2b95 | ||
|
|
803f712332 | ||
|
|
9d5d84a835 | ||
|
|
01eca82d33 | ||
|
|
8607fb6312 | ||
|
|
a0b5f79f32 | ||
|
|
fb74a2df27 | ||
|
|
84d36606cb | ||
|
|
6349262491 | ||
|
|
a3a35f2c8c | ||
|
|
b27841e1b5 | ||
|
|
6b72108ee2 | ||
|
|
f87c9bb9f7 | ||
|
|
62b0b65d47 | ||
|
|
bf51d8c19b | ||
|
|
b5e692ef8b | ||
|
|
1272632f3b | ||
|
|
a176d8fbdb | ||
|
|
aabb239c0f | ||
|
|
901daefd96 | ||
|
|
a9e462d952 | ||
|
|
09ab8c6c7c | ||
|
|
1a89538700 | ||
|
|
ebd8ad8937 | ||
|
|
ea2f0e7c3f | ||
|
|
18b87b2e20 | ||
|
|
76ac910b11 | ||
|
|
d87b81dd52 | ||
|
|
259a75e957 | ||
|
|
ba79bf1602 | ||
|
|
0a5ba708e4 | ||
|
|
a3ad740251 | ||
|
|
00ec2a8f09 | ||
|
|
9cf99ea4bf | ||
|
|
8f16fe54d3 | ||
|
|
c4d9f8a8ff | ||
|
|
c797222930 | ||
|
|
d53f0aea75 | ||
|
|
0ce4ef6000 | ||
|
|
a213e14ca3 | ||
|
|
a6bc9cafaf | ||
|
|
f56da385fe | ||
|
|
e271027c0c | ||
|
|
37ab3b1603 | ||
|
|
24168ed86e | ||
|
|
ecbec57a47 | ||
|
|
d5c773a58b | ||
|
|
70b4638a75 | ||
|
|
511d4817d3 | ||
|
|
79832c16e2 | ||
|
|
045ead985c | ||
|
|
55a4e59ef9 | ||
|
|
fabfef9c82 | ||
|
|
e59efb1233 | ||
|
|
50efd9726d | ||
|
|
d0a5425155 | ||
|
|
9814cf5779 | ||
|
|
efa5b9cea6 | ||
|
|
b3108e1ad2 | ||
|
|
ccb184ae64 | ||
|
|
c7cffea9ee | ||
|
|
b319618af1 | ||
|
|
e52d6e3fb8 | ||
|
|
e88ec86c93 | ||
|
|
7268ee9078 | ||
|
|
0209458cc0 | ||
|
|
5905ea0d84 | ||
|
|
12152a8ae4 | ||
|
|
69f56b86b7 | ||
|
|
a42f990818 | ||
|
|
57af75f988 | ||
|
|
b0bf8ca5f7 | ||
|
|
45c7cd3f74 | ||
|
|
38461eef6f | ||
|
|
e11a4ab6af | ||
|
|
87aac8708d | ||
|
|
065eb05e3e | ||
|
|
36e4435bbf | ||
|
|
4f25e1ba9f | ||
|
|
1fd4076082 | ||
|
|
1136108c97 | ||
|
|
15c0078c2d | ||
|
|
56556e5f23 | ||
|
|
c46c124363 | ||
|
|
f066fe47f0 | ||
|
|
85b2fb1e32 | ||
|
|
22c8637610 | ||
|
|
e5ed387426 | ||
|
|
dd58dad15a | ||
|
|
d440d5aa2c | ||
|
|
5edb0deffe | ||
|
|
bebd5ce415 | ||
|
|
dd6fc99ae1 | ||
|
|
5d4a002413 | ||
|
|
e708d19ea3 | ||
|
|
970b95509c | ||
|
|
ab4b3cbd34 | ||
|
|
674a535fc3 | ||
|
|
64e7f25124 | ||
|
|
7341ba5ee3 | ||
|
|
94b9014f95 | ||
|
|
b503c0d6d9 | ||
|
|
73f6982cbe | ||
|
|
0c7725217a | ||
|
|
19bf558e6c | ||
|
|
0d3f2f27e3 | ||
|
|
cb4a74e10b | ||
|
|
45f4eb48fb | ||
|
|
6a0110446c | ||
|
|
8e4ca90680 | ||
|
|
1297b13cd2 | ||
|
|
5e046e6a84 | ||
|
|
08138e9546 | ||
|
|
0759caec6e | ||
|
|
a33954a8f4 | ||
|
|
48cf57818d | ||
|
|
22302d8bcc | ||
|
|
3b738c6e68 | ||
|
|
963ce45f3f | ||
|
|
b20302c2a7 | ||
|
|
8e2402605e | ||
|
|
a45ccfaed0 | ||
|
|
05f9774416 | ||
|
|
32effc6657 | ||
|
|
20923bb2e8 | ||
|
|
cbe399ecd9 | ||
|
|
085c60a334 | ||
|
|
aed9c392eb | ||
|
|
7378ecf9a7 | ||
|
|
ecb1ca61af | ||
|
|
9665661445 | ||
|
|
94583703e1 | ||
|
|
519e9b8b5e | ||
|
|
911af53c5c | ||
|
|
795e39de1a | ||
|
|
c45231ef89 | ||
|
|
aea16ba5d2 | ||
|
|
f3d848da01 | ||
|
|
4bb68282be | ||
|
|
0755aa7e83 | ||
|
|
ba424e8494 | ||
|
|
1b4f97d263 | ||
|
|
f67449700b | ||
|
|
bb2e0d64e1 | ||
|
|
0bf5a7e902 | ||
|
|
91561d7ba7 | ||
|
|
5a4af31d18 | ||
|
|
585fac7af0 | ||
|
|
713d8d59fb | ||
|
|
f64cc04fe6 | ||
|
|
fb2ad83d79 | ||
|
|
708ca3d650 | ||
|
|
3c96d0b68e | ||
|
|
6944d5f901 | ||
|
|
8716ca5784 | ||
|
|
18b702b249 | ||
|
|
cf65ad49e8 | ||
|
|
7982433c71 | ||
|
|
ad7e0f7f32 | ||
|
|
dffe2e0b7c | ||
|
|
5b0eb7b42d | ||
|
|
14b84dd7c5 | ||
|
|
26e22b74b1 | ||
|
|
7b983be3d8 | ||
|
|
c4f1380943 | ||
|
|
bc193c7be5 | ||
|
|
3a7ea1f44b | ||
|
|
3c4669061b | ||
|
|
a3debf7741 | ||
|
|
4d4ef5eed5 | ||
|
|
128c5c3efa | ||
|
|
acbb3cbb70 | ||
|
|
edb28479cc | ||
|
|
a1ac37e771 | ||
|
|
ec0e6d1744 | ||
|
|
b88f0d672f | ||
|
|
f2fbef1f82 | ||
|
|
883fc6be27 | ||
|
|
7c20fb247c | ||
|
|
a5b369ede4 | ||
|
|
3507c72492 | ||
|
|
3d2588edf8 | ||
|
|
0ab05f4807 | ||
|
|
71ce17d977 | ||
|
|
23ada09d14 | ||
|
|
e753bb953b | ||
|
|
4fd26c251c | ||
|
|
40a0dfedea | ||
|
|
04ebd02874 | ||
|
|
accefc3ae1 | ||
|
|
385fc0078f | ||
|
|
b400903e25 | ||
|
|
fa07c1ec67 | ||
|
|
94f749a40d | ||
|
|
a4046e0c3d | ||
|
|
e5a2ac4b57 | ||
|
|
9287946b36 | ||
|
|
09e45794bc | ||
|
|
ed5717c4cd | ||
|
|
7f4328e0b1 | ||
|
|
c7848c4e75 | ||
|
|
88a3b54ca3 | ||
|
|
4f1aaccf81 | ||
|
|
3cc94ab4e8 | ||
|
|
51e8b4f533 | ||
|
|
61c8d6ec8c | ||
|
|
21951feb2b | ||
|
|
d5dc0b2f49 | ||
|
|
82c1a28e6f | ||
|
|
2138273c16 | ||
|
|
3d0d9036fd | ||
|
|
8e79ceba9e | ||
|
|
a34ae5d527 | ||
|
|
a99434929c | ||
|
|
dcd4ab4ab8 | ||
|
|
9fcf5977c0 | ||
|
|
bce1d7313d | ||
|
|
cb820a498b | ||
|
|
cc660bc7c1 | ||
|
|
282c55aa4a | ||
|
|
5f41eb798b | ||
|
|
e8b04e0e2c | ||
|
|
853be450bb | ||
|
|
578765ad83 | ||
|
|
27330a5dd6 | ||
|
|
f17b989650 | ||
|
|
60fd4a262c | ||
|
|
dd3bb13d97 | ||
|
|
86894a1cd2 | ||
|
|
712bc95db3 | ||
|
|
f72555f29a | ||
|
|
e2b3eb91dd | ||
|
|
4ea4e5da4e | ||
|
|
cbf55ccacf | ||
|
|
6ff307b3b5 | ||
|
|
8d7ab86182 | ||
|
|
6a01642a6f | ||
|
|
aac24bfa1b | ||
|
|
60c5d889ec | ||
|
|
d6c5bc3262 | ||
|
|
f5c574cd94 | ||
|
|
23908d9277 | ||
|
|
1b13deff04 | ||
|
|
74a1fa9e00 | ||
|
|
b3a84ba71b | ||
|
|
189cfd717f | ||
|
|
1b66c6a3ac | ||
|
|
df5dd94789 | ||
|
|
4b4fe9d493 | ||
|
|
95293f142a | ||
|
|
926d88fd30 | ||
|
|
b30b8a2944 | ||
|
|
b4dbfb58b8 | ||
|
|
e6c372b14f | ||
|
|
ea851962c1 | ||
|
|
3e6f0e63f3 | ||
|
|
75fd59df3f | ||
|
|
a2fa667f45 | ||
|
|
1421ba504d | ||
|
|
1568518ab3 | ||
|
|
8ff8ec5b70 | ||
|
|
702d3ae6ca | ||
|
|
ec73e4d10d | ||
|
|
d3b348a5b1 | ||
|
|
6fd533ce13 | ||
|
|
4bb3b5383f | ||
|
|
3d9aa1c29c | ||
|
|
a5ab490380 | ||
|
|
1c985537ec | ||
|
|
0dc105d0e4 | ||
|
|
678bdb7c3f | ||
|
|
d31ccf12d0 | ||
|
|
749d36d321 | ||
|
|
67f97cbca1 | ||
|
|
09d856f2ff | ||
|
|
0c94bf43f2 | ||
|
|
aca9af0202 | ||
|
|
9ca84e6b48 | ||
|
|
729bd57cae | ||
|
|
c114afadbd | ||
|
|
fa01bf2b75 | ||
|
|
9d9690410a | ||
|
|
69df42c51b | ||
|
|
bc8c2e0112 | ||
|
|
ebbee61f14 | ||
|
|
3b5d83a3ec | ||
|
|
af114f81a0 | ||
|
|
4b075bb075 | ||
|
|
0b95e72073 | ||
|
|
22f04f6a84 | ||
|
|
6c56b04b3f | ||
|
|
4801d3a305 | ||
|
|
41dc259744 | ||
|
|
316136f428 | ||
|
|
756da87346 | ||
|
|
9f4eb13e9e | ||
|
|
7999975016 | ||
|
|
d6939dd634 | ||
|
|
84e681f028 | ||
|
|
0a302265ec | ||
|
|
231e6eb3f1 | ||
|
|
110a6dd6f0 | ||
|
|
d5308a0493 | ||
|
|
1393942de3 | ||
|
|
d7dbb91e65 | ||
|
|
0fed2451ea | ||
|
|
f2a8368759 | ||
|
|
1c70a84fde | ||
|
|
e9607de5ae | ||
|
|
f8e875a7cd | ||
|
|
0f10c4fd6c | ||
|
|
1685f38ee6 | ||
|
|
0aefc2c65d | ||
|
|
4730ff8d08 | ||
|
|
21a8d832ce | ||
|
|
e0f3703ae6 | ||
|
|
44c0cf594b | ||
|
|
91b22ae21a | ||
|
|
43e27a8843 | ||
|
|
7e3da3007b | ||
|
|
4ee4f47065 | ||
|
|
6ed1c28d2c | ||
|
|
8073ad352b | ||
|
|
9da7152f83 | ||
|
|
208008d717 | ||
|
|
a2283c8229 | ||
|
|
0ca4633898 | ||
|
|
66f25bb434 | ||
|
|
14472cdbd9 | ||
|
|
e5f5a74e81 | ||
|
|
05e6fd969a | ||
|
|
3b3477d657 | ||
|
|
242767d373 | ||
|
|
8b2b65bbbc | ||
|
|
ca46edfe6f | ||
|
|
8e1b51be10 | ||
|
|
ee02e7b07d | ||
|
|
7ffbb3cc27 | ||
|
|
02fff24b4f | ||
|
|
85a4e4ccc1 | ||
|
|
642f9910c2 | ||
|
|
fe11baa4bc | ||
|
|
8b3dbf0972 | ||
|
|
47bc28ce55 | ||
|
|
f1e7d78c96 | ||
|
|
3dbb86914c | ||
|
|
857ac224c5 | ||
|
|
667095e429 | ||
|
|
195eb3356a | ||
|
|
c26c6e285a | ||
|
|
ee7e6e7234 | ||
|
|
f5f2f5a954 | ||
|
|
4aa67e464a | ||
|
|
c006ac3a07 | ||
|
|
7d7b48f104 | ||
|
|
4847cc3bd1 | ||
|
|
dff9e4ec59 | ||
|
|
a9d6eaf937 | ||
|
|
7131c96b26 | ||
|
|
1cafadea6c | ||
|
|
6e58bd8351 | ||
|
|
4449177517 | ||
|
|
34aa979f46 | ||
|
|
7d126ff414 | ||
|
|
41932287cf | ||
|
|
4cf2c45baa | ||
|
|
5c58fc2a28 | ||
|
|
af0feed3e5 | ||
|
|
ce64da2fe1 | ||
|
|
0c6a3731c3 | ||
|
|
9cf22df784 | ||
|
|
eea995637d | ||
|
|
8a2a450778 | ||
|
|
8273962372 | ||
|
|
c50b1f9900 | ||
|
|
1c9d98d57e | ||
|
|
e41555fe0f | ||
|
|
e36d95a4c8 | ||
|
|
6970055dca | ||
|
|
3e67415e3e | ||
|
|
a772179b6c | ||
|
|
6a708b35ee | ||
|
|
419f0be441 | ||
|
|
ca128ae380 | ||
|
|
ddf4eb3b78 | ||
|
|
5e013cad78 | ||
|
|
78665a7e80 | ||
|
|
56d5d4cc21 | ||
|
|
61eb56dfda | ||
|
|
33a5a369f2 | ||
|
|
5e1d73f3cd | ||
|
|
58d967b2f3 | ||
|
|
883368572f | ||
|
|
b95a7cb84f | ||
|
|
400a3f5ad2 | ||
|
|
7fcbca808b | ||
|
|
4eef001d58 | ||
|
|
8a5f2915e9 | ||
|
|
54722369d8 | ||
|
|
b2963bbf80 | ||
|
|
9257273d84 | ||
|
|
a4843ef418 | ||
|
|
1719ff93a7 | ||
|
|
fe7aa25ad8 | ||
|
|
f0f0d02bf7 | ||
|
|
4f3f614457 | ||
|
|
482a032d1a | ||
|
|
86ef638102 | ||
|
|
50a28fe1e8 | ||
|
|
95bce2b20c | ||
|
|
f1075377d3 | ||
|
|
be4797d619 | ||
|
|
14ea4322fe | ||
|
|
34520aa16e | ||
|
|
1faad5add7 | ||
|
|
698c5b54f3 | ||
|
|
7eee415b75 | ||
|
|
d59a4e7a77 | ||
|
|
e0475a5421 | ||
|
|
becd8ed049 | ||
|
|
f314699945 | ||
|
|
341a5eeefd | ||
|
|
5334e3d1aa | ||
|
|
d00a2a222e | ||
|
|
a3d54614c7 | ||
|
|
e567250449 | ||
|
|
e1d66ad0c1 | ||
|
|
eea21070ee | ||
|
|
be22736f23 | ||
|
|
12e0edd6e1 | ||
|
|
90ad3d6491 | ||
|
|
ddcbf2a7b4 | ||
|
|
0b4f1b4af2 | ||
|
|
33dcb4d49a | ||
|
|
8aa61ef45c | ||
|
|
53a5d62e5a | ||
|
|
3111bc89e5 | ||
|
|
89d0fe561f | ||
|
|
1af0277564 | ||
|
|
05131a9b0c | ||
|
|
aea687c0a1 | ||
|
|
764248bb0d | ||
|
|
1782474481 | ||
|
|
753c4b5d4f | ||
|
|
9d75c55146 | ||
|
|
139fd6d55c | ||
|
|
f3c5b82c82 | ||
|
|
e912656682 | ||
|
|
fd813d0e6c | ||
|
|
613c61abb4 | ||
|
|
391bc119de | ||
|
|
76e471c4bc | ||
|
|
a36f67cbe3 | ||
|
|
0a61cfc3be | ||
|
|
57d8f74554 | ||
|
|
8223b4b76c | ||
|
|
b59fd7c388 | ||
|
|
fc973a3bb9 | ||
|
|
15d7885c78 | ||
|
|
d68d5faa05 | ||
|
|
9e4d82a484 | ||
|
|
fa97df0eab | ||
|
|
62c6c9a78a | ||
|
|
b3255465f1 | ||
|
|
098f0aafd4 | ||
|
|
478474d0ce | ||
|
|
555edd60d4 | ||
|
|
d3ffb0bde1 | ||
|
|
c12db68363 | ||
|
|
c3b119ea81 | ||
|
|
a49281ced3 | ||
|
|
899b768b74 | ||
|
|
648f6016e3 | ||
|
|
fdd753263b | ||
|
|
27b4c2ac2d | ||
|
|
023a1b8da6 | ||
|
|
0b8791d1c5 | ||
|
|
e6d1e0cc27 | ||
|
|
4a4f2b5dae | ||
|
|
df96b7f4c0 | ||
|
|
5b3b89cb64 | ||
|
|
7fca81dd3f | ||
|
|
21d99f8dce | ||
|
|
c96863b188 | ||
|
|
b2a29280cb | ||
|
|
59f0a721c4 | ||
|
|
829353a5da | ||
|
|
2fd29cbf50 | ||
|
|
da81b63ec0 | ||
|
|
6de69aa9b7 | ||
|
|
8b4cb2457a | ||
|
|
b7100d5716 | ||
|
|
d1df98e0ca | ||
|
|
bee4a96b45 | ||
|
|
5a3706ac46 | ||
|
|
53b0eb8e1b | ||
|
|
3f7be59062 | ||
|
|
3cd0e2adb0 | ||
|
|
efc619b0af | ||
|
|
f53e7f7478 | ||
|
|
235e0e9e60 | ||
|
|
28ab41caad | ||
|
|
b3745b31c7 | ||
|
|
66df6fb2f6 | ||
|
|
98dfeb05ab | ||
|
|
50f47dcba9 | ||
|
|
efc89a7317 | ||
|
|
dbee099eeb | ||
|
|
7fa4a36c08 | ||
|
|
3609017c38 | ||
|
|
58898c0633 | ||
|
|
0e1dbbbd06 | ||
|
|
5b74808ed0 | ||
|
|
87d45c2a01 | ||
|
|
19e37b17aa | ||
|
|
080a11eb73 | ||
|
|
d2fcb5343f | ||
|
|
4a73828911 | ||
|
|
247035e9e4 | ||
|
|
e00a20465b | ||
|
|
3800b279d6 | ||
|
|
ba09d55aab | ||
|
|
8fc43ccd2d | ||
|
|
a361d7917b | ||
|
|
6388954e8f | ||
|
|
7bfa700c55 | ||
|
|
c2e481fb6a | ||
|
|
c2baaff3c1 | ||
|
|
bf83a0b2bd | ||
|
|
ddd6004b27 | ||
|
|
9155948ac8 | ||
|
|
803b250652 | ||
|
|
6ed5dce5ab | ||
|
|
c8e13ad393 | ||
|
|
ee794a121e | ||
|
|
72bc0acfbd | ||
|
|
49fb0cf359 | ||
|
|
7a37e6891f | ||
|
|
5e305fa854 | ||
|
|
df71d9c6de | ||
|
|
5e9d68695c | ||
|
|
fc46087ce9 | ||
|
|
75f9bed6b6 | ||
|
|
3555e81fee | ||
|
|
a44a0b4ebe | ||
|
|
ce31cc518a | ||
|
|
c61247f317 | ||
|
|
8f74b1090a | ||
|
|
d71e588800 | ||
|
|
c309c9f572 | ||
|
|
8a4efb3427 | ||
|
|
9db92bd942 | ||
|
|
27a92b1158 | ||
|
|
64b6b5d2a7 | ||
|
|
32b8c5c9b6 | ||
|
|
0bdb6580bd | ||
|
|
8ccabb7974 | ||
|
|
36818459e5 | ||
|
|
e5989d81b9 | ||
|
|
5a60bbc119 | ||
|
|
434fd6aa1f | ||
|
|
ba46a039ac | ||
|
|
c5d533ec71 | ||
|
|
0a5073f570 | ||
|
|
4bd62b1005 | ||
|
|
913ae94cf9 | ||
|
|
29a14632d3 | ||
|
|
424d045084 | ||
|
|
2bb7b28837 | ||
|
|
d8f64d1047 | ||
|
|
30c8d3e652 | ||
|
|
cf280e6655 | ||
|
|
b656124791 | ||
|
|
44743b5635 | ||
|
|
6e2a47287a | ||
|
|
c1f374cc8d | ||
|
|
a9ecf1449e | ||
|
|
1e6204e1ac | ||
|
|
1dddb63d9f | ||
|
|
d63a00a649 | ||
|
|
2388015b10 | ||
|
|
c15e75357a | ||
|
|
425cf894d4 | ||
|
|
5d576fccba | ||
|
|
1eb90b2405 | ||
|
|
774570ec41 | ||
|
|
d6d143e2a6 | ||
|
|
66d9b8da60 | ||
|
|
8a3620bafa | ||
|
|
70ffc2632f | ||
|
|
3a59cbbaa3 | ||
|
|
67b3663f39 | ||
|
|
2c0c4ce821 | ||
|
|
24f12c7b5e | ||
|
|
d88bea5e22 | ||
|
|
690d78edfa | ||
|
|
b91bde8a65 | ||
|
|
93b6883896 | ||
|
|
16872ecc41 | ||
|
|
8859e134ef | ||
|
|
1433a81c08 | ||
|
|
b4e418f251 | ||
|
|
c51050a9bc | ||
|
|
134b54dfe0 | ||
|
|
f4b8e4f4d6 | ||
|
|
e78319fccd | ||
|
|
a2f765b72a | ||
|
|
d2f81443f1 | ||
|
|
738deb2368 | ||
|
|
3b41379be4 | ||
|
|
7eea69df89 | ||
|
|
c13495e26e | ||
|
|
dc7642f2a5 | ||
|
|
a2db3cdd5b | ||
|
|
e2baa65238 | ||
|
|
98528da5e5 | ||
|
|
479b3c8ee1 | ||
|
|
a858934c04 | ||
|
|
446288d9a1 |
209 changed files with 20906 additions and 5481 deletions
|
|
@ -1,4 +1,4 @@
|
|||
image: alpine/latest
|
||||
image: alpine/edge
|
||||
packages:
|
||||
- musl-dev
|
||||
- eudev-libs
|
||||
|
|
@ -47,6 +47,9 @@ tasks:
|
|||
ninja -C bld/release -k0
|
||||
meson test -C bld/release --print-errorlogs
|
||||
- codespell: |
|
||||
python3 -m venv codespell-venv
|
||||
source codespell-venv/bin/activate
|
||||
pip install codespell
|
||||
cd foot
|
||||
~/.local/bin/codespell -Lser,doas,zar README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd
|
||||
deactivate
|
||||
|
|
@ -19,7 +19,7 @@ packages:
|
|||
- noto-emoji
|
||||
|
||||
sources:
|
||||
- https://git.sr.ht/~dnkl/foot
|
||||
- https://codeberg.org/dnkl/foot.git
|
||||
|
||||
# triggers:
|
||||
# - action: email
|
||||
|
|
@ -29,11 +29,12 @@ sources:
|
|||
tasks:
|
||||
- fcft: |
|
||||
cd foot/subprojects
|
||||
git clone https://codeberg.org/dnkl/tllist.git
|
||||
git clone https://codeberg.org/dnkl/fcft.git
|
||||
cd ../..
|
||||
- debug: |
|
||||
mkdir -p bld/debug
|
||||
meson --buildtype=debug -Dterminfo=disabled -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/debug
|
||||
meson setup --buildtype=debug -Dterminfo=disabled -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/debug
|
||||
ninja -C bld/debug -k0
|
||||
meson test -C bld/debug --print-errorlogs
|
||||
bld/debug/foot --version
|
||||
|
|
@ -41,7 +42,7 @@ tasks:
|
|||
|
||||
- release: |
|
||||
mkdir -p bld/release
|
||||
meson --buildtype=minsize -Db_pgo=generate -Dterminfo=disabled -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/release
|
||||
meson setup --buildtype=minsize -Db_pgo=generate -Dterminfo=disabled -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true foot bld/release
|
||||
ninja -C bld/release -k0
|
||||
meson test -C bld/release --print-errorlogs
|
||||
bld/release/foot --version
|
||||
|
|
|
|||
127
.forgejo/issue_template/bug.yml
Normal file
127
.forgejo/issue_template/bug.yml
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
name: Bug Report
|
||||
description: File a bug report
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please provide as many details as possible, we must be able to
|
||||
understand the bug in order to fix it.
|
||||
|
||||
Don't forget to search the issue tracker in case there is
|
||||
already an open issue for the bug you found.
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Foot Version
|
||||
description: "The output of `foot --version`"
|
||||
placeholder: "foot version: 1.17.2-11-gc4f13809 (May 20 2024, branch 'master') +pgo +ime +graphemes -assertions"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: term
|
||||
attributes:
|
||||
label: TERM environment variable
|
||||
description: "The output of `echo $TERM`"
|
||||
placeholder: "foot"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: compositor
|
||||
attributes:
|
||||
label: Compositor Name and Version
|
||||
description: "The name and version of your compositor"
|
||||
placeholder: "sway version 1.9"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: distro
|
||||
attributes:
|
||||
label: Distribution
|
||||
description: "The name of the Linux distribution, or BSD flavor, you are running. And, if applicable, the version"
|
||||
placeholder: "Fedora Workstation 41"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: multiplexer
|
||||
attributes:
|
||||
label: Terminal multiplexer
|
||||
description: "Terminal multiplexers are terminal emulators themselves, therefore the issue may be in the multiplexer, not foot. Please list which multiplexer(s) you use here (and mention in the problem description below if the issue only occurs in the multiplexer, but not in bare metal foot)"
|
||||
placeholder: "tmux, zellij"
|
||||
- type: input
|
||||
id: application
|
||||
attributes:
|
||||
label: Shell, TUI, application
|
||||
description: "Application(s) in which the problem occurs (list all known)"
|
||||
placeholder: "bash, neovim"
|
||||
- type: checkboxes
|
||||
id: server
|
||||
attributes:
|
||||
label: Server/standalone mode
|
||||
description: Does the issue occur in foot server, or standalone mode, or both? Note that you **cannot** test standalone mode by manually running `foot` from a `footclient` instance, since then the standalone foot will simply inherit the server process' context.
|
||||
options:
|
||||
- label: Standalone
|
||||
- label: Server
|
||||
- type: textarea
|
||||
id: config
|
||||
attributes:
|
||||
label: Foot config
|
||||
description: Paste your entire `foot.ini` here (do not forget to sanitize it!)
|
||||
render: ini
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: repro
|
||||
attributes:
|
||||
label: Description of Bug and Steps to Reproduce
|
||||
description: |
|
||||
Exactly what steps can someone else take to see the bug
|
||||
themselves? What happens?
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please provide as many details as possible, we must be able to
|
||||
understand the bug in order to fix it.
|
||||
|
||||
Other software
|
||||
--------------
|
||||
|
||||
**Compositors**: have you tested other compositors? Does the
|
||||
issue happen on all of them, or only your main compositor?
|
||||
|
||||
**Terminal multiplexers**: are you using tmux, zellij, or any
|
||||
other terminal multiplexer? Does the bug happen in a plain
|
||||
foot instance?
|
||||
|
||||
**IME** do you use an IME (e.g. fcitx5, ibus etc)? Which one?
|
||||
Does the bug happen if you disable the IME?
|
||||
|
||||
Obtaining logs and stacktraces
|
||||
------------------------------
|
||||
|
||||
Use a [debug
|
||||
build](https://codeberg.org/dnkl/foot/src/branch/master/INSTALL.md#debug-build)
|
||||
of foot if possible, to get a better quality stacktrace in
|
||||
case of a crash.
|
||||
|
||||
Run foot with logging enabled:
|
||||
```sh
|
||||
foot -d info 2> foot.log
|
||||
```
|
||||
|
||||
In many cases, tracing the Wayland communication is extremely helpful:
|
||||
```sh
|
||||
WAYLAND_DEBUG=1 foot -d info 2> foot.wayland.log
|
||||
```
|
||||
|
||||
Reproduce your problem as quickly as possible, and then exit foot.
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant logs, stacktraces, etc.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please attach files instead of pasting the logs, if the logs are large
|
||||
5
.forgejo/issue_template/config.yml
Normal file
5
.forgejo/issue_template/config.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: IRC
|
||||
url: https://web.libera.chat/?channels=#foot
|
||||
about: Join the IRC channel for foot-related discussion and support
|
||||
26
.forgejo/issue_template/feature_request.yml
Normal file
26
.forgejo/issue_template/feature_request.yml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
name: Feature Request
|
||||
description: Request a new feature
|
||||
labels: ["enhancement"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please search the man page (`foot.ini(5)` and `foot(1)`);
|
||||
maybe the feature already exists?
|
||||
|
||||
If the feature does not exist in your installed version of
|
||||
foot, please check the **latest** version of foot; maybe the
|
||||
feature has already been added?
|
||||
|
||||
Please describe your feature request in as much details as
|
||||
possible. Describe your use case. Explain why the existing
|
||||
feature set is not sufficient. Foot is (trying to be) a
|
||||
minimalistic terminal emulator; explain how your desired
|
||||
feature does not add bloat.
|
||||
- type: textarea
|
||||
id: request
|
||||
attributes:
|
||||
label: Describe your feature request
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
|
@ -1,22 +1,33 @@
|
|||
pipeline:
|
||||
codespell:
|
||||
# -*- yaml -*-
|
||||
|
||||
steps:
|
||||
- name: pychecks
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
- releases/*
|
||||
image: alpine:latest
|
||||
- event: [manual, pull_request]
|
||||
- event: [push, tag]
|
||||
branch: [master, releases/*]
|
||||
image: alpine:edge
|
||||
commands:
|
||||
- apk add openssl
|
||||
- apk add python3
|
||||
- apk add py3-pip
|
||||
- python3 -m venv venv
|
||||
- source venv/bin/activate
|
||||
- python -m pip install --upgrade pip
|
||||
- pip install codespell
|
||||
- codespell -Lser,doas,zar README.md INSTALL.md CHANGELOG.md *.c *.h doc/*.scd
|
||||
- pip install mypy
|
||||
- pip install ruff
|
||||
- codespell
|
||||
- mypy
|
||||
- ruff check
|
||||
- deactivate
|
||||
|
||||
subprojects:
|
||||
- name: subprojects
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
- releases/*
|
||||
image: alpine:latest
|
||||
- event: [manual, pull_request]
|
||||
- event: [push, tag]
|
||||
branch: [master, releases/*]
|
||||
image: alpine:edge
|
||||
commands:
|
||||
- apk add git
|
||||
- mkdir -p subprojects && cd subprojects
|
||||
|
|
@ -24,13 +35,13 @@ pipeline:
|
|||
- git clone https://codeberg.org/dnkl/fcft.git
|
||||
- cd ..
|
||||
|
||||
x64:
|
||||
- name: x64
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
- releases/*
|
||||
group: build
|
||||
image: alpine:latest
|
||||
- event: [manual, pull_request]
|
||||
- event: [push, tag]
|
||||
branch: [master, releases/*]
|
||||
depends_on: [subprojects]
|
||||
image: alpine:edge
|
||||
commands:
|
||||
- apk update
|
||||
- apk add musl-dev linux-headers meson ninja gcc clang scdoc ncurses
|
||||
|
|
@ -43,7 +54,7 @@ pipeline:
|
|||
# Debug
|
||||
- mkdir -p bld/debug-x64
|
||||
- cd bld/debug-x64
|
||||
- meson --buildtype=debug -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../..
|
||||
- meson setup --buildtype=debug -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../..
|
||||
- ninja -v -k0
|
||||
- ninja -v test
|
||||
- ./foot --version
|
||||
|
|
@ -53,7 +64,7 @@ pipeline:
|
|||
# Release (gcc)
|
||||
- mkdir -p bld/release-x64
|
||||
- cd bld/release-x64
|
||||
- meson --buildtype=release -Db_pgo=generate -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../..
|
||||
- meson setup --buildtype=release -Db_pgo=generate -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../..
|
||||
- ninja -v -k0
|
||||
- ninja -v test
|
||||
- ./foot --version
|
||||
|
|
@ -63,7 +74,7 @@ pipeline:
|
|||
# Release (clang)
|
||||
- mkdir -p bld/release-x64-clang
|
||||
- cd bld/release-x64-clang
|
||||
- CC=clang meson --buildtype=release -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../..
|
||||
- CC=clang meson setup --buildtype=release -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../..
|
||||
- ninja -v -k0
|
||||
- ninja -v test
|
||||
- ./foot --version
|
||||
|
|
@ -74,20 +85,20 @@ pipeline:
|
|||
- apk del harfbuzz harfbuzz-dev utf8proc utf8proc-dev
|
||||
- mkdir -p bld/debug
|
||||
- cd bld/debug
|
||||
- meson --buildtype=debug -Dgrapheme-clustering=disabled -Dfcft:grapheme-shaping=disabled -Dfcft:run-shaping=disabled -Dfcft:test-text-shaping=false ../..
|
||||
- meson setup --buildtype=debug -Dgrapheme-clustering=disabled -Dfcft:grapheme-shaping=disabled -Dfcft:run-shaping=disabled -Dfcft:test-text-shaping=false ../..
|
||||
- ninja -v -k0
|
||||
- ninja -v test
|
||||
- ./foot --version
|
||||
- ./footclient --version
|
||||
- cd ../..
|
||||
|
||||
x86:
|
||||
- name: x86
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
- releases/*
|
||||
group: build
|
||||
image: i386/alpine:latest
|
||||
- event: [manual, pull_request]
|
||||
- event: [push, tag]
|
||||
branch: [master, releases/*]
|
||||
depends_on: [subprojects]
|
||||
image: i386/alpine:edge
|
||||
commands:
|
||||
- apk update
|
||||
- apk add musl-dev linux-headers meson ninja gcc clang scdoc ncurses
|
||||
|
|
@ -100,7 +111,7 @@ pipeline:
|
|||
# Debug
|
||||
- mkdir -p bld/debug-x86
|
||||
- cd bld/debug-x86
|
||||
- meson --buildtype=debug -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../..
|
||||
- meson setup --buildtype=debug -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../..
|
||||
- ninja -v -k0
|
||||
- ninja -v test
|
||||
- ./foot --version
|
||||
|
|
@ -110,7 +121,7 @@ pipeline:
|
|||
# Release (gcc)
|
||||
- mkdir -p bld/release-x86
|
||||
- cd bld/release-x86
|
||||
- meson --buildtype=release -Db_pgo=generate -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../..
|
||||
- meson setup --buildtype=release -Db_pgo=generate -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../..
|
||||
- ninja -v -k0
|
||||
- ninja -v test
|
||||
- ./foot --version
|
||||
|
|
@ -120,7 +131,7 @@ pipeline:
|
|||
# Release (clang)
|
||||
- mkdir -p bld/release-x86-clang
|
||||
- cd bld/release-x86-clang
|
||||
- CC=clang meson --buildtype=release -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../..
|
||||
- CC=clang meson setup --buildtype=release -Dgrapheme-clustering=enabled -Dfcft:grapheme-shaping=enabled -Dfcft:run-shaping=enabled -Dfcft:test-text-shaping=true ../..
|
||||
- ninja -v -k0
|
||||
- ninja -v test
|
||||
- ./foot --version
|
||||
1597
CHANGELOG.md
1597
CHANGELOG.md
File diff suppressed because it is too large
Load diff
83
CODE_OF_CONDUCT.md
Normal file
83
CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
# Foot Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
Participants in the foot community are expected to uphold the described
|
||||
standards not only in official community spaces (issue trackers, IRC channels,
|
||||
etc.) but in all public spaces. The Code of Conduct however does acknowledge
|
||||
that people are fallible and that it is possible to truly correct a past
|
||||
pattern of unacceptable behavior. That is to say, the scope of the Code of
|
||||
Conduct does not necessarily extend into the distant past.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior
|
||||
may be reported to the community leaders responsible for enforcement
|
||||
at [daniel@ekloef.se](mailto:daniel@ekloef.se). All complaints will
|
||||
be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
The consequences for Code of Conduct violations will be decided upon and
|
||||
enforced by community leaders. These may include a formal warning, a temporary
|
||||
ban from community spaces, a permanent ban from community spaces, etc.
|
||||
|
||||
There are no hard and fast rules for exactly what behavior in which space will
|
||||
result in what consequences, it is up to the community leaders to enforce the
|
||||
Code of Conduct in a way that they believe best promotes a healthy community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the
|
||||
[Contributor Covenant](https://www.contributor-covenant.org/),
|
||||
version 2.1, available at
|
||||
https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
|
||||
69
INSTALL.md
69
INSTALL.md
|
|
@ -45,7 +45,8 @@ subprojects.
|
|||
* wayland (_client_ and _cursor_ libraries)
|
||||
* xkbcommon
|
||||
* utf8proc (_optional_, needed for grapheme clustering)
|
||||
* libutempter (_optional_, needed for utmp logging)
|
||||
* libutempter (_optional_, needed for utmp logging on Linux)
|
||||
* ulog (_optional_, needed for utmp logging on FreeBSD)
|
||||
* [fcft](https://codeberg.org/dnkl/fcft) [^1]
|
||||
|
||||
[^1]: can also be built as subprojects, in which case they are
|
||||
|
|
@ -93,24 +94,24 @@ A note on terminfo; the terminfo database exposes terminal
|
|||
capabilities to the applications running inside the terminal. As such,
|
||||
it is important that the terminfo used reflects the actual
|
||||
terminal. Using the `xterm-256color` terminfo will, in many cases,
|
||||
work, but I still recommend using foot’s own terminfo. There are two
|
||||
work, but I still recommend using foot's own terminfo. There are two
|
||||
reasons for this:
|
||||
|
||||
* foot’s terminfo contains a couple of non-standard capabilities,
|
||||
* foot's terminfo contains a couple of non-standard capabilities,
|
||||
used by e.g. tmux.
|
||||
* New capabilities added to the `xterm-256color` terminfo could
|
||||
potentially break foot.
|
||||
* There may be future additions or changes to foot’s terminfo.
|
||||
* There may be future additions or changes to foot's terminfo.
|
||||
|
||||
As of ncurses 2021-07-31, ncurses includes a version of foot’s
|
||||
As of ncurses 2021-07-31, ncurses includes a version of foot's
|
||||
terminfo. **The recommendation is to use those**, and only install the
|
||||
terminfo definitions from this git repo if the system’s ncurses
|
||||
terminfo definitions from this git repo if the system's ncurses
|
||||
predates 2021-07-31.
|
||||
|
||||
But, note that the foot terminfo definitions in ncurses’ lack the
|
||||
But, note that the foot terminfo definitions in ncurses' lack the
|
||||
non-standard capabilities. This mostly affects tmux; without them,
|
||||
`terminal-overrides` must be configured to enable truecolor
|
||||
support. For this reason, it _is_ possible to install “our” terminfo
|
||||
support. For this reason, it _is_ possible to install "our" terminfo
|
||||
definitions as well, either in a non-default location, or under a
|
||||
different name.
|
||||
|
||||
|
|
@ -123,10 +124,10 @@ details.
|
|||
Installing them under a different name generally works well, but will
|
||||
break applications that check if `$TERM == foot`.
|
||||
|
||||
Hence the recommendation to simply use ncurses’ terminfo definitions
|
||||
Hence the recommendation to simply use ncurses' terminfo definitions
|
||||
if available.
|
||||
|
||||
If packaging “our” terminfo definitions, I recommend doing that as a
|
||||
If packaging "our" terminfo definitions, I recommend doing that as a
|
||||
separate package, to allow them to be installed on remote systems
|
||||
without having to install foot itself.
|
||||
|
||||
|
|
@ -142,17 +143,19 @@ mkdir -p bld/release && cd bld/release
|
|||
|
||||
Available compile-time options:
|
||||
|
||||
| Option | Type | Default | Description | Extra dependencies |
|
||||
|--------------------------------------|---------|-------------------------|-----------------------------------------------------------|--------------------|
|
||||
| `-Ddocs` | feature | `auto` | Builds and install documentation | scdoc |
|
||||
| `-Dtests` | bool | `true` | Build tests (adds a `ninja test` build target) | none |
|
||||
| `-Dime` | bool | `true` | Enables IME support | None |
|
||||
| `-Dgrapheme-clustering` | feature | `auto` | Enables grapheme clustering | libutf8proc |
|
||||
| `-Dterminfo` | feature | `enabled` | Build and install terminfo files | tic (ncurses) |
|
||||
| `-Ddefault-terminfo` | string | `foot` | Default value of `TERM` | none |
|
||||
| `-Dcustom-terminfo-install-location` | string | `${datadir}/terminfo` | Value to set `TERMINFO` to | None |
|
||||
| `-Dsystemd-units-dir` | string | `${systemduserunitdir}` | Where to install the systemd service files (absolute) | None |
|
||||
| `-Ddefault-utempter-path` | feature | `auto` | Default path to utempter binary (‘none’ disables default) | libutempter |
|
||||
| Option | Type | Default | Description | Extra dependencies |
|
||||
|--------------------------------------|---------|-------------------------|---------------------------------------------------------------------------------|---------------------|
|
||||
| `-Ddocs` | feature | `auto` | Builds and install documentation | scdoc |
|
||||
| `-Dtests` | bool | `true` | Build tests (adds a `ninja test` build target) | None |
|
||||
| `-Dime` | bool | `true` | Enables IME support | None |
|
||||
| `-Dgrapheme-clustering` | feature | `auto` | Enables grapheme clustering | libutf8proc |
|
||||
| `-Dterminfo` | feature | `enabled` | Build and install terminfo files | tic (ncurses) |
|
||||
| `-Ddefault-terminfo` | string | `foot` | Default value of `TERM` | None |
|
||||
| `-Dterminfo-base-name` | string | `-Ddefault-terminfo` | Base name of the generated terminfo files | None |
|
||||
| `-Dcustom-terminfo-install-location` | string | `${datadir}/terminfo` | Value to set `TERMINFO` to | None |
|
||||
| `-Dsystemd-units-dir` | string | `${systemduserunitdir}` | Where to install the systemd service files (absolute) | None |
|
||||
| `-Dutmp-backend` | combo | `auto` | Which utmp backend to use (`none`, `libutempter`, `ulog` or `auto`) | libutempter or ulog |
|
||||
| `-Dutmp-default-helper-path` | string | `auto` | Default path to utmp helper binary. `auto` selects path based on `utmp-backend` | None |
|
||||
|
||||
Documentation includes the man pages, readme, changelog and license
|
||||
files.
|
||||
|
|
@ -163,9 +166,19 @@ under a different name. Setting this changes the default value of
|
|||
`$TERM`, and the names of the terminfo files (if
|
||||
`-Dterminfo=enabled`).
|
||||
|
||||
`-Dcustom-terminfo-install-location` enables foot’s terminfo to
|
||||
co-exist with ncurses’ version, without changing the terminfo
|
||||
names. The idea is that you install foot’s terminfo to a non-standard
|
||||
If you want foot to use the terminfo files from ncurses, but still
|
||||
package foot's own terminfo files under a different name, you can use
|
||||
the `-Dterminfo-base-name` option. Many distributions use the name
|
||||
`foot-extra`, and thus it might be a good idea to reuse that:
|
||||
|
||||
```sh
|
||||
meson ... -Ddefault-terminfo=foot -Dterminfo-base-name=foot-extra
|
||||
```
|
||||
(or just leave out `-Ddefault-terminfo`, since it defaults to `foot` anyway).
|
||||
|
||||
Finally, `-Dcustom-terminfo-install-location` enables foot's terminfo
|
||||
to co-exist with ncurses' version, without changing the terminfo
|
||||
names. The idea is that you install foot's terminfo to a non-standard
|
||||
location, for example `/usr/share/foot/terminfo`. Use
|
||||
`-Dcustom-terminfo-install-location` to tell foot where the terminfo
|
||||
is. Foot will set the environment variable `TERMINFO` to this value
|
||||
|
|
@ -181,7 +194,7 @@ in the meson build. It does **not** change the default value of
|
|||
`TERM`, and it does **not** disable `TERMINFO`, if
|
||||
`-Dcustom-terminfo-install-location` has been set. Use this if
|
||||
packaging the terminfo definitions in a separate package (and the
|
||||
build script isn’t shared with the ‘foot’ package).
|
||||
build script isn't shared with the 'foot' package).
|
||||
|
||||
Example:
|
||||
|
||||
|
|
@ -256,7 +269,7 @@ reason there are a number of helper scripts available.
|
|||
scripts in the `pgo` directory to do a complete PGO build. This script
|
||||
is intended to be used when doing manual builds.
|
||||
|
||||
Note that all “full” PGO builds (which `auto` will prefer, if
|
||||
Note that all "full" PGO builds (which `auto` will prefer, if
|
||||
possible) **require** `LC_CTYPE` to be set to an UTF-8 locale. This is
|
||||
**not** done automatically.
|
||||
|
||||
|
|
@ -357,7 +370,7 @@ fail.
|
|||
|
||||
The snippet above then creates an (empty) temporary file. Then, it
|
||||
runs a script that generates random escape sequences (if you cat
|
||||
`${tmp_file}` in a terminal, you’ll see random colored characters all
|
||||
`${tmp_file}` in a terminal, you'll see random colored characters all
|
||||
over the screen). Finally, we feed the randomly generated escape
|
||||
sequences to the PGO helper. This is what generates the profiling data
|
||||
used in the next step.
|
||||
|
|
@ -437,7 +450,7 @@ sed 's/@default_terminfo@/foot/g' foot.info | \
|
|||
tic -o <output-directory> -x -e foot,foot-direct -
|
||||
```
|
||||
|
||||
Where _”output-directory”_ **must** match the value passed to
|
||||
Where _"output-directory"_ **must** match the value passed to
|
||||
`-Dcustom-terminfo-install-location` in the foot build. If
|
||||
`-Dcustom-terminfo-install-location` has not been set, `-o
|
||||
<output-directory>` can simply be omitted.
|
||||
|
|
|
|||
185
README.md
185
README.md
|
|
@ -3,10 +3,8 @@
|
|||
The fast, lightweight and minimalistic Wayland terminal emulator.
|
||||
|
||||
[](https://ci.codeberg.org/dnkl/foot)
|
||||
[](https://gitlab.com/dnkl/foot/commits/master)
|
||||
[](https://builds.sr.ht/~dnkl/foot?)
|
||||
|
||||
[](https://repology.org/project/foot/versions)
|
||||
[](https://repology.org/project/foot/versions)
|
||||
|
||||
|
||||
## Index
|
||||
|
|
@ -22,11 +20,13 @@ The fast, lightweight and minimalistic Wayland terminal emulator.
|
|||
1. [Normal mode](#normal-mode)
|
||||
1. [Scrollback search](#scrollback-search)
|
||||
1. [Mouse](#mouse)
|
||||
1. [Touchscreen](#touchscreen)
|
||||
1. [Server (daemon) mode](#server-daemon-mode)
|
||||
1. [URLs](#urls)
|
||||
1. [Shell integration](#shell-integration)
|
||||
1. [Current working directory](#current-working-directory)
|
||||
1. [Jumping between prompts](#jumping-between-prompts)
|
||||
1. [Piping last command's output](#piping-last-command-s-output)
|
||||
1. [Alt/meta](#alt-meta)
|
||||
1. [Backspace](#backspace)
|
||||
1. [Keypad](#keypad)
|
||||
|
|
@ -35,6 +35,7 @@ The fast, lightweight and minimalistic Wayland terminal emulator.
|
|||
1. [Programmatically checking if running in foot](#programmatically-checking-if-running-in-foot)
|
||||
1. [XTGETTCAP](#xtgettcap)
|
||||
1. [Credits](#Credits)
|
||||
1. [Code of Conduct](#code-of-conduct)
|
||||
1. [Bugs](#bugs)
|
||||
1. [Contact](#contact)
|
||||
1. [IRC](#irc)
|
||||
|
|
@ -60,10 +61,11 @@ The fast, lightweight and minimalistic Wayland terminal emulator.
|
|||
* IME (via `text-input-v3`)
|
||||
* Multi-seat
|
||||
* True Color (24bpp)
|
||||
* [Styled and colored underlines](https://sw.kovidgoyal.net/kitty/underlines/)
|
||||
* [Synchronized Updates](https://gitlab.freedesktop.org/terminal-wg/specifications/-/merge_requests/2) support
|
||||
* [Sixel image support](https://en.wikipedia.org/wiki/Sixel)
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
# Installing
|
||||
|
|
@ -149,10 +151,10 @@ These are the default shortcuts. See `man foot.ini` and the example
|
|||
: Start a scrollback search
|
||||
|
||||
<kbd>ctrl</kbd>+<kbd>+</kbd>, <kbd>ctrl</kbd>+<kbd>=</kbd>
|
||||
: Increase font size by 0,5pt
|
||||
: Increase font size
|
||||
|
||||
<kbd>ctrl</kbd>+<kbd>-</kbd>
|
||||
: Decrease font size by 0,5pt
|
||||
: Decrease font size
|
||||
|
||||
<kbd>ctrl</kbd>+<kbd>0</kbd>
|
||||
: Reset font size
|
||||
|
|
@ -163,10 +165,13 @@ These are the default shortcuts. See `man foot.ini` and the example
|
|||
sequence](https://codeberg.org/dnkl/foot/wiki#user-content-spawning-new-terminal-instances-in-the-current-working-directory),
|
||||
the new terminal will start in the current working directory.
|
||||
|
||||
<kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>u</kbd>
|
||||
<kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>o</kbd>
|
||||
: Enter URL mode, where all currently visible URLs are tagged with a
|
||||
jump label with a key sequence that will open the URL.
|
||||
|
||||
<kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>u</kbd>
|
||||
: Enter Unicode input mode.
|
||||
|
||||
<kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>z</kbd>
|
||||
: Jump to the previous, currently not visible, prompt. Requires [shell
|
||||
integration](https://codeberg.org/dnkl/foot/wiki#user-content-jumping-between-prompts).
|
||||
|
|
@ -232,7 +237,11 @@ These are the default shortcuts. See `man foot.ini` and the example
|
|||
under the pointer up to, and until, the next space characters.
|
||||
|
||||
<kbd>left</kbd> - **triple-click**
|
||||
: Selects the entire row
|
||||
: Selects the everything between enclosing quotes, or the entire row
|
||||
if not inside a quote.
|
||||
|
||||
<kbd>left</kbd> - **quad-click**
|
||||
: Selects the entire row.
|
||||
|
||||
<kbd>middle</kbd>
|
||||
: Paste from _primary_ selection
|
||||
|
|
@ -242,9 +251,27 @@ These are the default shortcuts. See `man foot.ini` and the example
|
|||
selection, while hold-and-drag allows you to interactively resize
|
||||
the selection.
|
||||
|
||||
<kbd>ctrl</kbd>+<kbd>right</kbd>
|
||||
: Extend the current selection, but force it to be character wise,
|
||||
rather than depending on the original selection mode.
|
||||
|
||||
<kbd>wheel</kbd>
|
||||
: Scroll up/down in history
|
||||
|
||||
<kbd>ctrl</kbd>+<kbd>wheel</kbd>
|
||||
: Increase/decrease font size
|
||||
|
||||
|
||||
### Touchscreen
|
||||
|
||||
<kbd>tap</kbd>
|
||||
: Emulates mouse left button click.
|
||||
|
||||
<kbd>drag</kbd>
|
||||
: Scrolls up/down in history.
|
||||
: Holding for a while before dragging (time delay can be configured)
|
||||
emulates mouse dragging with left button held.
|
||||
|
||||
|
||||
## Server (daemon) mode
|
||||
|
||||
|
|
@ -277,7 +304,7 @@ when starting your Wayland compositor (i.e. logging in to your
|
|||
desktop), and then run `footclient` instead of `foot` whenever you
|
||||
want to launch a new terminal.
|
||||
|
||||
Foot support socket activation, which means `foot --server` will only be
|
||||
Foot supports socket activation, which means `foot --server` will only be
|
||||
started the first time you'll run `footclient`. (systemd user units are
|
||||
included, but it can work with other supervision suites).
|
||||
|
||||
|
|
@ -287,10 +314,10 @@ Foot supports URL detection. But, unlike many other terminal
|
|||
emulators, where URLs are highlighted when they are hovered and opened
|
||||
by clicking on them, foot uses a keyboard driven approach.
|
||||
|
||||
Pressing <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>u</kbd> enters _“URL
|
||||
mode”_, where all currently visible URLs are underlined, and is
|
||||
associated with a _“jump-label”_. The jump-label indicates the _key
|
||||
sequence_ (e.g. **”AF”**) to use to activate the URL.
|
||||
Pressing <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>o</kbd> enters _"URL
|
||||
mode"_, where all currently visible URLs are underlined, and is
|
||||
associated with a _"jump-label"_. The jump-label indicates the _key
|
||||
sequence_ (e.g. **"AF"**) to use to activate the URL.
|
||||
|
||||
The key binding can, of course, be customized, like all other key
|
||||
bindings in foot. See `show-urls-launch` and `show-urls-copy` in the
|
||||
|
|
@ -313,7 +340,7 @@ the jump label key sequences can be configured.
|
|||
|
||||
New foot terminal instances (bound to
|
||||
<kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>n</kbd> by default) will open in
|
||||
the current working directory, **if** the shell in the “parent”
|
||||
the current working directory, **if** the shell in the "parent"
|
||||
terminal reports directory changes.
|
||||
|
||||
This is done with the OSC-7 escape sequence. Most shells can be
|
||||
|
|
@ -344,6 +371,42 @@ See the
|
|||
[wiki](https://codeberg.org/dnkl/foot/wiki#user-content-jumping-between-prompts)
|
||||
for details, and examples for other shells.
|
||||
|
||||
### Piping last command's output
|
||||
|
||||
The key binding `pipe-command-output` can pipe the last command's
|
||||
output to an application of your choice (similar to the other `pipe-*`
|
||||
key bindings):
|
||||
|
||||
```ini
|
||||
[key-bindings]
|
||||
pipe-command-output=[sh -c "f=$(mktemp); cat - > $f; footclient emacsclient -nw $f; rm $f"] Control+Shift+g
|
||||
```
|
||||
|
||||
When pressing <kbd>ctrl</kbd>+<kbd>shift</kbd>+<kbd>g</kbd>, the last
|
||||
command's output is written to a temporary file, then an emacsclient
|
||||
is started in a new footclient instance. The temporary file is removed
|
||||
after the footclient instance has closed.
|
||||
|
||||
For this to work, the shell must emit an OSC-133;C (`\E]133;C\E\\`)
|
||||
sequence before command output starts, and an OSC-133;D
|
||||
(`\E]133;D\E\\`) when the command output ends.
|
||||
|
||||
In fish, one way to do this is to add `preexec` and `postexec` hooks:
|
||||
|
||||
```fish
|
||||
function foot_cmd_start --on-event fish_preexec
|
||||
echo -en "\e]133;C\e\\"
|
||||
end
|
||||
|
||||
function foot_cmd_end --on-event fish_postexec
|
||||
echo -en "\e]133;D\e\\"
|
||||
end
|
||||
```
|
||||
|
||||
See the
|
||||
[wiki](https://codeberg.org/dnkl/foot/wiki#user-content-piping-last-command-s-output)
|
||||
for details, and examples for other shells
|
||||
|
||||
|
||||
## Alt/meta
|
||||
|
||||
|
|
@ -365,13 +428,13 @@ mode_, `\E[?1034l`), and enabled again with `smm` (_set meta mode_,
|
|||
## Backspace
|
||||
|
||||
Foot transmits DEL (`^?`) on <kbd>backspace</kbd>. This corresponds to
|
||||
XTerm's `backarrowKey` option set to `false`, and to DECBKM being
|
||||
_reset_.
|
||||
XTerm's `backarrowKey` option set to `false`, and to
|
||||
[`DECBKM`](https://vt100.net/docs/vt510-rm/DECBKM.html) being _reset_.
|
||||
|
||||
To instead transmit BS (`^H`), press
|
||||
<kbd>ctrl</kbd>+<kbd>backspace</kbd>.
|
||||
|
||||
Note that foot does **not** implement DECBKM, and that the behavior
|
||||
Note that foot does **not** implement `DECBKM`, and that the behavior
|
||||
described above **cannot** be changed.
|
||||
|
||||
Finally, pressing <kbd>alt</kbd> will prefix the transmitted byte with
|
||||
|
|
@ -411,27 +474,53 @@ This is not how it is meant to be. Fonts are measured in _point sizes_
|
|||
**for a reason**; a given point size should have the same height on
|
||||
all mediums, be it printers or monitors, regardless of their DPI.
|
||||
|
||||
Foot’s default behavior is to use the monitor’s DPI to size fonts when
|
||||
output scaling has been disabled on **all** monitors. If at least one
|
||||
monitor has output scaling enabled, fonts will instead by sized using
|
||||
the scaling factor.
|
||||
That said, on Wayland, Hi-DPI monitors are typically handled by
|
||||
configuring a _"scaling factor"_ in the compositor. This is usually
|
||||
expressed as either a rational value (e.g. _1.5_), or as a percentage
|
||||
(e.g. _150%_), by which all fonts and window sizes are supposed to be
|
||||
multiplied.
|
||||
|
||||
This can be changed to either **always** use the monitor’s DPI
|
||||
(regardless of scaling factor), or to **never** use it, with the
|
||||
`dpi-aware` option in `foot.ini`. See the man page, **foot.ini**(5)
|
||||
for more information.
|
||||
For this reason, and because of the new _fractional scaling_ protocol
|
||||
(see below for details), and because this is how Wayland applications
|
||||
are expected to behave, foot >= 1.15 will default to scaling fonts
|
||||
using the compositor's scaling factor, and **not** the monitor
|
||||
DPI.
|
||||
|
||||
When fonts are sized using the monitor’s DPI, glyphs should always
|
||||
have the same physical height, regardless of monitor.
|
||||
This means the (assuming the monitors are at the same viewing
|
||||
distance) the font size will appear to change when you move the foot
|
||||
window across different monitors, **unless** you have configured the
|
||||
monitors' scaling factors correctly in the compositor.
|
||||
|
||||
Furthermore, foot will re-size the fonts on-the-fly when the window is
|
||||
moved between screens with different DPIs values. If the window covers
|
||||
multiple screens, with different DPIs, the highest DPI will be used.
|
||||
This can be changed by setting the `dpi-aware` option to `yes` in
|
||||
`foot.ini`. When enabled, fonts will **not** be sized using the
|
||||
scaling factor, but will instead be sized using the monitor's
|
||||
DPI. When the foot window is moved across monitors, the font size is
|
||||
updated for the current monitor's DPI.
|
||||
|
||||
This means that, assuming the monitors are **at the same viewing
|
||||
distance**, the font size will appear to be the same, at all times.
|
||||
|
||||
_Note_: if you configure **pixelsize**, rather than **size**, then DPI
|
||||
changes will **not** change the font size. Pixels are always pixels.
|
||||
|
||||
|
||||
### Fractional scaling on Wayland
|
||||
|
||||
For a long time, there was no **true** support for _fractional
|
||||
scaling_. That is, values like 1.5 (150%), 1.8 (180%) etc, only
|
||||
integer values, like 2 (200%).
|
||||
|
||||
Compositors that _did_ support fractional scaling did so using a hack;
|
||||
all applications were told to scale to 200%, and then the compositor
|
||||
would down-scale the rendered image to e.g. 150%. This works OK for
|
||||
everything **except fonts**, which ended up blurry.
|
||||
|
||||
With _wayland-protocols 1.32_, a new protocol was introduced, that
|
||||
allows compositors to tell applications the _actual_ scaling
|
||||
factor. Applications can then scale the image using a _viewport_
|
||||
object, instead of setting a scale factor on the raw pixel buffer.
|
||||
|
||||
|
||||
## Supported OSCs
|
||||
|
||||
OSC, _Operating System Command_, are escape sequences that interacts
|
||||
|
|
@ -458,11 +547,12 @@ with the terminal emulator itself. Foot implements the following OSCs:
|
|||
* `OSC 117` - reset highlight background color
|
||||
* `OSC 119` - reset highlight foreground color
|
||||
* `OSC 133` - [shell integration](#shell-integration)
|
||||
* `OSC 176` - set app ID
|
||||
* `OSC 555` - flash screen (**foot specific**)
|
||||
* `OSC 777` - desktop notification (only the `;notify` sub-command of
|
||||
OSC 777 is supported.)
|
||||
|
||||
See the **foot-ctlseq**(7) man page for a complete list of supported
|
||||
See the **foot-ctlseqs**(7) man page for a complete list of supported
|
||||
control sequences.
|
||||
|
||||
|
||||
|
|
@ -496,7 +586,7 @@ emulator actually responded to.
|
|||
|
||||
Starting with version 1.7.0, foot also implements `XTVERSION`, to
|
||||
which it will reply with `\EP>|foot(version)\E\\`. Version is
|
||||
e.g. “1.8.2” for a regular release, or “1.8.2-36-g7db8e06f” for a git
|
||||
e.g. "1.8.2" for a regular release, or "1.8.2-36-g7db8e06f" for a git
|
||||
build.
|
||||
|
||||
|
||||
|
|
@ -509,9 +599,9 @@ It allows querying the terminal for terminfo
|
|||
capabilities. Applications using this feature do not need to use the
|
||||
classic, file-based, terminfo definition. For example, if all
|
||||
applications used this feature, you would no longer have to install
|
||||
foot’s terminfo on remote hosts you SSH into.
|
||||
foot's terminfo on remote hosts you SSH into.
|
||||
|
||||
XTerm’s implementation (as of XTerm-370) only supports querying key
|
||||
XTerm's implementation (as of XTerm-370) only supports querying key
|
||||
(as in keyboard keys) capabilities, and three custom capabilities:
|
||||
|
||||
* `TN` - terminal name
|
||||
|
|
@ -523,7 +613,7 @@ Kitty has extended this, and also supports querying all integer and
|
|||
string capabilities.
|
||||
|
||||
Foot supports this, and extends it even further, to also include
|
||||
boolean capabilities. This means foot’s entire terminfo can be queried
|
||||
boolean capabilities. This means foot's entire terminfo can be queried
|
||||
via `XTGETTCAP`.
|
||||
|
||||
Note that both Kitty and foot handles **responses** to
|
||||
|
|
@ -535,7 +625,7 @@ capability/value pairs. There are a couple of issues with this:
|
|||
* The success/fail flag in the beginning of the response is always `1`
|
||||
(success), unless the very **first** queried capability is invalid.
|
||||
* XTerm will not respond **at all** to an invalid capability, unless
|
||||
it’s the first one in the `XTGETTCAP` query.
|
||||
it's the first one in the `XTGETTCAP` query.
|
||||
* XTerm will end the response at the first invalid capability.
|
||||
|
||||
In other words, if you send a large multi-capability query, you will
|
||||
|
|
@ -547,6 +637,14 @@ capability in the multi query. This allows us to send a proper
|
|||
success/fail flag for each queried capability. Responses for **all**
|
||||
queried capabilities are **always** sent. No queries are ever dropped.
|
||||
|
||||
All replies are in `tigetstr()` format. That is, given the same
|
||||
capability name, foot's reply is identical to what `tigetstr()` would
|
||||
have returned.
|
||||
|
||||
In addition to queries for terminfo entries, the `query-os-name` query
|
||||
will be answered with a response of the form `uname=$(uname -s)`,
|
||||
where `$(uname -s)` is the name of the OS foot was compiled for.
|
||||
|
||||
|
||||
# Credits
|
||||
|
||||
|
|
@ -554,6 +652,11 @@ queried capabilities are **always** sent. No queries are ever dropped.
|
|||
contributing foot's [logo](icons/hicolor/48x48/apps/foot.png).
|
||||
|
||||
|
||||
# Code of Conduct
|
||||
|
||||
See [Code of Conduct](CODE_OF_CONDUCT.md)
|
||||
|
||||
|
||||
# Bugs
|
||||
|
||||
Please report bugs to https://codeberg.org/dnkl/foot/issues
|
||||
|
|
@ -577,20 +680,24 @@ The report should contain the following:
|
|||
## IRC
|
||||
|
||||
Ask questions, hang out, sing praise or just say hi in the `#foot`
|
||||
channel on [irc.libera.chat](https://libera.chat). Logs are available
|
||||
at https://libera.irclog.whitequark.org/foot.
|
||||
channel on
|
||||
[irc.libera.chat](https://web.libera.chat/?channels=#foot). Logs are
|
||||
available at https://libera.irclog.whitequark.org/foot.
|
||||
|
||||
|
||||
## Mastodon
|
||||
|
||||
Every now and then I post foot related updates on
|
||||
[@dnkl@linuxrocks.online](https://linuxrocks.online/@dnkl)
|
||||
[@dnkl@social.treehouse.systems](https://social.treehouse.systems/@dnkl)
|
||||
|
||||
|
||||
# Sponsoring/donations
|
||||
|
||||
* Liberapay: https://liberapay.com/dnkl
|
||||
* GitHub Sponsors: https://github.com/sponsors/dnkl
|
||||
|
||||
[](https://liberapay.com/dnkl/donate)
|
||||
|
||||
|
||||
# License
|
||||
|
||||
|
|
|
|||
21
base64.c
21
base64.c
|
|
@ -36,16 +36,13 @@ static const uint8_t reverse_lookup[256] = {
|
|||
};
|
||||
|
||||
static const char lookup[64] = {
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
|
||||
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
|
||||
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
|
||||
'+', '/',
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/"
|
||||
};
|
||||
|
||||
char *
|
||||
base64_decode(const char *s)
|
||||
base64_decode(const char *s, size_t *size)
|
||||
{
|
||||
const size_t len = strlen(s);
|
||||
if (unlikely(len % 4 != 0)) {
|
||||
|
|
@ -57,6 +54,9 @@ base64_decode(const char *s)
|
|||
if (unlikely(ret == NULL))
|
||||
return NULL;
|
||||
|
||||
if (unlikely(size != NULL))
|
||||
*size = len / 4 * 3;
|
||||
|
||||
for (size_t i = 0, o = 0; i < len; i += 4, o += 3) {
|
||||
unsigned a = reverse_lookup[(unsigned char)s[i + 0]];
|
||||
unsigned b = reverse_lookup[(unsigned char)s[i + 1]];
|
||||
|
|
@ -71,6 +71,13 @@ base64_decode(const char *s)
|
|||
if (unlikely(i + 4 != len || (a | b) & P || (c & P && !(d & P))))
|
||||
goto invalid;
|
||||
|
||||
if (unlikely(size != NULL)) {
|
||||
if (c & P)
|
||||
*size = len / 4 * 3 - 2;
|
||||
else
|
||||
*size = len / 4 * 3 - 1;
|
||||
}
|
||||
|
||||
c &= 63;
|
||||
d &= 63;
|
||||
}
|
||||
|
|
|
|||
2
base64.h
2
base64.h
|
|
@ -3,6 +3,6 @@
|
|||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
char *base64_decode(const char *s);
|
||||
char *base64_decode(const char *s, size_t *out_len);
|
||||
char *base64_encode(const uint8_t *data, size_t size);
|
||||
void base64_encode_final(const uint8_t *data, size_t size, char result[4]);
|
||||
|
|
|
|||
459
box-drawing.c
459
box-drawing.c
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <fenv.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define LOG_MODULE "box-drawing"
|
||||
|
|
@ -33,9 +32,12 @@ struct buf {
|
|||
|
||||
int thickness[2];
|
||||
|
||||
/* For sextants and wedges */
|
||||
/* For octants, sextants and wedges */
|
||||
int x_halfs[2];
|
||||
int y_thirds[2];
|
||||
|
||||
/* For octants */
|
||||
int y_quads[3];
|
||||
};
|
||||
|
||||
static const pixman_color_t white = {0xffff, 0xffff, 0xffff, 0xffff};
|
||||
|
|
@ -1459,14 +1461,12 @@ draw_box_drawings_light_arc(struct buf *buf, char32_t wc)
|
|||
*/
|
||||
for (double i = y_min*16; i <= y_max*16; i++) {
|
||||
errno = 0;
|
||||
feclearexcept(FE_ALL_EXCEPT);
|
||||
|
||||
double y = i / 16.;
|
||||
double x = circle_hemisphere * sqrt(c_r2 - (y - c_y) * (y - c_y)) + c_x;
|
||||
|
||||
/* See math_error(7) */
|
||||
if (errno != 0 ||
|
||||
fetestexcept(FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW))
|
||||
if (errno != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
|
@ -2098,7 +2098,7 @@ draw_braille(struct buf *buf, char32_t wc)
|
|||
if (x_px_left >= 1) { x_spacing++; x_px_left--; }
|
||||
if (y_px_left >= 3) { y_spacing++; y_px_left -= 3; }
|
||||
|
||||
/* Fourth, margins (“spacing”, but on the sides) */
|
||||
/* Fourth, margins ("spacing", but on the sides) */
|
||||
if (x_px_left >= 2) { x_margin++; x_px_left -= 2; }
|
||||
if (y_px_left >= 2) { y_margin++; y_px_left -= 2; }
|
||||
|
||||
|
|
@ -2213,6 +2213,7 @@ draw_sextant(struct buf *buf, char32_t wc)
|
|||
LOWER_RIGHT = 1 << 5,
|
||||
};
|
||||
|
||||
/* TODO: move this to a separate file? */
|
||||
static const uint8_t matrix[60] = {
|
||||
/* U+1fb00 - U+1fb0f */
|
||||
UPPER_LEFT,
|
||||
|
|
@ -2308,6 +2309,398 @@ draw_sextant(struct buf *buf, char32_t wc)
|
|||
sextant_lower_right(buf);
|
||||
}
|
||||
|
||||
static void
|
||||
octant_upper_left(struct buf *buf)
|
||||
{
|
||||
rect(0, 0, buf->x_halfs[0], buf->y_quads[0]);
|
||||
}
|
||||
|
||||
static void
|
||||
octant_middle_up_left(struct buf *buf)
|
||||
{
|
||||
rect(0, buf->y_quads[0], buf->x_halfs[0], buf->y_quads[1]);
|
||||
}
|
||||
|
||||
static void
|
||||
octant_middle_down_left(struct buf *buf)
|
||||
{
|
||||
rect(0, buf->y_quads[1], buf->x_halfs[0], buf->y_quads[2]);
|
||||
}
|
||||
|
||||
static void
|
||||
octant_lower_left(struct buf *buf)
|
||||
{
|
||||
rect(0, buf->y_quads[2], buf->x_halfs[0], buf->height);
|
||||
}
|
||||
|
||||
static void
|
||||
octant_upper_right(struct buf *buf)
|
||||
{
|
||||
rect(buf->x_halfs[1], 0, buf->width, buf->y_quads[0]);
|
||||
}
|
||||
|
||||
static void
|
||||
octant_middle_up_right(struct buf *buf)
|
||||
{
|
||||
rect(buf->x_halfs[1], buf->y_quads[0], buf->width, buf->y_quads[1]);
|
||||
}
|
||||
|
||||
static void
|
||||
octant_middle_down_right(struct buf *buf)
|
||||
{
|
||||
rect(buf->x_halfs[1], buf->y_quads[1], buf->width, buf->y_quads[2]);
|
||||
}
|
||||
|
||||
static void
|
||||
octant_lower_right(struct buf *buf)
|
||||
{
|
||||
rect(buf->x_halfs[1], buf->y_quads[2], buf->width, buf->height);
|
||||
}
|
||||
|
||||
static void NOINLINE
|
||||
draw_octant(struct buf *buf, char32_t wc)
|
||||
{
|
||||
/*
|
||||
* Each byte encodes one octant:
|
||||
*
|
||||
* Bit octant part
|
||||
* 0 upper left
|
||||
* 1 middle, upper left
|
||||
* 2 middle, lower left
|
||||
* 3 lower, left
|
||||
* 4 upper right
|
||||
* 5 middle, upper right
|
||||
* 6 middle, lower right
|
||||
* 7 lower right
|
||||
*/
|
||||
enum {
|
||||
UPPER_LEFT = 1 << 0,
|
||||
MIDDLE_UP_LEFT = 1 << 1,
|
||||
MIDDLE_DOWN_LEFT = 1 << 2,
|
||||
LOWER_LEFT = 1 << 3,
|
||||
UPPER_RIGHT = 1 << 4,
|
||||
MIDDLE_UP_RIGHT = 1 << 5,
|
||||
MIDDLE_DOWN_RIGHT = 1 << 6,
|
||||
LOWER_RIGHT = 1 << 7,
|
||||
};
|
||||
|
||||
/* TODO: move this to a separate file */
|
||||
static const uint8_t matrix[230] = {
|
||||
/* U+1CD00 - U+1CD0F */
|
||||
MIDDLE_UP_LEFT,
|
||||
MIDDLE_UP_LEFT | UPPER_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | UPPER_RIGHT,
|
||||
MIDDLE_UP_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT,
|
||||
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT,
|
||||
MIDDLE_DOWN_LEFT,
|
||||
UPPER_LEFT | MIDDLE_DOWN_LEFT,
|
||||
UPPER_RIGHT | MIDDLE_DOWN_LEFT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_DOWN_LEFT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT,
|
||||
|
||||
/* U+1CD10 - U+1CD1F */
|
||||
MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT,
|
||||
UPPER_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT,
|
||||
UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT,
|
||||
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT,
|
||||
MIDDLE_DOWN_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_DOWN_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_DOWN_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_DOWN_RIGHT,
|
||||
MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT,
|
||||
|
||||
/* U+1CD20 - U+1CD2F */
|
||||
UPPER_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT,
|
||||
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT,
|
||||
MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
|
||||
MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
|
||||
MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
|
||||
|
||||
/* U+1CD30 - U+1CD3F */
|
||||
UPPER_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
|
||||
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT,
|
||||
UPPER_LEFT | LOWER_LEFT,
|
||||
UPPER_RIGHT | LOWER_LEFT,
|
||||
UPPER_LEFT | UPPER_RIGHT | LOWER_LEFT,
|
||||
MIDDLE_UP_LEFT | LOWER_LEFT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | LOWER_LEFT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | LOWER_LEFT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | LOWER_LEFT,
|
||||
MIDDLE_UP_RIGHT | LOWER_LEFT,
|
||||
UPPER_LEFT | MIDDLE_UP_RIGHT | LOWER_LEFT,
|
||||
UPPER_RIGHT | MIDDLE_UP_RIGHT | LOWER_LEFT,
|
||||
|
||||
/* U+1CD40 - U+1CD4F */
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | LOWER_LEFT,
|
||||
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | LOWER_LEFT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | LOWER_LEFT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | LOWER_LEFT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | LOWER_LEFT,
|
||||
UPPER_LEFT | MIDDLE_DOWN_LEFT | LOWER_LEFT,
|
||||
UPPER_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT,
|
||||
MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | LOWER_LEFT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | LOWER_LEFT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | LOWER_LEFT,
|
||||
MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT,
|
||||
UPPER_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT,
|
||||
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT,
|
||||
|
||||
/* U+1CD50 - U+1CD5F */
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT,
|
||||
MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
UPPER_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
UPPER_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
UPPER_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
|
||||
/* U+1CD60 - U+1CD6F */
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
UPPER_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
UPPER_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
UPPER_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
|
||||
/* U+1CD70 - U+1CD7F */
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT,
|
||||
UPPER_LEFT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | LOWER_RIGHT,
|
||||
MIDDLE_UP_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | LOWER_RIGHT,
|
||||
MIDDLE_UP_RIGHT | LOWER_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_RIGHT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_RIGHT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | LOWER_RIGHT,
|
||||
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | LOWER_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | LOWER_RIGHT,
|
||||
|
||||
/* U+1CD80 - U+1CD8F */
|
||||
MIDDLE_DOWN_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
|
||||
MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
|
||||
MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
|
||||
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_RIGHT,
|
||||
|
||||
/* U+1CD90 - U+1CD9F */
|
||||
UPPER_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
|
||||
/* U+1CDA0 - U+1CDAF */
|
||||
MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_RIGHT,
|
||||
UPPER_LEFT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
MIDDLE_UP_LEFT | LOWER_LEFT | LOWER_RIGHT,
|
||||
|
||||
/* U+1CDB0 - U+1CDBF */
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | LOWER_LEFT | LOWER_RIGHT,
|
||||
MIDDLE_UP_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
|
||||
MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
|
||||
|
||||
/* U+1CDC0 - U+1CDCF */
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
|
||||
MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
|
||||
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | LOWER_LEFT | LOWER_RIGHT,
|
||||
MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
|
||||
/* U+1CDD0 - U+1CDDF */
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
|
||||
/* U+1CDE0 - U+1CDE5 */
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | UPPER_RIGHT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_LEFT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
UPPER_RIGHT | MIDDLE_UP_LEFT | MIDDLE_UP_RIGHT | MIDDLE_DOWN_LEFT | MIDDLE_DOWN_RIGHT | LOWER_LEFT | LOWER_RIGHT,
|
||||
};
|
||||
|
||||
_Static_assert(ALEN(matrix) == 230, "incorrect number of codepoints");
|
||||
|
||||
#if defined(_DEBUG)
|
||||
const size_t last_implemented = 0x1cde5;
|
||||
for (size_t i = 0; i < sizeof(matrix) / sizeof(matrix[0]); i++) {
|
||||
if (i + 0x1cd00 > last_implemented)
|
||||
break;
|
||||
|
||||
for (size_t j = 0; j < sizeof(matrix) / sizeof(matrix[0]); j++) {
|
||||
if (j + 0x1cd00 > last_implemented)
|
||||
break;
|
||||
|
||||
if (i == j)
|
||||
continue;
|
||||
|
||||
if (matrix[i] == matrix[j]) {
|
||||
BUG("octant U+%05x (idx=%zu) is the same as U+%05x (idx=%zu)",
|
||||
matrix[i], i, matrix[j], j);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
xassert(wc >= 0x1cd00 && wc <= 0x1cde5);
|
||||
const size_t idx = wc - 0x1cd00;
|
||||
|
||||
xassert(idx < ALEN(matrix));
|
||||
uint8_t encoded = matrix[idx];
|
||||
|
||||
if (encoded & UPPER_LEFT)
|
||||
octant_upper_left(buf);
|
||||
|
||||
if (encoded & MIDDLE_UP_LEFT)
|
||||
octant_middle_up_left(buf);
|
||||
|
||||
if (encoded & MIDDLE_DOWN_LEFT)
|
||||
octant_middle_down_left(buf);
|
||||
|
||||
if (encoded & LOWER_LEFT)
|
||||
octant_lower_left(buf);
|
||||
|
||||
if (encoded & UPPER_RIGHT)
|
||||
octant_upper_right(buf);
|
||||
|
||||
if (encoded & MIDDLE_UP_RIGHT)
|
||||
octant_middle_up_right(buf);
|
||||
|
||||
if (encoded & MIDDLE_DOWN_RIGHT)
|
||||
octant_middle_down_right(buf);
|
||||
|
||||
if (encoded & LOWER_RIGHT)
|
||||
octant_lower_right(buf);
|
||||
}
|
||||
|
||||
static void NOINLINE
|
||||
draw_wedge_triangle(struct buf *buf, char32_t wc)
|
||||
{
|
||||
|
|
@ -2856,6 +3249,7 @@ draw_glyph(struct buf *buf, char32_t wc)
|
|||
|
||||
case 0x2800 ... 0x28ff: draw_braille(buf, wc); break;
|
||||
|
||||
case 0x1cd00 ... 0x1cde5: draw_octant(buf, wc); break;
|
||||
case 0x1fb00 ... 0x1fb3b: draw_sextant(buf, wc); break;
|
||||
|
||||
case 0x1fb3c ... 0x1fb40:
|
||||
|
|
@ -2957,24 +3351,51 @@ box_drawing(const struct terminal *term, char32_t wc)
|
|||
(double)term->conf->tweak.box_drawing_base_thickness * scale * cell_size * dpi / 72.0;
|
||||
base_thickness = max(base_thickness, 1);
|
||||
|
||||
int y0 = 0, y1 = 0;
|
||||
int y_third_0 = 0, y_third_1 = 0;
|
||||
switch (height % 3) {
|
||||
case 0:
|
||||
y0 = height / 3;
|
||||
y1 = 2 * height / 3;
|
||||
y_third_0 = height / 3;
|
||||
y_third_1 = 2 * height / 3;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
y0 = height / 3;
|
||||
y1 = 2 * height / 3 + 1;
|
||||
y_third_0 = height / 3;
|
||||
y_third_1 = 2 * height / 3 + 1;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
y0 = height / 3 + 1;
|
||||
y1 = y0 + height / 3;
|
||||
y_third_0 = height / 3 + 1;
|
||||
y_third_1 = y_third_0 + height / 3;
|
||||
break;
|
||||
}
|
||||
|
||||
/* TODO */
|
||||
int y_quad_0 = 0, y_quad_1 = 0, y_quad_2 = 0;
|
||||
switch (height % 4) {
|
||||
case 0:
|
||||
y_quad_0 = height / 4;
|
||||
y_quad_1 = height / 2;
|
||||
y_quad_2 = 3 * height / 4;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
y_quad_0 = height / 4;
|
||||
y_quad_1 = height / 2;
|
||||
y_quad_2 = 3 * height / 4;
|
||||
break;
|
||||
case 2:
|
||||
y_quad_0 = height / 4;
|
||||
y_quad_1 = height / 2;
|
||||
y_quad_2 = 3 * height / 4;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
y_quad_0 = height / 4;
|
||||
y_quad_1 = height / 2;
|
||||
y_quad_2 = 3 * height / 4;
|
||||
break;
|
||||
}
|
||||
|
||||
struct buf buf = {
|
||||
.data = data,
|
||||
.pix = pix,
|
||||
|
|
@ -2996,8 +3417,14 @@ box_drawing(const struct terminal *term, char32_t wc)
|
|||
},
|
||||
|
||||
.y_thirds = {
|
||||
y0, /* Endpoint first third, start point second third */
|
||||
y1, /* Endpoint second third, start point last third */
|
||||
y_third_0, /* Endpoint first third, start point second third */
|
||||
y_third_1, /* Endpoint second third, start point last third */
|
||||
},
|
||||
|
||||
.y_quads = {
|
||||
y_quad_0,
|
||||
y_quad_1,
|
||||
y_quad_2,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -3011,7 +3438,7 @@ box_drawing(const struct terminal *term, char32_t wc)
|
|||
.cols = 1,
|
||||
.pix = buf.pix,
|
||||
.x = -term->font_x_ofs,
|
||||
.y = term->font_y_ofs + term->fonts[0]->ascent,
|
||||
.y = term->font_baseline,
|
||||
.width = width,
|
||||
.height = height,
|
||||
.advance = {
|
||||
|
|
|
|||
30
char32.c
30
char32.c
|
|
@ -34,7 +34,7 @@ _Static_assert(
|
|||
#if !defined(__STDC_UTF_32__) || !__STDC_UTF_32__
|
||||
#error "char32_t does not use UTF-32"
|
||||
#endif
|
||||
#if (!defined(__STDC_ISO_10646__) || !__STDC_ISO_10646__) && !defined(__FreeBSD__)
|
||||
#if (!defined(__STDC_ISO_10646__) || !__STDC_ISO_10646__) && !defined(__FreeBSD__) && !defined(__OpenBSD__)
|
||||
#error "wchar_t does not use UTF-32"
|
||||
#endif
|
||||
|
||||
|
|
@ -53,6 +53,14 @@ UNITTEST
|
|||
xassert(c32cmp(U"b", U"a") > 0);
|
||||
}
|
||||
|
||||
UNITTEST
|
||||
{
|
||||
xassert(c32ncmp(U"foo", U"foot", 3) == 0);
|
||||
xassert(c32ncmp(U"foot", U"FOOT", 4) > 0);
|
||||
xassert(c32ncmp(U"a", U"b", 1) < 0);
|
||||
xassert(c32ncmp(U"bb", U"aa", 2) > 0);
|
||||
}
|
||||
|
||||
UNITTEST
|
||||
{
|
||||
char32_t copy[16];
|
||||
|
|
@ -129,11 +137,25 @@ UNITTEST
|
|||
|
||||
UNITTEST
|
||||
{
|
||||
char32_t *c = c32dup(U"foobar");
|
||||
xassert(!isc32upper(U'a'));
|
||||
xassert(isc32upper(U'A'));
|
||||
xassert(!isc32upper(U'a'));
|
||||
}
|
||||
|
||||
UNITTEST
|
||||
{
|
||||
xassert(hasc32upper(U"abc1A"));
|
||||
xassert(!hasc32upper(U"abc1_aaa"));
|
||||
xassert(!hasc32upper(U""));
|
||||
}
|
||||
|
||||
UNITTEST
|
||||
{
|
||||
char32_t *c = xc32dup(U"foobar");
|
||||
xassert(c32cmp(c, U"foobar") == 0);
|
||||
free(c);
|
||||
|
||||
c = c32dup(U"");
|
||||
c = xc32dup(U"");
|
||||
xassert(c32cmp(c, U"") == 0);
|
||||
free(c);
|
||||
}
|
||||
|
|
@ -176,7 +198,7 @@ done:
|
|||
return chars;
|
||||
|
||||
err:
|
||||
return (char32_t)-1;
|
||||
return (size_t)-1;
|
||||
}
|
||||
|
||||
UNITTEST
|
||||
|
|
|
|||
30
char32.h
30
char32.h
|
|
@ -8,6 +8,10 @@
|
|||
#include <wchar.h>
|
||||
#include <wctype.h>
|
||||
|
||||
#if defined(FOOT_GRAPHEME_CLUSTERING)
|
||||
#include <utf8proc.h>
|
||||
#endif
|
||||
|
||||
static inline size_t c32len(const char32_t *s) {
|
||||
return wcslen((const wchar_t *)s);
|
||||
}
|
||||
|
|
@ -16,6 +20,10 @@ static inline int c32cmp(const char32_t *s1, const char32_t *s2) {
|
|||
return wcscmp((const wchar_t *)s1, (const wchar_t *)s2);
|
||||
}
|
||||
|
||||
static inline int c32ncmp(const char32_t *s1, const char32_t *s2, size_t n) {
|
||||
return wcsncmp((const wchar_t *)s1, (const wchar_t *)s2, n);
|
||||
}
|
||||
|
||||
static inline char32_t *c32ncpy(char32_t *dst, const char32_t *src, size_t n) {
|
||||
return (char32_t *)wcsncpy((wchar_t *)dst, (const wchar_t *)src, n);
|
||||
}
|
||||
|
|
@ -56,6 +64,10 @@ static inline char32_t toc32upper(char32_t c) {
|
|||
return (char32_t)towupper((wint_t)c);
|
||||
}
|
||||
|
||||
static inline bool isc32upper(char32_t c32) {
|
||||
return iswupper((wint_t)c32);
|
||||
}
|
||||
|
||||
static inline bool isc32space(char32_t c32) {
|
||||
return iswspace((wint_t)c32);
|
||||
}
|
||||
|
|
@ -68,12 +80,30 @@ static inline bool isc32graph(char32_t c32) {
|
|||
return iswgraph((wint_t)c32);
|
||||
}
|
||||
|
||||
static inline bool hasc32upper(const char32_t *s) {
|
||||
for (int i = 0; s[i] != '\0'; i++) {
|
||||
if (isc32upper(s[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline int c32width(char32_t c) {
|
||||
#if defined(FOOT_GRAPHEME_CLUSTERING)
|
||||
return utf8proc_charwidth((utf8proc_int32_t)c);
|
||||
#else
|
||||
return wcwidth((wchar_t)c);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline int c32swidth(const char32_t *s, size_t n) {
|
||||
#if defined(FOOT_GRAPHEME_CLUSTERING)
|
||||
int width = 0;
|
||||
for (size_t i = 0; i < n; i++)
|
||||
width += utf8proc_charwidth((utf8proc_int32_t)s[i]);
|
||||
return width;
|
||||
#else
|
||||
return wcswidth((const wchar_t *)s, n);
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t mbsntoc32(char32_t *dst, const char *src, size_t nms, size_t len);
|
||||
|
|
|
|||
|
|
@ -29,3 +29,17 @@ struct client_data {
|
|||
} __attribute__((packed));
|
||||
|
||||
_Static_assert(sizeof(struct client_data) == 10, "protocol struct size error");
|
||||
|
||||
enum client_ipc_code {
|
||||
FOOT_IPC_SIGUSR,
|
||||
};
|
||||
|
||||
struct client_ipc_hdr {
|
||||
enum client_ipc_code ipc_code;
|
||||
uint8_t size;
|
||||
} __attribute__((packed));
|
||||
|
||||
|
||||
struct client_ipc_sigusr {
|
||||
int signo;
|
||||
} __attribute__((packed));
|
||||
|
|
|
|||
137
client.c
137
client.c
|
|
@ -1,12 +1,13 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <unistd.h>
|
||||
#include <getopt.h>
|
||||
#include <signal.h>
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
#include <limits.h>
|
||||
#include <signal.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
|
@ -22,7 +23,6 @@
|
|||
#include "foot-features.h"
|
||||
#include "macros.h"
|
||||
#include "util.h"
|
||||
#include "version.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
extern char **environ;
|
||||
|
|
@ -34,13 +34,20 @@ struct string {
|
|||
typedef tll(struct string) string_list_t;
|
||||
|
||||
static volatile sig_atomic_t aborted = 0;
|
||||
static volatile sig_atomic_t sigusr = 0;
|
||||
|
||||
static void
|
||||
sig_handler(int signo)
|
||||
sigint_handler(int signo)
|
||||
{
|
||||
aborted = 1;
|
||||
}
|
||||
|
||||
static void
|
||||
sigusr_handler(int signo)
|
||||
{
|
||||
sigusr = signo;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
sendall(int sock, const void *_buf, size_t len)
|
||||
{
|
||||
|
|
@ -62,19 +69,6 @@ sendall(int sock, const void *_buf, size_t len)
|
|||
return len;
|
||||
}
|
||||
|
||||
static const char *
|
||||
version_and_features(void)
|
||||
{
|
||||
static char buf[256];
|
||||
snprintf(buf, sizeof(buf), "version: %s %cpgo %cime %cgraphemes %cassertions",
|
||||
FOOT_VERSION,
|
||||
feature_pgo() ? '+' : '-',
|
||||
feature_ime() ? '+' : '-',
|
||||
feature_graphemes() ? '+' : '-',
|
||||
feature_assertions() ? '+' : '-');
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void
|
||||
print_usage(const char *prog_name)
|
||||
{
|
||||
|
|
@ -83,6 +77,7 @@ print_usage(const char *prog_name)
|
|||
" -t,--term=TERM value to set the environment variable TERM to (" FOOT_DEFAULT_TERM ")\n"
|
||||
" -T,--title=TITLE initial window title (foot)\n"
|
||||
" -a,--app-id=ID window application ID (foot)\n"
|
||||
" --toplevel-tag=TAG set a custom toplevel tag\n"
|
||||
" -w,--window-size-pixels=WIDTHxHEIGHT initial width and height, in pixels\n"
|
||||
" -W,--window-size-chars=WIDTHxHEIGHT initial width and height, in characters\n"
|
||||
" -m,--maximized start in maximized mode\n"
|
||||
|
|
@ -144,11 +139,15 @@ send_string_list(int fd, const string_list_t *string_list)
|
|||
return true;
|
||||
}
|
||||
|
||||
enum {
|
||||
TOPLEVEL_TAG_OPTION = CHAR_MAX + 1,
|
||||
};
|
||||
|
||||
int
|
||||
main(int argc, char *const *argv)
|
||||
{
|
||||
/* Custom exit code, to enable users to differentiate between foot
|
||||
* itself failing, and the client application failiing */
|
||||
* itself failing, and the client application failing */
|
||||
static const int foot_exit_failure = -36;
|
||||
int ret = foot_exit_failure;
|
||||
|
||||
|
|
@ -158,6 +157,7 @@ main(int argc, char *const *argv)
|
|||
{"term", required_argument, NULL, 't'},
|
||||
{"title", required_argument, NULL, 'T'},
|
||||
{"app-id", required_argument, NULL, 'a'},
|
||||
{"toplevel-tag", required_argument, NULL, TOPLEVEL_TAG_OPTION},
|
||||
{"window-size-pixels", required_argument, NULL, 'w'},
|
||||
{"window-size-chars", required_argument, NULL, 'W'},
|
||||
{"maximized", no_argument, NULL, 'm'},
|
||||
|
|
@ -227,6 +227,12 @@ main(int argc, char *const *argv)
|
|||
goto err;
|
||||
break;
|
||||
|
||||
case TOPLEVEL_TAG_OPTION:
|
||||
snprintf(buf, sizeof(buf), "toplevel-tag=%s", optarg);
|
||||
if (!push_string(&overrides, buf, &total_len))
|
||||
goto err;
|
||||
break;
|
||||
|
||||
case 'L':
|
||||
if (!push_string(&overrides, "login-shell=yes", &total_len))
|
||||
goto err;
|
||||
|
|
@ -314,11 +320,11 @@ main(int argc, char *const *argv)
|
|||
}
|
||||
|
||||
case 'l':
|
||||
if (optarg == NULL || strcmp(optarg, "auto") == 0)
|
||||
if (optarg == NULL || streq(optarg, "auto"))
|
||||
log_colorize = LOG_COLORIZE_AUTO;
|
||||
else if (strcmp(optarg, "never") == 0)
|
||||
else if (streq(optarg, "never"))
|
||||
log_colorize = LOG_COLORIZE_NEVER;
|
||||
else if (strcmp(optarg, "always") == 0)
|
||||
else if (streq(optarg, "always"))
|
||||
log_colorize = LOG_COLORIZE_ALWAYS;
|
||||
else {
|
||||
fprintf(stderr, "%s: argument must be one of 'never', 'always' or 'auto'\n", optarg);
|
||||
|
|
@ -327,7 +333,7 @@ main(int argc, char *const *argv)
|
|||
break;
|
||||
|
||||
case 'v':
|
||||
printf("footclient %s\n", version_and_features());
|
||||
print_version_and_features("footclient ");
|
||||
ret = EXIT_SUCCESS;
|
||||
goto err;
|
||||
|
||||
|
|
@ -371,16 +377,19 @@ main(int argc, char *const *argv)
|
|||
const char *xdg_runtime = getenv("XDG_RUNTIME_DIR");
|
||||
if (xdg_runtime != NULL) {
|
||||
const char *wayland_display = getenv("WAYLAND_DISPLAY");
|
||||
if (wayland_display != NULL)
|
||||
if (wayland_display != NULL) {
|
||||
snprintf(addr.sun_path, sizeof(addr.sun_path),
|
||||
"%s/foot-%s.sock", xdg_runtime, wayland_display);
|
||||
else
|
||||
connected = (connect(fd, (const struct sockaddr *)&addr, sizeof(addr)) == 0);
|
||||
}
|
||||
if (!connected) {
|
||||
LOG_WARN("%s: failed to connect, will now try %s/foot.sock",
|
||||
addr.sun_path, xdg_runtime);
|
||||
snprintf(addr.sun_path, sizeof(addr.sun_path),
|
||||
"%s/foot.sock", xdg_runtime);
|
||||
|
||||
if (connect(fd, (const struct sockaddr *)&addr, sizeof(addr)) == 0)
|
||||
connected = true;
|
||||
else
|
||||
connected = (connect(fd, (const struct sockaddr *)&addr, sizeof(addr)) == 0);
|
||||
}
|
||||
if (!connected)
|
||||
LOG_WARN("%s: failed to connect, will now try /tmp/foot.sock", addr.sun_path);
|
||||
}
|
||||
|
||||
|
|
@ -395,10 +404,10 @@ main(int argc, char *const *argv)
|
|||
|
||||
const char *cwd = custom_cwd;
|
||||
if (cwd == NULL) {
|
||||
errno = 0;
|
||||
size_t buf_len = 1024;
|
||||
do {
|
||||
_cwd = xrealloc(_cwd, buf_len);
|
||||
errno = 0;
|
||||
if (getcwd(_cwd, buf_len) == NULL && errno != ERANGE) {
|
||||
LOG_ERRNO("failed to get current working directory");
|
||||
goto err;
|
||||
|
|
@ -415,7 +424,7 @@ main(int argc, char *const *argv)
|
|||
|
||||
if (resolved_path_cwd != NULL &&
|
||||
resolved_path_pwd != NULL &&
|
||||
strcmp(resolved_path_cwd, resolved_path_pwd) == 0)
|
||||
streq(resolved_path_cwd, resolved_path_pwd))
|
||||
{
|
||||
/*
|
||||
* The resolved path of $PWD matches the resolved path of
|
||||
|
|
@ -518,15 +527,63 @@ main(int argc, char *const *argv)
|
|||
if (!send_string_list(fd, &envp))
|
||||
goto err;
|
||||
|
||||
struct sigaction sa = {.sa_handler = &sig_handler};
|
||||
sigemptyset(&sa.sa_mask);
|
||||
if (sigaction(SIGINT, &sa, NULL) < 0 || sigaction(SIGTERM, &sa, NULL) < 0) {
|
||||
struct sigaction sa_int = {.sa_handler = &sigint_handler};
|
||||
struct sigaction sa_usr = {.sa_handler = &sigusr_handler};
|
||||
sigemptyset(&sa_int.sa_mask);
|
||||
sigemptyset(&sa_usr.sa_mask);
|
||||
|
||||
if (sigaction(SIGINT, &sa_int, NULL) < 0 ||
|
||||
sigaction(SIGTERM, &sa_int, NULL) < 0 ||
|
||||
sigaction(SIGUSR1, &sa_usr, NULL) < 0 ||
|
||||
sigaction(SIGUSR2, &sa_usr, NULL) < 0)
|
||||
{
|
||||
LOG_ERRNO("failed to register signal handlers");
|
||||
goto err;
|
||||
}
|
||||
|
||||
int exit_code;
|
||||
ssize_t rcvd = recv(fd, &exit_code, sizeof(exit_code), 0);
|
||||
ssize_t rcvd = -1;
|
||||
|
||||
while (true) {
|
||||
rcvd = recv(fd, &exit_code, sizeof(exit_code), 0);
|
||||
|
||||
const int got_sigusr = sigusr;
|
||||
sigusr = 0;
|
||||
|
||||
if (rcvd < 0 && errno == EINTR) {
|
||||
if (aborted)
|
||||
break;
|
||||
else if (got_sigusr != 0) {
|
||||
LOG_DBG("sending sigusr %d to server", got_sigusr);
|
||||
|
||||
struct {
|
||||
struct client_ipc_hdr hdr;
|
||||
struct client_ipc_sigusr sigusr;
|
||||
} ipc = {
|
||||
.hdr = {
|
||||
.ipc_code = FOOT_IPC_SIGUSR,
|
||||
.size = sizeof(struct client_ipc_sigusr),
|
||||
},
|
||||
.sigusr = {
|
||||
.signo = got_sigusr,
|
||||
},
|
||||
};
|
||||
|
||||
ssize_t count = send(fd, &ipc, sizeof(ipc), 0);
|
||||
if (count < 0) {
|
||||
LOG_ERRNO("failed to send SIGUSR IPC to server");
|
||||
goto err;
|
||||
} else if ((size_t)count != sizeof(ipc)) {
|
||||
LOG_ERR("failed to send SIGUSR IPC to server");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (rcvd == -1 && errno == EINTR)
|
||||
xassert(aborted);
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ cmd_scrollback_up(struct terminal *term, int rows)
|
|||
const int grid_rows = grid->num_rows;
|
||||
|
||||
/* The view row number in scrollback relative coordinates. This is
|
||||
* the maximum number of rows we’re allowed to scroll */
|
||||
* the maximum number of rows we're allowed to scroll */
|
||||
int sb_start = grid_sb_start_ignore_uninitialized(grid, term->rows);
|
||||
int view_sb_rel =
|
||||
grid_row_abs_to_sb_precalc_sb_start(grid, sb_start, view);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ _foot()
|
|||
local cur prev flags word commands match previous_words i offset
|
||||
flags=(
|
||||
"--app-id"
|
||||
"--toplevel-tag"
|
||||
"--check-config"
|
||||
"--config"
|
||||
"--font"
|
||||
|
|
@ -19,6 +20,7 @@ _foot()
|
|||
"--maximized"
|
||||
"--override"
|
||||
"--print-pid"
|
||||
"--pty"
|
||||
"--server"
|
||||
"--term"
|
||||
"--title"
|
||||
|
|
@ -39,7 +41,7 @@ _foot()
|
|||
for word in "${previous_words[@]}" ; do
|
||||
match=$(printf "$commands" | grep -Fx "$word" 2>/dev/null)
|
||||
if [[ ! -z "$match" ]] ; then
|
||||
if [[ ${COMP_WORDS[i-1]} =~ ^(--app-id|--config|--font|--log-level|--term|--title|--window-size-pixels|--window-size-chars|--working-directory)$ ]] ; then
|
||||
if [[ ${COMP_WORDS[i-1]} =~ ^(--app-id|--toplevel-tag|--config|--font|--log-level|--pty|--term|--title|--window-size-pixels|--window-size-chars|--working-directory)$ ]] ; then
|
||||
(( i++ ))
|
||||
continue
|
||||
fi
|
||||
|
|
@ -74,7 +76,7 @@ _foot()
|
|||
COMPREPLY=( $(compgen -W "none error warning info" -- ${cur}) ) ;;
|
||||
--log-colorize|-l)
|
||||
COMPREPLY=( $(compgen -W "never always auto" -- ${cur}) ) ;;
|
||||
--app-id|--help|--override|--title|--version|--window-size-chars|--window-size-pixels|--check-config|-[ahoTvWwC])
|
||||
--app-id|--toplevel-tag|--help|--override|--pty|--title|--version|--window-size-chars|--window-size-pixels|--check-config|-[ahoTvWwC])
|
||||
# Don't autocomplete for these flags
|
||||
: ;;
|
||||
*)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ _footclient()
|
|||
local cur prev flags word commands match previous_words i offset
|
||||
flags=(
|
||||
"--app-id"
|
||||
"--toplevel-tag"
|
||||
"--fullscreen"
|
||||
"--help"
|
||||
"--hold"
|
||||
|
|
@ -35,7 +36,7 @@ _footclient()
|
|||
for word in "${previous_words[@]}" ; do
|
||||
match=$(printf "$commands" | grep -Fx "$word" 2>/dev/null)
|
||||
if [[ ! -z "$match" ]] ; then
|
||||
if [[ ${COMP_WORDS[i-1]} =~ ^(--app-id|--log-level|--server-socket|--term|--title|--window-size-pixels|--window-size-chars|--working-directory)$ ]] ; then
|
||||
if [[ ${COMP_WORDS[i-1]} =~ ^(--app-id|--toplevel-tag|--log-level|--server-socket|--term|--title|--window-size-pixels|--window-size-chars|--working-directory)$ ]] ; then
|
||||
(( i++ ))
|
||||
continue
|
||||
fi
|
||||
|
|
@ -67,7 +68,7 @@ _footclient()
|
|||
COMPREPLY=( $(compgen -W "none error warning info" -- ${cur}) ) ;;
|
||||
--log-colorize|-l)
|
||||
COMPREPLY=( $(compgen -W "never always auto" -- ${cur}) ) ;;
|
||||
--app-id|--help|--override|--title|--version|--window-size-chars|--window-size-pixels|-[ahoTvWw])
|
||||
--app-id|--toplevel-tag|--help|--override|--title|--version|--window-size-chars|--window-size-pixels|-[ahoTvWw])
|
||||
# Don't autocomplete for these flags
|
||||
: ;;
|
||||
*)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ complete -c foot -x -s f -l font -a "(fc-list : family | sed 's/,/
|
|||
complete -c foot -x -s t -l term -a '(find /usr/share/terminfo -type f -printf "%f\n")' -d "value to set the environment variable TERM to (foot)"
|
||||
complete -c foot -x -s T -l title -d "initial window title"
|
||||
complete -c foot -x -s a -l app-id -d "value to set the app-id property on the Wayland window to (foot)"
|
||||
complete -c foot -x -l toplevel-tag -d "value to set the toplevel-tag property on the Wayland window to"
|
||||
complete -c foot -s m -l maximized -d "start in maximized mode"
|
||||
complete -c foot -s F -l fullscreen -d "start in fullscreen mode"
|
||||
complete -c foot -s L -l login-shell -d "start shell as a login shell"
|
||||
|
|
@ -18,5 +19,6 @@ complete -c foot -r -s p -l print-pid
|
|||
complete -c foot -x -s d -l log-level -a "info warning error none" -d "log-level (warning)"
|
||||
complete -c foot -x -s l -l log-colorize -a "always never auto" -d "enable or disable colorization of log output on stderr"
|
||||
complete -c foot -s S -l log-no-syslog -d "disable syslog logging (server mode only)"
|
||||
complete -c foot -r -l pty -d "display an existing pty instead of creating one"
|
||||
complete -c foot -s v -l version -d "show the version number and quit"
|
||||
complete -c foot -s h -l help -d "show help message and quit"
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ complete -c footclient -x -a "(__fish_complete_subcom
|
|||
complete -c footclient -x -s t -l term -a '(find /usr/share/terminfo -type f -printf "%f\n")' -d "value to set the environment variable TERM to (foot)"
|
||||
complete -c footclient -x -s T -l title -d "initial window title"
|
||||
complete -c footclient -x -s a -l app-id -d "value to set the app-id property on the Wayland window to (foot)"
|
||||
complete -c footclient -x -l toplevel-tag -d "value to set the toplevel-tag property on the Wayland window to"
|
||||
complete -c footclient -s m -l maximized -d "start in maximized mode"
|
||||
complete -c footclient -s F -l fullscreen -d "start in fullscreen mode"
|
||||
complete -c footclient -s L -l login-shell -d "start shell as a login shell"
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ _arguments \
|
|||
'(-t --term)'{-t,--term}'[value to set the environment variable TERM to (foot)]:term:->terms' \
|
||||
'(-T --title)'{-T,--title}'[initial window title]:()' \
|
||||
'(-a --app-id)'{-a,--app-id}'[value to set the app-id property on the Wayland window to (foot)]:()' \
|
||||
'--toplevel-tag=[value to set the toplevel-tag property on the Wayland window to]:()' \
|
||||
'(-m --maximized)'{-m,--maximized}'[start in maximized mode]' \
|
||||
'(-F --fullscreen)'{-F,--fullscreen}'[start in fullscreen mode]' \
|
||||
'(-L --login-shell)'{-L,--login-shell}'[start shell as a login shell]' \
|
||||
|
|
@ -18,6 +19,7 @@ _arguments \
|
|||
'(-s --server)'{-s,--server}'[run as server; open terminals by running footclient]:server:_files' \
|
||||
'(-H --hold)'{-H,--hold}'[remain open after child process exits]' \
|
||||
'(-p --print-pid)'{-p,--print-pid}'[print PID to this file or FD when up and running (server mode only)]:pidfile:_files' \
|
||||
'--pty=[display an existing pty instead of creating one]:pty:_files' \
|
||||
'(-d --log-level)'{-d,--log-level}'[log level (warning)]:loglevel:(info warning error none)' \
|
||||
'(-l --log-colorize)'{-l,--log-colorize}'[enable or disable colorization of log output on stderr]:logcolor:(never always auto)' \
|
||||
'(-S --log-no-syslog)'{-s,--log-no-syslog}'[disable syslog logging (server mode only)]' \
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ _arguments \
|
|||
'(-t --term)'{-t,--term}'[value to set the environment variable TERM to (foot)]:term:->terms' \
|
||||
'(-T --title)'{-T,--title}'[initial window title]:()' \
|
||||
'(-a --app-id)'{-a,--app-id}'[value to set the app-id property on the Wayland window to (foot)]:()' \
|
||||
'--toplevel-tag=[value to set the toplevel-tag property on the Wayland window to]:()' \
|
||||
'(-m --maximized)'{-m,--maximized}'[start in maximized mode]' \
|
||||
'(-F --fullscreen)'{-F,--fullscreen}'[start in fullscreen mode]' \
|
||||
'(-L --login-shell)'{-L,--login-shell}'[start shell as a login shell]' \
|
||||
|
|
|
|||
83
composed.c
83
composed.c
|
|
@ -4,8 +4,54 @@
|
|||
#include <stdbool.h>
|
||||
|
||||
#include "debug.h"
|
||||
#include "terminal.h"
|
||||
|
||||
struct composed *
|
||||
uint32_t
|
||||
composed_key_from_chars(const uint32_t chars[], size_t count)
|
||||
{
|
||||
if (count == 0)
|
||||
return 0;
|
||||
|
||||
uint32_t key = chars[0];
|
||||
for (size_t i = 1; i < count; i++)
|
||||
key = composed_key_from_key(key, chars[i]);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
composed_key_from_key(uint32_t prev_key, uint32_t next_char)
|
||||
{
|
||||
unsigned bits = 32 - __builtin_clz(CELL_COMB_CHARS_HI - CELL_COMB_CHARS_LO);
|
||||
|
||||
/* Rotate old key 8 bits */
|
||||
uint32_t new_key = (prev_key << 8) | (prev_key >> (bits - 8));
|
||||
|
||||
/* xor with new char */
|
||||
new_key ^= next_char;
|
||||
|
||||
/* Multiply with magic hash constant */
|
||||
new_key *= 2654435761ul;
|
||||
|
||||
/* And mask, to ensure the new value is within range */
|
||||
new_key &= CELL_COMB_CHARS_HI - CELL_COMB_CHARS_LO;
|
||||
return new_key;
|
||||
}
|
||||
|
||||
UNITTEST
|
||||
{
|
||||
const char32_t chars[] = U"abcdef";
|
||||
|
||||
uint32_t k1 = composed_key_from_key(chars[0], chars[1]);
|
||||
uint32_t k2 = composed_key_from_chars(chars, 2);
|
||||
xassert(k1 == k2);
|
||||
|
||||
uint32_t k3 = composed_key_from_key(k2, chars[2]);
|
||||
uint32_t k4 = composed_key_from_chars(chars, 3);
|
||||
xassert(k3 == k4);
|
||||
}
|
||||
|
||||
const struct composed *
|
||||
composed_lookup(struct composed *root, uint32_t key)
|
||||
{
|
||||
struct composed *node = root;
|
||||
|
|
@ -20,6 +66,41 @@ composed_lookup(struct composed *root, uint32_t key)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
const struct composed *
|
||||
composed_lookup_without_collision(struct composed *root, uint32_t *key,
|
||||
const char32_t *prefix_text, size_t prefix_len,
|
||||
char32_t wc, int forced_width)
|
||||
{
|
||||
while (true) {
|
||||
const struct composed *cc = composed_lookup(root, *key);
|
||||
if (cc == NULL)
|
||||
return NULL;
|
||||
|
||||
bool match = cc->count == prefix_len + 1 &&
|
||||
cc->forced_width == forced_width &&
|
||||
cc->chars[prefix_len] == wc;
|
||||
|
||||
if (match) {
|
||||
for (size_t i = 0; i < prefix_len; i++) {
|
||||
if (cc->chars[i] != prefix_text[i]) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (match)
|
||||
return cc;
|
||||
|
||||
(*key)++;
|
||||
*key &= CELL_COMB_CHARS_HI - CELL_COMB_CHARS_LO;
|
||||
|
||||
/* TODO: this will loop infinitely if the composed table is full */
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
composed_insert(struct composed **root, struct composed *node)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,9 +10,16 @@ struct composed {
|
|||
uint32_t key;
|
||||
uint8_t count;
|
||||
uint8_t width;
|
||||
uint8_t forced_width;
|
||||
};
|
||||
|
||||
struct composed *composed_lookup(struct composed *root, uint32_t key);
|
||||
uint32_t composed_key_from_chars(const uint32_t chars[], size_t count);
|
||||
uint32_t composed_key_from_key(uint32_t prev_key, uint32_t next_char);
|
||||
|
||||
const struct composed *composed_lookup(struct composed *root, uint32_t key);
|
||||
const struct composed *composed_lookup_without_collision(
|
||||
struct composed *root, uint32_t *key,
|
||||
const char32_t *prefix, size_t prefix_len, char32_t wc, int forced_width);
|
||||
void composed_insert(struct composed **root, struct composed *node);
|
||||
|
||||
void composed_free(struct composed *root);
|
||||
|
|
|
|||
241
config.h
241
config.h
|
|
@ -1,7 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <regex.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <uchar.h>
|
||||
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
|
|
@ -27,7 +28,12 @@ struct font_size_adjustment {
|
|||
float percent;
|
||||
};
|
||||
|
||||
enum cursor_style { CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_BEAM };
|
||||
enum cursor_style { CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_BEAM, CURSOR_HOLLOW };
|
||||
enum cursor_unfocused_style {
|
||||
CURSOR_UNFOCUSED_UNCHANGED,
|
||||
CURSOR_UNFOCUSED_HOLLOW,
|
||||
CURSOR_UNFOCUSED_NONE
|
||||
};
|
||||
|
||||
enum conf_size_type {CONF_SIZE_PX, CONF_SIZE_CELLS};
|
||||
|
||||
|
|
@ -38,12 +44,14 @@ struct config_font {
|
|||
};
|
||||
DEFINE_LIST(struct config_font);
|
||||
|
||||
#if 0
|
||||
struct config_key_modifiers {
|
||||
bool shift;
|
||||
bool alt;
|
||||
bool ctrl;
|
||||
bool super;
|
||||
};
|
||||
#endif
|
||||
|
||||
struct argv {
|
||||
char **args;
|
||||
|
|
@ -53,6 +61,7 @@ enum binding_aux_type {
|
|||
BINDING_AUX_NONE,
|
||||
BINDING_AUX_PIPE,
|
||||
BINDING_AUX_TEXT,
|
||||
BINDING_AUX_REGEX,
|
||||
};
|
||||
|
||||
struct binding_aux {
|
||||
|
|
@ -66,6 +75,8 @@ struct binding_aux {
|
|||
uint8_t *data;
|
||||
size_t len;
|
||||
} text;
|
||||
|
||||
char *regex_name;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -74,9 +85,12 @@ enum key_binding_type {
|
|||
MOUSE_BINDING,
|
||||
};
|
||||
|
||||
typedef tll(char *) config_modifier_list_t;
|
||||
|
||||
struct config_key_binding {
|
||||
int action; /* One of the varios bind_action_* enums from wayland.h */
|
||||
struct config_key_modifiers modifiers;
|
||||
int action; /* One of the various bind_action_* enums from wayland.h */
|
||||
//struct config_key_modifiers modifiers;
|
||||
config_modifier_list_t modifiers;
|
||||
union {
|
||||
/* Key bindings */
|
||||
struct {
|
||||
|
|
@ -110,11 +124,107 @@ struct env_var {
|
|||
};
|
||||
typedef tll(struct env_var) env_var_list_t;
|
||||
|
||||
struct custom_regex {
|
||||
char *name;
|
||||
char *regex;
|
||||
regex_t preg;
|
||||
struct config_spawn_template launch;
|
||||
};
|
||||
|
||||
struct color_theme {
|
||||
uint32_t fg;
|
||||
uint32_t bg;
|
||||
uint32_t flash;
|
||||
uint32_t flash_alpha;
|
||||
uint32_t table[256];
|
||||
uint16_t alpha;
|
||||
uint32_t selection_fg;
|
||||
uint32_t selection_bg;
|
||||
uint32_t url;
|
||||
|
||||
uint32_t dim[8];
|
||||
uint32_t sixel[16];
|
||||
|
||||
enum {
|
||||
DIM_BLEND_TOWARDS_BLACK,
|
||||
DIM_BLEND_TOWARDS_WHITE,
|
||||
} dim_blend_towards;
|
||||
|
||||
enum {
|
||||
ALPHA_MODE_DEFAULT,
|
||||
ALPHA_MODE_MATCHING,
|
||||
ALPHA_MODE_ALL
|
||||
} alpha_mode;
|
||||
|
||||
struct {
|
||||
uint32_t text;
|
||||
uint32_t cursor;
|
||||
} cursor;
|
||||
|
||||
struct {
|
||||
uint32_t fg;
|
||||
uint32_t bg;
|
||||
} jump_label;
|
||||
|
||||
struct {
|
||||
uint32_t fg;
|
||||
uint32_t bg;
|
||||
} scrollback_indicator;
|
||||
|
||||
struct {
|
||||
struct {
|
||||
uint32_t fg;
|
||||
uint32_t bg;
|
||||
} no_match;
|
||||
|
||||
struct {
|
||||
uint32_t fg;
|
||||
uint32_t bg;
|
||||
} match;
|
||||
} search_box;
|
||||
|
||||
struct {
|
||||
bool cursor:1;
|
||||
bool jump_label:1;
|
||||
bool scrollback_indicator:1;
|
||||
bool url:1;
|
||||
bool search_box_no_match:1;
|
||||
bool search_box_match:1;
|
||||
uint8_t dim;
|
||||
} use_custom;
|
||||
|
||||
bool blur;
|
||||
};
|
||||
|
||||
enum which_color_theme {
|
||||
COLOR_THEME_DARK,
|
||||
COLOR_THEME_LIGHT,
|
||||
COLOR_THEME_1, /* Deprecated */
|
||||
COLOR_THEME_2, /* Deprecated */
|
||||
};
|
||||
|
||||
enum shm_bit_depth {
|
||||
SHM_BITS_AUTO,
|
||||
SHM_BITS_8,
|
||||
SHM_BITS_10,
|
||||
SHM_BITS_16,
|
||||
};
|
||||
|
||||
enum center_when {
|
||||
CENTER_INVALID,
|
||||
CENTER_NEVER,
|
||||
CENTER_FULLSCREEN,
|
||||
CENTER_MAXIMIZED_AND_FULLSCREEN,
|
||||
CENTER_ALWAYS,
|
||||
};
|
||||
|
||||
struct config {
|
||||
char *conf_path;
|
||||
char *term;
|
||||
char *shell;
|
||||
char *title;
|
||||
char *app_id;
|
||||
char *toplevel_tag;
|
||||
char32_t *word_delimiters;
|
||||
bool login_shell;
|
||||
bool locked_title;
|
||||
|
|
@ -125,19 +235,32 @@ struct config {
|
|||
uint32_t height;
|
||||
} size;
|
||||
|
||||
unsigned pad_x;
|
||||
unsigned pad_y;
|
||||
bool center;
|
||||
unsigned pad_left;
|
||||
unsigned pad_top;
|
||||
unsigned pad_right;
|
||||
unsigned pad_bottom;
|
||||
enum center_when center_when;
|
||||
|
||||
bool resize_by_cells;
|
||||
bool resize_keep_grid;
|
||||
|
||||
uint16_t resize_delay_ms;
|
||||
|
||||
struct {
|
||||
float amount;
|
||||
} dim;
|
||||
|
||||
struct {
|
||||
bool enabled;
|
||||
bool palette_based;
|
||||
float amount;
|
||||
} bold_in_bright;
|
||||
|
||||
enum { STARTUP_WINDOWED, STARTUP_MAXIMIZED, STARTUP_FULLSCREEN } startup_mode;
|
||||
|
||||
enum {DPI_AWARE_AUTO, DPI_AWARE_YES, DPI_AWARE_NO} dpi_aware;
|
||||
bool dpi_aware;
|
||||
bool gamma_correct;
|
||||
bool uppercase_regex_insert;
|
||||
struct config_font_list fonts[4];
|
||||
struct font_size_adjustment font_size_adjustment;
|
||||
|
||||
|
|
@ -153,12 +276,25 @@ struct config {
|
|||
struct pt_or_px underline_offset;
|
||||
struct pt_or_px underline_thickness;
|
||||
|
||||
struct pt_or_px strikeout_thickness;
|
||||
|
||||
bool box_drawings_uses_font_glyphs;
|
||||
bool can_shape_grapheme;
|
||||
|
||||
struct {
|
||||
enum {
|
||||
OSC52_DISABLED,
|
||||
OSC52_COPY_ENABLED,
|
||||
OSC52_PASTE_ENABLED,
|
||||
OSC52_ENABLED,
|
||||
} osc52;
|
||||
} security;
|
||||
|
||||
struct {
|
||||
bool urgent;
|
||||
bool notify;
|
||||
bool flash;
|
||||
bool system_bell;
|
||||
struct config_spawn_template command;
|
||||
bool command_focused;
|
||||
} bell;
|
||||
|
|
@ -192,63 +328,23 @@ struct config {
|
|||
OSC8_UNDERLINE_ALWAYS,
|
||||
} osc8_underline;
|
||||
|
||||
char32_t **protocols;
|
||||
char32_t *uri_characters;
|
||||
size_t prot_count;
|
||||
size_t max_prot_len;
|
||||
char *regex;
|
||||
regex_t preg;
|
||||
} url;
|
||||
|
||||
struct {
|
||||
uint32_t fg;
|
||||
uint32_t bg;
|
||||
uint32_t table[256];
|
||||
uint16_t alpha;
|
||||
uint32_t selection_fg;
|
||||
uint32_t selection_bg;
|
||||
uint32_t url;
|
||||
tll(struct custom_regex) custom_regexes;
|
||||
|
||||
uint32_t dim[8];
|
||||
|
||||
struct {
|
||||
uint32_t fg;
|
||||
uint32_t bg;
|
||||
} jump_label;
|
||||
|
||||
struct {
|
||||
uint32_t fg;
|
||||
uint32_t bg;
|
||||
} scrollback_indicator;
|
||||
|
||||
struct {
|
||||
struct {
|
||||
uint32_t fg;
|
||||
uint32_t bg;
|
||||
} no_match;
|
||||
|
||||
struct {
|
||||
uint32_t fg;
|
||||
uint32_t bg;
|
||||
} match;
|
||||
} search_box;
|
||||
|
||||
struct {
|
||||
bool selection:1;
|
||||
bool jump_label:1;
|
||||
bool scrollback_indicator:1;
|
||||
bool url:1;
|
||||
bool search_box_no_match:1;
|
||||
bool search_box_match:1;
|
||||
uint8_t dim;
|
||||
} use_custom;
|
||||
} colors;
|
||||
struct color_theme colors_dark;
|
||||
struct color_theme colors_light;
|
||||
enum which_color_theme initial_color_theme;
|
||||
|
||||
struct {
|
||||
enum cursor_style style;
|
||||
bool blink;
|
||||
enum cursor_unfocused_style unfocused_style;
|
||||
struct {
|
||||
uint32_t text;
|
||||
uint32_t cursor;
|
||||
} color;
|
||||
bool enabled;
|
||||
uint32_t rate_ms;
|
||||
} blink;
|
||||
struct pt_or_px beam_thickness;
|
||||
struct pt_or_px underline_thickness;
|
||||
} cursor;
|
||||
|
|
@ -256,7 +352,8 @@ struct config {
|
|||
struct {
|
||||
bool hide_when_typing;
|
||||
bool alternate_scroll_mode;
|
||||
struct config_key_modifiers selection_override_modifiers;
|
||||
//struct config_key_modifiers selection_override_modifiers;
|
||||
config_modifier_list_t selection_override_modifiers;
|
||||
} mouse;
|
||||
|
||||
struct {
|
||||
|
|
@ -285,6 +382,7 @@ struct config {
|
|||
uint16_t button_width;
|
||||
|
||||
bool hide_when_maximized;
|
||||
bool double_click_to_maximize;
|
||||
|
||||
struct {
|
||||
bool title_set:1;
|
||||
|
|
@ -297,7 +395,7 @@ struct config {
|
|||
uint32_t buttons;
|
||||
uint32_t minimize;
|
||||
uint32_t maximize;
|
||||
uint32_t quit; /* ‘close’ collides with #define in epoll-shim */
|
||||
uint32_t quit; /* 'close' collides with #define in epoll-shim */
|
||||
uint32_t border;
|
||||
} color;
|
||||
|
||||
|
|
@ -315,12 +413,16 @@ struct config {
|
|||
SELECTION_TARGET_BOTH
|
||||
} selection_target;
|
||||
|
||||
struct config_spawn_template notify;
|
||||
bool notify_focus_inhibit;
|
||||
struct {
|
||||
struct config_spawn_template command;
|
||||
struct config_spawn_template command_action_arg;
|
||||
struct config_spawn_template close;
|
||||
bool inhibit_when_focused;
|
||||
} desktop_notifications;
|
||||
|
||||
env_var_list_t env_vars;
|
||||
|
||||
char *utempter_path;
|
||||
char *utmp_helper_path;
|
||||
|
||||
struct {
|
||||
enum fcft_scaling_filter fcft_filter;
|
||||
|
|
@ -345,8 +447,15 @@ struct config {
|
|||
bool box_drawing_solid_shades;
|
||||
bool font_monospace_warn;
|
||||
bool sixel;
|
||||
enum shm_bit_depth surface_bit_depth;
|
||||
uint32_t min_stride_alignment;
|
||||
bool preapply_damage;
|
||||
} tweak;
|
||||
|
||||
struct {
|
||||
uint32_t long_press_delay;
|
||||
} touch;
|
||||
|
||||
user_notifications_t notifications;
|
||||
};
|
||||
|
||||
|
|
@ -355,17 +464,19 @@ bool config_override_apply(struct config *conf, config_override_t *overrides,
|
|||
bool config_load(
|
||||
struct config *conf, const char *path,
|
||||
user_notifications_t *initial_user_notifications,
|
||||
config_override_t *overrides, bool errors_are_fatal);
|
||||
config_override_t *overrides, bool errors_are_fatal,
|
||||
bool as_server);
|
||||
void config_free(struct config *conf);
|
||||
struct config *config_clone(const struct config *old);
|
||||
|
||||
bool config_font_parse(const char *pattern, struct config_font *font);
|
||||
void config_font_list_destroy(struct config_font_list *font_list);
|
||||
|
||||
#if 0
|
||||
struct seat;
|
||||
xkb_mod_mask_t
|
||||
conf_modifiers_to_mask(
|
||||
const struct seat *seat, const struct config_key_modifiers *modifiers);
|
||||
|
||||
#endif
|
||||
bool check_if_font_is_monospaced(
|
||||
const char *pattern, user_notifications_t *notifications);
|
||||
|
|
|
|||
640
csi.c
640
csi.c
|
|
@ -3,7 +3,6 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
#if defined(_DEBUG)
|
||||
#include <stdio.h>
|
||||
|
|
@ -32,7 +31,12 @@
|
|||
static void
|
||||
sgr_reset(struct terminal *term)
|
||||
{
|
||||
memset(&term->vt.attrs, 0, sizeof(term->vt.attrs));
|
||||
term->vt.attrs = (struct attributes){0};
|
||||
term->vt.underline = (struct underline_range_data){0};
|
||||
|
||||
term->bits_affecting_ascii_printer.underline_style = false;
|
||||
term->bits_affecting_ascii_printer.underline_color = false;
|
||||
term_update_ascii_printer(term);
|
||||
}
|
||||
|
||||
static const char *
|
||||
|
|
@ -88,17 +92,58 @@ csi_sgr(struct terminal *term)
|
|||
case 1: term->vt.attrs.bold = true; break;
|
||||
case 2: term->vt.attrs.dim = true; break;
|
||||
case 3: term->vt.attrs.italic = true; break;
|
||||
case 4: term->vt.attrs.underline = true; break;
|
||||
case 4: {
|
||||
term->vt.attrs.underline = true;
|
||||
term->vt.underline.style = UNDERLINE_SINGLE;
|
||||
|
||||
if (unlikely(term->vt.params.v[i].sub.idx == 1)) {
|
||||
enum underline_style style = term->vt.params.v[i].sub.value[0];
|
||||
|
||||
switch (style) {
|
||||
default:
|
||||
case UNDERLINE_NONE:
|
||||
term->vt.attrs.underline = false;
|
||||
term->vt.underline.style = UNDERLINE_NONE;
|
||||
term->bits_affecting_ascii_printer.underline_style = false;
|
||||
break;
|
||||
|
||||
case UNDERLINE_SINGLE:
|
||||
case UNDERLINE_DOUBLE:
|
||||
case UNDERLINE_CURLY:
|
||||
case UNDERLINE_DOTTED:
|
||||
case UNDERLINE_DASHED:
|
||||
term->vt.underline.style = style;
|
||||
term->bits_affecting_ascii_printer.underline_style =
|
||||
style > UNDERLINE_SINGLE;
|
||||
break;
|
||||
}
|
||||
} else
|
||||
term->bits_affecting_ascii_printer.underline_style = false;
|
||||
term_update_ascii_printer(term);
|
||||
break;
|
||||
}
|
||||
case 5: term->vt.attrs.blink = true; break;
|
||||
case 6: LOG_WARN("ignored: rapid blink"); break;
|
||||
case 7: term->vt.attrs.reverse = true; break;
|
||||
case 8: term->vt.attrs.conceal = true; break;
|
||||
case 9: term->vt.attrs.strikethrough = true; break;
|
||||
|
||||
case 21: break; /* double-underline, not implemented */
|
||||
case 21:
|
||||
term->vt.attrs.underline = true;
|
||||
term->vt.underline.style = UNDERLINE_DOUBLE;
|
||||
term->bits_affecting_ascii_printer.underline_style = true;
|
||||
term_update_ascii_printer(term);
|
||||
break;
|
||||
|
||||
case 22: term->vt.attrs.bold = term->vt.attrs.dim = false; break;
|
||||
case 23: term->vt.attrs.italic = false; break;
|
||||
case 24: term->vt.attrs.underline = false; break;
|
||||
case 24: {
|
||||
term->vt.attrs.underline = false;
|
||||
term->vt.underline.style = UNDERLINE_NONE;
|
||||
term->bits_affecting_ascii_printer.underline_style = false;
|
||||
term_update_ascii_printer(term);
|
||||
break;
|
||||
}
|
||||
case 25: term->vt.attrs.blink = false; break;
|
||||
case 26: break; /* rapid blink, ignored */
|
||||
case 27: term->vt.attrs.reverse = false; break;
|
||||
|
|
@ -119,7 +164,8 @@ csi_sgr(struct terminal *term)
|
|||
break;
|
||||
|
||||
case 38:
|
||||
case 48: {
|
||||
case 48:
|
||||
case 58: {
|
||||
uint32_t color;
|
||||
enum color_source src;
|
||||
|
||||
|
|
@ -194,7 +240,12 @@ csi_sgr(struct terminal *term)
|
|||
break;
|
||||
}
|
||||
|
||||
if (param == 38) {
|
||||
if (unlikely(param == 58)) {
|
||||
term->vt.underline.color_src = src;
|
||||
term->vt.underline.color = color;
|
||||
term->bits_affecting_ascii_printer.underline_color = true;
|
||||
term_update_ascii_printer(term);
|
||||
} else if (param == 38) {
|
||||
term->vt.attrs.fg_src = src;
|
||||
term->vt.attrs.fg = color;
|
||||
} else {
|
||||
|
|
@ -226,6 +277,13 @@ csi_sgr(struct terminal *term)
|
|||
term->vt.attrs.bg_src = COLOR_DEFAULT;
|
||||
break;
|
||||
|
||||
case 59:
|
||||
term->vt.underline.color_src = COLOR_DEFAULT;
|
||||
term->vt.underline.color = 0;
|
||||
term->bits_affecting_ascii_printer.underline_color = false;
|
||||
term_update_ascii_printer(term);
|
||||
break;
|
||||
|
||||
/* Bright foreground colors */
|
||||
case 90:
|
||||
case 91:
|
||||
|
|
@ -324,6 +382,11 @@ decset_decrst(struct terminal *term, unsigned param, bool enable)
|
|||
term->keypad_keys_mode = enable ? KEYPAD_APPLICATION : KEYPAD_NUMERICAL;
|
||||
break;
|
||||
|
||||
case 67:
|
||||
if (enable)
|
||||
LOG_WARN("unimplemented: DECBKM");
|
||||
break;
|
||||
|
||||
case 80:
|
||||
term->sixel.scrolling = !enable;
|
||||
break;
|
||||
|
|
@ -359,6 +422,8 @@ decset_decrst(struct terminal *term, unsigned param, bool enable)
|
|||
|
||||
case 1004:
|
||||
term->focus_events = enable;
|
||||
if (enable)
|
||||
term_to_slave(term, term->kbd_focus ? "\033[I" : "\033[O", 3);
|
||||
break;
|
||||
|
||||
case 1005:
|
||||
|
|
@ -473,6 +538,9 @@ decset_decrst(struct terminal *term, unsigned param, bool enable)
|
|||
tll_free(term->alt.scroll_damage);
|
||||
term_damage_view(term);
|
||||
}
|
||||
|
||||
term->bits_affecting_ascii_printer.sixels =
|
||||
tll_length(term->grid->sixel_images) > 0;
|
||||
term_update_ascii_printer(term);
|
||||
break;
|
||||
|
||||
|
|
@ -491,6 +559,23 @@ decset_decrst(struct terminal *term, unsigned param, bool enable)
|
|||
term_disable_app_sync_updates(term);
|
||||
break;
|
||||
|
||||
case 2027:
|
||||
#if defined(FOOT_GRAPHEME_CLUSTERING)
|
||||
term->grapheme_shaping = enable;
|
||||
#endif
|
||||
break;
|
||||
|
||||
case 2031:
|
||||
term->report_theme_changes = enable;
|
||||
break;
|
||||
|
||||
case 2048:
|
||||
if (enable)
|
||||
term_enable_size_notifications(term);
|
||||
else
|
||||
term_disable_size_notifications(term);
|
||||
break;
|
||||
|
||||
case 8452:
|
||||
term->sixel.cursor_right_of_graphics = enable;
|
||||
break;
|
||||
|
|
@ -498,8 +583,10 @@ decset_decrst(struct terminal *term, unsigned param, bool enable)
|
|||
case 737769:
|
||||
if (enable)
|
||||
term_ime_enable(term);
|
||||
else
|
||||
else {
|
||||
term_ime_disable(term);
|
||||
term->ime_reenable_after_url_mode = false;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
@ -551,6 +638,7 @@ decrqm(const struct terminal *term, unsigned param)
|
|||
case 25: return decrpm(!term->hide_cursor);
|
||||
case 45: return decrpm(term->reverse_wrap);
|
||||
case 66: return decrpm(term->keypad_keys_mode == KEYPAD_APPLICATION);
|
||||
case 67: return DECRPM_PERMANENTLY_RESET; /* https://vt100.net/docs/vt510-rm/DECBKM */
|
||||
case 80: return decrpm(!term->sixel.scrolling);
|
||||
case 1000: return decrpm(term->mouse_tracking == MOUSE_CLICK);
|
||||
case 1001: return DECRPM_PERMANENTLY_RESET;
|
||||
|
|
@ -572,6 +660,11 @@ decrqm(const struct terminal *term, unsigned param)
|
|||
case 1070: return decrpm(term->sixel.use_private_palette);
|
||||
case 2004: return decrpm(term->bracketed_paste);
|
||||
case 2026: return decrpm(term->render.app_sync_updates.enabled);
|
||||
case 2027: return term->conf->tweak.grapheme_width_method != GRAPHEME_WIDTH_DOUBLE
|
||||
? DECRPM_PERMANENTLY_RESET
|
||||
: decrpm(term->grapheme_shaping);
|
||||
case 2031: return decrpm(term->report_theme_changes);
|
||||
case 2048: return decrpm(term->size_notifications);
|
||||
case 8452: return decrpm(term->sixel.cursor_right_of_graphics);
|
||||
case 737769: return decrpm(term_ime_is_enabled(term));
|
||||
}
|
||||
|
|
@ -593,6 +686,7 @@ xtsave(struct terminal *term, unsigned param)
|
|||
case 45: term->xtsave.reverse_wrap = term->reverse_wrap; break;
|
||||
case 47: term->xtsave.alt_screen = term->grid == &term->alt; break;
|
||||
case 66: term->xtsave.application_keypad_keys = term->keypad_keys_mode == KEYPAD_APPLICATION; break;
|
||||
case 67: break;
|
||||
case 80: term->xtsave.sixel_display_mode = !term->sixel.scrolling; break;
|
||||
case 1000: term->xtsave.mouse_click = term->mouse_tracking == MOUSE_CLICK; break;
|
||||
case 1001: break;
|
||||
|
|
@ -614,6 +708,9 @@ xtsave(struct terminal *term, unsigned param)
|
|||
case 1070: term->xtsave.sixel_private_palette = term->sixel.use_private_palette; break;
|
||||
case 2004: term->xtsave.bracketed_paste = term->bracketed_paste; break;
|
||||
case 2026: term->xtsave.app_sync_updates = term->render.app_sync_updates.enabled; break;
|
||||
case 2027: term->xtsave.grapheme_shaping = term->grapheme_shaping; break;
|
||||
case 2031: term->xtsave.report_theme_changes = term->report_theme_changes; break;
|
||||
case 2048: term->xtsave.size_notifications = term->size_notifications; break;
|
||||
case 8452: term->xtsave.sixel_cursor_right_of_graphics = term->sixel.cursor_right_of_graphics; break;
|
||||
case 737769: term->xtsave.ime = term_ime_is_enabled(term); break;
|
||||
}
|
||||
|
|
@ -634,6 +731,7 @@ xtrestore(struct terminal *term, unsigned param)
|
|||
case 45: enable = term->xtsave.reverse_wrap; break;
|
||||
case 47: enable = term->xtsave.alt_screen; break;
|
||||
case 66: enable = term->xtsave.application_keypad_keys; break;
|
||||
case 67: return;
|
||||
case 80: enable = term->xtsave.sixel_display_mode; break;
|
||||
case 1000: enable = term->xtsave.mouse_click; break;
|
||||
case 1001: return;
|
||||
|
|
@ -655,6 +753,9 @@ xtrestore(struct terminal *term, unsigned param)
|
|||
case 1070: enable = term->xtsave.sixel_private_palette; break;
|
||||
case 2004: enable = term->xtsave.bracketed_paste; break;
|
||||
case 2026: enable = term->xtsave.app_sync_updates; break;
|
||||
case 2027: enable = term->xtsave.grapheme_shaping; break;
|
||||
case 2031: enable = term->xtsave.report_theme_changes; break;
|
||||
case 2048: enable = term->xtsave.size_notifications; break;
|
||||
case 8452: enable = term->xtsave.sixel_cursor_right_of_graphics; break;
|
||||
case 737769: enable = term->xtsave.ime; break;
|
||||
|
||||
|
|
@ -664,6 +765,24 @@ xtrestore(struct terminal *term, unsigned param)
|
|||
decset_decrst(term, param, enable);
|
||||
}
|
||||
|
||||
static bool
|
||||
params_to_rectangular_area(const struct terminal *term, int first_idx,
|
||||
int *top, int *left, int *bottom, int *right)
|
||||
{
|
||||
int rel_top = vt_param_get(term, first_idx + 0, 1) - 1;
|
||||
*left = min(vt_param_get(term, first_idx + 1, 1) - 1, term->cols - 1);
|
||||
int rel_bottom = vt_param_get(term, first_idx + 2, term->rows) - 1;
|
||||
*right = min(vt_param_get(term, first_idx + 3, term->cols) - 1, term->cols - 1);
|
||||
|
||||
if (rel_top > rel_bottom || *left > *right)
|
||||
return false;
|
||||
|
||||
*top = term_row_rel_to_abs(term, rel_top);
|
||||
*bottom = term_row_rel_to_abs(term, rel_bottom);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
csi_dispatch(struct terminal *term, uint8_t final)
|
||||
{
|
||||
|
|
@ -682,10 +801,20 @@ csi_dispatch(struct terminal *term, uint8_t final)
|
|||
int count = vt_param_get(term, 0, 1);
|
||||
LOG_DBG("REP: '%lc' %d times", (wint_t)term->vt.last_printed, count);
|
||||
|
||||
const int width = c32width(term->vt.last_printed);
|
||||
int width;
|
||||
|
||||
if (term->vt.last_printed >= CELL_COMB_CHARS_LO) {
|
||||
const struct composed *comp = composed_lookup(
|
||||
term->composed, term->vt.last_printed - CELL_COMB_CHARS_LO);
|
||||
|
||||
xassert(comp != NULL);
|
||||
width = comp->forced_width > 0 ? comp->forced_width : comp->width;
|
||||
} else
|
||||
width = c32width(term->vt.last_printed);
|
||||
|
||||
if (width > 0) {
|
||||
for (int i = 0; i < count; i++)
|
||||
term_print(term, term->vt.last_printed, width);
|
||||
term_print(term, term->vt.last_printed, width, false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
@ -723,6 +852,7 @@ csi_dispatch(struct terminal *term, uint8_t final)
|
|||
* - 22 ANSI color, e.g., VT525.
|
||||
* - 28 Rectangular editing.
|
||||
* - 29 ANSI text locator (i.e., DEC Locator mode).
|
||||
* - 52 Clipboard access
|
||||
*
|
||||
* Note: we report ourselves as a VT220, mainly to be able
|
||||
* to pass parameters, to indicate we support sixel, and
|
||||
|
|
@ -733,13 +863,15 @@ csi_dispatch(struct terminal *term, uint8_t final)
|
|||
*
|
||||
* Note: tertiary DA responds with "FOOT".
|
||||
*/
|
||||
if (term->conf->tweak.sixel) {
|
||||
static const char reply[] = "\033[?62;4;22c";
|
||||
term_to_slave(term, reply, sizeof(reply) - 1);
|
||||
} else {
|
||||
static const char reply[] = "\033[?62;22c";
|
||||
term_to_slave(term, reply, sizeof(reply) - 1);
|
||||
}
|
||||
char reply[32];
|
||||
|
||||
int len = snprintf(
|
||||
reply, sizeof(reply), "\033[?62%s;22;28%sc",
|
||||
term->conf->tweak.sixel ? ";4" : "",
|
||||
(term->conf->security.osc52 == OSC52_ENABLED ||
|
||||
term->conf->security.osc52 == OSC52_COPY_ENABLED ? ";52" : ""));
|
||||
|
||||
term_to_slave(term, reply, len);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -815,7 +947,7 @@ csi_dispatch(struct terminal *term, uint8_t final)
|
|||
case 'G': {
|
||||
/* Cursor horizontal absolute */
|
||||
int col = min(vt_param_get(term, 0, 1), term->cols) - 1;
|
||||
term_cursor_to(term, term->grid->cursor.point.row, col);
|
||||
term_cursor_col(term, col);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1072,44 +1204,30 @@ csi_dispatch(struct terminal *term, uint8_t final)
|
|||
break;
|
||||
|
||||
case 'h':
|
||||
/* Set mode */
|
||||
switch (vt_param_get(term, 0, 0)) {
|
||||
case 2: /* Keyboard Action Mode - AM */
|
||||
LOG_WARN("unimplemented: keyboard action mode (AM)");
|
||||
break;
|
||||
|
||||
case 4: /* Insert Mode - IRM */
|
||||
term->insert_mode = true;
|
||||
case 'l': {
|
||||
/* Set/Reset Mode (SM/RM) */
|
||||
int param = vt_param_get(term, 0, 0);
|
||||
bool sm = final == 'h';
|
||||
if (param == 4) {
|
||||
/* Insertion Replacement Mode (IRM) */
|
||||
term->insert_mode = sm;
|
||||
term->bits_affecting_ascii_printer.insert_mode = sm;
|
||||
term_update_ascii_printer(term);
|
||||
break;
|
||||
}
|
||||
|
||||
case 12: /* Send/receive Mode - SRM */
|
||||
LOG_WARN("unimplemented: send/receive mode (SRM)");
|
||||
break;
|
||||
|
||||
case 20: /* Automatic Newline Mode - LNM */
|
||||
/* TODO: would be easy to implemented; when active
|
||||
* term_linefeed() would _also_ do a
|
||||
* term_carriage_return() */
|
||||
LOG_WARN("unimplemented: automatic newline mode (LNM)");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
/* Reset mode */
|
||||
switch (vt_param_get(term, 0, 0)) {
|
||||
case 4: /* Insert Mode - IRM */
|
||||
term->insert_mode = false;
|
||||
term_update_ascii_printer(term);
|
||||
break;
|
||||
|
||||
case 2: /* Keyboard Action Mode - AM */
|
||||
case 12: /* Send/receive Mode - SRM */
|
||||
case 20: /* Automatic Newline Mode - LNM */
|
||||
break;
|
||||
/*
|
||||
* ECMA-48 defines modes 1-22, all of which were optional
|
||||
* (§7.1; "may have one state only") and are considered
|
||||
* deprecated (§7.1) in the latest (5th) edition. xterm only
|
||||
* documents modes 2, 4, 12 and 20, the last of which was
|
||||
* outright removed (§8.3.106) in 5th edition ECMA-48.
|
||||
*/
|
||||
if (sm) {
|
||||
LOG_WARN("SM with unimplemented mode: %d", param);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'r': {
|
||||
int start = vt_param_get(term, 0, 1);
|
||||
|
|
@ -1156,7 +1274,6 @@ csi_dispatch(struct terminal *term, uint8_t final)
|
|||
case 9: LOG_WARN("unimplemented: maximize/unmaximize window"); break;
|
||||
case 10: LOG_WARN("unimplemented: to/from full screen"); break;
|
||||
case 20: LOG_WARN("unimplemented: report icon label"); break;
|
||||
case 21: LOG_WARN("unimplemented: report window title"); break;
|
||||
case 24: LOG_WARN("unimplemented: resize window (DECSLPP)"); break;
|
||||
|
||||
case 11: /* report if window is iconified */
|
||||
|
|
@ -1206,8 +1323,8 @@ csi_dispatch(struct terminal *term, uint8_t final)
|
|||
|
||||
if (width >= 0 && height >= 0) {
|
||||
char reply[64];
|
||||
size_t n = xsnprintf(reply, sizeof(reply), "\033[4;%d;%dt",
|
||||
height / term->scale, width / term->scale);
|
||||
size_t n = xsnprintf(
|
||||
reply, sizeof(reply), "\033[4;%d;%dt", height, width);
|
||||
term_to_slave(term, reply, n);
|
||||
}
|
||||
break;
|
||||
|
|
@ -1217,8 +1334,8 @@ csi_dispatch(struct terminal *term, uint8_t final)
|
|||
tll_foreach(term->window->on_outputs, it) {
|
||||
char reply[64];
|
||||
size_t n = xsnprintf(reply, sizeof(reply), "\033[5;%d;%dt",
|
||||
it->item->dim.px_scaled.height,
|
||||
it->item->dim.px_scaled.width);
|
||||
it->item->dim.px_real.height,
|
||||
it->item->dim.px_real.width);
|
||||
term_to_slave(term, reply, n);
|
||||
break;
|
||||
}
|
||||
|
|
@ -1229,9 +1346,9 @@ csi_dispatch(struct terminal *term, uint8_t final)
|
|||
|
||||
case 16: { /* report cell size in pixels */
|
||||
char reply[64];
|
||||
size_t n = xsnprintf(reply, sizeof(reply), "\033[6;%d;%dt",
|
||||
term->cell_height / term->scale,
|
||||
term->cell_width / term->scale);
|
||||
size_t n = xsnprintf(
|
||||
reply, sizeof(reply), "\033[6;%d;%dt",
|
||||
term->cell_height, term->cell_width);
|
||||
term_to_slave(term, reply, n);
|
||||
break;
|
||||
}
|
||||
|
|
@ -1247,9 +1364,10 @@ csi_dispatch(struct terminal *term, uint8_t final)
|
|||
case 19: { /* report screen size in chars */
|
||||
tll_foreach(term->window->on_outputs, it) {
|
||||
char reply[64];
|
||||
size_t n = xsnprintf(reply, sizeof(reply), "\033[9;%d;%dt",
|
||||
it->item->dim.px_real.height / term->cell_height / term->scale,
|
||||
it->item->dim.px_real.width / term->cell_width / term->scale);
|
||||
size_t n = xsnprintf(
|
||||
reply, sizeof(reply), "\033[9;%d;%dt",
|
||||
it->item->dim.px_real.height / term->cell_height,
|
||||
it->item->dim.px_real.width / term->cell_width);
|
||||
term_to_slave(term, reply, n);
|
||||
break;
|
||||
}
|
||||
|
|
@ -1259,6 +1377,18 @@ csi_dispatch(struct terminal *term, uint8_t final)
|
|||
break;
|
||||
}
|
||||
|
||||
case 21: {
|
||||
#if 0 /* Disabled for now, see #1894 */
|
||||
char reply[3 + strlen(term->window_title) + 2 + 1];
|
||||
int chars = xsnprintf(
|
||||
reply, sizeof(reply), "\033]l%s\033\\", term->window_title);
|
||||
term_to_slave(term, reply, chars);
|
||||
#else
|
||||
LOG_WARN("CSI 21 t (report window title) ignored");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
case 22: { /* push window title */
|
||||
/* 0 - icon + title, 1 - icon, 2 - title */
|
||||
unsigned what = vt_param_get(term, 1, 0);
|
||||
|
|
@ -1398,6 +1528,82 @@ csi_dispatch(struct terminal *term, uint8_t final)
|
|||
break;
|
||||
}
|
||||
|
||||
case 'm': {
|
||||
int resource = vt_param_get(term, 0, 0);
|
||||
int value = -1;
|
||||
|
||||
switch (resource) {
|
||||
case 0: /* modifyKeyboard */
|
||||
value = 0;
|
||||
break;
|
||||
|
||||
case 1: /* modifyCursorKeys */
|
||||
case 2: /* modifyFunctionKeys */
|
||||
value = 1;
|
||||
break;
|
||||
|
||||
case 4: /* modifyOtherKeys */
|
||||
value = term->modify_other_keys_2 ? 2 : 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_WARN("XTQMODKEYS: invalid resource '%d' in '%s'",
|
||||
resource, csi_as_string(term, final, -1));
|
||||
break;
|
||||
}
|
||||
|
||||
if (value >= 0) {
|
||||
char reply[16] = {0};
|
||||
int chars = snprintf(reply, sizeof(reply),
|
||||
"\033[>%d;%dm", resource, value);
|
||||
term_to_slave(term, reply, chars);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'n': {
|
||||
const int param = vt_param_get(term, 0, 0);
|
||||
|
||||
switch (param) {
|
||||
case 996: { /* Query current theme mode (see private mode 2031) */
|
||||
/*
|
||||
* 1 - dark mode
|
||||
* 2 - light mode
|
||||
*
|
||||
* In foot, the themes aren't necessarily light/dark,
|
||||
* but by convention, the primary theme is dark, and
|
||||
* the alternative theme is light.
|
||||
*/
|
||||
char reply[16] = {0};
|
||||
int chars = snprintf(
|
||||
reply, sizeof(reply),
|
||||
"\033[?997;%dn",
|
||||
term->colors.active_theme == COLOR_THEME_DARK ? 1 : 2);
|
||||
|
||||
term_to_slave(term, reply, chars);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'p': {
|
||||
/*
|
||||
* Request status of ECMA-48/"ANSI" private mode (DECRQM
|
||||
* for SM/RM modes; see private="?$" case further below for
|
||||
* DECSET/DECRST modes)
|
||||
*/
|
||||
unsigned param = vt_param_get(term, 0, 0);
|
||||
unsigned status = DECRPM_NOT_RECOGNIZED;
|
||||
if (param == 4) {
|
||||
status = decrpm(term->insert_mode);
|
||||
}
|
||||
char reply[32];
|
||||
size_t n = xsnprintf(reply, sizeof(reply), "\033[%u;%u$y", param, status);
|
||||
term_to_slave(term, reply, n);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'u': {
|
||||
enum kitty_kbd_flags flags =
|
||||
term->grid->kitty_kbd.flags[term->grid->kitty_kbd.idx];
|
||||
|
|
@ -1438,10 +1644,10 @@ csi_dispatch(struct terminal *term, uint8_t final)
|
|||
* 64 - vt520
|
||||
* 65 - vt525
|
||||
*
|
||||
* Param 2 - firmware version
|
||||
* xterm uses its version number. We use an xterm
|
||||
* version number too, since e.g. Emacs uses this to
|
||||
* determine level of support.
|
||||
* Param 2 - firmware version xterm uses its version
|
||||
* number. We do to, in the format "MAJORMINORPATCH",
|
||||
* where all three version numbers are always two
|
||||
* digits. So e.g. 1.25.0 is reported as 012500.
|
||||
*
|
||||
* We report ourselves as a VT220. This must be
|
||||
* synchronized with the primary DA response.
|
||||
|
|
@ -1489,7 +1695,7 @@ csi_dispatch(struct terminal *term, uint8_t final)
|
|||
break;
|
||||
|
||||
default:
|
||||
LOG_WARN("invalid resource %d in %s",
|
||||
LOG_WARN("XTMODKEYS: invalid resource '%d' in '%s'",
|
||||
resource, csi_as_string(term, final, -1));
|
||||
break;
|
||||
}
|
||||
|
|
@ -1505,8 +1711,8 @@ csi_dispatch(struct terminal *term, uint8_t final)
|
|||
break;
|
||||
|
||||
case 4: /* modifyOtherKeys */
|
||||
/* We don’t support fully disabling modifyOtherKeys,
|
||||
* but simply revert back to mode ‘1’ */
|
||||
/* We don't support fully disabling modifyOtherKeys,
|
||||
* but simply revert back to mode '1' */
|
||||
term->modify_other_keys_2 = false;
|
||||
LOG_DBG("modifyOtherKeys=1");
|
||||
break;
|
||||
|
|
@ -1584,7 +1790,7 @@ csi_dispatch(struct terminal *term, uint8_t final)
|
|||
break;
|
||||
}
|
||||
}
|
||||
break; /* private[0] == ‘<’ */
|
||||
break; /* private[0] == '<' */
|
||||
}
|
||||
|
||||
case ' ': {
|
||||
|
|
@ -1594,13 +1800,14 @@ csi_dispatch(struct terminal *term, uint8_t final)
|
|||
switch (param) {
|
||||
case 0: /* blinking block, but we use it to reset to configured default */
|
||||
term->cursor_style = term->conf->cursor.style;
|
||||
term->cursor_blink.deccsusr = term->conf->cursor.blink;
|
||||
term->cursor_blink.deccsusr = term->conf->cursor.blink.enabled;
|
||||
term_cursor_blink_update(term);
|
||||
break;
|
||||
|
||||
case 1: /* blinking block */
|
||||
case 2: /* steady block */
|
||||
term->cursor_style = CURSOR_BLOCK;
|
||||
term->cursor_style = term->conf->cursor.style == CURSOR_HOLLOW
|
||||
? CURSOR_HOLLOW : CURSOR_BLOCK;
|
||||
break;
|
||||
|
||||
case 3: /* blinking underline */
|
||||
|
|
@ -1704,6 +1911,293 @@ csi_dispatch(struct terminal *term, uint8_t final)
|
|||
break; /* private[0] == '=' */
|
||||
}
|
||||
|
||||
case '$': {
|
||||
switch (final) {
|
||||
case 'r': { /* DECCARA */
|
||||
int top, left, bottom, right;
|
||||
if (!params_to_rectangular_area(
|
||||
term, 0, &top, &left, &bottom, &right))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
for (int r = top; r <= bottom; r++) {
|
||||
struct row *row = grid_row(term->grid, r);
|
||||
row->dirty = true;
|
||||
|
||||
for (int c = left; c <= right; c++) {
|
||||
struct attributes *a = &row->cells[c].attrs;
|
||||
a->clean = 0;
|
||||
|
||||
for (size_t i = 4; i < term->vt.params.idx; i++) {
|
||||
const int param = term->vt.params.v[i].value;
|
||||
|
||||
/* DECCARA only supports a sub-set of SGR parameters */
|
||||
switch (param) {
|
||||
case 0:
|
||||
a->bold = false;
|
||||
a->underline = false;
|
||||
a->blink = false;
|
||||
a->reverse = false;
|
||||
break;
|
||||
|
||||
case 1: a->bold = true; break;
|
||||
case 4: a->underline = true; break;
|
||||
case 5: a->blink = true; break;
|
||||
case 7: a->reverse = true; break;
|
||||
|
||||
case 22: a->bold = false; break;
|
||||
case 24: a->underline = false; break;
|
||||
case 25: a->blink = false; break;
|
||||
case 27: a->reverse = false; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 't': { /* DECRARA */
|
||||
int top, left, bottom, right;
|
||||
if (!params_to_rectangular_area(
|
||||
term, 0, &top, &left, &bottom, &right))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
for (int r = top; r <= bottom; r++) {
|
||||
struct row *row = grid_row(term->grid, r);
|
||||
row->dirty = true;
|
||||
|
||||
for (int c = left; c <= right; c++) {
|
||||
struct attributes *a = &row->cells[c].attrs;
|
||||
a->clean = 0;
|
||||
|
||||
for (size_t i = 4; i < term->vt.params.idx; i++) {
|
||||
const int param = term->vt.params.v[i].value;
|
||||
|
||||
/* DECRARA only supports a sub-set of SGR parameters */
|
||||
switch (param) {
|
||||
case 0:
|
||||
a->bold = !a->bold;
|
||||
a->underline = !a->underline;
|
||||
a->blink = !a->blink;
|
||||
a->reverse = !a->reverse;
|
||||
break;
|
||||
|
||||
case 1: a->bold = !a->bold; break;
|
||||
case 4: a->underline = !a->underline; break;
|
||||
case 5: a->blink = !a->blink; break;
|
||||
case 7: a->reverse = !a->reverse; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'v': { /* DECCRA */
|
||||
int src_top, src_left, src_bottom, src_right;
|
||||
if (!params_to_rectangular_area(
|
||||
term, 0, &src_top, &src_left, &src_bottom, &src_right))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
int src_page = vt_param_get(term, 4, 1);
|
||||
|
||||
int dst_rel_top = vt_param_get(term, 5, 1) - 1;
|
||||
int dst_left = vt_param_get(term, 6, 1) - 1;
|
||||
int dst_page = vt_param_get(term, 7, 1);
|
||||
|
||||
if (unlikely(src_page != 1 || dst_page != 1)) {
|
||||
/* We don’t support “pages” */
|
||||
break;
|
||||
}
|
||||
|
||||
int dst_rel_bottom = dst_rel_top + (src_bottom - src_top);
|
||||
int dst_right = min(dst_left + (src_right - src_left), term->cols - 1);
|
||||
|
||||
int dst_top = term_row_rel_to_abs(term, dst_rel_top);
|
||||
int dst_bottom = term_row_rel_to_abs(term, dst_rel_bottom);
|
||||
|
||||
/* Target area outside the screen is clipped */
|
||||
const size_t row_count = min(src_bottom - src_top,
|
||||
dst_bottom - dst_top) + 1;
|
||||
const size_t cell_count = min(src_right - src_left,
|
||||
dst_right - dst_left) + 1;
|
||||
|
||||
sixel_overwrite_by_rectangle(
|
||||
term, dst_top, dst_left, row_count, cell_count);
|
||||
|
||||
/*
|
||||
* Copy source area
|
||||
*
|
||||
* Note: since source and destination may overlap, we need
|
||||
* to copy out the entire source region first, and _then_
|
||||
* write the destination. I.e. this is similar to how
|
||||
* memmove() behaves, but adapted to our row/cell
|
||||
* structure.
|
||||
*/
|
||||
struct cell **copy = xmalloc(row_count * sizeof(copy[0]));
|
||||
for (int r = 0; r < row_count; r++) {
|
||||
copy[r] = xmalloc(cell_count * sizeof(copy[r][0]));
|
||||
|
||||
const struct row *row = grid_row(term->grid, src_top + r);
|
||||
const struct cell *cell = &row->cells[src_left];
|
||||
memcpy(copy[r], cell, cell_count * sizeof(copy[r][0]));
|
||||
}
|
||||
|
||||
/* Paste into destination area */
|
||||
for (int r = 0; r < row_count; r++) {
|
||||
struct row *row = grid_row(term->grid, dst_top + r);
|
||||
row->dirty = true;
|
||||
|
||||
struct cell *cell = &row->cells[dst_left];
|
||||
memcpy(cell, copy[r], cell_count * sizeof(copy[r][0]));
|
||||
free(copy[r]);
|
||||
|
||||
for (;cell < &row->cells[dst_left + cell_count]; cell++)
|
||||
cell->attrs.clean = 0;
|
||||
|
||||
if (unlikely(row->extra != NULL)) {
|
||||
/* TODO: technically, we should copy the source URIs... */
|
||||
grid_row_uri_range_erase(row, dst_left, dst_right);
|
||||
}
|
||||
}
|
||||
free(copy);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'x': { /* DECFRA */
|
||||
const uint8_t c = vt_param_get(term, 0, 0);
|
||||
|
||||
if (unlikely(!((c >= 32 && c < 126) || c >= 160)))
|
||||
break;
|
||||
|
||||
int top, left, bottom, right;
|
||||
if (!params_to_rectangular_area(
|
||||
term, 1, &top, &left, &bottom, &right))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
/* Erase the entire region at once (MUCH cheaper than
|
||||
* doing it row by row, or even character by
|
||||
* character). */
|
||||
sixel_overwrite_by_rectangle(
|
||||
term, top, left, bottom - top + 1, right - left + 1);
|
||||
|
||||
for (int r = top; r <= bottom; r++)
|
||||
term_fill(term, r, left, c, right - left + 1, true);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'z': { /* DECERA */
|
||||
int top, left, bottom, right;
|
||||
if (!params_to_rectangular_area(
|
||||
term, 0, &top, &left, &bottom, &right))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Note: term_erase() _also_ erases sixels, but since
|
||||
* we’re forced to erase one row at a time, erasing the
|
||||
* entire sixel here is more efficient.
|
||||
*/
|
||||
sixel_overwrite_by_rectangle(
|
||||
term, top, left, bottom - top + 1, right - left + 1);
|
||||
|
||||
for (int r = top; r <= bottom; r++)
|
||||
term_erase(term, r, left, r, right);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break; /* private[0] == ‘$’ */
|
||||
}
|
||||
|
||||
case '#': {
|
||||
switch (final) {
|
||||
case 'P': { /* XTPUSHCOLORS */
|
||||
int slot = vt_param_get(term, 0, 0);
|
||||
|
||||
/* Pm == 0, "push" (what xterm does is take take the
|
||||
*current* slot + 1, even if that's in the middle of the
|
||||
stack, and overwrites whatever is already in that
|
||||
slot) */
|
||||
if (slot == 0)
|
||||
slot = term->color_stack.idx + 1;
|
||||
|
||||
if (term->color_stack.size < slot) {
|
||||
const size_t new_size = slot;
|
||||
term->color_stack.stack = xrealloc(
|
||||
term->color_stack.stack,
|
||||
new_size * sizeof(term->color_stack.stack[0]));
|
||||
|
||||
/* Initialize new slots (except the selected slot,
|
||||
which is done below) */
|
||||
xassert(new_size > 0);
|
||||
for (size_t i = term->color_stack.size; i < new_size - 1; i++) {
|
||||
memcpy(&term->color_stack.stack[i], &term->colors,
|
||||
sizeof(term->colors));
|
||||
}
|
||||
term->color_stack.size = new_size;
|
||||
}
|
||||
|
||||
xassert(slot > 0);
|
||||
xassert(slot <= term->color_stack.size);
|
||||
term->color_stack.idx = slot;
|
||||
memcpy(&term->color_stack.stack[slot - 1], &term->colors,
|
||||
sizeof(term->colors));
|
||||
break;
|
||||
}
|
||||
|
||||
case 'Q': { /* XTPOPCOLORS */
|
||||
int slot = vt_param_get(term, 0, 0);
|
||||
|
||||
/* Pm == 0, "pop" (what xterm does is copy colors from the
|
||||
*current* slot, *and* decrease the current slot index,
|
||||
even if that's in the middle of the stack) */
|
||||
if (slot == 0)
|
||||
slot = term->color_stack.idx;
|
||||
|
||||
if (slot > 0 && slot <= term->color_stack.size) {
|
||||
memcpy(&term->colors, &term->color_stack.stack[slot - 1],
|
||||
sizeof(term->colors));
|
||||
term->color_stack.idx = slot - 1;
|
||||
|
||||
/* Assume a full palette switch *will* affect almost
|
||||
all cells. The alternative is to call
|
||||
term_damage_color() for all 256 palette entries
|
||||
*and* the default fg/bg (256 + 2 calls in total) */
|
||||
term_damage_view(term);
|
||||
term_damage_margins(term);
|
||||
} else if (slot == 0) {
|
||||
LOG_ERR("XTPOPCOLORS: cannot pop beyond the first element");
|
||||
} else {
|
||||
LOG_ERR(
|
||||
"XTPOPCOLORS: invalid color slot: %d "
|
||||
"(stack has %zu slots, current slot is %zu)",
|
||||
vt_param_get(term, 0, 0),
|
||||
term->color_stack.size, term->color_stack.idx);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'R': { /* XTREPORTCOLORS */
|
||||
char reply[64];
|
||||
size_t n = xsnprintf(reply, sizeof(reply), "\033[?%zu;%zu#Q",
|
||||
term->color_stack.idx, term->color_stack.size);
|
||||
term_to_slave(term, reply, n);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break; /* private[0] == '#' */
|
||||
}
|
||||
|
||||
case 0x243f: /* ?$ */
|
||||
switch (final) {
|
||||
case 'p': {
|
||||
|
|
@ -1731,7 +2225,7 @@ csi_dispatch(struct terminal *term, uint8_t final)
|
|||
break;
|
||||
}
|
||||
|
||||
break; /* private[0] == ‘?’ && private[1] == ‘$’ */
|
||||
break; /* private[0] == '?' && private[1] == '$' */
|
||||
|
||||
default:
|
||||
UNHANDLED();
|
||||
|
|
|
|||
128
cursor-shape.c
Normal file
128
cursor-shape.c
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define LOG_MODULE "cursor-shape"
|
||||
#define LOG_ENABLE_DBG 0
|
||||
#include "log.h"
|
||||
|
||||
#include "cursor-shape.h"
|
||||
#include "debug.h"
|
||||
#include "util.h"
|
||||
|
||||
const char *const *
|
||||
cursor_shape_to_string(enum cursor_shape shape)
|
||||
{
|
||||
static const char *const table[][CURSOR_SHAPE_COUNT]= {
|
||||
[CURSOR_SHAPE_NONE] = {NULL},
|
||||
[CURSOR_SHAPE_HIDDEN] = {"hidden", NULL},
|
||||
[CURSOR_SHAPE_LEFT_PTR] = {"default", "left_ptr", NULL},
|
||||
[CURSOR_SHAPE_TEXT] = {"text", "xterm", NULL},
|
||||
[CURSOR_SHAPE_TOP_LEFT_CORNER] = {"nw-resize", "top_left_corner", NULL},
|
||||
[CURSOR_SHAPE_TOP_RIGHT_CORNER] = {"ne-resize", "top_right_corner", NULL},
|
||||
[CURSOR_SHAPE_BOTTOM_LEFT_CORNER] = {"sw-resize", "bottom_left_corner", NULL},
|
||||
[CURSOR_SHAPE_BOTTOM_RIGHT_CORNER] = {"se-resize", "bottom_right_corner", NULL},
|
||||
[CURSOR_SHAPE_LEFT_SIDE] = {"w-resize", "left_side", NULL},
|
||||
[CURSOR_SHAPE_RIGHT_SIDE] = {"e-resize", "right_side", NULL},
|
||||
[CURSOR_SHAPE_TOP_SIDE] = {"n-resize", "top_side", NULL},
|
||||
[CURSOR_SHAPE_BOTTOM_SIDE] = {"s-resize", "bottom_side", NULL},
|
||||
|
||||
};
|
||||
|
||||
xassert(shape <= ALEN(table));
|
||||
return table[shape];
|
||||
}
|
||||
|
||||
enum wp_cursor_shape_device_v1_shape
|
||||
cursor_shape_to_server_shape(enum cursor_shape shape)
|
||||
{
|
||||
static const enum wp_cursor_shape_device_v1_shape table[CURSOR_SHAPE_COUNT] = {
|
||||
[CURSOR_SHAPE_LEFT_PTR] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT,
|
||||
[CURSOR_SHAPE_TEXT] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT,
|
||||
[CURSOR_SHAPE_TOP_LEFT_CORNER] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE,
|
||||
[CURSOR_SHAPE_TOP_RIGHT_CORNER] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE,
|
||||
[CURSOR_SHAPE_BOTTOM_LEFT_CORNER] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE,
|
||||
[CURSOR_SHAPE_BOTTOM_RIGHT_CORNER] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE,
|
||||
[CURSOR_SHAPE_LEFT_SIDE] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE,
|
||||
[CURSOR_SHAPE_RIGHT_SIDE] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE,
|
||||
[CURSOR_SHAPE_TOP_SIDE] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE,
|
||||
[CURSOR_SHAPE_BOTTOM_SIDE] = WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE,
|
||||
};
|
||||
|
||||
xassert(shape <= ALEN(table));
|
||||
xassert(table[shape] != 0);
|
||||
return table[shape];
|
||||
}
|
||||
|
||||
enum wp_cursor_shape_device_v1_shape
|
||||
cursor_string_to_server_shape(const char *xcursor, int bound_version)
|
||||
{
|
||||
if (xcursor == NULL)
|
||||
return 0;
|
||||
|
||||
static const char *const table[][2] = {
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT] = {"default", "left_ptr"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CONTEXT_MENU] = {"context-menu"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_HELP] = {"help", "question_arrow"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER] = {"pointer", "hand"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_PROGRESS] = {"progress", "left_ptr_watch"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_WAIT] = {"wait", "watch"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CELL] = {"cell"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR] = {"crosshair", "cross"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT] = {"text", "xterm"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_VERTICAL_TEXT] = {"vertical-text"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALIAS] = {"alias", "dnd-link"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COPY] = {"copy", "dnd-copy"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE] = {"move", "dnd-move"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NO_DROP] = {"no-drop", "dnd-no-drop"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NOT_ALLOWED] = {"not-allowed", "crossed_circle"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRAB] = {"grab", "hand1"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_GRABBING] = {"grabbing"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE] = {"e-resize", "right_side"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE] = {"n-resize", "top_side"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE] = {"ne-resize", "top_right_corner"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE] = {"nw-resize", "top_left_corner"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE] = {"s-resize", "bottom_side"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE] = {"se-resize", "bottom_right_corner"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE] = {"sw-resize", "bottom_left_corner"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE] = {"w-resize", "left_side"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE] = {"ew-resize", "sb_h_double_arrow"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE] = {"ns-resize", "sb_v_double_arrow"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NESW_RESIZE] = {"nesw-resize", "fd_double_arrow"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NWSE_RESIZE] = {"nwse-resize", "bd_double_arrow"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_COL_RESIZE] = {"col-resize", "sb_h_double_arrow"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ROW_RESIZE] = {"row-resize", "sb_v_double_arrow"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_SCROLL] = {"all-scroll", "fleur"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_IN] = {"zoom-in"},
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ZOOM_OUT] = {"zoom-out"},
|
||||
#if defined(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK_SINCE_VERSION) /* 1.42 */
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK] = {"dnd-ask"},
|
||||
#endif
|
||||
#if defined(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE_SINCE_VERSION) /* 1.42 */
|
||||
[WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE] = {"all-resize"},
|
||||
#endif
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < ALEN(table); i++) {
|
||||
#if defined(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK_SINCE_VERSION)
|
||||
if (i == WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK &&
|
||||
bound_version < WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DND_ASK_SINCE_VERSION)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
#if defined(WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE_SINCE_VERSION)
|
||||
if (i == WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE &&
|
||||
bound_version < WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_ALL_RESIZE_SINCE_VERSION)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
for (size_t j = 0; j < ALEN(table[i]); j++) {
|
||||
if (table[i][j] != NULL && streq(xcursor, table[i][j])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
29
cursor-shape.h
Normal file
29
cursor-shape.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#pragma once
|
||||
|
||||
#include <cursor-shape-v1.h>
|
||||
|
||||
enum cursor_shape {
|
||||
CURSOR_SHAPE_NONE,
|
||||
CURSOR_SHAPE_CUSTOM,
|
||||
CURSOR_SHAPE_HIDDEN,
|
||||
|
||||
CURSOR_SHAPE_LEFT_PTR,
|
||||
CURSOR_SHAPE_TEXT,
|
||||
CURSOR_SHAPE_TOP_LEFT_CORNER,
|
||||
CURSOR_SHAPE_TOP_RIGHT_CORNER,
|
||||
CURSOR_SHAPE_BOTTOM_LEFT_CORNER,
|
||||
CURSOR_SHAPE_BOTTOM_RIGHT_CORNER,
|
||||
CURSOR_SHAPE_LEFT_SIDE,
|
||||
CURSOR_SHAPE_RIGHT_SIDE,
|
||||
CURSOR_SHAPE_TOP_SIDE,
|
||||
CURSOR_SHAPE_BOTTOM_SIDE,
|
||||
|
||||
CURSOR_SHAPE_COUNT,
|
||||
};
|
||||
|
||||
const char *const *cursor_shape_to_string(enum cursor_shape shape);
|
||||
|
||||
enum wp_cursor_shape_device_v1_shape cursor_shape_to_server_shape(
|
||||
enum cursor_shape shape);
|
||||
enum wp_cursor_shape_device_v1_shape cursor_string_to_server_shape(
|
||||
const char *xcursor, int bound_version);
|
||||
91
dcs.c
91
dcs.c
|
|
@ -9,6 +9,7 @@
|
|||
#include "util.h"
|
||||
#include "vt.h"
|
||||
#include "xmalloc.h"
|
||||
#include "xsnprintf.h"
|
||||
|
||||
static bool
|
||||
ensure_size(struct terminal *term, size_t required_size)
|
||||
|
|
@ -111,14 +112,11 @@ static void
|
|||
xtgettcap_reply(struct terminal *term, const char *hex_cap_name, size_t len)
|
||||
{
|
||||
char *name = hex_decode(hex_cap_name, len);
|
||||
if (name == NULL)
|
||||
goto err;
|
||||
if (name == NULL) {
|
||||
LOG_WARN("XTGETTCAP: invalid hex encoding, ignoring capability");
|
||||
return;
|
||||
}
|
||||
|
||||
#if 0
|
||||
const struct foot_terminfo_entry *entry =
|
||||
bsearch(name, terminfo_capabilities, ALEN(terminfo_capabilities),
|
||||
sizeof(*entry), &terminfo_entry_compar);
|
||||
#endif
|
||||
const char *value;
|
||||
bool valid_capability = lookup_capability(name, &value);
|
||||
xassert(!valid_capability || value != NULL);
|
||||
|
|
@ -141,12 +139,12 @@ xtgettcap_reply(struct terminal *term, const char *hex_cap_name, size_t len)
|
|||
/*
|
||||
* Reply format:
|
||||
* \EP 1 + r cap=value \E\\
|
||||
* Where ‘cap’ and ‘value are hex encoded ascii strings
|
||||
* Where 'cap' and 'value are hex encoded ascii strings
|
||||
*/
|
||||
char *reply = xmalloc(
|
||||
5 + /* DCS 1 + r (\EP1+r) */
|
||||
len + /* capability name, hex encoded */
|
||||
1 + /* ‘=’ */
|
||||
1 + /* '=' */
|
||||
strlen(value) * 2 + /* capability value, hex encoded */
|
||||
2 + /* ST (\E\\) */
|
||||
1);
|
||||
|
|
@ -203,6 +201,12 @@ xtgettcap_unhook(struct terminal *term)
|
|||
const char *const end = (const char *)&term->vt.dcs.data[left];
|
||||
const char *p = (const char *)term->vt.dcs.data;
|
||||
|
||||
if (p == NULL) {
|
||||
/* Request is empty; send an error reply, without any capabilities */
|
||||
term_to_slave(term, "\033P0+r\033\\", 7);
|
||||
return;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const char *sep = memchr(p, ';', left);
|
||||
size_t cap_len;
|
||||
|
|
@ -242,7 +246,7 @@ decrqss_put(struct terminal *term, uint8_t c)
|
|||
return;
|
||||
|
||||
struct vt *vt = &term->vt;
|
||||
if (vt->dcs.idx > 2)
|
||||
if (vt->dcs.idx >= 2)
|
||||
return;
|
||||
vt->dcs.data[vt->dcs.idx++] = c;
|
||||
}
|
||||
|
|
@ -256,8 +260,8 @@ decrqss_unhook(struct terminal *term)
|
|||
/*
|
||||
* A note on the Ps parameter in the reply: many DEC manual
|
||||
* instances (e.g. https://vt100.net/docs/vt510-rm/DECRPSS) claim
|
||||
* that 0 means “request is valid”, and 1 means “request is
|
||||
* invalid”.
|
||||
* that 0 means "request is valid", and 1 means "request is
|
||||
* invalid".
|
||||
*
|
||||
* However, this appears to be a typo; actual hardware inverts the
|
||||
* response (as does XTerm and mlterm):
|
||||
|
|
@ -267,9 +271,9 @@ decrqss_unhook(struct terminal *term)
|
|||
if (n == 1 && query[0] == 'r') {
|
||||
/* DECSTBM - Set Top and Bottom Margins */
|
||||
char reply[64];
|
||||
int len = snprintf(reply, sizeof(reply), "\033P1$r%d;%dr\033\\",
|
||||
term->scroll_region.start + 1,
|
||||
term->scroll_region.end);
|
||||
size_t len = xsnprintf(reply, sizeof(reply), "\033P1$r%d;%dr\033\\",
|
||||
term->scroll_region.start + 1,
|
||||
term->scroll_region.end);
|
||||
term_to_slave(term, reply, len);
|
||||
}
|
||||
|
||||
|
|
@ -293,8 +297,15 @@ decrqss_unhook(struct terminal *term)
|
|||
append_sgr_attr("2");
|
||||
if (a->italic)
|
||||
append_sgr_attr("3");
|
||||
if (a->underline)
|
||||
append_sgr_attr("4");
|
||||
if (a->underline) {
|
||||
if (term->vt.underline.style > UNDERLINE_SINGLE) {
|
||||
char value[4];
|
||||
size_t val_len =
|
||||
xsnprintf(value, sizeof(value), "4:%d", term->vt.underline.style);
|
||||
append_sgr_attr_n(&reply, &len, value, val_len);
|
||||
} else
|
||||
append_sgr_attr("4");
|
||||
}
|
||||
if (a->blink)
|
||||
append_sgr_attr("5");
|
||||
if (a->reverse)
|
||||
|
|
@ -310,7 +321,7 @@ decrqss_unhook(struct terminal *term)
|
|||
|
||||
case COLOR_BASE16: {
|
||||
char value[4];
|
||||
int val_len = snprintf(
|
||||
size_t val_len = xsnprintf(
|
||||
value, sizeof(value), "%u",
|
||||
a->fg >= 8 ? a->fg - 8 + 90 : a->fg + 30);
|
||||
append_sgr_attr_n(&reply, &len, value, val_len);
|
||||
|
|
@ -319,7 +330,7 @@ decrqss_unhook(struct terminal *term)
|
|||
|
||||
case COLOR_BASE256: {
|
||||
char value[16];
|
||||
int val_len = snprintf(value, sizeof(value), "38:5:%u", a->fg);
|
||||
size_t val_len = xsnprintf(value, sizeof(value), "38:5:%u", a->fg);
|
||||
append_sgr_attr_n(&reply, &len, value, val_len);
|
||||
break;
|
||||
}
|
||||
|
|
@ -330,7 +341,7 @@ decrqss_unhook(struct terminal *term)
|
|||
uint8_t b = a->fg >> 0;
|
||||
|
||||
char value[32];
|
||||
int val_len = snprintf(
|
||||
size_t val_len = xsnprintf(
|
||||
value, sizeof(value), "38:2::%hhu:%hhu:%hhu", r, g, b);
|
||||
append_sgr_attr_n(&reply, &len, value, val_len);
|
||||
break;
|
||||
|
|
@ -343,7 +354,7 @@ decrqss_unhook(struct terminal *term)
|
|||
|
||||
case COLOR_BASE16: {
|
||||
char value[4];
|
||||
int val_len = snprintf(
|
||||
size_t val_len = xsnprintf(
|
||||
value, sizeof(value), "%u",
|
||||
a->bg >= 8 ? a->bg - 8 + 100 : a->bg + 40);
|
||||
append_sgr_attr_n(&reply, &len, value, val_len);
|
||||
|
|
@ -352,7 +363,7 @@ decrqss_unhook(struct terminal *term)
|
|||
|
||||
case COLOR_BASE256: {
|
||||
char value[16];
|
||||
int val_len = snprintf(value, sizeof(value), "48:5:%u", a->bg);
|
||||
size_t val_len = xsnprintf(value, sizeof(value), "48:5:%u", a->bg);
|
||||
append_sgr_attr_n(&reply, &len, value, val_len);
|
||||
break;
|
||||
}
|
||||
|
|
@ -363,13 +374,39 @@ decrqss_unhook(struct terminal *term)
|
|||
uint8_t b = a->bg >> 0;
|
||||
|
||||
char value[32];
|
||||
int val_len = snprintf(
|
||||
size_t val_len = xsnprintf(
|
||||
value, sizeof(value), "48:2::%hhu:%hhu:%hhu", r, g, b);
|
||||
append_sgr_attr_n(&reply, &len, value, val_len);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (term->vt.underline.color_src) {
|
||||
case COLOR_DEFAULT:
|
||||
case COLOR_BASE16:
|
||||
break;
|
||||
|
||||
case COLOR_BASE256: {
|
||||
char value[16];
|
||||
size_t val_len = xsnprintf(
|
||||
value, sizeof(value), "58:5:%u", term->vt.underline.color);
|
||||
append_sgr_attr_n(&reply, &len, value, val_len);
|
||||
break;
|
||||
}
|
||||
|
||||
case COLOR_RGB: {
|
||||
uint8_t r = term->vt.underline.color >> 16;
|
||||
uint8_t g = term->vt.underline.color >> 8;
|
||||
uint8_t b = term->vt.underline.color >> 0;
|
||||
|
||||
char value[32];
|
||||
size_t val_len = xsnprintf(
|
||||
value, sizeof(value), "58:2::%hhu:%hhu:%hhu", r, g, b);
|
||||
append_sgr_attr_n(&reply, &len, value, val_len);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#undef append_sgr_attr_n
|
||||
|
||||
reply[len - 1] = 'm';
|
||||
|
|
@ -385,6 +422,7 @@ decrqss_unhook(struct terminal *term)
|
|||
int mode;
|
||||
|
||||
switch (term->cursor_style) {
|
||||
case CURSOR_HOLLOW: /* FALLTHROUGH */
|
||||
case CURSOR_BLOCK: mode = 2; break;
|
||||
case CURSOR_UNDERLINE: mode = 4; break;
|
||||
case CURSOR_BEAM: mode = 6; break;
|
||||
|
|
@ -395,7 +433,7 @@ decrqss_unhook(struct terminal *term)
|
|||
mode--;
|
||||
|
||||
char reply[16];
|
||||
int len = snprintf(reply, sizeof(reply), "\033P1$r%d q\033\\", mode);
|
||||
size_t len = xsnprintf(reply, sizeof(reply), "\033P1$r%d q\033\\", mode);
|
||||
term_to_slave(term, reply, len);
|
||||
}
|
||||
|
||||
|
|
@ -424,11 +462,10 @@ dcs_hook(struct terminal *term, uint8_t final)
|
|||
break;
|
||||
}
|
||||
int p1 = vt_param_get(term, 0, 0);
|
||||
int p2 = vt_param_get(term, 1,0);
|
||||
int p2 = vt_param_get(term, 1, 0);
|
||||
int p3 = vt_param_get(term, 2, 0);
|
||||
|
||||
sixel_init(term, p1, p2, p3);
|
||||
term->vt.dcs.put_handler = &sixel_put;
|
||||
term->vt.dcs.put_handler = sixel_init(term, p1, p2, p3);
|
||||
term->vt.dcs.unhook_handler = &sixel_unhook;
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ m*.
|
|||
| 3
|
||||
: Italic
|
||||
| 4
|
||||
: Underline
|
||||
: Underline, including styled underlines
|
||||
| 5
|
||||
: Blink
|
||||
| 7
|
||||
|
|
@ -159,6 +159,8 @@ m*.
|
|||
: Conceal; text is not visible, but is copiable
|
||||
| 9
|
||||
: Crossed-out/strike
|
||||
| 21
|
||||
: Double underline
|
||||
| 22
|
||||
: Disable *bold* and *dim*
|
||||
| 23
|
||||
|
|
@ -176,15 +178,19 @@ m*.
|
|||
| 30-37
|
||||
: Select foreground color (using *regularN* in *foot.ini*(5))
|
||||
| 38
|
||||
: See "indexed and RGB colors" below
|
||||
: Select foreground color, see "indexed and RGB colors" below
|
||||
| 39
|
||||
: Use the default foreground color (*foreground* in *foot.ini*(5))
|
||||
| 40-47
|
||||
: Select background color (using *regularN* in *foot.ini*(5))
|
||||
| 48
|
||||
: See "indexed and RGB colors" below
|
||||
: Select background color, see "indexed and RGB colors" below
|
||||
| 49
|
||||
: Use the default background color (*background* in *foot.ini*(5))
|
||||
| 58
|
||||
: Select underline color, see "indexed and RGB colors" below
|
||||
| 59
|
||||
: Use the default underline color
|
||||
| 90-97
|
||||
: Select foreground color (using *brightN* in *foot.ini*(5))
|
||||
| 100-107
|
||||
|
|
@ -328,6 +334,15 @@ that corresponds to one of the following modes:
|
|||
| 2026
|
||||
: terminal-wg
|
||||
: Application synchronized updates mode
|
||||
| 2027
|
||||
: contour
|
||||
: Grapheme cluster processing
|
||||
| 2031
|
||||
: contour
|
||||
: Request color theme updates
|
||||
| 2048
|
||||
: TODO
|
||||
: In-band window resize notifications
|
||||
| 8452
|
||||
: xterm
|
||||
: Position cursor to the right of sixels, instead of on the next line
|
||||
|
|
@ -376,15 +391,24 @@ manipulation sequences. The generic format is:
|
|||
| 19
|
||||
: -
|
||||
: Report screen size, in characters.
|
||||
| 20
|
||||
: -
|
||||
: Report icon label.
|
||||
| 22
|
||||
: -
|
||||
: Push window title+icon. Foot does not support pushing the icon.
|
||||
: Push window title+icon.
|
||||
| 22
|
||||
: 1
|
||||
: Push window icon.
|
||||
| 22
|
||||
: 2
|
||||
: Push window title.
|
||||
| 23
|
||||
: -
|
||||
: Pop window title+icon. Foot does not support popping the icon.
|
||||
: Pop window title+icon.
|
||||
| 23
|
||||
: 1
|
||||
: Pop window icon.
|
||||
| 23
|
||||
: 2
|
||||
: Pop window title.
|
||||
|
|
@ -440,13 +464,13 @@ manipulation sequences. The generic format is:
|
|||
| \\E[ _Pm_ h
|
||||
: SM
|
||||
: VT100
|
||||
: Set mode. _Pm_=4 -> enable IRM (insert mode). All other values of
|
||||
_Pm_ are unsupported.
|
||||
: Set mode. _Pm_=4 -> enable IRM (Insertion Replacement Mode). All
|
||||
other values of _Pm_ are unsupported.
|
||||
| \\E[ _Pm_ l
|
||||
: RM
|
||||
: VT100
|
||||
: Reset mode. _Pm_=4 -> disable IRM (insert mode). All other values of
|
||||
_Pm_ are unsupported.
|
||||
: Reset mode. _Pm_=4 -> disable IRM (Insertion Replacement Mode). All
|
||||
other values of _Pm_ are unsupported.
|
||||
| \\E[ _Ps_ n
|
||||
: DSR
|
||||
: VT100
|
||||
|
|
@ -484,7 +508,39 @@ manipulation sequences. The generic format is:
|
|||
| \\E[ ? _Ps_ $ p
|
||||
: DECRQM
|
||||
: VT320
|
||||
: Request DEC private mode.
|
||||
: Request status of DEC private mode. The _Ps_ parameter corresponds
|
||||
to one of the values mentioned in the "Private Modes" section above
|
||||
(as set with DECSET/DECRST).
|
||||
| \\E[ _Ps_ $ p
|
||||
: DECRQM
|
||||
: VT320
|
||||
: Request status of ECMA-48/ANSI mode. See the descriptions for SM/RM
|
||||
above for recognized _Ps_ values.
|
||||
| \\E[ _Pt_ ; _Pl_ ; _Pb_ ; _Pr_ ; _Pm_ $ r
|
||||
: DECCARA
|
||||
: VT400
|
||||
: Change attributes in rectangular area. _Pt_, _Pl_, _Pb_ and _Pr_
|
||||
denotes the rectangle, _Pm_ denotes the SGR attributes.
|
||||
| \\E[ _Pt_ ; _Pl_ ; _Pb_ ; _Pr_ ; _Pm_ $ t
|
||||
: DECRARA
|
||||
: VT400
|
||||
: Invert attributes in rectangular area. _Pt_, _Pl_, _Pb_ and _Pr_
|
||||
denotes the rectangle, _Pm_ denotes the SGR attributes.
|
||||
| \\E[ _Pt_ ; _Pl_ ; _Pb_ ; _Pr_ ; _Pp_ ; _Pt_ ; _Pl_ ; _Pp_ $ v
|
||||
: DECCRA
|
||||
: VT400
|
||||
: Copy rectangular area. _Pt_, _Pl_, _Pb_ and _Pr_ denotes the
|
||||
rectangle, _Pt_ and _Pl_ denotes the target location.
|
||||
| \\E[ _Pc_ ; _Pt_ ; _Pl_ ; _Pb_ ; _Pr_ $ x
|
||||
: DECFRA
|
||||
: VT420
|
||||
: Fill rectangular area. _Pc_ is the character to use, _Pt_, _Pl_,
|
||||
_Pb_ and _Pr_ denotes the rectangle.
|
||||
| \\E[ _Pt_ ; _Pl_ ; _Pb_ ; _Pr_ $ z
|
||||
: DECERA
|
||||
: VT400
|
||||
: Erase rectangular area. _Pt_, _Pl_, _Pb_ and _Pr_ denotes the
|
||||
rectangle.
|
||||
| \\E[ _Ps_ T
|
||||
: SD
|
||||
: VT420
|
||||
|
|
@ -565,6 +621,10 @@ manipulation sequences. The generic format is:
|
|||
: xterm
|
||||
: Set level of the _modifyOtherKeys_ property to _Pv_. Note that foot
|
||||
only supports level 1 and 2, where level 1 is the default setting.
|
||||
| \\E[ ? _Pp_ m
|
||||
: XTQMODKEYS
|
||||
: xterm
|
||||
: Query key modifier options
|
||||
| \\E[ > 4 n
|
||||
: <unnamed>
|
||||
: xterm
|
||||
|
|
@ -587,6 +647,26 @@ manipulation sequences. The generic format is:
|
|||
: <unnamed>
|
||||
: kitty
|
||||
: Update current Kitty keyboard flags, according to _mode_.
|
||||
| \\E[ # P
|
||||
: XTPUSHCOLORS
|
||||
: xterm
|
||||
: Push current color palette onto stack
|
||||
| \\E[ # Q
|
||||
: XTPOPCOLORS
|
||||
: xterm
|
||||
: Pop color palette from stack
|
||||
| \\E[ # R
|
||||
: XTREPORTCOLORS
|
||||
: xterm
|
||||
: Report the current entry on the palette stack, and the number of
|
||||
palettes stored on the stack.
|
||||
| \\E[ ? 996 n
|
||||
: Query the current (color) theme mode
|
||||
: contour
|
||||
: The current color theme mode (light or dark) is reported as *CSI ?
|
||||
997 ; 1|2 n*, where *1* means dark and *2* light. By convention, the
|
||||
primary theme in foot is considered dark, and the alternative theme
|
||||
light.
|
||||
|
||||
|
||||
# OSC
|
||||
|
|
@ -598,8 +678,10 @@ All _OSC_ sequences begin with *\\E]*, sometimes abbreviated _OSC_.
|
|||
:< *Description*
|
||||
| \\E] 0 ; _Pt_ \\E\\
|
||||
: xterm
|
||||
: Set window icon and title to _Pt_ (foot does not support setting the
|
||||
icon)
|
||||
: Set window icon and title to _Pt_.
|
||||
| \\E] 1 ; _Pt_ \\E\\
|
||||
: xterm
|
||||
: Set window icon to _Pt_.
|
||||
| \\E] 2 ; _Pt_ \\E\\
|
||||
: xterm
|
||||
: Set window title to _Pt_
|
||||
|
|
@ -657,6 +739,13 @@ All _OSC_ sequences begin with *\\E]*, sometimes abbreviated _OSC_.
|
|||
: Copy _Pd_ (base64 encoded text) to the clipboard. _Pc_ denotes the
|
||||
target: *c* targets the clipboard and *s* and *p* the primary
|
||||
selection.
|
||||
| \\E] 66 ; _params_ ; text \\E\\
|
||||
: kitty
|
||||
: Text sizing protocol (only 'w', width, supported)
|
||||
| \\E] 99 ; _params_ ; _payload_ \\E\\
|
||||
: kitty
|
||||
: Desktop notification; uses *desktop-notifications.command* in
|
||||
*foot.ini*(5).
|
||||
| \\E] 104 ; _c_ \\E\\
|
||||
: xterm
|
||||
: Reset color number _c_ (multiple semicolon separated _c_ values may
|
||||
|
|
@ -680,12 +769,24 @@ All _OSC_ sequences begin with *\\E]*, sometimes abbreviated _OSC_.
|
|||
| \\E] 133 ; A \\E\\
|
||||
: FinalTerm
|
||||
: Mark start of shell prompt
|
||||
| \\E] 133 ; C \\E\\
|
||||
: FinalTerm
|
||||
: Mark start of command output
|
||||
| \\E] 133 ; D \\E\\
|
||||
: FinalTerm
|
||||
: Mark end of command output
|
||||
| \\E] 176 ; _app-id_ \\E\\
|
||||
: foot
|
||||
: Set app ID. _app-id_ is optional; if assigned,
|
||||
the terminal window App ID will be set to the value.
|
||||
An empty App ID resets the value to the default.
|
||||
| \\E] 555 \\E\\
|
||||
: foot
|
||||
: Flash the entire terminal (foot extension)
|
||||
| \\E] 777;notify;_title_;_msg_ \\E\\
|
||||
: urxvt
|
||||
: Desktop notification, uses *notify* in *foot.ini*(5).
|
||||
: Desktop notification, uses *desktop-notifications.command* in
|
||||
*foot.ini*(5).
|
||||
|
||||
# DCS
|
||||
|
||||
|
|
@ -696,7 +797,7 @@ and are terminated by *\\E\\* (ST).
|
|||
:< *Description*
|
||||
| \\EP q <sixel data> \\E\\
|
||||
: Emit a sixel image at the current cursor position
|
||||
| \\P $ q <query> \\E\\
|
||||
| \\EP $ q <query> \\E\\
|
||||
: Request selection or setting (DECRQSS). Implemented queries:
|
||||
DECSTBM, SGR and DECSCUSR.
|
||||
| \\EP = _C_ s \\E\\
|
||||
|
|
|
|||
215
doc/foot.1.scd
215
doc/foot.1.scd
|
|
@ -20,13 +20,16 @@ will start a new terminal window with your default shell.
|
|||
You can override the default shell by appending a custom command to
|
||||
the foot command line
|
||||
|
||||
*foot sh -c "echo hello world && sleep 5"*
|
||||
*foot htop*
|
||||
|
||||
# OPTIONS
|
||||
|
||||
*-c*,*--config*=_PATH_
|
||||
Path to configuration file, see *foot.ini*(5) for details.
|
||||
|
||||
The configuration file is automatically passed to new terminals
|
||||
spawned via *spawn-terminal* (see *foot.ini*(5)).
|
||||
|
||||
*-C*,*--check-config*
|
||||
Verify configuration and then exit with 0 if ok, otherwise exit
|
||||
with 230 (see *EXIT STATUS*).
|
||||
|
|
@ -65,7 +68,12 @@ the foot command line
|
|||
|
||||
*-a*,*--app-id*=_ID_
|
||||
Value to set the *app-id* property on the Wayland window
|
||||
to. Default: _foot_.
|
||||
to. Default: _foot_ (normal mode), or _footclient_ (server mode).
|
||||
|
||||
*toplevel-tag*=_TAG_
|
||||
Value to set the *toplevel-tag* property on the Wayland window
|
||||
to. The compositor can use this value for session management,
|
||||
window rules etc. Default: _not set_
|
||||
|
||||
*-m*,*--maximized*
|
||||
Start in maximized mode. If both *--maximized* and *--fullscreen*
|
||||
|
|
@ -78,6 +86,13 @@ the foot command line
|
|||
*-L*,*--login-shell*
|
||||
Start a login shell, by prepending a '-' to argv[0].
|
||||
|
||||
*--pty*
|
||||
Display an existing pty instead of creating one. This is useful
|
||||
for interacting with VM consoles.
|
||||
|
||||
This option is not currently supported in combination with
|
||||
*-s*,*--server*.
|
||||
|
||||
*-D*,*--working-directory*=_DIR_
|
||||
Initial working directory for the client application. Default:
|
||||
_CWD of foot_.
|
||||
|
|
@ -121,14 +136,14 @@ the foot command line
|
|||
of a socket provided by a supervision daemon (such as systemd or s6), and
|
||||
use that socket as it's own.
|
||||
|
||||
Two systemd units (foot-server@.{service,socket}) are provided to use that
|
||||
feature with systemd. They need to be instantiated with the value of
|
||||
$WAYLAND_DISPLAY (multiples instances can co-exists).
|
||||
Two systemd units (foot-server.{service,socket}) are provided to use that
|
||||
feature with systemd. To use socket activation, only enable the
|
||||
socket unit.
|
||||
|
||||
Note that starting *foot --server* as a systemd service will use
|
||||
the environment of the systemd user instance; thus, if you need specific
|
||||
environment variables, you'll need to import them using *systemctl --user
|
||||
import-environment* or use a drop-in for the foot-server service.
|
||||
the environment of the systemd user instance; thus, you'll need
|
||||
to import *$WAYLAND_DISPLAY* in it using *systemctl --user
|
||||
import-environment WAYLAND_DISPLAY*.
|
||||
|
||||
*-H*,*--hold*
|
||||
Remain open after child process exits.
|
||||
|
|
@ -183,16 +198,16 @@ default) available; see *foot.ini*(5).
|
|||
Paste from _clipboard_
|
||||
|
||||
*shift*+*insert*
|
||||
Paste from the _primary selection_.
|
||||
Paste from the _primary selection_
|
||||
|
||||
*ctrl*+*shift*+*r*
|
||||
Start a scrollback search
|
||||
|
||||
*ctrl*+*+*, *ctrl*+*=*
|
||||
Increase font size by 0.5pt
|
||||
Increase font size
|
||||
|
||||
*ctrl*+*-*
|
||||
Decrease font size by 0.5pt
|
||||
Decrease font size
|
||||
|
||||
*ctrl*+*0*
|
||||
Reset font size
|
||||
|
|
@ -202,9 +217,12 @@ default) available; see *foot.ini*(5).
|
|||
_OSC 7_ escape sequence, the new terminal will start in the
|
||||
current working directory.
|
||||
|
||||
*ctrl*+*shift*+*u*
|
||||
*ctrl*+*shift*+*o*
|
||||
Activate URL mode, allowing you to "launch" URLs.
|
||||
|
||||
*ctrl*+*shift*+*u*
|
||||
Activate Unicode input.
|
||||
|
||||
*ctrl*+*shift*+*z*
|
||||
Jump to the previous, currently not visible, prompt. Requires
|
||||
shell integration.
|
||||
|
|
@ -214,6 +232,8 @@ default) available; see *foot.ini*(5).
|
|||
|
||||
## SCROLLBACK SEARCH
|
||||
|
||||
These keyboard shortcuts affect the search selection:
|
||||
|
||||
*ctrl*+*r*
|
||||
Search _backward_ for the next match. If the search string is
|
||||
empty, the last searched-for string is used.
|
||||
|
|
@ -222,7 +242,13 @@ default) available; see *foot.ini*(5).
|
|||
Search _forward_ for the next match. If the search string is
|
||||
empty, the last searched-for string is used.
|
||||
|
||||
*ctrl*+*w*
|
||||
*shift*+*right*
|
||||
Extend current selection to the right by one character.
|
||||
|
||||
*shift*+*left*
|
||||
Extend current selection to the left by one character.
|
||||
|
||||
*ctrl*+*w*, *ctrl*+*shift*+*right*
|
||||
Extend current selection (and thus the search criteria) to the end
|
||||
of the word, or the next word if currently at a word separating
|
||||
character.
|
||||
|
|
@ -231,6 +257,15 @@ default) available; see *foot.ini*(5).
|
|||
Same as *ctrl*+*w*, except that the only word separating
|
||||
characters are whitespace characters.
|
||||
|
||||
*ctrl*+*shift*+*left*
|
||||
Extend current selection to the left to the last word boundary.
|
||||
|
||||
*shift*+*down*
|
||||
Extend current selection down one line
|
||||
|
||||
*shift*+*up*
|
||||
Extend current selection up one line.
|
||||
|
||||
*ctrl*+*v*, *ctrl*+*shift*+*v*, *ctrl*+*y*, *XF86Paste*
|
||||
Paste from clipboard into the search buffer.
|
||||
|
||||
|
|
@ -245,6 +280,46 @@ default) available; see *foot.ini*(5).
|
|||
selection. The terminal selection is kept, allowing you to press
|
||||
*ctrl*+*shift*+*c* to copy it to the clipboard.
|
||||
|
||||
These shortcuts affect the search box in scrollback-search mode:
|
||||
|
||||
*ctrl*+*b*
|
||||
Moves the cursor in the search box one **character** to the left.
|
||||
|
||||
*ctrl*+*left*, *alt*+*b*
|
||||
Moves the cursor in the search box one **word** to the left.
|
||||
|
||||
*ctrl*+*f*
|
||||
Moves the cursor in the search box one **character** to the right.
|
||||
|
||||
*ctrl*+*right*, *alt*+*f*
|
||||
Moves the cursor in the search box one **word** to the right.
|
||||
|
||||
*Home*, *ctrl*+*a*
|
||||
Moves the cursor in the search box to the beginning of the input.
|
||||
|
||||
*End*, *ctrl*+*e*
|
||||
Moves the cursor in the search box to the end of the input.
|
||||
|
||||
*alt*+*backspace*, *ctrl*+*backspace*
|
||||
Deletes the **word before** the cursor.
|
||||
|
||||
*alt*+*delete*, *ctrl*+*delete*
|
||||
Deletes the **word after** the cursor.
|
||||
|
||||
*ctrl*+*u*
|
||||
Deletes from the cursor to the start of the input
|
||||
|
||||
*ctrl*+*k*
|
||||
Deletes from the cursor to the end of the input
|
||||
|
||||
These shortcuts affect scrolling in scrollback-search mode:
|
||||
|
||||
*shift*+*page-up*
|
||||
Scrolls up/back one page in history.
|
||||
|
||||
*shift*+*page-down*
|
||||
Scroll down/forward one page in history.
|
||||
|
||||
## URL MODE
|
||||
|
||||
*t*
|
||||
|
|
@ -270,6 +345,10 @@ default) available; see *foot.ini*(5).
|
|||
characters.
|
||||
|
||||
*left*, triple-click
|
||||
Selects the everything between enclosing quotes, or the entire row
|
||||
if not inside a quote.
|
||||
|
||||
*left*, quad-click
|
||||
Selects the entire row
|
||||
|
||||
*middle*
|
||||
|
|
@ -280,9 +359,28 @@ default) available; see *foot.ini*(5).
|
|||
selection, while hold-and-drag allows you to interactively resize
|
||||
the selection.
|
||||
|
||||
*ctrl*+*right*
|
||||
Extend the current selection, but force it to be character wise,
|
||||
rather than depending on the original selection mode.
|
||||
|
||||
*wheel*
|
||||
Scroll up/down in history
|
||||
|
||||
*ctrl*+*wheel*
|
||||
Increase/decrease font size
|
||||
|
||||
## TOUCHSCREEN
|
||||
|
||||
*tap*
|
||||
Emulates mouse left button click.
|
||||
|
||||
*drag*
|
||||
Scrolls up/down in history.
|
||||
|
||||
Holding for a while before dragging (time delay can be configured)
|
||||
emulates mouse dragging with left button held.
|
||||
|
||||
|
||||
# FONT FORMAT
|
||||
|
||||
The font is specified in FontConfig syntax. That is, a colon-separated
|
||||
|
|
@ -298,10 +396,10 @@ Foot supports URL detection. But, unlike many other terminal
|
|||
emulators, where URLs are highlighted when they are hovered and opened
|
||||
by clicking on them, foot uses a keyboard driven approach.
|
||||
|
||||
Pressing *ctrl*+*shift*+*u* enters _“URL mode”_, where all currently
|
||||
Pressing *ctrl*+*shift*+*o* enters _"Open URL mode"_, where all currently
|
||||
visible URLs are underlined, and is associated with a
|
||||
_“jump-label”_. The jump-label indicates the _key sequence_
|
||||
(e.g. *”AF”*) to use to activate the URL.
|
||||
_"jump-label"_. The jump-label indicates the _key sequence_
|
||||
(e.g. *"AF"*) to use to activate the URL.
|
||||
|
||||
The key binding can, of course, be customized, like all other key
|
||||
bindings in foot. See *show-urls-launch* and *show-urls-copy* in
|
||||
|
|
@ -383,7 +481,7 @@ For more information, see *foot.ini*(5).
|
|||
|
||||
New foot terminal instances (bound to *ctrl*+*shift*+*n* by default)
|
||||
will open in the current working directory, if the shell in the
|
||||
“parent” terminal reports directory changes.
|
||||
"parent" terminal reports directory changes.
|
||||
|
||||
This is done with the OSC-7 escape sequence. Most shells can be
|
||||
scripted to do this, if they do not support it natively. See the wiki
|
||||
|
|
@ -409,6 +507,38 @@ See the wiki
|
|||
(https://codeberg.org/dnkl/foot/wiki#user-content-jumping-between-prompts)
|
||||
for details, and examples for other shells.
|
||||
|
||||
## Piping last command's output
|
||||
|
||||
The key binding *pipe-command-output* can pipe the last command's
|
||||
output to an application of your choice (similar to the other
|
||||
*pipe-\** key bindings):
|
||||
|
||||
*\[key-bindings\]++
|
||||
pipe-command-output=[sh -c "f=$(mktemp); cat - > $f; footclient emacsclient -nw $f; rm $f"] Control+Shift+g*
|
||||
|
||||
When pressing *ctrl*+*shift*+*g*, the last command's output is written
|
||||
to a temporary file, then an emacsclient is started in a new
|
||||
footclient instance. The temporary file is removed after the
|
||||
footclient instance has closed.
|
||||
|
||||
For this to work, the shell must emit an OSC-133;C (*\\E]133;C\\E\\\\*)
|
||||
sequence before command output starts, and an OSC-133;D
|
||||
(*\\E]133;D\\E\\\\*) when the command output ends.
|
||||
|
||||
In fish, one way to do this is to add _preexec_ and _postexec_ hooks:
|
||||
|
||||
*function foot_cmd_start --on-event fish_preexec
|
||||
echo -en "\\e]133;C\\e\\\\"
|
||||
end*
|
||||
|
||||
*function foot_cmd_end --on-event fish_postexec
|
||||
echo -en "\\e]133;D\\e\\\\"
|
||||
end*
|
||||
|
||||
See the wiki
|
||||
(https://codeberg.org/dnkl/foot/wiki#user-content-piping-last-commands-output)
|
||||
for details, and examples for other shells
|
||||
|
||||
# TERMINFO
|
||||
|
||||
Client applications use the terminfo identifier specified by the
|
||||
|
|
@ -449,10 +579,10 @@ also implemented (and extended, to some degree) by Kitty.
|
|||
|
||||
It allows querying the terminal for terminfo classic, file-based,
|
||||
terminfo definition. For example, if all applications used this
|
||||
feature, you would no longer have to install foot’s terminfo on remote
|
||||
feature, you would no longer have to install foot's terminfo on remote
|
||||
hosts you SSH into.
|
||||
|
||||
XTerm’s implementation (as of XTerm-370) only supports querying key
|
||||
XTerm's implementation (as of XTerm-370) only supports querying key
|
||||
(as in keyboard keys) capabilities, and three custom capabilities:
|
||||
|
||||
- TN - terminal name
|
||||
|
|
@ -464,7 +594,7 @@ Kitty has extended this, and also supports querying all integer and
|
|||
string capabilities.
|
||||
|
||||
Foot supports this, and extends it even further, to also include
|
||||
boolean capabilities. This means foot’s entire terminfo can be queried
|
||||
boolean capabilities. This means foot's entire terminfo can be queried
|
||||
via *XTGETTCAP*.
|
||||
|
||||
Note that both Kitty and foot handles responses to multi-capability
|
||||
|
|
@ -475,7 +605,7 @@ capability/value pairs. There are a couple of issues with this:
|
|||
|
||||
- The success/fail flag in the beginning of the response is always 1
|
||||
(success), unless the very first queried capability is invalid.
|
||||
- XTerm will not respond at all to an invalid capability, unless it’s
|
||||
- XTerm will not respond at all to an invalid capability, unless it's
|
||||
the first one in the XTGETTCAP query.
|
||||
- XTerm will end the response at the first invalid capability.
|
||||
|
||||
|
|
@ -539,27 +669,46 @@ In all other cases, the exit code is that of the client application
|
|||
set according to either the *--term* command-line option or the
|
||||
*term* config option in *foot.ini*(5).
|
||||
|
||||
*PWD*
|
||||
Current working directory (at the time of launching foot)
|
||||
|
||||
*COLORTERM*
|
||||
This variable is set to *truecolor*, to indicate to client
|
||||
applications that 24-bit RGB colors are supported.
|
||||
|
||||
*TERM_PROGRAM*
|
||||
Always set to *foot*. This can be used by client applications to
|
||||
check which terminal is in use, but with the caveat that it may
|
||||
have been inherited from a parent process in other terminals that
|
||||
aren't known to set the variable.
|
||||
*PWD*
|
||||
Current working directory (at the time of launching foot)
|
||||
|
||||
*TERM_PROGRAM_VERSION*
|
||||
Set to the foot version string, in the format _major_*.*_minor_*.*_patch_
|
||||
or _major_*.*_minor_*.*_patch_*-*_revision_*-\g*_commit_ for inter-release
|
||||
builds. The same caveat as for *TERM_PROGRAM* applies.
|
||||
*SHELL*
|
||||
Set to the launched shell, if the shell is valid (it is listed in
|
||||
*/etc/shells*).
|
||||
|
||||
In addition to the variables listed above, custom environment
|
||||
variables may be defined in *foot.ini*(5).
|
||||
|
||||
## Variables *unset* in the child process
|
||||
|
||||
*TERM_PROGRAM*
|
||||
*TERM_PROGRAM_VERSION*
|
||||
These environment variables are set by certain other terminal
|
||||
emulators. We unset them, to prevent applications from
|
||||
misdetecting foot.
|
||||
|
||||
In addition to the variables listed above, custom environment
|
||||
variables to unset may be defined in *foot.ini*(5).
|
||||
|
||||
# Signals
|
||||
|
||||
The following signals have special meaning in foot:
|
||||
|
||||
- SIGUSR1: switch to the dark color theme (*[colors-dark]*).
|
||||
- SIGUSR2: switch to the light color theme (*[colors-light]*).
|
||||
|
||||
Note: you can send SIGUSR1/SIGUSR2 to a *foot --server* process too,
|
||||
in which case all client instances will switch theme. Furthermore, all
|
||||
future client instances will also use the selected theme.
|
||||
|
||||
You can also send SIGUSR1/SIGUSR2 to a footclient instance, see
|
||||
*footclient*(1) for details.
|
||||
|
||||
|
||||
# BUGS
|
||||
|
||||
Please report bugs to https://codeberg.org/dnkl/foot/issues
|
||||
|
|
|
|||
1151
doc/foot.ini.5.scd
1151
doc/foot.ini.5.scd
File diff suppressed because it is too large
Load diff
|
|
@ -31,7 +31,12 @@ terminal has terminated.
|
|||
|
||||
*-a*,*--app-id*=_ID_
|
||||
Value to set the *app-id* property on the Wayland window
|
||||
to. Default: _foot_.
|
||||
to. Default: _foot_ (normal mode), or _footclient_ (server mode).
|
||||
|
||||
*toplevel-tag*=_TAG_
|
||||
Value to set the *toplevel-tag* property on the Wayland window
|
||||
to. The compositor can use this value for session management,
|
||||
window rules etc. Default: _not set_
|
||||
|
||||
*-w*,*--window-size-pixels*=_WIDTHxHEIGHT_
|
||||
Set initial window width and height, in pixels. Default: _700x500_.
|
||||
|
|
@ -73,6 +78,11 @@ terminal has terminated.
|
|||
The child process in the new terminal instance will use
|
||||
footclient's environment, instead of the server's.
|
||||
|
||||
Environment variables listed in the *Variables set in the child
|
||||
process* section will be overwritten by the foot server. For
|
||||
example, the new terminal will use *TERM* from the configuration,
|
||||
not footclient's environment.
|
||||
|
||||
*-d*,*--log-level*={*info*,*warning*,*error*,*none*}
|
||||
Log level, used both for log output on stderr as well as
|
||||
syslog. Default: _warning_.
|
||||
|
|
@ -146,6 +156,11 @@ terminfo entries manually, by copying *foot* and *foot-direct* to
|
|||
Used to construct the default _PATH_ for the *--server-socket*
|
||||
option, when no explicit argument is given (see above).
|
||||
|
||||
If the socket at default _PATH_ does not exist, *footclient* will
|
||||
fallback to the less specific path, with the following priority:
|
||||
*$XDG\_RUNTIME\_DIR/foot-$WAYLAND\_DISPLAY.sock*,
|
||||
*$XDG\_RUNTIME\_DIR/foot.sock*, */tmp/foot.sock*.
|
||||
|
||||
## Variables set in the child process
|
||||
|
||||
*TERM*
|
||||
|
|
@ -158,20 +173,42 @@ terminfo entries manually, by copying *foot* and *foot-direct* to
|
|||
This variable is set to *truecolor*, to indicate to client
|
||||
applications that 24-bit RGB colors are supported.
|
||||
|
||||
*TERM_PROGRAM*
|
||||
Always set to *foot*. This can be used by client applications to
|
||||
check which terminal is in use, but with the caveat that it may
|
||||
have been inherited from a parent process in other terminals that
|
||||
aren't known to set the variable.
|
||||
*PWD*
|
||||
Current working directory (at the time of launching foot)
|
||||
|
||||
*TERM_PROGRAM_VERSION*
|
||||
Set to the foot version string, in the format _major_*.*_minor_*.*_patch_
|
||||
or _major_*.*_minor_*.*_patch_*-*_revision_*-\g*_commit_ for inter-release
|
||||
builds. The same caveat as for *TERM_PROGRAM* applies.
|
||||
*SHELL*
|
||||
Set to the launched shell, if the shell is valid (it is listed in
|
||||
*/etc/shells*).
|
||||
|
||||
In addition to the variables listed above, custom environment
|
||||
variables may be defined in *foot.ini*(5).
|
||||
|
||||
## Variables *unset* in the child process
|
||||
|
||||
*TERM_PROGRAM*
|
||||
*TERM_PROGRAM_VERSION*
|
||||
These environment variables are set by certain other terminal
|
||||
emulators. We unset them, to prevent applications from
|
||||
misdetecting foot.
|
||||
|
||||
In addition to the variables listed above, custom environment
|
||||
variables to unset may be defined in *foot.ini*(5).
|
||||
|
||||
# Signals
|
||||
|
||||
The following signals have special meaning in footclient:
|
||||
|
||||
- SIGUSR1: switch to the dark color theme (*[colors-dark]*).
|
||||
- SIGUSR2: switch to the light color theme (*[colors-light]*).
|
||||
|
||||
When sending SIGUSR1/SIGUSR2 to a footclient instance, the theme is
|
||||
changed in that instance only. This is different from when you send
|
||||
SIGUSR1/SIGUSR2 to the server process, where all instances change the
|
||||
theme.
|
||||
|
||||
Note: for obvious reasons, this is not supported when footclient is
|
||||
started with *--no-wait*.
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
*foot*(1)
|
||||
|
|
|
|||
|
|
@ -1,17 +1,25 @@
|
|||
sh = find_program('sh', native: true)
|
||||
|
||||
scdoc_prog = find_program(scdoc.get_variable('scdoc'), native: true)
|
||||
|
||||
if utempter_path == ''
|
||||
default_utempter_value = 'not set'
|
||||
if utmp_backend != 'none'
|
||||
utmp_add_args = '@0@ $WAYLAND_DISPLAY'.format(utmp_add)
|
||||
utmp_del_args = (utmp_del_have_argument
|
||||
? '@0@ $WAYLAND_DISPLAY'.format(utmp_del)
|
||||
: '@0@'.format(utmp_del))
|
||||
utmp_path = utmp_default_helper_path
|
||||
else
|
||||
default_utempter_value = utempter_path
|
||||
utmp_add_args = '<no utmp support in foot>'
|
||||
utmp_del_args = '<no utmp support in foot>'
|
||||
utmp_path = 'none'
|
||||
endif
|
||||
|
||||
|
||||
conf_data = configuration_data(
|
||||
{
|
||||
'default_terminfo': get_option('default-terminfo'),
|
||||
'utempter': default_utempter_value,
|
||||
'utmp_backend': utmp_backend,
|
||||
'utmp_add_args': utmp_add_args,
|
||||
'utmp_del_args': utmp_del_args,
|
||||
'utmp_helper_path': utmp_path,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -33,8 +41,9 @@ foreach man_src : [{'name': 'foot', 'section' : 1},
|
|||
out,
|
||||
output: out,
|
||||
input: preprocessed,
|
||||
command: [sh, '-c', '@0@ < @INPUT@'.format(scdoc_prog.full_path())],
|
||||
command: scdoc_prog.full_path(),
|
||||
capture: true,
|
||||
feed: true,
|
||||
install: true,
|
||||
install_dir: join_paths(get_option('mandir'), 'man@0@'.format(section)))
|
||||
endforeach
|
||||
|
|
|
|||
BIN
doc/sixel-tux-foot.png
Normal file
BIN
doc/sixel-tux-foot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 291 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 117 KiB |
BIN
doc/tux-foot-ok.png
Normal file
BIN
doc/tux-foot-ok.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 394 KiB |
|
|
@ -256,8 +256,8 @@ extract_one(const struct terminal *term, const struct row *row,
|
|||
}
|
||||
}
|
||||
|
||||
xassert(next_tab_stop >= col);
|
||||
ctx->tab_spaces_left = next_tab_stop - col;
|
||||
if (next_tab_stop > col)
|
||||
ctx->tab_spaces_left = next_tab_stop - col - 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
30
fdm.c
30
fdm.c
|
|
@ -18,6 +18,18 @@
|
|||
#include "debug.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
#if !defined(SIGABBREV_NP)
|
||||
#include <stdio.h>
|
||||
|
||||
static const char *
|
||||
sigabbrev_np(int sig)
|
||||
{
|
||||
static char buf[16];
|
||||
snprintf(buf, sizeof(buf), "<%d>", sig);
|
||||
return buf;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct fd_handler {
|
||||
int fd;
|
||||
int events;
|
||||
|
|
@ -113,7 +125,8 @@ fdm_destroy(struct fdm *fdm)
|
|||
|
||||
for (int i = 0; i < SIGRTMAX; i++) {
|
||||
if (fdm->signal_handlers[i].callback != NULL)
|
||||
LOG_WARN("handler for signal %d not removed", i);
|
||||
LOG_WARN("handler for signal %d (SIG%s) not removed",
|
||||
i, sigabbrev_np(i));
|
||||
}
|
||||
|
||||
if (tll_length(fdm->hooks_low) > 0 ||
|
||||
|
|
@ -338,7 +351,8 @@ bool
|
|||
fdm_signal_add(struct fdm *fdm, int signo, fdm_signal_handler_t handler, void *data)
|
||||
{
|
||||
if (fdm->signal_handlers[signo].callback != NULL) {
|
||||
LOG_ERR("signal %d already has a handler", signo);
|
||||
LOG_ERR("signal %d (SIG%s) already has a handler",
|
||||
signo, sigabbrev_np(signo));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -347,14 +361,16 @@ fdm_signal_add(struct fdm *fdm, int signo, fdm_signal_handler_t handler, void *d
|
|||
sigaddset(&mask, signo);
|
||||
|
||||
if (sigprocmask(SIG_BLOCK, &mask, &original) < 0) {
|
||||
LOG_ERRNO("failed to block signal %d", signo);
|
||||
LOG_ERRNO("failed to block signal %d (SIG%s)",
|
||||
signo, sigabbrev_np(signo));
|
||||
return false;
|
||||
}
|
||||
|
||||
struct sigaction action = {.sa_handler = &signal_handler};
|
||||
sigemptyset(&action.sa_mask);
|
||||
if (sigaction(signo, &action, NULL) < 0) {
|
||||
LOG_ERRNO("failed to set signal handler for signal %d", signo);
|
||||
LOG_ERRNO("failed to set signal handler for signal %d (SIG%s)",
|
||||
signo, sigabbrev_np(signo));
|
||||
sigprocmask(SIG_SETMASK, &original, NULL);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -374,7 +390,8 @@ fdm_signal_del(struct fdm *fdm, int signo)
|
|||
struct sigaction action = {.sa_handler = SIG_DFL};
|
||||
sigemptyset(&action.sa_mask);
|
||||
if (sigaction(signo, &action, NULL) < 0) {
|
||||
LOG_ERRNO("failed to restore signal handler for signal %d", signo);
|
||||
LOG_ERRNO("failed to restore signal handler for signal %d (SIG%s)",
|
||||
signo, sigabbrev_np(signo));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -386,7 +403,8 @@ fdm_signal_del(struct fdm *fdm, int signo)
|
|||
sigemptyset(&mask);
|
||||
sigaddset(&mask, signo);
|
||||
if (sigprocmask(SIG_UNBLOCK, &mask, NULL) < 0) {
|
||||
LOG_ERRNO("failed to unblock signal %d", signo);
|
||||
LOG_ERRNO("failed to unblock signal %d (SIG%s)",
|
||||
signo, sigabbrev_np(signo));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
42
foot-features.c
Normal file
42
foot-features.c
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#include "foot-features.h"
|
||||
#include "version.h"
|
||||
|
||||
const char version_and_features[] =
|
||||
"version: " FOOT_VERSION
|
||||
|
||||
#if defined(FOOT_PGO_ENABLED) && FOOT_PGO_ENABLED
|
||||
" +pgo"
|
||||
#else
|
||||
" -pgo"
|
||||
#endif
|
||||
|
||||
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
||||
" +ime"
|
||||
#else
|
||||
" -ime"
|
||||
#endif
|
||||
|
||||
#if defined(FOOT_GRAPHEME_CLUSTERING) && FOOT_GRAPHEME_CLUSTERING
|
||||
" +graphemes"
|
||||
#else
|
||||
" -graphemes"
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_XDG_TOPLEVEL_TAG)
|
||||
" +toplevel-tag"
|
||||
#else
|
||||
" -toplevel-tag"
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_EXT_BACKGROUND_EFFECT)
|
||||
" +blur"
|
||||
#else
|
||||
" -blur"
|
||||
#endif
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
" +assertions"
|
||||
#else
|
||||
" -assertions"
|
||||
#endif
|
||||
;
|
||||
|
|
@ -1,39 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static inline bool feature_assertions(void)
|
||||
{
|
||||
#if defined(NDEBUG)
|
||||
return false;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
extern const char version_and_features[];
|
||||
|
||||
static inline bool feature_ime(void)
|
||||
static inline void
|
||||
print_version_and_features(const char *prefix)
|
||||
{
|
||||
#if defined(FOOT_IME_ENABLED) && FOOT_IME_ENABLED
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline bool feature_pgo(void)
|
||||
{
|
||||
#if defined(FOOT_PGO_ENABLED) && FOOT_PGO_ENABLED
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline bool feature_graphemes(void)
|
||||
{
|
||||
#if defined(FOOT_GRAPHEME_CLUSTERING) && FOOT_GRAPHEME_CLUSTERING
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
fputs(prefix, stdout);
|
||||
fputs(version_and_features, stdout);
|
||||
fputc('\n', stdout);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
[Service]
|
||||
ExecStart=@bindir@/foot --server=3
|
||||
Environment=WAYLAND_DISPLAY=%i
|
||||
UnsetEnvironment=LISTEN_PID LISTEN_FDS LISTEN_FDNAMES
|
||||
NonBlocking=true
|
||||
|
||||
[Unit]
|
||||
Requires=%N.socket
|
||||
Description=Foot terminal server mode for WAYLAND_DISPLAY=%i
|
||||
Description=Foot terminal server mode
|
||||
Documentation=man:foot(1)
|
||||
PartOf=graphical-session.target
|
||||
After=graphical-session.target
|
||||
ConditionEnvironment=WAYLAND_DISPLAY
|
||||
|
||||
[Install]
|
||||
WantedBy=wayland-instance@.target
|
||||
WantedBy=graphical-session.target
|
||||
10
foot-server.socket
Normal file
10
foot-server.socket
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
[Socket]
|
||||
ListenStream=%t/foot.sock
|
||||
|
||||
[Unit]
|
||||
PartOf=graphical-session.target
|
||||
After=graphical-session.target
|
||||
ConditionEnvironment=WAYLAND_DISPLAY
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
[Socket]
|
||||
ListenStream=%t/foot-%i.sock
|
||||
|
||||
[Install]
|
||||
WantedBy=wayland-instance@.target
|
||||
17
foot.info
17
foot.info
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
@default_terminfo@+base|foot base fragment,
|
||||
AX,
|
||||
Su,
|
||||
Tc,
|
||||
XF,
|
||||
XT,
|
||||
|
|
@ -38,9 +39,13 @@
|
|||
PE=\E[201~,
|
||||
PS=\E[200~,
|
||||
RV=\E[>c,
|
||||
Rect=\E[%p1%d;%p2%d;%p3%d;%p4%d;%p5%d$x,
|
||||
Se=\E[ q,
|
||||
Setulc=\E[58\:2\:\:%p1%{65536}%/%d\:%p1%{256}%/%{255}%&%d\:%p1%{255}%&%d%;m,
|
||||
Smulx=\E[4:%p1%dm,
|
||||
Ss=\E[%p1%d q,
|
||||
Sync=\E[?2026%?%p1%{1}%-%tl%eh,
|
||||
Sync=\E[?2026%?%p1%{1}%-%tl%eh%;,
|
||||
TS=\E]2;,
|
||||
XM=\E[?1006;1000%?%p1%{1}%=%th%el%;,
|
||||
XR=\E[>0q,
|
||||
acsc=``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
|
||||
|
|
@ -73,6 +78,8 @@
|
|||
ed=\E[J,
|
||||
el1=\E[1K,
|
||||
el=\E[K,
|
||||
fd=\E[?1004l,
|
||||
fe=\E[?1004h,
|
||||
flash=\E]555\E\\,
|
||||
fsl=\E\\,
|
||||
home=\E[H,
|
||||
|
|
@ -227,6 +234,7 @@
|
|||
kri=\E[1;2A,
|
||||
kxIN=\E[I,
|
||||
kxOUT=\E[O,
|
||||
nel=\EE,
|
||||
oc=\E]104\E\\,
|
||||
op=\E[39;49m,
|
||||
rc=\E8,
|
||||
|
|
@ -240,13 +248,15 @@
|
|||
rmcup=\E[?1049l\E[23;0;0t,
|
||||
rmir=\E[4l,
|
||||
rmkx=\E[?1l\E>,
|
||||
rmm=\E[?1036h\E[?1034l,
|
||||
rmso=\E[27m,
|
||||
rmul=\E[24m,
|
||||
rmxx=\E[29m,
|
||||
rs1=\Ec,
|
||||
rs2=\E[!p\E[4l\E>,
|
||||
rv=\E\\[[0-9]+;[0-9]+;[0-9]+c,
|
||||
rv=\E\\[>1;[0-9][0-9][0-9][0-9][0-9][0-9];0c,
|
||||
sc=\E7,
|
||||
setal=\E[58\:2\:\:%p1%{65536}%/%d\:%p1%{256}%/%{255}%&%d\:%p1%{255}%&%d%;m,
|
||||
setrgbb=\E[48\:2\:\:%p1%d\:%p2%d\:%p3%dm,
|
||||
setrgbf=\E[38\:2\:\:%p1%d\:%p2%d\:%p3%dm,
|
||||
sgr0=\E(B\E[m,
|
||||
|
|
@ -257,6 +267,7 @@
|
|||
smcup=\E[?1049h\E[22;0;0t,
|
||||
smir=\E[4h,
|
||||
smkx=\E[?1h\E=,
|
||||
smm=\E[?1036l\E[?1034h,
|
||||
smso=\E[7m,
|
||||
smul=\E[4m,
|
||||
smxx=\E[9m,
|
||||
|
|
@ -268,7 +279,7 @@
|
|||
u9=\E[c,
|
||||
vpa=\E[%i%p1%dd,
|
||||
xm=\E[<%i%p3%d;%p1%d;%p2%d;%?%p4%tM%em%;,
|
||||
xr=\EP>\\|[ -~]+\E\\\\,
|
||||
xr=\EP>\\|foot\\([0-9]+\\.[0-9]+\\.[0-9]+(-[0-9]+-g[a-f[0-9]+)?\\)?\E\\\\,
|
||||
|
||||
# XT,
|
||||
# AX,
|
||||
|
|
|
|||
160
foot.ini
160
foot.ini
|
|
@ -4,7 +4,7 @@
|
|||
# term=foot (or xterm-256color if built with -Dterminfo=disabled)
|
||||
# login-shell=no
|
||||
|
||||
# app-id=foot
|
||||
# app-id=foot # globally set wayland app-id. Default values are "foot" and "footclient" for desktop and server mode
|
||||
# title=foot
|
||||
# locked-title=no
|
||||
|
||||
|
|
@ -19,32 +19,50 @@
|
|||
# vertical-letter-offset=0
|
||||
# underline-offset=<font metrics>
|
||||
# underline-thickness=<font underline thickness>
|
||||
# strikeout-thickness=<font strikeout thickness>
|
||||
# box-drawings-uses-font-glyphs=no
|
||||
# dpi-aware=auto
|
||||
# dpi-aware=no
|
||||
# gamma-correct-blending=no
|
||||
|
||||
# initial-color-theme=dark
|
||||
# initial-window-size-pixels=700x500 # Or,
|
||||
# initial-window-size-chars=<COLSxROWS>
|
||||
# initial-window-mode=windowed
|
||||
# pad=0x0 # optionally append 'center'
|
||||
# pad=0x0 center-when-maximized-and-fullscreen
|
||||
# resize-by-cells=yes
|
||||
# resize-keep-grid=yes
|
||||
# resize-delay-ms=100
|
||||
|
||||
# notify=notify-send -a ${app-id} -i ${app-id} ${title} ${body}
|
||||
|
||||
# bold-text-in-bright=no
|
||||
# word-delimiters=,│`|:"'()[]{}<>
|
||||
# selection-target=primary
|
||||
# workers=<number of logical CPUs>
|
||||
# utempter=/usr/lib/utempter/utempter
|
||||
# utmp-helper=/usr/lib/utempter/utempter # When utmp backend is ‘libutempter’ (Linux)
|
||||
# utmp-helper=/usr/libexec/ulog-helper # When utmp backend is ‘ulog’ (FreeBSD)
|
||||
|
||||
# uppercase-regex-insert=yes
|
||||
|
||||
[environment]
|
||||
# name=value
|
||||
|
||||
[security]
|
||||
# osc52=enabled # disabled|copy-enabled|paste-enabled|enabled
|
||||
|
||||
[bell]
|
||||
# system=yes
|
||||
# urgent=no
|
||||
# notify=no
|
||||
# visual=no
|
||||
# command=
|
||||
# command-focused=no
|
||||
|
||||
[desktop-notifications]
|
||||
# command=notify-send --wait --app-name ${app-id} --icon ${app-id} --category ${category} --urgency ${urgency} --expire-time ${expire-time} --hint STRING:image-path:${icon} --hint BOOLEAN:suppress-sound:${muted} --hint STRING:sound-name:${sound-name} --replace-id ${replace-id} ${action-argument} --print-id -- ${title} ${body}
|
||||
# command-action-argument=--action ${action-name}=${action-label}
|
||||
# close=""
|
||||
# inhibit-when-focused=yes
|
||||
|
||||
|
||||
[scrollback]
|
||||
# lines=1000
|
||||
# multiplier=3.0
|
||||
|
|
@ -55,13 +73,24 @@
|
|||
# launch=xdg-open ${url}
|
||||
# label-letters=sadfjklewcmpgh
|
||||
# osc8-underline=url-mode
|
||||
# protocols=http, https, ftp, ftps, file, gemini, gopher
|
||||
# uri-characters=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.,~:;/?#@!$&%*+="'()[]
|
||||
# regex=(((https?://|mailto:|ftp://|file:|ssh:|ssh://|git://|tel:|magnet:|ipfs://|ipns://|gemini://|gopher://|news:)|www\.)([0-9a-zA-Z:/?#@!$&*+,;=.~_%^\-]+|\([]\["0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*\)|\[[\(\)"0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*\]|"[]\[\(\)0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*"|'[]\[\(\)0-9a-zA-Z:/?#@!$&*+,;=.~_%^\-]*')+([0-9a-zA-Z/#@$&*+=~_%^\-]|\([]\["0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*\)|\[[\(\)"0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*\]|"[]\[\(\)0-9a-zA-Z:/?#@!$&'*+,;=.~_%^\-]*"|'[]\[\(\)0-9a-zA-Z:/?#@!$&*+,;=.~_%^\-]*'))
|
||||
|
||||
# You can define your own regex's, by adding a section called
|
||||
# 'regex:<ID>' with a 'regex' and 'launch' key. These can then be tied
|
||||
# to a key-binding. See foot.ini(5) for details
|
||||
|
||||
# [regex:your-fancy-name]
|
||||
# regex=<a POSIX-Extended Regular Expression>
|
||||
# launch=<path to script or application> ${match}
|
||||
#
|
||||
# [key-bindings]
|
||||
# regex-launch=[your-fancy-name] Control+Shift+q
|
||||
# regex-copy=[your-fancy-name] Control+Alt+Shift+q
|
||||
|
||||
[cursor]
|
||||
# style=block
|
||||
# color=<inverse foreground/background>
|
||||
# blink=no
|
||||
# blink-rate=500
|
||||
# beam-thickness=1.5
|
||||
# underline-thickness=<font underline thickness>
|
||||
|
||||
|
|
@ -69,32 +98,41 @@
|
|||
# hide-when-typing=no
|
||||
# alternate-scroll-mode=yes
|
||||
|
||||
[colors]
|
||||
[touch]
|
||||
# long-press-delay=400
|
||||
|
||||
[colors-dark]
|
||||
# alpha=1.0
|
||||
# background=002b36
|
||||
# foreground=839496
|
||||
# alpha-mode=default # Can be `default`, `matching` or `all`
|
||||
# background=242424
|
||||
# foreground=ffffff
|
||||
# flash=7f7f00
|
||||
# flash-alpha=0.5
|
||||
|
||||
# cursor=<inverse foreground/background>
|
||||
|
||||
## Normal/regular colors (color palette 0-7)
|
||||
# regular0=073642 # black
|
||||
# regular1=dc322f # red
|
||||
# regular2=859900 # green
|
||||
# regular3=b58900 # yellow
|
||||
# regular4=268bd2 # blue
|
||||
# regular5=d33682 # magenta
|
||||
# regular6=2aa198 # cyan
|
||||
# regular7=eee8d5 # white
|
||||
# regular0=242424 # black
|
||||
# regular1=f62b5a # red
|
||||
# regular2=47b413 # green
|
||||
# regular3=e3c401 # yellow
|
||||
# regular4=24acd4 # blue
|
||||
# regular5=f2affd # magenta
|
||||
# regular6=13c299 # cyan
|
||||
# regular7=e6e6e6 # white
|
||||
|
||||
## Bright colors (color palette 8-15)
|
||||
# bright0=08404f # bright black
|
||||
# bright1=e35f5c # bright red
|
||||
# bright2=9fb700 # bright green
|
||||
# bright3=d9a400 # bright yellow
|
||||
# bright4=4ba1de # bright blue
|
||||
# bright5=dc619d # bright magenta
|
||||
# bright6=32c1b6 # bright cyan
|
||||
# bright0=616161 # bright black
|
||||
# bright1=ff4d51 # bright red
|
||||
# bright2=35d450 # bright green
|
||||
# bright3=e9e836 # bright yellow
|
||||
# bright4=5dc5f8 # bright blue
|
||||
# bright5=feabf2 # bright magenta
|
||||
# bright6=24dfc4 # bright cyan
|
||||
# bright7=ffffff # bright white
|
||||
|
||||
## dimmed colors (see foot.ini(5) man page)
|
||||
# dim-blend-towards=black
|
||||
# dim0=<not set>
|
||||
# ...
|
||||
# dim7=<not-set>
|
||||
|
|
@ -104,6 +142,24 @@
|
|||
# ...
|
||||
# 255 = <256-color palette #255>
|
||||
|
||||
## Sixel colors
|
||||
# sixel0 = 000000
|
||||
# sixel1 = 3333cc
|
||||
# sixel2 = cc2121
|
||||
# sixel3 = 33cc33
|
||||
# sixel4 = cc33cc
|
||||
# sixel5 = 33cccc
|
||||
# sixel6 = cccc33
|
||||
# sixel7 = 878787
|
||||
# sixel8 = 424242
|
||||
# sixel9 = 545499
|
||||
# sixel10 = 994242
|
||||
# sixel11 = 549954
|
||||
# sixel12 = 995499
|
||||
# sixel13 = 549999
|
||||
# sixel14 = 999954
|
||||
# sixel15 = cccccc
|
||||
|
||||
## Misc colors
|
||||
# selection-foreground=<inverse foreground/background>
|
||||
# selection-background=<inverse foreground/background>
|
||||
|
|
@ -113,12 +169,18 @@
|
|||
# search-box-match=<regular0> <regular3> # black-on-yellow
|
||||
# urls=<regular3>
|
||||
|
||||
[colors-light]
|
||||
# Alternative color theme, see man page foot.ini(5)
|
||||
# Same builtin defaults as [color], except for:
|
||||
# dim-blend-towards=white
|
||||
|
||||
[csd]
|
||||
# preferred=server
|
||||
# size=26
|
||||
# font=<primary font>
|
||||
# color=<foreground color>
|
||||
# hide-when-typing=no
|
||||
# hide-when-maximized=no
|
||||
# double-click-to-maximize=yes
|
||||
# border-width=0
|
||||
# border-color=<csd.color>
|
||||
# button-width=26
|
||||
|
|
@ -128,12 +190,14 @@
|
|||
# button-close-color=<regular1>
|
||||
|
||||
[key-bindings]
|
||||
# scrollback-up-page=Shift+Page_Up
|
||||
# scrollback-up-page=Shift+Page_Up Shift+KP_Page_Up
|
||||
# scrollback-up-half-page=none
|
||||
# scrollback-up-line=none
|
||||
# scrollback-down-page=Shift+Page_Down
|
||||
# scrollback-down-page=Shift+Page_Down Shift+KP_Page_Down
|
||||
# scrollback-down-half-page=none
|
||||
# scrollback-down-line=none
|
||||
# scrollback-home=none
|
||||
# scrollback-end=none
|
||||
# clipboard-copy=Control+Shift+c XF86Copy
|
||||
# clipboard-paste=Control+Shift+v XF86Paste
|
||||
# primary-paste=Shift+Insert
|
||||
|
|
@ -148,17 +212,22 @@
|
|||
# pipe-visible=[sh -c "xurls | fuzzel | xargs -r firefox"] none
|
||||
# pipe-scrollback=[sh -c "xurls | fuzzel | xargs -r firefox"] none
|
||||
# pipe-selected=[xargs -r firefox] none
|
||||
# show-urls-launch=Control+Shift+u
|
||||
# pipe-command-output=[wl-copy] none # Copy last command's output to the clipboard
|
||||
# show-urls-launch=Control+Shift+o
|
||||
# show-urls-copy=none
|
||||
# show-urls-persistent=none
|
||||
# prompt-prev=Control+Shift+z
|
||||
# prompt-next=Control+Shift+x
|
||||
# unicode-input=none
|
||||
# unicode-input=Control+Shift+u
|
||||
# color-theme-switch-1=none
|
||||
# color-theme-switch-2=none
|
||||
# color-theme-toggle=none
|
||||
# noop=none
|
||||
# quit=none
|
||||
|
||||
[search-bindings]
|
||||
# cancel=Control+g Control+c Escape
|
||||
# commit=Return
|
||||
# commit=Return KP_Enter
|
||||
# find-prev=Control+r
|
||||
# find-next=Control+s
|
||||
# cursor-left=Left Control+b
|
||||
|
|
@ -171,11 +240,27 @@
|
|||
# delete-prev-word=Mod1+BackSpace Control+BackSpace
|
||||
# delete-next=Delete
|
||||
# delete-next-word=Mod1+d Control+Delete
|
||||
# extend-to-word-boundary=Control+w
|
||||
# delete-to-start=Control+u
|
||||
# delete-to-end=Control+k
|
||||
# extend-char=Shift+Right
|
||||
# extend-to-word-boundary=Control+w Control+Shift+Right
|
||||
# extend-to-next-whitespace=Control+Shift+w
|
||||
# extend-line-down=Shift+Down
|
||||
# extend-backward-char=Shift+Left
|
||||
# extend-backward-to-word-boundary=Control+Shift+Left
|
||||
# extend-backward-to-next-whitespace=none
|
||||
# extend-line-up=Shift+Up
|
||||
# clipboard-paste=Control+v Control+Shift+v Control+y XF86Paste
|
||||
# primary-paste=Shift+Insert
|
||||
# unicode-input=none
|
||||
# scrollback-up-page=Shift+Page_Up Shift+KP_Page_Up
|
||||
# scrollback-up-half-page=none
|
||||
# scrollback-up-line=none
|
||||
# scrollback-down-page=Shift+Page_Down Shift+KP_Page_Down
|
||||
# scrollback-down-half-page=none
|
||||
# scrollback-down-line=none
|
||||
# scrollback-home=none
|
||||
# scrollback-end=none
|
||||
|
||||
[url-bindings]
|
||||
# cancel=Control+g Control+c Control+d Escape
|
||||
|
|
@ -185,6 +270,10 @@
|
|||
# \x03=Mod4+c # Map Super+c -> Ctrl+c
|
||||
|
||||
[mouse-bindings]
|
||||
# scrollback-up-mouse=BTN_WHEEL_BACK
|
||||
# scrollback-down-mouse=BTN_WHEEL_FORWARD
|
||||
# font-increase=Control+BTN_WHEEL_BACK
|
||||
# font-decrease=Control+BTN_WHEEL_FORWARD
|
||||
# selection-override-modifiers=Shift
|
||||
# primary-paste=BTN_MIDDLE
|
||||
# select-begin=BTN_LEFT
|
||||
|
|
@ -193,6 +282,7 @@
|
|||
# select-extend-character-wise=Control+BTN_RIGHT
|
||||
# select-word=BTN_LEFT-2
|
||||
# select-word-whitespace=Control+BTN_LEFT-2
|
||||
# select-row=BTN_LEFT-3
|
||||
# select-quote = BTN_LEFT-3
|
||||
# select-row=BTN_LEFT-4
|
||||
|
||||
# vim: ft=dosini
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ patch=$(echo "${new_version}" | sed -r 's/([0-9]+)\.([0-9]+)\.([0-9]+).*/\3/')
|
|||
extra=$(echo "${new_version}" | sed -r 's/([0-9]+)\.([0-9]+)\.([0-9]+)(-([0-9]+-g[a-z0-9]+) .*)?.*/\5/')
|
||||
|
||||
new_version="#define FOOT_VERSION \"${new_version}\"
|
||||
#define FOOT_VERSION_SHORT \"${git_version:-${default_version}}\"
|
||||
#define FOOT_MAJOR ${major}
|
||||
#define FOOT_MINOR ${minor}
|
||||
#define FOOT_PATCH ${patch}
|
||||
|
|
|
|||
38
grid.h
38
grid.h
|
|
@ -16,7 +16,7 @@ void grid_resize_without_reflow(
|
|||
int old_screen_rows, int new_screen_rows);
|
||||
|
||||
void grid_resize_and_reflow(
|
||||
struct grid *grid, int new_rows, int new_cols,
|
||||
struct grid *grid, const struct terminal *term, int new_rows, int new_cols,
|
||||
int old_screen_rows, int new_screen_rows,
|
||||
size_t tracking_points_count,
|
||||
struct coord *const _tracking_points[static tracking_points_count]);
|
||||
|
|
@ -86,13 +86,38 @@ grid_row_in_view(struct grid *grid, int row_no)
|
|||
|
||||
void grid_row_uri_range_put(
|
||||
struct row *row, int col, const char *uri, uint64_t id);
|
||||
void grid_row_uri_range_add(struct row *row, struct row_uri_range range);
|
||||
void grid_row_uri_range_erase(struct row *row, int start, int end);
|
||||
|
||||
void grid_row_underline_range_put(
|
||||
struct row *row, int col, struct underline_range_data data);
|
||||
void grid_row_underline_range_erase(struct row *row, int start, int end);
|
||||
|
||||
static inline void
|
||||
grid_row_uri_range_destroy(struct row_uri_range *range)
|
||||
grid_row_uri_range_destroy(struct row_range *range)
|
||||
{
|
||||
free(range->uri);
|
||||
free(range->uri.uri);
|
||||
}
|
||||
|
||||
static inline void
|
||||
grid_row_underline_range_destroy(struct row_range *range)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void
|
||||
grid_row_range_destroy(struct row_range *range, enum row_range_type type)
|
||||
{
|
||||
switch (type) {
|
||||
case ROW_RANGE_URI: grid_row_uri_range_destroy(range); break;
|
||||
case ROW_RANGE_UNDERLINE: grid_row_underline_range_destroy(range); break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
grid_row_ranges_destroy(struct row_ranges *ranges, enum row_range_type type)
|
||||
{
|
||||
for (int i = 0; i < ranges->count; i++) {
|
||||
grid_row_range_destroy(&ranges->v[i], type);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
|
|
@ -103,9 +128,10 @@ grid_row_reset_extra(struct row *row)
|
|||
if (likely(extra == NULL))
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < extra->uri_ranges.count; i++)
|
||||
grid_row_uri_range_destroy(&extra->uri_ranges.v[i]);
|
||||
grid_row_ranges_destroy(&extra->uri_ranges, ROW_RANGE_URI);
|
||||
grid_row_ranges_destroy(&extra->underline_ranges, ROW_RANGE_UNDERLINE);
|
||||
free(extra->uri_ranges.v);
|
||||
free(extra->underline_ranges.v);
|
||||
|
||||
free(extra);
|
||||
row->extra = NULL;
|
||||
|
|
|
|||
41
hsl.c
41
hsl.c
|
|
@ -2,41 +2,6 @@
|
|||
|
||||
#include <math.h>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
void
|
||||
rgb_to_hsl(uint32_t rgb, int *hue, int *sat, int *lum)
|
||||
{
|
||||
double r = (double)((rgb >> 16) & 0xff) / 255.;
|
||||
double g = (double)((rgb >> 8) & 0xff) / 255.;
|
||||
double b = (double)((rgb >> 0) & 0xff) / 255.;
|
||||
|
||||
double x_max = max(max(r, g), b);
|
||||
double x_min = min(min(r, g), b);
|
||||
double V = x_max;
|
||||
|
||||
double C = x_max - x_min;
|
||||
double L = (x_max + x_min) / 2.;
|
||||
|
||||
*lum = 100 * L;
|
||||
|
||||
if (C == 0.0)
|
||||
*hue = 0;
|
||||
else if (V == r)
|
||||
*hue = 60. * (0. + (g - b) / C);
|
||||
else if (V == g)
|
||||
*hue = 60. * (2. + (b - r) / C);
|
||||
else if (V == b)
|
||||
*hue = 60. * (4. + (r - g) / C);
|
||||
if (*hue < 0)
|
||||
*hue += 360;
|
||||
|
||||
double S = C == 0.0
|
||||
? 0
|
||||
: C / (1. - fabs(2. * L - 1.));
|
||||
*sat = 100 * S;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
hsl_to_rgb(int hue, int sat, int lum)
|
||||
{
|
||||
|
|
@ -83,7 +48,7 @@ hsl_to_rgb(int hue, int sat, int lum)
|
|||
b += m;
|
||||
|
||||
return (
|
||||
(int)round(r * 255.) << 16 |
|
||||
(int)round(g * 255.) << 8 |
|
||||
(int)round(b * 255.) << 0);
|
||||
(uint8_t)round(r * 255.) << 16 |
|
||||
(uint8_t)round(g * 255.) << 8 |
|
||||
(uint8_t)round(b * 255.) << 0);
|
||||
}
|
||||
|
|
|
|||
1
hsl.h
1
hsl.h
|
|
@ -2,5 +2,4 @@
|
|||
|
||||
#include <stdint.h>
|
||||
|
||||
void rgb_to_hsl(uint32_t rgb, int *hue, int *sat, int *lum);
|
||||
uint32_t hsl_to_rgb(int hue, int sat, int lum);
|
||||
|
|
|
|||
12
ime.c
12
ime.c
|
|
@ -68,6 +68,16 @@ enter(void *data, struct zwp_text_input_v3 *zwp_text_input_v3,
|
|||
|
||||
/* The main grid is the *only* input-receiving surface we have */
|
||||
seat->ime_focus = term;
|
||||
|
||||
const struct coord *cursor = &term->grid->cursor.point;
|
||||
|
||||
term_ime_set_cursor_rect(
|
||||
term,
|
||||
term->margins.left + cursor->col * term->cell_width,
|
||||
term->margins.top + cursor->row * term->cell_height,
|
||||
term->cell_width,
|
||||
term->cell_height);
|
||||
|
||||
ime_enable(seat);
|
||||
}
|
||||
|
||||
|
|
@ -175,7 +185,7 @@ done(void *data, struct zwp_text_input_v3 *zwp_text_input_v3,
|
|||
}
|
||||
|
||||
/*
|
||||
* 2. Delete requested surroundin text
|
||||
* 2. Delete requested surrounding text
|
||||
*
|
||||
* We don't support deleting surrounding text. But, we also never
|
||||
* call set_surrounding_text() so hopefully we should never
|
||||
|
|
|
|||
14
input.h
14
input.h
|
|
@ -3,34 +3,38 @@
|
|||
#include <stdint.h>
|
||||
#include <wayland-client.h>
|
||||
|
||||
#include "wayland.h"
|
||||
#include "cursor-shape.h"
|
||||
#include "misc.h"
|
||||
#include "wayland.h"
|
||||
|
||||
/*
|
||||
* Custom defines for mouse wheel left/right buttons.
|
||||
*
|
||||
* Libinput does not define these. On Wayland, all scroll events (both
|
||||
* vertical and horizontal) are reported not as buttons, as ‘axis’
|
||||
* vertical and horizontal) are reported not as buttons, as 'axis'
|
||||
* events.
|
||||
*
|
||||
* Libinput _does_ define BTN_BACK and BTN_FORWARD, which is
|
||||
* what we use for vertical scroll events. But for horizontal scroll
|
||||
* events, there aren’t any pre-defined mouse buttons.
|
||||
* events, there aren't any pre-defined mouse buttons.
|
||||
*
|
||||
* Mouse buttons are in the range 0x110 - 0x11f, with joystick defines
|
||||
* starting at 0x120.
|
||||
*/
|
||||
#define BTN_WHEEL_BACK 0x11c
|
||||
#define BTN_WHEEL_FORWARD 0x11d
|
||||
#define BTN_WHEEL_LEFT 0x11e
|
||||
#define BTN_WHEEL_RIGHT 0x11f
|
||||
|
||||
extern const struct wl_keyboard_listener keyboard_listener;
|
||||
extern const struct wl_pointer_listener pointer_listener;
|
||||
extern const struct wl_touch_listener touch_listener;
|
||||
|
||||
void input_repeat(struct seat *seat, uint32_t key);
|
||||
|
||||
void get_current_modifiers(const struct seat *seat,
|
||||
xkb_mod_mask_t *effective,
|
||||
xkb_mod_mask_t *consumed,
|
||||
uint32_t key);
|
||||
uint32_t key, bool filter_locked);
|
||||
|
||||
const char *xcursor_for_csd_border(struct terminal *term, int x, int y);
|
||||
enum cursor_shape xcursor_for_csd_border(struct terminal *term, int x, int y);
|
||||
|
|
|
|||
141
key-binding.c
141
key-binding.c
|
|
@ -11,14 +11,24 @@
|
|||
#include "terminal.h"
|
||||
#include "util.h"
|
||||
#include "wayland.h"
|
||||
#include "xkbcommon-vmod.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
struct vmod_map {
|
||||
const char *name;
|
||||
xkb_mod_mask_t virtual_mask;
|
||||
xkb_mod_mask_t real_mask;
|
||||
};
|
||||
|
||||
struct key_set {
|
||||
struct key_binding_set public;
|
||||
|
||||
const struct config *conf;
|
||||
const struct seat *seat;
|
||||
size_t conf_ref_count;
|
||||
|
||||
/* Virtual to real modifier mappings */
|
||||
struct vmod_map vmods[8];
|
||||
};
|
||||
typedef tll(struct key_set) bind_set_list_t;
|
||||
|
||||
|
|
@ -44,6 +54,50 @@ key_binding_manager_destroy(struct key_binding_manager *mgr)
|
|||
free(mgr);
|
||||
}
|
||||
|
||||
static void
|
||||
initialize_vmod_mappings(struct key_set *set)
|
||||
{
|
||||
if (set->seat == NULL || set->seat->kbd.xkb_keymap == NULL)
|
||||
return;
|
||||
|
||||
set->vmods[0].name = XKB_VMOD_NAME_ALT;
|
||||
set->vmods[1].name = XKB_VMOD_NAME_HYPER;
|
||||
set->vmods[2].name = XKB_VMOD_NAME_LEVEL3;
|
||||
set->vmods[3].name = XKB_VMOD_NAME_LEVEL5;
|
||||
set->vmods[4].name = XKB_VMOD_NAME_META;
|
||||
set->vmods[5].name = XKB_VMOD_NAME_NUM;
|
||||
set->vmods[6].name = XKB_VMOD_NAME_SCROLL;
|
||||
set->vmods[7].name = XKB_VMOD_NAME_SUPER;
|
||||
|
||||
struct xkb_state *scratch_state = xkb_state_new(set->seat->kbd.xkb_keymap);
|
||||
xassert(scratch_state != NULL);
|
||||
|
||||
for (size_t i = 0; i < ALEN(set->vmods); i++) {
|
||||
xkb_mod_index_t virt_idx = xkb_keymap_mod_get_index(
|
||||
set->seat->kbd.xkb_keymap, set->vmods[i].name);
|
||||
|
||||
if (virt_idx != XKB_MOD_INVALID) {
|
||||
xkb_mod_mask_t vmask = 1 << virt_idx;
|
||||
xkb_state_update_mask(scratch_state, vmask, 0, 0, 0, 0, 0);
|
||||
set->vmods[i].real_mask = xkb_state_serialize_mods(
|
||||
scratch_state, XKB_STATE_MODS_DEPRESSED) & ~vmask;
|
||||
set->vmods[i].virtual_mask = vmask;
|
||||
|
||||
LOG_DBG("%s: 0x%04x -> 0x%04x",
|
||||
set->vmods[i].name,
|
||||
set->vmods[i].virtual_mask,
|
||||
set->vmods[i].real_mask);
|
||||
} else {
|
||||
set->vmods[i].virtual_mask = 0;
|
||||
set->vmods[i].real_mask = 0;
|
||||
|
||||
LOG_DBG("%s: virtual modifier not available", set->vmods[i].name);
|
||||
}
|
||||
}
|
||||
|
||||
xkb_state_unref(scratch_state);
|
||||
}
|
||||
|
||||
void
|
||||
key_binding_new_for_seat(struct key_binding_manager *mgr,
|
||||
const struct seat *seat)
|
||||
|
|
@ -67,6 +121,7 @@ key_binding_new_for_seat(struct key_binding_manager *mgr,
|
|||
};
|
||||
|
||||
tll_push_back(mgr->binding_sets, set);
|
||||
initialize_vmod_mappings(&tll_back(mgr->binding_sets));
|
||||
|
||||
LOG_DBG("new (seat): set=%p, seat=%p, conf=%p, ref-count=1",
|
||||
(void *)&tll_back(mgr->binding_sets),
|
||||
|
|
@ -107,6 +162,7 @@ key_binding_new_for_conf(struct key_binding_manager *mgr,
|
|||
};
|
||||
|
||||
tll_push_back(mgr->binding_sets, set);
|
||||
initialize_vmod_mappings(&tll_back(mgr->binding_sets));
|
||||
|
||||
load_keymap(&tll_back(mgr->binding_sets));
|
||||
|
||||
|
|
@ -243,27 +299,27 @@ maybe_repair_key_combo(const struct seat *seat,
|
|||
* modifier, and replace the shifted symbol with its unshifted
|
||||
* variant.
|
||||
*
|
||||
* For example, the combo is “Control+Shift+U”. In this case,
|
||||
* Shift is the modifier used to “shift” ‘u’ to ‘U’, after which
|
||||
* ‘Shift’ will have been “consumed”. Since we filter out consumed
|
||||
* For example, the combo is "Control+Shift+U". In this case,
|
||||
* Shift is the modifier used to "shift" 'u' to 'U', after which
|
||||
* 'Shift' will have been "consumed". Since we filter out consumed
|
||||
* modifiers when matching key combos, this key combo will never
|
||||
* trigger (we will never be able to match the ‘Shift’ modifier).
|
||||
* trigger (we will never be able to match the 'Shift' modifier).
|
||||
*
|
||||
* There are two correct variants of the above key combo:
|
||||
* - “Control+U” (upper case ‘U’)
|
||||
* - “Control+Shift+u” (lower case ‘u’)
|
||||
* - "Control+U" (upper case 'U')
|
||||
* - "Control+Shift+u" (lower case 'u')
|
||||
*
|
||||
* What we do here is, for each key *code*, check if there are any
|
||||
* (shifted) levels where it produces ‘sym’. If there are, check
|
||||
* (shifted) levels where it produces 'sym'. If there are, check
|
||||
* *which* sets of modifiers are needed to produce it, and compare
|
||||
* with ‘mods’.
|
||||
* with 'mods'.
|
||||
*
|
||||
* If there is at least one common modifier, it means ‘sym’ is a
|
||||
* “shifted” symbol, with the corresponding shifting modifier
|
||||
* If there is at least one common modifier, it means 'sym' is a
|
||||
* "shifted" symbol, with the corresponding shifting modifier
|
||||
* explicitly included in the key combo. I.e. the key combo will
|
||||
* never trigger.
|
||||
*
|
||||
* We then proceed and “repair” the key combo by replacing ‘sym’
|
||||
* We then proceed and "repair" the key combo by replacing 'sym'
|
||||
* with the corresponding unshifted symbol.
|
||||
*
|
||||
* To reduce the noise, we ignore all key codes where the shifted
|
||||
|
|
@ -283,7 +339,7 @@ maybe_repair_key_combo(const struct seat *seat,
|
|||
seat->kbd.xkb_keymap, code, layout_idx, 0, &base_syms);
|
||||
|
||||
if (base_count == 0 || sym == base_syms[0]) {
|
||||
/* No unshifted symbols, or unshifted symbol is same as ‘sym’ */
|
||||
/* No unshifted symbols, or unshifted symbol is same as 'sym' */
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -313,7 +369,7 @@ maybe_repair_key_combo(const struct seat *seat,
|
|||
seat->kbd.xkb_keymap, code, layout_idx, level_idx,
|
||||
mod_masks, ALEN(mod_masks));
|
||||
|
||||
/* Check if key combo’s modifier set intersects */
|
||||
/* Check if key combo's modifier set intersects */
|
||||
for (size_t j = 0; j < mod_mask_count; j++) {
|
||||
if ((mod_masks[j] & mods) != mod_masks[j])
|
||||
continue;
|
||||
|
|
@ -359,19 +415,19 @@ key_cmp(struct key_binding a, struct key_binding b)
|
|||
* Sort bindings such that bindings with the same symbol are
|
||||
* sorted with the binding having the most modifiers comes first.
|
||||
*
|
||||
* This fixes an issue where the “wrong” key binding are triggered
|
||||
* when used with “consumed” modifiers.
|
||||
* This fixes an issue where the "wrong" key binding are triggered
|
||||
* when used with "consumed" modifiers.
|
||||
*
|
||||
* For example: if Control+BackSpace is bound before
|
||||
* Control+Shift+BackSpace, then the latter binding is never
|
||||
* triggered.
|
||||
*
|
||||
* Why? Because Shift is a consumed modifier. This means
|
||||
* Control+BackSpace is “the same” as Control+Shift+BackSpace.
|
||||
* Control+BackSpace is "the same" as Control+Shift+BackSpace.
|
||||
*
|
||||
* By sorting bindings with more modifiers first, we work around
|
||||
* the problem. But note that it is *just* a workaround, and I’m
|
||||
* not confident there aren’t cases where it doesn’t work.
|
||||
* the problem. But note that it is *just* a workaround, and I'm
|
||||
* not confident there aren't cases where it doesn't work.
|
||||
*
|
||||
* See https://codeberg.org/dnkl/foot/issues/1280
|
||||
*/
|
||||
|
|
@ -404,6 +460,41 @@ sort_binding_list(key_binding_list_t *list)
|
|||
tll_sort(*list, key_cmp);
|
||||
}
|
||||
|
||||
static xkb_mod_mask_t
|
||||
mods_to_mask(const struct seat *seat,
|
||||
const struct vmod_map *vmods, size_t vmod_count,
|
||||
const config_modifier_list_t *mods)
|
||||
{
|
||||
xkb_mod_mask_t mask = 0;
|
||||
tll_foreach(*mods, it) {
|
||||
const xkb_mod_index_t idx = xkb_keymap_mod_get_index(seat->kbd.xkb_keymap, it->item);
|
||||
|
||||
if (idx == XKB_MOD_INVALID) {
|
||||
LOG_ERR("%s: invalid modifier name", it->item);
|
||||
continue;
|
||||
}
|
||||
|
||||
xkb_mod_mask_t mod = 1 << idx;
|
||||
|
||||
/* Check if this is a virtual modifier, and if so, use the
|
||||
real modifier it maps to instead */
|
||||
for (size_t i = 0; i < vmod_count; i++) {
|
||||
if (vmods[i].virtual_mask == mod) {
|
||||
mask |= vmods[i].real_mask;
|
||||
mod = 0;
|
||||
|
||||
LOG_DBG("%s: virtual modifier, mapped to 0x%04x",
|
||||
it->item, vmods[i].real_mask);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mask |= mod;
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
static void NOINLINE
|
||||
convert_key_binding(struct key_set *set,
|
||||
const struct config_key_binding *conf_binding,
|
||||
|
|
@ -411,7 +502,8 @@ convert_key_binding(struct key_set *set,
|
|||
{
|
||||
const struct seat *seat = set->seat;
|
||||
|
||||
xkb_mod_mask_t mods = conf_modifiers_to_mask(seat, &conf_binding->modifiers);
|
||||
xkb_mod_mask_t mods = mods_to_mask(
|
||||
seat, set->vmods, ALEN(set->vmods), &conf_binding->modifiers);
|
||||
xkb_keysym_t sym = maybe_repair_key_combo(seat, conf_binding->k.sym, mods);
|
||||
|
||||
struct key_binding binding = {
|
||||
|
|
@ -469,7 +561,7 @@ convert_mouse_binding(struct key_set *set,
|
|||
.type = MOUSE_BINDING,
|
||||
.action = conf_binding->action,
|
||||
.aux = &conf_binding->aux,
|
||||
.mods = conf_modifiers_to_mask(set->seat, &conf_binding->modifiers),
|
||||
.mods = mods_to_mask(set->seat, set->vmods, ALEN(set->vmods), &conf_binding->modifiers),
|
||||
.m = {
|
||||
.button = conf_binding->m.button,
|
||||
.count = conf_binding->m.count,
|
||||
|
|
@ -509,8 +601,9 @@ load_keymap(struct key_set *set)
|
|||
convert_url_bindings(set);
|
||||
convert_mouse_bindings(set);
|
||||
|
||||
set->public.selection_overrides = conf_modifiers_to_mask(
|
||||
set->seat, &set->conf->mouse.selection_override_modifiers);
|
||||
set->public.selection_overrides = mods_to_mask(
|
||||
set->seat, set->vmods, ALEN(set->vmods),
|
||||
&set->conf->mouse.selection_override_modifiers);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -520,8 +613,10 @@ key_binding_load_keymap(struct key_binding_manager *mgr,
|
|||
tll_foreach(mgr->binding_sets, it) {
|
||||
struct key_set *set = &it->item;
|
||||
|
||||
if (set->seat == seat)
|
||||
if (set->seat == seat) {
|
||||
initialize_vmod_mappings(set);
|
||||
load_keymap(set);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ enum bind_action_normal {
|
|||
BIND_ACTION_PIPE_SCROLLBACK,
|
||||
BIND_ACTION_PIPE_VIEW,
|
||||
BIND_ACTION_PIPE_SELECTED,
|
||||
BIND_ACTION_PIPE_COMMAND_OUTPUT,
|
||||
BIND_ACTION_SHOW_URLS_COPY,
|
||||
BIND_ACTION_SHOW_URLS_LAUNCH,
|
||||
BIND_ACTION_SHOW_URLS_PERSISTENT,
|
||||
|
|
@ -39,22 +40,41 @@ enum bind_action_normal {
|
|||
BIND_ACTION_PROMPT_PREV,
|
||||
BIND_ACTION_PROMPT_NEXT,
|
||||
BIND_ACTION_UNICODE_INPUT,
|
||||
BIND_ACTION_QUIT,
|
||||
BIND_ACTION_REGEX_LAUNCH,
|
||||
BIND_ACTION_REGEX_COPY,
|
||||
BIND_ACTION_THEME_SWITCH_1,
|
||||
BIND_ACTION_THEME_SWITCH_2,
|
||||
BIND_ACTION_THEME_SWITCH_DARK,
|
||||
BIND_ACTION_THEME_SWITCH_LIGHT,
|
||||
BIND_ACTION_THEME_TOGGLE,
|
||||
|
||||
/* Mouse specific actions - i.e. they require a mouse coordinate */
|
||||
BIND_ACTION_SCROLLBACK_UP_MOUSE,
|
||||
BIND_ACTION_SCROLLBACK_DOWN_MOUSE,
|
||||
BIND_ACTION_SELECT_BEGIN,
|
||||
BIND_ACTION_SELECT_BEGIN_BLOCK,
|
||||
BIND_ACTION_SELECT_EXTEND,
|
||||
BIND_ACTION_SELECT_EXTEND_CHAR_WISE,
|
||||
BIND_ACTION_SELECT_WORD,
|
||||
BIND_ACTION_SELECT_WORD_WS,
|
||||
BIND_ACTION_SELECT_QUOTE,
|
||||
BIND_ACTION_SELECT_ROW,
|
||||
|
||||
BIND_ACTION_KEY_COUNT = BIND_ACTION_UNICODE_INPUT + 1,
|
||||
BIND_ACTION_KEY_COUNT = BIND_ACTION_THEME_TOGGLE + 1,
|
||||
BIND_ACTION_COUNT = BIND_ACTION_SELECT_ROW + 1,
|
||||
};
|
||||
|
||||
enum bind_action_search {
|
||||
BIND_ACTION_SEARCH_NONE,
|
||||
BIND_ACTION_SEARCH_SCROLLBACK_UP_PAGE,
|
||||
BIND_ACTION_SEARCH_SCROLLBACK_UP_HALF_PAGE,
|
||||
BIND_ACTION_SEARCH_SCROLLBACK_UP_LINE,
|
||||
BIND_ACTION_SEARCH_SCROLLBACK_DOWN_PAGE,
|
||||
BIND_ACTION_SEARCH_SCROLLBACK_DOWN_HALF_PAGE,
|
||||
BIND_ACTION_SEARCH_SCROLLBACK_DOWN_LINE,
|
||||
BIND_ACTION_SEARCH_SCROLLBACK_HOME,
|
||||
BIND_ACTION_SEARCH_SCROLLBACK_END,
|
||||
BIND_ACTION_SEARCH_CANCEL,
|
||||
BIND_ACTION_SEARCH_COMMIT,
|
||||
BIND_ACTION_SEARCH_FIND_PREV,
|
||||
|
|
@ -69,8 +89,16 @@ enum bind_action_search {
|
|||
BIND_ACTION_SEARCH_DELETE_PREV_WORD,
|
||||
BIND_ACTION_SEARCH_DELETE_NEXT,
|
||||
BIND_ACTION_SEARCH_DELETE_NEXT_WORD,
|
||||
BIND_ACTION_SEARCH_DELETE_TO_START,
|
||||
BIND_ACTION_SEARCH_DELETE_TO_END,
|
||||
BIND_ACTION_SEARCH_EXTEND_CHAR,
|
||||
BIND_ACTION_SEARCH_EXTEND_WORD,
|
||||
BIND_ACTION_SEARCH_EXTEND_WORD_WS,
|
||||
BIND_ACTION_SEARCH_EXTEND_LINE_DOWN,
|
||||
BIND_ACTION_SEARCH_EXTEND_BACKWARD_CHAR,
|
||||
BIND_ACTION_SEARCH_EXTEND_BACKWARD_WORD,
|
||||
BIND_ACTION_SEARCH_EXTEND_BACKWARD_WORD_WS,
|
||||
BIND_ACTION_SEARCH_EXTEND_LINE_UP,
|
||||
BIND_ACTION_SEARCH_CLIPBOARD_PASTE,
|
||||
BIND_ACTION_SEARCH_PRIMARY_PASTE,
|
||||
BIND_ACTION_SEARCH_UNICODE_INPUT,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ struct kitty_key_data {
|
|||
|
||||
_Static_assert(sizeof(struct kitty_key_data) == 7, "bad size");
|
||||
|
||||
/* Note! *Must* Be kept sorted (on ‘sym’) */
|
||||
/* Note! *Must* Be kept sorted (on 'sym') */
|
||||
static const struct kitty_key_data kitty_keymap[] = {
|
||||
{XKB_KEY_ISO_Level3_Shift, 57453, 'u', true},
|
||||
{XKB_KEY_ISO_Level5_Shift, 57454, 'u', true},
|
||||
|
|
@ -70,7 +70,7 @@ static const struct kitty_key_data kitty_keymap[] = {
|
|||
|
||||
{XKB_KEY_F1, 1, 'P', false},
|
||||
{XKB_KEY_F2, 1, 'Q', false},
|
||||
{XKB_KEY_F3, 1, 'R', false},
|
||||
{XKB_KEY_F3, 13, '~', false},
|
||||
{XKB_KEY_F4, 1, 'S', false},
|
||||
{XKB_KEY_F5, 15, '~', false},
|
||||
{XKB_KEY_F6, 17, '~', false},
|
||||
|
|
|
|||
13
log.c
13
log.c
|
|
@ -40,7 +40,13 @@ log_init(enum log_colorize _colorize, bool _do_syslog,
|
|||
[LOG_FACILITY_DAEMON] = LOG_DAEMON,
|
||||
};
|
||||
|
||||
colorize = _colorize == LOG_COLORIZE_ALWAYS || (_colorize == LOG_COLORIZE_AUTO && isatty(STDERR_FILENO));
|
||||
/* Don't use colors if NO_COLOR is defined and not empty */
|
||||
const char *no_color_str = getenv("NO_COLOR");
|
||||
const bool no_color = no_color_str != NULL && no_color_str[0] != '\0';
|
||||
|
||||
colorize = _colorize == LOG_COLORIZE_ALWAYS
|
||||
|| (_colorize == LOG_COLORIZE_AUTO
|
||||
&& !no_color && isatty(STDERR_FILENO));
|
||||
do_syslog = _do_syslog;
|
||||
log_level = _log_level;
|
||||
|
||||
|
|
@ -105,6 +111,9 @@ _sys_log(enum log_class log_class, const char *module,
|
|||
if (!do_syslog)
|
||||
return;
|
||||
|
||||
if (log_class > log_level)
|
||||
return;
|
||||
|
||||
/* Map our log level to syslog's level */
|
||||
int level = log_level_map[log_class].syslog_equivalent;
|
||||
|
||||
|
|
@ -199,7 +208,7 @@ log_level_from_string(const char *str)
|
|||
return -1;
|
||||
|
||||
for (int i = 0, n = map_len(); i < n; i++)
|
||||
if (strcmp(str, log_level_map[i].name) == 0)
|
||||
if (streq(str, log_level_map[i].name))
|
||||
return i;
|
||||
|
||||
return -1;
|
||||
|
|
|
|||
281
main.c
281
main.c
|
|
@ -1,8 +1,8 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <stdbool.h>
|
||||
#include <limits.h>
|
||||
#include <locale.h>
|
||||
#include <getopt.h>
|
||||
#include <signal.h>
|
||||
|
|
@ -31,12 +31,9 @@
|
|||
#include "shm.h"
|
||||
#include "terminal.h"
|
||||
#include "util.h"
|
||||
#include "version.h"
|
||||
#include "xmalloc.h"
|
||||
#include "xsnprintf.h"
|
||||
|
||||
#include "char32.h"
|
||||
|
||||
#if !defined(__STDC_UTF_32__) || !__STDC_UTF_32__
|
||||
#error "char32_t does not use UTF-32"
|
||||
#endif
|
||||
|
|
@ -48,17 +45,31 @@ fdm_sigint(struct fdm *fdm, int signo, void *data)
|
|||
return true;
|
||||
}
|
||||
|
||||
static const char *
|
||||
version_and_features(void)
|
||||
struct sigusr_context {
|
||||
struct terminal *term;
|
||||
struct server *server;
|
||||
};
|
||||
|
||||
static bool
|
||||
fdm_sigusr(struct fdm *fdm, int signo, void *data)
|
||||
{
|
||||
static char buf[256];
|
||||
snprintf(buf, sizeof(buf), "version: %s %cpgo %cime %cgraphemes %cassertions",
|
||||
FOOT_VERSION,
|
||||
feature_pgo() ? '+' : '-',
|
||||
feature_ime() ? '+' : '-',
|
||||
feature_graphemes() ? '+' : '-',
|
||||
feature_assertions() ? '+' : '-');
|
||||
return buf;
|
||||
xassert(signo == SIGUSR1 || signo == SIGUSR2);
|
||||
|
||||
struct sigusr_context *ctx = data;
|
||||
|
||||
if (ctx->server != NULL) {
|
||||
if (signo == SIGUSR1)
|
||||
server_global_theme_switch_to_dark(ctx->server);
|
||||
else
|
||||
server_global_theme_switch_to_light(ctx->server);
|
||||
} else {
|
||||
if (signo == SIGUSR1)
|
||||
term_theme_switch_to_dark(ctx->term);
|
||||
else
|
||||
term_theme_switch_to_light(ctx->term);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -73,9 +84,11 @@ print_usage(const char *prog_name)
|
|||
" -t,--term=TERM value to set the environment variable TERM to (" FOOT_DEFAULT_TERM ")\n"
|
||||
" -T,--title=TITLE initial window title (foot)\n"
|
||||
" -a,--app-id=ID window application ID (foot)\n"
|
||||
" --toplevel-tag=TAG set a custom toplevel tag\n"
|
||||
" -m,--maximized start in maximized mode\n"
|
||||
" -F,--fullscreen start in fullscreen mode\n"
|
||||
" -L,--login-shell start shell as a login shell\n"
|
||||
" --pty=PATH display an existing PTY instead of creating one\n"
|
||||
" -D,--working-directory=DIR directory to start in (CWD)\n"
|
||||
" -w,--window-size-pixels=WIDTHxHEIGHT initial width and height, in pixels\n"
|
||||
" -W,--window-size-chars=WIDTHxHEIGHT initial width and height, in characters\n"
|
||||
|
|
@ -85,7 +98,7 @@ print_usage(const char *prog_name)
|
|||
" -p,--print-pid=FILE|FD print PID to file or FD (only applicable in server mode)\n"
|
||||
" -d,--log-level={info|warning|error|none} log level (warning)\n"
|
||||
" -l,--log-colorize=[{never|always|auto}] enable/disable colorization of log output on stderr\n"
|
||||
" -s,--log-no-syslog disable syslog logging (only applicable in server mode)\n"
|
||||
" -S,--log-no-syslog disable syslog logging (only applicable in server mode)\n"
|
||||
" -v,--version show the version number and quit\n"
|
||||
" -e ignored (for compatibility with xterm -e)\n";
|
||||
|
||||
|
|
@ -171,11 +184,16 @@ sanitize_signals(void)
|
|||
sigaction(i, &dfl, NULL);
|
||||
}
|
||||
|
||||
enum {
|
||||
PTY_OPTION = CHAR_MAX + 1,
|
||||
TOPLEVEL_TAG_OPTION = CHAR_MAX + 2,
|
||||
};
|
||||
|
||||
int
|
||||
main(int argc, char *const *argv)
|
||||
{
|
||||
/* Custom exit code, to enable users to differentiate between foot
|
||||
* itself failing, and the client application failiing */
|
||||
* itself failing, and the client application failing */
|
||||
static const int foot_exit_failure = -26;
|
||||
int ret = foot_exit_failure;
|
||||
|
||||
|
|
@ -198,6 +216,7 @@ main(int argc, char *const *argv)
|
|||
{"term", required_argument, NULL, 't'},
|
||||
{"title", required_argument, NULL, 'T'},
|
||||
{"app-id", required_argument, NULL, 'a'},
|
||||
{"toplevel-tag", required_argument, NULL, TOPLEVEL_TAG_OPTION},
|
||||
{"login-shell", no_argument, NULL, 'L'},
|
||||
{"working-directory", required_argument, NULL, 'D'},
|
||||
{"font", required_argument, NULL, 'f'},
|
||||
|
|
@ -208,6 +227,7 @@ main(int argc, char *const *argv)
|
|||
{"maximized", no_argument, NULL, 'm'},
|
||||
{"fullscreen", no_argument, NULL, 'F'},
|
||||
{"presentation-timings", no_argument, NULL, 'P'}, /* Undocumented */
|
||||
{"pty", required_argument, NULL, PTY_OPTION},
|
||||
{"print-pid", required_argument, NULL, 'p'},
|
||||
{"log-level", required_argument, NULL, 'd'},
|
||||
{"log-colorize", optional_argument, NULL, 'l'},
|
||||
|
|
@ -219,21 +239,12 @@ main(int argc, char *const *argv)
|
|||
|
||||
bool check_config = false;
|
||||
const char *conf_path = NULL;
|
||||
const char *conf_term = NULL;
|
||||
const char *conf_title = NULL;
|
||||
const char *conf_app_id = NULL;
|
||||
const char *custom_cwd = NULL;
|
||||
bool login_shell = false;
|
||||
tll(char *) conf_fonts = tll_init();
|
||||
enum conf_size_type conf_size_type = CONF_SIZE_PX;
|
||||
int conf_width = -1;
|
||||
int conf_height = -1;
|
||||
const char *pty_path = NULL;
|
||||
bool as_server = false;
|
||||
const char *conf_server_socket_path = NULL;
|
||||
bool presentation_timings = false;
|
||||
bool hold = false;
|
||||
bool maximized = false;
|
||||
bool fullscreen = false;
|
||||
bool unlink_pid_file = false;
|
||||
const char *pid_file = NULL;
|
||||
enum log_class log_level = LOG_CLASS_WARNING;
|
||||
|
|
@ -258,23 +269,27 @@ main(int argc, char *const *argv)
|
|||
break;
|
||||
|
||||
case 'o':
|
||||
tll_push_back(overrides, optarg);
|
||||
tll_push_back(overrides, xstrdup(optarg));
|
||||
break;
|
||||
|
||||
case 't':
|
||||
conf_term = optarg;
|
||||
tll_push_back(overrides, xstrjoin("term=", optarg));
|
||||
break;
|
||||
|
||||
case 'L':
|
||||
login_shell = true;
|
||||
tll_push_back(overrides, xstrdup("login-shell=yes"));
|
||||
break;
|
||||
|
||||
case 'T':
|
||||
conf_title = optarg;
|
||||
tll_push_back(overrides, xstrjoin("title=", optarg));
|
||||
break;
|
||||
|
||||
case 'a':
|
||||
conf_app_id = optarg;
|
||||
tll_push_back(overrides, xstrjoin("app-id=", optarg));
|
||||
break;
|
||||
|
||||
case TOPLEVEL_TAG_OPTION:
|
||||
tll_push_back(overrides, xstrjoin("toplevel-tag=", optarg));
|
||||
break;
|
||||
|
||||
case 'D': {
|
||||
|
|
@ -287,27 +302,11 @@ main(int argc, char *const *argv)
|
|||
break;
|
||||
}
|
||||
|
||||
case 'f':
|
||||
tll_free_and_free(conf_fonts, free);
|
||||
for (char *font = strtok(optarg, ","); font != NULL; font = strtok(NULL, ",")) {
|
||||
|
||||
/* Strip leading spaces */
|
||||
while (*font != '\0' && isspace(*font))
|
||||
font++;
|
||||
|
||||
/* Strip trailing spaces */
|
||||
char *end = font + strlen(font);
|
||||
xassert(*end == '\0');
|
||||
end--;
|
||||
while (end > font && isspace(*end))
|
||||
*(end--) = '\0';
|
||||
|
||||
if (strlen(font) == 0)
|
||||
continue;
|
||||
|
||||
tll_push_back(conf_fonts, font);
|
||||
}
|
||||
case 'f': {
|
||||
char *font_override = xstrjoin("font=", optarg);
|
||||
tll_push_back(overrides, font_override);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'w': {
|
||||
unsigned width, height;
|
||||
|
|
@ -316,9 +315,9 @@ main(int argc, char *const *argv)
|
|||
return ret;
|
||||
}
|
||||
|
||||
conf_size_type = CONF_SIZE_PX;
|
||||
conf_width = width;
|
||||
conf_height = height;
|
||||
tll_push_back(
|
||||
overrides, xasprintf("initial-window-size-pixels=%ux%u",
|
||||
width, height));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -329,9 +328,9 @@ main(int argc, char *const *argv)
|
|||
return ret;
|
||||
}
|
||||
|
||||
conf_size_type = CONF_SIZE_CELLS;
|
||||
conf_width = width;
|
||||
conf_height = height;
|
||||
tll_push_back(
|
||||
overrides, xasprintf("initial-window-size-chars=%ux%u",
|
||||
width, height));
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -341,6 +340,10 @@ main(int argc, char *const *argv)
|
|||
conf_server_socket_path = optarg;
|
||||
break;
|
||||
|
||||
case PTY_OPTION:
|
||||
pty_path = optarg;
|
||||
break;
|
||||
|
||||
case 'P':
|
||||
presentation_timings = true;
|
||||
break;
|
||||
|
|
@ -350,13 +353,11 @@ main(int argc, char *const *argv)
|
|||
break;
|
||||
|
||||
case 'm':
|
||||
maximized = true;
|
||||
fullscreen = false;
|
||||
tll_push_back(overrides, xstrdup("initial-window-mode=maximized"));
|
||||
break;
|
||||
|
||||
case 'F':
|
||||
fullscreen = true;
|
||||
maximized = false;
|
||||
tll_push_back(overrides, xstrdup("initial-window-mode=fullscreen"));
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
|
|
@ -378,11 +379,11 @@ main(int argc, char *const *argv)
|
|||
}
|
||||
|
||||
case 'l':
|
||||
if (optarg == NULL || strcmp(optarg, "auto") == 0)
|
||||
if (optarg == NULL || streq(optarg, "auto"))
|
||||
log_colorize = LOG_COLORIZE_AUTO;
|
||||
else if (strcmp(optarg, "never") == 0)
|
||||
else if (streq(optarg, "never"))
|
||||
log_colorize = LOG_COLORIZE_NEVER;
|
||||
else if (strcmp(optarg, "always") == 0)
|
||||
else if (streq(optarg, "always"))
|
||||
log_colorize = LOG_COLORIZE_ALWAYS;
|
||||
else {
|
||||
fprintf(stderr, "%s: argument must be one of 'never', 'always' or 'auto'\n", optarg);
|
||||
|
|
@ -395,7 +396,7 @@ main(int argc, char *const *argv)
|
|||
break;
|
||||
|
||||
case 'v':
|
||||
printf("foot %s\n", version_and_features());
|
||||
print_version_and_features("foot ");
|
||||
return EXIT_SUCCESS;
|
||||
|
||||
case 'h':
|
||||
|
|
@ -410,6 +411,11 @@ main(int argc, char *const *argv)
|
|||
}
|
||||
}
|
||||
|
||||
if (as_server && pty_path) {
|
||||
fputs("error: --pty is incompatible with server mode\n", stderr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
log_init(log_colorize, as_server && log_syslog,
|
||||
as_server ? LOG_FACILITY_DAEMON : LOG_FACILITY_USER, log_level);
|
||||
|
||||
|
|
@ -418,7 +424,7 @@ main(int argc, char *const *argv)
|
|||
argv += optind;
|
||||
}
|
||||
|
||||
LOG_INFO("%s", version_and_features());
|
||||
LOG_INFO("%s", version_and_features);
|
||||
|
||||
{
|
||||
struct utsname name;
|
||||
|
|
@ -438,35 +444,46 @@ main(int argc, char *const *argv)
|
|||
* that does not exist on this system, then the above call may return
|
||||
* NULL. We should just continue with the fallback method below.
|
||||
*/
|
||||
LOG_WARN("setlocale() failed");
|
||||
locale = "C";
|
||||
LOG_ERR("setlocale() failed. The most common cause is that the "
|
||||
"configured locale is not available, or has been misspelled");
|
||||
}
|
||||
|
||||
LOG_INFO("locale: %s", locale);
|
||||
LOG_INFO("locale: %s", locale != NULL ? locale : "<invalid>");
|
||||
|
||||
bool bad_locale = !locale_is_utf8();
|
||||
bool bad_locale = locale == NULL || !locale_is_utf8();
|
||||
if (bad_locale) {
|
||||
static const char fallback_locales[][12] = {
|
||||
"C.UTF-8",
|
||||
"en_US.UTF-8",
|
||||
};
|
||||
char *saved_locale = locale != NULL ? xstrdup(locale) : NULL;
|
||||
|
||||
/*
|
||||
* Try to force an UTF-8 locale. If we succeed, launch the
|
||||
* user’s shell as usual, but add a user-notification saying
|
||||
* user's shell as usual, but add a user-notification saying
|
||||
* the locale has been changed.
|
||||
*/
|
||||
for (size_t i = 0; i < ALEN(fallback_locales); i++) {
|
||||
const char *const fallback_locale = fallback_locales[i];
|
||||
|
||||
if (setlocale(LC_CTYPE, fallback_locale) != NULL) {
|
||||
LOG_WARN("'%s' is not a UTF-8 locale, using '%s' instead",
|
||||
locale, fallback_locale);
|
||||
if (saved_locale != NULL) {
|
||||
LOG_WARN(
|
||||
"'%s' is not a UTF-8 locale, falling back to '%s'",
|
||||
saved_locale, fallback_locale);
|
||||
|
||||
user_notification_add_fmt(
|
||||
&user_notifications, USER_NOTIFICATION_WARNING,
|
||||
"'%s' is not a UTF-8 locale, using '%s' instead",
|
||||
locale, fallback_locale);
|
||||
user_notification_add_fmt(
|
||||
&user_notifications, USER_NOTIFICATION_WARNING,
|
||||
"'%s' is not a UTF-8 locale, falling back to '%s'",
|
||||
saved_locale, fallback_locale);
|
||||
|
||||
} else {
|
||||
LOG_WARN(
|
||||
"invalid locale, falling back to '%s'", fallback_locale);
|
||||
user_notification_add_fmt(
|
||||
&user_notifications, USER_NOTIFICATION_WARNING,
|
||||
"invalid locale, falling back to '%s'", fallback_locale);
|
||||
}
|
||||
|
||||
bad_locale = false;
|
||||
break;
|
||||
|
|
@ -474,22 +491,31 @@ main(int argc, char *const *argv)
|
|||
}
|
||||
|
||||
if (bad_locale) {
|
||||
LOG_ERR(
|
||||
"'%s' is not a UTF-8 locale, and failed to find a fallback",
|
||||
locale);
|
||||
if (saved_locale != NULL) {
|
||||
LOG_ERR(
|
||||
"'%s' is not a UTF-8 locale, and failed to find a fallback",
|
||||
saved_locale);
|
||||
|
||||
user_notification_add_fmt(
|
||||
&user_notifications, USER_NOTIFICATION_ERROR,
|
||||
"'%s' is not a UTF-8 locale, and failed to find a fallback",
|
||||
locale);
|
||||
user_notification_add_fmt(
|
||||
&user_notifications, USER_NOTIFICATION_ERROR,
|
||||
"'%s' is not a UTF-8 locale, and failed to find a fallback",
|
||||
saved_locale);
|
||||
} else {
|
||||
LOG_ERR("invalid locale, and failed to find a fallback");
|
||||
|
||||
user_notification_add_fmt(
|
||||
&user_notifications, USER_NOTIFICATION_ERROR,
|
||||
"invalid locale, and failed to find a fallback");
|
||||
}
|
||||
}
|
||||
free(saved_locale);
|
||||
}
|
||||
|
||||
struct config conf = {NULL};
|
||||
bool conf_successful = config_load(
|
||||
&conf, conf_path, &user_notifications, &overrides, check_config);
|
||||
&conf, conf_path, &user_notifications, &overrides, check_config, as_server);
|
||||
|
||||
tll_free(overrides);
|
||||
tll_free_and_free(overrides, free);
|
||||
if (!conf_successful) {
|
||||
config_free(&conf);
|
||||
return ret;
|
||||
|
|
@ -508,55 +534,11 @@ main(int argc, char *const *argv)
|
|||
(enum fcft_log_colorize)log_colorize,
|
||||
as_server && log_syslog,
|
||||
(enum fcft_log_class)log_level);
|
||||
fcft_set_scaling_filter(conf.tweak.fcft_filter);
|
||||
|
||||
if (conf_term != NULL) {
|
||||
free(conf.term);
|
||||
conf.term = xstrdup(conf_term);
|
||||
}
|
||||
if (conf_title != NULL) {
|
||||
free(conf.title);
|
||||
conf.title = xstrdup(conf_title);
|
||||
}
|
||||
if (conf_app_id != NULL) {
|
||||
free(conf.app_id);
|
||||
conf.app_id = xstrdup(conf_app_id);
|
||||
}
|
||||
if (login_shell)
|
||||
conf.login_shell = true;
|
||||
if (tll_length(conf_fonts) > 0) {
|
||||
for (size_t i = 0; i < ALEN(conf.fonts); i++)
|
||||
config_font_list_destroy(&conf.fonts[i]);
|
||||
|
||||
struct config_font_list *font_list = &conf.fonts[0];
|
||||
xassert(font_list->count == 0);
|
||||
xassert(font_list->arr == NULL);
|
||||
|
||||
font_list->arr = xmalloc(
|
||||
tll_length(conf_fonts) * sizeof(font_list->arr[0]));
|
||||
|
||||
tll_foreach(conf_fonts, it) {
|
||||
struct config_font font;
|
||||
if (!config_font_parse(it->item, &font)) {
|
||||
LOG_ERR("%s: invalid font specification", it->item);
|
||||
} else
|
||||
font_list->arr[font_list->count++] = font;
|
||||
}
|
||||
tll_free(conf_fonts);
|
||||
}
|
||||
if (conf_width > 0 && conf_height > 0) {
|
||||
conf.size.type = conf_size_type;
|
||||
conf.size.width = conf_width;
|
||||
conf.size.height = conf_height;
|
||||
}
|
||||
if (conf_server_socket_path != NULL) {
|
||||
free(conf.server_socket_path);
|
||||
conf.server_socket_path = xstrdup(conf_server_socket_path);
|
||||
}
|
||||
if (maximized)
|
||||
conf.startup_mode = STARTUP_MAXIMIZED;
|
||||
else if (fullscreen)
|
||||
conf.startup_mode = STARTUP_FULLSCREEN;
|
||||
conf.presentation_timings = presentation_timings;
|
||||
conf.hold_at_exit = hold;
|
||||
|
||||
|
|
@ -586,10 +568,10 @@ main(int argc, char *const *argv)
|
|||
char *_cwd = NULL;
|
||||
|
||||
if (cwd == NULL) {
|
||||
errno = 0;
|
||||
size_t buf_len = 1024;
|
||||
do {
|
||||
_cwd = xrealloc(_cwd, buf_len);
|
||||
errno = 0;
|
||||
if (getcwd(_cwd, buf_len) == NULL && errno != ERANGE) {
|
||||
LOG_ERRNO("failed to get current working directory");
|
||||
goto out;
|
||||
|
|
@ -606,7 +588,7 @@ main(int argc, char *const *argv)
|
|||
|
||||
if (resolved_path_cwd != NULL &&
|
||||
resolved_path_pwd != NULL &&
|
||||
strcmp(resolved_path_cwd, resolved_path_pwd) == 0)
|
||||
streq(resolved_path_cwd, resolved_path_pwd))
|
||||
{
|
||||
/*
|
||||
* The resolved path of $PWD matches the resolved path of
|
||||
|
|
@ -622,6 +604,7 @@ main(int argc, char *const *argv)
|
|||
}
|
||||
|
||||
shm_set_max_pool_size(conf.tweak.max_shm_pool_size);
|
||||
shm_set_min_stride_alignment(conf.tweak.min_stride_alignment);
|
||||
|
||||
if ((fdm = fdm_init()) == NULL)
|
||||
goto out;
|
||||
|
|
@ -642,7 +625,7 @@ main(int argc, char *const *argv)
|
|||
goto out;
|
||||
|
||||
if (!as_server && (term = term_init(
|
||||
&conf, fdm, reaper, wayl, "foot", cwd, token,
|
||||
&conf, fdm, reaper, wayl, "foot", cwd, token, pty_path,
|
||||
argc, argv, NULL,
|
||||
&term_shutdown_cb, &shutdown_ctx)) == NULL) {
|
||||
goto out;
|
||||
|
|
@ -660,6 +643,17 @@ main(int argc, char *const *argv)
|
|||
goto out;
|
||||
}
|
||||
|
||||
struct sigusr_context sigusr_context = {
|
||||
.term = term,
|
||||
.server = server,
|
||||
};
|
||||
|
||||
if (!fdm_signal_add(fdm, SIGUSR1, &fdm_sigusr, &sigusr_context) ||
|
||||
!fdm_signal_add(fdm, SIGUSR2, &fdm_sigusr, &sigusr_context))
|
||||
{
|
||||
goto out;
|
||||
}
|
||||
|
||||
struct sigaction sig_ign = {.sa_handler = SIG_IGN};
|
||||
sigemptyset(&sig_ign.sa_mask);
|
||||
if (sigaction(SIGHUP, &sig_ign, NULL) < 0 ||
|
||||
|
|
@ -695,6 +689,8 @@ out:
|
|||
wayl_destroy(wayl);
|
||||
key_binding_manager_destroy(key_binding_manager);
|
||||
reaper_destroy(reaper);
|
||||
fdm_signal_del(fdm, SIGUSR1);
|
||||
fdm_signal_del(fdm, SIGUSR2);
|
||||
fdm_signal_del(fdm, SIGTERM);
|
||||
fdm_signal_del(fdm, SIGINT);
|
||||
fdm_destroy(fdm);
|
||||
|
|
@ -709,3 +705,22 @@ out:
|
|||
log_deinit();
|
||||
return ret == EXIT_SUCCESS && !as_server ? shutdown_ctx.exit_code : ret;
|
||||
}
|
||||
|
||||
UNITTEST
|
||||
{
|
||||
char *s = xstrjoin("foo", "bar");
|
||||
xassert(streq(s, "foobar"));
|
||||
free(s);
|
||||
|
||||
s = xstrjoin3("foo", " ", "bar");
|
||||
xassert(streq(s, "foo bar"));
|
||||
free(s);
|
||||
|
||||
s = xstrjoin3("foo", ",", "bar");
|
||||
xassert(streq(s, "foo,bar"));
|
||||
free(s);
|
||||
|
||||
s = xstrjoin3("foo", "bar", "baz");
|
||||
xassert(streq(s, "foobarbaz"));
|
||||
free(s);
|
||||
}
|
||||
|
|
|
|||
196
meson.build
196
meson.build
|
|
@ -1,7 +1,7 @@
|
|||
project('foot', 'c',
|
||||
version: '1.14.0',
|
||||
version: '1.26.1',
|
||||
license: 'MIT',
|
||||
meson_version: '>=0.58.0',
|
||||
meson_version: '>=0.59.0',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
'warning_level=1',
|
||||
|
|
@ -12,34 +12,78 @@ is_debug_build = get_option('buildtype').startswith('debug')
|
|||
|
||||
cc = meson.get_compiler('c')
|
||||
|
||||
if cc.has_function('memfd_create')
|
||||
# Newer clang versions warns when using __COUNTER__ without -std=c2y
|
||||
if cc.has_argument('-Wc2y-extensions')
|
||||
add_project_arguments('-Wno-c2y-extensions', language: 'c')
|
||||
endif
|
||||
|
||||
if cc.has_function('memfd_create',
|
||||
args: ['-D_GNU_SOURCE'],
|
||||
prefix: '#include <sys/mman.h>')
|
||||
add_project_arguments('-DMEMFD_CREATE', language: 'c')
|
||||
endif
|
||||
|
||||
utempter_path = get_option('default-utempter-path')
|
||||
if utempter_path == ''
|
||||
utempter = find_program(
|
||||
'utempter',
|
||||
required: false,
|
||||
dirs: [join_paths(get_option('prefix'), get_option('libdir'), 'utempter'),
|
||||
join_paths(get_option('prefix'), get_option('libexecdir'), 'utempter'),
|
||||
'/usr/lib/utempter',
|
||||
'/usr/libexec/utempter',
|
||||
'/lib/utempter']
|
||||
)
|
||||
if utempter.found()
|
||||
utempter_path = utempter.full_path()
|
||||
# Missing on DragonFly, FreeBSD < 14.1
|
||||
if cc.has_function('execvpe',
|
||||
args: ['-D_GNU_SOURCE'],
|
||||
prefix: '#include <unistd.h>')
|
||||
add_project_arguments('-DEXECVPE', language: 'c')
|
||||
endif
|
||||
|
||||
if cc.has_function('sigabbrev_np',
|
||||
args: ['-D_GNU_SOURCE'],
|
||||
prefix: '#include <string.h>')
|
||||
add_project_arguments('-DSIGABBREV_NP', language: 'c')
|
||||
endif
|
||||
|
||||
utmp_backend = get_option('utmp-backend')
|
||||
if utmp_backend == 'auto'
|
||||
host_os = host_machine.system()
|
||||
if host_os == 'linux'
|
||||
utmp_backend = 'libutempter'
|
||||
elif host_os == 'freebsd'
|
||||
utmp_backend = 'ulog'
|
||||
else
|
||||
utempter_path = ''
|
||||
utmp_backend = 'none'
|
||||
endif
|
||||
elif utempter_path == 'none'
|
||||
utempter_path = ''
|
||||
endif
|
||||
|
||||
utmp_default_helper_path = get_option('utmp-default-helper-path')
|
||||
|
||||
if utmp_backend == 'none'
|
||||
utmp_add = ''
|
||||
utmp_del = ''
|
||||
utmp_del_have_argument = false
|
||||
utmp_default_helper_path = ''
|
||||
elif utmp_backend == 'libutempter'
|
||||
utmp_add = 'add'
|
||||
utmp_del = 'del'
|
||||
utmp_del_have_argument = false
|
||||
if utmp_default_helper_path == 'auto'
|
||||
utmp_default_helper_path = join_paths('/usr', get_option('libdir'), 'utempter', 'utempter')
|
||||
endif
|
||||
elif utmp_backend == 'ulog'
|
||||
utmp_add = 'login'
|
||||
utmp_del = 'logout'
|
||||
utmp_del_have_argument = false
|
||||
if utmp_default_helper_path == 'auto'
|
||||
utmp_default_helper_path = join_paths('/usr', get_option('libexecdir'), 'ulog-helper')
|
||||
endif
|
||||
else
|
||||
error('invalid utmp backend')
|
||||
endif
|
||||
|
||||
add_project_arguments(
|
||||
['-D_GNU_SOURCE=200809L',
|
||||
'-DFOOT_DEFAULT_TERM="@0@"'.format(get_option('default-terminfo')),
|
||||
'-DFOOT_DEFAULT_UTEMPTER_PATH="@0@"'.format(utempter_path)] +
|
||||
'-DFOOT_DEFAULT_TERM="@0@"'.format(get_option('default-terminfo'))] +
|
||||
(utmp_backend != 'none'
|
||||
? ['-DUTMP_ADD="@0@"'.format(utmp_add),
|
||||
'-DUTMP_DEL="@0@"'.format(utmp_del),
|
||||
'-DUTMP_DEFAULT_HELPER_PATH="@0@"'.format(utmp_default_helper_path)]
|
||||
: []) +
|
||||
(utmp_del_have_argument
|
||||
? ['-DUTMP_DEL_HAVE_ARGUMENT=1']
|
||||
: []) +
|
||||
(is_debug_build
|
||||
? ['-D_DEBUG']
|
||||
: [cc.get_supported_arguments('-fno-asynchronous-unwind-tables')]) +
|
||||
|
|
@ -99,7 +143,9 @@ math = cc.find_library('m')
|
|||
threads = [dependency('threads'), cc.find_library('stdthreads', required: false)]
|
||||
libepoll = dependency('epoll-shim', required: false)
|
||||
pixman = dependency('pixman-1')
|
||||
wayland_protocols = dependency('wayland-protocols')
|
||||
wayland_protocols = dependency('wayland-protocols', version: '>=1.41',
|
||||
fallback: 'wayland-protocols',
|
||||
default_options: ['tests=false'])
|
||||
wayland_client = dependency('wayland-client')
|
||||
wayland_cursor = dependency('wayland-cursor')
|
||||
xkb = dependency('xkbcommon', version: '>=1.0.0')
|
||||
|
|
@ -110,8 +156,12 @@ if utf8proc.found()
|
|||
add_project_arguments('-DFOOT_GRAPHEME_CLUSTERING=1', language: 'c')
|
||||
endif
|
||||
|
||||
tllist = dependency('tllist', version: '>=1.0.4', fallback: 'tllist')
|
||||
fcft = dependency('fcft', version: ['>=3.0.1', '<4.0.0'], fallback: 'fcft')
|
||||
if pixman.version().version_compare('>=0.46.0')
|
||||
add_project_arguments('-DHAVE_PIXMAN_RGBA_16', language: 'c')
|
||||
endif
|
||||
|
||||
tllist = dependency('tllist', version: '>=1.1.0', fallback: 'tllist')
|
||||
fcft = dependency('fcft', version: ['>=3.3.1', '<4.0.0'], fallback: 'fcft')
|
||||
|
||||
wayland_protocols_datadir = wayland_protocols.get_variable('pkgdatadir')
|
||||
|
||||
|
|
@ -122,17 +172,30 @@ wscanner_prog = find_program(
|
|||
wl_proto_headers = []
|
||||
wl_proto_src = []
|
||||
wl_proto_xml = [
|
||||
wayland_protocols_datadir + '/stable/xdg-shell/xdg-shell.xml',
|
||||
wayland_protocols_datadir + '/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml',
|
||||
wayland_protocols_datadir + '/unstable/xdg-output/xdg-output-unstable-v1.xml',
|
||||
wayland_protocols_datadir + '/unstable/primary-selection/primary-selection-unstable-v1.xml',
|
||||
wayland_protocols_datadir + '/stable/presentation-time/presentation-time.xml',
|
||||
wayland_protocols_datadir + '/unstable/text-input/text-input-unstable-v3.xml',
|
||||
]
|
||||
wayland_protocols_datadir / 'stable/xdg-shell/xdg-shell.xml',
|
||||
wayland_protocols_datadir / 'unstable/xdg-decoration/xdg-decoration-unstable-v1.xml',
|
||||
wayland_protocols_datadir / 'unstable/xdg-output/xdg-output-unstable-v1.xml',
|
||||
wayland_protocols_datadir / 'unstable/primary-selection/primary-selection-unstable-v1.xml',
|
||||
wayland_protocols_datadir / 'stable/presentation-time/presentation-time.xml',
|
||||
wayland_protocols_datadir / 'unstable/text-input/text-input-unstable-v3.xml',
|
||||
wayland_protocols_datadir / 'staging/xdg-activation/xdg-activation-v1.xml',
|
||||
wayland_protocols_datadir / 'stable/viewporter/viewporter.xml',
|
||||
wayland_protocols_datadir / 'staging/fractional-scale/fractional-scale-v1.xml',
|
||||
wayland_protocols_datadir / 'unstable/tablet/tablet-unstable-v2.xml', # required by cursor-shape-v1
|
||||
wayland_protocols_datadir / 'staging/cursor-shape/cursor-shape-v1.xml',
|
||||
wayland_protocols_datadir / 'staging/single-pixel-buffer/single-pixel-buffer-v1.xml',
|
||||
wayland_protocols_datadir / 'staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml',
|
||||
wayland_protocols_datadir / 'staging/xdg-system-bell/xdg-system-bell-v1.xml',
|
||||
wayland_protocols_datadir / 'staging/color-management/color-management-v1.xml',
|
||||
]
|
||||
|
||||
if wayland_protocols.version().version_compare('>=1.21')
|
||||
add_project_arguments('-DHAVE_XDG_ACTIVATION', language: 'c')
|
||||
wl_proto_xml += [wayland_protocols_datadir + '/staging/xdg-activation/xdg-activation-v1.xml']
|
||||
if (wayland_protocols.version().version_compare('>=1.43'))
|
||||
wl_proto_xml += [wayland_protocols_datadir / 'staging/xdg-toplevel-tag/xdg-toplevel-tag-v1.xml']
|
||||
add_project_arguments('-DHAVE_XDG_TOPLEVEL_TAG=1', language: 'c')
|
||||
endif
|
||||
if (wayland_protocols.version().version_compare('>=1.45'))
|
||||
wl_proto_xml += [wayland_protocols_datadir / 'staging/ext-background-effect/ext-background-effect-v1.xml']
|
||||
add_project_arguments('-DHAVE_EXT_BACKGROUND_EFFECT=1', language: 'c')
|
||||
endif
|
||||
|
||||
foreach prot : wl_proto_xml
|
||||
|
|
@ -167,13 +230,30 @@ builtin_terminfo = custom_target(
|
|||
'@default_terminfo@', foot_terminfo, 'foot', '@OUTPUT@']
|
||||
)
|
||||
|
||||
generate_emoji_variation_sequences = files('scripts/generate-emoji-variation-sequences.py')
|
||||
emoji_variation_sequences = custom_target(
|
||||
'generate_emoji_variation_sequences',
|
||||
input: 'unicode/emoji-variation-sequences.txt',
|
||||
output: 'emoji-variation-sequences.h',
|
||||
command: [python, generate_emoji_variation_sequences, '@INPUT@', '@OUTPUT@']
|
||||
)
|
||||
|
||||
generate_srgb_funcs = files('scripts/srgb.py')
|
||||
srgb_funcs = custom_target(
|
||||
'generate_srgb_funcs',
|
||||
output: ['srgb.c', 'srgb.h'],
|
||||
command: [python, generate_srgb_funcs, '@OUTPUT0@', '@OUTPUT1@']
|
||||
)
|
||||
|
||||
common = static_library(
|
||||
'common',
|
||||
'log.c', 'log.h',
|
||||
'char32.c', 'char32.h',
|
||||
'debug.c', 'debug.h',
|
||||
'macros.h',
|
||||
'xmalloc.c', 'xmalloc.h',
|
||||
'xsnprintf.c', 'xsnprintf.h'
|
||||
'xsnprintf.c', 'xsnprintf.h',
|
||||
dependencies: [utf8proc]
|
||||
)
|
||||
|
||||
misc = static_library(
|
||||
|
|
@ -181,20 +261,24 @@ misc = static_library(
|
|||
'hsl.c', 'hsl.h',
|
||||
'macros.h',
|
||||
'misc.c', 'misc.h',
|
||||
'uri.c', 'uri.h'
|
||||
'uri.c', 'uri.h',
|
||||
dependencies: [utf8proc],
|
||||
link_with: [common]
|
||||
)
|
||||
|
||||
vtlib = static_library(
|
||||
'vtlib',
|
||||
'base64.c', 'base64.h',
|
||||
'composed.c', 'composed.h',
|
||||
'cursor-shape.c', 'cursor-shape.h',
|
||||
'csi.c', 'csi.h',
|
||||
'dcs.c', 'dcs.h',
|
||||
'macros.h',
|
||||
'osc.c', 'osc.h',
|
||||
'sixel.c', 'sixel.h',
|
||||
'vt.c', 'vt.h',
|
||||
builtin_terminfo, wl_proto_src + wl_proto_headers,
|
||||
builtin_terminfo, srgb_funcs,
|
||||
wl_proto_src + wl_proto_headers,
|
||||
version,
|
||||
dependencies: [libepoll, pixman, fcft, tllist, wayland_client, xkb, utf8proc],
|
||||
link_with: [common, misc],
|
||||
|
|
@ -205,11 +289,19 @@ pgolib = static_library(
|
|||
'grid.c', 'grid.h',
|
||||
'selection.c', 'selection.h',
|
||||
'terminal.c', 'terminal.h',
|
||||
emoji_variation_sequences,
|
||||
wl_proto_src + wl_proto_headers,
|
||||
dependencies: [libepoll, pixman, fcft, tllist, wayland_client, xkb, utf8proc],
|
||||
link_with: vtlib,
|
||||
)
|
||||
|
||||
tokenize = static_library(
|
||||
'tokenizelib',
|
||||
'tokenize.c',
|
||||
dependencies: [utf8proc],
|
||||
link_with: [common],
|
||||
)
|
||||
|
||||
if get_option('b_pgo') == 'generate'
|
||||
executable(
|
||||
'pgo',
|
||||
|
|
@ -228,7 +320,7 @@ executable(
|
|||
'commands.c', 'commands.h',
|
||||
'extract.c', 'extract.h',
|
||||
'fdm.c', 'fdm.h',
|
||||
'foot-features.h',
|
||||
'foot-features.c', 'foot-features.h',
|
||||
'ime.c', 'ime.h',
|
||||
'input.c', 'input.h',
|
||||
'key-binding.c', 'key-binding.h',
|
||||
|
|
@ -247,7 +339,8 @@ executable(
|
|||
'url-mode.c', 'url-mode.h',
|
||||
'user-notification.c', 'user-notification.h',
|
||||
'wayland.c', 'wayland.h', 'shm-formats.h',
|
||||
wl_proto_src + wl_proto_headers, version,
|
||||
'xkbcommon-vmod.h',
|
||||
srgb_funcs, wl_proto_src + wl_proto_headers, version,
|
||||
dependencies: [math, threads, libepoll, pixman, wayland_client, wayland_cursor, xkb, fontconfig, utf8proc,
|
||||
tllist, fcft],
|
||||
link_with: pgolib,
|
||||
|
|
@ -256,16 +349,16 @@ executable(
|
|||
executable(
|
||||
'footclient',
|
||||
'client.c', 'client-protocol.h',
|
||||
'foot-features.h',
|
||||
'foot-features.c', 'foot-features.h',
|
||||
'macros.h',
|
||||
'util.h',
|
||||
version,
|
||||
dependencies: [tllist],
|
||||
dependencies: [tllist, utf8proc],
|
||||
link_with: common,
|
||||
install: true)
|
||||
|
||||
install_data(
|
||||
'org.codeberg.dnkl.foot.desktop', 'org.codeberg.dnkl.foot-server.desktop', 'org.codeberg.dnkl.footclient.desktop',
|
||||
'foot.desktop', 'foot-server.desktop', 'footclient.desktop',
|
||||
install_dir: join_paths(get_option('datadir'), 'applications'))
|
||||
|
||||
systemd = dependency('systemd', required: false)
|
||||
|
|
@ -283,13 +376,13 @@ if systemd.found() or custom_systemd_units_dir != ''
|
|||
|
||||
configure_file(
|
||||
configuration: configuration,
|
||||
input: 'foot-server@.service.in',
|
||||
input: 'foot-server.service.in',
|
||||
output: '@BASENAME@',
|
||||
install_dir: systemd_units_dir
|
||||
)
|
||||
|
||||
install_data(
|
||||
'foot-server@.socket',
|
||||
'foot-server.socket',
|
||||
install_dir: systemd_units_dir)
|
||||
endif
|
||||
|
||||
|
|
@ -306,11 +399,16 @@ if get_option('themes')
|
|||
install_subdir('themes', install_dir: join_paths(get_option('datadir'), 'foot'))
|
||||
endif
|
||||
|
||||
terminfo_base_name = get_option('terminfo-base-name')
|
||||
if terminfo_base_name == ''
|
||||
terminfo_base_name = get_option('default-terminfo')
|
||||
endif
|
||||
|
||||
tic = find_program('tic', native: true, required: get_option('terminfo'))
|
||||
if tic.found()
|
||||
conf_data = configuration_data(
|
||||
{
|
||||
'default_terminfo': get_option('default-terminfo'),
|
||||
'default_terminfo': terminfo_base_name
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -321,9 +419,9 @@ if tic.found()
|
|||
)
|
||||
custom_target(
|
||||
'terminfo',
|
||||
output: get_option('default-terminfo')[0],
|
||||
output: terminfo_base_name[0],
|
||||
input: preprocessed,
|
||||
command: [tic, '-x', '-o', '@OUTDIR@', '-e', '@0@,@0@-direct'.format(get_option('default-terminfo')), '@INPUT@'],
|
||||
command: [tic, '-x', '-o', '@OUTDIR@', '-e', '@0@,@0@-direct'.format(terminfo_base_name), '@INPUT@'],
|
||||
install: true,
|
||||
install_dir: terminfo_install_location
|
||||
)
|
||||
|
|
@ -343,8 +441,10 @@ summary(
|
|||
'Themes': get_option('themes'),
|
||||
'IME': get_option('ime'),
|
||||
'Grapheme clustering': utf8proc.found(),
|
||||
'Utempter path': utempter_path,
|
||||
'utmp backend': utmp_backend,
|
||||
'utmp helper default path': utmp_default_helper_path,
|
||||
'Build terminfo': tic.found(),
|
||||
'Terminfo base name': terminfo_base_name,
|
||||
'Terminfo install location': terminfo_install_location,
|
||||
'Default TERM': get_option('default-terminfo'),
|
||||
'Set TERMINFO': get_option('custom-terminfo-install-location') != '',
|
||||
|
|
|
|||
|
|
@ -15,12 +15,15 @@ option('tests', type: 'boolean', value: true, description: 'Build tests')
|
|||
option('terminfo', type: 'feature', value: 'enabled', description: 'Build and install foot\'s terminfo files.')
|
||||
option('default-terminfo', type: 'string', value: 'foot',
|
||||
description: 'Default value of the "term" option in foot.ini.')
|
||||
|
||||
option('terminfo-base-name', type: 'string',
|
||||
description: 'Base name of the generated terminfo files. Defaults to the value of the \'default-terminfo\' meson option')
|
||||
option('custom-terminfo-install-location', type: 'string', value: '',
|
||||
description: 'Path to foot\'s terminfo, relative to ${prefix}. If set, foot will set $TERMINFO to this value in the client process.')
|
||||
|
||||
option('systemd-units-dir', type: 'string', value: '',
|
||||
description: 'Where to install the systemd service files (absolute path). Default: ${systemduserunitdir}')
|
||||
|
||||
option('default-utempter-path', type: 'string', value: '',
|
||||
description: 'Default path to utempter helper binary. Default: auto-detect')
|
||||
option('utmp-backend', type: 'combo', value: 'auto', choices: ['none', 'libutempter', 'ulog', 'auto'],
|
||||
description: 'Which utmp logging backend to use. This affects how (with what arguments) the utmp helper binary (see \'utmp-default-helper-path\')is called. Default: auto (linux=libutempter, freebsd=ulog, others=none)')
|
||||
option('utmp-default-helper-path', type: 'string', value: 'auto',
|
||||
description: 'Default path to the utmp helper binary. Default: auto-detect')
|
||||
|
|
|
|||
19
misc.c
19
misc.c
|
|
@ -1,5 +1,6 @@
|
|||
#include "misc.h"
|
||||
#include "char32.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
bool
|
||||
isword(char32_t wc, bool spaces_only, const char32_t *delimiters)
|
||||
|
|
@ -42,3 +43,21 @@ timespec_sub(const struct timespec *a, const struct timespec *b,
|
|||
res->tv_nsec += one_sec_in_ns;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
is_valid_utf8_and_printable(const char *value)
|
||||
{
|
||||
char32_t *wide = ambstoc32(value);
|
||||
if (wide == NULL)
|
||||
return false;
|
||||
|
||||
for (const char32_t *c = wide; *c != U'\0'; c++) {
|
||||
if (!isc32print(*c)) {
|
||||
free(wide);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
free(wide);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
2
misc.h
2
misc.h
|
|
@ -8,3 +8,5 @@ bool isword(char32_t wc, bool spaces_only, const char32_t *delimiters);
|
|||
|
||||
void timespec_add(const struct timespec *a, const struct timespec *b, struct timespec *res);
|
||||
void timespec_sub(const struct timespec *a, const struct timespec *b, struct timespec *res);
|
||||
|
||||
bool is_valid_utf8_and_printable(const char *value);
|
||||
|
|
|
|||
734
notify.c
734
notify.c
|
|
@ -1,9 +1,11 @@
|
|||
#include "notify.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
|
|
@ -12,43 +14,574 @@
|
|||
#include "log.h"
|
||||
#include "config.h"
|
||||
#include "spawn.h"
|
||||
#include "terminal.h"
|
||||
#include "util.h"
|
||||
#include "wayland.h"
|
||||
#include "xmalloc.h"
|
||||
#include "xsnprintf.h"
|
||||
|
||||
void
|
||||
notify_notify(const struct terminal *term, const char *title, const char *body)
|
||||
notify_free(struct terminal *term, struct notification *notif)
|
||||
{
|
||||
LOG_DBG("notify: title=\"%s\", msg=\"%s\"", title, body);
|
||||
if (notif->pid > 0)
|
||||
fdm_del(term->fdm, notif->stdout_fd);
|
||||
|
||||
if (term->conf->notify_focus_inhibit && term->kbd_focus) {
|
||||
/* No notifications while we’re focused */
|
||||
return;
|
||||
free(notif->id);
|
||||
free(notif->title);
|
||||
free(notif->body);
|
||||
free(notif->category);
|
||||
free(notif->app_id);
|
||||
free(notif->icon_cache_id);
|
||||
free(notif->icon_symbolic_name);
|
||||
free(notif->icon_data);
|
||||
free(notif->sound_name);
|
||||
free(notif->xdg_token);
|
||||
free(notif->stdout_data);
|
||||
|
||||
tll_free_and_free(notif->actions, free);
|
||||
|
||||
if (notif->icon_path != NULL) {
|
||||
unlink(notif->icon_path);
|
||||
free(notif->icon_path);
|
||||
|
||||
if (notif->icon_fd >= 0)
|
||||
close(notif->icon_fd);
|
||||
}
|
||||
|
||||
if (title == NULL || body == NULL)
|
||||
return;
|
||||
memset(notif, 0, sizeof(*notif));
|
||||
}
|
||||
|
||||
if (term->conf->notify.argv.args == NULL)
|
||||
static bool
|
||||
write_icon_file(const void *data, size_t data_sz, int *fd, char **filename,
|
||||
char **symbolic_name)
|
||||
{
|
||||
xassert(*filename == NULL);
|
||||
xassert(*symbolic_name == NULL);
|
||||
|
||||
char name[64] = "/tmp/foot-notification-icon-XXXXXX";
|
||||
|
||||
*filename = NULL;
|
||||
*symbolic_name = NULL;
|
||||
*fd = mkostemp(name, O_CLOEXEC);
|
||||
|
||||
if (*fd < 0) {
|
||||
LOG_ERRNO("failed to create temporary file for icon cache");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (write(*fd, data, data_sz) != (ssize_t)data_sz) {
|
||||
LOG_ERRNO("failed to write icon data to temporary file");
|
||||
close(*fd);
|
||||
*fd = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_DBG("wrote icon data to %s", name);
|
||||
*filename = xstrdup(name);
|
||||
*symbolic_name = xstrjoin("file://", *filename);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
to_integer(const char *line, size_t len, uint32_t *res)
|
||||
{
|
||||
bool is_id = true;
|
||||
uint32_t maybe_id = 0;
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
char digit = line[i];
|
||||
if (digit < '0' || digit > '9') {
|
||||
is_id = false;
|
||||
break;
|
||||
}
|
||||
|
||||
maybe_id *= 10;
|
||||
maybe_id += digit - '0';
|
||||
}
|
||||
|
||||
*res = maybe_id;
|
||||
return is_id;
|
||||
}
|
||||
|
||||
static void
|
||||
consume_stdout(struct notification *notif, bool eof)
|
||||
{
|
||||
char *data = notif->stdout_data;
|
||||
const char *line = data;
|
||||
size_t left = notif->stdout_sz;
|
||||
|
||||
/* Process stdout, line-by-line */
|
||||
while (left > 0) {
|
||||
line = data;
|
||||
size_t len = left;
|
||||
char *eol = (char *)memchr(line, '\n', left);
|
||||
|
||||
if (eol != NULL) {
|
||||
*eol = '\0';
|
||||
len = strlen(line);
|
||||
data = eol + 1;
|
||||
} else if (!eof)
|
||||
break;
|
||||
|
||||
uint32_t maybe_id = 0;
|
||||
uint32_t maybe_button_nr = 0;
|
||||
|
||||
/* Check for daemon assigned ID, either '123', or 'id=123' */
|
||||
if ((notif->external_id == 0 && to_integer(line, len, &maybe_id)) ||
|
||||
(len > 3 && memcmp(line, "id=", 3) == 0 &&
|
||||
to_integer(&line[3], len - 3, &maybe_id)))
|
||||
{
|
||||
notif->external_id = maybe_id;
|
||||
LOG_DBG("external ID: %u", notif->external_id);
|
||||
}
|
||||
|
||||
/* Check for triggered action, either 'default' or 'action=default' */
|
||||
else if ((len == 7 && memcmp(line, "default", 7) == 0) ||
|
||||
(len == 7 + 7 && memcmp(line, "action=default", 7 + 7) == 0))
|
||||
{
|
||||
notif->activated = true;
|
||||
LOG_DBG("notification's default action was triggered");
|
||||
}
|
||||
|
||||
else if (len > 7 && memcmp(line, "action=", 7) == 0) {
|
||||
notif->activated = true;
|
||||
|
||||
if (to_integer(&line[7], len - 7, &maybe_button_nr)) {
|
||||
notif->activated_button = maybe_button_nr;
|
||||
LOG_DBG("custom action %u triggered", notif->activated_button);
|
||||
} else {
|
||||
LOG_DBG("unrecognized action triggered: %.*s",
|
||||
(int)(len - 7), &line[7]);
|
||||
}
|
||||
}
|
||||
|
||||
else if (notif->external_id > 0 &&
|
||||
to_integer(line, len, &maybe_button_nr) &&
|
||||
maybe_button_nr > 0 &&
|
||||
maybe_button_nr <= notif->button_count)
|
||||
{
|
||||
/* Single integer, appearing *after* the ID, and is within
|
||||
the custom button/action range */
|
||||
notif->activated = true;
|
||||
notif->activated_button = maybe_button_nr;
|
||||
LOG_DBG("custom action %u triggered", notif->activated_button);
|
||||
}
|
||||
|
||||
/* Check for XDG activation token, 'xdgtoken=xyz' */
|
||||
else if (len > 9 && memcmp(line, "xdgtoken=", 9) == 0) {
|
||||
notif->xdg_token = xstrndup(&line[9], len - 9);
|
||||
LOG_DBG("XDG token: \"%s\"", notif->xdg_token);
|
||||
}
|
||||
|
||||
left -= len + (eol != NULL ? 1 : 0);
|
||||
}
|
||||
|
||||
if (left > 0)
|
||||
memmove(notif->stdout_data, data, left);
|
||||
|
||||
notif->stdout_sz = left;
|
||||
}
|
||||
|
||||
static bool
|
||||
fdm_notify_stdout(struct fdm *fdm, int fd, int events, void *data)
|
||||
{
|
||||
const struct terminal *term = data;
|
||||
struct notification *notif = NULL;
|
||||
|
||||
/* Find notification */
|
||||
tll_foreach(term->active_notifications, it) {
|
||||
if (it->item.stdout_fd == fd) {
|
||||
notif = &it->item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (events & EPOLLIN) {
|
||||
char buf[512];
|
||||
ssize_t count = read(fd, buf, sizeof(buf) - 1);
|
||||
|
||||
if (count < 0) {
|
||||
if (errno == EINTR)
|
||||
return true;
|
||||
|
||||
LOG_ERRNO("failed to read notification activation token");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count > 0 && notif != NULL) {
|
||||
if (notif->stdout_data == NULL) {
|
||||
xassert(notif->stdout_sz == 0);
|
||||
notif->stdout_data = xmemdup(buf, count);
|
||||
} else {
|
||||
notif->stdout_data = xrealloc(notif->stdout_data, notif->stdout_sz + count);
|
||||
memcpy(¬if->stdout_data[notif->stdout_sz], buf, count);
|
||||
}
|
||||
|
||||
notif->stdout_sz += count;
|
||||
consume_stdout(notif, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (events & EPOLLHUP) {
|
||||
fdm_del(fdm, fd);
|
||||
if (notif != NULL) {
|
||||
notif->stdout_fd = -1;
|
||||
consume_stdout(notif, true);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
notif_done(struct reaper *reaper, pid_t pid, int status, void *data)
|
||||
{
|
||||
struct terminal *term = data;
|
||||
|
||||
tll_foreach(term->active_notifications, it) {
|
||||
struct notification *notif = &it->item;
|
||||
if (notif->pid != pid)
|
||||
continue;
|
||||
|
||||
LOG_DBG("notification %s closed",
|
||||
notif->id != NULL ? notif->id : "<unset>");
|
||||
|
||||
if (notif->activated && notif->focus) {
|
||||
LOG_DBG("focus window on notification activation: \"%s\"",
|
||||
notif->xdg_token);
|
||||
|
||||
if (notif->xdg_token == NULL)
|
||||
LOG_WARN("cannot focus window: no activation token available");
|
||||
else
|
||||
wayl_activate(term->wl, term->window, notif->xdg_token);
|
||||
}
|
||||
|
||||
if (notif->activated && notif->report_activated) {
|
||||
LOG_DBG("sending notification activation event to client");
|
||||
|
||||
const char *id = notif->id != NULL ? notif->id : "0";
|
||||
|
||||
char button_nr[16] = {0};
|
||||
if (notif->activated_button > 0) {
|
||||
xsnprintf(
|
||||
button_nr, sizeof(button_nr), "%u", notif->activated_button);
|
||||
}
|
||||
|
||||
char reply[7 + strlen(id) + 1 + strlen(button_nr) + 2 + 1];
|
||||
size_t n = xsnprintf(
|
||||
reply, sizeof(reply), "\033]99;i=%s;%s\033\\", id, button_nr);
|
||||
term_to_slave(term, reply, n);
|
||||
}
|
||||
|
||||
if (notif->report_closed) {
|
||||
LOG_DBG("sending notification close event to client");
|
||||
|
||||
const char *id = notif->id != NULL ? notif->id : "0";
|
||||
char reply[7 + strlen(id) + 1 + 7 + 1 + 2 + 1];
|
||||
size_t n = xsnprintf(
|
||||
reply, sizeof(reply), "\033]99;i=%s:p=close;\033\\", id);
|
||||
term_to_slave(term, reply, n);
|
||||
}
|
||||
|
||||
notify_free(term, notif);
|
||||
tll_remove(term->active_notifications, it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
expand_action_to_argv(struct terminal *term, const char *name, const char *label,
|
||||
size_t *argc, char ***argv)
|
||||
{
|
||||
char **expanded = NULL;
|
||||
size_t count = 0;
|
||||
|
||||
if (!spawn_expand_template(
|
||||
&term->conf->desktop_notifications.command_action_arg, 2,
|
||||
(const char *[]){"action-name", "action-label"},
|
||||
(const char *[]){name, label},
|
||||
&count, &expanded))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Append to the "global" actions argv */
|
||||
*argv = xrealloc(*argv, (*argc + count) * sizeof((*argv)[0]));
|
||||
memcpy(&(*argv)[*argc], expanded, count * sizeof(expanded[0]));
|
||||
*argc += count;
|
||||
|
||||
free(expanded);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
notify_notify(struct terminal *term, struct notification *notif)
|
||||
{
|
||||
xassert(notif->xdg_token == NULL);
|
||||
xassert(notif->external_id == 0);
|
||||
xassert(notif->pid == 0);
|
||||
xassert(notif->stdout_fd <= 0);
|
||||
xassert(notif->stdout_data == NULL);
|
||||
xassert(notif->icon_path == NULL);
|
||||
xassert(notif->icon_fd <= 0);
|
||||
|
||||
notif->pid = -1;
|
||||
notif->stdout_fd = -1;
|
||||
notif->icon_fd = -1;
|
||||
|
||||
if (term->conf->desktop_notifications.command.argv.args == NULL)
|
||||
return false;
|
||||
|
||||
if ((term->conf->desktop_notifications.inhibit_when_focused ||
|
||||
notif->when != NOTIFY_ALWAYS)
|
||||
&& term->kbd_focus)
|
||||
{
|
||||
/* No notifications while we're focused */
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *app_id = notif->app_id != NULL
|
||||
? notif->app_id
|
||||
: term->app_id != NULL
|
||||
? term->app_id
|
||||
: term->conf->app_id;
|
||||
const char *title = notif->title != NULL ? notif->title : notif->body;
|
||||
const char *body = notif->title != NULL && notif->body != NULL ? notif->body : "";
|
||||
|
||||
/* Icon: symbolic name if present, otherwise a filename */
|
||||
const char *icon_name_or_path = "";
|
||||
|
||||
if (notif->icon_cache_id != NULL) {
|
||||
for (size_t i = 0; i < ALEN(term->notification_icons); i++) {
|
||||
const struct notification_icon *icon = &term->notification_icons[i];
|
||||
|
||||
if (icon->id != NULL && streq(icon->id, notif->icon_cache_id)) {
|
||||
/* For now, we set the symbolic name to 'file:///path'
|
||||
* when using a file based icon. */
|
||||
xassert(icon->symbolic_name != NULL);
|
||||
icon_name_or_path = icon->symbolic_name;
|
||||
|
||||
LOG_DBG("using icon from cache (cache ID: %s): %s",
|
||||
icon->id, icon_name_or_path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (notif->icon_symbolic_name != NULL) {
|
||||
icon_name_or_path = notif->icon_symbolic_name;
|
||||
LOG_DBG("using symbolic icon from notification: %s", icon_name_or_path);
|
||||
} else if (notif->icon_data_sz > 0) {
|
||||
xassert(notif->icon_data != NULL);
|
||||
|
||||
if (write_icon_file(
|
||||
notif->icon_data, notif->icon_data_sz,
|
||||
¬if->icon_fd,
|
||||
¬if->icon_path,
|
||||
¬if->icon_symbolic_name))
|
||||
icon_name_or_path = notif->icon_symbolic_name;
|
||||
|
||||
LOG_DBG("using icon data from notification: %s", icon_name_or_path);
|
||||
}
|
||||
|
||||
bool track_notification = notif->focus ||
|
||||
notif->report_activated ||
|
||||
notif->may_be_programatically_closed;
|
||||
|
||||
uint32_t replaces_id = 0;
|
||||
if (notif->id != NULL) {
|
||||
tll_foreach(term->active_notifications, it) {
|
||||
struct notification *existing = &it->item;
|
||||
|
||||
if (existing->id == NULL)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* When replacing/updating a notification, we may have
|
||||
* *multiple* notification helpers running for the "same"
|
||||
* notification. Make sure only the *last* notification's
|
||||
* report closed/activated are honored, to avoid sending
|
||||
* multiple reports.
|
||||
*
|
||||
* This also means we cannot 'break' out of the loop - we
|
||||
* must check *all* notifications.
|
||||
*/
|
||||
if (existing->external_id != 0 && streq(existing->id, notif->id)) {
|
||||
replaces_id = existing->external_id;
|
||||
existing->report_activated = false;
|
||||
existing->report_closed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char replaces_id_str[16];
|
||||
xsnprintf(replaces_id_str, sizeof(replaces_id_str), "%u", replaces_id);
|
||||
|
||||
const char *urgency_str =
|
||||
notif->urgency == NOTIFY_URGENCY_LOW
|
||||
? "low"
|
||||
: notif->urgency == NOTIFY_URGENCY_NORMAL
|
||||
? "normal" : "critical";
|
||||
|
||||
LOG_DBG("notify: title=\"%s\", body=\"%s\", app-id=\"%s\", category=\"%s\", "
|
||||
"urgency=\"%s\", icon=\"%s\", expires=%d, replaces=%u, muted=%s, "
|
||||
"sound-name=%s (tracking: %s)",
|
||||
title, body, app_id, notif->category, urgency_str, icon_name_or_path,
|
||||
notif->expire_time, replaces_id,
|
||||
notif->muted ? "yes" : "no", notif->sound_name,
|
||||
track_notification ? "yes" : "no");
|
||||
|
||||
xassert(title != NULL);
|
||||
if (title == NULL)
|
||||
return false;
|
||||
|
||||
char **argv = NULL;
|
||||
size_t argc = 0;
|
||||
char **action_argv = NULL;
|
||||
size_t action_argc = 0;
|
||||
|
||||
char expire_time[16];
|
||||
xsnprintf(expire_time, sizeof(expire_time), "%d", notif->expire_time);
|
||||
|
||||
if (term->conf->desktop_notifications.command_action_arg.argv.args) {
|
||||
if (!expand_action_to_argv(
|
||||
term, "default", "Activate", &action_argc, &action_argv))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t action_idx = 1;
|
||||
tll_foreach(notif->actions, it) {
|
||||
|
||||
/* Custom actions use a numerical name, starting at 1 */
|
||||
char name[16];
|
||||
xsnprintf(name, sizeof(name), "%zu", action_idx++);
|
||||
|
||||
if (!expand_action_to_argv(
|
||||
term, name, it->item, &action_argc, &action_argv))
|
||||
{
|
||||
for (size_t i = 0; i < action_argc; i++)
|
||||
free(action_argv[i]);
|
||||
free(action_argv);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!spawn_expand_template(
|
||||
&term->conf->notify, 4,
|
||||
(const char *[]){"app-id", "window-title", "title", "body"},
|
||||
(const char *[]){term->conf->app_id, term->window_title, title, body},
|
||||
&argc, &argv))
|
||||
&term->conf->desktop_notifications.command, 12,
|
||||
(const char *[]){
|
||||
"app-id", "window-title", "icon", "title", "body", "category",
|
||||
"urgency", "muted", "sound-name", "expire-time", "replace-id",
|
||||
"action-argument"},
|
||||
(const char *[]){
|
||||
app_id, term->window_title, icon_name_or_path, title,
|
||||
body != NULL ? body : "",
|
||||
notif->category != NULL ? notif->category : "", urgency_str,
|
||||
notif->muted ? "true" : "false",
|
||||
notif->sound_name != NULL ? notif->sound_name : "",
|
||||
expire_time, replaces_id_str,
|
||||
|
||||
/* Custom expansion below, since we need to expand to multiple arguments */
|
||||
"${action-argument}"},
|
||||
&argc, &argv))
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Post-process the expanded argv, and patch in all the --action
|
||||
arguments we expanded earlier */
|
||||
for (size_t i = 0; i < argc; i++) {
|
||||
if (!streq(argv[i], "${action-argument}"))
|
||||
continue;
|
||||
|
||||
if (action_argc == 0) {
|
||||
free(argv[i]);
|
||||
|
||||
/* Remove ${command-argument}, but include terminating NULL */
|
||||
memmove(&argv[i], &argv[i + 1], (argc - i) * sizeof(argv[0]));
|
||||
argc--;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Remove the "${action-argument}" entry, add all actions argument
|
||||
from earlier, but include terminating NULL */
|
||||
argv = xrealloc(argv, (argc + action_argc) * sizeof(argv[0]));
|
||||
|
||||
/* Move remaining arguments to after the action arguments */
|
||||
memmove(&argv[i + action_argc],
|
||||
&argv[i + 1],
|
||||
(argc - i) * sizeof(argv[0])); /* Include terminating NULL */
|
||||
|
||||
free(argv[i]); /* Free xstrdup("${action-argument}"); */
|
||||
|
||||
/* Insert the action arguments */
|
||||
for (size_t j = 0; j < action_argc; j++) {
|
||||
argv[i + j] = action_argv[j];
|
||||
action_argv[j] = NULL;
|
||||
}
|
||||
|
||||
argc += action_argc;
|
||||
argc--; /* The ${action-argument} option has been removed */
|
||||
break;
|
||||
}
|
||||
|
||||
LOG_DBG("notify command:");
|
||||
for (size_t i = 0; i < argc; i++)
|
||||
LOG_DBG(" argv[%zu] = \"%s\"", i, argv[i]);
|
||||
xassert(argv[argc] == NULL);
|
||||
|
||||
int stdout_fds[2] = {-1, -1};
|
||||
if (track_notification) {
|
||||
if (pipe2(stdout_fds, O_CLOEXEC | O_NONBLOCK) < 0) {
|
||||
LOG_WARN("failed to create stdout pipe");
|
||||
track_notification = false;
|
||||
/* Non-fatal */
|
||||
} else {
|
||||
tll_push_back(term->active_notifications, *notif);
|
||||
|
||||
/* We've taken over ownership of all data; clear, so that
|
||||
notify_free() doesn't double free */
|
||||
notif->id = NULL;
|
||||
notif->title = NULL;
|
||||
notif->body = NULL;
|
||||
notif->category = NULL;
|
||||
notif->app_id = NULL;
|
||||
notif->icon_cache_id = NULL;
|
||||
notif->icon_symbolic_name = NULL;
|
||||
notif->icon_data = NULL;
|
||||
notif->icon_data_sz = 0;
|
||||
notif->icon_path = NULL;
|
||||
notif->sound_name = NULL;
|
||||
notif->icon_fd = -1;
|
||||
notif->stdout_fd = -1;
|
||||
struct notification *new_notif = &tll_back(term->active_notifications);
|
||||
|
||||
/* We don't need these anymore. They'll be free:d by the caller */
|
||||
new_notif->button_count = tll_length(notif->actions);
|
||||
memset(&new_notif->actions, 0, sizeof(new_notif->actions));
|
||||
notif = new_notif;
|
||||
}
|
||||
}
|
||||
|
||||
if (stdout_fds[0] >= 0) {
|
||||
fdm_add(term->fdm, stdout_fds[0], EPOLLIN,
|
||||
&fdm_notify_stdout, (void *)term);
|
||||
}
|
||||
|
||||
/* Redirect stdin to /dev/null, but ignore failure to open */
|
||||
int devnull = open("/dev/null", O_RDONLY);
|
||||
spawn(term->reaper, NULL, argv, devnull, -1, -1, NULL);
|
||||
pid_t pid = spawn(
|
||||
term->reaper, NULL, argv, devnull, stdout_fds[1], -1,
|
||||
track_notification ? ¬if_done : NULL, (void *)term, NULL);
|
||||
|
||||
if (stdout_fds[1] >= 0) {
|
||||
/* Close write-end of stdout pipe */
|
||||
close(stdout_fds[1]);
|
||||
}
|
||||
|
||||
if (pid < 0 && stdout_fds[0] >= 0) {
|
||||
/* Remove FDM callback if we failed to spawn */
|
||||
fdm_del(term->fdm, stdout_fds[0]);
|
||||
}
|
||||
|
||||
if (devnull >= 0)
|
||||
close(devnull);
|
||||
|
|
@ -56,4 +589,177 @@ notify_notify(const struct terminal *term, const char *title, const char *body)
|
|||
for (size_t i = 0; i < argc; i++)
|
||||
free(argv[i]);
|
||||
free(argv);
|
||||
for (size_t i = 0; i < action_argc; i++)
|
||||
free(action_argv[i]);
|
||||
free(action_argv);
|
||||
|
||||
notif->pid = pid;
|
||||
notif->stdout_fd = stdout_fds[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
notify_close(struct terminal *term, const char *id)
|
||||
{
|
||||
xassert(id != NULL);
|
||||
LOG_DBG("close notification %s", id);
|
||||
|
||||
tll_foreach(term->active_notifications, it) {
|
||||
const struct notification *notif = &it->item;
|
||||
if (notif->id == NULL || !streq(notif->id, id))
|
||||
continue;
|
||||
|
||||
if (term->conf->desktop_notifications.close.argv.args == NULL) {
|
||||
LOG_DBG(
|
||||
"trying to close notification \"%s\" by sending SIGINT to %u",
|
||||
id, notif->pid);
|
||||
|
||||
if (notif->pid == 0) {
|
||||
LOG_WARN(
|
||||
"cannot close notification \"%s\": no helper process running",
|
||||
id);
|
||||
} else {
|
||||
/* Best-effort... */
|
||||
kill(notif->pid, SIGINT);
|
||||
}
|
||||
} else {
|
||||
LOG_DBG(
|
||||
"trying to close notification \"%s\" "
|
||||
"by running user defined command", id);
|
||||
|
||||
if (notif->external_id == 0) {
|
||||
LOG_WARN("cannot close notification \"%s\": "
|
||||
"no daemon assigned notification ID available", id);
|
||||
return;
|
||||
}
|
||||
|
||||
char **argv = NULL;
|
||||
size_t argc = 0;
|
||||
|
||||
char external_id[16];
|
||||
xsnprintf(external_id, sizeof(external_id), "%u", notif->external_id);
|
||||
|
||||
if (!spawn_expand_template(
|
||||
&term->conf->desktop_notifications.close, 1,
|
||||
(const char *[]){"id"},
|
||||
(const char *[]){external_id},
|
||||
&argc, &argv))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int devnull = open("/dev/null", O_RDONLY);
|
||||
spawn(
|
||||
term->reaper, NULL, argv, devnull, -1, -1,
|
||||
NULL, (void *)term, NULL);
|
||||
|
||||
if (devnull >= 0)
|
||||
close(devnull);
|
||||
|
||||
for (size_t i = 0; i < argc; i++)
|
||||
free(argv[i]);
|
||||
free(argv);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_WARN("cannot close notification \"%s\": no such notification", id);
|
||||
}
|
||||
|
||||
static void
|
||||
add_icon(struct notification_icon *icon, const char *id, const char *symbolic_name,
|
||||
const uint8_t *data, size_t data_sz)
|
||||
{
|
||||
icon->id = xstrdup(id);
|
||||
icon->symbolic_name = symbolic_name != NULL ? xstrdup(symbolic_name) : NULL;
|
||||
icon->tmp_file_name = NULL;
|
||||
icon->tmp_file_fd = -1;
|
||||
|
||||
/*
|
||||
* Dump in-line data to a temporary file. This allows us to pass
|
||||
* the filename as a parameter to notification helpers
|
||||
* (i.e. notify-send -i <path>).
|
||||
*
|
||||
* Optimization: since we always prefer (i.e. use) the symbolic
|
||||
* name if present, there's no need to create a file on disk if we
|
||||
* have a symbolic name.
|
||||
*/
|
||||
if (symbolic_name == NULL && data_sz > 0) {
|
||||
write_icon_file(
|
||||
data, data_sz,
|
||||
&icon->tmp_file_fd,
|
||||
&icon->tmp_file_name,
|
||||
&icon->symbolic_name);
|
||||
}
|
||||
|
||||
LOG_DBG("added icon to cache: ID=%s: sym=%s, file=%s",
|
||||
icon->id, icon->symbolic_name, icon->tmp_file_name);
|
||||
}
|
||||
|
||||
void
|
||||
notify_icon_add(struct terminal *term, const char *id,
|
||||
const char *symbolic_name, const uint8_t *data, size_t data_sz)
|
||||
{
|
||||
#if defined(_DEBUG)
|
||||
for (size_t i = 0; i < ALEN(term->notification_icons); i++) {
|
||||
struct notification_icon *icon = &term->notification_icons[i];
|
||||
if (icon->id != NULL && streq(icon->id, id)) {
|
||||
BUG("notification icon cache already contains \"%s\"", id);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
for (size_t i = 0; i < ALEN(term->notification_icons); i++) {
|
||||
struct notification_icon *icon = &term->notification_icons[i];
|
||||
if (icon->id == NULL) {
|
||||
add_icon(icon, id, symbolic_name, data, data_sz);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Cache full - throw out first entry, add new entry last */
|
||||
notify_icon_free(&term->notification_icons[0]);
|
||||
memmove(&term->notification_icons[0],
|
||||
&term->notification_icons[1],
|
||||
((ALEN(term->notification_icons) - 1) *
|
||||
sizeof(term->notification_icons[0])));
|
||||
|
||||
add_icon(
|
||||
&term->notification_icons[ALEN(term->notification_icons) - 1],
|
||||
id, symbolic_name, data, data_sz);
|
||||
}
|
||||
|
||||
void
|
||||
notify_icon_del(struct terminal *term, const char *id)
|
||||
{
|
||||
for (size_t i = 0; i < ALEN(term->notification_icons); i++) {
|
||||
struct notification_icon *icon = &term->notification_icons[i];
|
||||
|
||||
if (icon->id == NULL || !streq(icon->id, id))
|
||||
continue;
|
||||
|
||||
LOG_DBG("expelled %s from the notification icon cache", icon->id);
|
||||
notify_icon_free(icon);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
notify_icon_free(struct notification_icon *icon)
|
||||
{
|
||||
if (icon->tmp_file_name != NULL) {
|
||||
unlink(icon->tmp_file_name);
|
||||
if (icon->tmp_file_fd >= 0)
|
||||
close(icon->tmp_file_fd);
|
||||
}
|
||||
|
||||
free(icon->id);
|
||||
free(icon->symbolic_name);
|
||||
free(icon->tmp_file_name);
|
||||
|
||||
icon->id = NULL;
|
||||
icon->symbolic_name = NULL;
|
||||
icon->tmp_file_name = NULL;
|
||||
icon->tmp_file_fd = -1;
|
||||
}
|
||||
|
|
|
|||
95
notify.h
95
notify.h
|
|
@ -1,6 +1,95 @@
|
|||
#pragma once
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "terminal.h"
|
||||
#include <tllist.h>
|
||||
|
||||
void notify_notify(
|
||||
const struct terminal *term, const char *title, const char *body);
|
||||
struct terminal;
|
||||
|
||||
enum notify_when {
|
||||
/* First, so that it can be left out of initializer and still be
|
||||
the default */
|
||||
NOTIFY_ALWAYS,
|
||||
|
||||
NOTIFY_UNFOCUSED,
|
||||
NOTIFY_INVISIBLE
|
||||
};
|
||||
|
||||
enum notify_urgency {
|
||||
/* First, so that it can be left out of initializer and still be
|
||||
the default */
|
||||
NOTIFY_URGENCY_NORMAL,
|
||||
|
||||
NOTIFY_URGENCY_LOW,
|
||||
NOTIFY_URGENCY_CRITICAL,
|
||||
};
|
||||
|
||||
struct notification {
|
||||
/*
|
||||
* Set by caller of notify_notify()
|
||||
*/
|
||||
char *id; /* Internal notification ID */
|
||||
|
||||
char *app_id; /* Custom app-id, overrides the terminal's app-id if set */
|
||||
char *title; /* Required */
|
||||
char *body;
|
||||
char *category;
|
||||
|
||||
enum notify_when when;
|
||||
enum notify_urgency urgency;
|
||||
int32_t expire_time;
|
||||
|
||||
tll(char *) actions;
|
||||
|
||||
char *icon_cache_id;
|
||||
char *icon_symbolic_name;
|
||||
uint8_t *icon_data;
|
||||
size_t icon_data_sz;
|
||||
|
||||
bool focus; /* Focus the foot window when notification is activated */
|
||||
bool may_be_programatically_closed; /* OSC-99: notification may be programmatically closed by the client */
|
||||
bool report_activated; /* OSC-99: report notification activation to client */
|
||||
bool report_closed; /* OSC-99: report notification closed to client */
|
||||
|
||||
bool muted; /* Explicitly mute the notification */
|
||||
char *sound_name; /* Should be set to NULL if muted == true */
|
||||
|
||||
/*
|
||||
* Used internally by notify
|
||||
*/
|
||||
|
||||
uint32_t external_id; /* Daemon assigned notification ID */
|
||||
bool activated; /* User 'activated' the notification */
|
||||
uint32_t button_count; /* Number of buttons (custom actions) in notification */
|
||||
uint32_t activated_button; /* User activated one of the custom actions */
|
||||
char *xdg_token; /* XDG activation token, from daemon */
|
||||
|
||||
pid_t pid; /* Notifier command PID */
|
||||
int stdout_fd; /* Notifier command's stdout */
|
||||
|
||||
char *stdout_data; /* Data we've reado from command's stdout */
|
||||
size_t stdout_sz;
|
||||
|
||||
/* Used when notification provides raw icon data, and it's
|
||||
bypassing the icon cache */
|
||||
char *icon_path;
|
||||
int icon_fd;
|
||||
};
|
||||
|
||||
struct notification_icon {
|
||||
char *id;
|
||||
char *symbolic_name;
|
||||
char *tmp_file_name;
|
||||
int tmp_file_fd;
|
||||
};
|
||||
|
||||
bool notify_notify(struct terminal *term, struct notification *notif);
|
||||
void notify_close(struct terminal *term, const char *id);
|
||||
void notify_free(struct terminal *term, struct notification *notif);
|
||||
|
||||
void notify_icon_add(struct terminal *term, const char *id,
|
||||
const char *symbolic_name, const uint8_t *data,
|
||||
size_t data_sz);
|
||||
void notify_icon_del(struct terminal *term, const char *id);
|
||||
void notify_icon_free(struct notification_icon *icon);
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>org.codeberg.dnkl.foot</id>
|
||||
<metadata_license>MIT</metadata_license>
|
||||
<project_license>MIT</project_license>
|
||||
<developer_name>dnkl</developer_name>
|
||||
<name>foot</name>
|
||||
<summary>The fast, lightweight and minimalistic Wayland terminal emulator.</summary>
|
||||
<description>
|
||||
<ul>
|
||||
<li>Fast</li>
|
||||
<li>Lightweight, in dependencies, on-disk and in-memory</li>
|
||||
<li>Wayland native</li>
|
||||
<li>DE agnostic</li>
|
||||
<li>Server/daemon mode</li>
|
||||
<li>User configurable font fallback</li>
|
||||
<li>On-the-fly font resize</li>
|
||||
<li>On-the-fly DPI font size adjustment</li>
|
||||
<li>Scrollback search</li>
|
||||
<li>Keyboard driven URL detection</li>
|
||||
<li>Color emoji support</li>
|
||||
<li>IME (via text-input-v3)</li>
|
||||
<li>Multi-seat</li>
|
||||
<li>True Color (24bpp)</li>
|
||||
<li>Synchronized Updates support</li>
|
||||
<li>Sixel image support</li>
|
||||
</ul>
|
||||
</description>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<caption>Foot with sixel graphics</caption>
|
||||
<image>https://codeberg.org/dnkl/foot/media/branch/master/doc/sixel-wow.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<releases>
|
||||
<release version="1.13.1" date="2022-08-31">
|
||||
</release>
|
||||
<release version="1.13.0" date="2022-08-07">
|
||||
</release>
|
||||
</releases>
|
||||
<launchable type="desktop-id">org.codeberg.dnkl.foot.desktop</launchable>
|
||||
<url type="homepage">https://codeberg.org/dnkl/foot</url>
|
||||
<url type="bugtracker">https://codeberg.org/dnkl/foot/issues</url>
|
||||
<content_rating type="oars-1.1"/>
|
||||
</component>
|
||||
|
|
@ -10,5 +10,5 @@ trap "rm -rf '${runtime_dir}'" EXIT INT HUP TERM
|
|||
|
||||
XDG_RUNTIME_DIR="${runtime_dir}" WLR_RENDERER=pixman WLR_BACKENDS=headless cage "${srcdir}"/pgo/full-inner.sh "${srcdir}" "${blddir}"
|
||||
|
||||
# Cage’s exit code doesn’t reflect our script’s exit code
|
||||
# Cage's exit code doesn't reflect our script's exit code
|
||||
[ -f "${blddir}"/pgo-ok ] || exit 1
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ trap cleanup EXIT INT HUP TERM
|
|||
# Generate a custom config that executes our generate-pgo-data script
|
||||
> "${sway_conf}" echo "exec '${srcdir}'/pgo/full-headless-sway-inner.sh '${srcdir}' '${blddir}'"
|
||||
|
||||
# Run Sway. full-headless-sway-inner.sh ends with a ‘swaymsg exit’
|
||||
XDG_RUNTIME_DIR="${runtime_dir}" WLR_RENDERER=pixman WLR_BACKENDS=headless sway -c "${sway_conf}"
|
||||
# Run Sway. full-headless-sway-inner.sh ends with a 'swaymsg exit'
|
||||
XDG_RUNTIME_DIR="${runtime_dir}" WLR_RENDERER=pixman WLR_BACKENDS=headless sway -c "${sway_conf}" --unsupported-gpu
|
||||
|
||||
# Sway’s exit code doesn’t reflect our script’s exit code
|
||||
# Sway's exit code doesn't reflect our script's exit code
|
||||
[ -f "${blddir}"/pgo-ok ] || exit 1
|
||||
|
|
|
|||
69
pgo/pgo.c
69
pgo/pgo.c
|
|
@ -60,7 +60,8 @@ fdm_event_del(struct fdm *fdm, int fd, int events)
|
|||
}
|
||||
|
||||
bool
|
||||
render_resize_force(struct terminal *term, int width, int height)
|
||||
render_resize(
|
||||
struct terminal *term, int width, int height, uint8_t resize_options)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
@ -68,6 +69,12 @@ render_resize_force(struct terminal *term, int width, int height)
|
|||
void render_refresh(struct terminal *term) {}
|
||||
void render_refresh_csd(struct terminal *term) {}
|
||||
void render_refresh_title(struct terminal *term) {}
|
||||
void render_refresh_app_id(struct terminal *term) {}
|
||||
void render_refresh_icon(struct terminal *term) {}
|
||||
|
||||
void render_overlay(struct terminal *term) {}
|
||||
|
||||
void render_buffer_release_callback(struct buffer *buf, void *data) {}
|
||||
|
||||
bool
|
||||
render_xcursor_is_valid(const struct seat *seat, const char *cursor)
|
||||
|
|
@ -76,15 +83,15 @@ render_xcursor_is_valid(const struct seat *seat, const char *cursor)
|
|||
}
|
||||
|
||||
bool
|
||||
render_xcursor_set(struct seat *seat, struct terminal *term, const char *xcursor)
|
||||
render_xcursor_set(struct seat *seat, struct terminal *term, enum cursor_shape shape)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *
|
||||
enum cursor_shape
|
||||
xcursor_for_csd_border(struct terminal *term, int x, int y)
|
||||
{
|
||||
return XCURSOR_LEFT_PTR;
|
||||
return CURSOR_SHAPE_LEFT_PTR;
|
||||
}
|
||||
|
||||
struct wl_window *
|
||||
|
|
@ -94,14 +101,17 @@ wayl_win_init(struct terminal *term, const char *token)
|
|||
}
|
||||
|
||||
void wayl_win_destroy(struct wl_window *win) {}
|
||||
void wayl_win_alpha_changed(struct wl_window *win) {}
|
||||
bool wayl_win_set_urgent(struct wl_window *win) { return true; }
|
||||
bool wayl_win_ring_bell(const struct wl_window *win) { return true; }
|
||||
bool wayl_fractional_scaling(const struct wayland *wayl) { return true; }
|
||||
|
||||
bool
|
||||
pid_t
|
||||
spawn(struct reaper *reaper, const char *cwd, char *const argv[],
|
||||
int stdin_fd, int stdout_fd, int stderr_fd,
|
||||
const char *xdg_activation_token)
|
||||
reaper_cb cb, void *cb_data, const char *xdg_activation_token)
|
||||
{
|
||||
return true;
|
||||
return 2;
|
||||
}
|
||||
|
||||
pid_t
|
||||
|
|
@ -120,6 +130,12 @@ render_worker_thread(void *_ctx)
|
|||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
wayl_do_linear_blending(const struct wayland *wayl, const struct config *conf)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
struct extraction_context *
|
||||
extract_begin(enum selection_kind kind, bool strip_trailing_empty)
|
||||
{
|
||||
|
|
@ -147,8 +163,36 @@ void ime_enable(struct seat *seat) {}
|
|||
void ime_disable(struct seat *seat) {}
|
||||
void ime_reset_preedit(struct seat *seat) {}
|
||||
|
||||
bool
|
||||
notify_notify(struct terminal *term, struct notification *notif)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
notify_notify(const struct terminal *term, const char *title, const char *body)
|
||||
notify_close(struct terminal *term, const char *id)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
notify_free(struct terminal *term, struct notification *notif)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
notify_icon_add(struct terminal *term, const char *id,
|
||||
const char *symbolic_name, const uint8_t *data,
|
||||
size_t data_sz)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
notify_icon_del(struct terminal *term, const char *id)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
notify_icon_free(struct notification_icon *icon)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -159,9 +203,13 @@ void urls_reset(struct terminal *term) {}
|
|||
|
||||
void shm_unref(struct buffer *buf) {}
|
||||
void shm_chain_free(struct buffer_chain *chain) {}
|
||||
enum shm_bit_depth shm_chain_bit_depth(const struct buffer_chain *chain) { return SHM_BITS_8; }
|
||||
|
||||
struct buffer_chain *
|
||||
shm_chain_new(struct wl_shm *shm, bool scrollable, size_t pix_instances)
|
||||
shm_chain_new(
|
||||
struct wayland *wayl, bool scrollable, size_t pix_instances,
|
||||
enum shm_bit_depth desired_bit_depth,
|
||||
void (*release_cb)(struct buffer *buf, void *data), void *cb_data)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
|
@ -171,7 +219,8 @@ void search_selection_cancelled(struct terminal *term) {}
|
|||
|
||||
void get_current_modifiers(const struct seat *seat,
|
||||
xkb_mod_mask_t *effective,
|
||||
xkb_mod_mask_t *consumed, uint32_t key) {}
|
||||
xkb_mod_mask_t *consumed, uint32_t key,
|
||||
bool filter_locked) {}
|
||||
|
||||
static struct key_binding_set kbd;
|
||||
static bool kbd_initialized = false;
|
||||
|
|
|
|||
13
pgo/pgo.sh
13
pgo/pgo.sh
|
|
@ -54,13 +54,10 @@ case ${mode} in
|
|||
;;
|
||||
|
||||
auto)
|
||||
# TODO: once Sway 1.6.2 has been released, prefer
|
||||
# full-headless-sway
|
||||
|
||||
if [ -n "${WAYLAND_DISPLAY+x}" ]; then
|
||||
mode=full-current-session
|
||||
# elif command -v sway > /dev/null; then # Requires 1.6.2
|
||||
# mode=full-headless-sway
|
||||
elif command -v sway > /dev/null; then
|
||||
mode=full-headless-sway
|
||||
elif command -v cage > /dev/null; then
|
||||
mode=full-headless-cage
|
||||
else
|
||||
|
|
@ -82,6 +79,7 @@ set -x
|
|||
# echo "CFLAGS: ${CFLAGS}"
|
||||
|
||||
export CFLAGS
|
||||
export CCACHE_DISABLE=1
|
||||
meson setup --buildtype=release -Db_lto=true "${@}" "${blddir}" "${srcdir}"
|
||||
|
||||
if [ ${do_pgo} = yes ]; then
|
||||
|
|
@ -97,11 +95,12 @@ if [ ${do_pgo} = yes ]; then
|
|||
ninja -C "${blddir}"
|
||||
|
||||
# If fcft/tllist are subprojects, we need to ensure their tests
|
||||
# have been executed, or we’ll get “profile count data file not
|
||||
# found” errors.
|
||||
# have been executed, or we'll get "profile count data file not
|
||||
# found" errors.
|
||||
ninja -C "${blddir}" test
|
||||
|
||||
# Run mode-dependent script to generate profiling data
|
||||
export LLVM_PROFILE_FILE="${blddir}/default_%m.profraw"
|
||||
"${srcdir}"/pgo/${mode}.sh "${srcdir}" "${blddir}"
|
||||
|
||||
if [ ${compiler} = clang ]; then
|
||||
|
|
|
|||
10
pyproject.toml
Normal file
10
pyproject.toml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
[tool.pyright]
|
||||
strict = ['scripts']
|
||||
|
||||
[tool.mypy]
|
||||
files = '$MYPY_CONFIG_FILE_DIR/scripts'
|
||||
strict = true
|
||||
|
||||
[tool.codespell]
|
||||
skip = 'pyproject.toml,./subprojects,./pkg,./src,./bld,foot.info,./unicode,./venv'
|
||||
ignore-regex = 'terminfo capability `rin`|\* Simon Ser|\* \[zar\]\(https://codeberg.org/zar\)|iterm theme|iterm.toml|iterm/OneHalfDark.itermcolors'
|
||||
18
quirks.c
18
quirks.c
|
|
@ -66,3 +66,21 @@ quirk_weston_csd_off(struct terminal *term)
|
|||
for (int i = 0; i < ALEN(term->window->csd.surface); i++)
|
||||
quirk_weston_subsurface_desync_off(term->window->csd.surface[i].sub);
|
||||
}
|
||||
|
||||
#if 0
|
||||
static bool
|
||||
is_sway(void)
|
||||
{
|
||||
static bool is_sway = false;
|
||||
static bool initialized = false;
|
||||
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
is_sway = getenv("SWAYSOCK") != NULL;
|
||||
if (is_sway)
|
||||
LOG_WARN("applying wl_surface_damage_buffer() workaround for Sway");
|
||||
}
|
||||
|
||||
return is_sway;
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
20
render.h
20
render.h
|
|
@ -10,18 +10,29 @@ struct renderer;
|
|||
struct renderer *render_init(struct fdm *fdm, struct wayland *wayl);
|
||||
void render_destroy(struct renderer *renderer);
|
||||
|
||||
bool render_resize(struct terminal *term, int width, int height);
|
||||
bool render_resize_force(struct terminal *term, int width, int height);
|
||||
enum resize_options {
|
||||
RESIZE_NORMAL = 0,
|
||||
RESIZE_FORCE = 1 << 0,
|
||||
RESIZE_BY_CELLS = 1 << 1,
|
||||
RESIZE_KEEP_GRID = 1 << 2,
|
||||
};
|
||||
|
||||
bool render_resize(
|
||||
struct terminal *term, int width, int height, uint8_t resize_options);
|
||||
|
||||
void render_refresh(struct terminal *term);
|
||||
void render_refresh_app_id(struct terminal *term);
|
||||
void render_refresh_icon(struct terminal *term);
|
||||
void render_refresh_csd(struct terminal *term);
|
||||
void render_refresh_search(struct terminal *term);
|
||||
void render_refresh_title(struct terminal *term);
|
||||
void render_refresh_urls(struct terminal *term);
|
||||
bool render_xcursor_set(
|
||||
struct seat *seat, struct terminal *term, const char *xcursor);
|
||||
struct seat *seat, struct terminal *term, enum cursor_shape shape);
|
||||
bool render_xcursor_is_valid(const struct seat *seat, const char *cursor);
|
||||
|
||||
void render_overlay(struct terminal *term);
|
||||
|
||||
struct render_worker_context {
|
||||
int my_id;
|
||||
struct terminal *term;
|
||||
|
|
@ -36,3 +47,6 @@ struct csd_data {
|
|||
};
|
||||
|
||||
struct csd_data get_csd_data(const struct terminal *term, enum csd_surface surf_idx);
|
||||
|
||||
void render_buffer_release_callback(struct buffer *buf, void *data);
|
||||
void render_wait_for_preapply_damage(struct terminal *term);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import termios
|
|||
from datetime import datetime
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('files', type=argparse.FileType('rb'), nargs='+')
|
||||
parser.add_argument('--iterations', type=int, default=20)
|
||||
|
|
@ -24,12 +24,12 @@ def main():
|
|||
termios.TIOCGWINSZ,
|
||||
struct.pack('HHHH', 0, 0, 0, 0)))
|
||||
|
||||
times = {name: [] for name in [f.name for f in args.files]}
|
||||
times: dict[str, list[float]] = {name: [] for name in [f.name for f in args.files]}
|
||||
|
||||
for f in args.files:
|
||||
bench_bytes = f.read()
|
||||
|
||||
for i in range(args.iterations):
|
||||
for _ in range(args.iterations):
|
||||
start = datetime.now()
|
||||
sys.stdout.buffer.write(bench_bytes)
|
||||
stop = datetime.now()
|
||||
|
|
@ -48,4 +48,4 @@ def main():
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import struct
|
|||
import sys
|
||||
import termios
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
class ColorVariant(enum.IntEnum):
|
||||
NONE = enum.auto()
|
||||
|
|
@ -17,7 +19,7 @@ class ColorVariant(enum.IntEnum):
|
|||
RGB = enum.auto()
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
'out', type=argparse.FileType(mode='w'), nargs='?', help='name of output file')
|
||||
|
|
@ -38,10 +40,16 @@ def main():
|
|||
opts = parser.parse_args()
|
||||
out = opts.out if opts.out is not None else sys.stdout
|
||||
|
||||
lines: int | None = None
|
||||
cols: int | None = None
|
||||
width: int | None = None
|
||||
height: int | None = None
|
||||
|
||||
if opts.rows is None or opts.cols is None:
|
||||
try:
|
||||
def dummy(*args):
|
||||
def dummy(*args: Any) -> None:
|
||||
"""Need a handler installed for sigwait() to trigger."""
|
||||
_ = args
|
||||
pass
|
||||
signal.signal(signal.SIGWINCH, dummy)
|
||||
|
||||
|
|
@ -53,6 +61,9 @@ def main():
|
|||
termios.TIOCGWINSZ,
|
||||
struct.pack('HHHH', 0, 0, 0, 0)))
|
||||
|
||||
assert width is not None
|
||||
assert height is not None
|
||||
|
||||
if width > 0 and height > 0:
|
||||
break
|
||||
|
||||
|
|
@ -71,9 +82,11 @@ def main():
|
|||
|
||||
if opts.rows is not None:
|
||||
lines = opts.rows
|
||||
assert lines is not None
|
||||
height = 15 * lines # PGO helper binary hardcodes cell height to 15px
|
||||
if opts.cols is not None:
|
||||
cols = opts.cols
|
||||
assert cols is not None
|
||||
width = 8 * cols # PGO help binary hardcodes cell width to 8px
|
||||
|
||||
if lines is None or cols is None or height is None or width is None:
|
||||
|
|
@ -190,8 +203,8 @@ def main():
|
|||
# The sixel 'alphabet'
|
||||
sixels = '?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'
|
||||
|
||||
last_pos = None
|
||||
last_size = None
|
||||
last_pos: tuple[int, int] | None = None
|
||||
last_size: tuple[int, int] = 0, 0
|
||||
|
||||
for _ in range(20):
|
||||
if last_pos is not None and random.randrange(2):
|
||||
|
|
@ -207,8 +220,9 @@ def main():
|
|||
six_height, six_width = last_size
|
||||
six_rows = (six_height + 5) // 6 # Round up; each sixel is 6 pixels
|
||||
|
||||
# Begin sixel
|
||||
out.write('\033Pq')
|
||||
# Begin sixel (with P2 set to either 0 or 1 - opaque or transparent)
|
||||
sixel_p2 = random.randrange(2)
|
||||
out.write(f'\033P;{sixel_p2}q')
|
||||
|
||||
# Sixel size. Without this, sixels will be
|
||||
# auto-resized on cell-boundaries.
|
||||
|
|
@ -253,4 +267,4 @@ def main():
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from typing import Dict, Union
|
||||
|
||||
|
||||
class Capability:
|
||||
def __init__(self, name: str, value: Union[bool, int, str]):
|
||||
def __init__(self, name: str, value: bool | int | str):
|
||||
self._name = name
|
||||
self._value = value
|
||||
|
||||
|
|
@ -17,25 +15,37 @@ class Capability:
|
|||
return self._name
|
||||
|
||||
@property
|
||||
def value(self) -> Union[bool, int, str]:
|
||||
def value(self) -> bool | int | str:
|
||||
return self._value
|
||||
|
||||
def __lt__(self, other):
|
||||
def __lt__(self, other: object) -> bool:
|
||||
if not isinstance(other, Capability):
|
||||
return NotImplemented
|
||||
return self._name < other._name
|
||||
|
||||
def __le__(self, other):
|
||||
def __le__(self, other: object) -> bool:
|
||||
if not isinstance(other, Capability):
|
||||
return NotImplemented
|
||||
return self._name <= other._name
|
||||
|
||||
def __eq__(self, other):
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if not isinstance(other, Capability):
|
||||
return NotImplemented
|
||||
return self._name == other._name
|
||||
|
||||
def __ne__(self, other):
|
||||
return self._name != other._name
|
||||
def __ne__(self, other: object) -> bool:
|
||||
if not isinstance(other, Capability):
|
||||
return NotImplemented
|
||||
return bool(self._name != other._name)
|
||||
|
||||
def __gt__(self, other):
|
||||
return self._name > other._name
|
||||
def __gt__(self, other: object) -> bool:
|
||||
if not isinstance(other, Capability):
|
||||
return NotImplemented
|
||||
return bool(self._name > other._name)
|
||||
|
||||
def __ge__(self, other):
|
||||
def __ge__(self, other: object) -> bool:
|
||||
if not isinstance(other, Capability):
|
||||
return NotImplemented
|
||||
return self._name >= other._name
|
||||
|
||||
|
||||
|
|
@ -50,27 +60,33 @@ class IntCapability(Capability):
|
|||
|
||||
class StringCapability(Capability):
|
||||
def __init__(self, name: str, value: str):
|
||||
# Expand \E to literal ESC in non-parameterized capabilities
|
||||
if '%' not in value:
|
||||
# Ensure e.g. \E7 doesn’t get translated to “\0337”, which
|
||||
# would be interpreted as octal 337 by the C compiler
|
||||
value = re.sub(r'\\E([0-7])', r'\\033" "\1', value)
|
||||
# see terminfo(5) for valid escape sequences
|
||||
|
||||
# Replace \E with an actual escape
|
||||
value = re.sub(r'\\E', r'\\033', value)
|
||||
# Control characters
|
||||
def translate_ctrl_chr(m: re.Match[str]) -> str:
|
||||
ctrl = m.group(1)
|
||||
if ctrl == '?':
|
||||
return '\\x7f'
|
||||
return f'\\x{ord(ctrl) - ord("@"):02x}'
|
||||
value = re.sub(r'\^([@A-Z[\\\\\]^_?])', translate_ctrl_chr, value)
|
||||
|
||||
# Don’t escape ‘:’
|
||||
value = value.replace('\\:', ':')
|
||||
# Ensure e.g. \E7 (or \e7) doesn’t get translated to “\0337”,
|
||||
# which would be interpreted as octal 337 by the C compiler
|
||||
value = re.sub(r'(\\E|\\e)([0-7])', r'\\033" "\2', value)
|
||||
|
||||
else:
|
||||
value = value.replace("\\", "\\\\")
|
||||
# # Need to double-escape backslashes. These only occur in
|
||||
# # ‘\E\’ combos. Note that \E itself is updated below
|
||||
# value = value.replace('\\E\\\\', '\\E\\\\\\\\')
|
||||
# Replace \E and \e with ESC
|
||||
value = re.sub(r'\\E|\\e', r'\\033', value)
|
||||
|
||||
# # Need to double-escape \E in C string literals
|
||||
# value = value.replace('\\E', '\\\\E')
|
||||
# Unescape ,:^
|
||||
value = re.sub(r'\\(,|:|\^)', r'\1', value)
|
||||
|
||||
# Replace \s with space
|
||||
value = value.replace('\\s', ' ')
|
||||
|
||||
# Let \\, \n, \r, \t, \b and \f "fall through", to the C string literal
|
||||
|
||||
if re.search(r'\\l', value):
|
||||
raise NotImplementedError('\\l escape sequence')
|
||||
|
||||
super().__init__(name, value)
|
||||
|
||||
|
|
@ -79,7 +95,7 @@ class Fragment:
|
|||
def __init__(self, name: str, description: str):
|
||||
self._name = name
|
||||
self._description = description
|
||||
self._caps = {}
|
||||
self._caps = dict[str, Capability]()
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
|
|
@ -90,18 +106,18 @@ class Fragment:
|
|||
return self._description
|
||||
|
||||
@property
|
||||
def caps(self) -> Dict[str, Capability]:
|
||||
def caps(self) -> dict[str, Capability]:
|
||||
return self._caps
|
||||
|
||||
def add_capability(self, cap: Capability):
|
||||
def add_capability(self, cap: Capability) -> None:
|
||||
assert cap.name not in self._caps
|
||||
self._caps[cap.name] = cap
|
||||
|
||||
def del_capability(self, name: str):
|
||||
def del_capability(self, name: str) -> None:
|
||||
del self._caps[name]
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('source_entry_name')
|
||||
parser.add_argument('source', type=argparse.FileType('r'))
|
||||
|
|
@ -114,15 +130,15 @@ def main():
|
|||
source = opts.source
|
||||
target = opts.target
|
||||
|
||||
lines = []
|
||||
for l in source.readlines():
|
||||
l = l.strip()
|
||||
if l.startswith('#'):
|
||||
lines = list[str]()
|
||||
for line in source.readlines():
|
||||
line = line.strip()
|
||||
if line.startswith('#'):
|
||||
continue
|
||||
lines.append(l)
|
||||
lines.append(line)
|
||||
|
||||
fragments = {}
|
||||
cur_fragment = None
|
||||
fragments = dict[str, Fragment]()
|
||||
cur_fragment: Fragment | None = None
|
||||
|
||||
for m in re.finditer(
|
||||
r'(?P<name>(?P<entry_name>[-+\w@]+)\|(?P<entry_desc>.+?),)|'
|
||||
|
|
@ -141,17 +157,20 @@ def main():
|
|||
|
||||
elif m.group('bool_cap') is not None:
|
||||
name = m.group('bool_name')
|
||||
assert cur_fragment is not None
|
||||
cur_fragment.add_capability(BoolCapability(name))
|
||||
|
||||
elif m.group('int_cap') is not None:
|
||||
name = m.group('int_name')
|
||||
value = int(m.group('int_val'), 0)
|
||||
cur_fragment.add_capability(IntCapability(name, value))
|
||||
int_value = int(m.group('int_val'), 0)
|
||||
assert cur_fragment is not None
|
||||
cur_fragment.add_capability(IntCapability(name, int_value))
|
||||
|
||||
elif m.group('str_cap') is not None:
|
||||
name = m.group('str_name')
|
||||
value = m.group('str_val')
|
||||
cur_fragment.add_capability(StringCapability(name, value))
|
||||
str_value = m.group('str_val')
|
||||
assert cur_fragment is not None
|
||||
cur_fragment.add_capability(StringCapability(name, str_value))
|
||||
|
||||
else:
|
||||
assert False
|
||||
|
|
@ -160,6 +179,9 @@ def main():
|
|||
for frag in fragments.values():
|
||||
for cap in frag.caps.values():
|
||||
if cap.name == 'use':
|
||||
assert isinstance(cap, StringCapability)
|
||||
assert isinstance(cap.value, str)
|
||||
|
||||
use_frag = fragments[cap.value]
|
||||
for use_cap in use_frag.caps.values():
|
||||
frag.add_capability(use_cap)
|
||||
|
|
@ -179,8 +201,9 @@ def main():
|
|||
entry.add_capability(StringCapability('TN', target_entry_name))
|
||||
entry.add_capability(StringCapability('name', target_entry_name))
|
||||
entry.add_capability(IntCapability('RGB', 8)) # 8 bits per channel
|
||||
entry.add_capability(StringCapability('query-os-name', os.uname().sysname))
|
||||
|
||||
terminfo_parts = []
|
||||
terminfo_parts = list[str]()
|
||||
for cap in sorted(entry.caps.values()):
|
||||
name = cap.name
|
||||
value = str(cap.value)
|
||||
|
|
@ -204,4 +227,4 @@ def main():
|
|||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
main()
|
||||
|
|
|
|||
102
scripts/generate-emoji-variation-sequences.py
Normal file
102
scripts/generate-emoji-variation-sequences.py
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
|
||||
|
||||
class Codepoint:
|
||||
def __init__(self, start: int, end: None | int = None):
|
||||
self.start = start
|
||||
self.end = start if end is None else end
|
||||
self.vs15 = False
|
||||
self.vs16 = False
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'{self.start:x}-{self.end:x}, vs15={self.vs15}, vs16={self.vs16}'
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('input', type=argparse.FileType('r'))
|
||||
parser.add_argument('output', type=argparse.FileType('w'))
|
||||
opts = parser.parse_args()
|
||||
|
||||
codepoints: dict[int, Codepoint] = {}
|
||||
|
||||
for line in opts.input:
|
||||
line = line.rstrip()
|
||||
if not line:
|
||||
continue
|
||||
if line[0] == '#':
|
||||
continue
|
||||
|
||||
# Example: "0023 FE0E ; text style; # (1.1) NUMBER SIGN"
|
||||
cps, _ = line.split(';', maxsplit=1) # cps = "0023 FE0F "
|
||||
cps = cps.strip().split(' ') # cps = ["0023", "FE0F"]
|
||||
|
||||
if len(cps) != 2:
|
||||
raise NotImplementedError(f'emoji variation sequences with more than one base codepoint: {cps}')
|
||||
|
||||
cp, vs = cps # cp = "0023", vs = "FE0F"
|
||||
cp = int(cp, 16) # cp = 0x23
|
||||
vs = int(vs, 16) # vs = 0xfe0f
|
||||
|
||||
assert vs in [0xfe0e, 0xfe0f]
|
||||
|
||||
if cp not in codepoints:
|
||||
codepoints[cp] = Codepoint(cp)
|
||||
|
||||
assert codepoints[cp].start == cp
|
||||
|
||||
if vs == 0xfe0e:
|
||||
codepoints[cp].vs15 = True
|
||||
else:
|
||||
codepoints[cp].vs16 = True
|
||||
|
||||
sorted_list = sorted(codepoints.values(), key=lambda cp: cp.start)
|
||||
|
||||
compacted: list[Codepoint] = []
|
||||
for i, cp in enumerate(sorted_list):
|
||||
assert cp.end == cp.start
|
||||
|
||||
if i == 0:
|
||||
compacted.append(cp)
|
||||
continue
|
||||
|
||||
last_cp = compacted[-1]
|
||||
if last_cp.end == cp.start - 1 and last_cp.vs15 == cp.vs15 and last_cp.vs16 == cp.vs16:
|
||||
compacted[-1].end = cp.start
|
||||
else:
|
||||
compacted.append(cp)
|
||||
|
||||
opts.output.write('#pragma once\n')
|
||||
opts.output.write('#include <stdint.h>\n')
|
||||
opts.output.write('#include <stdbool.h>\n')
|
||||
opts.output.write('\n')
|
||||
opts.output.write('struct emoji_vs {\n')
|
||||
opts.output.write(' uint32_t start:21;\n')
|
||||
opts.output.write(' uint32_t end:21;\n')
|
||||
opts.output.write(' bool vs15:1;\n')
|
||||
opts.output.write(' bool vs16:1;\n')
|
||||
opts.output.write('} __attribute__((packed));\n')
|
||||
opts.output.write('_Static_assert(sizeof(struct emoji_vs) == 6, "unexpected struct size");\n')
|
||||
opts.output.write('\n')
|
||||
opts.output.write('#if defined(FOOT_GRAPHEME_CLUSTERING)\n')
|
||||
opts.output.write('\n')
|
||||
|
||||
opts.output.write(f'static const struct emoji_vs emoji_vs[{len(compacted)}] = {{\n')
|
||||
|
||||
for cp in compacted:
|
||||
opts.output.write(' {\n')
|
||||
opts.output.write(f' .start = 0x{cp.start:X},\n')
|
||||
opts.output.write(f' .end = 0x{cp.end:x},\n')
|
||||
opts.output.write(f' .vs15 = {"true" if cp.vs15 else "false"},\n')
|
||||
opts.output.write(f' .vs16 = {"true" if cp.vs16 else "false"},\n')
|
||||
opts.output.write(' },\n')
|
||||
|
||||
opts.output.write('};\n')
|
||||
opts.output.write('\n')
|
||||
opts.output.write('#endif /* FOOT_GRAPHEME_CLUSTERING */\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
70
scripts/srgb.py
Executable file
70
scripts/srgb.py
Executable file
|
|
@ -0,0 +1,70 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import math
|
||||
|
||||
|
||||
# Note: we use a pure gamma 2.2 function, rather than the piece-wise
|
||||
# sRGB transfer function, since that is what all compositors do.
|
||||
|
||||
def srgb_to_linear(f: float) -> float:
|
||||
assert(f >= 0 and f <= 1.0)
|
||||
return math.pow(f, 2.2)
|
||||
|
||||
|
||||
def linear_to_srgb(f: float) -> float:
|
||||
return math.pow(f, 1 / 2.2)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('c_output', type=argparse.FileType('w'))
|
||||
parser.add_argument('h_output', type=argparse.FileType('w'))
|
||||
opts = parser.parse_args()
|
||||
|
||||
linear_table: list[int] = []
|
||||
|
||||
for i in range(256):
|
||||
linear_table.append(int(srgb_to_linear(float(i) / 255) * 65535 + 0.5))
|
||||
|
||||
|
||||
opts.h_output.write("#pragma once\n")
|
||||
opts.h_output.write("#include <stdint.h>\n")
|
||||
opts.h_output.write("\n")
|
||||
opts.h_output.write('/* 8-bit input, 16-bit output */\n')
|
||||
opts.h_output.write("extern const uint16_t srgb_decode_8_to_16_table[256];")
|
||||
|
||||
opts.h_output.write('\n')
|
||||
opts.h_output.write('static inline uint16_t\n')
|
||||
opts.h_output.write('srgb_decode_8_to_16(uint8_t v)\n')
|
||||
opts.h_output.write('{\n')
|
||||
opts.h_output.write(' return srgb_decode_8_to_16_table[v];\n')
|
||||
opts.h_output.write('}\n')
|
||||
|
||||
opts.h_output.write('\n')
|
||||
opts.h_output.write('/* 8-bit input, 8-bit output */\n')
|
||||
opts.h_output.write("extern const uint8_t srgb_decode_8_to_8_table[256];\n")
|
||||
|
||||
opts.h_output.write('\n')
|
||||
opts.h_output.write('static inline uint8_t\n')
|
||||
opts.h_output.write('srgb_decode_8_to_8(uint8_t v)\n')
|
||||
opts.h_output.write('{\n')
|
||||
opts.h_output.write(' return srgb_decode_8_to_8_table[v];\n')
|
||||
opts.h_output.write('}\n')
|
||||
|
||||
opts.c_output.write('#include "srgb.h"\n')
|
||||
opts.c_output.write('\n')
|
||||
|
||||
opts.c_output.write("const uint16_t srgb_decode_8_to_16_table[256] = {\n")
|
||||
for i in range(256):
|
||||
opts.c_output.write(f' {linear_table[i]},\n')
|
||||
opts.c_output.write('};\n')
|
||||
|
||||
opts.c_output.write("const uint8_t srgb_decode_8_to_8_table[256] = {\n")
|
||||
for i in range(256):
|
||||
opts.c_output.write(f' {linear_table[i] >> 8},\n')
|
||||
opts.c_output.write('};\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
597
search.c
597
search.c
|
|
@ -9,12 +9,14 @@
|
|||
#define LOG_ENABLE_DBG 0
|
||||
#include "log.h"
|
||||
#include "char32.h"
|
||||
#include "commands.h"
|
||||
#include "config.h"
|
||||
#include "extract.h"
|
||||
#include "grid.h"
|
||||
#include "input.h"
|
||||
#include "key-binding.h"
|
||||
#include "misc.h"
|
||||
#include "quirks.h"
|
||||
#include "render.h"
|
||||
#include "selection.h"
|
||||
#include "shm.h"
|
||||
|
|
@ -81,7 +83,14 @@ search_ensure_size(struct terminal *term, size_t wanted_size)
|
|||
}
|
||||
|
||||
static bool
|
||||
has_wrapped_around(const struct terminal *term, int abs_row_no)
|
||||
has_wrapped_around_left(const struct terminal *term, int abs_row_no)
|
||||
{
|
||||
int rebased_row = grid_row_abs_to_sb(term->grid, term->rows, abs_row_no);
|
||||
return rebased_row == term->grid->num_rows - 1 || term->grid->rows[abs_row_no] == NULL;
|
||||
}
|
||||
|
||||
static bool
|
||||
has_wrapped_around_right(const struct terminal *term, int abs_row_no)
|
||||
{
|
||||
int rebased_row = grid_row_abs_to_sb(term->grid, term->rows, abs_row_no);
|
||||
return rebased_row == 0;
|
||||
|
|
@ -117,11 +126,6 @@ search_cancel_keep_selection(struct terminal *term)
|
|||
|
||||
term_xcursor_update(term);
|
||||
render_refresh(term);
|
||||
|
||||
/* Work around Sway bug - unmapping a sub-surface does not damage
|
||||
* the underlying surface */
|
||||
term_damage_margins(term);
|
||||
term_damage_view(term);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -239,13 +243,19 @@ search_update_selection(struct terminal *term, const struct range *match)
|
|||
}
|
||||
|
||||
if (start_row != term->search.match.row ||
|
||||
start_col != term->search.match.col)
|
||||
start_col != term->search.match.col ||
|
||||
|
||||
/* Pointer leave events trigger selection_finalize() :/ */
|
||||
!term->selection.ongoing)
|
||||
{
|
||||
int selection_row = start_row - grid->view + grid->num_rows;
|
||||
selection_row &= grid->num_rows - 1;
|
||||
|
||||
selection_start(
|
||||
term, start_col, selection_row, SELECTION_CHAR_WISE, false);
|
||||
|
||||
term->search.match.row = start_row;
|
||||
term->search.match.col = start_col;
|
||||
}
|
||||
|
||||
/* Update selection endpoint */
|
||||
|
|
@ -273,20 +283,25 @@ matches_cell(const struct terminal *term, const struct cell *cell, size_t search
|
|||
if (composed == NULL && base == 0 && term->search.buf[search_ofs] == U' ')
|
||||
return 1;
|
||||
|
||||
if (c32ncasecmp(&base, &term->search.buf[search_ofs], 1) != 0)
|
||||
return -1;
|
||||
if (hasc32upper(term->search.buf)) {
|
||||
if (c32ncmp(&base, &term->search.buf[search_ofs], 1) != 0)
|
||||
return -1;
|
||||
} else {
|
||||
if (c32ncasecmp(&base, &term->search.buf[search_ofs], 1) != 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (composed != NULL) {
|
||||
if (search_ofs + 1 + composed->count > term->search.len)
|
||||
if (search_ofs + composed->count > term->search.len)
|
||||
return -1;
|
||||
|
||||
for (size_t j = 1; j < composed->count; j++) {
|
||||
if (composed->chars[j] != term->search.buf[search_ofs + 1 + j])
|
||||
if (composed->chars[j] != term->search.buf[search_ofs + j])
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return composed != NULL ? 1 + composed->count : 1;
|
||||
return composed != NULL ? composed->count : 1;
|
||||
}
|
||||
|
||||
static bool
|
||||
|
|
@ -373,8 +388,11 @@ find_next(struct terminal *term, enum search_direction direction,
|
|||
match_len += additional_chars;
|
||||
match_end_col++;
|
||||
|
||||
while (match_row->cells[match_end_col].wc > CELL_SPACER)
|
||||
while (match_end_col < term->cols &&
|
||||
match_row->cells[match_end_col].wc > CELL_SPACER)
|
||||
{
|
||||
match_end_col++;
|
||||
}
|
||||
}
|
||||
|
||||
if (match_len != term->search.len) {
|
||||
|
|
@ -550,6 +568,7 @@ search_matches_next(struct search_match_iterator *iter)
|
|||
term->cols - 1,
|
||||
grid_row_absolute_in_view(grid, term->rows - 1)};
|
||||
|
||||
/* BUG: matches *starting* outside the view, but ending *inside*, aren't matched */
|
||||
struct range match;
|
||||
bool found = find_next(term, SEARCH_FORWARD, abs_start, abs_end, &match);
|
||||
if (!found)
|
||||
|
|
@ -569,11 +588,6 @@ search_matches_next(struct search_match_iterator *iter)
|
|||
match.start.row, match.start.col,
|
||||
match.end.row, match.end.col, grid->view);
|
||||
|
||||
xassert(match.start.row >= 0);
|
||||
xassert(match.start.row < term->rows);
|
||||
xassert(match.end.row >= 0);
|
||||
xassert(match.end.row < term->rows);
|
||||
|
||||
/* Assert match end comes *after* the match start */
|
||||
xassert(match.end.row > match.start.row ||
|
||||
(match.end.row == match.start.row &&
|
||||
|
|
@ -646,67 +660,299 @@ search_add_chars(struct terminal *term, const char *src, size_t count)
|
|||
add_wchars(term, c32s, chars);
|
||||
}
|
||||
|
||||
enum extend_direction {SEARCH_EXTEND_LEFT, SEARCH_EXTEND_RIGHT};
|
||||
|
||||
static bool
|
||||
coord_advance_left(const struct terminal *term, struct coord *pos,
|
||||
const struct row **row)
|
||||
{
|
||||
const struct grid *grid = term->grid;
|
||||
struct coord new_pos = *pos;
|
||||
|
||||
if (--new_pos.col < 0) {
|
||||
new_pos.row = (new_pos.row - 1 + grid->num_rows) & (grid->num_rows - 1);
|
||||
new_pos.col = term->cols - 1;
|
||||
|
||||
if (has_wrapped_around_left(term, new_pos.row))
|
||||
return false;
|
||||
|
||||
if (row != NULL)
|
||||
*row = grid->rows[new_pos.row];
|
||||
}
|
||||
|
||||
*pos = new_pos;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
coord_advance_right(const struct terminal *term, struct coord *pos,
|
||||
const struct row **row)
|
||||
{
|
||||
const struct grid *grid = term->grid;
|
||||
struct coord new_pos = *pos;
|
||||
|
||||
if (++new_pos.col >= term->cols) {
|
||||
new_pos.row = (new_pos.row + 1) & (grid->num_rows - 1);
|
||||
new_pos.col = 0;
|
||||
|
||||
if (has_wrapped_around_right(term, new_pos.row))
|
||||
return false;
|
||||
|
||||
if (row != NULL)
|
||||
*row = grid->rows[new_pos.row];
|
||||
}
|
||||
|
||||
*pos = new_pos;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
search_extend_find_char(const struct terminal *term, struct coord *target,
|
||||
enum extend_direction direction)
|
||||
{
|
||||
if (term->search.match_len == 0)
|
||||
return false;
|
||||
|
||||
struct coord pos = direction == SEARCH_EXTEND_LEFT
|
||||
? selection_get_start(term) : selection_get_end(term);
|
||||
xassert(pos.row >= 0);
|
||||
xassert(pos.row < term->grid->num_rows);
|
||||
|
||||
*target = pos;
|
||||
|
||||
const struct row *row = term->grid->rows[pos.row];
|
||||
|
||||
while (true) {
|
||||
switch (direction) {
|
||||
case SEARCH_EXTEND_LEFT:
|
||||
if (!coord_advance_left(term, &pos, &row))
|
||||
return false;
|
||||
break;
|
||||
|
||||
case SEARCH_EXTEND_RIGHT:
|
||||
if (!coord_advance_right(term, &pos, &row))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
const char32_t wc = row->cells[pos.col].wc;
|
||||
|
||||
if (wc >= CELL_SPACER || wc == U'\0')
|
||||
continue;
|
||||
|
||||
*target = pos;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
search_extend_find_char_left(const struct terminal *term, struct coord *target)
|
||||
{
|
||||
return search_extend_find_char(term, target, SEARCH_EXTEND_LEFT);
|
||||
}
|
||||
|
||||
static bool
|
||||
search_extend_find_char_right(const struct terminal *term, struct coord *target)
|
||||
{
|
||||
return search_extend_find_char(term, target, SEARCH_EXTEND_RIGHT);
|
||||
}
|
||||
|
||||
static bool
|
||||
search_extend_find_word(const struct terminal *term, bool spaces_only,
|
||||
struct coord *target, enum extend_direction direction)
|
||||
{
|
||||
if (term->search.match_len == 0)
|
||||
return false;
|
||||
|
||||
struct grid *grid = term->grid;
|
||||
struct coord pos = direction == SEARCH_EXTEND_LEFT
|
||||
? selection_get_start(term)
|
||||
: selection_get_end(term);
|
||||
|
||||
xassert(pos.row >= 0);
|
||||
xassert(pos.row < grid->num_rows);
|
||||
|
||||
*target = pos;
|
||||
|
||||
/* First character to consider is the *next* character */
|
||||
switch (direction) {
|
||||
case SEARCH_EXTEND_LEFT:
|
||||
if (!coord_advance_left(term, &pos, NULL))
|
||||
return false;
|
||||
break;
|
||||
|
||||
case SEARCH_EXTEND_RIGHT:
|
||||
if (!coord_advance_right(term, &pos, NULL))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
xassert(pos.row >= 0);
|
||||
xassert(pos.row < grid->num_rows);
|
||||
xassert(grid->rows[pos.row] != NULL);
|
||||
|
||||
/* Find next word boundary */
|
||||
switch (direction) {
|
||||
case SEARCH_EXTEND_LEFT:
|
||||
selection_find_word_boundary_left(term, &pos, spaces_only);
|
||||
break;
|
||||
|
||||
case SEARCH_EXTEND_RIGHT:
|
||||
selection_find_word_boundary_right(term, &pos, spaces_only, false);
|
||||
break;
|
||||
}
|
||||
|
||||
*target = pos;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
search_extend_find_word_left(const struct terminal *term, bool spaces_only,
|
||||
struct coord *target)
|
||||
{
|
||||
return search_extend_find_word(term, spaces_only, target, SEARCH_EXTEND_LEFT);
|
||||
}
|
||||
|
||||
static bool
|
||||
search_extend_find_word_right(const struct terminal *term, bool spaces_only,
|
||||
struct coord *target)
|
||||
{
|
||||
return search_extend_find_word(term, spaces_only, target, SEARCH_EXTEND_RIGHT);
|
||||
}
|
||||
|
||||
static bool
|
||||
search_extend_find_line(const struct terminal *term, struct coord *target,
|
||||
enum extend_direction direction)
|
||||
{
|
||||
if (term->search.match_len == 0)
|
||||
return false;
|
||||
|
||||
struct coord pos = direction == SEARCH_EXTEND_LEFT
|
||||
? selection_get_start(term) : selection_get_end(term);
|
||||
|
||||
xassert(pos.row >= 0);
|
||||
xassert(pos.row < term->grid->num_rows);
|
||||
|
||||
*target = pos;
|
||||
|
||||
const struct grid *grid = term->grid;
|
||||
|
||||
switch (direction) {
|
||||
case SEARCH_EXTEND_LEFT:
|
||||
pos.row = (pos.row - 1 + grid->num_rows) & (grid->num_rows - 1);
|
||||
if (has_wrapped_around_left(term, pos.row))
|
||||
return false;
|
||||
break;
|
||||
|
||||
case SEARCH_EXTEND_RIGHT:
|
||||
pos.row = (pos.row + 1) & (grid->num_rows - 1);
|
||||
if (has_wrapped_around_right(term, pos.row))
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
*target = pos;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
search_extend_find_line_up(const struct terminal *term, struct coord *target)
|
||||
{
|
||||
return search_extend_find_line(term, target, SEARCH_EXTEND_LEFT);
|
||||
}
|
||||
|
||||
static bool
|
||||
search_extend_find_line_down(const struct terminal *term, struct coord *target)
|
||||
{
|
||||
return search_extend_find_line(term, target, SEARCH_EXTEND_RIGHT);
|
||||
}
|
||||
|
||||
static void
|
||||
search_match_to_end_of_word(struct terminal *term, bool spaces_only)
|
||||
search_extend_left(struct terminal *term, const struct coord *target)
|
||||
{
|
||||
if (term->search.match_len == 0)
|
||||
return;
|
||||
|
||||
xassert(term->selection.coords.end.row >= 0);
|
||||
const struct coord last_coord = selection_get_start(term);
|
||||
struct coord pos = *target;
|
||||
const struct row *row = term->grid->rows[pos.row];
|
||||
|
||||
struct grid *grid = term->grid;
|
||||
const bool move_cursor = term->search.cursor == term->search.len;
|
||||
const bool move_cursor = term->search.cursor != 0;
|
||||
|
||||
struct coord old_end = selection_get_end(term);
|
||||
struct coord new_end = old_end;
|
||||
struct row *row = NULL;
|
||||
|
||||
xassert(new_end.row >= 0);
|
||||
xassert(new_end.row < grid->num_rows);
|
||||
|
||||
/* Advances a coordinate by one column, to the right. Returns
|
||||
* false if we’ve reached the scrollback wrap-around */
|
||||
#define advance_pos(coord) __extension__ \
|
||||
({ \
|
||||
bool wrapped_around = false; \
|
||||
if (++(coord).col >= term->cols) { \
|
||||
(coord).row = ((coord).row + 1) & (grid->num_rows - 1); \
|
||||
(coord).col = 0; \
|
||||
row = grid->rows[(coord).row]; \
|
||||
if (has_wrapped_around(term, (coord.row))) \
|
||||
wrapped_around = true; \
|
||||
} \
|
||||
!wrapped_around; \
|
||||
})
|
||||
|
||||
/* First character to consider is the *next* character */
|
||||
if (!advance_pos(new_end))
|
||||
struct extraction_context *ctx = extract_begin(SELECTION_NONE, false);
|
||||
if (ctx == NULL)
|
||||
return;
|
||||
|
||||
xassert(new_end.row >= 0);
|
||||
xassert(new_end.row < grid->num_rows);
|
||||
xassert(grid->rows[new_end.row] != NULL);
|
||||
while (pos.col != last_coord.col || pos.row != last_coord.row) {
|
||||
if (!extract_one(term, row, &row->cells[pos.col], pos.col, ctx))
|
||||
break;
|
||||
if (!coord_advance_right(term, &pos, &row))
|
||||
break;
|
||||
}
|
||||
|
||||
/* Find next word boundary */
|
||||
new_end.row -= grid->view + grid->num_rows;
|
||||
new_end.row &= grid->num_rows - 1;
|
||||
selection_find_word_boundary_right(term, &new_end, spaces_only, false);
|
||||
new_end.row += grid->view;
|
||||
new_end.row &= grid->num_rows - 1;
|
||||
char32_t *new_text;
|
||||
size_t new_len;
|
||||
|
||||
struct coord pos = old_end;
|
||||
row = grid->rows[pos.row];
|
||||
if (!extract_finish_wide(ctx, &new_text, &new_len))
|
||||
return;
|
||||
|
||||
if (!search_ensure_size(term, term->search.len + new_len))
|
||||
return;
|
||||
|
||||
memmove(&term->search.buf[new_len], &term->search.buf[0],
|
||||
term->search.len * sizeof(term->search.buf[0]));
|
||||
|
||||
size_t actually_copied = 0;
|
||||
for (size_t i = 0; i < new_len; i++) {
|
||||
if (new_text[i] == U'\n') {
|
||||
/* extract() adds newlines, which we never match against */
|
||||
continue;
|
||||
}
|
||||
|
||||
term->search.buf[actually_copied++] = new_text[i];
|
||||
term->search.len++;
|
||||
}
|
||||
|
||||
xassert(actually_copied <= new_len);
|
||||
if (actually_copied < new_len) {
|
||||
memmove(
|
||||
&term->search.buf[actually_copied], &term->search.buf[new_len],
|
||||
(term->search.len - actually_copied) * sizeof(term->search.buf[0]));
|
||||
}
|
||||
|
||||
term->search.buf[term->search.len] = U'\0';
|
||||
free(new_text);
|
||||
|
||||
if (move_cursor)
|
||||
term->search.cursor += actually_copied;
|
||||
|
||||
struct range match = {.start = *target, .end = selection_get_end(term)};
|
||||
search_update_selection(term, &match);
|
||||
|
||||
term->search.match_len = term->search.len;
|
||||
}
|
||||
|
||||
static void
|
||||
search_extend_right(struct terminal *term, const struct coord *target)
|
||||
{
|
||||
if (term->search.match_len == 0)
|
||||
return;
|
||||
|
||||
struct coord pos = selection_get_end(term);
|
||||
const struct row *row = term->grid->rows[pos.row];
|
||||
|
||||
const bool move_cursor = term->search.cursor == term->search.len;
|
||||
|
||||
struct extraction_context *ctx = extract_begin(SELECTION_NONE, false);
|
||||
if (ctx == NULL)
|
||||
return;
|
||||
|
||||
do {
|
||||
if (!advance_pos(pos))
|
||||
if (!coord_advance_right(term, &pos, &row))
|
||||
break;
|
||||
if (!extract_one(term, row, &row->cells[pos.col], pos.col, ctx))
|
||||
break;
|
||||
} while (pos.col != new_end.col || pos.row != new_end.row);
|
||||
} while (pos.col != target->col || pos.row != target->row);
|
||||
|
||||
char32_t *new_text;
|
||||
size_t new_len;
|
||||
|
|
@ -732,12 +978,9 @@ search_match_to_end_of_word(struct terminal *term, bool spaces_only)
|
|||
if (move_cursor)
|
||||
term->search.cursor = term->search.len;
|
||||
|
||||
struct range match = {.start = term->search.match, .end = new_end};
|
||||
struct range match = {.start = term->search.match, .end = *target};
|
||||
search_update_selection(term, &match);
|
||||
|
||||
term->search.match_len = term->search.len;
|
||||
|
||||
#undef advance_pos
|
||||
}
|
||||
|
||||
static size_t
|
||||
|
|
@ -826,6 +1069,62 @@ execute_binding(struct seat *seat, struct terminal *term,
|
|||
case BIND_ACTION_SEARCH_NONE:
|
||||
return false;
|
||||
|
||||
case BIND_ACTION_SEARCH_SCROLLBACK_UP_PAGE:
|
||||
if (term->grid == &term->normal) {
|
||||
cmd_scrollback_up(term, term->rows);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
case BIND_ACTION_SEARCH_SCROLLBACK_UP_HALF_PAGE:
|
||||
if (term->grid == &term->normal) {
|
||||
cmd_scrollback_up(term, max(term->rows / 2, 1));
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case BIND_ACTION_SEARCH_SCROLLBACK_UP_LINE:
|
||||
if (term->grid == &term->normal) {
|
||||
cmd_scrollback_up(term, 1);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case BIND_ACTION_SEARCH_SCROLLBACK_DOWN_PAGE:
|
||||
if (term->grid == &term->normal) {
|
||||
cmd_scrollback_down(term, term->rows);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
case BIND_ACTION_SEARCH_SCROLLBACK_DOWN_HALF_PAGE:
|
||||
if (term->grid == &term->normal) {
|
||||
cmd_scrollback_down(term, max(term->rows / 2, 1));
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case BIND_ACTION_SEARCH_SCROLLBACK_DOWN_LINE:
|
||||
if (term->grid == &term->normal) {
|
||||
cmd_scrollback_down(term, 1);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case BIND_ACTION_SEARCH_SCROLLBACK_HOME:
|
||||
if (term->grid == &term->normal) {
|
||||
cmd_scrollback_up(term, term->grid->num_rows);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case BIND_ACTION_SEARCH_SCROLLBACK_END:
|
||||
if (term->grid == &term->normal) {
|
||||
cmd_scrollback_down(term, term->grid->num_rows);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case BIND_ACTION_SEARCH_CANCEL:
|
||||
if (term->search.view_followed_offset)
|
||||
grid->view = grid->offset;
|
||||
|
|
@ -833,6 +1132,7 @@ execute_binding(struct seat *seat, struct terminal *term,
|
|||
grid->view = ensure_view_is_allocated(
|
||||
term, term->search.original_view);
|
||||
}
|
||||
term_damage_view(term);
|
||||
search_cancel(term);
|
||||
return true;
|
||||
|
||||
|
|
@ -970,32 +1270,123 @@ execute_binding(struct seat *seat, struct terminal *term,
|
|||
return true;
|
||||
}
|
||||
|
||||
case BIND_ACTION_SEARCH_EXTEND_WORD:
|
||||
search_match_to_end_of_word(term, false);
|
||||
*update_search_result = false;
|
||||
*redraw = true;
|
||||
return true;
|
||||
case BIND_ACTION_SEARCH_DELETE_TO_START: {
|
||||
if (term->search.cursor > 0) {
|
||||
memmove(&term->search.buf[0],
|
||||
&term->search.buf[term->search.cursor],
|
||||
(term->search.len - term->search.cursor)
|
||||
* sizeof(char32_t));
|
||||
|
||||
case BIND_ACTION_SEARCH_EXTEND_WORD_WS:
|
||||
search_match_to_end_of_word(term, true);
|
||||
*update_search_result = false;
|
||||
*redraw = true;
|
||||
term->search.len -= term->search.cursor;
|
||||
term->search.cursor = 0;
|
||||
*update_search_result = *redraw = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case BIND_ACTION_SEARCH_DELETE_TO_END: {
|
||||
if (term->search.cursor < term->search.len) {
|
||||
term->search.buf[term->search.cursor] = '\0';
|
||||
term->search.len = term->search.cursor;
|
||||
*update_search_result = *redraw = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case BIND_ACTION_SEARCH_EXTEND_CHAR: {
|
||||
struct coord target;
|
||||
if (search_extend_find_char_right(term, &target)) {
|
||||
search_extend_right(term, &target);
|
||||
*update_search_result = false;
|
||||
*redraw = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case BIND_ACTION_SEARCH_EXTEND_WORD: {
|
||||
struct coord target;
|
||||
if (search_extend_find_word_right(term, false, &target)) {
|
||||
search_extend_right(term, &target);
|
||||
*update_search_result = false;
|
||||
*redraw = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case BIND_ACTION_SEARCH_EXTEND_WORD_WS: {
|
||||
struct coord target;
|
||||
if (search_extend_find_word_right(term, true, &target)) {
|
||||
search_extend_right(term, &target);
|
||||
*update_search_result = false;
|
||||
*redraw = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case BIND_ACTION_SEARCH_EXTEND_LINE_DOWN: {
|
||||
struct coord target;
|
||||
if (search_extend_find_line_down(term, &target)) {
|
||||
search_extend_right(term, &target);
|
||||
*update_search_result = false;
|
||||
*redraw = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case BIND_ACTION_SEARCH_EXTEND_BACKWARD_CHAR: {
|
||||
struct coord target;
|
||||
if (search_extend_find_char_left(term, &target)) {
|
||||
search_extend_left(term, &target);
|
||||
*update_search_result = false;
|
||||
*redraw = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case BIND_ACTION_SEARCH_EXTEND_BACKWARD_WORD: {
|
||||
struct coord target;
|
||||
if (search_extend_find_word_left(term, false, &target)) {
|
||||
search_extend_left(term, &target);
|
||||
*update_search_result = false;
|
||||
*redraw = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case BIND_ACTION_SEARCH_EXTEND_BACKWARD_WORD_WS: {
|
||||
struct coord target;
|
||||
if (search_extend_find_word_left(term, true, &target)) {
|
||||
search_extend_left(term, &target);
|
||||
*update_search_result = false;
|
||||
*redraw = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case BIND_ACTION_SEARCH_EXTEND_LINE_UP: {
|
||||
struct coord target;
|
||||
if (search_extend_find_line_up(term, &target)) {
|
||||
search_extend_left(term, &target);
|
||||
*update_search_result = false;
|
||||
*redraw = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case BIND_ACTION_SEARCH_CLIPBOARD_PASTE:
|
||||
text_from_clipboard(
|
||||
seat, term, &from_clipboard_cb, &from_clipboard_done, term);
|
||||
seat, term, false, &from_clipboard_cb, &from_clipboard_done, term);
|
||||
*update_search_result = *redraw = true;
|
||||
return true;
|
||||
|
||||
case BIND_ACTION_SEARCH_PRIMARY_PASTE:
|
||||
text_from_primary(
|
||||
seat, term, &from_clipboard_cb, &from_clipboard_done, term);
|
||||
seat, term, false, &from_clipboard_cb, &from_clipboard_done, term);
|
||||
*update_search_result = *redraw = true;
|
||||
return true;
|
||||
|
||||
case BIND_ACTION_SEARCH_UNICODE_INPUT:
|
||||
unicode_mode_activate(seat);
|
||||
unicode_mode_activate(term);
|
||||
return true;
|
||||
|
||||
case BIND_ACTION_SEARCH_COUNT:
|
||||
|
|
@ -1011,17 +1402,12 @@ void
|
|||
search_input(struct seat *seat, struct terminal *term,
|
||||
const struct key_binding_set *bindings, uint32_t key,
|
||||
xkb_keysym_t sym, xkb_mod_mask_t mods, xkb_mod_mask_t consumed,
|
||||
xkb_mod_mask_t locked,
|
||||
const xkb_keysym_t *raw_syms, size_t raw_count,
|
||||
uint32_t serial)
|
||||
{
|
||||
LOG_DBG("search: input: sym=%d/0x%x, mods=0x%08x, consumed=0x%08x",
|
||||
sym, sym, mods, consumed);
|
||||
|
||||
const xkb_mod_mask_t bind_mods =
|
||||
mods & seat->kbd.bind_significant & ~locked;
|
||||
const xkb_mod_mask_t bind_consumed =
|
||||
consumed & seat->kbd.bind_significant & ~locked;
|
||||
enum xkb_compose_status compose_status = seat->kbd.xkb_compose_state != NULL
|
||||
? xkb_compose_state_get_status(seat->kbd.xkb_compose_state)
|
||||
: XKB_COMPOSE_NOTHING;
|
||||
|
|
@ -1030,27 +1416,17 @@ search_input(struct seat *seat, struct terminal *term,
|
|||
bool update_search_result = false;
|
||||
bool redraw = false;
|
||||
|
||||
/* Key bindings */
|
||||
/*
|
||||
* Key bindings
|
||||
*/
|
||||
|
||||
/* Match untranslated symbols */
|
||||
tll_foreach(bindings->search, it) {
|
||||
const struct key_binding *bind = &it->item;
|
||||
|
||||
/* Match translated symbol */
|
||||
if (bind->k.sym == sym &&
|
||||
bind->mods == (bind_mods & ~bind_consumed)) {
|
||||
|
||||
if (execute_binding(seat, term, bind, serial,
|
||||
&update_search_result, &search_direction,
|
||||
&redraw))
|
||||
{
|
||||
goto update_search;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (bind->mods != bind_mods || bind_mods != (mods & ~locked))
|
||||
if (bind->mods != mods || bind->mods == 0)
|
||||
continue;
|
||||
|
||||
/* Match untranslated symbols */
|
||||
for (size_t i = 0; i < raw_count; i++) {
|
||||
if (bind->k.sym == raw_syms[i]) {
|
||||
if (execute_binding(seat, term, bind, serial,
|
||||
|
|
@ -1062,8 +1438,32 @@ search_input(struct seat *seat, struct terminal *term,
|
|||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Match translated symbol */
|
||||
tll_foreach(bindings->search, it) {
|
||||
const struct key_binding *bind = &it->item;
|
||||
|
||||
if (bind->k.sym == sym &&
|
||||
bind->mods == (mods & ~consumed)) {
|
||||
|
||||
if (execute_binding(seat, term, bind, serial,
|
||||
&update_search_result, &search_direction,
|
||||
&redraw))
|
||||
{
|
||||
goto update_search;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Match raw key code */
|
||||
tll_foreach(bindings->search, it) {
|
||||
const struct key_binding *bind = &it->item;
|
||||
|
||||
if (bind->mods != mods || bind->mods == 0)
|
||||
continue;
|
||||
|
||||
/* Match raw key code */
|
||||
tll_foreach(bind->k.key_codes, code) {
|
||||
if (code->item == key) {
|
||||
if (execute_binding(seat, term, bind, serial,
|
||||
|
|
@ -1084,7 +1484,8 @@ search_input(struct seat *seat, struct terminal *term,
|
|||
count = xkb_compose_state_get_utf8(
|
||||
seat->kbd.xkb_compose_state, (char *)buf, sizeof(buf));
|
||||
xkb_compose_state_reset(seat->kbd.xkb_compose_state);
|
||||
} else if (compose_status == XKB_COMPOSE_CANCELLED) {
|
||||
} else if (compose_status == XKB_COMPOSE_CANCELLED ||
|
||||
compose_status == XKB_COMPOSE_COMPOSING) {
|
||||
count = 0;
|
||||
} else {
|
||||
count = xkb_state_key_get_utf8(
|
||||
|
|
|
|||
1
search.h
1
search.h
|
|
@ -11,7 +11,6 @@ void search_input(
|
|||
struct seat *seat, struct terminal *term,
|
||||
const struct key_binding_set *bindings, uint32_t key,
|
||||
xkb_keysym_t sym, xkb_mod_mask_t mods, xkb_mod_mask_t consumed,
|
||||
xkb_mod_mask_t locked,
|
||||
const xkb_keysym_t *raw_syms, size_t raw_count,
|
||||
uint32_t serial);
|
||||
void search_add_chars(struct terminal *term, const char *text, size_t len);
|
||||
|
|
|
|||
364
selection.c
364
selection.c
|
|
@ -19,6 +19,7 @@
|
|||
#include "char32.h"
|
||||
#include "commands.h"
|
||||
#include "config.h"
|
||||
#include "debug.h"
|
||||
#include "extract.h"
|
||||
#include "grid.h"
|
||||
#include "misc.h"
|
||||
|
|
@ -298,6 +299,7 @@ foreach_selected(
|
|||
switch (term->selection.kind) {
|
||||
case SELECTION_CHAR_WISE:
|
||||
case SELECTION_WORD_WISE:
|
||||
case SELECTION_QUOTE_WISE:
|
||||
case SELECTION_LINE_WISE:
|
||||
foreach_selected_normal(term, start, end, cb, data);
|
||||
return;
|
||||
|
|
@ -339,16 +341,19 @@ selection_to_text(const struct terminal *term)
|
|||
return extract_finish(ctx, &text, NULL) ? text : NULL;
|
||||
}
|
||||
|
||||
/* Coordinates are in *absolute* row numbers (NOT view local) */
|
||||
void
|
||||
selection_find_word_boundary_left(struct terminal *term, struct coord *pos,
|
||||
selection_find_word_boundary_left(const struct terminal *term, struct coord *pos,
|
||||
bool spaces_only)
|
||||
{
|
||||
xassert(pos->row >= 0);
|
||||
xassert(pos->row < term->rows);
|
||||
const struct grid *grid = term->grid;
|
||||
|
||||
xassert(pos->col >= 0);
|
||||
xassert(pos->col < term->cols);
|
||||
xassert(pos->row >= 0);
|
||||
pos->row &= grid->num_rows - 1;
|
||||
|
||||
const struct row *r = grid_row_in_view(term->grid, pos->row);
|
||||
const struct row *r = grid->rows[pos->row];
|
||||
char32_t c = r->cells[pos->col].wc;
|
||||
|
||||
while (c >= CELL_SPACER) {
|
||||
|
|
@ -372,15 +377,22 @@ selection_find_word_boundary_left(struct terminal *term, struct coord *pos,
|
|||
int next_col = pos->col - 1;
|
||||
int next_row = pos->row;
|
||||
|
||||
const struct row *row = grid_row_in_view(term->grid, next_row);
|
||||
const struct row *row = grid->rows[next_row];
|
||||
|
||||
/* Linewrap */
|
||||
if (next_col < 0) {
|
||||
next_col = term->cols - 1;
|
||||
if (--next_row < 0)
|
||||
break;
|
||||
|
||||
row = grid_row_in_view(term->grid, next_row);
|
||||
next_row = (next_row - 1 + grid->num_rows) & (grid->num_rows - 1);
|
||||
|
||||
if (grid_row_abs_to_sb(grid, term->rows, next_row) == term->grid->num_rows - 1 ||
|
||||
grid->rows[next_row] == NULL)
|
||||
{
|
||||
/* Scrollback wrap-around */
|
||||
break;
|
||||
}
|
||||
|
||||
row = grid->rows[next_row];
|
||||
|
||||
if (row->linebreak) {
|
||||
/* Hard linebreak, treat as space. I.e. break selection */
|
||||
|
|
@ -417,17 +429,20 @@ selection_find_word_boundary_left(struct terminal *term, struct coord *pos,
|
|||
}
|
||||
}
|
||||
|
||||
/* Coordinates are in *absolute* row numbers (NOT view local) */
|
||||
void
|
||||
selection_find_word_boundary_right(struct terminal *term, struct coord *pos,
|
||||
selection_find_word_boundary_right(const struct terminal *term, struct coord *pos,
|
||||
bool spaces_only,
|
||||
bool stop_on_space_to_word_boundary)
|
||||
{
|
||||
xassert(pos->row >= 0);
|
||||
xassert(pos->row < term->rows);
|
||||
const struct grid *grid = term->grid;
|
||||
|
||||
xassert(pos->col >= 0);
|
||||
xassert(pos->col < term->cols);
|
||||
xassert(pos->row >= 0);
|
||||
pos->row &= grid->num_rows - 1;
|
||||
|
||||
const struct row *r = grid_row_in_view(term->grid, pos->row);
|
||||
const struct row *r = grid->rows[pos->row];
|
||||
char32_t c = r->cells[pos->col].wc;
|
||||
|
||||
while (c >= CELL_SPACER) {
|
||||
|
|
@ -452,7 +467,7 @@ selection_find_word_boundary_right(struct terminal *term, struct coord *pos,
|
|||
int next_col = pos->col + 1;
|
||||
int next_row = pos->row;
|
||||
|
||||
const struct row *row = grid_row_in_view(term->grid, next_row);
|
||||
const struct row *row = term->grid->rows[next_row];
|
||||
|
||||
/* Linewrap */
|
||||
if (next_col >= term->cols) {
|
||||
|
|
@ -462,10 +477,14 @@ selection_find_word_boundary_right(struct terminal *term, struct coord *pos,
|
|||
}
|
||||
|
||||
next_col = 0;
|
||||
if (++next_row >= term->rows)
|
||||
break;
|
||||
next_row = (next_row + 1) & (grid->num_rows - 1);
|
||||
|
||||
row = grid_row_in_view(term->grid, next_row);
|
||||
if (grid_row_abs_to_sb(grid, term->rows, next_row) == 0) {
|
||||
/* Scrollback wrap-around */
|
||||
break;
|
||||
}
|
||||
|
||||
row = grid->rows[next_row];
|
||||
}
|
||||
|
||||
c = row->cells[next_col].wc;
|
||||
|
|
@ -508,9 +527,92 @@ selection_find_word_boundary_right(struct terminal *term, struct coord *pos,
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
selection_find_line_boundary_left(struct terminal *term, struct coord *pos,
|
||||
bool spaces_only)
|
||||
static bool
|
||||
selection_find_quote_left(struct terminal *term, struct coord *pos,
|
||||
char32_t *quote_char)
|
||||
{
|
||||
const struct row *row = grid_row_in_view(term->grid, pos->row);
|
||||
char32_t wc = row->cells[pos->col].wc;
|
||||
|
||||
if (*quote_char == '\0' ? (wc == '"' || wc == '\'')
|
||||
: wc == *quote_char)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int next_row = pos->row;
|
||||
int next_col = pos->col;
|
||||
|
||||
while (true) {
|
||||
if (--next_col < 0) {
|
||||
next_col = term->cols - 1;
|
||||
if (--next_row < 0)
|
||||
return false;
|
||||
|
||||
row = grid_row_in_view(term->grid, next_row);
|
||||
if (row->linebreak)
|
||||
return false;
|
||||
}
|
||||
|
||||
wc = row->cells[next_col].wc;
|
||||
|
||||
if (*quote_char == '\0' ? (wc == '"' || wc == '\'')
|
||||
: wc == *quote_char)
|
||||
{
|
||||
xassert(next_col + 1 <= term->cols);
|
||||
if (next_col + 1 == term->cols) {
|
||||
xassert(next_row < pos->row);
|
||||
pos->row = next_row + 1;
|
||||
pos->col = 0;
|
||||
} else {
|
||||
pos->row = next_row;
|
||||
pos->col = next_col + 1;
|
||||
}
|
||||
|
||||
*quote_char = wc;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
selection_find_quote_right(struct terminal *term, struct coord *pos, char32_t quote_char)
|
||||
{
|
||||
if (quote_char == '\0')
|
||||
return false;
|
||||
|
||||
const struct row *row = grid_row_in_view(term->grid, pos->row);
|
||||
char32_t wc = row->cells[pos->col].wc;
|
||||
if (wc == quote_char)
|
||||
return false;
|
||||
|
||||
int next_row = pos->row;
|
||||
int next_col = pos->col;
|
||||
|
||||
while (true) {
|
||||
if (++next_col >= term->cols) {
|
||||
next_col = 0;
|
||||
if (++next_row >= term->rows)
|
||||
return false;
|
||||
|
||||
if (row->linebreak)
|
||||
return false;
|
||||
|
||||
row = grid_row_in_view(term->grid, next_row);
|
||||
}
|
||||
|
||||
wc = row->cells[next_col].wc;
|
||||
if (wc == quote_char) {
|
||||
pos->row = next_row;
|
||||
pos->col = next_col - 1;
|
||||
xassert(pos->col >= 0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
selection_find_line_boundary_left(struct terminal *term, struct coord *pos)
|
||||
{
|
||||
int next_row = pos->row;
|
||||
pos->col = 0;
|
||||
|
|
@ -530,9 +632,8 @@ selection_find_line_boundary_left(struct terminal *term, struct coord *pos,
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
selection_find_line_boundary_right(struct terminal *term, struct coord *pos,
|
||||
bool spaces_only)
|
||||
static void
|
||||
selection_find_line_boundary_right(struct terminal *term, struct coord *pos)
|
||||
{
|
||||
int next_row = pos->row;
|
||||
pos->col = term->cols - 1;
|
||||
|
|
@ -562,6 +663,7 @@ selection_start(struct terminal *term, int col, int row,
|
|||
LOG_DBG("%s selection started at %d,%d",
|
||||
kind == SELECTION_CHAR_WISE ? "character-wise" :
|
||||
kind == SELECTION_WORD_WISE ? "word-wise" :
|
||||
kind == SELECTION_QUOTE_WISE ? "quote-wise" :
|
||||
kind == SELECTION_LINE_WISE ? "line-wise" :
|
||||
kind == SELECTION_BLOCK ? "block" : "<unknown>",
|
||||
row, col);
|
||||
|
|
@ -581,24 +683,84 @@ selection_start(struct terminal *term, int col, int row,
|
|||
break;
|
||||
|
||||
case SELECTION_WORD_WISE: {
|
||||
struct coord start = {col, row}, end = {col, row};
|
||||
struct coord start = {col, term->grid->view + row};
|
||||
struct coord end = {col, term->grid->view + row};
|
||||
selection_find_word_boundary_left(term, &start, spaces_only);
|
||||
selection_find_word_boundary_right(term, &end, spaces_only, true);
|
||||
|
||||
term->selection.coords.start = (struct coord){
|
||||
start.col, term->grid->view + start.row};
|
||||
term->selection.coords.start = start;
|
||||
|
||||
term->selection.pivot.start = term->selection.coords.start;
|
||||
term->selection.pivot.end = (struct coord){end.col, term->grid->view + end.row};
|
||||
term->selection.pivot.end = end;
|
||||
|
||||
selection_update(term, end.col, end.row);
|
||||
/*
|
||||
* FIXME: go through selection.c and make sure all public
|
||||
* functions use the *same* coordinate system...
|
||||
*
|
||||
* selection_find_word_boundary*() uses absolute row numbers,
|
||||
* while selection_update(), and pretty much all others, use
|
||||
* view-local.
|
||||
*/
|
||||
|
||||
selection_update(term, end.col, end.row - term->grid->view);
|
||||
break;
|
||||
}
|
||||
|
||||
case SELECTION_QUOTE_WISE: {
|
||||
struct coord start = {col, row}, end = {col, row};
|
||||
|
||||
char32_t quote_char = '\0';
|
||||
bool found_left = selection_find_quote_left(term, &start, "e_char);
|
||||
bool found_right = selection_find_quote_right(term, &end, quote_char);
|
||||
|
||||
if (found_left && !found_right) {
|
||||
xassert(quote_char != '\0');
|
||||
|
||||
/*
|
||||
* Try to flip the quote character we're looking for.
|
||||
*
|
||||
* This lets us handle things like:
|
||||
*
|
||||
* "nested 'quotes are fun', right"
|
||||
*
|
||||
* In the example above, starting the selection at
|
||||
* "right", will otherwise not match. find-left will find
|
||||
* the single quote, causing find-right to fail.
|
||||
*
|
||||
* By flipping the quote-character, and re-trying, we
|
||||
* find-left will find the starting double quote, letting
|
||||
* find-right succeed as well.
|
||||
*/
|
||||
|
||||
if (quote_char == '\'')
|
||||
quote_char = '"';
|
||||
else if (quote_char == '"')
|
||||
quote_char = '\'';
|
||||
|
||||
found_left = selection_find_quote_left(term, &start, "e_char);
|
||||
found_right = selection_find_quote_right(term, &end, quote_char);
|
||||
}
|
||||
|
||||
if (found_left && found_right) {
|
||||
term->selection.coords.start = (struct coord){
|
||||
start.col, term->grid->view + start.row};
|
||||
|
||||
term->selection.pivot.start = term->selection.coords.start;
|
||||
term->selection.pivot.end = (struct coord){end.col, term->grid->view + end.row};
|
||||
|
||||
term->selection.kind = SELECTION_WORD_WISE;
|
||||
selection_update(term, end.col, end.row);
|
||||
break;
|
||||
} else {
|
||||
term->selection.kind = SELECTION_LINE_WISE;
|
||||
/* FALLTHROUGH */
|
||||
}
|
||||
}
|
||||
|
||||
case SELECTION_LINE_WISE: {
|
||||
struct coord start = {0, row}, end = {term->cols - 1, row};
|
||||
selection_find_line_boundary_left(term, &start, spaces_only);
|
||||
selection_find_line_boundary_right(term, &end, spaces_only);
|
||||
selection_find_line_boundary_left(term, &start);
|
||||
selection_find_line_boundary_right(term, &end);
|
||||
|
||||
term->selection.coords.start = (struct coord){
|
||||
start.col, term->grid->view + start.row};
|
||||
|
|
@ -703,8 +865,8 @@ pixman_region_for_coords_block(const struct terminal *term,
|
|||
return region;
|
||||
}
|
||||
|
||||
/* Returns a pixman region representing the selection between ‘start’
|
||||
* and ‘end’ (given the current selection kind), in *scrollback
|
||||
/* Returns a pixman region representing the selection between 'start'
|
||||
* and 'end' (given the current selection kind), in *scrollback
|
||||
* relative coordinates* */
|
||||
static pixman_region32_t
|
||||
pixman_region_for_coords(const struct terminal *term,
|
||||
|
|
@ -766,17 +928,17 @@ mark_selected_region(struct terminal *term, pixman_box32_t *boxes,
|
|||
* followed by non-empty cell(s), since this
|
||||
* corresponds to what gets extracted when the
|
||||
* selection is copied (that is, empty cells
|
||||
* “between” non-empty cells are converted to
|
||||
* "between" non-empty cells are converted to
|
||||
* spaces).
|
||||
*
|
||||
* However, they way we handle selection updates
|
||||
* (diffing the “old” selection area against the
|
||||
* “new” one, using pixman regions), means we
|
||||
* can’t correctly update the state of empty
|
||||
* cells. The result is “random” empty cells being
|
||||
* rendered as selected when they shouldn’t.
|
||||
* (diffing the "old" selection area against the
|
||||
* "new" one, using pixman regions), means we
|
||||
* can't correctly update the state of empty
|
||||
* cells. The result is "random" empty cells being
|
||||
* rendered as selected when they shouldn't.
|
||||
*
|
||||
* “Fix” by *never* highlighting selected empty
|
||||
* "Fix" by *never* highlighting selected empty
|
||||
* cells (they still get converted to spaces when
|
||||
* copied, if followed by non-empty cells).
|
||||
*/
|
||||
|
|
@ -789,8 +951,8 @@ mark_selected_region(struct terminal *term, pixman_box32_t *boxes,
|
|||
*
|
||||
* This is due to how the algorithm for updating
|
||||
* the selection works; it uses regions to
|
||||
* calculate the difference between the “old” and
|
||||
* the “new” selection. This makes it impossible
|
||||
* calculate the difference between the "old" and
|
||||
* the "new" selection. This makes it impossible
|
||||
* to tell if an empty cell is a *trailing* empty
|
||||
* cell (that should not be highlighted), or an
|
||||
* empty cells between non-empty cells (that
|
||||
|
|
@ -802,7 +964,7 @@ mark_selected_region(struct terminal *term, pixman_box32_t *boxes,
|
|||
* empty cell is trailing or not.
|
||||
*
|
||||
* So, what we need to do is check if a
|
||||
* ‘selected’, and empty cell has been marked as
|
||||
* 'selected', and empty cell has been marked as
|
||||
* selected, temporarily unmark (forcing it dirty,
|
||||
* to ensure it gets re-rendered). If it is *not*
|
||||
* a trailing empty cell, it will get re-tagged as
|
||||
|
|
@ -810,6 +972,7 @@ mark_selected_region(struct terminal *term, pixman_box32_t *boxes,
|
|||
*/
|
||||
cell->attrs.clean = false;
|
||||
cell->attrs.selected = false;
|
||||
row->dirty = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -817,8 +980,10 @@ mark_selected_region(struct terminal *term, pixman_box32_t *boxes,
|
|||
xassert(c - j >= 0);
|
||||
struct cell *cell = &row->cells[c - j];
|
||||
|
||||
if (dirty_cells)
|
||||
if (dirty_cells) {
|
||||
cell->attrs.clean = false;
|
||||
row->dirty = true;
|
||||
}
|
||||
cell->attrs.selected = selected;
|
||||
}
|
||||
|
||||
|
|
@ -887,7 +1052,7 @@ set_pivot_point_for_block_and_char_wise(struct terminal *term,
|
|||
|
||||
*pivot_start = start;
|
||||
|
||||
/* First, make sure ‘start’ isn’t in the middle of a
|
||||
/* First, make sure 'start' isn't in the middle of a
|
||||
* multi-column character */
|
||||
while (true) {
|
||||
const struct row *row = term->grid->rows[pivot_start->row & (term->grid->num_rows - 1)];
|
||||
|
|
@ -896,7 +1061,7 @@ set_pivot_point_for_block_and_char_wise(struct terminal *term,
|
|||
if (cell->wc < CELL_SPACER)
|
||||
break;
|
||||
|
||||
/* Multi-column chars don’t cross rows */
|
||||
/* Multi-column chars don't cross rows */
|
||||
xassert(pivot_start->col > 0);
|
||||
if (pivot_start->col == 0)
|
||||
break;
|
||||
|
|
@ -962,16 +1127,16 @@ selection_update(struct terminal *term, int col, int row)
|
|||
if (!term->selection.ongoing)
|
||||
return;
|
||||
|
||||
LOG_DBG("selection updated: start = %d,%d, end = %d,%d -> %d, %d",
|
||||
term->selection.coords.start.row, term->selection.coords.start.col,
|
||||
term->selection.coords.end.row, term->selection.coords.end.col,
|
||||
row, col);
|
||||
|
||||
xassert(term->grid->view + row != -1);
|
||||
|
||||
struct coord new_start = term->selection.coords.start;
|
||||
struct coord new_end = {col, term->grid->view + row};
|
||||
|
||||
LOG_DBG("selection updated: start = %d,%d, end = %d,%d -> %d, %d",
|
||||
term->selection.coords.start.row, term->selection.coords.start.col,
|
||||
term->selection.coords.end.row, term->selection.coords.end.col,
|
||||
new_end.row, new_end.col);
|
||||
|
||||
/* Adjust start point if the selection has changed 'direction' */
|
||||
if (!(new_end.row == new_start.row && new_end.col == new_start.col)) {
|
||||
enum selection_direction new_direction = term->selection.direction;
|
||||
|
|
@ -1031,41 +1196,39 @@ selection_update(struct terminal *term, int col, int row)
|
|||
|
||||
case SELECTION_WORD_WISE:
|
||||
switch (term->selection.direction) {
|
||||
case SELECTION_LEFT: {
|
||||
struct coord end = {col, row};
|
||||
case SELECTION_LEFT:
|
||||
new_end = (struct coord){col, term->grid->view + row};
|
||||
selection_find_word_boundary_left(
|
||||
term, &end, term->selection.spaces_only);
|
||||
new_end = (struct coord){end.col, term->grid->view + end.row};
|
||||
term, &new_end, term->selection.spaces_only);
|
||||
break;
|
||||
}
|
||||
|
||||
case SELECTION_RIGHT: {
|
||||
struct coord end = {col, row};
|
||||
case SELECTION_RIGHT:
|
||||
new_end = (struct coord){col, term->grid->view + row};
|
||||
selection_find_word_boundary_right(
|
||||
term, &end, term->selection.spaces_only, true);
|
||||
new_end = (struct coord){end.col, term->grid->view + end.row};
|
||||
term, &new_end, term->selection.spaces_only, true);
|
||||
break;
|
||||
}
|
||||
|
||||
case SELECTION_UNDIR:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case SELECTION_QUOTE_WISE:
|
||||
BUG("quote-wise selection should always be transformed to either word-wise or line-wise");
|
||||
break;
|
||||
|
||||
case SELECTION_LINE_WISE:
|
||||
switch (term->selection.direction) {
|
||||
case SELECTION_LEFT: {
|
||||
struct coord end = {0, row};
|
||||
selection_find_line_boundary_left(
|
||||
term, &end, term->selection.spaces_only);
|
||||
selection_find_line_boundary_left(term, &end);
|
||||
new_end = (struct coord){end.col, term->grid->view + end.row};
|
||||
break;
|
||||
}
|
||||
|
||||
case SELECTION_RIGHT: {
|
||||
struct coord end = {col, row};
|
||||
selection_find_line_boundary_right(
|
||||
term, &end, term->selection.spaces_only);
|
||||
selection_find_line_boundary_right(term, &end);
|
||||
new_end = (struct coord){end.col, term->grid->view + end.row};
|
||||
break;
|
||||
}
|
||||
|
|
@ -1215,16 +1378,19 @@ selection_extend_normal(struct terminal *term, int col, int row,
|
|||
xassert(new_kind == SELECTION_CHAR_WISE ||
|
||||
new_kind == SELECTION_WORD_WISE);
|
||||
|
||||
struct coord pivot_start = {new_start.col, new_start.row - term->grid->view};
|
||||
struct coord pivot_start = {new_start.col, new_start.row};
|
||||
struct coord pivot_end = pivot_start;
|
||||
|
||||
selection_find_word_boundary_left(term, &pivot_start, spaces_only);
|
||||
selection_find_word_boundary_right(term, &pivot_end, spaces_only, true);
|
||||
|
||||
term->selection.pivot.start =
|
||||
(struct coord){pivot_start.col, term->grid->view + pivot_start.row};
|
||||
term->selection.pivot.end =
|
||||
(struct coord){pivot_end.col, term->grid->view + pivot_end.row};
|
||||
term->selection.pivot.start = pivot_start;
|
||||
term->selection.pivot.end = pivot_end;
|
||||
break;
|
||||
}
|
||||
|
||||
case SELECTION_QUOTE_WISE: {
|
||||
BUG("quote-wise selection should always be transformed to either word-wise or line-wise");
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1235,8 +1401,8 @@ selection_extend_normal(struct terminal *term, int col, int row,
|
|||
struct coord pivot_start = {new_start.col, new_start.row - term->grid->view};
|
||||
struct coord pivot_end = pivot_start;
|
||||
|
||||
selection_find_line_boundary_left(term, &pivot_start, spaces_only);
|
||||
selection_find_line_boundary_right(term, &pivot_end, spaces_only);
|
||||
selection_find_line_boundary_left(term, &pivot_start);
|
||||
selection_find_line_boundary_right(term, &pivot_end);
|
||||
|
||||
term->selection.pivot.start =
|
||||
(struct coord){pivot_start.col, term->grid->view + pivot_start.row};
|
||||
|
|
@ -1362,6 +1528,7 @@ selection_extend(struct seat *seat, struct terminal *term,
|
|||
|
||||
case SELECTION_CHAR_WISE:
|
||||
case SELECTION_WORD_WISE:
|
||||
case SELECTION_QUOTE_WISE:
|
||||
case SELECTION_LINE_WISE:
|
||||
selection_extend_normal(term, col, row, new_kind);
|
||||
break;
|
||||
|
|
@ -1662,7 +1829,7 @@ send_clipboard_or_primary(struct seat *seat, int fd, const char *selection,
|
|||
return;
|
||||
}
|
||||
|
||||
size_t len = strlen(selection);
|
||||
size_t len = selection != NULL ? strlen(selection) : 0;
|
||||
size_t async_idx = 0;
|
||||
|
||||
switch (async_write(fd, selection, len, &async_idx)) {
|
||||
|
|
@ -1701,7 +1868,6 @@ send(void *data, struct wl_data_source *wl_data_source, const char *mime_type,
|
|||
struct seat *seat = data;
|
||||
const struct wl_clipboard *clipboard = &seat->clipboard;
|
||||
|
||||
xassert(clipboard->text != NULL);
|
||||
send_clipboard_or_primary(seat, fd, clipboard->text, "clipboard");
|
||||
}
|
||||
|
||||
|
|
@ -1720,7 +1886,7 @@ cancelled(void *data, struct wl_data_source *wl_data_source)
|
|||
clipboard->text = NULL;
|
||||
}
|
||||
|
||||
/* We don’t support dragging *from* */
|
||||
/* We don't support dragging *from* */
|
||||
static void
|
||||
dnd_drop_performed(void *data, struct wl_data_source *wl_data_source)
|
||||
{
|
||||
|
|
@ -1756,7 +1922,6 @@ primary_send(void *data,
|
|||
struct seat *seat = data;
|
||||
const struct wl_primary *primary = &seat->primary;
|
||||
|
||||
xassert(primary->text != NULL);
|
||||
send_clipboard_or_primary(seat, fd, primary->text, "primary");
|
||||
}
|
||||
|
||||
|
|
@ -1841,6 +2006,7 @@ struct clipboard_receive {
|
|||
int timeout_fd;
|
||||
struct itimerspec timeout;
|
||||
bool bracketed;
|
||||
bool no_strip;
|
||||
bool quote_paths;
|
||||
|
||||
void (*decoder)(struct clipboard_receive *ctx, char *data, size_t size);
|
||||
|
|
@ -1925,7 +2091,7 @@ decode_one_uri(struct clipboard_receive *ctx, char *uri, size_t len)
|
|||
ctx->cb(" ", 1, ctx->user);
|
||||
ctx->add_space = true;
|
||||
|
||||
if (strcmp(scheme, "file") == 0 && hostname_is_localhost(host)) {
|
||||
if (streq(scheme, "file") && hostname_is_localhost(host)) {
|
||||
if (ctx->quote_paths)
|
||||
ctx->cb("'", 1, ctx->user);
|
||||
|
||||
|
|
@ -1988,6 +2154,8 @@ static bool
|
|||
fdm_receive(struct fdm *fdm, int fd, int events, void *data)
|
||||
{
|
||||
struct clipboard_receive *ctx = data;
|
||||
const bool no_strip = ctx->no_strip;
|
||||
const bool bracketed = ctx->bracketed;
|
||||
|
||||
if ((events & EPOLLHUP) && !(events & EPOLLIN))
|
||||
goto done;
|
||||
|
|
@ -2039,13 +2207,14 @@ fdm_receive(struct fdm *fdm, int fd, int events, void *data)
|
|||
break;
|
||||
|
||||
case '\n':
|
||||
if (!ctx->bracketed)
|
||||
if (!no_strip && !bracketed) {
|
||||
p[i] = '\r';
|
||||
}
|
||||
break;
|
||||
|
||||
case '\r':
|
||||
/* Convert \r\n -> \r */
|
||||
if (!ctx->bracketed && i + 1 < left && p[i + 1] == '\n') {
|
||||
if (!no_strip && !bracketed && i + 1 < left && p[i + 1] == '\n') {
|
||||
i++;
|
||||
skip_one();
|
||||
goto again;
|
||||
|
|
@ -2058,16 +2227,19 @@ fdm_receive(struct fdm *fdm, int fd, int events, void *data)
|
|||
case '\x11': case '\x12': case '\x13': case '\x14': case '\x15':
|
||||
case '\x16': case '\x17': case '\x18': case '\x19': case '\x1a':
|
||||
case '\x1b': case '\x1c': case '\x1d': case '\x1e': case '\x1f':
|
||||
skip_one();
|
||||
goto again;
|
||||
if (!no_strip) {
|
||||
skip_one();
|
||||
goto again;
|
||||
}
|
||||
break;
|
||||
|
||||
/*
|
||||
* In addition to stripping non-formatting C0 controls,
|
||||
* XTerm has an option, “disallowedPasteControls”, that
|
||||
* XTerm has an option, "disallowedPasteControls", that
|
||||
* defines C0 controls that will be replaced with spaces
|
||||
* when pasted.
|
||||
*
|
||||
* It’s default value is BS,DEL,ENQ,EOT,NUL
|
||||
* It's default value is BS,DEL,ENQ,EOT,NUL
|
||||
*
|
||||
* Instead of replacing them with spaces, we allow them in
|
||||
* bracketed paste mode, and strip them completely in
|
||||
|
|
@ -2077,7 +2249,7 @@ fdm_receive(struct fdm *fdm, int fd, int events, void *data)
|
|||
* handled above.
|
||||
*/
|
||||
case '\b': case '\x7f': case '\x00':
|
||||
if (!ctx->bracketed) {
|
||||
if (!no_strip && !bracketed) {
|
||||
skip_one();
|
||||
goto again;
|
||||
}
|
||||
|
|
@ -2098,8 +2270,8 @@ done:
|
|||
}
|
||||
|
||||
static void
|
||||
begin_receive_clipboard(struct terminal *term, int read_fd,
|
||||
enum data_offer_mime_type mime_type,
|
||||
begin_receive_clipboard(struct terminal *term, bool no_strip,
|
||||
int read_fd, enum data_offer_mime_type mime_type,
|
||||
void (*cb)(char *data, size_t size, void *user),
|
||||
void (*done)(void *user), void *user)
|
||||
{
|
||||
|
|
@ -2132,6 +2304,7 @@ begin_receive_clipboard(struct terminal *term, int read_fd,
|
|||
.timeout_fd = timeout_fd,
|
||||
.timeout = timeout,
|
||||
.bracketed = term->bracketed_paste,
|
||||
.no_strip = no_strip,
|
||||
.quote_paths = term->grid == &term->normal,
|
||||
.decoder = (mime_type == DATA_OFFER_MIME_URI_LIST
|
||||
? &fdm_receive_decoder_uri
|
||||
|
|
@ -2161,6 +2334,7 @@ err:
|
|||
|
||||
void
|
||||
text_from_clipboard(struct seat *seat, struct terminal *term,
|
||||
bool no_strip,
|
||||
void (*cb)(char *data, size_t size, void *user),
|
||||
void (*done)(void *user), void *user)
|
||||
{
|
||||
|
|
@ -2193,7 +2367,8 @@ text_from_clipboard(struct seat *seat, struct terminal *term,
|
|||
/* Don't keep our copy of the write-end open (or we'll never get EOF) */
|
||||
close(write_fd);
|
||||
|
||||
begin_receive_clipboard(term, read_fd, clipboard->mime_type, cb, done, user);
|
||||
begin_receive_clipboard(
|
||||
term, no_strip, read_fd, clipboard->mime_type, cb, done, user);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -2236,7 +2411,8 @@ selection_from_clipboard(struct seat *seat, struct terminal *term, uint32_t seri
|
|||
if (term->bracketed_paste)
|
||||
term_paste_data_to_slave(term, "\033[200~", 6);
|
||||
|
||||
text_from_clipboard(seat, term, &receive_offer, &receive_offer_done, term);
|
||||
text_from_clipboard(
|
||||
seat, term, false, &receive_offer, &receive_offer_done, term);
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
@ -2305,7 +2481,7 @@ selection_to_primary(struct seat *seat, struct terminal *term, uint32_t serial)
|
|||
|
||||
void
|
||||
text_from_primary(
|
||||
struct seat *seat, struct terminal *term,
|
||||
struct seat *seat, struct terminal *term, bool no_strip,
|
||||
void (*cb)(char *data, size_t size, void *user),
|
||||
void (*done)(void *user), void *user)
|
||||
{
|
||||
|
|
@ -2343,7 +2519,8 @@ text_from_primary(
|
|||
/* Don't keep our copy of the write-end open (or we'll never get EOF) */
|
||||
close(write_fd);
|
||||
|
||||
begin_receive_clipboard(term, read_fd, primary->mime_type, cb, done, user);
|
||||
begin_receive_clipboard(
|
||||
term, no_strip, read_fd, primary->mime_type, cb, done, user);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
@ -2365,7 +2542,8 @@ selection_from_primary(struct seat *seat, struct terminal *term)
|
|||
if (term->bracketed_paste)
|
||||
term_paste_data_to_slave(term, "\033[200~", 6);
|
||||
|
||||
text_from_primary(seat, term, &receive_offer, &receive_offer_done, term);
|
||||
text_from_primary(
|
||||
seat, term, false, &receive_offer, &receive_offer_done, term);
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
@ -2379,7 +2557,7 @@ select_mime_type_for_offer(const char *_mime_type,
|
|||
if (mime_type_map[i] == NULL)
|
||||
continue;
|
||||
|
||||
if (strcmp(_mime_type, mime_type_map[i]) == 0) {
|
||||
if (streq(_mime_type, mime_type_map[i])) {
|
||||
mime_type = i;
|
||||
break;
|
||||
}
|
||||
|
|
@ -2561,7 +2739,7 @@ enter(void *data, struct wl_data_device *wl_data_device, uint32_t serial,
|
|||
|
||||
reject_offer:
|
||||
/* Either terminal is already busy sending paste data, or mouse
|
||||
* pointer isn’t over the grid */
|
||||
* pointer isn't over the grid */
|
||||
seat->clipboard.window = NULL;
|
||||
wl_data_offer_accept(offer, serial, NULL);
|
||||
wl_data_offer_set_actions(
|
||||
|
|
@ -2654,10 +2832,10 @@ drop(void *data, struct wl_data_device *wl_data_device)
|
|||
term_paste_data_to_slave(term, "\033[200~", 6);
|
||||
|
||||
begin_receive_clipboard(
|
||||
term, read_fd, clipboard->mime_type,
|
||||
term, false, read_fd, clipboard->mime_type,
|
||||
&receive_dnd, &receive_dnd_done, ctx);
|
||||
|
||||
/* data offer is now “owned” by the receive context */
|
||||
/* data offer is now "owned" by the receive context */
|
||||
clipboard->data_offer = NULL;
|
||||
clipboard->mime_type = DATA_OFFER_MIME_UNSET;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,12 +63,12 @@ bool text_to_primary(
|
|||
* point).
|
||||
*/
|
||||
void text_from_clipboard(
|
||||
struct seat *seat, struct terminal *term,
|
||||
struct seat *seat, struct terminal *term, bool no_strip,
|
||||
void (*cb)(char *data, size_t size, void *user),
|
||||
void (*done)(void *user), void *user);
|
||||
|
||||
void text_from_primary(
|
||||
struct seat *seat, struct terminal *term,
|
||||
struct seat *seat, struct terminal *term, bool no_strip,
|
||||
void (*cb)(char *data, size_t size, void *user),
|
||||
void (*dont)(void *user), void *user);
|
||||
|
||||
|
|
@ -78,9 +78,9 @@ void selection_start_scroll_timer(
|
|||
void selection_stop_scroll_timer(struct terminal *term);
|
||||
|
||||
void selection_find_word_boundary_left(
|
||||
struct terminal *term, struct coord *pos, bool spaces_only);
|
||||
const struct terminal *term, struct coord *pos, bool spaces_only);
|
||||
void selection_find_word_boundary_right(
|
||||
struct terminal *term, struct coord *pos, bool spaces_only,
|
||||
const struct terminal *term, struct coord *pos, bool spaces_only,
|
||||
bool stop_on_space_to_word_boundary);
|
||||
|
||||
struct coord selection_get_start(const struct terminal *term);
|
||||
|
|
|
|||
102
server.c
102
server.c
|
|
@ -4,6 +4,7 @@
|
|||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
|
|
@ -18,17 +19,18 @@
|
|||
#include "log.h"
|
||||
|
||||
#include "client-protocol.h"
|
||||
#include "shm.h"
|
||||
#include "terminal.h"
|
||||
#include "util.h"
|
||||
#include "wayland.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
#define NON_ZERO_OPT (INT_MIN / 7)
|
||||
|
||||
struct client;
|
||||
struct terminal_instance;
|
||||
|
||||
struct server {
|
||||
const struct config *conf;
|
||||
struct config *conf;
|
||||
struct fdm *fdm;
|
||||
struct reaper *reaper;
|
||||
struct wayland *wayl;
|
||||
|
|
@ -154,10 +156,61 @@ fdm_client(struct fdm *fdm, int fd, int events, void *data)
|
|||
xassert(events & EPOLLIN);
|
||||
|
||||
if (client->instance != NULL) {
|
||||
uint8_t dummy[128];
|
||||
ssize_t count = read(fd, dummy, sizeof(dummy));
|
||||
LOG_WARN("client unexpectedly sent %zd bytes", count);
|
||||
return true; /* TODO: shutdown instead? */
|
||||
struct client_ipc_hdr ipc_hdr;
|
||||
ssize_t count = read(fd, &ipc_hdr, sizeof(ipc_hdr));
|
||||
|
||||
if (count != sizeof(ipc_hdr)) {
|
||||
LOG_WARN("client unexpectedly sent %zd bytes", count);
|
||||
return true; /* TODO: shutdown instead? */
|
||||
}
|
||||
|
||||
switch (ipc_hdr.ipc_code) {
|
||||
case FOOT_IPC_SIGUSR: {
|
||||
xassert(ipc_hdr.size == sizeof(struct client_ipc_sigusr));
|
||||
|
||||
struct client_ipc_sigusr sigusr;
|
||||
count = read(fd, &sigusr, sizeof(sigusr));
|
||||
if (count < 0) {
|
||||
LOG_ERRNO("failed to read SIGUSR IPC data from client");
|
||||
return true; /* TODO: shutdown instead? */
|
||||
}
|
||||
|
||||
if ((size_t)count != sizeof(sigusr)) {
|
||||
LOG_ERR("failed to read SIGUSR IPC data from client");
|
||||
return true; /* TODO: shutdown instead? */
|
||||
}
|
||||
|
||||
switch (sigusr.signo) {
|
||||
case SIGUSR1:
|
||||
term_theme_switch_to_dark(client->instance->terminal);
|
||||
break;
|
||||
|
||||
case SIGUSR2:
|
||||
term_theme_switch_to_light(client->instance->terminal);
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_ERR(
|
||||
"client sent bad SIGUSR number: %d "
|
||||
"(expected SIGUSR1=%d or SIGUSR2=%d)",
|
||||
sigusr.signo, SIGUSR1, SIGUSR2);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
LOG_WARN(
|
||||
"client sent unrecognized IPC (0x%04x), ignoring %hhu bytes",
|
||||
ipc_hdr.ipc_code, ipc_hdr.size);
|
||||
|
||||
/* TODO: slightly broken, since not all data is guaranteed
|
||||
to be readable yet */
|
||||
uint8_t dummy[ipc_hdr.size];
|
||||
(void)!!read(fd, dummy, ipc_hdr.size);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (client->buffer.data == NULL) {
|
||||
|
|
@ -212,6 +265,12 @@ fdm_client(struct fdm *fdm, int fd, int events, void *data)
|
|||
return true;
|
||||
}
|
||||
|
||||
if (tll_length(server->wayl->monitors) == 0) {
|
||||
LOG_ERR("no monitors available for new terminal");
|
||||
client_send_exit_code(client, -26);
|
||||
goto shutdown;
|
||||
}
|
||||
|
||||
/* All initialization data received - time to instantiate a terminal! */
|
||||
|
||||
xassert(client->instance == NULL);
|
||||
|
|
@ -301,7 +360,7 @@ fdm_client(struct fdm *fdm, int fd, int events, void *data)
|
|||
#undef CHECK_BUF_AND_NULL
|
||||
#undef CHECK_BUF
|
||||
|
||||
struct terminal_instance *instance = malloc(sizeof(struct terminal_instance));
|
||||
struct terminal_instance *instance = xmalloc(sizeof(struct terminal_instance));
|
||||
|
||||
const bool need_to_clone_conf =
|
||||
tll_length(overrides)> 0 ||
|
||||
|
|
@ -332,7 +391,8 @@ fdm_client(struct fdm *fdm, int fd, int events, void *data)
|
|||
instance->terminal = term_init(
|
||||
conf != NULL ? conf : server->conf,
|
||||
server->fdm, server->reaper, server->wayl, "footclient", cwd, token,
|
||||
cdata.argc, argv, envp, &term_shutdown_handler, instance);
|
||||
NULL, cdata.argc, argv, (const char *const *)envp,
|
||||
&term_shutdown_handler, instance);
|
||||
|
||||
if (instance->terminal == NULL) {
|
||||
LOG_ERR("failed to instantiate new terminal");
|
||||
|
|
@ -468,7 +528,7 @@ prepare_socket(int fd)
|
|||
}
|
||||
|
||||
int const socket_options[] = { SO_DOMAIN, SO_ACCEPTCONN, SO_TYPE };
|
||||
int const socket_options_values[] = { AF_UNIX, 1, SOCK_STREAM};
|
||||
int const socket_options_values[] = { AF_UNIX, NON_ZERO_OPT, SOCK_STREAM};
|
||||
char const * const socket_options_names[] = { "SO_DOMAIN", "SO_ACCEPTCONN", "SO_TYPE" };
|
||||
|
||||
xassert(ALEN(socket_options) == ALEN(socket_options_values));
|
||||
|
|
@ -483,6 +543,8 @@ prepare_socket(int fd)
|
|||
LOG_ERRNO("failed to read socket option from passed file descriptor");
|
||||
return false;
|
||||
}
|
||||
if (socket_options_values[i] == NON_ZERO_OPT && socket_option)
|
||||
socket_option = NON_ZERO_OPT;
|
||||
if (socket_option != socket_options_values[i]) {
|
||||
LOG_ERR("wrong socket value for socket option '%s' on passed file descriptor",
|
||||
socket_options_names[i]);
|
||||
|
|
@ -494,7 +556,7 @@ prepare_socket(int fd)
|
|||
}
|
||||
|
||||
struct server *
|
||||
server_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
|
||||
server_init(struct config *conf, struct fdm *fdm, struct reaper *reaper,
|
||||
struct wayland *wayl)
|
||||
{
|
||||
int fd;
|
||||
|
|
@ -606,3 +668,23 @@ server_destroy(struct server *server)
|
|||
unlink(server->sock_path);
|
||||
free(server);
|
||||
}
|
||||
|
||||
void
|
||||
server_global_theme_switch_to_dark(struct server *server)
|
||||
{
|
||||
server->conf->initial_color_theme = COLOR_THEME_DARK;
|
||||
tll_foreach(server->clients, it)
|
||||
term_theme_switch_to_dark(it->item->instance->terminal);
|
||||
tll_foreach(server->terminals, it)
|
||||
term_theme_switch_to_dark(it->item->terminal);
|
||||
}
|
||||
|
||||
void
|
||||
server_global_theme_switch_to_light(struct server *server)
|
||||
{
|
||||
server->conf->initial_color_theme = COLOR_THEME_LIGHT;
|
||||
tll_foreach(server->clients, it)
|
||||
term_theme_switch_to_light(it->item->instance->terminal);
|
||||
tll_foreach(server->terminals, it)
|
||||
term_theme_switch_to_light(it->item->terminal);
|
||||
}
|
||||
|
|
|
|||
5
server.h
5
server.h
|
|
@ -6,6 +6,9 @@
|
|||
#include "wayland.h"
|
||||
|
||||
struct server;
|
||||
struct server *server_init(const struct config *conf, struct fdm *fdm,
|
||||
struct server *server_init(struct config *conf, struct fdm *fdm,
|
||||
struct reaper *reaper, struct wayland *wayl);
|
||||
void server_destroy(struct server *server);
|
||||
|
||||
void server_global_theme_switch_to_dark(struct server *server);
|
||||
void server_global_theme_switch_to_light(struct server *server);
|
||||
|
|
|
|||
|
|
@ -117,5 +117,22 @@ static const struct shm_formats {
|
|||
{WL_SHM_FORMAT_ARGB16161616, "ARGB16161616"},
|
||||
{WL_SHM_FORMAT_ABGR16161616, "ABGR16161616"},
|
||||
#endif
|
||||
#if WAYLAND_VERSION_MAJOR > 1 || WAYLAND_VERSION_MINOR >= 23
|
||||
{WL_SHM_FORMAT_C1, "C1"},
|
||||
{WL_SHM_FORMAT_C2, "C2"},
|
||||
{WL_SHM_FORMAT_C4, "C4"},
|
||||
{WL_SHM_FORMAT_D1, "D1"},
|
||||
{WL_SHM_FORMAT_D2, "D2"},
|
||||
{WL_SHM_FORMAT_D4, "D4"},
|
||||
{WL_SHM_FORMAT_D8, "D8"},
|
||||
{WL_SHM_FORMAT_R1, "R1"},
|
||||
{WL_SHM_FORMAT_R2, "R2"},
|
||||
{WL_SHM_FORMAT_R4, "R4"},
|
||||
{WL_SHM_FORMAT_R10, "R10"},
|
||||
{WL_SHM_FORMAT_R12, "R12"},
|
||||
{WL_SHM_FORMAT_AVUY8888, "AVUY8888"},
|
||||
{WL_SHM_FORMAT_XVUY8888, "XVUY8888"},
|
||||
{WL_SHM_FORMAT_P030, "P030"},
|
||||
#endif
|
||||
};
|
||||
#endif
|
||||
|
|
|
|||
203
shm.c
203
shm.c
|
|
@ -13,7 +13,6 @@
|
|||
|
||||
#include <pixman.h>
|
||||
|
||||
#include <fcft/stride.h>
|
||||
#include <tllist.h>
|
||||
|
||||
#define LOG_MODULE "shm"
|
||||
|
|
@ -21,12 +20,17 @@
|
|||
#include "log.h"
|
||||
#include "debug.h"
|
||||
#include "macros.h"
|
||||
#include "stride.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
#if !defined(MAP_UNINITIALIZED)
|
||||
#define MAP_UNINITIALIZED 0
|
||||
#endif
|
||||
|
||||
#if !defined(MFD_NOEXEC_SEAL)
|
||||
#define MFD_NOEXEC_SEAL 0
|
||||
#endif
|
||||
|
||||
#define TIME_SCROLL 0
|
||||
|
||||
#define FORCED_DOUBLE_BUFFERING 0
|
||||
|
|
@ -57,6 +61,8 @@ static off_t max_pool_size = 512 * 1024 * 1024;
|
|||
static bool can_punch_hole = false;
|
||||
static bool can_punch_hole_initialized = false;
|
||||
|
||||
static size_t min_stride_alignment = 0;
|
||||
|
||||
struct buffer_pool {
|
||||
int fd; /* memfd */
|
||||
struct wl_shm_pool *wl_pool;
|
||||
|
|
@ -80,6 +86,9 @@ struct buffer_private {
|
|||
size_t size;
|
||||
|
||||
bool scrollable;
|
||||
|
||||
void (*release_cb)(struct buffer *buf, void *data);
|
||||
void *cb_data;
|
||||
};
|
||||
|
||||
struct buffer_chain {
|
||||
|
|
@ -87,6 +96,12 @@ struct buffer_chain {
|
|||
struct wl_shm *shm;
|
||||
size_t pix_instances;
|
||||
bool scrollable;
|
||||
|
||||
pixman_format_code_t pixman_fmt;
|
||||
enum wl_shm_format shm_format;
|
||||
|
||||
void (*release_cb)(struct buffer *buf, void *data);
|
||||
void *cb_data;
|
||||
};
|
||||
|
||||
static tll(struct buffer_private *) deferred;
|
||||
|
|
@ -102,6 +117,12 @@ shm_set_max_pool_size(off_t _max_pool_size)
|
|||
max_pool_size = _max_pool_size;
|
||||
}
|
||||
|
||||
void
|
||||
shm_set_min_stride_alignment(size_t _min_stride_alignment)
|
||||
{
|
||||
min_stride_alignment = _min_stride_alignment;
|
||||
}
|
||||
|
||||
static void
|
||||
buffer_destroy_dont_close(struct buffer *buf)
|
||||
{
|
||||
|
|
@ -110,6 +131,7 @@ buffer_destroy_dont_close(struct buffer *buf)
|
|||
if (buf->pix[i] != NULL)
|
||||
pixman_image_unref(buf->pix[i]);
|
||||
}
|
||||
|
||||
if (buf->wl_buf != NULL)
|
||||
wl_buffer_destroy(buf->wl_buf);
|
||||
|
||||
|
|
@ -151,7 +173,9 @@ buffer_destroy(struct buffer_private *buf)
|
|||
pool_unref(buf->pool);
|
||||
buf->pool = NULL;
|
||||
|
||||
pixman_region32_fini(&buf->public.dirty);
|
||||
for (size_t i = 0; i < buf->public.pix_instances; i++)
|
||||
pixman_region32_fini(&buf->public.dirty[i]);
|
||||
free(buf->public.dirty);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
|
|
@ -210,6 +234,10 @@ buffer_release(void *data, struct wl_buffer *wl_buffer)
|
|||
xassert(found);
|
||||
if (!found)
|
||||
LOG_WARN("deferred delete: buffer not on the 'deferred' list");
|
||||
} else {
|
||||
if (buffer->release_cb != NULL) {
|
||||
buffer->release_cb(&buffer->public, buffer->cb_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -217,7 +245,6 @@ static const struct wl_buffer_listener buffer_listener = {
|
|||
.release = &buffer_release,
|
||||
};
|
||||
|
||||
#if __SIZEOF_POINTER__ == 8
|
||||
static size_t
|
||||
page_size(void)
|
||||
{
|
||||
|
|
@ -234,7 +261,6 @@ page_size(void)
|
|||
xassert(size > 0);
|
||||
return size;
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool
|
||||
instantiate_offset(struct buffer_private *buf, off_t new_offset)
|
||||
|
|
@ -248,14 +274,14 @@ instantiate_offset(struct buffer_private *buf, off_t new_offset)
|
|||
|
||||
void *mmapped = MAP_FAILED;
|
||||
struct wl_buffer *wl_buf = NULL;
|
||||
pixman_image_t **pix = xcalloc(buf->public.pix_instances, sizeof(*pix));
|
||||
pixman_image_t **pix = xcalloc(buf->public.pix_instances, sizeof(pix[0]));
|
||||
|
||||
mmapped = (uint8_t *)pool->real_mmapped + new_offset;
|
||||
|
||||
wl_buf = wl_shm_pool_create_buffer(
|
||||
pool->wl_pool, new_offset,
|
||||
buf->public.width, buf->public.height, buf->public.stride,
|
||||
WL_SHM_FORMAT_ARGB8888);
|
||||
buf->chain->shm_format);
|
||||
|
||||
if (wl_buf == NULL) {
|
||||
LOG_ERR("failed to create SHM buffer");
|
||||
|
|
@ -265,8 +291,10 @@ instantiate_offset(struct buffer_private *buf, off_t new_offset)
|
|||
/* One pixman image for each worker thread (do we really need multiple?) */
|
||||
for (size_t i = 0; i < buf->public.pix_instances; i++) {
|
||||
pix[i] = pixman_image_create_bits_no_clear(
|
||||
PIXMAN_a8r8g8b8, buf->public.width, buf->public.height,
|
||||
buf->chain->pixman_fmt,
|
||||
buf->public.width, buf->public.height,
|
||||
(uint32_t *)mmapped, buf->public.stride);
|
||||
|
||||
if (pix[i] == NULL) {
|
||||
LOG_ERR("failed to create pixman image");
|
||||
goto err;
|
||||
|
|
@ -316,7 +344,15 @@ get_new_buffers(struct buffer_chain *chain, size_t count,
|
|||
|
||||
size_t total_size = 0;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
stride[i] = stride_for_format_and_width(PIXMAN_a8r8g8b8, widths[i]);
|
||||
stride[i] = stride_for_format_and_width(
|
||||
chain->pixman_fmt, widths[i]);
|
||||
|
||||
if (min_stride_alignment > 0) {
|
||||
const size_t m = min_stride_alignment;
|
||||
stride[i] = (stride[i] + m - 1) / m * m;
|
||||
}
|
||||
|
||||
xassert(min_stride_alignment == 0 || stride[i] % min_stride_alignment == 0);
|
||||
sizes[i] = stride[i] * heights[i];
|
||||
total_size += sizes[i];
|
||||
}
|
||||
|
|
@ -331,7 +367,20 @@ get_new_buffers(struct buffer_chain *chain, size_t count,
|
|||
|
||||
/* Backing memory for SHM */
|
||||
#if defined(MEMFD_CREATE)
|
||||
pool_fd = memfd_create("foot-wayland-shm-buffer-pool", MFD_CLOEXEC | MFD_ALLOW_SEALING);
|
||||
/*
|
||||
* Older kernels reject MFD_NOEXEC_SEAL with EINVAL. Try first
|
||||
* *with* it, and if that fails, try again *without* it.
|
||||
*/
|
||||
errno = 0;
|
||||
pool_fd = memfd_create(
|
||||
"foot-wayland-shm-buffer-pool",
|
||||
MFD_CLOEXEC | MFD_ALLOW_SEALING | MFD_NOEXEC_SEAL);
|
||||
|
||||
if (pool_fd < 0 && errno == EINVAL && MFD_NOEXEC_SEAL != 0) {
|
||||
pool_fd = memfd_create(
|
||||
"foot-wayland-shm-buffer-pool", MFD_CLOEXEC | MFD_ALLOW_SEALING);
|
||||
}
|
||||
|
||||
#elif defined(__FreeBSD__)
|
||||
// memfd_create on FreeBSD 13 is SHM_ANON without sealing support
|
||||
pool_fd = shm_open(SHM_ANON, O_RDWR | O_CLOEXEC, 0600);
|
||||
|
|
@ -345,9 +394,11 @@ get_new_buffers(struct buffer_chain *chain, size_t count,
|
|||
goto err;
|
||||
}
|
||||
|
||||
const size_t page_sz = page_size();
|
||||
|
||||
#if __SIZEOF_POINTER__ == 8
|
||||
off_t offset = chain->scrollable && max_pool_size > 0
|
||||
? (max_pool_size / 4) & ~(page_size() - 1)
|
||||
? (max_pool_size / 4) & ~(page_sz - 1)
|
||||
: 0;
|
||||
off_t memfd_size = chain->scrollable && max_pool_size > 0
|
||||
? max_pool_size
|
||||
|
|
@ -357,7 +408,8 @@ get_new_buffers(struct buffer_chain *chain, size_t count,
|
|||
off_t memfd_size = total_size;
|
||||
#endif
|
||||
|
||||
xassert(chain->scrollable || (offset == 0 && memfd_size == total_size));
|
||||
/* Page align */
|
||||
memfd_size = (memfd_size + page_sz - 1) & ~(page_sz - 1);
|
||||
|
||||
LOG_DBG("memfd-size: %lu, initial offset: %lu", memfd_size, offset);
|
||||
|
||||
|
|
@ -389,6 +441,9 @@ get_new_buffers(struct buffer_chain *chain, size_t count,
|
|||
memfd_size = total_size;
|
||||
chain->scrollable = false;
|
||||
|
||||
/* Page align */
|
||||
memfd_size = (memfd_size + page_sz - 1) & ~(page_sz - 1);
|
||||
|
||||
if (ftruncate(pool_fd, memfd_size) < 0) {
|
||||
LOG_ERRNO("failed to set size of SHM backing memory file");
|
||||
goto err;
|
||||
|
|
@ -458,6 +513,8 @@ get_new_buffers(struct buffer_chain *chain, size_t count,
|
|||
.offset = 0,
|
||||
.size = sizes[i],
|
||||
.scrollable = chain->scrollable,
|
||||
.release_cb = chain->release_cb,
|
||||
.cb_data = chain->cb_data,
|
||||
};
|
||||
|
||||
if (!instantiate_offset(buf, offset)) {
|
||||
|
|
@ -470,7 +527,12 @@ get_new_buffers(struct buffer_chain *chain, size_t count,
|
|||
else
|
||||
tll_push_front(chain->bufs, buf);
|
||||
|
||||
pixman_region32_init(&buf->public.dirty);
|
||||
buf->public.dirty = xmalloc(
|
||||
chain->pix_instances * sizeof(buf->public.dirty[0]));
|
||||
|
||||
for (size_t j = 0; j < chain->pix_instances; j++)
|
||||
pixman_region32_init(&buf->public.dirty[j]);
|
||||
|
||||
pool->ref_count++;
|
||||
offset += buf->size;
|
||||
bufs[i] = &buf->public;
|
||||
|
|
@ -487,7 +549,7 @@ get_new_buffers(struct buffer_chain *chain, size_t count,
|
|||
#endif
|
||||
|
||||
if (!(bufs[0] && shm_can_scroll(bufs[0]))) {
|
||||
/* We only need to keep the pool FD open if we’re going to SHM
|
||||
/* We only need to keep the pool FD open if we're going to SHM
|
||||
* scroll it */
|
||||
close(pool_fd);
|
||||
pool->fd = -1;
|
||||
|
|
@ -527,7 +589,7 @@ struct buffer *
|
|||
shm_get_buffer(struct buffer_chain *chain, int width, int height)
|
||||
{
|
||||
LOG_DBG(
|
||||
"chain=%p: looking for a re-usable %dx%d buffer "
|
||||
"chain=%p: looking for a reusable %dx%d buffer "
|
||||
"among %zu potential buffers",
|
||||
(void *)chain, width, height, tll_length(chain->bufs));
|
||||
|
||||
|
|
@ -546,16 +608,16 @@ shm_get_buffer(struct buffer_chain *chain, int width, int height)
|
|||
buf->public.age++;
|
||||
else
|
||||
#if FORCED_DOUBLE_BUFFERING
|
||||
if (buf->age == 0)
|
||||
buf->age++;
|
||||
if (buf->public.age == 0)
|
||||
buf->public.age++;
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if (cached == NULL)
|
||||
if (cached == NULL) {
|
||||
cached = buf;
|
||||
else {
|
||||
} else {
|
||||
/* We have multiple buffers eligible for
|
||||
* re-use. Pick the “youngest” one, and mark the
|
||||
* reuse. Pick the "youngest" one, and mark the
|
||||
* other one for purging */
|
||||
if (buf->public.age < cached->public.age) {
|
||||
shm_unref(&cached->public);
|
||||
|
|
@ -565,8 +627,8 @@ shm_get_buffer(struct buffer_chain *chain, int width, int height)
|
|||
* TODO: I think we _can_ use shm_unref()
|
||||
* here...
|
||||
*
|
||||
* shm_unref() may remove ‘it’, but that
|
||||
* should be safe; “our” tll_foreach() already
|
||||
* shm_unref() may remove 'it', but that
|
||||
* should be safe; "our" tll_foreach() already
|
||||
* holds the next pointer.
|
||||
*/
|
||||
if (buffer_unref_no_remove_from_chain(buf))
|
||||
|
|
@ -577,9 +639,10 @@ shm_get_buffer(struct buffer_chain *chain, int width, int height)
|
|||
}
|
||||
|
||||
if (cached != NULL) {
|
||||
LOG_DBG("re-using buffer %p from cache", (void *)cached);
|
||||
LOG_DBG("reusing buffer %p from cache", (void *)cached);
|
||||
cached->busy = true;
|
||||
pixman_region32_clear(&cached->public.dirty);
|
||||
for (size_t i = 0; i < cached->public.pix_instances; i++)
|
||||
pixman_region32_clear(&cached->public.dirty[i]);
|
||||
xassert(cached->public.pix_instances == chain->pix_instances);
|
||||
return &cached->public;
|
||||
}
|
||||
|
|
@ -927,14 +990,90 @@ shm_unref(struct buffer *_buf)
|
|||
}
|
||||
|
||||
struct buffer_chain *
|
||||
shm_chain_new(struct wl_shm *shm, bool scrollable, size_t pix_instances)
|
||||
shm_chain_new(struct wayland *wayl, bool scrollable, size_t pix_instances,
|
||||
enum shm_bit_depth desired_bit_depth,
|
||||
void (*release_cb)(struct buffer *buf, void *data), void *cb_data)
|
||||
{
|
||||
pixman_format_code_t pixman_fmt = PIXMAN_a8r8g8b8;
|
||||
enum wl_shm_format shm_fmt = WL_SHM_FORMAT_ARGB8888;
|
||||
|
||||
static bool have_logged = false;
|
||||
static bool have_logged_10_fallback = false;
|
||||
|
||||
#if defined(HAVE_PIXMAN_RGBA_16)
|
||||
static bool have_logged_16_fallback = false;
|
||||
|
||||
if (desired_bit_depth == SHM_BITS_16) {
|
||||
if (wayl->shm_have_abgr161616) {
|
||||
pixman_fmt = PIXMAN_a16b16g16r16;
|
||||
shm_fmt = WL_SHM_FORMAT_ABGR16161616;
|
||||
|
||||
if (!have_logged) {
|
||||
have_logged = true;
|
||||
LOG_INFO("using 16-bit BGR surfaces");
|
||||
}
|
||||
} else {
|
||||
if (!have_logged_16_fallback) {
|
||||
have_logged_16_fallback = true;
|
||||
|
||||
LOG_WARN(
|
||||
"16-bit surfaces requested, but compositor does not "
|
||||
"implement ABGR161616+XBGR161616");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (desired_bit_depth >= SHM_BITS_10 && pixman_fmt == PIXMAN_a8r8g8b8) {
|
||||
if (wayl->shm_have_argb2101010) {
|
||||
pixman_fmt = PIXMAN_a2r10g10b10;
|
||||
shm_fmt = WL_SHM_FORMAT_ARGB2101010;
|
||||
|
||||
if (!have_logged) {
|
||||
have_logged = true;
|
||||
LOG_INFO("using 10-bit RGB surfaces");
|
||||
}
|
||||
}
|
||||
|
||||
else if (wayl->shm_have_abgr2101010) {
|
||||
pixman_fmt = PIXMAN_a2b10g10r10;
|
||||
shm_fmt = WL_SHM_FORMAT_ABGR2101010;
|
||||
|
||||
if (!have_logged) {
|
||||
have_logged = true;
|
||||
LOG_INFO("using 10-bit BGR surfaces");
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
if (!have_logged_10_fallback) {
|
||||
have_logged_10_fallback = true;
|
||||
|
||||
LOG_WARN(
|
||||
"10-bit surfaces requested, but compositor does not "
|
||||
"implement ARGB2101010+XRGB2101010, or "
|
||||
"ABGR2101010+XBGR2101010");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!have_logged) {
|
||||
have_logged = true;
|
||||
LOG_INFO("using 8-bit RGB surfaces");
|
||||
}
|
||||
}
|
||||
|
||||
struct buffer_chain *chain = xmalloc(sizeof(*chain));
|
||||
*chain = (struct buffer_chain){
|
||||
.bufs = tll_init(),
|
||||
.shm = shm,
|
||||
.shm = wayl->shm,
|
||||
.pix_instances = pix_instances,
|
||||
.scrollable = scrollable,
|
||||
|
||||
.pixman_fmt = pixman_fmt,
|
||||
.shm_format = shm_fmt,
|
||||
|
||||
.release_cb = release_cb,
|
||||
.cb_data = cb_data,
|
||||
};
|
||||
return chain;
|
||||
}
|
||||
|
|
@ -954,3 +1093,17 @@ shm_chain_free(struct buffer_chain *chain)
|
|||
|
||||
free(chain);
|
||||
}
|
||||
|
||||
enum shm_bit_depth
|
||||
shm_chain_bit_depth(const struct buffer_chain *chain)
|
||||
{
|
||||
const pixman_format_code_t fmt = chain->pixman_fmt;
|
||||
|
||||
return fmt == PIXMAN_a8r8g8b8
|
||||
? SHM_BITS_8
|
||||
#if defined(HAVE_PIXMAN_RGBA_16)
|
||||
: fmt == PIXMAN_a16b16g16r16
|
||||
? SHM_BITS_16
|
||||
#endif
|
||||
: SHM_BITS_10;
|
||||
}
|
||||
|
|
|
|||
29
shm.h
29
shm.h
|
|
@ -9,6 +9,9 @@
|
|||
|
||||
#include <tllist.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "wayland.h"
|
||||
|
||||
struct damage;
|
||||
|
||||
struct buffer {
|
||||
|
|
@ -24,21 +27,39 @@ struct buffer {
|
|||
|
||||
unsigned age;
|
||||
|
||||
pixman_region32_t dirty;
|
||||
/*
|
||||
* First item in the array is used to track frame-to-frame
|
||||
* damage. This is used when re-applying damage from the last
|
||||
* frame, when the compositor doesn't release buffers immediately
|
||||
* (forcing us to double buffer)
|
||||
*
|
||||
* The remaining items are used to track surface damage. Each
|
||||
* worker thread adds its own cell damage to "its" region. When
|
||||
* the frame is done, all damage is converted to a single region,
|
||||
* which is then used in calls to wl_surface_damage_buffer().
|
||||
*/
|
||||
pixman_region32_t *dirty;
|
||||
};
|
||||
|
||||
void shm_fini(void);
|
||||
|
||||
/* TODO: combine into shm_init() */
|
||||
void shm_set_max_pool_size(off_t max_pool_size);
|
||||
void shm_set_min_stride_alignment(size_t min_stride_alignment);
|
||||
|
||||
struct buffer_chain;
|
||||
struct buffer_chain *shm_chain_new(
|
||||
struct wl_shm *shm, bool scrollable, size_t pix_instances);
|
||||
struct wayland *wayl, bool scrollable, size_t pix_instances,
|
||||
enum shm_bit_depth desired_bit_depth,
|
||||
void (*release_cb)(struct buffer *buf, void *data), void *cb_data);
|
||||
void shm_chain_free(struct buffer_chain *chain);
|
||||
|
||||
enum shm_bit_depth shm_chain_bit_depth(const struct buffer_chain *chain);
|
||||
|
||||
/*
|
||||
* Returns a single buffer.
|
||||
*
|
||||
* May returned a cached buffer. If so, the buffer’s age indicates how
|
||||
* May returned a cached buffer. If so, the buffer's age indicates how
|
||||
* many shm_get_buffer() calls have been made for the same
|
||||
* width/height while the buffer was still busy.
|
||||
*
|
||||
|
|
@ -46,7 +67,7 @@ void shm_chain_free(struct buffer_chain *chain);
|
|||
*/
|
||||
struct buffer *shm_get_buffer(struct buffer_chain *chain, int width, int height);
|
||||
/*
|
||||
* Returns many buffers, described by ‘info’, all sharing the same SHM
|
||||
* Returns many buffers, described by 'info', all sharing the same SHM
|
||||
* buffer pool.
|
||||
*
|
||||
* Never returns cached buffers. However, the newly created buffers
|
||||
|
|
|
|||
6
sixel.h
6
sixel.h
|
|
@ -6,10 +6,11 @@
|
|||
#define SIXEL_MAX_WIDTH 10000u
|
||||
#define SIXEL_MAX_HEIGHT 10000u
|
||||
|
||||
typedef void (*sixel_put)(struct terminal *term, uint8_t c);
|
||||
|
||||
void sixel_fini(struct terminal *term);
|
||||
|
||||
void sixel_init(struct terminal *term, int p1, int p2, int p3);
|
||||
void sixel_put(struct terminal *term, uint8_t c);
|
||||
sixel_put sixel_init(struct terminal *term, int p1, int p2, int p3);
|
||||
void sixel_unhook(struct terminal *term);
|
||||
|
||||
void sixel_destroy(struct sixel *sixel);
|
||||
|
|
@ -19,6 +20,7 @@ void sixel_scroll_up(struct terminal *term, int rows);
|
|||
void sixel_scroll_down(struct terminal *term, int rows);
|
||||
|
||||
void sixel_cell_size_changed(struct terminal *term);
|
||||
void sixel_sync_cache(const struct terminal *term, struct sixel *sixel);
|
||||
|
||||
void sixel_reflow_grid(struct terminal *term, struct grid *grid);
|
||||
|
||||
|
|
|
|||
175
slave.c
175
slave.c
|
|
@ -19,14 +19,18 @@
|
|||
|
||||
#include "debug.h"
|
||||
#include "macros.h"
|
||||
#include "terminal.h"
|
||||
#include "tokenize.h"
|
||||
#include "version.h"
|
||||
#include "util.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
extern char **environ;
|
||||
|
||||
#if defined(__FreeBSD__)
|
||||
struct environ {
|
||||
size_t count;
|
||||
char **envp;
|
||||
};
|
||||
|
||||
#if !defined(EXECVPE)
|
||||
static char *
|
||||
find_file_in_path(const char *file)
|
||||
{
|
||||
|
|
@ -51,7 +55,7 @@ find_file_in_path(const char *file)
|
|||
path != NULL;
|
||||
path = strtok(NULL, ":"))
|
||||
{
|
||||
char *full = xasprintf("%s/%s", path, file);
|
||||
char *full = xstrjoin3(path, "/", file);
|
||||
if (access(full, F_OK) == 0) {
|
||||
free(path_list);
|
||||
return full;
|
||||
|
|
@ -77,11 +81,11 @@ foot_execvpe(const char *file, char *const argv[], char *const envp[])
|
|||
return ret;
|
||||
}
|
||||
|
||||
#else /* !__FreeBSD__ */
|
||||
#else /* EXECVPE */
|
||||
|
||||
#define foot_execvpe(file, argv, envp) execvpe(file, argv, envp)
|
||||
|
||||
#endif /* !__FreeBSD__ */
|
||||
#endif /* EXECVPE */
|
||||
|
||||
static bool
|
||||
is_valid_shell(const char *shell)
|
||||
|
|
@ -117,7 +121,7 @@ is_valid_shell(const char *shell)
|
|||
if (line[0] == '#')
|
||||
continue;
|
||||
|
||||
if (strcmp(line, shell) == 0) {
|
||||
if (streq(line, shell)) {
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -154,6 +158,7 @@ emit_one_notification(int fd, const struct user_notification *notif)
|
|||
xassert(prefix != NULL);
|
||||
|
||||
if (write(fd, prefix, strlen(prefix)) < 0 ||
|
||||
write(fd, "foot: ", 6) < 0 ||
|
||||
write(fd, notif->text, strlen(notif->text)) < 0 ||
|
||||
write(fd, postfix, strlen(postfix)) < 0)
|
||||
{
|
||||
|
|
@ -176,7 +181,8 @@ emit_one_notification(int fd, const struct user_notification *notif)
|
|||
}
|
||||
|
||||
static bool
|
||||
emit_notifications_of_kind(int fd, const user_notifications_t *notifications,
|
||||
emit_notifications_of_kind(int fd,
|
||||
const user_notifications_t *notifications,
|
||||
enum user_notification_kind kind)
|
||||
{
|
||||
tll_foreach(*notifications, it) {
|
||||
|
|
@ -304,9 +310,69 @@ err:
|
|||
_exit(errno);
|
||||
}
|
||||
|
||||
static bool
|
||||
env_matches_var_name(const char *e, const char *name)
|
||||
{
|
||||
const size_t e_len = strlen(e);
|
||||
const size_t name_len = strlen(name);
|
||||
|
||||
if (e_len <= name_len)
|
||||
return false;
|
||||
if (memcmp(e, name, name_len) != 0)
|
||||
return false;
|
||||
if (e[name_len] != '=')
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
add_to_env(struct environ *env, const char *name, const char *value)
|
||||
{
|
||||
if (env->envp == NULL)
|
||||
setenv(name, value, 1);
|
||||
else {
|
||||
char *e = xstrjoin3(name, "=", value);
|
||||
|
||||
/* Search for existing variable. If found, replace it with the
|
||||
new value */
|
||||
for (size_t i = 0; i < env->count; i++) {
|
||||
if (env_matches_var_name(env->envp[i], name)) {
|
||||
free(env->envp[i]);
|
||||
env->envp[i] = e;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* If the variable does not already exist, add it */
|
||||
env->envp = xrealloc(env->envp, (env->count + 2) * sizeof(env->envp[0]));
|
||||
env->envp[env->count++] = e;
|
||||
env->envp[env->count] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
del_from_env(struct environ *env, const char *name)
|
||||
{
|
||||
if (env->envp == NULL)
|
||||
unsetenv(name);
|
||||
else {
|
||||
for (size_t i = 0; i < env->count; i++) {
|
||||
if (env_matches_var_name(env->envp[i], name)) {
|
||||
free(env->envp[i]);
|
||||
memmove(&env->envp[i],
|
||||
&env->envp[i + 1],
|
||||
(env->count - i) * sizeof(env->envp[0]));
|
||||
env->count--;
|
||||
xassert(env->envp[env->count] == NULL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pid_t
|
||||
slave_spawn(int ptmx, int argc, const char *cwd, char *const *argv,
|
||||
char *const *envp, const env_var_list_t *extra_env_vars,
|
||||
const char *const *envp, const env_var_list_t *extra_env_vars,
|
||||
const char *term_env, const char *conf_shell, bool login_shell,
|
||||
const user_notifications_t *notifications)
|
||||
{
|
||||
|
|
@ -351,14 +417,76 @@ slave_spawn(int ptmx, int argc, const char *cwd, char *const *argv,
|
|||
_exit(errno_copy);
|
||||
}
|
||||
|
||||
setenv("TERM", term_env, 1);
|
||||
setenv("TERM_PROGRAM", "foot", 1);
|
||||
setenv("TERM_PROGRAM_VERSION", FOOT_VERSION_SHORT, 1);
|
||||
setenv("COLORTERM", "truecolor", 1);
|
||||
setenv("PWD", cwd, 1);
|
||||
/* Create a mutable copy of the environment */
|
||||
struct environ custom_env = {0};
|
||||
if (envp != NULL) {
|
||||
for (const char *const *e = envp; *e != NULL; e++)
|
||||
custom_env.count++;
|
||||
|
||||
custom_env.envp = xcalloc(
|
||||
custom_env.count + 1, sizeof(custom_env.envp[0]));
|
||||
|
||||
size_t i = 0;
|
||||
for (const char *const *e = envp; *e != NULL; e++, i++)
|
||||
custom_env.envp[i] = xstrdup(*e);
|
||||
xassert(custom_env.envp[custom_env.count] == NULL);
|
||||
}
|
||||
|
||||
add_to_env(&custom_env, "TERM", term_env);
|
||||
add_to_env(&custom_env, "COLORTERM", "truecolor");
|
||||
add_to_env(&custom_env, "PWD", cwd);
|
||||
|
||||
del_from_env(&custom_env, "TERM_PROGRAM"); /* Wezterm, Ghostty */
|
||||
del_from_env(&custom_env, "TERM_PROGRAM_VERSION"); /* Wezterm, Ghostty */
|
||||
del_from_env(&custom_env, "TERMINAL_NAME"); /* Contour */
|
||||
del_from_env(&custom_env, "TERMINAL_VERSION_STRING"); /* Contour */
|
||||
del_from_env(&custom_env, "TERMINAL_VERSION_TRIPLE"); /* Contour */
|
||||
|
||||
/* XTerm specific */
|
||||
del_from_env(&custom_env, "XTERM_SHELL");
|
||||
del_from_env(&custom_env, "XTERM_VERSION");
|
||||
del_from_env(&custom_env, "XTERM_LOCALE");
|
||||
|
||||
/* Mlterm specific */
|
||||
del_from_env(&custom_env, "MLTERM");
|
||||
|
||||
/* Zutty specific */
|
||||
del_from_env(&custom_env, "ZUTTY_VERSION");
|
||||
|
||||
/* Ghostty specific */
|
||||
del_from_env(&custom_env, "GHOSTTY_BIN_DIR");
|
||||
del_from_env(&custom_env, "GHOSTTY_SHELL_INTEGRATION_NO_SUDO");
|
||||
del_from_env(&custom_env, "GHOSTTY_RESOURCES_DIR");
|
||||
|
||||
/* Kitty specific */
|
||||
del_from_env(&custom_env, "KITTY_WINDOW_ID");
|
||||
del_from_env(&custom_env, "KITTY_PID");
|
||||
del_from_env(&custom_env, "KITTY_PUBLIC_KEY");
|
||||
del_from_env(&custom_env, "KITTY_INSTALLATION_DIR");
|
||||
|
||||
/* Contour specific */
|
||||
del_from_env(&custom_env, "CONTOUR_PROFILE");
|
||||
|
||||
/* Wezterm specific */
|
||||
del_from_env(&custom_env, "WEZTERM_PANE");
|
||||
del_from_env(&custom_env, "WEZTERM_EXECUTABLE");
|
||||
del_from_env(&custom_env, "WEZTERM_CONFIG_FILE");
|
||||
del_from_env(&custom_env, "WEZTERM_EXECUTABLE_DIR");
|
||||
del_from_env(&custom_env, "WEZTERM_UNIX_SOCKET");
|
||||
del_from_env(&custom_env, "WEZTERM_CONFIG_DIR");
|
||||
|
||||
/* Alacritty specific */
|
||||
del_from_env(&custom_env, "ALACRITTY_LOG");
|
||||
del_from_env(&custom_env, "ALACRITTY_WINDOW_ID");
|
||||
del_from_env(&custom_env, "ALACRITTY_SOCKET");
|
||||
|
||||
/* VTE, gnome-terminal, kgx etc */
|
||||
del_from_env(&custom_env, "VTE_VERSION");
|
||||
del_from_env(&custom_env, "GNOME_TERMINAL_SERVICE");
|
||||
del_from_env(&custom_env, "GNOME_TERMINAL_SCREEN");
|
||||
|
||||
#if defined(FOOT_TERMINFO_PATH)
|
||||
setenv("TERMINFO", FOOT_TERMINFO_PATH, 1);
|
||||
add_to_env(&custom_env, "TERMINFO", FOOT_TERMINFO_PATH);
|
||||
#endif
|
||||
|
||||
if (extra_env_vars != NULL) {
|
||||
|
|
@ -367,9 +495,9 @@ slave_spawn(int ptmx, int argc, const char *cwd, char *const *argv,
|
|||
const char *value = it->item.value;
|
||||
|
||||
if (strlen(value) == 0)
|
||||
unsetenv(name);
|
||||
del_from_env(&custom_env, name);
|
||||
else
|
||||
setenv(name, value, 1);
|
||||
add_to_env(&custom_env, name, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -393,14 +521,23 @@ slave_spawn(int ptmx, int argc, const char *cwd, char *const *argv,
|
|||
}
|
||||
|
||||
if (is_valid_shell(shell_argv[0]))
|
||||
setenv("SHELL", shell_argv[0], 1);
|
||||
add_to_env(&custom_env, "SHELL", shell_argv[0]);
|
||||
|
||||
slave_exec(ptmx, shell_argv, envp != NULL ? envp : environ,
|
||||
slave_exec(ptmx, shell_argv,
|
||||
custom_env.envp != NULL ? custom_env.envp : environ,
|
||||
fork_pipe[1], login_shell, notifications);
|
||||
BUG("Unexpected return from slave_exec()");
|
||||
break;
|
||||
|
||||
default: {
|
||||
|
||||
/*
|
||||
* Don't stay in CWD, since it may be an ephemeral path. For
|
||||
* example, it may be a mount point of, say, a thumb drive. Us
|
||||
* keeping it open will prevent the user from unmounting it.
|
||||
*/
|
||||
(void)!!chdir("/");
|
||||
|
||||
close(fork_pipe[1]); /* Close write end */
|
||||
LOG_DBG("slave has PID %d", pid);
|
||||
|
||||
|
|
|
|||
2
slave.h
2
slave.h
|
|
@ -7,7 +7,7 @@
|
|||
#include "user-notification.h"
|
||||
|
||||
pid_t slave_spawn(
|
||||
int ptmx, int argc, const char *cwd, char *const *argv, char *const *envp,
|
||||
int ptmx, int argc, const char *cwd, char *const *argv, const char *const *envp,
|
||||
const env_var_list_t *extra_env_vars, const char *term_env,
|
||||
const char *conf_shell, bool login_shell,
|
||||
const user_notifications_t *notifications);
|
||||
|
|
|
|||
16
spawn.c
16
spawn.c
|
|
@ -15,9 +15,9 @@
|
|||
#include "debug.h"
|
||||
#include "xmalloc.h"
|
||||
|
||||
bool
|
||||
pid_t
|
||||
spawn(struct reaper *reaper, const char *cwd, char *const argv[],
|
||||
int stdin_fd, int stdout_fd, int stderr_fd,
|
||||
int stdin_fd, int stdout_fd, int stderr_fd, reaper_cb cb, void *cb_data,
|
||||
const char *xdg_activation_token)
|
||||
{
|
||||
int pipe_fds[2] = {-1, -1};
|
||||
|
|
@ -104,16 +104,16 @@ spawn(struct reaper *reaper, const char *cwd, char *const argv[],
|
|||
close(pipe_fds[0]);
|
||||
|
||||
if (ret == 0) {
|
||||
reaper_add(reaper, pid, NULL, NULL);
|
||||
return true;
|
||||
reaper_add(reaper, pid, cb, cb_data);
|
||||
return pid;
|
||||
} else if (ret < 0) {
|
||||
LOG_ERRNO("failed to read from pipe");
|
||||
return false;
|
||||
return -1;
|
||||
} else {
|
||||
LOG_ERRNO_P(errno_copy, "%s: failed to spawn", argv[0]);
|
||||
errno = errno_copy;
|
||||
waitpid(pid, NULL, 0);
|
||||
return false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
err:
|
||||
|
|
@ -121,7 +121,7 @@ err:
|
|||
close(pipe_fds[0]);
|
||||
if (pipe_fds[1] != -1)
|
||||
close(pipe_fds[1]);
|
||||
return false;
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
@ -145,7 +145,7 @@ spawn_expand_template(const struct config_spawn_template *template,
|
|||
expanded[len] = '\0'; \
|
||||
} while (0)
|
||||
|
||||
*argv = malloc((*argc + 1) * sizeof((*argv)[0]));
|
||||
*argv = xmalloc((*argc + 1) * sizeof((*argv)[0]));
|
||||
|
||||
/* Expand the provided keys */
|
||||
for (size_t i = 0; i < *argc; i++) {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue