Compare commits
453 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
853b0d9161 | ||
|
|
9dc5230000 | ||
|
|
2746e0319b | ||
|
|
e107254f1e | ||
|
|
2f9ffa3178 | ||
|
|
d64e5df5b4 | ||
|
|
454fb63392 | ||
|
|
4a42eb085c | ||
|
|
4661e74b5e | ||
|
|
d05eed3c01 | ||
|
|
45d8f98fd5 | ||
|
|
05feaecf8a | ||
|
|
5413c15e97 | ||
|
|
be840af45c | ||
|
|
4215998929 | ||
|
|
1ee963ede9 | ||
|
|
11c0a9567f | ||
|
|
822477cb35 | ||
|
|
cd6573493c | ||
|
|
1993b7e318 | ||
|
|
3b1747a107 | ||
|
|
9a69c1eecf | ||
|
|
82a1c101fd | ||
|
|
093e3cf1f7 | ||
|
|
00aaa84ab0 | ||
|
|
caf5b32d5c | ||
|
|
854696cfb5 | ||
|
|
c06ac08aab | ||
|
|
1597ec27ee | ||
|
|
6094944345 | ||
|
|
d54a10b200 | ||
|
|
e020701df1 | ||
|
|
5834b7e824 | ||
|
|
91ce3bdbd5 | ||
|
|
ffbbbf3477 | ||
|
|
051d3537e3 | ||
|
|
d91597b1c1 | ||
|
|
47cab33aa8 | ||
|
|
910e58585f | ||
|
|
6a530de220 | ||
|
|
0639167185 | ||
|
|
60cc6ce174 | ||
|
|
b9a796b24a | ||
|
|
f8befe378a | ||
|
|
230147c815 | ||
|
|
c3173af2f1 | ||
|
|
ca510e5e0f | ||
|
|
f169624560 | ||
|
|
5fdfb14530 | ||
|
|
a56670bca8 | ||
|
|
b660d6a902 | ||
|
|
9aba43119b | ||
|
|
96d3762712 | ||
|
|
69f68dcd74 | ||
|
|
a36618f96c | ||
|
|
b42dc21068 | ||
|
|
f1f2282947 | ||
|
|
1da50c4ae0 | ||
|
|
06b3137bd7 | ||
|
|
ee12c2d1e1 | ||
|
|
f844de8e66 | ||
|
|
26608b74d9 | ||
|
|
5489db6bc8 | ||
|
|
f4959d5da4 | ||
|
|
bffa4a543c | ||
|
|
908c86544b | ||
|
|
82b9821204 | ||
|
|
c1c405f4fc | ||
|
|
c6befec0fd | ||
|
|
c6f62f4695 | ||
|
|
d6e2a38b5c | ||
|
|
6fe98f7847 | ||
|
|
9947890472 | ||
|
|
cf2ea348c7 | ||
|
|
e6b41172da | ||
|
|
866e3dd909 | ||
|
|
3ae5f31d04 | ||
|
|
a95870120c | ||
|
|
fde43a078b | ||
|
|
a715c03d30 | ||
|
|
fae2b637ee | ||
|
|
d06f97c4d4 | ||
|
|
13474ff787 | ||
|
|
74785f4795 | ||
|
|
d7583fbc17 | ||
|
|
fbb6bf666f | ||
|
|
892ae730cb | ||
|
|
ecb9b895c7 | ||
|
|
a5da4d54f3 | ||
|
|
3702960a87 | ||
|
|
6911c116e7 | ||
|
|
bfbe73f665 | ||
|
|
0f64420281 | ||
|
|
26990d90f2 | ||
|
|
ba1f40f45f | ||
|
|
c52f13900d | ||
|
|
8d64937054 | ||
|
|
9c8746c00f | ||
|
|
fa25afedd2 | ||
|
|
46de32eedd | ||
|
|
944a262688 | ||
|
|
aaee2b3eae | ||
|
|
dfb3850989 | ||
|
|
90d7b9769c | ||
|
|
16f83396b4 | ||
|
|
8fe28d1a95 | ||
|
|
d06e87eb8d | ||
|
|
3e184daf29 | ||
|
|
7d3a3ae0fb | ||
|
|
6984dff01a | ||
|
|
47b2caa116 | ||
|
|
57f984ae67 | ||
|
|
b85da284a7 | ||
|
|
ab6f1fd160 | ||
|
|
ed690c7ba0 | ||
|
|
d5c8319b6c | ||
|
|
1786b13f0d | ||
|
|
fd95a47183 | ||
|
|
30131ed697 | ||
|
|
a1e4b44955 | ||
|
|
029cb8af19 | ||
|
|
60c3fedb73 | ||
|
|
96614a2f32 | ||
|
|
1ba0eaca22 | ||
|
|
2ac6180b90 | ||
|
|
170a322cc2 | ||
|
|
d130126204 | ||
|
|
2b236955bd | ||
|
|
be27a2f50d | ||
|
|
804bca3a9a | ||
|
|
14795c303c | ||
|
|
e6a28b9475 | ||
|
|
69e13d7821 | ||
|
|
2a91514a31 | ||
|
|
9dcf37b428 | ||
|
|
9e3a9e8225 | ||
|
|
c8fd8eff21 | ||
|
|
6fe625f469 | ||
|
|
d26ddcbfe5 | ||
|
|
6e6af01b7a | ||
|
|
a59423df81 | ||
|
|
2bfa06b7df | ||
|
|
9c9b110ed1 | ||
|
|
588cc4c79c | ||
|
|
4d0323fa9e | ||
|
|
3f400b8ad0 | ||
|
|
a5791b2e64 | ||
|
|
b2c696f680 | ||
|
|
b82b3e85da | ||
|
|
6b658f88be | ||
|
|
5b0f848a40 | ||
|
|
9ac027234b | ||
|
|
2fbb36b95f | ||
|
|
b825dc124a | ||
|
|
0b89d4b2a7 | ||
|
|
327bca26d8 | ||
|
|
c45342e74f | ||
|
|
62eb0033b1 | ||
|
|
a68eb3a71e | ||
|
|
080c73d1a4 | ||
|
|
1f53ae4614 | ||
|
|
23bc304477 | ||
|
|
c6bfd05276 | ||
|
|
85252a3bd1 | ||
|
|
9ffcc51183 | ||
|
|
39afa033e4 | ||
|
|
3b9d70af41 | ||
|
|
c55b52a7cc | ||
|
|
131b0c5b3d | ||
|
|
aa876585e8 | ||
|
|
f1754e12c0 | ||
|
|
e12d2f6a1d | ||
|
|
b88ca36a5a | ||
|
|
7abd58abf2 | ||
|
|
d62183a2b8 | ||
|
|
9d6a8735eb | ||
|
|
decc37eba1 | ||
|
|
3f58d51ec6 | ||
|
|
304e815ed4 | ||
|
|
0af2bac9ed | ||
|
|
5e4ed2fc75 | ||
|
|
de3fc07123 | ||
|
|
b18b80ca40 | ||
|
|
5e759ed424 | ||
|
|
941229ee62 | ||
|
|
55d400b17d | ||
|
|
2ba393f084 | ||
|
|
6479cb7deb | ||
|
|
8128774386 | ||
|
|
a6c86fd794 | ||
|
|
4f3d4c26f6 | ||
|
|
c5dc0d8c93 | ||
|
|
06e31ece8f | ||
|
|
812ec43d46 | ||
|
|
8ec41334ec | ||
|
|
f6097d4a37 | ||
|
|
eddced6b45 | ||
|
|
8e9b29419f | ||
|
|
016d0b5f07 | ||
|
|
4ab34d5042 | ||
|
|
28671a622b | ||
|
|
3e434ba0ce | ||
|
|
6ae232a323 | ||
|
|
5caf49323c | ||
|
|
12cdf435aa | ||
|
|
d7b9a45ff3 | ||
|
|
0967021858 | ||
|
|
f0856c285c | ||
|
|
ac95dffd6b | ||
|
|
2bdcae8149 | ||
|
|
c0ef3caec8 | ||
|
|
d7f4707e05 | ||
|
|
5bc4280a48 | ||
|
|
8ade46bdf0 | ||
|
|
227c1538be | ||
|
|
516d442e9a | ||
|
|
8252144cc3 | ||
|
|
0ac5e248f2 | ||
|
|
5ce8e3241b | ||
|
|
e48b9aa284 | ||
|
|
f795c2c8da | ||
|
|
4b5ead023e | ||
|
|
ac368e7916 | ||
|
|
6fb58eb841 | ||
|
|
4db383e430 | ||
|
|
e9c63d3001 | ||
|
|
b242bcebcf | ||
|
|
ebcd1d43ea | ||
|
|
807e972330 | ||
|
|
103dc7b55d | ||
|
|
ddc587933d | ||
|
|
458c148934 | ||
|
|
3597cc636e | ||
|
|
ce2665ca96 | ||
|
|
6ab64aa5b7 | ||
|
|
612a9317b0 | ||
|
|
dba30fc987 | ||
|
|
41cd852f2f | ||
|
|
c822eff1bf | ||
|
|
bd5cbbb010 | ||
|
|
70288d7b68 | ||
|
|
813336e068 | ||
|
|
b17117190d | ||
|
|
a4ac843cca | ||
|
|
70f23caa9a | ||
|
|
729452448b | ||
|
|
07ea5019c9 | ||
|
|
6c7efc4de8 | ||
|
|
298c3aede9 | ||
|
|
854616ed2e | ||
|
|
d44e1442c2 | ||
|
|
46bdd537a8 | ||
|
|
52c831ca22 | ||
|
|
b7056a82f4 | ||
|
|
e1d3e6b2f6 | ||
|
|
43e805a00d | ||
|
|
673185a63b | ||
|
|
c4fdcc5dbc | ||
|
|
8e9da491f4 | ||
|
|
7f3316269d | ||
|
|
93b2b44893 | ||
|
|
34ef7f8c33 | ||
|
|
bc4f35a6bb | ||
|
|
680ddc7e10 | ||
|
|
20d0591e77 | ||
|
|
117fb13177 | ||
|
|
863a9eada4 | ||
|
|
f36050b303 | ||
|
|
3c81e8ddaa | ||
|
|
38cf8a21ef | ||
|
|
c083e023b6 | ||
|
|
220144361a | ||
|
|
3e0fc25b6b | ||
|
|
ce0d08cc6d | ||
|
|
445cea8e3a | ||
|
|
d83940a8fc | ||
|
|
53f52beb09 | ||
|
|
63aec362c9 | ||
|
|
f97268dbd8 | ||
|
|
b952e74145 | ||
|
|
6fe33394e1 | ||
|
|
c6ea9bbace | ||
|
|
058ebb0d42 | ||
|
|
884717489f | ||
|
|
b895d9d3cc | ||
|
|
39ff5699d6 | ||
|
|
ecc4f91fb4 | ||
|
|
03a0375e7f | ||
|
|
aceb36ae60 | ||
|
|
e4d3ec9217 | ||
|
|
e4a3f6e4cd | ||
|
|
40b0c8e5b8 | ||
|
|
b0632bddfc | ||
|
|
8e46943985 | ||
|
|
36f9f86714 | ||
|
|
a352cecc6a | ||
|
|
9cc9cc2be0 | ||
|
|
b7af69cba8 | ||
|
|
19252515bc | ||
|
|
64021b6143 | ||
|
|
e938cae8a0 | ||
|
|
93e8ccd792 | ||
|
|
46f4fe4ecc | ||
|
|
d854dc597b | ||
|
|
1d05918961 | ||
|
|
533b76378a | ||
|
|
2472f8b8b4 | ||
|
|
d216a5c9d3 | ||
|
|
e05af2650e | ||
|
|
b37c3e3d25 | ||
|
|
b04f206e39 | ||
|
|
ab389e1d76 | ||
|
|
25bf911537 | ||
|
|
8323108ca1 | ||
|
|
b3d4281b8b | ||
|
|
eada44be1e | ||
|
|
16b09672c8 | ||
|
|
535da94536 | ||
|
|
d3ff9afbb5 | ||
|
|
d2f5e7e46e | ||
|
|
2e500f0817 | ||
|
|
a0938bd606 | ||
|
|
3686cef63c | ||
|
|
381d7e6a98 | ||
|
|
f9e6d5dd7a | ||
|
|
3d2a1ef80a | ||
|
|
570138eb72 | ||
|
|
e1ca00d164 | ||
|
|
af23c0febf | ||
|
|
ce2c21885d | ||
|
|
cc44d9a999 | ||
|
|
579973b1ef | ||
|
|
f45d5d1a53 | ||
|
|
3ba7599aa4 | ||
|
|
8649a7e229 | ||
|
|
3bdea50548 | ||
|
|
46cffcc059 | ||
|
|
986292c662 | ||
|
|
603e26baa8 | ||
|
|
9a02399cee | ||
|
|
5b6a564190 | ||
|
|
7b6e864823 | ||
|
|
01ba240272 | ||
|
|
36ba1043d5 | ||
|
|
fee005bc87 | ||
|
|
3a818c0f20 | ||
|
|
fc65ca36b1 | ||
|
|
abbf6a85d7 | ||
|
|
eaa5e636f9 | ||
|
|
4c93f61353 | ||
|
|
e4b3f46366 | ||
|
|
c94f41b2da | ||
|
|
e44aa7a9a9 | ||
|
|
d4c23ec24b | ||
|
|
8db3bef66d | ||
|
|
63099f7a83 | ||
|
|
1f75e8d321 | ||
|
|
73bcbbe1b1 | ||
|
|
bb6ee88440 | ||
|
|
2245db63ab | ||
|
|
d65a7ed250 | ||
|
|
60542da091 | ||
|
|
fcae64f7fd | ||
|
|
91e751d5a2 | ||
|
|
00d4836ee4 | ||
|
|
9db03797da | ||
|
|
c8158875b4 | ||
|
|
9bf9cb9926 | ||
|
|
496364fdbf | ||
|
|
fcf4c235d2 | ||
|
|
e150b99ba8 | ||
|
|
39376a94ab | ||
|
|
52afb16391 | ||
|
|
1c67683406 | ||
|
|
fd500ee99d | ||
|
|
8a5eac3457 | ||
|
|
af2171d01d | ||
|
|
db5a7dc22a | ||
|
|
42c3dbe025 | ||
|
|
f8bc7052ea | ||
|
|
bae1f4b354 | ||
|
|
757b3dd573 | ||
|
|
b23c8875f7 | ||
|
|
8d645d0de6 | ||
|
|
46bc841162 | ||
|
|
7da136dca4 | ||
|
|
4ef47087d4 | ||
|
|
98e0a07f15 | ||
|
|
ddebebd26b | ||
|
|
0e1a828003 | ||
|
|
b95bab0e34 | ||
|
|
521949b567 | ||
|
|
5df0b4b571 | ||
|
|
e8949c7df1 | ||
|
|
8ff8db3c36 | ||
|
|
f2243a7a90 | ||
|
|
36b6fef990 | ||
|
|
ab2a22a78b | ||
|
|
001577c58c | ||
|
|
33bbce6c38 | ||
|
|
09e2bdaeba | ||
|
|
ef12e9a5ab | ||
|
|
60213accae | ||
|
|
62367c686d | ||
|
|
32c10a19f2 | ||
|
|
921226783f | ||
|
|
8c8d5dc019 | ||
|
|
dcd6079c9b | ||
|
|
131a6158c8 | ||
|
|
e4f12ac349 | ||
|
|
ba134d3c7c | ||
|
|
6668d03157 | ||
|
|
9c7616cc98 | ||
|
|
1a29b28fa5 | ||
|
|
6e0b29a65b | ||
|
|
0370c5e297 | ||
|
|
31580ddc7f | ||
|
|
052c4bea5c | ||
|
|
c1ec2ad19b | ||
|
|
fb5d2a05c2 | ||
|
|
2a2c350eb6 | ||
|
|
057a635279 | ||
|
|
d3d2612eba | ||
|
|
5bf58a4c87 | ||
|
|
bfa22cafea | ||
|
|
337dd653ae | ||
|
|
8d0b8b59c4 | ||
|
|
3f063bcb6f | ||
|
|
b638ca593d | ||
|
|
f2ec5a4e77 | ||
|
|
b2af112a04 | ||
|
|
87f033fbd4 | ||
|
|
d539c6e288 | ||
|
|
5354eaa27e | ||
|
|
b1fb440345 | ||
|
|
1d06cb976e | ||
|
|
b6ced01676 | ||
|
|
74b461e25e | ||
|
|
da1a720782 | ||
|
|
3a1e44da68 | ||
|
|
146305d6c6 | ||
|
|
679335f19b | ||
|
|
27ff84ff42 | ||
|
|
be4790802e | ||
|
|
b6690045ed | ||
|
|
37ebd2a179 | ||
|
|
13757ac1ca | ||
|
|
68258785ac | ||
|
|
d0067077ed | ||
|
|
eb83b37936 | ||
|
|
9f3a3a1d98 | ||
|
|
4b1ea08eef | ||
|
|
a901498758 |
@@ -1,13 +1,14 @@
|
||||
BasedOnStyle: google
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
IndentWidth: 4
|
||||
PointerBindsToType: false
|
||||
BasedOnStyle: google
|
||||
ColumnLimit: 0
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SortIncludes: false
|
||||
ForEachMacros: [ TAILQ_FOREACH, TAILQ_FOREACH_REVERSE, SLIST_FOREACH, CIRCLEQ_FOREACH, CIRCLEQ_FOREACH_REVERSE, NODES_FOREACH, NODES_FOREACH_REVERSE, FOREACH_NONINTERNAL]
|
||||
IndentWidth: 4
|
||||
InsertBraces: true
|
||||
PointerBindsToType: false
|
||||
SortIncludes: false
|
||||
SpaceBeforeParens: ControlStatements
|
||||
TypenameMacros: [ SLIST_HEAD, SLIST_ENTRY, LIST_HEAD, LIST_ENTRY, SIMPLEQ_HEAD, SIMPLEQ_ENTRY, TAILQ_HEAD, TAILQ_ENTRY, CIRCLEQ_HEAD, CIRCLEQ_ENTRY ]
|
||||
|
||||
29
.github/CONTRIBUTING.md
vendored
@@ -4,7 +4,7 @@
|
||||
|
||||
Note that bug reports and feature requests for related projects should be filed in the corresponding repositories for [i3status](https://github.com/i3/i3status) and [i3lock](https://github.com/i3/i3lock).
|
||||
|
||||
## i3 bug reports and feature requests
|
||||
## i3 bug reports
|
||||
|
||||
1. Read the [debugging instructions](https://i3wm.org/docs/debugging.html).
|
||||
2. Make sure you include a link to your logfile in your report (section 3).
|
||||
@@ -18,6 +18,20 @@ Note that bug reports and feature requests for related projects should be filed
|
||||
encountered the issue you are about to report while using a compositor,
|
||||
please try reproducing it without a compositor.
|
||||
|
||||
## i3 feature requests
|
||||
|
||||
1. Read the [project goals](https://i3wm.org) on the website and make sure that
|
||||
they are compatible with the feature you want to suggest.
|
||||
2. We are generally happy with the current feature set of i3 and instead focus
|
||||
on maintenance such as stability and fixing bugs. New features will rarely
|
||||
be considered if they require additional configuration and/or commands, or
|
||||
if they add significant complexity (either through the exposed configuration
|
||||
or mental complexity) to the project.
|
||||
3. Explain in detail what problem the feature addresses and why existing
|
||||
features fall short.
|
||||
4. Consider whether the feature could instead be implemented using the
|
||||
[IPC](https://i3wm.org/docs/ipc.html) or other external tooling.
|
||||
|
||||
## Pull requests
|
||||
|
||||
* Before sending a pull request for new features, please check with us that the
|
||||
@@ -27,11 +41,14 @@ Note that bug reports and feature requests for related projects should be filed
|
||||
* Use `clang-format` to format your code.
|
||||
* Run the [testsuite](https://i3wm.org/docs/testsuite.html)
|
||||
* If your changes should be reported on the next release's changelog, also
|
||||
update the [RELEASE-notes-next](../RELEASE-notes-next) file in the root
|
||||
folder. Example of changes that should be reported are bug fixes present in
|
||||
the latest stable version of i3 and new enhancements. Example of changes that
|
||||
should not be reported are minor code improvements, documentation, regression
|
||||
and fixes for bugs that were introduced in the `next` branch.
|
||||
add a small single-line file starting with a number (see examples) containing
|
||||
a short explanation of your change either in the
|
||||
[changes](../release-notes/changes) or the
|
||||
[bugfixes](../release-notes/bugfixes/) folder. Example of changes that should
|
||||
be reported are bug fixes present in the latest stable version of i3 and new
|
||||
enhancements. Example of changes that should not be reported are minor code
|
||||
improvements, documentation, regression and fixes for bugs that were
|
||||
introduced in the `next` branch.
|
||||
|
||||
## Finding something to do
|
||||
|
||||
|
||||
7
.github/ISSUE_TEMPLATE.md
vendored
@@ -3,7 +3,12 @@ PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATI
|
||||
-->
|
||||
|
||||
## I'm submitting a…
|
||||
<!-- Please check one of the following options with "x" -->
|
||||
<!--
|
||||
Check one of the following options with "x".
|
||||
|
||||
Please note that at this point we focus on maintaining i3 and fixing bugs, and will rarely consider features which require further configuration or significant complexity.
|
||||
In such cases you should consider and present specific benefits derived from adding this feature such that it can be weighed against the cost of additional complexity and maintenance.
|
||||
-->
|
||||
<pre>
|
||||
[ ] Bug
|
||||
[ ] Feature Request
|
||||
|
||||
72
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,72 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
---
|
||||
|
||||
<!--
|
||||
PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
|
||||
-->
|
||||
|
||||
## I'm submitting a…
|
||||
<!-- Please check one of the following options with "x" -->
|
||||
<pre>
|
||||
[x] Bug
|
||||
[ ] Feature Request
|
||||
[ ] Documentation Request
|
||||
[ ] Other (Please describe in detail)
|
||||
</pre>
|
||||
|
||||
## Current Behavior
|
||||
<!--
|
||||
Describe the current behavior,
|
||||
e.g., »When pressing Alt+j (focus left), the window above the current window is focused.«
|
||||
-->
|
||||
|
||||
## Expected Behavior
|
||||
<!--
|
||||
Describe the desired behavior you expect after mitigation of the issue,
|
||||
e.g., »The window left next to the current window should be focused.«
|
||||
-->
|
||||
|
||||
## Reproduction Instructions
|
||||
<!--
|
||||
Please provide detailed instructions on how the bug can be reproduced.
|
||||
E.g., »Open three windows in a V[A H[B C]] layout on a new workspace«
|
||||
-->
|
||||
|
||||
## Environment
|
||||
<!--
|
||||
Please include your exact i3 version.
|
||||
Note that we only support the latest major release and the current development version. If you are using an older version of i3, please first update to the current release version and reproduce the issue there.
|
||||
-->
|
||||
Output of `i3 --moreversion 2>&-`:
|
||||
<pre>
|
||||
i3 version:
|
||||
</pre>
|
||||
|
||||
<!--
|
||||
Please include your (complete) i3 config with which the issue occurs. You can either paste the file directly or provide a link to a service such as pastebin.
|
||||
|
||||
If you would like to help debugging the issue, please try to reduce the config such that it is as close to the default config as possible while still reproducing the issue. This can help us bisect the root cause.
|
||||
-->
|
||||
<details><summary>Config file</summary><pre>
|
||||
</pre>
|
||||
</details>
|
||||
|
||||
<!--
|
||||
Providing a logfile can help us trace the root cause of an issue much quicker. You can learn how to generate the logfile here:
|
||||
https://i3wm.org/docs/debugging.html
|
||||
|
||||
Providing the logfile is optional.
|
||||
-->
|
||||
<pre>
|
||||
Logfile URL:
|
||||
</pre>
|
||||
|
||||
<!--
|
||||
Please also answer the questions below to help us process your issue faster. If you have any other information to share, please add it here as well.
|
||||
-->
|
||||
<pre>
|
||||
- Linux Distribution & Version:
|
||||
- Are you using a compositor (e.g., xcompmgr or compton):
|
||||
</pre>
|
||||
107
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Executable file
@@ -0,0 +1,107 @@
|
||||
name: Bug Report
|
||||
description: Create a report to help us improve.
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: Welcome
|
||||
options:
|
||||
- label: Yes, I'm using the latest major release or the current development version. These are the only supported versions.
|
||||
required: true
|
||||
- label: Yes, I've searched similar issues and discussions on GitHub and didn't find any.
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: current
|
||||
attributes:
|
||||
label: Current Behavior
|
||||
placeholder: |-
|
||||
Describe the current behavior,
|
||||
e.g., »When pressing Alt+j (focus left), the window above the current window is focused.«
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
placeholder: |-
|
||||
Describe the desired behavior you expect after mitigation of the issue,
|
||||
e.g., »The window left next to the current window should be focused.«
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Reproduction Instructions
|
||||
placeholder: |-
|
||||
Please provide detailed instructions on how the bug can be reproduced.
|
||||
E.g., »Open three windows in a V[A H[B C]] layout on a new workspace«
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: version
|
||||
attributes:
|
||||
label: i3 version
|
||||
description: |-
|
||||
Paste the output of
|
||||
```
|
||||
i3 --moreversion 2>&-
|
||||
```
|
||||
render: text
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: config
|
||||
attributes:
|
||||
label: Config file
|
||||
description: |-
|
||||
Please include your (complete) i3 config with which the issue occurs.
|
||||
|
||||
If you would like to help debugging the issue, please try to reduce the config such that it is as close to the default config as possible while still reproducing the issue. This can help us bisect the root cause.
|
||||
render: text
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: distro
|
||||
attributes:
|
||||
label: Linux distribution & Version
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: compositor
|
||||
attributes:
|
||||
label: Are you using a compositor?
|
||||
description: |-
|
||||
Try running
|
||||
```shell
|
||||
pidof picom
|
||||
pidof compton
|
||||
```
|
||||
If any IDs show up, you are running a compositor
|
||||
options:
|
||||
- I don't know
|
||||
- I am sure I don't run any compositor
|
||||
- picom
|
||||
- compton
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: verbose-output
|
||||
attributes:
|
||||
label: Logfile
|
||||
description: |-
|
||||
Providing the URL to a logfile can help us trace the root cause of an issue much quicker. You can learn how to generate the logfile here:
|
||||
https://i3wm.org/docs/debugging.html
|
||||
|
||||
Providing the logfile is optional.
|
||||
validations:
|
||||
required: false
|
||||
7
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
contact_links:
|
||||
- name: Userguide
|
||||
url: https://i3wm.org/docs/userguide.html
|
||||
about: i3 User’s Guide
|
||||
- name: Ask a question or request support for using i3
|
||||
url: https://github.com/i3/i3/discussions/new
|
||||
about: Ask a question or request support for using i3
|
||||
47
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,47 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
---
|
||||
|
||||
<!--
|
||||
PLEASE HELP US PROCESS GITHUB ISSUES FASTER BY PROVIDING THE FOLLOWING INFORMATION.
|
||||
-->
|
||||
|
||||
## I'm submitting a…
|
||||
<!-- Please check one of the following options with "x" -->
|
||||
<pre>
|
||||
[ ] Bug
|
||||
[x] Feature Request
|
||||
[ ] Documentation Request
|
||||
[ ] Other (Please describe in detail)
|
||||
</pre>
|
||||
|
||||
## Current Behavior
|
||||
<!--
|
||||
Describe the current behavior,
|
||||
e.g., »When pressing Alt+j (focus left), the window above the current window is focused.«
|
||||
-->
|
||||
|
||||
## Desired Behavior
|
||||
<!--
|
||||
Describe the desired behavior you expect after mitigation of the issue,
|
||||
e.g., »The window left next to the current window should be focused.«
|
||||
-->
|
||||
|
||||
## Environment
|
||||
<!--
|
||||
Please include your exact i3 version.
|
||||
Note that we only support the latest major release and the current development version. If you are using an older version of i3, please first update to the current release version and reproduce the issue there.
|
||||
-->
|
||||
Output of `i3 --moreversion 2>&-`:
|
||||
<pre>
|
||||
i3 version:
|
||||
</pre>
|
||||
|
||||
<!--
|
||||
Please also answer the questions below to help us process your issue faster. If you have any other information to share, please add it here as well.
|
||||
-->
|
||||
<pre>
|
||||
- Linux Distribution & Version:
|
||||
- Are you using a compositor (e.g., xcompmgr or compton):
|
||||
</pre>
|
||||
57
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Executable file
@@ -0,0 +1,57 @@
|
||||
name: Feature request
|
||||
description: Suggest an idea for this project
|
||||
labels: [enhancement]
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: Welcome
|
||||
options:
|
||||
- label: Yes, I've searched similar issues and discussions on GitHub and didn't find any.
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: impact
|
||||
attributes:
|
||||
label: Impact
|
||||
description: |-
|
||||
Please note that at this point we focus on maintaining i3 and fixing bugs, and will rarely consider features which require further configuration or significant complexity.
|
||||
In such cases you should consider and present specific benefits derived from adding this feature such that it can be weighed against the cost of additional complexity and maintenance.
|
||||
Keep in mind that i3 provides a powerful way to interact with it through its IPC interface: https://i3wm.org/docs/ipc.html.
|
||||
options:
|
||||
- label: This feature requires new configuration and/or commands
|
||||
required: false
|
||||
|
||||
|
||||
- type: textarea
|
||||
id: current
|
||||
attributes:
|
||||
label: Current Behavior
|
||||
placeholder: |-
|
||||
Describe the current behavior,
|
||||
e.g., »When pressing Alt+j (focus left), the window above the current window is focused.«
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: desired
|
||||
attributes:
|
||||
label: Desired Behavior
|
||||
placeholder: |-
|
||||
Describe the desired behavior you expect after mitigation of the issue,
|
||||
e.g., »The window left next to the current window should be focused.«
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: version
|
||||
attributes:
|
||||
label: i3 version
|
||||
description: |-
|
||||
Paste the output of
|
||||
```
|
||||
i3 --moreversion 2>&-
|
||||
```
|
||||
render: text
|
||||
validations:
|
||||
required: true
|
||||
98
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
name: GitHub Actions
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ next, master, actions ]
|
||||
pull_request:
|
||||
branches: [ next ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: build and test
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
compiler: [gcc, clang]
|
||||
env:
|
||||
CC: ${{ matrix.compiler }}
|
||||
DOCKER_PASS: ${{ secrets.DOCKER_PASS }}
|
||||
DOCKER_EMAIL: ${{ secrets.DOCKER_EMAIL }}
|
||||
DOCKER_USER: ${{ secrets.DOCKER_USER }}
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
BALTO_TOKEN: ${{ secrets.BALTO_TOKEN }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- run: git fetch --prune --unshallow
|
||||
- name: construct container name
|
||||
run: |
|
||||
echo "BASENAME=i3wm/travis-base:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base.Dockerfile)" >> $GITHUB_ENV
|
||||
echo "BASENAME_UBUNTU=i3wm/travis-base-ubuntu:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-ubuntu.Dockerfile)" >> $GITHUB_ENV
|
||||
- name: fetch or build Docker container
|
||||
run: |
|
||||
docker pull ${{ env.BASENAME }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME }} travis/travis-base.Dockerfile
|
||||
- name: fetch or build extra Docker containers
|
||||
if: github.ref == 'refs/heads/next' && matrix.compiler == 'gcc'
|
||||
run: |
|
||||
docker pull ${{ env.BASENAME_UBUNTU }} || ./travis/docker-build-and-push.sh ${{ env.BASENAME_UBUNTU }} travis/travis-base-ubuntu.Dockerfile
|
||||
- name: build i3
|
||||
run: |
|
||||
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${{ env.BASENAME }} /bin/sh -c 'rm -rf build; mkdir -p build && cd build && CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Wstrict-prototypes -Wmissing-prototypes -Werror -fno-common -D_FORTIFY_SOURCE=3" meson setup .. -Ddocs=true -Dmans=true -Db_sanitize=address --buildtype=debugoptimized && ninja -v'
|
||||
- name: Upload docs html for manual inspection
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: i3-docs
|
||||
path: |
|
||||
build/*.html
|
||||
if: matrix.compiler == 'gcc'
|
||||
- name: check spelling
|
||||
run: |
|
||||
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/check-spelling.pl
|
||||
- name: run i3 tests
|
||||
run: |
|
||||
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${{ env.BASENAME }} ./travis/run-tests.sh
|
||||
- name: Archive test logs
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-logs
|
||||
path: build/testsuite-*
|
||||
if: ${{ failure() }}
|
||||
- name: build dist tarball
|
||||
run: |
|
||||
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${{ env.BASENAME }} /bin/sh -c 'rm -rf distbuild; mkdir distbuild && cd distbuild && meson setup .. -Ddocs=true -Dmans=true && meson dist --no-tests'
|
||||
- name: build Debian packages
|
||||
if: github.ref == 'refs/heads/next' && matrix.compiler == 'gcc'
|
||||
run: |
|
||||
echo "::group::Debian amd64"
|
||||
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/debian-build.sh deb/debian-amd64/DIST
|
||||
echo "::endgroup::"
|
||||
echo "::group::Ubuntu amd64"
|
||||
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME_UBUNTU }} ./travis/debian-build.sh deb/ubuntu-amd64/DIST
|
||||
echo "::endgroup::"
|
||||
- name: push Debian packages to balto
|
||||
if: github.ref == 'refs/heads/next' && matrix.compiler == 'gcc'
|
||||
run: |
|
||||
travis/push-balto.sh
|
||||
- name: build docs
|
||||
if: github.ref == 'refs/heads/next' && matrix.compiler == 'gcc'
|
||||
run: |
|
||||
docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${{ env.BASENAME }} ./travis/docs.sh
|
||||
- name: push docs to GitHub pages
|
||||
if: github.ref == 'refs/heads/next' && matrix.compiler == 'gcc'
|
||||
run: |
|
||||
travis/deploy-github-pages.sh
|
||||
formatting:
|
||||
name: Check formatting
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: check & print release notes
|
||||
run: ./release-notes/generator.pl
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get install -y clang-format-15
|
||||
- name: Check formatting
|
||||
run: clang-format-15 --dry-run --Werror $(git ls-files '*.c' '*.h')
|
||||
- name: Verify safe wrapper functions are used
|
||||
run: ./travis/check-safe-wrappers.sh
|
||||
1
.gitignore
vendored
@@ -50,4 +50,3 @@ LAST_VERSION
|
||||
# it is up to you to arrange for it to be ignored by git,
|
||||
# e.g. by listing your directory in .git/info/exclude.
|
||||
/build
|
||||
|
||||
|
||||
71
.travis.yml
@@ -1,71 +0,0 @@
|
||||
dist: trusty
|
||||
services:
|
||||
- docker
|
||||
language: c
|
||||
compiler:
|
||||
- gcc
|
||||
- clang
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
# For https support in HTTP::Tiny.
|
||||
- libio-socket-ssl-perl
|
||||
env:
|
||||
global:
|
||||
- BASENAME="i3wm/travis-base:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base.Dockerfile)"
|
||||
- BASENAME_386="i3wm/travis-base-386:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-386.Dockerfile)"
|
||||
- BASENAME_UBUNTU="i3wm/travis-base-ubuntu:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-ubuntu.Dockerfile)"
|
||||
- BASENAME_UBUNTU_386="i3wm/travis-base-ubuntu-386:$(date +'%Y-%m')-$(./travis/ha.sh travis/travis-base-ubuntu-386.Dockerfile)"
|
||||
- secure: "B5IICA8MPx/FKaB50rTPqL8P1NU+Q0yuWl+lElL4+a9xSyLikfm3NzUPHoVwx8lNw2AVK6br7p0OmF7vMFjqAgrgc1cajTtEae5uFRKNUrWLpXM046YgNEYLLIHsQOjInxE+S4O6EFVzsUqsu8aeo2Xhq4sm4iUocG7e5isYgYo=" # DOCKER_PASS
|
||||
- secure: "EIvrq8PG7lRjidppG0RCv4F0X4GP3DT9F5+ixVuGPfhK/hZT3jYC2AVY9G+NnUcXVwQEpW92rlqpftQ/qZ13FoyWokC8ZyoyD06fr5FPCfoFF3OczZwAJzZYkObI/hE9+/hXcylx/Os6N4INd2My1ntGk3JPsWL9riopod5EjSg=" # DOCKER_EMAIL
|
||||
- secure: "hvhBunS4xXTgnIOsk/BPT7I7FrJhvVwCSt5PfxxvMqNaztOJI9BuK7ZrZ5Cy38KyHwlh3VHAH5AaCygJcPauoSQCV3bpnlbaWn3ruq2F0Q697Q5uNf73liXzyUqb9/Zvfvge4y4WWOhP5tVz1C6ZBe/NfhU7pqKLMA+6ads+99c=" # DOCKER_USER
|
||||
- secure: "uJuuefmnJUuEH15ZD8xQilibx7EeBvMHBLoIZ8bgGHeleEImBD0XbD1ypvhYJKpviOmw5BkZmc9bVO8DGDEHYbSlIa2xDlF6vGrwgCEaxcMIhOAhv+dW9C/maJVieLOEPM01/fK2qdKESZaLvlopkWmxZwDyMObI9L7AMW9zQD8=" # BINTRAY_USER
|
||||
- secure: "L3aPSNLySPXtWCW+xf8h/AAdquwNgxyTQpYOwexJmTPav82Qx8uQlp1yJkUmt+a+FLZDFfQeMivaHq0311RvuQVmkAJx49DjaddrwqOJut2UPsoVDn1WeuAcSHIXOq/0H+zgFMr/PGY0HXIsw1mTMhgheGJNqg09BvYWROCEAcA=" # BINTRAY_KEY
|
||||
- secure: "sBMVn4C/WRWgoAytEFGx4CC5O55Q63h02AcuBnb1jXcBm0RenoBpzUPtxSseJwDPUA1o/UkuEDDjm3PosT5NF+dvED01VDFMsPVE11K0u6+avYy3jYXqyUEDW3G2o6Wo/2aqNjmd++8jskBdS9+Cx9gaFbgxfzSp0Yfu3oJm/4c=" # GH_TOKEN
|
||||
install:
|
||||
- if [ -a .git/shallow ]; then git fetch --unshallow; fi
|
||||
- docker pull ${BASENAME} || ./travis/docker-build-and-push.sh ${BASENAME} travis/travis-base.Dockerfile
|
||||
- ./travis/skip-pkg.sh || docker pull ${BASENAME_UBUNTU} || ./travis/docker-build-and-push.sh ${BASENAME_UBUNTU} travis/travis-base-ubuntu.Dockerfile
|
||||
- ./travis/skip-pkg.sh || docker pull ${BASENAME_386} || ./travis/docker-build-and-push.sh ${BASENAME_386} travis/travis-base-386.Dockerfile
|
||||
- ./travis/skip-pkg.sh || docker pull ${BASENAME_UBUNTU_386} || ./travis/docker-build-and-push.sh ${BASENAME_UBUNTU_386} travis/travis-base-ubuntu-386.Dockerfile
|
||||
script:
|
||||
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-safe-wrappers.sh
|
||||
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-formatting.sh
|
||||
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${BASENAME} /bin/sh -c 'rm -rf build; mkdir -p build && cd build && CFLAGS="-Wformat -Wformat-security -Wextra -Wno-unused-parameter -Wstrict-prototypes -Wmissing-prototypes -Werror -fno-common" meson .. -Ddocs=true -Dmans=true -Db_sanitize=address && ninja -v'
|
||||
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/check-spelling.pl
|
||||
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${BASENAME} ./travis/run-tests.sh
|
||||
- docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 -e CC ${BASENAME} /bin/sh -c 'rm -rf distbuild; mkdir distbuild && cd distbuild && meson .. -Ddocs=true -Dmans=true && ninja -v dist'
|
||||
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/debian-build.sh deb/debian-amd64/DIST
|
||||
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME_UBUNTU} ./travis/debian-build.sh deb/ubuntu-amd64/DIST
|
||||
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME_386} linux32 ./travis/debian-build.sh deb/debian-i386/DIST
|
||||
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME_UBUNTU_386} linux32 ./travis/debian-build.sh deb/ubuntu-i386/DIST
|
||||
- ./travis/skip-pkg.sh || docker run -v $PWD:/usr/src/i3/ -w /usr/src/i3 ${BASENAME} ./travis/docs.sh
|
||||
- ./travis/skip-pkg.sh || travis/prep-bintray.sh
|
||||
|
||||
deploy:
|
||||
- provider: bintray
|
||||
file: travis/bintray-autobuild-debian.json
|
||||
user: $BINTRAY_USER
|
||||
key: $BINTRAY_KEY
|
||||
skip_cleanup: true
|
||||
on:
|
||||
branch: next
|
||||
condition: $CC = gcc
|
||||
- provider: bintray
|
||||
file: travis/bintray-autobuild-ubuntu.json
|
||||
user: $BINTRAY_USER
|
||||
key: $BINTRAY_KEY
|
||||
skip_cleanup: true
|
||||
on:
|
||||
branch: next
|
||||
condition: $CC = gcc
|
||||
- provider: script
|
||||
script: travis/deploy-github-pages.sh
|
||||
skip_cleanup: true
|
||||
on:
|
||||
branch: next
|
||||
condition: $CC = gcc
|
||||
|
||||
after_deploy:
|
||||
- travis/cleanup-bintray.pl i3-autobuild
|
||||
- travis/cleanup-bintray.pl i3-autobuild-ubuntu
|
||||
@@ -1,5 +1,14 @@
|
||||
Revision history for AnyEvent-I3
|
||||
|
||||
0.19 2024-04-09
|
||||
|
||||
* use Carp for errors (includes stacktraces)
|
||||
* introduce (preferred) RUN_COMMAND spelling
|
||||
* migrate tooling to ExtUtils::MakeMaker
|
||||
* implement the tick event
|
||||
* introduce the sync IPC command
|
||||
* introduce the GET_BINDING_STATE IPC command
|
||||
|
||||
0.18 2017-08-19
|
||||
|
||||
* support the GET_CONFIG command
|
||||
|
||||
@@ -8,7 +8,6 @@ use AnyEvent::Handle;
|
||||
use AnyEvent::Socket;
|
||||
use AnyEvent;
|
||||
use Encode;
|
||||
use Scalar::Util qw(tainted);
|
||||
use Carp;
|
||||
|
||||
=head1 NAME
|
||||
@@ -17,11 +16,11 @@ AnyEvent::I3 - communicate with the i3 window manager
|
||||
|
||||
=cut
|
||||
|
||||
our $VERSION = '0.18';
|
||||
our $VERSION = '0.19';
|
||||
|
||||
=head1 VERSION
|
||||
|
||||
Version 0.18
|
||||
Version 0.19
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
@@ -132,35 +131,10 @@ sub i3 {
|
||||
AnyEvent::I3->new(@_)
|
||||
}
|
||||
|
||||
# Calls i3, even when running in taint mode.
|
||||
sub _call_i3 {
|
||||
my ($args) = @_;
|
||||
|
||||
my $path_tainted = tainted($ENV{PATH});
|
||||
# This effectively circumvents taint mode checking for $ENV{PATH}. We
|
||||
# do this because users might specify PATH explicitly to call i3 in a
|
||||
# custom location (think ~/.bin/).
|
||||
(local $ENV{PATH}) = ($ENV{PATH} =~ /(.*)/);
|
||||
|
||||
# In taint mode, we also need to remove all relative directories from
|
||||
# PATH (like . or ../bin). We only do this in taint mode and warn the
|
||||
# user, since this might break a real-world use case for some people.
|
||||
if ($path_tainted) {
|
||||
my @dirs = split /:/, $ENV{PATH};
|
||||
my @filtered = grep !/^\./, @dirs;
|
||||
if (scalar @dirs != scalar @filtered) {
|
||||
$ENV{PATH} = join ':', @filtered;
|
||||
warn qq|Removed relative directories from PATH because you | .
|
||||
qq|are running Perl with taint mode enabled. Remove -T | .
|
||||
qq|to be able to use relative directories in PATH. | .
|
||||
qq|New PATH is "$ENV{PATH}"|;
|
||||
}
|
||||
}
|
||||
# Otherwise the qx() operator wont work:
|
||||
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
|
||||
chomp(my $result = qx(i3 $args));
|
||||
# Circumventing taint mode again: the socket can be anywhere on the
|
||||
# system and that’s okay.
|
||||
if ($result =~ /^([^\0]+)$/) {
|
||||
return $1;
|
||||
}
|
||||
@@ -182,21 +156,21 @@ instance on the current DISPLAY which is almost always what you want.
|
||||
sub new {
|
||||
my ($class, $path) = @_;
|
||||
|
||||
$path = _call_i3('--get-socketpath') unless $path;
|
||||
|
||||
# This is the old default path (v3.*). This fallback line can be removed in
|
||||
# a year from now. -- Michael, 2012-07-09
|
||||
$path ||= '~/.i3/ipc.sock';
|
||||
# We have I3SOCK now
|
||||
$path ||= $ENV{I3SOCK};
|
||||
$path ||= _call_i3('--get-socketpath');
|
||||
|
||||
# Check if we need to resolve ~
|
||||
if ($path =~ /~/) {
|
||||
# We use getpwuid() instead of $ENV{HOME} because the latter is tainted
|
||||
# and thus produces warnings when running tests with perl -T
|
||||
my $home = (getpwuid($<))[7];
|
||||
my $home = $ENV{HOME};
|
||||
confess "Could not get home directory" unless $home and -d $home;
|
||||
$path =~ s/~/$home/g;
|
||||
}
|
||||
|
||||
if(!-S $path) {
|
||||
die "$path is not a socket", $/;
|
||||
}
|
||||
|
||||
bless { path => $path } => $class;
|
||||
}
|
||||
|
||||
@@ -315,6 +289,11 @@ sub subscribe {
|
||||
|
||||
# Register callbacks for each message type
|
||||
for my $key (keys %{$callbacks}) {
|
||||
if (!exists $events{$key}) {
|
||||
warn "Could not subscribe to event type '$key'." .
|
||||
" Supported events are " . join(" ", sort keys %events), $/;
|
||||
next;
|
||||
}
|
||||
my $type = $events{$key};
|
||||
$self->{callbacks}->{$type} = $callbacks->{$key};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!perl -T
|
||||
#!perl
|
||||
|
||||
use Test::More tests => 1;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!perl -T
|
||||
#!perl
|
||||
# vim:ts=4:sw=4:expandtab
|
||||
|
||||
use Test::More tests => 3;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!perl -T
|
||||
#!perl
|
||||
# vim:ts=4:sw=4:expandtab
|
||||
|
||||
use Test::More tests => 3;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!perl -T
|
||||
#!perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!perl -T
|
||||
#!perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!perl -T
|
||||
#!perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
1
I3_VERSION
Normal file
@@ -0,0 +1 @@
|
||||
4.19.1-non-git
|
||||
@@ -27,7 +27,7 @@ https://mesonbuild.com/Quick-guide.html#compiling-a-meson-project
|
||||
In case you’re unfamiliar:
|
||||
|
||||
$ mkdir -p build && cd build
|
||||
$ meson ..
|
||||
$ meson setup
|
||||
$ ninja
|
||||
|
||||
Please make sure that i3-migrate-config-to-v4 and i3-config-wizard are
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
 i3: A tiling window manager
|
||||
=====================================================
|
||||
|
||||
[](https://travis-ci.org/i3/i3)
|
||||
[](https://github.com/i3/i3/actions/workflows/main.yml)
|
||||
[](https://github.com/i3/i3/issues)
|
||||
[](https://github.com/i3/i3/pulls)
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
|
||||
┌──────────────────────────────┐
|
||||
│ Release notes for i3 v4.19.1 │
|
||||
└──────────────────────────────┘
|
||||
|
||||
This is i3 v4.19. This version is considered stable. All users of i3 are
|
||||
strongly encouraged to upgrade.
|
||||
|
||||
This is a bugfix release for v4.19
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Bugfixes │
|
||||
└────────────────────────────┘
|
||||
|
||||
• fix workspaces not moving to assigned output after output becomes available
|
||||
• fix duplicate bindcode after i3-config-wizard
|
||||
• fix commented-out rofi call in default i3 config
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Thanks! │
|
||||
└────────────────────────────┘
|
||||
|
||||
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
|
||||
|
||||
Anaël Beutot, Imran Virani, Orestis Floros
|
||||
|
||||
-- Michael Stapelberg, 2021-02-01
|
||||
62
RELEASE-NOTES-4.24
Normal file
@@ -0,0 +1,62 @@
|
||||
|
||||
┌──────────────────────────────┐
|
||||
│ Release notes for i3 v4.24 │
|
||||
└──────────────────────────────┘
|
||||
|
||||
This is i3 v4.24. This version is considered stable. All users of i3 are
|
||||
strongly encouraged to upgrade.
|
||||
|
||||
Aside from a number of bug fixes and detail improvements, the tiling drag
|
||||
feature has been extended to allow swapping containers with the mouse.
|
||||
See the swap_modifier in the userguide:
|
||||
https://i3wm.org/docs/userguide.html
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Changes in i3 v4.24 │
|
||||
└────────────────────────────┘
|
||||
|
||||
• docs/userguide: add note to default_border about title bar in stacked/tabbed
|
||||
• docs/userguide: fix focus output next example
|
||||
• docs/hacking-howto: update build instructions
|
||||
• docs/testsuite: update instructions
|
||||
• docs/ipc: update section on IPC socket location
|
||||
• docs/ipc: describe workspace events in more detail
|
||||
• i3-sensible-terminal: add rio
|
||||
• i3bar: use short-form text on a per-block basis
|
||||
• reap zombie children when starting
|
||||
• do not grab mouse pointer when executing bindings
|
||||
• tiling drag: swap containers with the mouse
|
||||
• disable automatic v3-to-v4 migration script
|
||||
• pass _NET_WM_STATE_MAXIMIZED_{HORZ, VERT} (fixes tab bar in Google Chrome)
|
||||
• avoid creating redundant containers when switching between layouts
|
||||
• deprecate smart_borders in favour of hide_edge_borders smart/smart_no_gaps
|
||||
• float windows of type _NET_WM_WINDOW_TYPE_NOTIFICATION by default
|
||||
• add “popup_during_fullscreen all”
|
||||
• mark fullscreen windows as maximized
|
||||
• support multiple _NET_WM_STATE changes in one ClientMessage
|
||||
• fix size_t format specifiers on 32-bit systems
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Bugfixes │
|
||||
└────────────────────────────┘
|
||||
|
||||
• i3bar: fix clicks when horizontal padding is used
|
||||
• consider workspace_auto_back_and_forth in focus workspace
|
||||
• workspace next/prev: do not skip identically numbered workspaces
|
||||
• make order of numbered workspace consistent with non-numbered
|
||||
• fix crash with focus output and command criteria matching scratchpad window
|
||||
• fix crash when reloading config with invalid criteria
|
||||
• fix error log related crash
|
||||
|
||||
┌────────────────────────────┐
|
||||
│ Thanks! │
|
||||
└────────────────────────────┘
|
||||
|
||||
Thanks for testing, bugfixes, discussions and everything I forgot go out to:
|
||||
|
||||
a-kenji, Alessandro Vinciguerra, Bimba Laszlo, colona_, Eddie Lebow, Harimbola
|
||||
Santatra, Junicchi, Malix, Nikolay Nechaev, Orestis Floros, rsgowman,
|
||||
sethpollen, Seth Pollen, systec-awe, Tasos Sahanidis, Theo Buehler, Wesley
|
||||
Schwengle, Yonas Yanfa, yuvallangerontheroad
|
||||
|
||||
-- Michael Stapelberg, 2024-11-06
|
||||
@@ -319,7 +319,7 @@
|
||||
<dc:title>steckdenis</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:creator>
|
||||
<dc:description>Logo for I3, an improved dynamic tiling window manager: http://i3.zekjur.net/</dc:description>
|
||||
<dc:description>Logo for i3 - an improved tiling window manager: http://i3.zekjur.net/</dc:description>
|
||||
<cc:license
|
||||
rdf:resource="http://creativecommons.org/licenses/by-sa/3.0/" />
|
||||
</cc:Work>
|
||||
|
||||
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
@@ -329,7 +329,7 @@
|
||||
<dc:title>steckdenis</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:creator>
|
||||
<dc:description>Logo for I3, an improved dynamic tiling window manager: http://i3.zekjur.net/</dc:description>
|
||||
<dc:description>Logo for i3 - an improved tiling window manager: http://i3.zekjur.net/</dc:description>
|
||||
<cc:license
|
||||
rdf:resource="http://creativecommons.org/licenses/by-sa/3.0/" />
|
||||
</cc:Work>
|
||||
|
||||
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
56
debian/changelog
vendored
@@ -1,3 +1,57 @@
|
||||
i3-wm (4.24-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michael Stapelberg <stapelberg@debian.org> Wed, 06 Nov 2024 18:34:06 +0100
|
||||
|
||||
i3-wm (4.23-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michael Stapelberg <stapelberg@debian.org> Sun, 29 Oct 2023 15:42:11 +0100
|
||||
|
||||
i3-wm (4.22-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michael Stapelberg <stapelberg@debian.org> Mon, 02 Jan 2023 09:46:22 +0100
|
||||
|
||||
i3-wm (4.21.2-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michael Stapelberg <stapelberg@debian.org> Mon, 24 Oct 2022 21:22:36 +0200
|
||||
|
||||
i3-wm (4.21.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michael Stapelberg <stapelberg@debian.org> Mon, 24 Oct 2022 21:22:36 +0200
|
||||
|
||||
i3-wm (4.21-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michael Stapelberg <stapelberg@debian.org> Wed, 21 Sep 2022 18:14:14 +0200
|
||||
|
||||
i3-wm (4.20.1-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michael Stapelberg <stapelberg@debian.org> Wed, 03 Nov 2021 09:22:48 +0100
|
||||
|
||||
i3-wm (4.20-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michael Stapelberg <stapelberg@debian.org> Sat, 27 Feb 2021 10:32:17 +0100
|
||||
|
||||
i3-wm (4.19.2-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Michael Stapelberg <stapelberg@debian.org> Sat, 27 Feb 2021 10:32:17 +0100
|
||||
|
||||
i3-wm (4.19-1) unstable; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
@@ -609,7 +663,7 @@ i3-wm (3.d-bf1-1) unstable; urgency=low
|
||||
* Bugfix: Resize client after updating base_width/base_height
|
||||
* Bugfix: Force render containers after setting the client active
|
||||
* Bugfix: Fix two problems in resizing floating windows with right mouse
|
||||
* Bugfix: Use more precise floating point arithmetics
|
||||
* Bugfix: Use more precise floating point arithmetic
|
||||
* Bugfix: Correctly place new windows below fullscreen windows
|
||||
|
||||
-- Michael Stapelberg <michael@stapelberg.de> Mon, 21 Dec 2009 22:33:02 +0100
|
||||
|
||||
4
debian/control
vendored
@@ -22,7 +22,7 @@ Build-Depends: debhelper (>= 10),
|
||||
pkg-config,
|
||||
libev-dev (>= 1:4.04),
|
||||
libyajl-dev (>= 2.0.4),
|
||||
libpcre3-dev (>= 1:8.10),
|
||||
libpcre2-dev,
|
||||
libstartup-notification0-dev (>= 0.10),
|
||||
libcairo2-dev (>= 1.14.4),
|
||||
libpango1.0-dev,
|
||||
@@ -44,7 +44,7 @@ Architecture: any
|
||||
Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
|
||||
Provides: x-window-manager
|
||||
Recommends: xfonts-base, fonts-dejavu-core, libanyevent-i3-perl (>= 0.12), libjson-xs-perl, rxvt-unicode | x-terminal-emulator
|
||||
Description: improved dynamic tiling window manager
|
||||
Description: improved tiling window manager
|
||||
Key features of i3 are good documentation, reasonable defaults (changeable in
|
||||
a simple configuration file) and good multi-monitor support. The user
|
||||
interface is designed for power users and emphasizes keyboard usage. i3 uses
|
||||
|
||||
4
debian/rules
vendored
@@ -17,9 +17,5 @@ override_dh_auto_configure:
|
||||
# Set -Ddocdir; the default is /usr/share/doc/i3
|
||||
dh_auto_configure -- -Ddocdir=/usr/share/doc/i3-wm -Dmans=true
|
||||
|
||||
override_dh_builddeb:
|
||||
# bintray does not support xz currently.
|
||||
dh_builddeb -- -Zgzip
|
||||
|
||||
%:
|
||||
dh $@ --buildsystem=meson
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
add_ignore_event, xcb_intern_atom, xcb_intern_atom_reply, fprintf, printf, free, load_configuration,%
|
||||
XInternAtom, exit, strlen}}
|
||||
}{}
|
||||
\title{i3 - an improved dynamic tiling window manager}
|
||||
\title{i3 - an improved tiling window manager}
|
||||
\author{sECuRE beim NoName e.V.\\
|
||||
~\\
|
||||
powered by \LaTeX, of course}
|
||||
|
||||
@@ -147,16 +147,17 @@ After pressing "b" in the crash dialog, you will get a file called
|
||||
id (PID) and the second one is incremented each time you generate a backtrace,
|
||||
starting at 0.
|
||||
|
||||
== Sending bug reports/debugging on IRC
|
||||
In Linux, if the backtrace just says +No stack.+, that's because gdb does not
|
||||
have necessary permissions to attach to a running process. You can fix that by
|
||||
running from a terminal (you can open a new tty, e.g. with ctrl-alt-F2):
|
||||
|
||||
When sending bug reports, please attach the *whole* log file. Even if you think
|
||||
you found the section which clearly highlights the problem, additional
|
||||
information might be necessary to completely diagnose the problem.
|
||||
---------------------------------------------------------------------
|
||||
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
|
||||
---------------------------------------------------------------------
|
||||
|
||||
When debugging with us in IRC, be prepared to use a so-called nopaste service
|
||||
such as https://pastebin.com because pasting large amounts of text in IRC
|
||||
sometimes leads to incomplete lines (servers have line length limitations) or
|
||||
flood kicks.
|
||||
Afterwards, try re-generating the stack trace. Note that this setting re-sets
|
||||
after reboot, see more info at
|
||||
https://www.kernel.org/doc/Documentation/security/Yama.txt.
|
||||
|
||||
== Debugging i3bar
|
||||
|
||||
|
||||
BIN
docs/gaps1920.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
@@ -14,7 +14,7 @@ you find necessary, please do not hesitate to contact me.
|
||||
<p>
|
||||
++++
|
||||
This document is not 100% up to date. Specifically, everything up to and
|
||||
including <<data_structures>> has been updated recently. The rest might contain
|
||||
including <<startup>> has been updated recently. The rest might contain
|
||||
outdated information.
|
||||
++++
|
||||
</p>
|
||||
@@ -28,36 +28,24 @@ https://mesonbuild.com/[The Meson Build system]; see
|
||||
https://mesonbuild.com/Quick-guide.html#compiling-a-meson-project[Quickstart
|
||||
Guide → Compiling a Meson project]. In case you’re unfamiliar:
|
||||
|
||||
$ mkdir -p build && cd build
|
||||
$ meson ..
|
||||
$ ninja
|
||||
mkdir -p build
|
||||
meson setup build
|
||||
meson compile -C build
|
||||
|
||||
=== Build system features
|
||||
|
||||
* We use the +AX_ENABLE_BUILDDIR+ macro to enforce builds happening in a separate
|
||||
directory. This is a prerequisite for the +AX_EXTEND_SRCDIR+ macro and building
|
||||
in a separate directory is common practice anyway. In case this causes any
|
||||
trouble when packaging i3 for your distribution, please open an issue.
|
||||
* +ninja test+ runs the i3 testsuite. See docs/testsuite for details.
|
||||
|
||||
* +make check+ runs the i3 testsuite. See docs/testsuite for details.
|
||||
* +meson dist+ builds a release tarball and runs tests on the result.
|
||||
|
||||
* +make distcheck+ (runs testsuite on +make dist+ result, tiny bit quicker
|
||||
feedback cycle than waiting for the travis build to catch the issue).
|
||||
* +meson -Ddocs=true -Dmans=true+ will enable the options to build docs and
|
||||
manpages. These options require additional dependencies that are normally not
|
||||
required for users who just want to build i3.
|
||||
|
||||
* +make uninstall+ (occasionally requested by users who compile from source)
|
||||
|
||||
* +make+ will build manpages/docs by default if the tools are installed.
|
||||
Conversely, manpages/docs are not tried to be built for users who don’t want
|
||||
to install all these dependencies to get started hacking on i3. Manpages and
|
||||
docs can be disabled with the +--disable-mans++ and ++--disable-docs++
|
||||
configure options respectively.
|
||||
|
||||
* non-release builds will enable address sanitizer by default. Use the
|
||||
+--disable-sanitizers+ configure option to turn off all sanitizers, and see
|
||||
+--help+ for available sanitizers.
|
||||
|
||||
* Coverage reports are now generated using +make check-code-coverage+, which
|
||||
requires specifying +--enable-code-coverage+ when calling configure.
|
||||
* +meson -Db_sanitize=address+ will enable the address sanitizer which is
|
||||
disabled by default. A summary of memory leaks will be printed on program
|
||||
exit. This can include false-positives. For other options of the +b_sanitize+
|
||||
flag see https://mesonbuild.com/Builtin-options.html.
|
||||
|
||||
== Pull requests
|
||||
|
||||
@@ -341,30 +329,26 @@ ensure that the operating system on which i3 is compiled has all the expected
|
||||
features, i3 comes with +include/queue.h+. On BSD systems, you can use +man
|
||||
queue(3)+. On Linux, you have to use google (or read the source).
|
||||
|
||||
The lists used are +SLIST+ (single linked lists), +CIRCLEQ+ (circular
|
||||
queues) and +TAILQ+ (tail queues). Usually, only forward traversal is necessary,
|
||||
so an +SLIST+ works fine. If inserting elements at arbitrary positions or at
|
||||
the end of a list is necessary, a +TAILQ+ is used instead. However, for the
|
||||
windows inside a container, a +CIRCLEQ+ is necessary to go from the currently
|
||||
selected window to the window above/below.
|
||||
The lists used are +SLIST+ (single linked lists), +CIRCLEQ+ (circular queues)
|
||||
and +TAILQ+ (tail queues). Usually, +TAILQ+ is used which allows inserting
|
||||
elements at arbitrary positions or at the end of the list. If only forward
|
||||
traversal is necessary, an +SLIST+ can be used. +CIRCLEQ+ is used just to
|
||||
manage the X11 state of each window.
|
||||
|
||||
== Naming conventions
|
||||
[[startup]]
|
||||
== Startup (src/main.c, main())
|
||||
|
||||
There is a row of standard variables used in many events. The following names
|
||||
should be chosen for those:
|
||||
|
||||
* +conn+ is the xcb_connection_t
|
||||
* +event+ is the event of the particular type
|
||||
* +con+ names a container
|
||||
* +current+ is a loop variable when using +TAILQ_FOREACH+ etc.
|
||||
|
||||
== Startup (src/mainx.c, main())
|
||||
Among other things, the main() function does the following:
|
||||
|
||||
* Establish the xcb connection
|
||||
* Load the i3 config
|
||||
* Check for XKB extension on the separate X connection, load Xcursor
|
||||
* Check for RandR screens (with a fall-back to Xinerama)
|
||||
* Set up EWMH hints
|
||||
* Grab the keycodes for which bindings exist
|
||||
* Check for XRandR screens
|
||||
* Manage all existing windows
|
||||
* Exec configured startup processes
|
||||
* Start i3bar if configured
|
||||
* Enter the event loop
|
||||
|
||||
== Keybindings
|
||||
@@ -400,8 +384,8 @@ the correct state.
|
||||
Then, it looks through all bindings and gets the one which matches the received
|
||||
event.
|
||||
|
||||
The bound command is parsed by the cmdparse lexer/parser, see +parse_cmd+ in
|
||||
+src/cmdparse.y+.
|
||||
The bound command is parsed by the i3 parser, see +parse_command+ in
|
||||
+src/commands_parser.c+.
|
||||
|
||||
== Manage windows (src/main.c, manage_window() and reparent_window())
|
||||
|
||||
@@ -564,9 +548,7 @@ split container.
|
||||
==== Default layout
|
||||
|
||||
In default layout, containers are placed horizontally or vertically next to
|
||||
each other (depending on the +con->orientation+). If a child is a leaf node (as
|
||||
opposed to a split container) and has border style "normal", appropriate space
|
||||
will be reserved for its window decoration.
|
||||
each other (depending on the +con->orientation+).
|
||||
|
||||
==== Stacked layout
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ $parser->html_header_after_title(
|
||||
<ul id="nav">
|
||||
<li><a style="border-bottom: 2px solid #fff" href="/docs">Docs</a></li>
|
||||
<li><a href="/screenshots">Screens</a></li>
|
||||
<li><a href="https://www.reddit.com/r/i3wm/">FAQ</a></li>
|
||||
<li><a href="https://www.github.com/i3/i3/discussions">Get Help</a></li>
|
||||
<li><a href="/contact">Contact</a></li>
|
||||
<li><a href="https://bugs.i3wm.org/">Bugs</a></li>
|
||||
</ul>
|
||||
|
||||
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 20 KiB |
BIN
docs/i3-sync.dia
BIN
docs/i3-sync.png
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 13 KiB |
@@ -191,7 +191,7 @@ separator_block_width::
|
||||
is 9 pixels), since the separator line is drawn in the middle.
|
||||
markup::
|
||||
A string that indicates how the text of the block should be parsed. Set to
|
||||
+"pango"+ to use https://developer.gnome.org/pango/stable/pango-Markup.html[Pango markup].
|
||||
+"pango"+ to use https://developer.gnome.org/pango/1.46/[Pango markup].
|
||||
Set to +"none"+ to not use any markup (default). Pango markup only works
|
||||
if you use a pango font.
|
||||
|
||||
|
||||
184
docs/i3bar-workspace-protocol
Normal file
@@ -0,0 +1,184 @@
|
||||
i3bar workspace buttons protocol
|
||||
================================
|
||||
|
||||
This document explains the protocol in which i3bar expects input for
|
||||
configuring workspace buttons. This feature is available since i3 version 4.23.
|
||||
|
||||
The program defined by the +workspace_command+ configuration option for i3bar can
|
||||
modify the workspace buttons displayed by i3bar. The command should constantly
|
||||
print in its standard output a stream of messages following the protocol
|
||||
defined in this page.
|
||||
|
||||
If you are looking for the status line protocol instead, see https://i3wm.org/docs/i3bar-protocol.html.
|
||||
|
||||
== The protocol
|
||||
|
||||
Each message should be a newline-delimited JSON array. The array is in the same
|
||||
format as the +GET_WORKSPACES+ ipc event, see
|
||||
https://i3wm.org/docs/ipc.html#_workspaces_reply.
|
||||
|
||||
As an example, this is the output of the +i3-msg -t get_workspaces+ command:
|
||||
------------------------------
|
||||
[
|
||||
{
|
||||
"id": 94131549984064,
|
||||
"num": 1,
|
||||
"name": "1",
|
||||
"visible": false,
|
||||
"focused": false,
|
||||
"output": "HDMI-A-0",
|
||||
"urgent": false
|
||||
},
|
||||
{
|
||||
"id": 94131550477584,
|
||||
"num": 2,
|
||||
"name": "2",
|
||||
"visible": true,
|
||||
"focused": true,
|
||||
"output": "HDMI-A-0",
|
||||
"urgent": false
|
||||
},
|
||||
{
|
||||
"id": 94131550452704,
|
||||
"num": 3,
|
||||
"name": "3:some workspace",
|
||||
"visible": false,
|
||||
"focused": false,
|
||||
"output": "HDMI-A-0",
|
||||
"urgent": false
|
||||
}
|
||||
]
|
||||
------------------------------
|
||||
|
||||
Please note that this example was pretty printed for human consumption, with
|
||||
the +"rect"+ field removed. Workspace button commands should output each array
|
||||
in one line.
|
||||
|
||||
Each element in the array represents a workspace. i3bar creates one workspace
|
||||
button for each element in the array. The order of these buttons is the same as
|
||||
the order of the elements in the array.
|
||||
|
||||
In general, we recommend subscribing to the +workspace+ and +output+
|
||||
https://i3wm.org/docs/ipc.html#_workspace_event[events],
|
||||
fetching the current workspace information with +GET_WORKSPACES+, modifying the
|
||||
JSON array in the response according to your needs and then printing it to the
|
||||
standard output. However, you are free to build a new message from the ground
|
||||
up.
|
||||
|
||||
=== Workspace objects in detail
|
||||
|
||||
The documentation of +GET_WORKSPACES+ should be sufficient to understand the
|
||||
meaning of each property but here we provide extra notes for each property and
|
||||
its meaning with respect to i3bar.
|
||||
|
||||
All properties but +name+ are optional.
|
||||
|
||||
id (integer)::
|
||||
If it is included it will be used to switch to that workspace when you
|
||||
click the corresponding button. If it's not provided, the +name+ will be
|
||||
used. You can use the +id+ field to present workspaces under a modified
|
||||
name.
|
||||
num (integer)::
|
||||
The only use of a workspace's number is if the +strip_workspace_numbers+
|
||||
setting is enabled.
|
||||
name (string)::
|
||||
The only required property. If an +id+ is provided you can freely change
|
||||
the +name+ as you wish, effectively renaming the buttons of i3bar.
|
||||
visible (boolean)::
|
||||
Defaults to +false+ if not included. +focused+ takes precedence over it,
|
||||
however +visible+ is important for more than one monitor.
|
||||
focused (boolean)::
|
||||
Defaults to +false+ if not included. Generally, exactly one of the
|
||||
workspaces should be +focused+. If not, no button will have the
|
||||
+focused_workspace+ color.
|
||||
urgent (boolean)::
|
||||
Defaults to +false+ if not included.
|
||||
rect (map)::
|
||||
Not used by i3bar but will be ignored.
|
||||
output (string)::
|
||||
Defaults to the primary output if not included.
|
||||
|
||||
== Examples
|
||||
|
||||
These example scripts require the https://stedolan.github.io/jq/[jq] utility to
|
||||
be installed but otherwise just use the standard +i3-msg+ utility included with
|
||||
i3. However, you can write your own scripts in your preferred language, with
|
||||
the help of one of the
|
||||
https://i3wm.org/docs/ipc.html#_see_also_existing_libraries[pre-existing i3
|
||||
libraries]
|
||||
|
||||
=== Base configuration
|
||||
|
||||
------------------------------
|
||||
bar {
|
||||
…
|
||||
workspace_command /path/to/your/script.sh
|
||||
…
|
||||
}
|
||||
------------------------------
|
||||
|
||||
=== Re-create the default behaviour of i3bar
|
||||
|
||||
Not very useful by itself but this will be the basic building block of all the
|
||||
following scripts. This one does not require +jq+.
|
||||
|
||||
------------------------------
|
||||
#!/bin/sh
|
||||
i3-msg -t subscribe -m '["workspace", "output"]' | {
|
||||
# Initially print the current workspaces before we receive any events. This
|
||||
# avoids having an empty bar when starting up.
|
||||
i3-msg -t get_workspaces;
|
||||
# Then, while we receive events, update the workspace information.
|
||||
while read EVENT; do i3-msg -t get_workspaces; done;
|
||||
}
|
||||
------------------------------
|
||||
|
||||
=== Hide workspace named +foo+ unless if it is focused.
|
||||
|
||||
------------------------------
|
||||
#!/bin/sh
|
||||
i3-msg -t subscribe -m '["workspace", "output"]' | {
|
||||
i3-msg -t get_workspaces;
|
||||
while read EVENT; do i3-msg -t get_workspaces; done;
|
||||
} | jq --unbuffered -c '[ .[] | select(.name != "foo" or .focused) ]'
|
||||
------------------------------
|
||||
|
||||
Important! Make sure you use the +--unbuffered+ flag with +jq+, otherwise you
|
||||
might not get the changes in real-time but whenever they are flushed, which
|
||||
might mean that you are getting an empty bar until enough events are written.
|
||||
|
||||
=== Show empty workspaces +foo+ and +bar+ on LVDS1 even if they do not exist at the moment.
|
||||
|
||||
------------------------------
|
||||
#!/bin/sh
|
||||
i3-msg -t subscribe -m '["workspace", "output"]' | {
|
||||
i3-msg -t get_workspaces;
|
||||
while read EVENT; do i3-msg -t get_workspaces; done;
|
||||
} | jq --unbuffered -c '
|
||||
def fake_ws(name): {
|
||||
name: name,
|
||||
output: "LVDS1",
|
||||
};
|
||||
. + [ fake_ws("foo"), fake_ws("bar") ] | unique_by(.name)
|
||||
'
|
||||
------------------------------
|
||||
|
||||
=== Sort workspaces in reverse alphanumeric order
|
||||
|
||||
------------------------------
|
||||
#!/bin/sh
|
||||
i3-msg -t subscribe -m '["workspace", "output"]' | {
|
||||
i3-msg -t get_workspaces;
|
||||
while read EVENT; do i3-msg -t get_workspaces; done;
|
||||
} | jq --unbuffered -c 'sort_by(.name) | reverse'
|
||||
------------------------------
|
||||
|
||||
=== Append "foo" to the name of each workspace
|
||||
|
||||
------------------------------
|
||||
#!/bin/sh
|
||||
i3-msg -t subscribe -m '["workspace", "output"]' | {
|
||||
i3-msg -t get_workspaces;
|
||||
while read EVENT; do i3-msg -t get_workspaces; done;
|
||||
} | jq --unbuffered -c '[ .[] | .name |= . + " foo" ]'
|
||||
------------------------------
|
||||
288
docs/ipc
@@ -10,14 +10,22 @@ workspace bar.
|
||||
|
||||
The method of choice for IPC in our case is a unix socket because it has very
|
||||
little overhead on both sides and is usually available without headaches in
|
||||
most languages. In the default configuration file, the ipc-socket gets created
|
||||
in +/tmp/i3-%u.XXXXXX/ipc-socket.%p+ where +%u+ is your UNIX username, +%p+ is
|
||||
the PID of i3 and XXXXXX is a string of random characters from the portable
|
||||
filename character set (see mkdtemp(3)). You can get the socketpath from i3 by
|
||||
calling +i3 --get-socketpath+.
|
||||
most languages.
|
||||
By default i3 will set the path of the IPC socket based on:
|
||||
|
||||
All i3 utilities, like +i3-msg+ and +i3-input+ will read the +I3_SOCKET_PATH+
|
||||
X11 property, stored on the X11 root window.
|
||||
1. The +ipc-socket+ configuration directive if it is used
|
||||
2. The +I3SOCK+ environmental variable if it is set
|
||||
3. +$XDG_RUNTIME_DIR/i3/ipc-socket.%p+ if the directory is available where +%p+
|
||||
is the PID of i3 and XXXXXX is a string of random characters
|
||||
4. +/tmp/i3-%u.XXXXXX/ipc-socket.%p+ where +%u+ is your UNIX username
|
||||
|
||||
You can get the socketpath from i3 by executing +i3 --get-socketpath+, which
|
||||
will print the path to the standard output (plus a newline) or by reading the
|
||||
+I3SOCK+ environmental variable.
|
||||
|
||||
All i3 utilities, like +i3-msg+ and +i3-input+ will determine the path of the
|
||||
IPC socket frome the +I3SOCK+ environmental variable if it is set or the
|
||||
+I3_SOCKET_PATH+ X11 property, stored on the X11 root window.
|
||||
|
||||
[WARNING]
|
||||
.Use an existing library!
|
||||
@@ -39,12 +47,12 @@ my $sock = IO::Socket::UNIX->new(Peer => $path);
|
||||
|
||||
== Sending messages to i3
|
||||
|
||||
To send a message to i3, you have to format in the binary message format which
|
||||
i3 expects. This format specifies a magic string in the beginning to ensure
|
||||
the integrity of messages (to prevent follow-up errors). Following the magic
|
||||
string comes the length of the payload of the message as 32-bit integer, and
|
||||
the type of the message as 32-bit integer (the integers are not converted, so
|
||||
they are in native byte order).
|
||||
To send a message to i3, you have to format it in the binary message format
|
||||
which i3 expects. This format specifies a magic string in the beginning to
|
||||
ensure the integrity of messages (to prevent follow-up errors). Following the
|
||||
magic string comes the length of the payload of the message as a 32-bit
|
||||
integer, and the type of the message as a 32-bit integer (the integers are not
|
||||
converted, so they are in native byte order).
|
||||
|
||||
The magic string currently is "i3-ipc" and will only be changed when a change
|
||||
in the IPC API is done which breaks compatibility (we hope that we don’t need
|
||||
@@ -96,18 +104,30 @@ $sock->write(format_ipc_command("exit"));
|
||||
|
||||
== Receiving replies from i3
|
||||
|
||||
Replies from i3 usually consist of a simple string (the length of the string
|
||||
is the message_length, so you can consider them length-prefixed) which in turn
|
||||
contain the JSON serialization of a data structure. For example, the
|
||||
GET_WORKSPACES message returns an array of workspaces (each workspace is a map
|
||||
with certain attributes).
|
||||
Each message sent to i3 will cause exactly one reply to be sent in return. The
|
||||
order of the sent replies will always correspond to the order of the sent
|
||||
requests. The only exception to this is <<events>>, which (once subscribed to)
|
||||
may be sent at any time (though never in the middle of another event or reply).
|
||||
|
||||
=== Reply format
|
||||
It is generally safe to send several messages to i3 without first waiting for a
|
||||
reply for each one (pipelining) -- though, note that depending on the language /
|
||||
network library you use, writing to the socket without also reading from it may
|
||||
cause a deadlock due to the socket buffers getting full.
|
||||
|
||||
The reply format is identical to the normal message format. There also is
|
||||
the magic string, then the message length, then the message type and the
|
||||
payload.
|
||||
|
||||
The payload of replies from i3 usually consists of a simple string (the length
|
||||
of the string is the message_length, so you can consider them length-prefixed),
|
||||
which in turn contain the JSON serialization of a data structure. For example,
|
||||
the GET_WORKSPACES message returns an array of workspaces (each workspace is a
|
||||
map with certain attributes).
|
||||
|
||||
Replies currently have a 1:1 correspondence to messages, with the message type
|
||||
of the reply corresponding to the message type of the message which caused the
|
||||
reply to be sent.
|
||||
|
||||
The following reply types are implemented:
|
||||
|
||||
COMMAND (0)::
|
||||
@@ -127,16 +147,30 @@ BAR_CONFIG (6)::
|
||||
VERSION (7)::
|
||||
Reply to the GET_VERSION message.
|
||||
BINDING_MODES (8)::
|
||||
Reply to the GET_BINDING_MODES message.
|
||||
Reply to the GET_BINDING_MODES message.
|
||||
GET_CONFIG (9)::
|
||||
Reply to the GET_CONFIG message.
|
||||
TICK (10)::
|
||||
Reply to the SEND_TICK message.
|
||||
SYNC (11)::
|
||||
Reply to the SYNC message.
|
||||
GET_BINDING_STATE (12)::
|
||||
Reply to the GET_BINDING_STATE message.
|
||||
|
||||
== Messages and replies
|
||||
|
||||
[[_command_reply]]
|
||||
=== COMMAND reply
|
||||
=== RUN_COMMAND / COMMAND
|
||||
|
||||
Run the payload as an https://i3wm.org/docs/userguide.html#list_of_commands[i3
|
||||
command] (like the commands you can bind to keys).
|
||||
|
||||
*Message:*
|
||||
|
||||
The message payload is the string containing the command to execute. There is
|
||||
no JSON encoding or trailing newline.
|
||||
|
||||
*Reply:*
|
||||
|
||||
The reply consists of a list of serialized maps for each command that was
|
||||
parsed. Each has the property +success (bool)+ and may also include a
|
||||
@@ -170,7 +204,15 @@ When the specified command cannot be parsed, `success` will be false and
|
||||
-------------------
|
||||
|
||||
[[_workspaces_reply]]
|
||||
=== WORKSPACES reply
|
||||
=== GET_WORKSPACES / WORKSPACES
|
||||
|
||||
Get the list of current workspaces.
|
||||
|
||||
*Message:*
|
||||
|
||||
No payload.
|
||||
|
||||
*Reply:*
|
||||
|
||||
The reply consists of a serialized list of workspaces. Each workspace has the
|
||||
following properties:
|
||||
@@ -234,7 +276,16 @@ output (string)::
|
||||
-------------------
|
||||
|
||||
[[_subscribe_reply]]
|
||||
=== SUBSCRIBE reply
|
||||
=== SUBSCRIBE
|
||||
|
||||
Subscribe this IPC connection to the event types specified in the message
|
||||
payload. See <<events>>.
|
||||
|
||||
*Message:*
|
||||
|
||||
A JSON-encoded array of event types to subscribe to.
|
||||
|
||||
*Reply:*
|
||||
|
||||
The reply consists of a single serialized map. The only property is
|
||||
+success (bool)+, indicating whether the subscription was successful (the
|
||||
@@ -246,7 +297,15 @@ default) or whether a JSON parse error occurred.
|
||||
-------------------
|
||||
|
||||
[[_outputs_reply]]
|
||||
=== OUTPUTS reply
|
||||
=== GET_OUTPUTS / OUTPUTS
|
||||
|
||||
Get the list of current outputs.
|
||||
|
||||
*Message:*
|
||||
|
||||
No payload.
|
||||
|
||||
*Reply:*
|
||||
|
||||
The reply consists of a serialized list of outputs. Each output has the
|
||||
following properties:
|
||||
@@ -257,7 +316,7 @@ active (boolean)::
|
||||
Whether this output is currently active (has a valid mode).
|
||||
primary (boolean)::
|
||||
Whether this output is currently the primary output.
|
||||
current_workspace (string)::
|
||||
current_workspace (string or null)::
|
||||
The name of the current workspace that is visible on this output. +null+ if
|
||||
the output is not active.
|
||||
rect (map)::
|
||||
@@ -293,7 +352,15 @@ rect (map)::
|
||||
-------------------
|
||||
|
||||
[[_tree_reply]]
|
||||
=== TREE reply
|
||||
=== GET_TREE / TREE
|
||||
|
||||
Get the i3 layout tree.
|
||||
|
||||
*Message:*
|
||||
|
||||
No payload.
|
||||
|
||||
*Reply:*
|
||||
|
||||
The reply consists of a serialized tree. Each node in the tree (representing
|
||||
one container) has at least the properties listed below. While the nodes might
|
||||
@@ -329,7 +396,7 @@ orientation (string)::
|
||||
"vertical".
|
||||
THIS FIELD IS OBSOLETE. It is still present, but your code should not
|
||||
use it. Instead, rely on the layout field.
|
||||
percent (float)::
|
||||
percent (float or null)::
|
||||
The percentage which this container takes in its parent. A value of
|
||||
+null+ means that the percent property does not make sense for this
|
||||
container, for example for the root container.
|
||||
@@ -350,21 +417,30 @@ deco_rect (map)::
|
||||
The coordinates of the *window decoration* inside its container. These
|
||||
coordinates are relative to the container and do not include the actual
|
||||
client window.
|
||||
actual_deco_rect (map)::
|
||||
See +deco_rect+. i3 v4.22 changed the way title bars are rendered. Before
|
||||
i3 v4.22, the deco_rect was always relative to the parent coordinates.
|
||||
Starting with i3 v4.22, this remains true for tabbed/stacked containers
|
||||
(actual_deco_rect is identical to deco_rect), but for normal-border leaf
|
||||
containers within vertical/horizontal split containers, actual_deco_rect
|
||||
is relative to the container itself. For more background, see
|
||||
https://github.com/i3/i3/issues/1966
|
||||
geometry (map)::
|
||||
The original geometry the window specified when i3 mapped it. Used when
|
||||
switching a window to floating mode, for example.
|
||||
window (integer)::
|
||||
window (integer or null)::
|
||||
The X11 window ID of the *actual client window* inside this container.
|
||||
This field is set to null for split containers or otherwise empty
|
||||
This field is set to +null+ for split containers or otherwise empty
|
||||
containers. This ID corresponds to what xwininfo(1) and other
|
||||
X11-related tools display (usually in hex).
|
||||
window_properties (map)::
|
||||
This optional field contains all available X11 window properties from the
|
||||
following list: *title*, *instance*, *class*, *window_role* and *transient_for*.
|
||||
following list: *title*, *instance*, *class*, *window_role*, *machine*
|
||||
and *transient_for*.
|
||||
window_type (string)::
|
||||
The window type (_NET_WM_WINDOW_TYPE). Possible values are undefined, normal,
|
||||
dialog, utility, toolbar, splash, menu, dropdown_menu, popup_menu, tooltip and
|
||||
notification.
|
||||
The window type (_NET_WM_WINDOW_TYPE). Possible values are `undefined`,
|
||||
unknown, normal, dialog, utility, toolbar, splash, menu, dropdown_menu,
|
||||
popup_menu, tooltip and notification.
|
||||
urgent (bool)::
|
||||
Whether this container (window, split container, floating container or
|
||||
workspace) has the urgency hint set, directly or indirectly. All parent
|
||||
@@ -379,6 +455,9 @@ focus (array of integer)::
|
||||
order. Traversing the tree by following the first entry in this array
|
||||
will result in eventually reaching the one node with +focused+ set to
|
||||
true.
|
||||
sticky (bool)::
|
||||
Whether this window is "sticky". If it is also floating, this window will
|
||||
be present on all workspaces on the same output.
|
||||
fullscreen_mode (integer)::
|
||||
Whether this container is in fullscreen state or not.
|
||||
Possible values are
|
||||
@@ -386,12 +465,18 @@ fullscreen_mode (integer)::
|
||||
+1+ (fullscreened on output) or
|
||||
+2+ (fullscreened globally).
|
||||
Note that all workspaces are considered fullscreened on their respective output.
|
||||
|
||||
floating (string)::
|
||||
Floating state of container.
|
||||
Can be either "auto_on", "auto_off", "user_on" or "user_off"
|
||||
nodes (array of node)::
|
||||
The tiling (i.e. non-floating) child containers of this node.
|
||||
floating_nodes (array of node)::
|
||||
The floating child containers of this node. Only non-empty on nodes with
|
||||
type +workspace+.
|
||||
scratchpad_state (string)::
|
||||
Whether the window is not in the scratchpad ("none"), freshly moved to
|
||||
the scratchpad but not yet resized ("fresh") or moved to the scratchpad
|
||||
and resized ("changed").
|
||||
|
||||
Please note that in the following example, I have left out some keys/values
|
||||
which are not relevant for the type of the node. Otherwise, the example would
|
||||
@@ -532,7 +617,15 @@ JSON dump:
|
||||
-----------------------
|
||||
|
||||
[[_marks_reply]]
|
||||
=== MARKS reply
|
||||
=== GET_MARKS / MARKS
|
||||
|
||||
Gets the names of all currently set marks.
|
||||
|
||||
*Message:*
|
||||
|
||||
No payload.
|
||||
|
||||
*Reply:*
|
||||
|
||||
The reply consists of a single array of strings for each container that has a
|
||||
mark. A mark can only be set on one container, so the array is unique.
|
||||
@@ -541,7 +634,15 @@ The order of that array is undefined.
|
||||
If no window has a mark the response will be the empty array [].
|
||||
|
||||
[[_bar_config_reply]]
|
||||
=== BAR_CONFIG reply
|
||||
=== GET_BAR_CONFIG / BAR_CONFIG
|
||||
|
||||
Gets the specified bar configuration or the names of all bar configurations if payload is empty.
|
||||
|
||||
*Message:*
|
||||
|
||||
No payload, or the ID of the bar whose configuration to retrieve.
|
||||
|
||||
*Reply:*
|
||||
|
||||
This can be used by third-party workspace bars (especially i3bar, but others
|
||||
are free to implement compatible alternatives) to get the +bar+ block
|
||||
@@ -641,7 +742,15 @@ binding_mode_text/binding_mode_bg/binding_mode_border::
|
||||
--------------
|
||||
|
||||
[[_version_reply]]
|
||||
=== VERSION reply
|
||||
=== GET_VERSION / VERSION
|
||||
|
||||
Gets the i3 version.
|
||||
|
||||
*Message:*
|
||||
|
||||
No payload.
|
||||
|
||||
*Reply:*
|
||||
|
||||
The reply consists of a single JSON dictionary with the following keys:
|
||||
|
||||
@@ -674,7 +783,15 @@ loaded_config_file_name (string)::
|
||||
-------------------
|
||||
|
||||
[[_binding_modes_reply]]
|
||||
=== BINDING_MODES reply
|
||||
=== GET_BINDING_MODES / BINDING_MODES
|
||||
|
||||
Gets the names of all currently configured binding modes.
|
||||
|
||||
*Message:*
|
||||
|
||||
No payload.
|
||||
|
||||
*Reply:*
|
||||
|
||||
The reply consists of an array of all currently configured binding modes.
|
||||
|
||||
@@ -684,18 +801,66 @@ The reply consists of an array of all currently configured binding modes.
|
||||
---------------------
|
||||
|
||||
[[_config_reply]]
|
||||
=== CONFIG reply
|
||||
=== GET_CONFIG / CONFIG
|
||||
|
||||
The config reply is a map which currently only contains the "config" member,
|
||||
which is a string containing the config file as loaded by i3 most recently.
|
||||
Returns the last loaded i3 config.
|
||||
|
||||
*Message:*
|
||||
|
||||
No payload.
|
||||
|
||||
*Reply:*
|
||||
|
||||
The config reply is a map which contains the following fields:
|
||||
|
||||
config (string)::
|
||||
The top-level config file contents that i3 has loaded most recently.
|
||||
This field is kept for backwards compatibility. See +included_configs+
|
||||
instead.
|
||||
included_configs (array of maps)::
|
||||
i3 adds one entry to this array for each config file it loads, in
|
||||
order. The first entry’s +raw_contents+ are identical to the +config+
|
||||
field.
|
||||
|
||||
Each +included_configs+ entry contains the following fields
|
||||
|
||||
path (string)::
|
||||
Absolute path name to the config file that i3 loaded.
|
||||
raw_contents (string)::
|
||||
The raw contents of the file as i3 read them.
|
||||
variable_replaced_contents (string)::
|
||||
The contents of the file after i3 replaced all variables. This is useful
|
||||
for debugging variable replacement.
|
||||
|
||||
*Example:*
|
||||
-------------------
|
||||
{ "config": "font pango:monospace 8\nbindsym Mod4+q exit\n" }
|
||||
{
|
||||
"config": "include font.cfg\n",
|
||||
"included_configs": [
|
||||
{
|
||||
"path": "/home/michael/configfiles/i3/config",
|
||||
"raw_contents": "include font.cfg\n",
|
||||
"variable_replaced_contents": "include font.cfg\n"
|
||||
},
|
||||
{
|
||||
"path": "/home/michael/configfiles/i3/font.cfg",
|
||||
"raw_contents": "set $font pango:monospace 8\nfont $font",
|
||||
"variable_replaced_contents": "set pango:monospace 8 pango:monospace 8\nfont pango:monospace 8\n"
|
||||
}
|
||||
],
|
||||
}
|
||||
-------------------
|
||||
|
||||
[[_tick_reply]]
|
||||
=== TICK reply
|
||||
=== SEND_TICK / TICK
|
||||
|
||||
Sends a tick event with the specified payload.
|
||||
|
||||
*Message:*
|
||||
|
||||
The payload of the tick event to send to IPC event listeners.
|
||||
|
||||
*Reply:*
|
||||
|
||||
The reply is a map containing the "success" member. After the reply was
|
||||
received, the tick event has been written to all IPC connections which subscribe
|
||||
@@ -709,7 +874,15 @@ events generated prior to the +SEND_TICK+ message (happened-before relation).
|
||||
-------------------
|
||||
|
||||
[[_sync_reply]]
|
||||
=== SYNC reply
|
||||
=== SYNC
|
||||
|
||||
Sends an i3 sync event with the specified random value to the specified window.
|
||||
|
||||
*Message:*
|
||||
|
||||
A JSON-encoded map with the properties "rnd" and "window" (both integer).
|
||||
|
||||
*Reply:*
|
||||
|
||||
The reply is a map containing the "success" member. After the reply was
|
||||
received, the https://i3wm.org/docs/testsuite.html#i3_sync[i3 sync message] was
|
||||
@@ -721,7 +894,15 @@ responded to.
|
||||
-------------------
|
||||
|
||||
[[_binding_state_reply]]
|
||||
=== GET_BINDING_STATE reply
|
||||
=== GET_BINDING_STATE
|
||||
|
||||
Request the current binding state, i.e. the currently active binding mode name.
|
||||
|
||||
*Message:*
|
||||
|
||||
No payload.
|
||||
|
||||
*Reply:*
|
||||
|
||||
The binding_state reply is a map which currently only contains the "name"
|
||||
member, which is the name of the currently active binding mode as a string.
|
||||
@@ -823,9 +1004,18 @@ if ($is_event) {
|
||||
=== workspace event
|
||||
|
||||
This event consists of a single serialized map containing a property
|
||||
+change (string)+ which indicates the type of the change ("focus", "init",
|
||||
"empty", "urgent", "reload", "rename", "restored", "move"). A
|
||||
+current (object)+ property will be present with the affected workspace
|
||||
+change (string)+ which indicates the type of the change.
|
||||
|
||||
* +empty+ – the workspace has become empty
|
||||
* +focus+ – the workspace has received input focus
|
||||
* +init+ – the workspace has been created
|
||||
* +move+ – the workspace has been moved to a different output
|
||||
* +reload+ – i3 config has been reloaded
|
||||
* +rename+ – the workspace's name has changed
|
||||
* +restored+ – the workspace's layout has changed to a previously saved layout
|
||||
* +urgent+ – the workspace has become urgent or lost its urgent status
|
||||
|
||||
A +current (object)+ property will be present with the affected workspace
|
||||
whenever the type of event affects a workspace (otherwise, it will be +null+).
|
||||
|
||||
When the change is "focus", an +old (object)+ property will be present with the
|
||||
@@ -925,6 +1115,8 @@ binding that ran a command because of user input. The +change (string)+ field
|
||||
indicates what sort of binding event was triggered (right now it will always be
|
||||
+"run"+ but may be expanded in the future).
|
||||
|
||||
The +mode (string)+ field contains the name of the mode the binding was run in.
|
||||
|
||||
The +binding (object)+ field contains details about the binding that was run:
|
||||
|
||||
command (string)::
|
||||
|
||||
@@ -185,9 +185,9 @@ Therefore, if you just start Emacs via dmenu, it will not get swallowed by that
|
||||
container. Only if you start Emacs with the proper instance name (+emacs24
|
||||
--name notmuch+), it will get swallowed.
|
||||
|
||||
You can match on "class", "instance", "window_role" and "title". All values are
|
||||
case-sensitive regular expressions (PCRE). Use +xprop(1)+ and click into a
|
||||
window to see its properties:
|
||||
You can match on "class", "instance", "window_role", "title" and "machine". All
|
||||
values are case-sensitive regular expressions (PCRE). Use +xprop(1)+ and click
|
||||
into a window to see its properties:
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
$ xprop
|
||||
|
||||
193
docs/testsuite
@@ -4,11 +4,10 @@ Michael Stapelberg <michael@i3wm.org>
|
||||
September 2012
|
||||
|
||||
This document explains how the i3 testsuite works, how to use it and extend it.
|
||||
It is targeted at developers who not necessarily have been doing testing before
|
||||
or have not been testing in Perl before. In general, the testsuite is not of
|
||||
It is targeted at developers who haven't necessarily done testing before,
|
||||
or have not used Perl for testing before. In general, the testsuite is not of
|
||||
interest for end users.
|
||||
|
||||
|
||||
== Introduction
|
||||
|
||||
The i3 testsuite is a collection of files which contain testcases for various
|
||||
@@ -37,8 +36,8 @@ that, but it will also be useful for every future change.
|
||||
|
||||
Apart from this document, you should also have a look at:
|
||||
|
||||
1. The "Modern Perl" book, which can be found at
|
||||
http://onyxneon.com/books/modern_perl/modern_perl_a4.pdf
|
||||
1. The "Modern Perl" book:
|
||||
https://i3wm.org/downloads/modern_perl_a4.pdf
|
||||
2. The latest Perl documentation of the "i3test" (general testcase setup) and
|
||||
"i3test::Test" (additional test instructions) modules:
|
||||
https://build.i3wm.org/docs/lib-i3test.html respectively
|
||||
@@ -77,30 +76,20 @@ used to install the testsuite. Many users prefer to use the more modern
|
||||
The tests additionally require +Xephyr(1)+ to run a nested X server. Install
|
||||
+xserver-xephyr+ on Debian or +xorg-server-xephyr+ on Arch Linux.
|
||||
|
||||
.Installing testsuite dependencies using cpanminus (preferred)
|
||||
.Installing testsuite dependencies using cpanminus
|
||||
--------------------------------------------------------------------------------
|
||||
$ cd ~/i3/testcases
|
||||
$ sudo apt-get install cpanminus
|
||||
$ sudo cpanm .
|
||||
# Install testsuite system-level dependencies. Xvfb is optional but recommended.
|
||||
$ sudo apt-get install xcb-proto cpanminus xvfb xserver-xephyr
|
||||
# Install dependencies in ~/perl5 local library
|
||||
$ cpanm --local-lib=~/perl5 local::lib App::cpanminus Module::Install
|
||||
# Activate the local library
|
||||
$ eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib)
|
||||
$ cd ~/i3/testcases/
|
||||
$ cpanm .
|
||||
$ cd ~/i3/AnyEvent-I3
|
||||
$ sudo cpanm Module::Install
|
||||
$ sudo cpanm .
|
||||
$ cpanm .
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
If you don’t want to use cpanminus for some reason, the same works with cpan:
|
||||
|
||||
.Installing testsuite dependencies using cpan
|
||||
--------------------------------------------------------------------------------
|
||||
$ cd ~/i3/testcases
|
||||
$ sudo cpan .
|
||||
$ cd ~/i3/AnyEvent-I3
|
||||
$ sudo cpan Module::Install
|
||||
$ sudo cpan .
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
In case you don’t have root permissions, you can also install into your home
|
||||
directory, see https://michael.stapelberg.de/cpan/
|
||||
|
||||
=== Mechanisms
|
||||
|
||||
==== Script: complete-run
|
||||
@@ -120,48 +109,57 @@ tests are run under Xvfb.
|
||||
---------------------------------------
|
||||
$ cd ~/i3
|
||||
|
||||
$ mkdir -p build && cd build
|
||||
$ mkdir -p build
|
||||
|
||||
$ meson ..
|
||||
$ meson setup build
|
||||
$ cd build
|
||||
|
||||
$ ninja
|
||||
$ meson compile
|
||||
# output omitted because it is very long
|
||||
|
||||
$ cd testcases
|
||||
|
||||
$ ./complete-run.pl
|
||||
# output omitted because it is very long
|
||||
All tests successful.
|
||||
Files=78, Tests=734, 27 wallclock secs ( 0.38 usr 0.48 sys + 17.65 cusr 3.21 csys = 21.72 CPU)
|
||||
Result: PASS
|
||||
|
||||
$ ./complete-run.pl t/04-floating.t
|
||||
[:3] i3 startup: took 0.07s, status = 1
|
||||
[:3] Running t/04-floating.t with logfile testsuite-2011-09-24-16-06-04-4.0.2-226-g1eb011a/i3-log-for-04-floating.t
|
||||
[:3] t/04-floating.t finished
|
||||
[:3] killing i3
|
||||
output for t/04-floating.t:
|
||||
ok 1 - use X11::XCB::Window;
|
||||
ok 2 - The object isa X11::XCB::Window
|
||||
ok 3 - Window is mapped
|
||||
ok 4 - i3 raised the width to 75
|
||||
ok 5 - i3 raised the height to 50
|
||||
ok 6 - i3 did not map it to (0x0)
|
||||
ok 7 - The object isa X11::XCB::Window
|
||||
ok 8 - i3 let the width at 80
|
||||
ok 9 - i3 let the height at 90
|
||||
ok 10 - i3 mapped it to x=1
|
||||
ok 11 - i3 mapped it to y=18
|
||||
ok 12 - The object isa X11::XCB::Window
|
||||
ok 13 - i3 let the width at 80
|
||||
ok 14 - i3 let the height at 90
|
||||
1..14
|
||||
$ ./complete-run.pl t/005-floating.t
|
||||
Running tests under Xvfb display :99
|
||||
Starting 1 Xephyr instances, starting at :100...
|
||||
|
||||
Rough time estimate for this run: 9.65 seconds
|
||||
|
||||
Writing logfile to 'testsuite-2024-05-01-21-33-45-4.23-28-g5834b7e8/complete-run.log'...
|
||||
[:100] i3/testcases/t/005-floating.t: finished
|
||||
completed 0 of 1 tests
|
||||
|
||||
All tests successful.
|
||||
Files=1, Tests=14, 0 wallclock secs ( 0.01 usr 0.00 sys + 0.19 cusr 0.03 csys = 0.23 CPU)
|
||||
Files=1, Tests=13, 0 wallclock secs ( 0.00 usr + 0.00 sys = 0.00 CPU)
|
||||
Result: PASS
|
||||
|
||||
$ less latest/i3-log-for-04-floating.t
|
||||
The slowest tests are:
|
||||
i3/testcases/t/005-floating.t with 0.07 seconds
|
||||
|
||||
Test output:
|
||||
[:100] i3/testcases/t/005-floating.t: starting
|
||||
[:100] i3/testcases/t/005-floating.t: finished
|
||||
output for i3/testcases/t/005-floating.t:
|
||||
ok 1 - An object of class 'X11::XCB::Window' isa 'X11::XCB::Window'
|
||||
ok 2 - Window is mapped
|
||||
ok 3 - i3 raised the width to 75
|
||||
ok 4 - i3 raised the height to 50
|
||||
ok 5 - i3 did not map it to (0x0)
|
||||
ok 6 - An object of class 'X11::XCB::Window' isa 'X11::XCB::Window'
|
||||
ok 7 - i3 let the width at 80
|
||||
ok 8 - i3 let the height at 90
|
||||
ok 9 - i3 mapped it to x=20
|
||||
ok 10 - i3 mapped it to y=20
|
||||
ok 11 - An object of class 'X11::XCB::Window' isa 'X11::XCB::Window'
|
||||
ok 12 - i3 let the width at 80
|
||||
ok 13 - i3 let the height at 90
|
||||
1..13
|
||||
|
||||
$ less latest/i3-log-for-005-floating.t
|
||||
----------------------------------------
|
||||
|
||||
If your attempt to run the tests with a bare call to ./complete-run.pl fails, try this:
|
||||
@@ -173,37 +171,34 @@ $ ./complete-run.pl --parallel=1 --keep-xserver-output
|
||||
This will show the output of Xephyr, which is the X server implementation we
|
||||
use for testing.
|
||||
|
||||
===== make command: +make check+
|
||||
Make check runs the i3 testsuite.
|
||||
You can still use ./testcases/complete-run.pl to get the interactive progress output.
|
||||
===== ninja command: +ninja test+
|
||||
+ninja test+ runs the i3 testsuite.
|
||||
You can still use ./complete-run.pl to get the interactive progress output.
|
||||
|
||||
.Example invocation of +make check+
|
||||
.Example invocation of +ninja test+
|
||||
---------------------------------------
|
||||
$ cd ~/i3
|
||||
|
||||
$ mkdir -p build && cd build
|
||||
$ mkdir -p build
|
||||
|
||||
$ meson ..
|
||||
$ meson setup build
|
||||
$ cd build
|
||||
|
||||
$ ninja
|
||||
# output omitted because it is very long
|
||||
$ ninja test
|
||||
[1/102] Generating config.h with a custom command
|
||||
[1/2] Running all tests.
|
||||
1/1 complete-run OK 34.39s
|
||||
|
||||
$ make check
|
||||
# output omitted because it is very long
|
||||
PASS: testcases/complete-run.pl
|
||||
============================================================================
|
||||
Testsuite summary for i3 4.13
|
||||
============================================================================
|
||||
# TOTAL: 1
|
||||
# PASS: 1
|
||||
# SKIP: 0
|
||||
# XFAIL: 0
|
||||
# FAIL: 0
|
||||
# XPASS: 0
|
||||
# ERROR: 0
|
||||
============================================================================
|
||||
Ok: 1
|
||||
Expected Fail: 0
|
||||
Fail: 0
|
||||
Unexpected Pass: 0
|
||||
Skipped: 0
|
||||
Timeout: 0
|
||||
|
||||
$ less test-suite.log
|
||||
Full log written to i3/build/meson-logs/testlog.txt
|
||||
|
||||
$ less latest/complete-run.log
|
||||
----------------------------------------
|
||||
|
||||
==== Coverage testing
|
||||
@@ -481,7 +476,7 @@ an i3 crash resulting in the testcase being unable to communicate with i3 via
|
||||
IPC anymore.
|
||||
|
||||
[[i3_sync]]
|
||||
== Appendix A: The i3 sync protocol
|
||||
== Appendix A: The I3_SYNC protocol
|
||||
|
||||
Consider the following situation: You open two windows in your testcase, then
|
||||
you use +focus left+ and want to verify that the X11 focus has been updated
|
||||
@@ -499,9 +494,9 @@ is($x->input_focus, $left->id, 'left window focused');
|
||||
However, the test fails. Sometimes. Apparently, there is a race condition in
|
||||
your test. If you think about it, this is because you are using two different
|
||||
pieces of software: You tell i3 to update focus, i3 confirms that, and then you
|
||||
ask X11 to give you the current focus. There is a certain time i3 needs to
|
||||
update the X11 state. If the testcase gets CPU time before X11 processed i3's
|
||||
requests, the test will fail.
|
||||
ask X11 to give you the current focus. There is a certain time that the X11
|
||||
server needs to process the requests from i3. If the testcase's request for the
|
||||
input focus is processed before i3's requests, the test will fail.
|
||||
|
||||
image::i3-sync.png["Diagram of the race condition", title="Diagram of the race condition"]
|
||||
|
||||
@@ -531,10 +526,10 @@ less robust.
|
||||
|
||||
The real solution for this problem is a mechanism which I call "the i3 sync
|
||||
protocol". The idea is to send a request (which does not modify state) via X11
|
||||
to i3 which will then be answered. Due to the request's position in the event
|
||||
queue (*after* all previous events), you can be sure that by the time you
|
||||
receive the reply, all other events have been dealt with by i3 (and, more
|
||||
importantly, X11).
|
||||
to i3 which will then be answered, again via X11. Because this answer is
|
||||
generated via an X11 request, it will be sent to the X11 server *after* all
|
||||
previous requests. Thus, you can be sure that by the time you receive the reply,
|
||||
all other events have been dealt with by i3 (and, more importantly, X11).
|
||||
|
||||
image::i3-sync-working.png["Diagram of the i3 sync solution", title="Diagram of the i3 sync solution"]
|
||||
|
||||
@@ -569,7 +564,35 @@ i3 will then reply with the same ClientMessage, sent to the window specified in
|
||||
request. You should use a random value in +data[1]+ and check that you received
|
||||
the same one when getting the reply.
|
||||
|
||||
== Appendix B: Socket activation
|
||||
== Appendix B: The sync IPC command
|
||||
|
||||
The above I3_SYNC protocol allows to synchronise with i3. However, it is not
|
||||
enough for tests that also involve i3bar: There might still be messages from
|
||||
i3bar in-flight even after synchronising with i3. Thus, there also exists a sync
|
||||
IPC command, that is however not meant to be used directly. Instead, i3bar uses
|
||||
it for implementing the I3_SYNC protocol.
|
||||
|
||||
The intended usage works like this:
|
||||
|
||||
1. You send an I3_SYNC message to i3bar's window. See <<i3_sync>>.
|
||||
2. i3bar sends a SYNC IPC command to i3 with payload
|
||||
+{"window":your-window-here,"rnd":your-random-value}+.
|
||||
3. i3 reacts to this IPC command as if it received an I3_SYNC request via X11.
|
||||
|
||||
This protocol is used, for example, in t/525-i3bar-mouse-bindings.t: A mouse
|
||||
button press on i3bar is triggered. i3bar reacts to this by sending IPC commands
|
||||
to i3.
|
||||
|
||||
The necessary synchronisation is achieved by sending an I3_SYNC event to i3bar:
|
||||
Because i3bar reacts with a sync IPC command to i3, all previous IPC commands from
|
||||
i3bar will be handled first. Because i3 reacts via X11, all previous X11
|
||||
requests from i3 will be handled by the X11 server first.
|
||||
|
||||
The actual test also has to sync with i3 first due to how X11 handling works.
|
||||
For more details, refer to the documentation for +XAllowEvents+ with mode
|
||||
+ReplayPointer+.
|
||||
|
||||
== Appendix C: Socket activation
|
||||
|
||||
Socket activation is a mechanism which was made popular by systemd, an init
|
||||
replacement. It basically describes creating a listening socket before starting
|
||||
|
||||
584
docs/userguide
@@ -3,9 +3,9 @@ i3 User’s Guide
|
||||
Michael Stapelberg <michael@i3wm.org>
|
||||
|
||||
This document contains all the information you need to configure and use the i3
|
||||
window manager. If it does not, please check https://www.reddit.com/r/i3wm/
|
||||
first, then contact us on IRC (preferred) or post your question(s) on the
|
||||
mailing list.
|
||||
window manager. If it does not you can https://i3wm.org/contact/[contact us] on
|
||||
https://github.com/i3/i3/discussions[GitHub Discussions], IRC, or the mailing
|
||||
list.
|
||||
|
||||
== Default keybindings
|
||||
|
||||
@@ -53,12 +53,23 @@ existing window (rotated displays).
|
||||
|
||||
image:two_terminals.png[Two terminals]
|
||||
|
||||
To move the focus between the two terminals, you can use the direction keys
|
||||
which you might know from the editor +vi+. However, in i3, your homerow is used
|
||||
for these keys (in +vi+, the keys are shifted to the left by one for
|
||||
compatibility with most keyboard layouts). Therefore, +$mod+j+ is left, +$mod+k+
|
||||
is down, +$mod+l+ is up and `$mod+;` is right. So, to switch between the
|
||||
terminals, use +$mod+k+ or +$mod+l+. Of course, you can also use the arrow keys.
|
||||
To move the focus between the two terminals, you can use the arrow keys. For
|
||||
convenience, the arrows are also available directly on the
|
||||
https://en.wikipedia.org/wiki/Touch_typing[keyboard’s home row] underneath your
|
||||
right hand:
|
||||
|
||||
|===
|
||||
| `$mod+j` | left
|
||||
| `$mod+k` | down
|
||||
| `$mod+l` | up
|
||||
| `$mod+;` | right
|
||||
|===
|
||||
|
||||
Note that this differs by one key from the popular text editor `vi`, which was
|
||||
https://twitter.com/hillelogram/status/1326600125569961991[developed on an
|
||||
ADM-3A terminal and therefore uses `hjkl` instead of `jkl;`] -- i3’s default is
|
||||
meant to require minimal finger movement, but some `vi` users change their i3
|
||||
config for consistency.
|
||||
|
||||
At the moment, your workspace is split (it contains two terminals) in a
|
||||
specific direction (horizontal by default). Every window can be split
|
||||
@@ -67,7 +78,7 @@ horizontally or vertically again, just like the workspace. The terminology is
|
||||
or browser) and "split container" for containers that consist of one or more
|
||||
windows.
|
||||
|
||||
TODO: picture of the tree
|
||||
//TODO: picture of the tree
|
||||
|
||||
To split a window vertically, press +$mod+v+ before you create the new window.
|
||||
To split it horizontally, press +$mod+h+.
|
||||
@@ -185,6 +196,54 @@ provided by the i3 https://github.com/i3/i3/blob/next/etc/config.keycodes[defaul
|
||||
|
||||
Floating windows are always on top of tiling windows.
|
||||
|
||||
[[tiling_drag]]
|
||||
=== Moving tiling containers with the mouse
|
||||
|
||||
Since i3 4.21, it's possible to drag tiling containers using the mouse. The
|
||||
drag can be initiated either by dragging the window's titlebar or by pressing
|
||||
the <<floating_modifier>> and dragging the container while holding the
|
||||
left-click button. See the <<config_tiling_drag>> option for configuring which
|
||||
action triggers the tiling drag.
|
||||
|
||||
Once the drag is initiated and the cursor has left the original container, drop
|
||||
indicators are created according to the position of the cursor relatively to
|
||||
the target container. These indicators help you understand what the resulting
|
||||
<<tree>> layout is going to be after you release the mouse button.
|
||||
|
||||
The possible drop positions are:
|
||||
|
||||
Drop on container::
|
||||
This happens when the mouse is relatively near the center of a container.
|
||||
If the mouse is released, the result is exactly as if you had run the
|
||||
+move container to mark+ command. See <<move_to_mark>>.
|
||||
If the swap modifier is pressed before initiating the drag (+tiling_drag
|
||||
swap_modifier+ set to Shift by default), the containers are swapped
|
||||
instead. In that case, the result is exactly as if you had run the +swap
|
||||
container with mark+ command. See <<swapping_containers>>.
|
||||
Drop as sibling::
|
||||
This happens when the mouse is relatively near the edge of a container. If
|
||||
the mouse is released, the dragged container will become a sibling of the
|
||||
target container, placed left/right/up/down according to the position of
|
||||
the indicator.
|
||||
This might or might not create a new v-split or h-split according to the
|
||||
previous layout of the target container. For example, if the target
|
||||
container is in an h-split and you drop the dragged container below it, the
|
||||
new layout will have to be a v-split.
|
||||
Drop to parent::
|
||||
This happens when the mouse is relatively near the edge of a container (but
|
||||
even closer to the border in comparison to the sibling case above) *and* if
|
||||
that edge is also the parent container's edge. For example, if three
|
||||
containers are in a horizontal layout then edges where this can happen is
|
||||
the left edge of the left container, the right edge of the right container
|
||||
and all bottom and top edges of all three containers.
|
||||
If the mouse is released, the container is first dropped as a sibling to
|
||||
the target container, like in the case above, and then is moved
|
||||
directionally like with the +move left|right|down|up+ command. See
|
||||
<<move_direction>>.
|
||||
|
||||
The color of the indicator matches the +client.focused+ setting. See <<client_colors>>.
|
||||
|
||||
[[tree]]
|
||||
== Tree
|
||||
|
||||
i3 stores all information about the X11 outputs, workspaces and layout of the
|
||||
@@ -263,7 +322,7 @@ single workspace on which you open three terminal windows. All these terminal
|
||||
windows are directly attached to one node inside i3’s layout tree, the
|
||||
workspace node. By default, the workspace node’s orientation is +horizontal+.
|
||||
|
||||
Now you move one of these terminals down (+$mod+Shift+j+ by default). The
|
||||
Now you move one of these terminals down (+$mod+Shift+k+ by default). The
|
||||
workspace node’s orientation will be changed to +vertical+. The terminal window
|
||||
you moved down is directly attached to the workspace and appears on the bottom
|
||||
of the screen. A new (horizontal) container was created to accommodate the
|
||||
@@ -299,14 +358,89 @@ keyboard layout. To start the wizard, use the command +i3-config-wizard+.
|
||||
Please note that you must not have +~/.i3/config+, otherwise the wizard will
|
||||
exit.
|
||||
|
||||
Since i3 4.0, a new configuration format is used. i3 will try to automatically
|
||||
detect the format version of a config file based on a few different keywords,
|
||||
but if you want to make sure that your config is read with the new format,
|
||||
include the following line in your config file:
|
||||
[[include]]
|
||||
=== Include directive
|
||||
|
||||
---------------------
|
||||
# i3 config file (v4)
|
||||
---------------------
|
||||
Since i3 v4.20, it is possible to include other configuration files from your i3
|
||||
configuration.
|
||||
|
||||
*Syntax*:
|
||||
-----------------
|
||||
include <pattern>
|
||||
-----------------
|
||||
|
||||
i3 expands `pattern` using shell-like word expansion, specifically using the
|
||||
https://manpages.debian.org/wordexp.3[`wordexp(3)` C standard library function].
|
||||
|
||||
*Examples*:
|
||||
--------------------------------------------------------------------------------
|
||||
# Tilde expands to the user’s home directory:
|
||||
include ~/.config/i3/assignments.conf
|
||||
|
||||
# Environment variables are expanded:
|
||||
include $HOME/.config/i3/assignments.conf
|
||||
|
||||
# Wildcards are expanded:
|
||||
include ~/.config/i3/config.d/*.conf
|
||||
|
||||
# Command substitution:
|
||||
include ~/.config/i3/`hostname`.conf
|
||||
|
||||
# i3 loads each path only once, so including the i3 config will not result
|
||||
# in an endless loop, but in an error:
|
||||
include ~/.config/i3/config
|
||||
|
||||
# i3 changes the working directory while parsing a config file
|
||||
# so that relative paths are interpreted relative to the directory
|
||||
# of the config file that contains the path:
|
||||
include assignments.conf
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
If a specified file cannot be read, for example because of a lack of file
|
||||
permissions, or because of a dangling symlink, i3 will report an error and
|
||||
continue processing your remaining configuration.
|
||||
|
||||
To list all loaded configuration files, run `i3 --moreversion`:
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
% i3 --moreversion
|
||||
Binary i3 version: 4.19.2-87-gfcae64f7+ © 2009 Michael Stapelberg and contributors
|
||||
Running i3 version: 4.19.2-87-gfcae64f7+ (pid 963940)
|
||||
Loaded i3 config:
|
||||
/tmp/i3.cfg (main) (last modified: 2021-05-13T16:42:31 CEST, 463 seconds ago)
|
||||
/tmp/included.cfg (included) (last modified: 2021-05-13T16:42:43 CEST, 451 seconds ago)
|
||||
/tmp/another.cfg (included) (last modified: 2021-05-13T16:42:46 CEST, 448 seconds ago)
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Variables are shared between all config files, but beware of the following limitation:
|
||||
|
||||
* You can define a variable and use it within an included file.
|
||||
* You cannot use (in the parent file) a variable that was defined within an included file.
|
||||
|
||||
This is a technical limitation: variable expansion happens in a separate stage
|
||||
before parsing include directives.
|
||||
|
||||
Conceptually, included files can only add to the configuration, not undo the
|
||||
effects of already-processed configuration. For example, you can only add new
|
||||
key bindings, not overwrite or remove existing key bindings. This means:
|
||||
|
||||
* The `include` directive is suitable for organizing large configurations into
|
||||
separate files, possibly selecting files based on conditionals.
|
||||
|
||||
* The `include` directive is not suitable for expressing “use the default
|
||||
configuration with the following changes”. For that case, we still recommend
|
||||
copying and modifying the default config.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Implementation-wise, i3 does not currently construct one big configuration from
|
||||
all `include` directives. Instead, i3’s config file parser interprets all
|
||||
configuration directives in its `parse_file()` function. When processing an
|
||||
`include` configuration directive, the parser recursively calls `parse_file()`.
|
||||
|
||||
This means the evaluation order of files forms a tree, or one could say i3 uses
|
||||
depth-first traversal.
|
||||
====
|
||||
|
||||
=== Comments
|
||||
|
||||
@@ -373,8 +507,8 @@ your bindings in the same physical location on the keyboard, use keycodes.
|
||||
If you don’t switch layouts, and want a clean and simple config file, use
|
||||
keysyms.
|
||||
|
||||
Some tools (such as +import+ or +xdotool+) might be unable to run upon a
|
||||
KeyPress event, because the keyboard/pointer is still grabbed. For these
|
||||
Some tools (such as +xdotool+) might be unable to run upon a
|
||||
KeyPress event, because the keyboard is still grabbed. For these
|
||||
situations, the +--release+ flag can be used, which will execute the command
|
||||
after the keys have been released.
|
||||
|
||||
@@ -443,7 +577,7 @@ for the keybinding.
|
||||
# The middle button over a titlebar kills the window
|
||||
bindsym --release button2 kill
|
||||
|
||||
# The middle button and a modifer over any part of the window kills the window
|
||||
# The middle button and a modifier over any part of the window kills the window
|
||||
bindsym --whole-window $mod+button2 kill
|
||||
|
||||
# The right button toggles floating
|
||||
@@ -597,14 +731,18 @@ Default is +left+
|
||||
title_align left|center|right
|
||||
---------------------------------------------
|
||||
|
||||
[[default_border]]
|
||||
=== Default border style for new windows
|
||||
|
||||
This option determines which border style new windows will have. The default is
|
||||
This option determines which border style *new* windows will have. The default is
|
||||
+normal+. Note that default_floating_border applies only to windows which are starting out as
|
||||
floating windows, e.g., dialog windows, but not windows that are floated later on.
|
||||
|
||||
Setting border style to +pixel+ eliminates title bars. The border style +normal+ allows you to
|
||||
adjust edge border width while keeping your title bar.
|
||||
Setting border style to +pixel+ eliminates title bars in split layouts. The border style
|
||||
+normal+ allows you to adjust edge border width while keeping your title bar.
|
||||
|
||||
The title bar is always visible in stacking and tabbed layouts, and this cannot be changed
|
||||
through configuration.
|
||||
|
||||
*Syntax*:
|
||||
---------------------------------------------
|
||||
@@ -639,14 +777,23 @@ default_border pixel 3
|
||||
=== Hiding borders adjacent to the screen edges
|
||||
|
||||
You can hide container borders adjacent to the screen edges using
|
||||
+hide_edge_borders+. This is useful if you are using scrollbars, or do not want
|
||||
to waste even two pixels in displayspace. The "smart" setting hides borders on
|
||||
workspaces with only one window visible, but keeps them on workspaces with
|
||||
multiple windows visible. Default is none.
|
||||
+hide_edge_borders+ (the default is +none+). Hiding borders is useful if you are
|
||||
using scrollbars, or do not want to waste even two pixels in displayspace.
|
||||
|
||||
The "smart" setting hides borders on workspaces with only one window visible,
|
||||
but keeps them on workspaces with multiple windows visible.
|
||||
|
||||
The "smart_no_gaps" setting hides edge-specific borders of a container if the
|
||||
container is the only container on its workspace and the gaps to the screen edge
|
||||
are +0+.
|
||||
|
||||
[[_smart_borders]]
|
||||
+hide_edge_borders+ has replaced the old +smart_borders+ syntax. Use the former
|
||||
instead of the latter.
|
||||
|
||||
*Syntax*:
|
||||
-----------------------------------------------
|
||||
hide_edge_borders none|vertical|horizontal|both|smart
|
||||
hide_edge_borders none|vertical|horizontal|both|smart|smart_no_gaps
|
||||
-----------------------------------------------
|
||||
|
||||
*Example*:
|
||||
@@ -800,7 +947,7 @@ considered.
|
||||
*Syntax*:
|
||||
------------------------------------------------------------
|
||||
assign <criteria> [→] [workspace] [number] <workspace>
|
||||
assign <criteria> [→] output left|right|up|down|primary|<output>
|
||||
assign <criteria> [→] output left|right|up|down|primary|nonprimary|<output>
|
||||
------------------------------------------------------------
|
||||
|
||||
*Examples*:
|
||||
@@ -832,6 +979,9 @@ assign [class="^URxvt$"] → output right
|
||||
|
||||
# Assign urxvt to the primary output
|
||||
assign [class="^URxvt$"] → output primary
|
||||
|
||||
# Assign urxvt to the first non-primary output
|
||||
assign [class="^URxvt$"] → output nonprimary
|
||||
----------------------
|
||||
|
||||
Note that you might not have a primary output configured yet. To do so, run:
|
||||
@@ -945,6 +1095,7 @@ workspace 5 output VGA1 LVDS1
|
||||
workspace "2: vim" output VGA1
|
||||
---------------------------
|
||||
|
||||
[[client_colors]]
|
||||
=== Changing colors
|
||||
|
||||
You can change all colors which i3 uses to draw the window decorations.
|
||||
@@ -961,6 +1112,10 @@ client.focused::
|
||||
client.focused_inactive::
|
||||
A client which is the focused one of its container, but it does not have
|
||||
the focus at the moment.
|
||||
client.focused_tab_title::
|
||||
Tab or stack container title that is the parent of the focused container
|
||||
but not directly focused. Defaults to focused_inactive if not specified and
|
||||
does not use the indicator and child_border colors.
|
||||
client.unfocused::
|
||||
A client which is not the focused one of its container.
|
||||
client.urgent::
|
||||
@@ -974,7 +1129,8 @@ client.background::
|
||||
which do not cover the whole area of this window expose the color. Note
|
||||
that this colorclass only takes a single color.
|
||||
|
||||
Colors are in HTML hex format (#rrggbb), see the following example:
|
||||
Colors are in HTML hex format (#rrggbb, optionally #rrggbbaa), see the following
|
||||
example:
|
||||
|
||||
*Examples (default colors)*:
|
||||
----------------------------------------------------------------------
|
||||
@@ -1004,7 +1160,7 @@ i3 uses Unix sockets to provide an IPC interface. This allows third-party
|
||||
programs to get information from i3, such as the current workspaces
|
||||
(to display a workspace bar), and to control i3.
|
||||
|
||||
The IPC socket is enabled by default and will be created in
|
||||
By default, an IPC socket will be created in
|
||||
+$XDG_RUNTIME_DIR/i3/ipc-socket.%p+ if the directory is available, falling back
|
||||
to +/tmp/i3-%u.XXXXXX/ipc-socket.%p+, where +%u+ is your UNIX username, +%p+ is
|
||||
the PID of i3 and XXXXXX is a string of random characters from the portable
|
||||
@@ -1068,19 +1224,21 @@ mouse_warping none
|
||||
When you are in fullscreen mode, some applications still open popup windows
|
||||
(take Xpdf for example). This is because these applications might not be aware
|
||||
that they are in fullscreen mode (they do not check the corresponding hint).
|
||||
There are three things which are possible to do in this situation:
|
||||
i3 supports four options for this situation:
|
||||
|
||||
1. Display the popup if it belongs to the fullscreen application only. This is
|
||||
the default and should be reasonable behavior for most users.
|
||||
2. Just ignore the popup (don’t map it). This won’t interrupt you while you are
|
||||
in fullscreen. However, some apps might react badly to this (deadlock until
|
||||
you go out of fullscreen).
|
||||
3. Leave fullscreen mode.
|
||||
1. +smart+: Display the popup if it belongs to the fullscreen application only.
|
||||
This is the default and should be reasonable behavior for most users.
|
||||
2. +ignore+: Just ignore the popup (don’t map it). This won’t interrupt you
|
||||
while you are in fullscreen. However, some apps might react badly to this
|
||||
(deadlock until you go out of fullscreen).
|
||||
3. +leave_fullscreen+: Leave fullscreen mode.
|
||||
4. +all+: Since i3 4.24: Display all floating windows regardless to which
|
||||
application they belong to.
|
||||
|
||||
*Syntax*:
|
||||
-----------------------------------------------------
|
||||
popup_during_fullscreen smart|ignore|leave_fullscreen
|
||||
-----------------------------------------------------
|
||||
---------------------------------------------------------
|
||||
popup_during_fullscreen smart|ignore|leave_fullscreen|all
|
||||
---------------------------------------------------------
|
||||
|
||||
*Example*:
|
||||
------------------------------
|
||||
@@ -1259,6 +1417,119 @@ fullscreen toggle
|
||||
bindsym Mod1+F fullscreen toggle
|
||||
-------------------
|
||||
|
||||
[[config_tiling_drag]]
|
||||
=== Tiling drag
|
||||
|
||||
You can configure how to initiate the tiling drag feature (see <<tiling_drag>>).
|
||||
|
||||
The default is +modifier+.
|
||||
|
||||
Since i3 4.24, you can configure a modifier key which, when pressed, will swap
|
||||
instead of moving containers when dropping directly onto another container.
|
||||
Defaults to +Shift+. Note that you have to be pressing both the floating
|
||||
modifer and the swap modifier before the drag is initiated.
|
||||
|
||||
*Syntax*:
|
||||
--------------------------------
|
||||
tiling_drag off
|
||||
tiling_drag modifier|titlebar [modifier|titlebar]
|
||||
tiling_drag swap_modifier <modifier>
|
||||
--------------------------------
|
||||
|
||||
*Examples*:
|
||||
--------------------------------
|
||||
# Only initiate a tiling drag when the modifier is held:
|
||||
tiling_drag modifier
|
||||
|
||||
# Initiate a tiling drag on either titlebar click or held modifier:
|
||||
tiling_drag modifier titlebar
|
||||
|
||||
# Disable tiling drag altogether
|
||||
tiling_drag off
|
||||
|
||||
# Use Control to swap containers
|
||||
tiling_drag swap_modifier Control
|
||||
|
||||
# Setting the swap_modifier to be the same key as the floating modifier will
|
||||
# always swap without the need to hold two keys
|
||||
floating_modifier Mod4
|
||||
tiling_drag swap_modifier Mod4
|
||||
--------------------------------
|
||||
|
||||
[[gaps]]
|
||||
=== Gaps
|
||||
|
||||
Since i3 4.22, you can configure window gaps.
|
||||
“Gaps” are added spacing between windows (or split containers) and to the screen edges:
|
||||
|
||||
image::gaps1920.png["Screenshot of i3 with gaps enabled (10 px inner gaps, 20 px outer gaps)",title="Gaps enabled (10 px inner gaps, 20 px outer gaps)"]
|
||||
|
||||
You can configure two different kind of gaps:
|
||||
|
||||
1. Inner gaps are space between two adjacent windows (or split containers).
|
||||
2. Outer gaps are space along the screen edges. You can configure each side
|
||||
(left, right, top, bottom) separately.
|
||||
|
||||
If you are familiar with HTML and CSS, you can think of inner gaps as `padding`,
|
||||
and of outer gaps as `margin`, applied to a `<div>` around your window or split
|
||||
container.
|
||||
|
||||
Note that outer gaps are added to the inner gaps, meaning the total gap size
|
||||
between a screen edge and a window (or split container) will be the sum of outer
|
||||
and inner gaps.
|
||||
|
||||
You can define gaps either globally or per workspace using the following
|
||||
syntax.
|
||||
|
||||
*Syntax*:
|
||||
-----------------------
|
||||
# Inner gaps for all windows: space between two adjacent windows (or split containers).
|
||||
gaps inner <gap_size>[px]
|
||||
|
||||
# Outer gaps for all windows: space along the screen edges.
|
||||
gaps outer|horizontal|vertical|top|left|bottom|right <gap_size>[px]
|
||||
|
||||
# Inner and outer gaps for all windows on a specific workspace.
|
||||
# <ws> can be a workspace number or name.
|
||||
workspace <ws> gaps inner <gap_size>[px]
|
||||
workspace <ws> gaps outer|horizontal|vertical|top|left|bottom|right <gap_size>[px]
|
||||
|
||||
# Enabling “Smart Gaps” means no gaps will be shown when there is
|
||||
# precisely one window or split container on the workspace.
|
||||
#
|
||||
# inverse_outer only enables outer gaps when there is exactly one
|
||||
# window or split container on the workspace.
|
||||
smart_gaps on|off|inverse_outer
|
||||
-----------------------
|
||||
|
||||
Outer gaps can be configured for each side individually with the `top`, `left`,
|
||||
`bottom` and `right` directive. `horizontal` and `vertical` are shortcuts for
|
||||
`left`/`right` and `top`/`bottom`, respectively.
|
||||
|
||||
*Example*:
|
||||
-------------------------------------------------
|
||||
# Configure 5px of space between windows and to the screen edges.
|
||||
gaps inner 5px
|
||||
|
||||
# Configure an additional 5px of extra space to the screen edges,
|
||||
# for a total gap of 10px to the screen edges, and 5px between windows.
|
||||
gaps outer 5px
|
||||
|
||||
# Overwrite gaps to 0, I need all the space I can get on workspace 3.
|
||||
workspace 3 gaps inner 0
|
||||
workspace 3 gaps outer 0
|
||||
|
||||
# Only enable outer gaps when there is exactly one window or split container on the workspace.
|
||||
smart_gaps inverse_outer
|
||||
-------------------------------------------------
|
||||
|
||||
Tip: Gaps can additionally be changed at runtime with the `gaps` command, see
|
||||
<<changing_gaps>>.
|
||||
|
||||
Tip: You can find an
|
||||
https://github.com/Airblader/i3/wiki/Example-Configuration[example
|
||||
configuration] that uses modes to illustrate various gap configurations.
|
||||
|
||||
== Configuring i3bar
|
||||
|
||||
The bar at the bottom of your monitor is drawn by a separate process called
|
||||
@@ -1340,6 +1611,30 @@ bar {
|
||||
}
|
||||
-------------------------------------------------
|
||||
|
||||
[[workspace_command]]
|
||||
=== Workspace buttons command
|
||||
|
||||
Since i3 4.23, i3bar can run a program and use its +stdout+ output to define
|
||||
the workspace buttons displayed on the left hand side of the bar. With this
|
||||
feature, you can, for example, rename the buttons of workspaces, hide specific
|
||||
workspaces, always show a workspace button even if the workspace does not exist
|
||||
or change the order of the buttons.
|
||||
|
||||
Also see <<status_command>> for the statusline option and
|
||||
https://i3wm.org/docs/i3bar-workspace-protocol.html for the detailed protocol.
|
||||
|
||||
*Syntax*:
|
||||
------------------------
|
||||
workspace_command <command>
|
||||
------------------------
|
||||
|
||||
*Example*:
|
||||
-------------------------------------------------
|
||||
bar {
|
||||
workspace_command /path/to/script.sh
|
||||
}
|
||||
-------------------------------------------------
|
||||
|
||||
=== Display mode
|
||||
|
||||
You can either have i3bar be visible permanently at one edge of the screen
|
||||
@@ -1378,7 +1673,7 @@ the windows key). The default value for the hidden_state is hide.
|
||||
mode dock|hide|invisible
|
||||
hidden_state hide|show
|
||||
modifier <Modifier>|none
|
||||
------------------------
|
||||
-------------------------
|
||||
|
||||
*Example*:
|
||||
----------------
|
||||
@@ -1548,7 +1843,7 @@ tray_output none|primary|<output>
|
||||
---------------------------------
|
||||
|
||||
*Example*:
|
||||
-------------------------
|
||||
----------------------------------
|
||||
# disable system tray
|
||||
bar {
|
||||
tray_output none
|
||||
@@ -1563,7 +1858,7 @@ bar {
|
||||
bar {
|
||||
tray_output HDMI2
|
||||
}
|
||||
-------------------------
|
||||
----------------------------------
|
||||
|
||||
Note that you might not have a primary output configured yet. To do so, run:
|
||||
-------------------------
|
||||
@@ -1587,10 +1882,10 @@ tray_padding <px> [px]
|
||||
-------------------------
|
||||
|
||||
*Example*:
|
||||
-------------------------
|
||||
---------------------------
|
||||
# Obey Fitts's law
|
||||
tray_padding 0
|
||||
-------------------------
|
||||
---------------------------
|
||||
|
||||
=== Font
|
||||
|
||||
@@ -1728,8 +2023,8 @@ bar {
|
||||
|
||||
=== Colors
|
||||
|
||||
As with i3, colors are in HTML hex format (#rrggbb). The following colors can
|
||||
be configured at the moment:
|
||||
As with i3, colors are in HTML hex format (#rrggbb, optionally #rrggbbaa). The
|
||||
following colors can be configured at the moment:
|
||||
|
||||
background::
|
||||
Background color of the bar.
|
||||
@@ -1813,6 +2108,49 @@ while +#000000FF+ will be a fully opaque black (the same as +#000000+).
|
||||
Please note that due to the way the tray specification works, enabling this
|
||||
flag will cause all tray icons to have a transparent background.
|
||||
|
||||
[[i3bar_padding]]
|
||||
=== Padding
|
||||
|
||||
To make i3bar higher (without increasing the font size), and/or add padding to
|
||||
the left and right side of i3bar, you can use the +padding+ directive:
|
||||
|
||||
*Syntax*:
|
||||
--------------------------------------
|
||||
bar {
|
||||
# 2px left/right and 2px top/bottom padding
|
||||
padding 2px
|
||||
|
||||
# 2px top/bottom padding, no left/right padding
|
||||
padding 2px 0
|
||||
|
||||
# 2px top padding, no left/right padding, 4px bottom padding
|
||||
padding 2px 0 4px
|
||||
|
||||
# four value syntax
|
||||
padding top[px] right[px] bottom[px] left[px]
|
||||
}
|
||||
--------------------------------------
|
||||
|
||||
*Examples*:
|
||||
--------------------------------------
|
||||
bar {
|
||||
# 2px left/right and 2px top/bottom padding
|
||||
padding 2px
|
||||
|
||||
# 2px top/bottom padding, no left/right padding
|
||||
padding 2px 0
|
||||
|
||||
# 2px top padding, no left/right padding, 4px bottom padding
|
||||
padding 2px 0 4px
|
||||
|
||||
# 2px top padding, 6px right padding, 4px bottom padding, 1px left padding
|
||||
padding 2px 6px 4px 1px
|
||||
}
|
||||
--------------------------------------
|
||||
|
||||
Note: As a convenience for users who migrate from i3-gaps to i3, the +height+
|
||||
directive from i3-gaps is supported by i3, but should be changed to +padding+.
|
||||
|
||||
[[list_of_commands]]
|
||||
== List of commands
|
||||
|
||||
@@ -1860,15 +2198,24 @@ bindsym $mod+x [class="(?i)firefox"] kill
|
||||
# kill only the About dialog from Firefox
|
||||
bindsym $mod+x [class="Firefox" window_role="About"] kill
|
||||
|
||||
# kill all windows except for Firefox and Gnome Terminal.
|
||||
# case-insensitive and uses negative lookaheads, supported by PCRE
|
||||
bindsym $mod+x [class="^(?i)(?!firefox)(?!gnome-terminal).*"] kill
|
||||
|
||||
# enable floating mode and move container to workspace 4
|
||||
for_window [class="^evil-app$"] floating enable, move container to workspace 4
|
||||
|
||||
# enable window icons for all windows with extra horizontal padding of 1px
|
||||
for_window [all] title_window_icon padding 1px
|
||||
|
||||
# move all floating windows to the scratchpad
|
||||
bindsym $mod+x [floating] move scratchpad
|
||||
------------------------------------
|
||||
|
||||
The criteria which are currently implemented are:
|
||||
|
||||
all::
|
||||
Matches all windows. This criterion requires no value.
|
||||
class::
|
||||
Compares the window class (the second part of WM_CLASS). Use the
|
||||
special value +\_\_focused__+ to match all windows having the same window
|
||||
@@ -1885,6 +2232,10 @@ window_type::
|
||||
Compare the window type (_NET_WM_WINDOW_TYPE). Possible values are
|
||||
+normal+, +dialog+, +utility+, +toolbar+, +splash+, +menu+, +dropdown_menu+,
|
||||
+popup_menu+, +tooltip+ and +notification+.
|
||||
machine::
|
||||
Compares the name of the machine the client window is running on
|
||||
(WM_CLIENT_MACHINE). Usually, it is equal to the hostname of the local
|
||||
machine, but it may differ if remote X11 apps are used.
|
||||
id::
|
||||
Compares the X11 window ID, which you can get via +xwininfo+ for example.
|
||||
title::
|
||||
@@ -1922,9 +2273,9 @@ tiling_from::
|
||||
tiling are matched. With "user", only windows that the user made tiling
|
||||
are matched.
|
||||
|
||||
The criteria +class+, +instance+, +role+, +title+, +workspace+ and +mark+ are
|
||||
actually regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc perlre+ for
|
||||
information on how to use them.
|
||||
The criteria +class+, +instance+, +role+, +title+, +workspace+, +machine+ and
|
||||
+mark+ are actually regular expressions (PCRE). See +pcresyntax(3)+ or +perldoc
|
||||
perlre+ for information on how to use them.
|
||||
|
||||
[[exec]]
|
||||
=== Executing applications (exec)
|
||||
@@ -2074,6 +2425,9 @@ available:
|
||||
<criteria>::
|
||||
Sets focus to the container that matches the specified criteria.
|
||||
See <<command_criteria>>.
|
||||
workspace::
|
||||
Sets focus to the workspace that contains the container that matches the
|
||||
specified criteria.
|
||||
left|right|up|down::
|
||||
Sets focus to the nearest container in the given direction.
|
||||
parent::
|
||||
@@ -2100,10 +2454,11 @@ output::
|
||||
*Syntax*:
|
||||
----------------------------------------------
|
||||
<criteria> focus
|
||||
<criteria> focus workspace
|
||||
focus left|right|down|up
|
||||
focus parent|child|floating|tiling|mode_toggle
|
||||
focus next|prev [sibling]
|
||||
focus output left|right|up|down|primary|<output>
|
||||
focus output left|right|down|up|current|primary|nonprimary|next|<output1> [output2]…
|
||||
----------------------------------------------
|
||||
|
||||
*Examples*:
|
||||
@@ -2111,6 +2466,10 @@ focus output left|right|up|down|primary|<output>
|
||||
# Focus firefox
|
||||
bindsym $mod+F1 [class="Firefox"] focus
|
||||
|
||||
# Focus the workspace where firefox is, without necessarily focusing firefox
|
||||
# itself.
|
||||
bindsym $mod+x [class="Firefox"] focus workspace
|
||||
|
||||
# Focus container on the left, bottom, top, right
|
||||
bindsym $mod+j focus left
|
||||
bindsym $mod+k focus down
|
||||
@@ -2123,6 +2482,9 @@ bindsym $mod+u focus parent
|
||||
# Focus last floating/tiling container
|
||||
bindsym $mod+g focus mode_toggle
|
||||
|
||||
# Focus the next output (effectively toggles when you only have two outputs)
|
||||
bindsym $mod+x focus output next
|
||||
|
||||
# Focus the output right to the current one
|
||||
bindsym $mod+x focus output right
|
||||
|
||||
@@ -2131,6 +2493,12 @@ bindsym $mod+x focus output HDMI-2
|
||||
|
||||
# Focus the primary output
|
||||
bindsym $mod+x focus output primary
|
||||
|
||||
# Cycle focus through non-primary outputs
|
||||
bindsym $mod+x focus output nonprimary
|
||||
|
||||
# Cycle focus between outputs VGA1 and LVDS1 but not DVI0
|
||||
bindsym $mod+x focus output VGA1 LVDS1
|
||||
-------------------------------------------------
|
||||
|
||||
Note that you might not have a primary output configured yet. To do so, run:
|
||||
@@ -2138,6 +2506,7 @@ Note that you might not have a primary output configured yet. To do so, run:
|
||||
xrandr --output <output> --primary
|
||||
-------------------------
|
||||
|
||||
[[move_direction]]
|
||||
=== Moving containers
|
||||
|
||||
Use the +move+ command to move a container.
|
||||
@@ -2186,6 +2555,7 @@ bindsym $mod+c move absolute position center
|
||||
bindsym $mod+m move position mouse
|
||||
-------------------------------------------------------
|
||||
|
||||
[[swapping_containers]]
|
||||
=== Swapping containers
|
||||
|
||||
Two containers can be swapped (i.e., move to each other's position) by using
|
||||
@@ -2264,9 +2634,8 @@ exhausting numbered ones and looks for numbered ones after exhausting named ones
|
||||
See <<move_to_outputs>> for how to move a container/workspace to a different
|
||||
RandR output.
|
||||
|
||||
Workspace names are parsed as
|
||||
https://developer.gnome.org/pango/stable/pango-Markup.html[Pango markup]
|
||||
by i3bar.
|
||||
Workspace names are parsed as https://developer.gnome.org/pango/1.46/[Pango
|
||||
markup] by i3bar.
|
||||
|
||||
[[back_and_forth]]
|
||||
To switch back to the previously focused workspace, use +workspace
|
||||
@@ -2373,9 +2742,11 @@ bindsym $mod+2 workspace number "2: mail"
|
||||
If a workspace does not exist, the command +workspace number "1: mail"+ will
|
||||
create workspace "1: mail".
|
||||
|
||||
If a workspace with number 1 does already exist, the command will switch to this
|
||||
If a workspace with number 1 already exists, the command will switch to this
|
||||
workspace and ignore the text part. So even when the workspace has been renamed
|
||||
to "1: web", the above command will still switch to it.
|
||||
"1: web", the above command will still switch to it. The command +workspace 1+
|
||||
will however create and move to a new workspace "1" alongside the existing
|
||||
"1: mail" workspace.
|
||||
|
||||
=== Moving workspaces to a different screen
|
||||
|
||||
@@ -2390,16 +2761,19 @@ To move a container to another RandR output (addressed by names like +LVDS1+ or
|
||||
+right+, +up+ or +down+), there are two commands:
|
||||
|
||||
*Syntax*:
|
||||
------------------------------------------------------------
|
||||
move container to output left|right|down|up|current|primary|<output>
|
||||
move workspace to output left|right|down|up|current|primary|<output>
|
||||
------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------
|
||||
move container to output left|right|down|up|current|primary|nonprimary|next|<output1> [output2]…
|
||||
move workspace to output left|right|down|up|current|primary|nonprimary|next|<output1> [output2]…
|
||||
-------------------------------------------------------------------------------------
|
||||
|
||||
*Examples*:
|
||||
--------------------------------------------------------
|
||||
# Move the current workspace to the next output
|
||||
# (effectively toggles when you only have two outputs)
|
||||
bindsym $mod+x move workspace to output right
|
||||
bindsym $mod+x move workspace to output next
|
||||
|
||||
# Cycle this workspace between outputs VGA1 and LVDS1 but not DVI0
|
||||
bindsym $mod+x move workspace to output VGA1 LVDS1
|
||||
|
||||
# Put this window on the presentation output.
|
||||
bindsym $mod+x move container to output VGA1
|
||||
@@ -2408,11 +2782,17 @@ bindsym $mod+x move container to output VGA1
|
||||
bindsym $mod+x move container to output primary
|
||||
--------------------------------------------------------
|
||||
|
||||
If you specify more than one output, the container/workspace is cycled through
|
||||
them: If it is already in one of the outputs of the list, it will move to the
|
||||
next output in the list. If it is in an output not in the list, it will move to
|
||||
the first specified output. Non-existing outputs are skipped.
|
||||
|
||||
Note that you might not have a primary output configured yet. To do so, run:
|
||||
-------------------------
|
||||
xrandr --output <output> --primary
|
||||
-------------------------
|
||||
|
||||
[[move_to_mark]]
|
||||
=== Moving containers/windows to marks
|
||||
|
||||
To move a container to another container with a specific mark (see <<vim_like_marks>>),
|
||||
@@ -2558,9 +2938,8 @@ unmark irssi
|
||||
|
||||
By default, i3 will simply print the X11 window title. Using +title_format+,
|
||||
this can be customized by setting the format to the desired output. This
|
||||
directive supports
|
||||
https://developer.gnome.org/pango/stable/pango-Markup.html[Pango markup]
|
||||
and the following placeholders which will be replaced:
|
||||
directive supports https://developer.gnome.org/pango/1.46/[Pango markup] and the
|
||||
following placeholders which will be replaced:
|
||||
|
||||
+%title+::
|
||||
For normal windows, this is the X11 window title (_NET_WM_NAME or WM_NAME
|
||||
@@ -2573,6 +2952,9 @@ and the following placeholders which will be replaced:
|
||||
+%instance+::
|
||||
The X11 window instance (first part of WM_CLASS). This corresponds to the
|
||||
+instance+ criterion, see <<command_criteria>>.
|
||||
+%machine+::
|
||||
The X11 name of the machine (WM_CLIENT_MACHINE). This corresponds to the
|
||||
+machine+ criterion, see <<command_criteria>>.
|
||||
|
||||
Using the <<for_window>> directive, you can set the title format for any window
|
||||
based on <<command_criteria>>.
|
||||
@@ -2594,6 +2976,32 @@ for_window [class=".*"] title_format "<b>%title</b>"
|
||||
for_window [class="(?i)firefox"] title_format "<span foreground='red'>%title</span>"
|
||||
-------------------------------------------------------------------------------------
|
||||
|
||||
[[title_window_icon]]
|
||||
=== Window title icon
|
||||
|
||||
By default, i3 does not display the window icon in the title bar.
|
||||
|
||||
Starting with i3 v4.20, you can optionally enable window icons either for
|
||||
specific windows or for all windows (using the <<for_window>> directive).
|
||||
|
||||
*Syntax*:
|
||||
----------------------------------------
|
||||
title_window_icon <yes|no|toggle>
|
||||
title_window_icon <padding|toggle> <px>
|
||||
----------------------------------------
|
||||
|
||||
*Examples*:
|
||||
-------------------------------------------------------------------------------------
|
||||
# show the window icon for the focused window to make it stand out
|
||||
bindsym $mod+p title_window_icon on
|
||||
|
||||
# enable window icons for all windows
|
||||
for_window [all] title_window_icon on
|
||||
|
||||
# enable window icons for all windows with extra horizontal padding
|
||||
for_window [all] title_window_icon padding 3px
|
||||
-------------------------------------------------------------------------------------
|
||||
|
||||
=== Changing border style
|
||||
|
||||
To change the border of the current client, you can use +border normal+ to use the normal
|
||||
@@ -2623,10 +3031,16 @@ border 1pixel
|
||||
bindsym $mod+t border normal 0
|
||||
# use no window title and a thick border
|
||||
bindsym $mod+y border pixel 3
|
||||
# use window title *and* a thick border
|
||||
bindsym $mod+y border normal 3
|
||||
# use neither window title nor border
|
||||
bindsym $mod+u border none
|
||||
# no border on VLC
|
||||
for_window [class="vlc"] border none
|
||||
----------------------------------------------
|
||||
|
||||
To change the default for all windows, see the directive <<default_border>>.
|
||||
|
||||
[[shmlog]]
|
||||
=== Enabling shared memory logging
|
||||
|
||||
@@ -2775,6 +3189,35 @@ bindsym $mod+b bar mode hide bar-1
|
||||
bindsym $mod+Shift+b bar mode invisible bar-1
|
||||
------------------------------------------------
|
||||
|
||||
[[changing_gaps]]
|
||||
=== Changing gaps
|
||||
|
||||
Gaps can be modified at runtime with the following command syntax:
|
||||
|
||||
*Syntax*:
|
||||
-----------------------------------------------
|
||||
# Inner gaps: space between two adjacent windows (or split containers).
|
||||
gaps inner current|all set|plus|minus|toggle <gap_size_in_px>
|
||||
# Outer gaps: space along the screen edges.
|
||||
gaps outer|horizontal|vertical|top|right|bottom|left current|all set|plus|minus|toggle <gap_size_in_px>
|
||||
-----------------------------------------------
|
||||
|
||||
With `current` or `all` you can change gaps either for only the currently
|
||||
focused or all currently existing workspaces (note that this does not affect the
|
||||
global configuration itself).
|
||||
|
||||
*Examples*:
|
||||
----------------------------------------------
|
||||
gaps inner all set 20
|
||||
gaps outer current plus 5
|
||||
gaps horizontal current plus 40
|
||||
gaps outer current toggle 60
|
||||
|
||||
for_window [class="vlc"] gaps inner 0, gaps outer 0
|
||||
----------------------------------------------
|
||||
|
||||
To change the gaps for all windows, see the <<gaps>> configuration directive.
|
||||
|
||||
[[multi_monitor]]
|
||||
== Multiple monitors
|
||||
|
||||
@@ -2808,7 +3251,8 @@ To help you get going if you have never used multiple monitors before, here is
|
||||
a short overview of the xrandr options which will probably be of interest to
|
||||
you. It is always useful to get an overview of the current screen configuration.
|
||||
Just run "xrandr" and you will get an output like the following:
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
-------------------------------------------------------------------------------------
|
||||
$ xrandr
|
||||
Screen 0: minimum 320 x 200, current 1280 x 800, maximum 8192 x 8192
|
||||
VGA1 disconnected (normal left inverted right x axis y axis)
|
||||
@@ -2821,7 +3265,7 @@ LVDS1 connected 1280x800+0+0 (normal left inverted right x axis y axis) 261mm x
|
||||
720x400 85.0
|
||||
640x400 85.1
|
||||
640x350 85.1
|
||||
--------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------
|
||||
|
||||
Several things are important here: You can see that +LVDS1+ is connected (of
|
||||
course, it is the internal flat panel) but +VGA1+ is not. If you have a monitor
|
||||
@@ -2833,12 +3277,15 @@ combined resolution of your monitors. By default, it is usually too low and has
|
||||
to be increased by editing +/etc/X11/xorg.conf+.
|
||||
|
||||
So, say you connected VGA1 and want to use it as an additional screen:
|
||||
|
||||
-------------------------------------------
|
||||
xrandr --output VGA1 --auto --left-of LVDS1
|
||||
-------------------------------------------
|
||||
|
||||
This command makes xrandr try to find the native resolution of the device
|
||||
connected to +VGA1+ and configures it to the left of your internal flat panel.
|
||||
When running "xrandr" again, the output looks like this:
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
$ xrandr
|
||||
Screen 0: minimum 320 x 200, current 2560 x 1024, maximum 8192 x 8192
|
||||
@@ -2861,6 +3308,7 @@ LVDS1 connected 1280x800+1280+0 (normal left inverted right x axis y axis) 261mm
|
||||
640x400 85.1
|
||||
640x350 85.1
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Please note that i3 uses exactly the same API as xrandr does, so it will see
|
||||
only what you can see in xrandr.
|
||||
|
||||
|
||||
@@ -17,6 +17,10 @@ font pango:monospace 8
|
||||
# text rendering and scalability on retina/hidpi displays (thanks to pango).
|
||||
#font pango:DejaVu Sans Mono 8
|
||||
|
||||
# Start XDG autostart .desktop files using dex. See also
|
||||
# https://wiki.archlinux.org/index.php/XDG_Autostart
|
||||
exec --no-startup-id dex --autostart --environment i3
|
||||
|
||||
# The combination of xss-lock, nm-applet and pactl is a popular choice, so
|
||||
# they are included here as an example. Modify as you see fit.
|
||||
|
||||
@@ -45,6 +49,10 @@ set $right semicolon
|
||||
# use Mouse+Mod1 to drag floating windows to their wanted position
|
||||
floating_modifier Mod1
|
||||
|
||||
# move tiling windows via drag & drop by left-clicking into the title bar,
|
||||
# or left-clicking anywhere into the window while holding the floating modifier.
|
||||
tiling_drag modifier titlebar
|
||||
|
||||
# start a terminal
|
||||
bindsym Mod1+Return exec i3-sensible-terminal
|
||||
|
||||
|
||||
@@ -18,6 +18,10 @@ font pango:monospace 8
|
||||
# text rendering and scalability on retina/hidpi displays (thanks to pango).
|
||||
#font pango:DejaVu Sans Mono 8
|
||||
|
||||
# Start XDG autostart .desktop files using dex. See also
|
||||
# https://wiki.archlinux.org/index.php/XDG_Autostart
|
||||
exec --no-startup-id dex --autostart --environment i3
|
||||
|
||||
# The combination of xss-lock, nm-applet and pactl is a popular choice, so
|
||||
# they are included here as an example. Modify as you see fit.
|
||||
|
||||
@@ -39,6 +43,10 @@ bindsym XF86AudioMicMute exec --no-startup-id pactl set-source-mute @DEFAULT_SOU
|
||||
# Use Mouse+$mod to drag floating windows to their wanted position
|
||||
floating_modifier $mod
|
||||
|
||||
# move tiling windows via drag & drop by left-clicking into the title bar,
|
||||
# or left-clicking anywhere into the window while holding the floating modifier.
|
||||
tiling_drag modifier titlebar
|
||||
|
||||
# start a terminal
|
||||
bindcode $mod+36 exec i3-sensible-terminal
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env perl
|
||||
# vim:ts=4:sw=4:expandtab
|
||||
#
|
||||
# i3 - an improved dynamic tiling window manager
|
||||
# i3 - an improved tiling window manager
|
||||
# © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
#
|
||||
# generate-command-parser.pl: script to generate parts of the command parser
|
||||
@@ -133,7 +133,7 @@ close($enumfh);
|
||||
open(my $callfh, '>', "GENERATED_${prefix}_call.h");
|
||||
my $resultname = uc(substr($prefix, 0, 1)) . substr($prefix, 1) . 'ResultIR';
|
||||
say $callfh '#pragma once';
|
||||
say $callfh "static void GENERATED_call(const int call_identifier, struct $resultname *result) {";
|
||||
say $callfh "static void GENERATED_call(Match *current_match, struct stack *stack, const int call_identifier, struct $resultname *result) {";
|
||||
say $callfh ' switch (call_identifier) {';
|
||||
my $call_id = 0;
|
||||
for my $state (@keys) {
|
||||
@@ -150,8 +150,8 @@ for my $state (@keys) {
|
||||
# calls to get_string(). Also replaces state names (like FOR_WINDOW)
|
||||
# with their ID (useful for cfg_criteria_init(FOR_WINDOW) e.g.).
|
||||
$cmd =~ s/$_/$statenum{$_}/g for @keys;
|
||||
$cmd =~ s/\$([a-z_]+)/get_string("$1")/g;
|
||||
$cmd =~ s/\&([a-z_]+)/get_long("$1")/g;
|
||||
$cmd =~ s/\$([a-z_]+)/get_string(stack, "$1")/g;
|
||||
$cmd =~ s/\&([a-z_]+)/get_long(stack, "$1")/g;
|
||||
# For debugging/testing, we print the call using printf() and thus need
|
||||
# to generate a format string. The format uses %d for <number>s,
|
||||
# literal numbers or state IDs and %s for NULL, <string>s and literal
|
||||
@@ -175,9 +175,9 @@ for my $state (@keys) {
|
||||
say $callfh '#ifndef TEST_PARSER';
|
||||
my $real_cmd = $cmd;
|
||||
if ($real_cmd =~ /\(\)/) {
|
||||
$real_cmd =~ s/\(/(¤t_match, result/;
|
||||
$real_cmd =~ s/\(/(current_match, result/;
|
||||
} else {
|
||||
$real_cmd =~ s/\(/(¤t_match, result, /;
|
||||
$real_cmd =~ s/\(/(current_match, result, /;
|
||||
}
|
||||
say $callfh " $real_cmd;";
|
||||
say $callfh '#else';
|
||||
@@ -228,8 +228,15 @@ for my $state (@keys) {
|
||||
($call_identifier) = ($next_state =~ /^call ([0-9]+)$/);
|
||||
$next_state = '__CALL';
|
||||
}
|
||||
my $identifier = $token->{identifier};
|
||||
say $tokfh qq| { "$token_name", "$identifier", $next_state, { $call_identifier } },|;
|
||||
my $identifier;
|
||||
# Set $identifier to NULL if there is no identifier
|
||||
if ($token->{identifier} eq ""){
|
||||
$identifier = "NULL"
|
||||
}
|
||||
else{
|
||||
$identifier = qq|"$token->{identifier}"|;
|
||||
}
|
||||
say $tokfh qq| { "$token_name", $identifier, $next_state, { $call_identifier } },|;
|
||||
}
|
||||
say $tokfh '};';
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* i3-config-wizard: Program to convert configs using keycodes to configs using
|
||||
@@ -157,8 +157,9 @@ static struct stack_entry stack[10];
|
||||
static void push_string(const char *identifier, const char *str) {
|
||||
for (int c = 0; c < 10; c++) {
|
||||
if (stack[c].identifier != NULL &&
|
||||
strcmp(stack[c].identifier, identifier) != 0)
|
||||
strcmp(stack[c].identifier, identifier) != 0) {
|
||||
continue;
|
||||
}
|
||||
if (stack[c].identifier == NULL) {
|
||||
/* Found a free slot, let’s store it here. */
|
||||
stack[c].identifier = identifier;
|
||||
@@ -184,8 +185,9 @@ static void push_string(const char *identifier, const char *str) {
|
||||
|
||||
static void push_long(const char *identifier, long num) {
|
||||
for (int c = 0; c < 10; c++) {
|
||||
if (stack[c].identifier != NULL)
|
||||
if (stack[c].identifier != NULL) {
|
||||
continue;
|
||||
}
|
||||
/* Found a free slot, let’s store it here. */
|
||||
stack[c].identifier = identifier;
|
||||
stack[c].val.num = num;
|
||||
@@ -204,18 +206,21 @@ static void push_long(const char *identifier, long num) {
|
||||
|
||||
static const char *get_string(const char *identifier) {
|
||||
for (int c = 0; c < 10; c++) {
|
||||
if (stack[c].identifier == NULL)
|
||||
if (stack[c].identifier == NULL) {
|
||||
break;
|
||||
if (strcmp(identifier, stack[c].identifier) == 0)
|
||||
}
|
||||
if (strcmp(identifier, stack[c].identifier) == 0) {
|
||||
return stack[c].val.str;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void clear_stack(void) {
|
||||
for (int c = 0; c < 10; c++) {
|
||||
if (stack[c].type == STACK_STR)
|
||||
if (stack[c].type == STACK_STR) {
|
||||
free(stack[c].val.str);
|
||||
}
|
||||
stack[c].identifier = NULL;
|
||||
stack[c].val.str = NULL;
|
||||
stack[c].val.num = 0;
|
||||
@@ -233,11 +238,13 @@ static bool keysym_used_on_other_key(KeySym sym, xcb_keycode_t except_keycode) {
|
||||
max_keycode = xcb_get_setup(conn)->max_keycode;
|
||||
|
||||
for (i = min_keycode; i && i <= max_keycode; i++) {
|
||||
if (i == except_keycode)
|
||||
if (i == except_keycode) {
|
||||
continue;
|
||||
}
|
||||
for (int level = 0; level < 4; level++) {
|
||||
if (xcb_key_symbols_get_keysym(keysyms, i, level) != sym)
|
||||
if (xcb_key_symbols_get_keysym(keysyms, i, level) != sym) {
|
||||
continue;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -269,22 +276,27 @@ static char *next_state(const cmdp_token *token) {
|
||||
* qwerty (yes, that happens quite often). */
|
||||
const xkb_keysym_t *syms;
|
||||
int num = xkb_keymap_key_get_syms_by_level(xkb_keymap, keycode, 0, 0, &syms);
|
||||
if (num == 0)
|
||||
if (num == 0) {
|
||||
errx(1, "xkb_keymap_key_get_syms_by_level returned no symbols for keycode %d", keycode);
|
||||
if (!keysym_used_on_other_key(syms[0], keycode))
|
||||
}
|
||||
if (!keysym_used_on_other_key(syms[0], keycode)) {
|
||||
level = 0;
|
||||
}
|
||||
}
|
||||
|
||||
const xkb_keysym_t *syms;
|
||||
int num = xkb_keymap_key_get_syms_by_level(xkb_keymap, keycode, 0, level, &syms);
|
||||
if (num == 0)
|
||||
if (num == 0) {
|
||||
errx(1, "xkb_keymap_key_get_syms_by_level returned no symbols for keycode %d", keycode);
|
||||
if (num > 1)
|
||||
}
|
||||
if (num > 1) {
|
||||
printf("xkb_keymap_key_get_syms_by_level (keycode = %d) returned %d symbolsinstead of 1, using only the first one.\n", keycode, num);
|
||||
}
|
||||
|
||||
char str[4096];
|
||||
if (xkb_keysym_get_name(syms[0], str, sizeof(str)) == -1)
|
||||
if (xkb_keysym_get_name(syms[0], str, sizeof(str)) == -1) {
|
||||
errx(EXIT_FAILURE, "xkb_keysym_get_name(%u) failed", syms[0]);
|
||||
}
|
||||
const char *release = get_string("release");
|
||||
char *res;
|
||||
char *modrep = (modifiers == NULL ? sstrdup("") : sstrdup(modifiers));
|
||||
@@ -303,8 +315,9 @@ static char *next_state(const cmdp_token *token) {
|
||||
/* See if we are jumping back to a state in which we were in previously
|
||||
* (statelist contains INITIAL) and just move statelist_idx accordingly. */
|
||||
for (int i = 0; i < statelist_idx; i++) {
|
||||
if (statelist[i] != _next_state)
|
||||
if (statelist[i] != _next_state) {
|
||||
continue;
|
||||
}
|
||||
statelist_idx = i + 1;
|
||||
return NULL;
|
||||
}
|
||||
@@ -329,10 +342,9 @@ static char *rewrite_binding(const char *input) {
|
||||
while ((size_t)(walk - input) <= len) {
|
||||
/* Skip whitespace before every token, newlines are relevant since they
|
||||
* separate configuration directives. */
|
||||
while ((*walk == ' ' || *walk == '\t') && *walk != '\0')
|
||||
while ((*walk == ' ' || *walk == '\t') && *walk != '\0') {
|
||||
walk++;
|
||||
|
||||
//printf("remaining input: %s\n", walk);
|
||||
}
|
||||
|
||||
cmdp_token_ptr *ptr = &(tokens[state]);
|
||||
for (c = 0; c < ptr->n; c++) {
|
||||
@@ -341,11 +353,13 @@ static char *rewrite_binding(const char *input) {
|
||||
/* A literal. */
|
||||
if (token->name[0] == '\'') {
|
||||
if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) {
|
||||
if (token->identifier != NULL)
|
||||
if (token->identifier != NULL) {
|
||||
push_string(token->identifier, token->name + 1);
|
||||
}
|
||||
walk += strlen(token->name) - 1;
|
||||
if ((result = next_state(token)) != NULL)
|
||||
if ((result = next_state(token)) != NULL) {
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
@@ -357,20 +371,24 @@ static char *rewrite_binding(const char *input) {
|
||||
errno = 0;
|
||||
long int num = strtol(walk, &end, 10);
|
||||
if ((errno == ERANGE && (num == LONG_MIN || num == LONG_MAX)) ||
|
||||
(errno != 0 && num == 0))
|
||||
(errno != 0 && num == 0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* No valid numbers found */
|
||||
if (end == walk)
|
||||
if (end == walk) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (token->identifier != NULL)
|
||||
if (token->identifier != NULL) {
|
||||
push_long(token->identifier, num);
|
||||
}
|
||||
|
||||
/* Set walk to the first non-number character */
|
||||
walk = end;
|
||||
if ((result = next_state(token)) != NULL)
|
||||
if ((result = next_state(token)) != NULL) {
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -381,12 +399,14 @@ static char *rewrite_binding(const char *input) {
|
||||
if (*walk == '"') {
|
||||
beginning++;
|
||||
walk++;
|
||||
while (*walk != '\0' && (*walk != '"' || *(walk - 1) == '\\'))
|
||||
while (*walk != '\0' && (*walk != '"' || *(walk - 1) == '\\')) {
|
||||
walk++;
|
||||
}
|
||||
} else {
|
||||
if (token->name[0] == 's') {
|
||||
while (*walk != '\0' && *walk != '\r' && *walk != '\n')
|
||||
while (*walk != '\0' && *walk != '\r' && *walk != '\n') {
|
||||
walk++;
|
||||
}
|
||||
} else {
|
||||
/* For a word, the delimiters are white space (' ' or
|
||||
* '\t'), closing square bracket (]), comma (,) and
|
||||
@@ -394,8 +414,9 @@ static char *rewrite_binding(const char *input) {
|
||||
while (*walk != ' ' && *walk != '\t' &&
|
||||
*walk != ']' && *walk != ',' &&
|
||||
*walk != ';' && *walk != '\r' &&
|
||||
*walk != '\n' && *walk != '\0')
|
||||
*walk != '\n' && *walk != '\0') {
|
||||
walk++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (walk != beginning) {
|
||||
@@ -408,33 +429,36 @@ static char *rewrite_binding(const char *input) {
|
||||
/* We only handle escaped double quotes to not break
|
||||
* backwards compatibility with people using \w in
|
||||
* regular expressions etc. */
|
||||
if (beginning[inpos] == '\\' && beginning[inpos + 1] == '"')
|
||||
if (beginning[inpos] == '\\' && beginning[inpos + 1] == '"') {
|
||||
inpos++;
|
||||
}
|
||||
str[outpos] = beginning[inpos];
|
||||
}
|
||||
if (token->identifier)
|
||||
if (token->identifier) {
|
||||
push_string(token->identifier, str);
|
||||
}
|
||||
free(str);
|
||||
/* If we are at the end of a quoted string, skip the ending
|
||||
* double quote. */
|
||||
if (*walk == '"')
|
||||
if (*walk == '"') {
|
||||
walk++;
|
||||
if ((result = next_state(token)) != NULL)
|
||||
}
|
||||
if ((result = next_state(token)) != NULL) {
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(token->name, "end") == 0) {
|
||||
//printf("checking for end: *%s*\n", walk);
|
||||
if (*walk == '\0' || *walk == '\n' || *walk == '\r') {
|
||||
if ((result = next_state(token)) != NULL)
|
||||
if ((result = next_state(token)) != NULL) {
|
||||
return result;
|
||||
/* To make sure we start with an appropriate matching
|
||||
* datastructure for commands which do *not* specify any
|
||||
}
|
||||
/* To make sure we start with an appropriate matching data
|
||||
* structure for commands which do *not* specify any
|
||||
* criteria, we re-initialize the criteria system after
|
||||
* every command. */
|
||||
// TODO: make this testable
|
||||
walk++;
|
||||
break;
|
||||
}
|
||||
@@ -517,17 +541,19 @@ static int handle_expose(void) {
|
||||
txt(13, 10, "to abort", white, black);
|
||||
|
||||
/* the not-selected modifier */
|
||||
if (modifier == MOD_Mod4)
|
||||
if (modifier == MOD_Mod4) {
|
||||
txt(5, 5, "<Alt>", white, black);
|
||||
else
|
||||
} else {
|
||||
txt(5, 4, "<Win>", white, black);
|
||||
}
|
||||
|
||||
/* the selected modifier */
|
||||
set_font(&bold_font);
|
||||
if (modifier == MOD_Mod4)
|
||||
if (modifier == MOD_Mod4) {
|
||||
txt(2, 4, "-> <Win>", white, black);
|
||||
else
|
||||
} else {
|
||||
txt(2, 5, "-> <Alt>", white, black);
|
||||
}
|
||||
|
||||
set_font(&font);
|
||||
txt(4, 9, "<Enter>", green, black);
|
||||
@@ -566,8 +592,9 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
|
||||
strlen("i3: generate config"),
|
||||
"i3: generate config");
|
||||
xcb_flush(conn);
|
||||
} else
|
||||
} else {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
/* Swap between modifiers when up or down is pressed. */
|
||||
@@ -577,8 +604,9 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
|
||||
}
|
||||
|
||||
/* cancel any time */
|
||||
if (sym == XK_Escape)
|
||||
if (sym == XK_Escape) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/* Check if this is Mod1 or Mod4. The modmap contains Shift, Lock, Control,
|
||||
* Mod1, Mod2, Mod3, Mod4, Mod5 (in that order) */
|
||||
@@ -587,8 +615,9 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
|
||||
int mask = 3;
|
||||
for (int i = 0; i < modmap_reply->keycodes_per_modifier; i++) {
|
||||
xcb_keycode_t code = modmap[(mask * modmap_reply->keycodes_per_modifier) + i];
|
||||
if (code == XCB_NONE)
|
||||
if (code == XCB_NONE) {
|
||||
continue;
|
||||
}
|
||||
printf("Modifier keycode for Mod1: 0x%02x\n", code);
|
||||
if (code == event->detail) {
|
||||
modifier = MOD_Mod1;
|
||||
@@ -600,8 +629,9 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
|
||||
mask = 6;
|
||||
for (int i = 0; i < modmap_reply->keycodes_per_modifier; i++) {
|
||||
xcb_keycode_t code = modmap[(mask * modmap_reply->keycodes_per_modifier) + i];
|
||||
if (code == XCB_NONE)
|
||||
if (code == XCB_NONE) {
|
||||
continue;
|
||||
}
|
||||
printf("Modifier keycode for Mod4: 0x%02x\n", code);
|
||||
if (code == event->detail) {
|
||||
modifier = MOD_Mod4;
|
||||
@@ -618,11 +648,13 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
|
||||
*
|
||||
*/
|
||||
static void handle_button_press(xcb_button_press_event_t *event) {
|
||||
if (current_step != STEP_GENERATE)
|
||||
if (current_step != STEP_GENERATE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->event_x < col_x(5) || event->event_x > col_x(10))
|
||||
if (event->event_x < col_x(5) || event->event_x > col_x(10)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->event_y >= row_y(4) && event->event_y <= (row_y(4) + font.height)) {
|
||||
modifier = MOD_Mod4;
|
||||
@@ -644,20 +676,24 @@ static void finish(void) {
|
||||
|
||||
struct xkb_context *xkb_context;
|
||||
|
||||
if ((xkb_context = xkb_context_new(0)) == NULL)
|
||||
if ((xkb_context = xkb_context_new(0)) == NULL) {
|
||||
errx(1, "could not create xkbcommon context");
|
||||
}
|
||||
|
||||
int32_t device_id = xkb_x11_get_core_keyboard_device_id(conn);
|
||||
if ((xkb_keymap = xkb_x11_keymap_new_from_device(xkb_context, conn, device_id, 0)) == NULL)
|
||||
if ((xkb_keymap = xkb_x11_keymap_new_from_device(xkb_context, conn, device_id, 0)) == NULL) {
|
||||
errx(1, "xkb_x11_keymap_new_from_device failed");
|
||||
}
|
||||
|
||||
FILE *kc_config = fopen(SYSCONFDIR "/i3/config.keycodes", "r");
|
||||
if (kc_config == NULL)
|
||||
if (kc_config == NULL) {
|
||||
err(1, "Could not open input file \"%s\"", SYSCONFDIR "/i3/config.keycodes");
|
||||
}
|
||||
|
||||
FILE *ks_config = fopen(config_path, "w");
|
||||
if (ks_config == NULL)
|
||||
if (ks_config == NULL) {
|
||||
err(1, "Could not open output config file \"%s\"", config_path);
|
||||
}
|
||||
free(config_path);
|
||||
|
||||
char *line = NULL;
|
||||
@@ -688,8 +724,9 @@ static void finish(void) {
|
||||
#endif
|
||||
/* skip the warning block at the beginning of the input file */
|
||||
if (head_of_file &&
|
||||
strncmp("# WARNING", line, strlen("# WARNING")) == 0)
|
||||
strncmp("# WARNING", line, strlen("# WARNING")) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
head_of_file = false;
|
||||
|
||||
@@ -703,10 +740,11 @@ static void finish(void) {
|
||||
|
||||
/* Set the modifier the user chose */
|
||||
if (strncmp(walk, "set $mod ", strlen("set $mod ")) == 0) {
|
||||
if (modifier == MOD_Mod1)
|
||||
if (modifier == MOD_Mod1) {
|
||||
fputs("set $mod Mod1\n", ks_config);
|
||||
else
|
||||
} else {
|
||||
fputs("set $mod Mod4\n", ks_config);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -771,12 +809,13 @@ int main(int argc, char *argv[]) {
|
||||
return 0;
|
||||
case 'm':
|
||||
headless_run = true;
|
||||
if (strcmp(optarg, "alt") == 0)
|
||||
if (strcmp(optarg, "alt") == 0) {
|
||||
modifier = MOD_Mod1;
|
||||
else if (strcmp(optarg, "win") == 0)
|
||||
} else if (strcmp(optarg, "win") == 0) {
|
||||
modifier = MOD_Mod4;
|
||||
else
|
||||
} else {
|
||||
err(EXIT_FAILURE, "Invalid modifier key %s", optarg);
|
||||
}
|
||||
break;
|
||||
case 'h':
|
||||
printf("i3-config-wizard " I3_VERSION "\n");
|
||||
@@ -793,8 +832,9 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
/* Always write to $XDG_CONFIG_HOME/i3/config by default. */
|
||||
if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL)
|
||||
if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) {
|
||||
xdg_config_home = "~/.config";
|
||||
}
|
||||
|
||||
xdg_config_home = resolve_tilde(xdg_config_home);
|
||||
sasprintf(&config_path, "%s/i3/config", xdg_config_home);
|
||||
@@ -803,9 +843,11 @@ int main(int argc, char *argv[]) {
|
||||
char *config_dir;
|
||||
struct stat stbuf;
|
||||
sasprintf(&config_dir, "%s/i3", xdg_config_home);
|
||||
if (stat(config_dir, &stbuf) != 0)
|
||||
if (mkdirp(config_dir, DEFAULT_DIR_MODE) != 0)
|
||||
if (stat(config_dir, &stbuf) != 0) {
|
||||
if (mkdirp(config_dir, DEFAULT_DIR_MODE) != 0) {
|
||||
err(EXIT_FAILURE, "mkdirp(%s) failed", config_dir);
|
||||
}
|
||||
}
|
||||
free(config_dir);
|
||||
free(xdg_config_home);
|
||||
|
||||
@@ -819,8 +861,9 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
int screen;
|
||||
if ((conn = xcb_connect(NULL, &screen)) == NULL ||
|
||||
xcb_connection_has_error(conn))
|
||||
xcb_connection_has_error(conn)) {
|
||||
errx(1, "Cannot open display");
|
||||
}
|
||||
|
||||
if (xkb_x11_setup_xkb_extension(conn,
|
||||
XKB_X11_MIN_MAJOR_XKB_VERSION,
|
||||
@@ -829,8 +872,9 @@ int main(int argc, char *argv[]) {
|
||||
NULL,
|
||||
NULL,
|
||||
&xkb_base_event,
|
||||
&xkb_base_error) != 1)
|
||||
&xkb_base_error) != 1) {
|
||||
errx(EXIT_FAILURE, "Could not setup XKB extension.");
|
||||
}
|
||||
|
||||
keysyms = xcb_key_symbols_alloc(conn);
|
||||
xcb_get_modifier_mapping_cookie_t modmap_cookie;
|
||||
@@ -856,8 +900,9 @@ int main(int argc, char *argv[]) {
|
||||
root_screen = xcb_aux_get_screen(conn, screen);
|
||||
root = root_screen->root;
|
||||
|
||||
if (!(modmap_reply = xcb_get_modifier_mapping_reply(conn, modmap_cookie, NULL)))
|
||||
if (!(modmap_reply = xcb_get_modifier_mapping_reply(conn, modmap_cookie, NULL))) {
|
||||
errx(EXIT_FAILURE, "Could not get modifier mapping");
|
||||
}
|
||||
|
||||
xcb_numlock_mask = get_mod_mask_for(XCB_NUM_LOCK, symbols, modmap_reply);
|
||||
|
||||
|
||||
@@ -46,9 +46,11 @@ sub slurp {
|
||||
|
||||
my @entry_types;
|
||||
my $dmenu_cmd = 'dmenu -i';
|
||||
my $show_duplicates;
|
||||
my $result = GetOptions(
|
||||
'dmenu=s' => \$dmenu_cmd,
|
||||
'entry-type=s' => \@entry_types,
|
||||
'show-duplicates' => \$show_duplicates,
|
||||
'version' => sub {
|
||||
say "dmenu-desktop 1.5 © 2012 Michael Stapelberg";
|
||||
exit 0;
|
||||
@@ -154,6 +156,8 @@ find(
|
||||
},
|
||||
no_chdir => 1,
|
||||
follow_fast => 1,
|
||||
# Ignore any duplicate files and directories and proceed normally:
|
||||
follow_skip => 2,
|
||||
},
|
||||
@searchdirs
|
||||
);
|
||||
@@ -290,7 +294,7 @@ for my $app (keys %apps) {
|
||||
}
|
||||
|
||||
$choices{$name} = $app;
|
||||
next;
|
||||
next unless $show_duplicates;
|
||||
}
|
||||
|
||||
if ((scalar grep { $_ eq 'command' } @entry_types) > 0) {
|
||||
@@ -325,7 +329,7 @@ for my $app (keys %apps) {
|
||||
if (!(scalar grep { $_ eq lc(basename($command)) } @keys) > 0) {
|
||||
$choices{basename($command)} = $app;
|
||||
}
|
||||
next;
|
||||
next unless $show_duplicates;
|
||||
}
|
||||
|
||||
if ((scalar grep { $_ eq 'filename' } @entry_types) > 0) {
|
||||
@@ -364,9 +368,14 @@ binmode $dmenu_in, ':utf8';
|
||||
binmode $dmenu_out, ':utf8';
|
||||
|
||||
# Feed dmenu the possible choices.
|
||||
# Since the process might have already exited, we ignore SIGPIPE.
|
||||
$SIG{PIPE} = 'IGNORE';
|
||||
|
||||
say $dmenu_in $_ for sort keys %choices;
|
||||
close($dmenu_in);
|
||||
|
||||
$SIG{PIPE} = 'DEFAULT';
|
||||
|
||||
waitpid($pid, 0);
|
||||
my $status = ($? >> 8);
|
||||
|
||||
@@ -411,7 +420,7 @@ my $exec = $app->{Exec};
|
||||
my $location = $app->{_Location};
|
||||
|
||||
# Quote as described by “The Exec key”:
|
||||
# https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s06.html
|
||||
# https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s07.html
|
||||
sub quote {
|
||||
my ($str) = @_;
|
||||
$str =~ s/("|`|\$|\\)/\\$1/g;
|
||||
@@ -423,6 +432,17 @@ $choice = quote($choice);
|
||||
$location = quote($location);
|
||||
$name = quote($name);
|
||||
|
||||
# https://standards.freedesktop.org/desktop-entry-spec/latest/ar01s07.html:
|
||||
#
|
||||
# Note that the general escape rule for values of type string states that the
|
||||
# backslash character can be escaped as ("\\") as well and that this escape rule
|
||||
# is applied before the quoting rule. As such, to unambiguously represent a
|
||||
# literal backslash character in a quoted argument in a desktop entry file
|
||||
# requires the use of four successive backslash characters ("\\\\"). Likewise, a
|
||||
# literal dollar sign in a quoted argument in a desktop entry file is
|
||||
# unambiguously represented with ("\\$").
|
||||
$exec =~ s/\\\\/\\/g;
|
||||
|
||||
# Remove deprecated field codes, as the spec dictates.
|
||||
$exec =~ s/%[dDnNvm]//g;
|
||||
|
||||
@@ -479,8 +499,10 @@ EOT
|
||||
# starts with a double quote ("), everything is parsed as-is until the next
|
||||
# double quote which is NOT preceded by a backslash (\).
|
||||
#
|
||||
# Therefore, we escape all double quotes (") by replacing them with \"
|
||||
$exec =~ s/"/\\"/g;
|
||||
# Therefore, we escape all double quotes (") by replacing them with \".
|
||||
# To not change the meaning of any double quote, backslashes need to be
|
||||
# escaped as well.
|
||||
$exec =~ s/(["\\])/\\$1/g;
|
||||
|
||||
if (exists($app->{StartupNotify}) && !$app->{StartupNotify}) {
|
||||
$nosn = '--no-startup-id';
|
||||
@@ -498,7 +520,7 @@ system('i3-msg', $cmd) == 0 or die "Could not launch i3-msg: $?";
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
i3-dmenu-desktop [--dmenu='dmenu -i'] [--entry-type=name]
|
||||
i3-dmenu-desktop [--dmenu='dmenu -i'] [--entry-type=name] [--show-duplicates]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
@@ -549,11 +571,15 @@ version of dmenu.
|
||||
|
||||
Display the (localized) "Name" (type = name), the command (type = command) or
|
||||
the (*.desktop) filename (type = filename) in dmenu. This option can be
|
||||
specified multiple times.
|
||||
specified multiple times. Duplicates will be removed, see '--show-duplicates'.
|
||||
|
||||
Examples are "GNU Image Manipulation Program" (type = name), "gimp" (type =
|
||||
command), and "libreoffice-writer" (type = filename).
|
||||
|
||||
=item B<--show-duplicates>
|
||||
|
||||
Show duplicates if '--entry-type' is specified multiple times.
|
||||
|
||||
=back
|
||||
|
||||
=head1 VERSION
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* i3-dump-log/main.c: Dumps the i3 SHM log to stdout.
|
||||
@@ -25,10 +25,10 @@
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#if !defined(__OpenBSD__)
|
||||
static uint32_t offset_next_write;
|
||||
#endif
|
||||
static uint32_t wrap_count;
|
||||
|
||||
static i3_shmlog_header *header;
|
||||
@@ -36,17 +36,12 @@ static char *logbuffer,
|
||||
*walk;
|
||||
static int ipcfd = -1;
|
||||
|
||||
static volatile bool interrupted = false;
|
||||
|
||||
static void sighandler(int signal) {
|
||||
interrupted = true;
|
||||
}
|
||||
|
||||
static void disable_shmlog(void) {
|
||||
const char *disablecmd = "debuglog off; shmlog off";
|
||||
if (ipc_send_message(ipcfd, strlen(disablecmd),
|
||||
I3_IPC_MESSAGE_TYPE_COMMAND, (uint8_t *)disablecmd) != 0)
|
||||
I3_IPC_MESSAGE_TYPE_COMMAND, (uint8_t *)disablecmd) != 0) {
|
||||
err(EXIT_FAILURE, "IPC send");
|
||||
}
|
||||
|
||||
/* Ensure the command was sent by waiting for the reply: */
|
||||
uint32_t reply_length = 0;
|
||||
@@ -59,8 +54,9 @@ static void disable_shmlog(void) {
|
||||
}
|
||||
|
||||
static int check_for_wrap(void) {
|
||||
if (wrap_count == header->wrap_count)
|
||||
if (wrap_count == header->wrap_count) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The log wrapped. Print the remaining content and reset walk to the top
|
||||
* of the log. */
|
||||
@@ -154,8 +150,9 @@ int main(int argc, char *argv[]) {
|
||||
ipcfd = ipc_connect(NULL);
|
||||
const char *enablecmd = "debuglog on; shmlog 5242880";
|
||||
if (ipc_send_message(ipcfd, strlen(enablecmd),
|
||||
I3_IPC_MESSAGE_TYPE_COMMAND, (uint8_t *)enablecmd) != 0)
|
||||
I3_IPC_MESSAGE_TYPE_COMMAND, (uint8_t *)enablecmd) != 0) {
|
||||
err(EXIT_FAILURE, "IPC send");
|
||||
}
|
||||
/* By the time we receive a reply, I3_SHMLOG_PATH is set: */
|
||||
uint32_t reply_length = 0;
|
||||
uint8_t *reply = NULL;
|
||||
@@ -181,29 +178,33 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
if (*shmname == '\0')
|
||||
if (*shmname == '\0') {
|
||||
errx(EXIT_FAILURE, "Cannot dump log: SHM logging is disabled in i3.");
|
||||
}
|
||||
|
||||
struct stat statbuf;
|
||||
|
||||
/* NB: While we must never write, we need O_RDWR for the pthread condvar. */
|
||||
int logbuffer_shm = shm_open(shmname, O_RDWR, 0);
|
||||
if (logbuffer_shm == -1)
|
||||
if (logbuffer_shm == -1) {
|
||||
err(EXIT_FAILURE, "Could not shm_open SHM segment for the i3 log (%s)", shmname);
|
||||
}
|
||||
|
||||
if (fstat(logbuffer_shm, &statbuf) != 0)
|
||||
if (fstat(logbuffer_shm, &statbuf) != 0) {
|
||||
err(EXIT_FAILURE, "stat(%s)", shmname);
|
||||
}
|
||||
|
||||
/* NB: While we must never write, we need PROT_WRITE for the pthread condvar. */
|
||||
logbuffer = mmap(NULL, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, logbuffer_shm, 0);
|
||||
if (logbuffer == MAP_FAILED)
|
||||
logbuffer = mmap(NULL, statbuf.st_size, PROT_READ, MAP_SHARED, logbuffer_shm, 0);
|
||||
if (logbuffer == MAP_FAILED) {
|
||||
err(EXIT_FAILURE, "Could not mmap SHM segment for the i3 log");
|
||||
}
|
||||
|
||||
header = (i3_shmlog_header *)logbuffer;
|
||||
|
||||
if (verbose)
|
||||
if (verbose) {
|
||||
printf("next_write = %d, last_wrap = %d, logbuffer_size = %d, shmname = %s\n",
|
||||
header->offset_next_write, header->offset_last_wrap, header->size, shmname);
|
||||
}
|
||||
free(shmname);
|
||||
walk = logbuffer + header->offset_next_write;
|
||||
|
||||
@@ -214,8 +215,9 @@ int main(int argc, char *argv[]) {
|
||||
/* In case there was a write to the buffer already, skip the first
|
||||
* old line, it very likely is mangled. Not a problem, though, the log
|
||||
* is chatty enough to have plenty lines left. */
|
||||
while (*walk != '\n')
|
||||
while (*walk != '\n') {
|
||||
walk++;
|
||||
}
|
||||
walk++;
|
||||
}
|
||||
|
||||
@@ -233,25 +235,38 @@ int main(int argc, char *argv[]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Handle SIGINT gracefully to invoke atexit handlers, if any. */
|
||||
struct sigaction action;
|
||||
action.sa_handler = sighandler;
|
||||
sigemptyset(&action.sa_mask);
|
||||
action.sa_flags = 0;
|
||||
sigaction(SIGINT, &action, NULL);
|
||||
char *log_stream_socket_path = root_atom_contents("I3_LOG_STREAM_SOCKET_PATH", NULL, 0);
|
||||
if (log_stream_socket_path == NULL) {
|
||||
errx(EXIT_FAILURE, "could not determine i3 log stream socket path: possible i3-dump-log and i3 version mismatch");
|
||||
}
|
||||
|
||||
/* Since pthread_cond_wait() expects a mutex, we need to provide one.
|
||||
* To not lock i3 (that’s bad, mhkay?) we just define one outside of
|
||||
* the shared memory. */
|
||||
pthread_mutex_t dummy_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
pthread_mutex_lock(&dummy_mutex);
|
||||
while (!interrupted) {
|
||||
pthread_cond_wait(&(header->condvar), &dummy_mutex);
|
||||
/* If this was not a spurious wakeup, print the new lines. */
|
||||
if (header->offset_next_write != offset_next_write) {
|
||||
offset_next_write = header->offset_next_write;
|
||||
print_till_end();
|
||||
int sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
|
||||
if (sockfd == -1) {
|
||||
err(EXIT_FAILURE, "Could not create socket");
|
||||
}
|
||||
|
||||
(void)fcntl(sockfd, F_SETFD, FD_CLOEXEC);
|
||||
|
||||
struct sockaddr_un addr;
|
||||
memset(&addr, 0, sizeof(struct sockaddr_un));
|
||||
addr.sun_family = AF_LOCAL;
|
||||
strncpy(addr.sun_path, log_stream_socket_path, sizeof(addr.sun_path) - 1);
|
||||
if (connect(sockfd, (const struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) {
|
||||
err(EXIT_FAILURE, "Could not connect to i3 on socket %s", log_stream_socket_path);
|
||||
}
|
||||
|
||||
/* Same size as the buffer used in log.c vlog(): */
|
||||
char buf[4096];
|
||||
for (;;) {
|
||||
const int n = read(sockfd, buf, sizeof(buf));
|
||||
if (n == -1) {
|
||||
err(EXIT_FAILURE, "read(log-stream-socket):");
|
||||
}
|
||||
if (n == 0) {
|
||||
exit(0); /* i3 closed the socket */
|
||||
}
|
||||
buf[n] = '\0';
|
||||
swrite(STDOUT_FILENO, buf, n);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# This list can be used to convert X11 Keysyms to Unicode 2.1 character.
|
||||
# The list is not checked for correctness by Unicode officials. Use it
|
||||
# at your own risk and the creator is not responsable for any damage that
|
||||
# at your own risk and the creator is not responsible for any damage that
|
||||
# occurred due to using this list.
|
||||
#
|
||||
# The list is created by looking at the Keysym names and the Unicode data
|
||||
|
||||
@@ -822,21 +822,23 @@ long keysym2ucs(xcb_keysym_t keysym) {
|
||||
|
||||
/* first check for Latin-1 characters (1:1 mapping) */
|
||||
if ((keysym >= 0x0020 && keysym <= 0x007e) ||
|
||||
(keysym >= 0x00a0 && keysym <= 0x00ff))
|
||||
(keysym >= 0x00a0 && keysym <= 0x00ff)) {
|
||||
return keysym;
|
||||
}
|
||||
|
||||
/* also check for directly encoded 24-bit UCS characters */
|
||||
if ((keysym & 0xff000000) == 0x01000000)
|
||||
if ((keysym & 0xff000000) == 0x01000000) {
|
||||
return keysym & 0x00ffffff;
|
||||
}
|
||||
|
||||
/* binary search in table */
|
||||
while (max >= min) {
|
||||
mid = (min + max) / 2;
|
||||
if (keysymtab[mid].keysym < keysym)
|
||||
if (keysymtab[mid].keysym < keysym) {
|
||||
min = mid + 1;
|
||||
else if (keysymtab[mid].keysym > keysym)
|
||||
} else if (keysymtab[mid].keysym > keysym) {
|
||||
max = mid - 1;
|
||||
else {
|
||||
} else {
|
||||
/* found it */
|
||||
return keysymtab[mid].ucs;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* i3-input/main.c: Utility which lets the user input commands and sends them
|
||||
@@ -163,29 +163,32 @@ static void finish_input(void) {
|
||||
char *command = (char *)concat_strings(glyphs_utf8, input_position);
|
||||
|
||||
/* count the occurrences of %s in the string */
|
||||
int c;
|
||||
int len = strlen(format);
|
||||
int cnt = 0;
|
||||
for (c = 0; c < (len - 1); c++)
|
||||
if (format[c] == '%' && format[c + 1] == 's')
|
||||
const size_t len = strlen(format);
|
||||
size_t cnt = 0;
|
||||
for (size_t c = 0; c < (len - 1); c++) {
|
||||
if (format[c] == '%' && format[c + 1] == 's') {
|
||||
cnt++;
|
||||
printf("occurrences = %d\n", cnt);
|
||||
}
|
||||
}
|
||||
printf("occurrences = %zu\n", cnt);
|
||||
|
||||
/* allocate space for the output */
|
||||
int inputlen = strlen(command);
|
||||
char *full = scalloc(strlen(format) - (2 * cnt) /* format without all %s */
|
||||
+ (inputlen * cnt) /* replaced %s */
|
||||
+ 1, /* trailing NUL */
|
||||
1);
|
||||
const size_t input_len = strlen(command);
|
||||
const size_t full_len = MAX(input_len, /* avoid compiler warning */
|
||||
strlen(format) - (2 * cnt) /* format without all %s */
|
||||
+ (input_len * cnt) /* replaced %s */
|
||||
+ 1 /* trailing NUL */
|
||||
);
|
||||
char *full = scalloc(full_len, 1);
|
||||
char *dest = full;
|
||||
for (c = 0; c < len; c++) {
|
||||
/* if this is not % or it is % but without a following 's',
|
||||
* just copy the character */
|
||||
if (format[c] != '%' || (c == (len - 1)) || format[c + 1] != 's')
|
||||
for (size_t c = 0; c < len; c++) {
|
||||
/* if this is not % or it is % but without a following 's', just copy
|
||||
* the character */
|
||||
if (format[c] != '%' || (c == (len - 1)) || format[c + 1] != 's') {
|
||||
*(dest++) = format[c];
|
||||
else {
|
||||
strncat(dest, command, inputlen);
|
||||
dest += inputlen;
|
||||
} else {
|
||||
strncat(dest, command, input_len);
|
||||
dest += input_len;
|
||||
/* skip the following 's' of '%s' */
|
||||
c++;
|
||||
}
|
||||
@@ -223,8 +226,9 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
|
||||
|
||||
/* If modeswitch is currently active, we need to look in group 2 or 3,
|
||||
* respectively. */
|
||||
if (modeswitch_active)
|
||||
if (modeswitch_active) {
|
||||
col += 2;
|
||||
}
|
||||
|
||||
xcb_keysym_t sym = xcb_key_press_lookup_keysym(symbols, event, col);
|
||||
if (sym == XK_Mode_switch) {
|
||||
@@ -233,12 +237,14 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (sym == XK_Return)
|
||||
if (sym == XK_Return) {
|
||||
finish_input();
|
||||
}
|
||||
|
||||
if (sym == XK_BackSpace) {
|
||||
if (input_position == 0)
|
||||
if (input_position == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
input_position--;
|
||||
free(glyphs_utf8[input_position]);
|
||||
@@ -259,8 +265,9 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
|
||||
printf("xcb_is_misc_function_key = %d\n", xcb_is_misc_function_key(sym));
|
||||
printf("xcb_is_modifier_key = %d\n", xcb_is_modifier_key(sym));
|
||||
|
||||
if (xcb_is_modifier_key(sym) || xcb_is_cursor_key(sym))
|
||||
if (xcb_is_modifier_key(sym) || xcb_is_cursor_key(sym)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("sym = %c (%d)\n", sym, sym);
|
||||
|
||||
@@ -284,8 +291,9 @@ static int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press
|
||||
glyphs_utf8[input_position] = out;
|
||||
input_position++;
|
||||
|
||||
if (input_position == limit)
|
||||
if (input_position == limit) {
|
||||
finish_input();
|
||||
}
|
||||
|
||||
handle_expose(NULL, conn, NULL);
|
||||
return 1;
|
||||
@@ -399,7 +407,7 @@ int main(int argc, char *argv[]) {
|
||||
socket_path = sstrdup(optarg);
|
||||
break;
|
||||
case 'v':
|
||||
printf("i3-input " I3_VERSION);
|
||||
printf("i3-input " I3_VERSION "\n");
|
||||
return EXIT_OK;
|
||||
case 'p':
|
||||
/* This option is deprecated, but will still work in i3 v4.1, 4.2 and 4.3 */
|
||||
@@ -439,8 +447,9 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
int screen;
|
||||
conn = xcb_connect(NULL, &screen);
|
||||
if (!conn || xcb_connection_has_error(conn))
|
||||
if (!conn || xcb_connection_has_error(conn)) {
|
||||
die("Cannot open display");
|
||||
}
|
||||
|
||||
sockfd = ipc_connect(socket_path);
|
||||
|
||||
@@ -453,8 +462,9 @@ int main(int argc, char *argv[]) {
|
||||
font = load_font(pattern ? pattern : "pango:monospace 8", true);
|
||||
set_font(&font);
|
||||
|
||||
if (prompt != NULL)
|
||||
if (prompt != NULL) {
|
||||
prompt_offset = predict_text_width(prompt);
|
||||
}
|
||||
|
||||
const xcb_rectangle_t win_pos = get_window_position();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* i3-msg/main.c: Utility which sends messages to a running i3-instance using
|
||||
@@ -61,22 +61,24 @@ static int exit_code = 0;
|
||||
static reply_t last_reply;
|
||||
|
||||
static int reply_boolean_cb(void *params, int val) {
|
||||
if (strcmp(last_key, "success") == 0)
|
||||
if (strcmp(last_key, "success") == 0) {
|
||||
last_reply.success = val;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int reply_string_cb(void *params, const unsigned char *val, size_t len) {
|
||||
char *str = sstrndup((const char *)val, len);
|
||||
|
||||
if (strcmp(last_key, "error") == 0)
|
||||
if (strcmp(last_key, "error") == 0) {
|
||||
last_reply.error = str;
|
||||
else if (strcmp(last_key, "input") == 0)
|
||||
} else if (strcmp(last_key, "input") == 0) {
|
||||
last_reply.input = str;
|
||||
else if (strcmp(last_key, "errorposition") == 0)
|
||||
} else if (strcmp(last_key, "errorposition") == 0) {
|
||||
last_reply.errorposition = str;
|
||||
else
|
||||
} else {
|
||||
free(str);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -146,16 +148,13 @@ static yajl_callbacks config_callbacks = {
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
#if defined(__OpenBSD__)
|
||||
if (pledge("stdio rpath unix", NULL) == -1)
|
||||
err(EXIT_FAILURE, "pledge");
|
||||
#endif
|
||||
char *socket_path = NULL;
|
||||
int o, option_index = 0;
|
||||
uint32_t message_type = I3_IPC_MESSAGE_TYPE_RUN_COMMAND;
|
||||
char *payload = NULL;
|
||||
bool quiet = false;
|
||||
bool monitor = false;
|
||||
bool raw_reply = false;
|
||||
|
||||
static struct option long_options[] = {
|
||||
{"socket", required_argument, 0, 's'},
|
||||
@@ -164,9 +163,10 @@ int main(int argc, char *argv[]) {
|
||||
{"quiet", no_argument, 0, 'q'},
|
||||
{"monitor", no_argument, 0, 'm'},
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{"raw", no_argument, 0, 'r'},
|
||||
{0, 0, 0, 0}};
|
||||
|
||||
char *options_string = "s:t:vhqm";
|
||||
char *options_string = "s:t:vhqmr";
|
||||
|
||||
while ((o = getopt_long(argc, argv, options_string, long_options, &option_index)) != -1) {
|
||||
if (o == 's') {
|
||||
@@ -217,6 +217,8 @@ int main(int argc, char *argv[]) {
|
||||
return 0;
|
||||
} else if (o == '?') {
|
||||
exit(EXIT_FAILURE);
|
||||
} else if (o == 'r') {
|
||||
raw_reply = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,12 +242,14 @@ int main(int argc, char *argv[]) {
|
||||
optind++;
|
||||
}
|
||||
|
||||
if (!payload)
|
||||
if (!payload) {
|
||||
payload = sstrdup("");
|
||||
}
|
||||
|
||||
int sockfd = ipc_connect(socket_path);
|
||||
if (ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t *)payload) == -1)
|
||||
if (ipc_send_message(sockfd, strlen(payload), message_type, (uint8_t *)payload) == -1) {
|
||||
err(EXIT_FAILURE, "IPC: write()");
|
||||
}
|
||||
free(payload);
|
||||
|
||||
uint32_t reply_length;
|
||||
@@ -253,48 +257,57 @@ int main(int argc, char *argv[]) {
|
||||
uint8_t *reply;
|
||||
int ret;
|
||||
if ((ret = ipc_recv_message(sockfd, &reply_type, &reply_length, &reply)) != 0) {
|
||||
if (ret == -1)
|
||||
if (ret == -1) {
|
||||
err(EXIT_FAILURE, "IPC: read()");
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
if (reply_type != message_type)
|
||||
if (reply_type != message_type) {
|
||||
errx(EXIT_FAILURE, "IPC: Received reply of type %d but expected %d", reply_type, message_type);
|
||||
}
|
||||
/* For the reply of commands, have a look if that command was successful.
|
||||
* If not, nicely format the error message. */
|
||||
if (reply_type == I3_IPC_REPLY_TYPE_COMMAND) {
|
||||
yajl_handle handle = yajl_alloc(&reply_callbacks, NULL, NULL);
|
||||
yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
|
||||
yajl_free(handle);
|
||||
if (!raw_reply) {
|
||||
yajl_handle handle = yajl_alloc(&reply_callbacks, NULL, NULL);
|
||||
yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
|
||||
yajl_free(handle);
|
||||
|
||||
switch (state) {
|
||||
case yajl_status_ok:
|
||||
break;
|
||||
case yajl_status_client_canceled:
|
||||
case yajl_status_error:
|
||||
errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
|
||||
switch (state) {
|
||||
case yajl_status_ok:
|
||||
break;
|
||||
case yajl_status_client_canceled:
|
||||
case yajl_status_error:
|
||||
errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!quiet) {
|
||||
if (!quiet || raw_reply) {
|
||||
printf("%.*s\n", reply_length, reply);
|
||||
}
|
||||
} else if (reply_type == I3_IPC_REPLY_TYPE_CONFIG) {
|
||||
yajl_handle handle = yajl_alloc(&config_callbacks, NULL, NULL);
|
||||
yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
|
||||
yajl_free(handle);
|
||||
if (raw_reply) {
|
||||
printf("%.*s\n", reply_length, reply);
|
||||
} else {
|
||||
yajl_handle handle = yajl_alloc(&config_callbacks, NULL, NULL);
|
||||
yajl_status state = yajl_parse(handle, (const unsigned char *)reply, reply_length);
|
||||
yajl_free(handle);
|
||||
|
||||
switch (state) {
|
||||
case yajl_status_ok:
|
||||
break;
|
||||
case yajl_status_client_canceled:
|
||||
case yajl_status_error:
|
||||
errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
|
||||
switch (state) {
|
||||
case yajl_status_ok:
|
||||
break;
|
||||
case yajl_status_client_canceled:
|
||||
case yajl_status_error:
|
||||
errx(EXIT_FAILURE, "IPC: Could not parse JSON reply.");
|
||||
}
|
||||
}
|
||||
} else if (reply_type == I3_IPC_REPLY_TYPE_SUBSCRIBE) {
|
||||
do {
|
||||
free(reply);
|
||||
if ((ret = ipc_recv_message(sockfd, &reply_type, &reply_length, &reply)) != 0) {
|
||||
if (ret == -1)
|
||||
if (ret == -1) {
|
||||
err(EXIT_FAILURE, "IPC: read()");
|
||||
}
|
||||
exit(1);
|
||||
}
|
||||
|
||||
|
||||
103
i3-nagbar/main.c
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* i3-nagbar is a utility which displays a nag message, for example in the case
|
||||
@@ -126,9 +126,11 @@ static void start_application(const char *command) {
|
||||
}
|
||||
|
||||
static button_t *get_button_at(int16_t x, int16_t y) {
|
||||
for (int c = 0; c < buttoncnt; c++)
|
||||
if (x >= (buttons[c].x) && x <= (buttons[c].x + buttons[c].width))
|
||||
for (int c = 0; c < buttoncnt; c++) {
|
||||
if (x >= (buttons[c].x) && x <= (buttons[c].x + buttons[c].width)) {
|
||||
return &buttons[c];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@@ -148,11 +150,13 @@ static void handle_button_release(xcb_connection_t *conn, xcb_button_release_eve
|
||||
printf("button released on x = %d, y = %d\n",
|
||||
event->event_x, event->event_y);
|
||||
/* If the user hits the close button, we exit(0) */
|
||||
if (event->event_x >= btn_close.x && event->event_x < btn_close.x + btn_close.width)
|
||||
if (event->event_x >= btn_close.x && event->event_x < btn_close.x + btn_close.width) {
|
||||
exit(0);
|
||||
}
|
||||
button_t *button = get_button_at(event->event_x, event->event_y);
|
||||
if (!button)
|
||||
if (!button) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* We need to create a custom script containing our actual command
|
||||
* since not every terminal emulator which is contained in
|
||||
@@ -266,13 +270,9 @@ static int handle_expose(xcb_connection_t *conn, xcb_expose_event_t *event) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the position and size the i3-nagbar window should use.
|
||||
* This will be the primary output or a fallback if it cannot be determined.
|
||||
* Tries to position the rectangle on the primary output.
|
||||
*/
|
||||
static xcb_rectangle_t get_window_position(void) {
|
||||
/* Default values if we cannot determine the primary output or its CRTC info. */
|
||||
xcb_rectangle_t result = (xcb_rectangle_t){50, 50, 500, font.height + 2 * MSG_PADDING + BAR_BORDER};
|
||||
|
||||
static void set_window_position_primary(xcb_rectangle_t *result) {
|
||||
xcb_randr_get_screen_resources_current_cookie_t rcookie = xcb_randr_get_screen_resources_current(conn, root);
|
||||
xcb_randr_get_output_primary_cookie_t pcookie = xcb_randr_get_output_primary(conn, root);
|
||||
|
||||
@@ -314,14 +314,61 @@ static xcb_rectangle_t get_window_position(void) {
|
||||
goto free_resources;
|
||||
}
|
||||
|
||||
result.x = crtc->x;
|
||||
result.y = crtc->y;
|
||||
result->x = crtc->x;
|
||||
result->y = crtc->y;
|
||||
goto free_resources;
|
||||
|
||||
free_resources:
|
||||
free(res);
|
||||
free(primary);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to position the rectangle on the output with input focus.
|
||||
* If unsuccessful, try to position on primary output.
|
||||
*/
|
||||
static void set_window_position_focus(xcb_rectangle_t *result) {
|
||||
bool success = false;
|
||||
xcb_get_input_focus_reply_t *input_focus = NULL;
|
||||
xcb_get_geometry_reply_t *geometry = NULL;
|
||||
xcb_translate_coordinates_reply_t *coordinates = NULL;
|
||||
|
||||
/* To avoid the input window disappearing while determining its position */
|
||||
xcb_grab_server(conn);
|
||||
|
||||
input_focus = xcb_get_input_focus_reply(conn, xcb_get_input_focus(conn), NULL);
|
||||
if (input_focus == NULL || input_focus->focus == XCB_NONE) {
|
||||
LOG("Failed to receive the current input focus or no window has the input focus right now.\n");
|
||||
goto free_resources;
|
||||
}
|
||||
|
||||
geometry = xcb_get_geometry_reply(conn, xcb_get_geometry(conn, input_focus->focus), NULL);
|
||||
if (geometry == NULL) {
|
||||
LOG("Failed to received window geometry.\n");
|
||||
goto free_resources;
|
||||
}
|
||||
|
||||
coordinates = xcb_translate_coordinates_reply(
|
||||
conn, xcb_translate_coordinates(conn, input_focus->focus, root, geometry->x, geometry->y), NULL);
|
||||
if (coordinates == NULL) {
|
||||
LOG("Failed to translate coordinates.\n");
|
||||
goto free_resources;
|
||||
}
|
||||
|
||||
LOG("Found current focus at x = %i / y = %i.\n", coordinates->dst_x, coordinates->dst_y);
|
||||
result->x = coordinates->dst_x;
|
||||
result->y = coordinates->dst_y;
|
||||
success = true;
|
||||
|
||||
free_resources:
|
||||
xcb_ungrab_server(conn);
|
||||
free(input_focus);
|
||||
free(coordinates);
|
||||
free(geometry);
|
||||
if (!success) {
|
||||
LOG("Could not position on focused output, trying to position on primary output.\n");
|
||||
set_window_position_primary(result);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
@@ -360,6 +407,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
argv0 = argv[0];
|
||||
|
||||
bool position_on_primary = false;
|
||||
char *pattern = sstrdup("pango:monospace 8");
|
||||
int o, option_index = 0;
|
||||
enum { TYPE_ERROR = 0,
|
||||
@@ -373,9 +421,10 @@ int main(int argc, char *argv[]) {
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{"message", required_argument, 0, 'm'},
|
||||
{"type", required_argument, 0, 't'},
|
||||
{"primary", no_argument, 0, 'p'},
|
||||
{0, 0, 0, 0}};
|
||||
|
||||
char *options_string = "b:B:f:m:t:vh";
|
||||
char *options_string = "b:B:f:m:t:vhp";
|
||||
|
||||
prompt = i3string_from_utf8("Please do not run this program.");
|
||||
|
||||
@@ -399,8 +448,11 @@ int main(int argc, char *argv[]) {
|
||||
case 'h':
|
||||
free(pattern);
|
||||
printf("i3-nagbar " I3_VERSION "\n");
|
||||
printf("i3-nagbar [-m <message>] [-b <button> <action>] [-B <button> <action>] [-t warning|error] [-f <font>] [-v]\n");
|
||||
printf("i3-nagbar [-m <message>] [-b <button> <action>] [-B <button> <action>] [-t warning|error] [-f <font>] [-v] [-p]\n");
|
||||
return 0;
|
||||
case 'p':
|
||||
position_on_primary = true;
|
||||
break;
|
||||
case 'b':
|
||||
case 'B':
|
||||
buttons = srealloc(buttons, sizeof(button_t) * (buttoncnt + 1));
|
||||
@@ -412,8 +464,9 @@ int main(int argc, char *argv[]) {
|
||||
buttons[buttoncnt].action);
|
||||
buttoncnt++;
|
||||
printf("now %d buttons\n", buttoncnt);
|
||||
if (optind < argc)
|
||||
if (optind < argc) {
|
||||
optind++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -422,8 +475,9 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
int screens;
|
||||
if ((conn = xcb_connect(NULL, &screens)) == NULL ||
|
||||
xcb_connection_has_error(conn))
|
||||
xcb_connection_has_error(conn)) {
|
||||
die("Cannot open display");
|
||||
}
|
||||
|
||||
/* Place requests for the atoms we need as soon as possible */
|
||||
#define xmacro(atom) \
|
||||
@@ -459,12 +513,13 @@ int main(int argc, char *argv[]) {
|
||||
font = load_font(pattern, true);
|
||||
set_font(&font);
|
||||
|
||||
#if defined(__OpenBSD__)
|
||||
if (pledge("stdio rpath wpath cpath getpw proc exec", NULL) == -1)
|
||||
err(EXIT_FAILURE, "pledge");
|
||||
#endif
|
||||
|
||||
xcb_rectangle_t win_pos = get_window_position();
|
||||
/* Default values if we cannot determine the preferred window position. */
|
||||
xcb_rectangle_t win_pos = (xcb_rectangle_t){50, 50, 500, font.height + 2 * MSG_PADDING + BAR_BORDER};
|
||||
if (position_on_primary) {
|
||||
set_window_position_primary(&win_pos);
|
||||
} else {
|
||||
set_window_position_focus(&win_pos);
|
||||
}
|
||||
|
||||
xcb_cursor_context_t *cursor_ctx;
|
||||
if (xcb_cursor_context_new(conn, root_screen, &cursor_ctx) < 0) {
|
||||
|
||||
@@ -8,6 +8,11 @@
|
||||
# Distributions/packagers can enhance this script with a
|
||||
# distribution-specific mechanism to find the preferred pager.
|
||||
|
||||
# The less -E and -F options exit immediately for short files, strip if present.
|
||||
case "$LESS" in
|
||||
*[EF]*) LESS=`echo "$LESS" | tr -d EF`
|
||||
esac
|
||||
|
||||
# Hopefully one of these is installed (no flamewars about preference please!):
|
||||
# We don't use 'more' because it will exit if the file is too short.
|
||||
# Worst case scenario we'll open the file in your editor.
|
||||
|
||||
@@ -8,7 +8,13 @@
|
||||
# We welcome patches that add distribution-specific mechanisms to find the
|
||||
# preferred terminal emulator. On Debian, there is the x-terminal-emulator
|
||||
# symlink for example.
|
||||
for terminal in "$TERMINAL" x-terminal-emulator urxvt rxvt termit terminator Eterm aterm uxterm xterm gnome-terminal roxterm xfce4-terminal termite lxterminal mate-terminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda alacritty hyper; do
|
||||
#
|
||||
# Invariants:
|
||||
# 1. $TERMINAL must come first
|
||||
# 2. Distribution-specific mechanisms come next, e.g. x-terminal-emulator
|
||||
# 3. The terminal emulator with best accessibility comes first.
|
||||
# 4. No order is guaranteed/desired for the remaining terminal emulators.
|
||||
for terminal in "$TERMINAL" x-terminal-emulator mate-terminal gnome-terminal terminator xfce4-terminal urxvt rxvt termit Eterm aterm uxterm xterm roxterm termite lxterminal terminology st qterminal lilyterm tilix terminix konsole kitty guake tilda alacritty hyper wezterm rio; do
|
||||
if command -v "$terminal" > /dev/null 2>&1; then
|
||||
exec "$terminal" "$@"
|
||||
fi
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
#include <config.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <ev.h>
|
||||
|
||||
#define STDIN_CHUNK_SIZE 1024
|
||||
|
||||
@@ -40,40 +40,88 @@ typedef struct {
|
||||
*/
|
||||
bool click_events;
|
||||
bool click_events_init;
|
||||
|
||||
/**
|
||||
* stdin- and SIGCHLD-watchers
|
||||
*/
|
||||
ev_io *stdin_io;
|
||||
ev_child *child_sig;
|
||||
int stdin_fd;
|
||||
|
||||
/**
|
||||
* Line read from child that did not include a newline character.
|
||||
*/
|
||||
char *pending_line;
|
||||
} i3bar_child;
|
||||
|
||||
/*
|
||||
* Remove all blocks from the given statusline.
|
||||
* If free_resources is set, the fields of each status block will be free'd.
|
||||
*/
|
||||
void clear_statusline(struct statusline_head *head, bool free_resources);
|
||||
|
||||
/*
|
||||
* Start a child process with the specified command and reroute stdin.
|
||||
* We actually start a $SHELL to execute the command so we don't have to care
|
||||
* about arguments and such
|
||||
* We actually start a shell to execute the command so we don't have to care
|
||||
* about arguments and such.
|
||||
*
|
||||
* If `command' is NULL, such as in the case when no `status_command' is given
|
||||
* in the bar config, no child will be started.
|
||||
*
|
||||
*/
|
||||
void start_child(char *command);
|
||||
|
||||
/*
|
||||
* Same as start_child but starts the configured client that manages workspace
|
||||
* buttons.
|
||||
*
|
||||
*/
|
||||
void start_ws_child(char *command);
|
||||
|
||||
/*
|
||||
* Returns true if the status child process is alive.
|
||||
*
|
||||
*/
|
||||
bool status_child_is_alive(void);
|
||||
|
||||
/*
|
||||
* Returns true if the workspace child process is alive.
|
||||
*
|
||||
*/
|
||||
bool ws_child_is_alive(void);
|
||||
|
||||
/*
|
||||
* kill()s the child process (if any). Called when exit()ing.
|
||||
*
|
||||
*/
|
||||
void kill_child_at_exit(void);
|
||||
void kill_children_at_exit(void);
|
||||
|
||||
/*
|
||||
* kill()s the child process (if any) and closes and
|
||||
* free()s the stdin- and SIGCHLD-watchers
|
||||
* kill()s the child process (if any) and closes and free()s the stdin- and
|
||||
* SIGCHLD-watchers
|
||||
*
|
||||
*/
|
||||
void kill_child(void);
|
||||
|
||||
/*
|
||||
* kill()s the workspace child process (if any) and closes and free()s the
|
||||
* stdin- and SIGCHLD-watchers.
|
||||
* Similar to kill_child.
|
||||
*
|
||||
*/
|
||||
void kill_ws_child(void);
|
||||
|
||||
/*
|
||||
* Sends a SIGSTOP to the child process (if existent)
|
||||
*
|
||||
*/
|
||||
void stop_child(void);
|
||||
void stop_children(void);
|
||||
|
||||
/*
|
||||
* Sends a SIGCONT to the child process (if existent)
|
||||
*
|
||||
*/
|
||||
void cont_child(void);
|
||||
void cont_children(void);
|
||||
|
||||
/*
|
||||
* Whether or not the child want click events
|
||||
@@ -86,3 +134,14 @@ bool child_want_click_events(void);
|
||||
*
|
||||
*/
|
||||
void send_block_clicked(int button, const char *name, const char *instance, int x, int y, int x_rel, int y_rel, int out_x, int out_y, int width, int height, int mods);
|
||||
|
||||
/*
|
||||
* When workspace_command is enabled this function is used to re-parse the
|
||||
* latest received JSON from the client.
|
||||
*/
|
||||
void repeat_last_ws_json(void);
|
||||
|
||||
/*
|
||||
* Replaces the workspace buttons with an error message.
|
||||
*/
|
||||
void set_workspace_button_error(const char *message);
|
||||
|
||||
@@ -46,6 +46,9 @@ struct status_block {
|
||||
i3String *full_text;
|
||||
i3String *short_text;
|
||||
|
||||
bool use_short;
|
||||
uint32_t render_length;
|
||||
|
||||
char *color;
|
||||
char *background;
|
||||
char *border;
|
||||
@@ -66,7 +69,7 @@ struct status_block {
|
||||
uint32_t border_left;
|
||||
bool pango_markup;
|
||||
|
||||
/* The amount of pixels necessary to render a separater after the block. */
|
||||
/* The amount of pixels necessary to render a separator after the block. */
|
||||
uint32_t sep_block_width;
|
||||
|
||||
/* Continuously-updated information on how to render this status block. */
|
||||
|
||||
@@ -38,11 +38,21 @@ typedef struct tray_output_t {
|
||||
TAILQ_ENTRY(tray_output_t) tray_outputs;
|
||||
} tray_output_t;
|
||||
|
||||
/* Matches i3/include/data.h */
|
||||
struct Rect {
|
||||
uint32_t x;
|
||||
uint32_t y;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
};
|
||||
|
||||
typedef struct config_t {
|
||||
uint32_t modifier;
|
||||
TAILQ_HEAD(bindings_head, binding_t) bindings;
|
||||
position_t position;
|
||||
bool verbose;
|
||||
uint32_t bar_height;
|
||||
struct Rect padding;
|
||||
bool transparency;
|
||||
struct xcb_color_strings_t colors;
|
||||
bool disable_binding_mode_indicator;
|
||||
@@ -52,6 +62,7 @@ typedef struct config_t {
|
||||
bool strip_ws_name;
|
||||
char *bar_id;
|
||||
char *command;
|
||||
char *workspace_command;
|
||||
char *fontname;
|
||||
i3String *separator_symbol;
|
||||
TAILQ_HEAD(tray_outputs_head, tray_output_t) tray_outputs;
|
||||
@@ -69,10 +80,17 @@ typedef struct config_t {
|
||||
extern config_t config;
|
||||
|
||||
/**
|
||||
* Start parsing the received bar configuration JSON string
|
||||
* Parse the received bar configuration JSON string
|
||||
*
|
||||
*/
|
||||
void parse_config_json(char *json);
|
||||
void parse_config_json(const unsigned char *json, size_t size);
|
||||
|
||||
/**
|
||||
* Parse the received bar configuration list. The only usecase right now is to
|
||||
* automatically get the first bar id.
|
||||
*
|
||||
*/
|
||||
void parse_get_first_i3bar_config(const unsigned char *json, size_t size);
|
||||
|
||||
/**
|
||||
* free()s the color strings as soon as they are not needed anymore.
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
* socket_path must be a valid path to the ipc_socket of i3
|
||||
*
|
||||
*/
|
||||
int init_connection(const char *socket_path);
|
||||
void init_connection(const char *socket_path);
|
||||
|
||||
/*
|
||||
* Destroy the connection to i3.
|
||||
|
||||
@@ -24,7 +24,7 @@ struct mode {
|
||||
typedef struct mode mode;
|
||||
|
||||
/*
|
||||
* Start parsing the received JSON string
|
||||
* Parse the received JSON string
|
||||
*
|
||||
*/
|
||||
void parse_mode_json(char *json);
|
||||
void parse_mode_json(const unsigned char *json, size_t size);
|
||||
|
||||
@@ -22,10 +22,10 @@ SLIST_HEAD(outputs_head, i3_output);
|
||||
extern struct outputs_head* outputs;
|
||||
|
||||
/*
|
||||
* Start parsing the received JSON string
|
||||
* Parse the received JSON string
|
||||
*
|
||||
*/
|
||||
void parse_outputs_json(char* json);
|
||||
void parse_outputs_json(const unsigned char* json, size_t size);
|
||||
|
||||
/*
|
||||
* Initiate the outputs list
|
||||
@@ -65,8 +65,6 @@ struct i3_output {
|
||||
surface_t statusline_buffer;
|
||||
/* How much of statusline_buffer's horizontal space was used on last statusline render. */
|
||||
int statusline_width;
|
||||
/* Whether statusline block short texts where used on last statusline render. */
|
||||
bool statusline_short_text;
|
||||
/* The actual window on which we draw. */
|
||||
surface_t bar;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -18,10 +18,10 @@ typedef struct i3_ws i3_ws;
|
||||
TAILQ_HEAD(ws_head, i3_ws);
|
||||
|
||||
/*
|
||||
* Start parsing the received JSON string
|
||||
* Parse the received JSON string
|
||||
*
|
||||
*/
|
||||
void parse_workspaces_json(char *json);
|
||||
void parse_workspaces_json(const unsigned char *json, size_t size);
|
||||
|
||||
/*
|
||||
* free() all workspace data structures
|
||||
@@ -38,7 +38,6 @@ struct i3_ws {
|
||||
bool visible; /* If the ws is currently visible on an output */
|
||||
bool focused; /* If the ws is currently focused */
|
||||
bool urgent; /* If the urgent hint of the ws is set */
|
||||
rect rect; /* The rect of the ws (not used (yet)) */
|
||||
struct i3_output *output; /* The current output of the ws */
|
||||
|
||||
TAILQ_ENTRY(i3_ws) tailq; /* Pointer for the TAILQ-Macro */
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include <config.h>
|
||||
|
||||
#include <stdint.h>
|
||||
//#include "outputs.h"
|
||||
|
||||
#define _NET_SYSTEM_TRAY_ORIENTATION_HORZ 0
|
||||
#define _NET_SYSTEM_TRAY_ORIENTATION_VERT 1
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
*
|
||||
*/
|
||||
#include "common.h"
|
||||
#include "queue.h"
|
||||
#include "yajl_utils.h"
|
||||
|
||||
#include <ctype.h> /* isspace */
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <ev.h>
|
||||
@@ -20,6 +22,7 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@@ -27,14 +30,30 @@
|
||||
#include <yajl/yajl_parse.h>
|
||||
|
||||
/* Global variables for child_*() */
|
||||
i3bar_child child;
|
||||
#define DLOG_CHILD DLOG("%s: pid=%ld stopped=%d stop_signal=%d cont_signal=%d click_events=%d click_events_init=%d\n", \
|
||||
__func__, (long)child.pid, child.stopped, child.stop_signal, child.cont_signal, child.click_events, child.click_events_init)
|
||||
i3bar_child status_child = {0};
|
||||
i3bar_child ws_child = {0};
|
||||
|
||||
/* stdin- and SIGCHLD-watchers */
|
||||
ev_io *stdin_io;
|
||||
int stdin_fd;
|
||||
ev_child *child_sig;
|
||||
#define DLOG_CHILD(c) \
|
||||
do { \
|
||||
if ((c).pid == 0) { \
|
||||
DLOG("%s: child pid = 0\n", __func__); \
|
||||
} else if ((c).pid == status_child.pid) { \
|
||||
DLOG("%s: status_command: pid=%ld stopped=%d stop_signal=%d cont_signal=%d click_events=%d click_events_init=%d\n", \
|
||||
__func__, (long)(c).pid, (c).stopped, (c).stop_signal, (c).cont_signal, (c).click_events, (c).click_events_init); \
|
||||
} else if ((c).pid == ws_child.pid) { \
|
||||
DLOG("%s: workspace_command: pid=%ld stopped=%d stop_signal=%d cont_signal=%d click_events=%d click_events_init=%d\n", \
|
||||
__func__, (long)(c).pid, (c).stopped, (c).stop_signal, (c).cont_signal, (c).click_events, (c).click_events_init); \
|
||||
} else { \
|
||||
ELOG("%s: unknown child, this should never happen " \
|
||||
"pid=%ld stopped=%d stop_signal=%d cont_signal=%d click_events=%d click_events_init=%d\n", \
|
||||
__func__, (long)(c).pid, (c).stopped, (c).stop_signal, (c).cont_signal, (c).click_events, (c).click_events_init); \
|
||||
} \
|
||||
} while (0)
|
||||
#define DLOG_CHILDREN \
|
||||
do { \
|
||||
DLOG_CHILD(status_child); \
|
||||
DLOG_CHILD(ws_child); \
|
||||
} while (0)
|
||||
|
||||
/* JSON parser for stdin */
|
||||
yajl_handle parser;
|
||||
@@ -66,7 +85,7 @@ int child_stdin;
|
||||
* Remove all blocks from the given statusline.
|
||||
* If free_resources is set, the fields of each status block will be free'd.
|
||||
*/
|
||||
static void clear_statusline(struct statusline_head *head, bool free_resources) {
|
||||
void clear_statusline(struct statusline_head *head, bool free_resources) {
|
||||
struct status_block *first;
|
||||
while (!TAILQ_EMPTY(head)) {
|
||||
first = TAILQ_FIRST(head);
|
||||
@@ -127,7 +146,7 @@ __attribute__((format(printf, 1, 2))) static void set_statusline_error(const cha
|
||||
TAILQ_INSERT_TAIL(&statusline_head, message_block, blocks);
|
||||
|
||||
finish:
|
||||
FREE(message);
|
||||
free(message);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
@@ -135,18 +154,27 @@ finish:
|
||||
* Stop and free() the stdin- and SIGCHLD-watchers
|
||||
*
|
||||
*/
|
||||
static void cleanup(void) {
|
||||
if (stdin_io != NULL) {
|
||||
ev_io_stop(main_loop, stdin_io);
|
||||
FREE(stdin_io);
|
||||
static void cleanup(i3bar_child *c) {
|
||||
DLOG_CHILD(*c);
|
||||
|
||||
if (c->stdin_io != NULL) {
|
||||
ev_io_stop(main_loop, c->stdin_io);
|
||||
FREE(c->stdin_io);
|
||||
|
||||
if (c->pid == status_child.pid) {
|
||||
close(child_stdin);
|
||||
child_stdin = 0;
|
||||
}
|
||||
close(c->stdin_fd);
|
||||
}
|
||||
|
||||
if (child_sig != NULL) {
|
||||
ev_child_stop(main_loop, child_sig);
|
||||
FREE(child_sig);
|
||||
if (c->child_sig != NULL) {
|
||||
ev_child_stop(main_loop, c->child_sig);
|
||||
FREE(c->child_sig);
|
||||
}
|
||||
|
||||
memset(&child, 0, sizeof(i3bar_child));
|
||||
FREE(c->pending_line);
|
||||
memset(c, 0, sizeof(i3bar_child));
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -169,10 +197,11 @@ static int stdin_start_map(void *context) {
|
||||
memset(&(ctx->block), '\0', sizeof(struct status_block));
|
||||
|
||||
/* Default width of the separator block. */
|
||||
if (config.separator_symbol == NULL)
|
||||
if (config.separator_symbol == NULL) {
|
||||
ctx->block.sep_block_width = logical_px(9);
|
||||
else
|
||||
} else {
|
||||
ctx->block.sep_block_width = logical_px(8) + separator_symbol_width;
|
||||
}
|
||||
|
||||
/* By default we draw all four borders if a border is set. */
|
||||
ctx->block.border_top = 1;
|
||||
@@ -311,10 +340,12 @@ static int stdin_end_map(void *context) {
|
||||
memcpy(new_block, &(ctx->block), sizeof(struct status_block));
|
||||
/* Ensure we have a full_text set, so that when it is missing (or null),
|
||||
* i3bar doesn’t crash and the user gets an annoying message. */
|
||||
if (!new_block->full_text)
|
||||
if (!new_block->full_text) {
|
||||
new_block->full_text = i3string_from_utf8("SPEC VIOLATION: full_text is NULL!");
|
||||
if (new_block->urgent)
|
||||
}
|
||||
if (new_block->urgent) {
|
||||
ctx->has_urgent = true;
|
||||
}
|
||||
|
||||
if (new_block->min_width_str) {
|
||||
i3String *text = i3string_from_utf8(new_block->min_width_str);
|
||||
@@ -325,10 +356,13 @@ static int stdin_end_map(void *context) {
|
||||
|
||||
i3string_set_markup(new_block->full_text, new_block->pango_markup);
|
||||
|
||||
if (new_block->short_text != NULL)
|
||||
new_block->use_short = false;
|
||||
if (new_block->short_text != NULL) {
|
||||
i3string_set_markup(new_block->short_text, new_block->pango_markup);
|
||||
}
|
||||
|
||||
TAILQ_INSERT_TAIL(&statusline_buffer, new_block, blocks);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -358,15 +392,13 @@ static int stdin_end_array(void *context) {
|
||||
* Returns NULL on EOF.
|
||||
*
|
||||
*/
|
||||
static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) {
|
||||
int fd = watcher->fd;
|
||||
int n = 0;
|
||||
static unsigned char *get_buffer(int fd, int *ret_buffer_len) {
|
||||
int rec = 0;
|
||||
int buffer_len = STDIN_CHUNK_SIZE;
|
||||
unsigned char *buffer = smalloc(buffer_len + 1);
|
||||
buffer[0] = '\0';
|
||||
while (1) {
|
||||
n = read(fd, buffer + rec, buffer_len - rec);
|
||||
const ssize_t n = read(fd, buffer + rec, buffer_len - rec);
|
||||
if (n == -1) {
|
||||
if (errno == EAGAIN) {
|
||||
/* finish up */
|
||||
@@ -386,10 +418,11 @@ static unsigned char *get_buffer(ev_io *watcher, int *ret_buffer_len) {
|
||||
|
||||
if (rec == buffer_len) {
|
||||
buffer_len += STDIN_CHUNK_SIZE;
|
||||
buffer = srealloc(buffer, buffer_len);
|
||||
buffer = srealloc(buffer, buffer_len + 1);
|
||||
}
|
||||
}
|
||||
if (*buffer == '\0') {
|
||||
buffer[rec] = '\0';
|
||||
if (buffer[0] == '\0') {
|
||||
FREE(buffer);
|
||||
rec = -1;
|
||||
}
|
||||
@@ -419,8 +452,9 @@ static bool read_json_input(unsigned char *input, int length) {
|
||||
char *message = (char *)yajl_get_error(parser, 0, input, length);
|
||||
|
||||
/* strip the newline yajl adds to the error message */
|
||||
if (message[strlen(message) - 1] == '\n')
|
||||
if (message[strlen(message) - 1] == '\n') {
|
||||
message[strlen(message) - 1] = '\0';
|
||||
}
|
||||
|
||||
fprintf(stderr, "[i3bar] Could not parse JSON input (code = %d, message = %s): %.*s\n",
|
||||
status, message, length, input);
|
||||
@@ -439,13 +473,14 @@ static bool read_json_input(unsigned char *input, int length) {
|
||||
* in statusline
|
||||
*
|
||||
*/
|
||||
static void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
|
||||
static void stdin_io_cb(int fd) {
|
||||
int rec;
|
||||
unsigned char *buffer = get_buffer(watcher, &rec);
|
||||
if (buffer == NULL)
|
||||
unsigned char *buffer = get_buffer(fd, &rec);
|
||||
if (buffer == NULL) {
|
||||
return;
|
||||
}
|
||||
bool has_urgent = false;
|
||||
if (child.version > 0) {
|
||||
if (status_child.version > 0) {
|
||||
has_urgent = read_json_input(buffer, rec);
|
||||
} else {
|
||||
read_flat_input((char *)buffer, rec);
|
||||
@@ -459,22 +494,23 @@ static void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
|
||||
* whether this is JSON or plain text
|
||||
*
|
||||
*/
|
||||
static void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
|
||||
static void stdin_io_first_line_cb(int fd) {
|
||||
int rec;
|
||||
unsigned char *buffer = get_buffer(watcher, &rec);
|
||||
if (buffer == NULL)
|
||||
unsigned char *buffer = get_buffer(fd, &rec);
|
||||
if (buffer == NULL) {
|
||||
return;
|
||||
}
|
||||
DLOG("Detecting input type based on buffer *%.*s*\n", rec, buffer);
|
||||
/* Detect whether this is JSON or plain text. */
|
||||
unsigned int consumed = 0;
|
||||
/* At the moment, we don’t care for the version. This might change
|
||||
* in the future, but for now, we just discard it. */
|
||||
parse_json_header(&child, buffer, rec, &consumed);
|
||||
if (child.version > 0) {
|
||||
/* If hide-on-modifier is set, we start of by sending the
|
||||
* child a SIGSTOP, because the bars aren't mapped at start */
|
||||
parse_json_header(&status_child, buffer, rec, &consumed);
|
||||
if (status_child.version > 0) {
|
||||
/* If hide-on-modifier is set, we start of by sending the status_child
|
||||
* a SIGSTOP, because the bars aren't mapped at start */
|
||||
if (config.hide_on_modifier) {
|
||||
stop_child();
|
||||
stop_children();
|
||||
}
|
||||
draw_bars(read_json_input(buffer + consumed, rec - consumed));
|
||||
} else {
|
||||
@@ -485,9 +521,133 @@ static void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int rev
|
||||
read_flat_input((char *)buffer, rec);
|
||||
}
|
||||
free(buffer);
|
||||
ev_io_stop(main_loop, stdin_io);
|
||||
ev_io_init(stdin_io, &stdin_io_cb, stdin_fd, EV_READ);
|
||||
ev_io_start(main_loop, stdin_io);
|
||||
}
|
||||
|
||||
static bool isempty(char *s) {
|
||||
while (*s != '\0') {
|
||||
if (!isspace(*s)) {
|
||||
return false;
|
||||
}
|
||||
s++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static char *append_string(const char *previous, const char *str) {
|
||||
if (previous != NULL) {
|
||||
char *result;
|
||||
sasprintf(&result, "%s%s", previous, str);
|
||||
return result;
|
||||
}
|
||||
return sstrdup(str);
|
||||
}
|
||||
|
||||
static char *ws_last_json;
|
||||
|
||||
static void ws_stdin_io_cb(int fd) {
|
||||
int rec;
|
||||
unsigned char *buffer = get_buffer(fd, &rec);
|
||||
if (buffer == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
gchar **strings = g_strsplit((const char *)buffer, "\n", 0);
|
||||
for (int idx = 0; strings[idx] != NULL; idx++) {
|
||||
if (ws_child.pending_line == NULL && isempty(strings[idx])) {
|
||||
/* In the normal case where the buffer ends with '\n', the last
|
||||
* string should be empty */
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strings[idx + 1] == NULL) {
|
||||
/* This is the last string but it is not empty, meaning that we have
|
||||
* read data that is incomplete, save it for later. */
|
||||
char *new = append_string(ws_child.pending_line, strings[idx]);
|
||||
free(ws_child.pending_line);
|
||||
ws_child.pending_line = new;
|
||||
continue;
|
||||
}
|
||||
|
||||
free(ws_last_json);
|
||||
ws_last_json = append_string(ws_child.pending_line, strings[idx]);
|
||||
FREE(ws_child.pending_line);
|
||||
|
||||
parse_workspaces_json((const unsigned char *)ws_last_json, strlen(ws_last_json));
|
||||
}
|
||||
|
||||
g_strfreev(strings);
|
||||
free(buffer);
|
||||
|
||||
draw_bars(false);
|
||||
}
|
||||
|
||||
static void common_stdin_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
|
||||
if (watcher == status_child.stdin_io) {
|
||||
if (status_child.version == (uint32_t)-1) {
|
||||
stdin_io_first_line_cb(watcher->fd);
|
||||
} else {
|
||||
stdin_io_cb(watcher->fd);
|
||||
}
|
||||
} else if (watcher == ws_child.stdin_io) {
|
||||
ws_stdin_io_cb(watcher->fd);
|
||||
} else {
|
||||
ELOG("Got callback for unknown watcher fd=%d\n", watcher->fd);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* When workspace_command is enabled this function is used to re-parse the
|
||||
* latest received JSON from the client.
|
||||
*/
|
||||
void repeat_last_ws_json(void) {
|
||||
if (ws_last_json) {
|
||||
DLOG("Repeating last workspace JSON\n");
|
||||
parse_workspaces_json((const unsigned char *)ws_last_json, strlen(ws_last_json));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Wrapper around set_workspace_button_error to mimic the call of
|
||||
* set_statusline_error.
|
||||
*/
|
||||
__attribute__((format(printf, 1, 2))) static void set_workspace_button_error_f(const char *format, ...) {
|
||||
char *message;
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
if (vasprintf(&message, format, args) == -1) {
|
||||
goto finish;
|
||||
}
|
||||
|
||||
set_workspace_button_error(message);
|
||||
|
||||
finish:
|
||||
free(message);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
/*
|
||||
* Replaces the workspace buttons with an error message.
|
||||
*/
|
||||
void set_workspace_button_error(const char *message) {
|
||||
free_workspaces();
|
||||
|
||||
char *name = NULL;
|
||||
sasprintf(&name, "Error: %s", message);
|
||||
|
||||
i3_output *output;
|
||||
SLIST_FOREACH (output, outputs, slist) {
|
||||
i3_ws *fake_ws = scalloc(1, sizeof(i3_ws));
|
||||
/* Don't set the canonical_name field to make this workspace unfocusable. */
|
||||
fake_ws->name = i3string_from_utf8(name);
|
||||
fake_ws->name_width = predict_text_width(fake_ws->name);
|
||||
fake_ws->num = -1;
|
||||
fake_ws->urgent = fake_ws->visible = true;
|
||||
fake_ws->output = output;
|
||||
|
||||
TAILQ_INSERT_TAIL(output->workspaces, fake_ws, tailq);
|
||||
}
|
||||
|
||||
free(name);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -497,27 +657,45 @@ static void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int rev
|
||||
*
|
||||
*/
|
||||
static void child_sig_cb(struct ev_loop *loop, ev_child *watcher, int revents) {
|
||||
int exit_status = WEXITSTATUS(watcher->rstatus);
|
||||
const int exit_status = WEXITSTATUS(watcher->rstatus);
|
||||
|
||||
ELOG("Child (pid: %d) unexpectedly exited with status %d\n",
|
||||
child.pid,
|
||||
watcher->pid,
|
||||
exit_status);
|
||||
|
||||
void (*error_function_pointer)(const char *, ...) = NULL;
|
||||
const char *command_type = "";
|
||||
i3bar_child *c = NULL;
|
||||
if (watcher->pid == status_child.pid) {
|
||||
command_type = "status_command";
|
||||
error_function_pointer = set_statusline_error;
|
||||
c = &status_child;
|
||||
} else if (watcher->pid == ws_child.pid) {
|
||||
command_type = "workspace_command";
|
||||
error_function_pointer = set_workspace_button_error_f;
|
||||
c = &ws_child;
|
||||
} else {
|
||||
ELOG("Unknown child pid, this should never happen\n");
|
||||
return;
|
||||
}
|
||||
DLOG_CHILD(*c);
|
||||
|
||||
/* this error is most likely caused by a user giving a nonexecutable or
|
||||
* nonexistent file, so we will handle those cases separately. */
|
||||
if (exit_status == 126)
|
||||
set_statusline_error("status_command is not executable (exit %d)", exit_status);
|
||||
else if (exit_status == 127)
|
||||
set_statusline_error("status_command not found or is missing a library dependency (exit %d)", exit_status);
|
||||
else
|
||||
set_statusline_error("status_command process exited unexpectedly (exit %d)", exit_status);
|
||||
if (exit_status == 126) {
|
||||
error_function_pointer("%s is not executable (exit %d)", command_type, exit_status);
|
||||
} else if (exit_status == 127) {
|
||||
error_function_pointer("%s not found or is missing a library dependency (exit %d)", command_type, exit_status);
|
||||
} else {
|
||||
error_function_pointer("%s process exited unexpectedly (exit %d)", command_type, exit_status);
|
||||
}
|
||||
|
||||
cleanup();
|
||||
cleanup(c);
|
||||
draw_bars(false);
|
||||
}
|
||||
|
||||
static void child_write_output(void) {
|
||||
if (child.click_events) {
|
||||
if (status_child.click_events) {
|
||||
const unsigned char *output;
|
||||
size_t size;
|
||||
ssize_t n;
|
||||
@@ -525,13 +703,14 @@ static void child_write_output(void) {
|
||||
yajl_gen_get_buf(gen, &output, &size);
|
||||
|
||||
n = writeall(child_stdin, output, size);
|
||||
if (n != -1)
|
||||
if (n != -1) {
|
||||
n = writeall(child_stdin, "\n", 1);
|
||||
}
|
||||
|
||||
yajl_gen_clear(gen);
|
||||
|
||||
if (n == -1) {
|
||||
child.click_events = false;
|
||||
status_child.click_events = false;
|
||||
kill_child();
|
||||
set_statusline_error("child_write_output failed");
|
||||
draw_bars(false);
|
||||
@@ -539,6 +718,41 @@ static void child_write_output(void) {
|
||||
}
|
||||
}
|
||||
|
||||
static pid_t sfork(void) {
|
||||
const pid_t pid = fork();
|
||||
if (pid == -1) {
|
||||
ELOG("Couldn't fork(): %s\n", strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
return pid;
|
||||
}
|
||||
|
||||
static void spipe(int pipedes[2]) {
|
||||
if (pipe(pipedes) == -1) {
|
||||
err(EXIT_FAILURE, "pipe(pipe_in)");
|
||||
}
|
||||
}
|
||||
|
||||
static void exec_shell(char *command) {
|
||||
execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (char *)NULL);
|
||||
}
|
||||
|
||||
static void setup_child_cb(i3bar_child *child) {
|
||||
/* We set O_NONBLOCK because blocking is evil in event-driven software */
|
||||
fcntl(child->stdin_fd, F_SETFL, O_NONBLOCK);
|
||||
|
||||
child->stdin_io = smalloc(sizeof(ev_io));
|
||||
ev_io_init(child->stdin_io, &common_stdin_cb, child->stdin_fd, EV_READ);
|
||||
ev_io_start(main_loop, child->stdin_io);
|
||||
|
||||
/* We must cleanup, if the child unexpectedly terminates */
|
||||
child->child_sig = smalloc(sizeof(ev_child));
|
||||
ev_child_init(child->child_sig, &child_sig_cb, child->pid, 0);
|
||||
ev_child_start(main_loop, child->child_sig);
|
||||
|
||||
DLOG_CHILD(*child);
|
||||
}
|
||||
|
||||
/*
|
||||
* Start a child process with the specified command and reroute stdin.
|
||||
* We actually start a shell to execute the command so we don't have to care
|
||||
@@ -549,8 +763,9 @@ static void child_write_output(void) {
|
||||
*
|
||||
*/
|
||||
void start_child(char *command) {
|
||||
if (command == NULL)
|
||||
if (command == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Allocate a yajl parser which will be used to parse stdin. */
|
||||
static yajl_callbacks callbacks = {
|
||||
@@ -564,69 +779,77 @@ void start_child(char *command) {
|
||||
.yajl_end_array = stdin_end_array,
|
||||
};
|
||||
parser = yajl_alloc(&callbacks, NULL, &parser_context);
|
||||
|
||||
gen = yajl_gen_alloc(NULL);
|
||||
|
||||
int pipe_in[2]; /* pipe we read from */
|
||||
int pipe_out[2]; /* pipe we write to */
|
||||
spipe(pipe_in);
|
||||
spipe(pipe_out);
|
||||
|
||||
if (pipe(pipe_in) == -1)
|
||||
err(EXIT_FAILURE, "pipe(pipe_in)");
|
||||
if (pipe(pipe_out) == -1)
|
||||
err(EXIT_FAILURE, "pipe(pipe_out)");
|
||||
status_child.pid = sfork();
|
||||
if (status_child.pid == 0) {
|
||||
/* Child-process. Reroute streams and start shell */
|
||||
close(pipe_in[0]);
|
||||
close(pipe_out[1]);
|
||||
|
||||
child.pid = fork();
|
||||
switch (child.pid) {
|
||||
case -1:
|
||||
ELOG("Couldn't fork(): %s\n", strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
case 0:
|
||||
/* Child-process. Reroute streams and start shell */
|
||||
dup2(pipe_in[1], STDOUT_FILENO);
|
||||
dup2(pipe_out[0], STDIN_FILENO);
|
||||
|
||||
close(pipe_in[0]);
|
||||
close(pipe_out[1]);
|
||||
setpgid(status_child.pid, 0);
|
||||
exec_shell(command);
|
||||
return;
|
||||
}
|
||||
/* Parent-process. Reroute streams */
|
||||
close(pipe_in[1]);
|
||||
close(pipe_out[0]);
|
||||
|
||||
dup2(pipe_in[1], STDOUT_FILENO);
|
||||
dup2(pipe_out[0], STDIN_FILENO);
|
||||
status_child.stdin_fd = pipe_in[0];
|
||||
child_stdin = pipe_out[1];
|
||||
status_child.version = -1;
|
||||
|
||||
setpgid(child.pid, 0);
|
||||
execl(_PATH_BSHELL, _PATH_BSHELL, "-c", command, (char *)NULL);
|
||||
return;
|
||||
default:
|
||||
/* Parent-process. Reroute streams */
|
||||
setup_child_cb(&status_child);
|
||||
}
|
||||
|
||||
close(pipe_in[1]);
|
||||
close(pipe_out[0]);
|
||||
|
||||
stdin_fd = pipe_in[0];
|
||||
child_stdin = pipe_out[1];
|
||||
|
||||
break;
|
||||
/*
|
||||
* Same as start_child but starts the configured client that manages workspace
|
||||
* buttons.
|
||||
*
|
||||
*/
|
||||
void start_ws_child(char *command) {
|
||||
if (command == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* We set O_NONBLOCK because blocking is evil in event-driven software */
|
||||
fcntl(stdin_fd, F_SETFL, O_NONBLOCK);
|
||||
ws_child.stop_signal = SIGSTOP;
|
||||
ws_child.cont_signal = SIGCONT;
|
||||
|
||||
stdin_io = smalloc(sizeof(ev_io));
|
||||
ev_io_init(stdin_io, &stdin_io_first_line_cb, stdin_fd, EV_READ);
|
||||
ev_io_start(main_loop, stdin_io);
|
||||
int pipe_in[2]; /* pipe we read from */
|
||||
spipe(pipe_in);
|
||||
|
||||
/* We must cleanup, if the child unexpectedly terminates */
|
||||
child_sig = smalloc(sizeof(ev_child));
|
||||
ev_child_init(child_sig, &child_sig_cb, child.pid, 0);
|
||||
ev_child_start(main_loop, child_sig);
|
||||
ws_child.pid = sfork();
|
||||
if (ws_child.pid == 0) {
|
||||
/* Child-process. Reroute streams and start shell */
|
||||
close(pipe_in[0]);
|
||||
dup2(pipe_in[1], STDOUT_FILENO);
|
||||
|
||||
atexit(kill_child_at_exit);
|
||||
DLOG_CHILD;
|
||||
setpgid(ws_child.pid, 0);
|
||||
exec_shell(command);
|
||||
return;
|
||||
}
|
||||
/* Parent-process. Reroute streams */
|
||||
close(pipe_in[1]);
|
||||
ws_child.stdin_fd = pipe_in[0];
|
||||
|
||||
setup_child_cb(&ws_child);
|
||||
}
|
||||
|
||||
static void child_click_events_initialize(void) {
|
||||
DLOG_CHILD;
|
||||
DLOG_CHILD(status_child);
|
||||
|
||||
if (!child.click_events_init) {
|
||||
if (!status_child.click_events_init) {
|
||||
yajl_gen_array_open(gen);
|
||||
child_write_output();
|
||||
child.click_events_init = true;
|
||||
status_child.click_events_init = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -635,7 +858,7 @@ static void child_click_events_initialize(void) {
|
||||
*
|
||||
*/
|
||||
void send_block_clicked(int button, const char *name, const char *instance, int x, int y, int x_rel, int y_rel, int out_x, int out_y, int width, int height, int mods) {
|
||||
if (!child.click_events) {
|
||||
if (!status_child.click_events) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -658,20 +881,27 @@ void send_block_clicked(int button, const char *name, const char *instance, int
|
||||
|
||||
ystr("modifiers");
|
||||
yajl_gen_array_open(gen);
|
||||
if (mods & XCB_MOD_MASK_SHIFT)
|
||||
if (mods & XCB_MOD_MASK_SHIFT) {
|
||||
ystr("Shift");
|
||||
if (mods & XCB_MOD_MASK_CONTROL)
|
||||
}
|
||||
if (mods & XCB_MOD_MASK_CONTROL) {
|
||||
ystr("Control");
|
||||
if (mods & XCB_MOD_MASK_1)
|
||||
}
|
||||
if (mods & XCB_MOD_MASK_1) {
|
||||
ystr("Mod1");
|
||||
if (mods & XCB_MOD_MASK_2)
|
||||
}
|
||||
if (mods & XCB_MOD_MASK_2) {
|
||||
ystr("Mod2");
|
||||
if (mods & XCB_MOD_MASK_3)
|
||||
}
|
||||
if (mods & XCB_MOD_MASK_3) {
|
||||
ystr("Mod3");
|
||||
if (mods & XCB_MOD_MASK_4)
|
||||
}
|
||||
if (mods & XCB_MOD_MASK_4) {
|
||||
ystr("Mod4");
|
||||
if (mods & XCB_MOD_MASK_5)
|
||||
}
|
||||
if (mods & XCB_MOD_MASK_5) {
|
||||
ystr("Mod5");
|
||||
}
|
||||
yajl_gen_array_close(gen);
|
||||
|
||||
ystr("x");
|
||||
@@ -702,35 +932,85 @@ void send_block_clicked(int button, const char *name, const char *instance, int
|
||||
child_write_output();
|
||||
}
|
||||
|
||||
static bool is_alive(i3bar_child *c) {
|
||||
return c->pid > 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if the status child process is alive.
|
||||
*
|
||||
*/
|
||||
bool status_child_is_alive(void) {
|
||||
return is_alive(&status_child);
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true if the workspace child process is alive.
|
||||
*
|
||||
*/
|
||||
bool ws_child_is_alive(void) {
|
||||
return is_alive(&ws_child);
|
||||
}
|
||||
|
||||
/*
|
||||
* kill()s the child process (if any). Called when exit()ing.
|
||||
*
|
||||
*/
|
||||
void kill_child_at_exit(void) {
|
||||
DLOG_CHILD;
|
||||
void kill_children_at_exit(void) {
|
||||
DLOG_CHILDREN;
|
||||
cont_children();
|
||||
|
||||
if (child.pid > 0) {
|
||||
if (child.cont_signal > 0 && child.stopped)
|
||||
killpg(child.pid, child.cont_signal);
|
||||
killpg(child.pid, SIGTERM);
|
||||
if (is_alive(&status_child)) {
|
||||
killpg(status_child.pid, SIGTERM);
|
||||
}
|
||||
if (is_alive(&ws_child)) {
|
||||
killpg(ws_child.pid, SIGTERM);
|
||||
}
|
||||
}
|
||||
|
||||
static void cont_child(i3bar_child *c) {
|
||||
if (is_alive(c) && c->cont_signal > 0 && c->stopped) {
|
||||
c->stopped = false;
|
||||
killpg(c->pid, c->cont_signal);
|
||||
}
|
||||
}
|
||||
|
||||
static void kill_and_wait(i3bar_child *c) {
|
||||
DLOG_CHILD(*c);
|
||||
if (!is_alive(c)) {
|
||||
return;
|
||||
}
|
||||
|
||||
cont_child(c);
|
||||
killpg(c->pid, SIGTERM);
|
||||
int status;
|
||||
waitpid(c->pid, &status, 0);
|
||||
cleanup(c);
|
||||
}
|
||||
|
||||
/*
|
||||
* kill()s the child process (if existent) and closes and
|
||||
* free()s the stdin- and SIGCHLD-watchers
|
||||
* kill()s the child process (if any) and closes and free()s the stdin- and
|
||||
* SIGCHLD-watchers
|
||||
*
|
||||
*/
|
||||
void kill_child(void) {
|
||||
DLOG_CHILD;
|
||||
kill_and_wait(&status_child);
|
||||
}
|
||||
|
||||
if (child.pid > 0) {
|
||||
if (child.cont_signal > 0 && child.stopped)
|
||||
killpg(child.pid, child.cont_signal);
|
||||
killpg(child.pid, SIGTERM);
|
||||
int status;
|
||||
waitpid(child.pid, &status, 0);
|
||||
cleanup();
|
||||
/*
|
||||
* kill()s the workspace child process (if any) and closes and free()s the
|
||||
* stdin- and SIGCHLD-watchers.
|
||||
* Similar to kill_child.
|
||||
*
|
||||
*/
|
||||
void kill_ws_child(void) {
|
||||
kill_and_wait(&ws_child);
|
||||
}
|
||||
|
||||
static void stop_child(i3bar_child *c) {
|
||||
if (c->stop_signal > 0 && !c->stopped) {
|
||||
c->stopped = true;
|
||||
killpg(c->pid, c->stop_signal);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -738,26 +1018,21 @@ void kill_child(void) {
|
||||
* Sends a SIGSTOP to the child process (if existent)
|
||||
*
|
||||
*/
|
||||
void stop_child(void) {
|
||||
DLOG_CHILD;
|
||||
|
||||
if (child.stop_signal > 0 && !child.stopped) {
|
||||
child.stopped = true;
|
||||
killpg(child.pid, child.stop_signal);
|
||||
}
|
||||
void stop_children(void) {
|
||||
DLOG_CHILDREN;
|
||||
stop_child(&status_child);
|
||||
stop_child(&ws_child);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sends a SIGCONT to the child process (if existent)
|
||||
*
|
||||
*/
|
||||
void cont_child(void) {
|
||||
DLOG_CHILD;
|
||||
void cont_children(void) {
|
||||
DLOG_CHILDREN;
|
||||
|
||||
if (child.cont_signal > 0 && child.stopped) {
|
||||
child.stopped = false;
|
||||
killpg(child.pid, child.cont_signal);
|
||||
}
|
||||
cont_child(&status_child);
|
||||
cont_child(&ws_child);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -765,5 +1040,5 @@ void cont_child(void) {
|
||||
*
|
||||
*/
|
||||
bool child_want_click_events(void) {
|
||||
return child.click_events;
|
||||
return status_child.click_events;
|
||||
}
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <yajl/yajl_parse.h>
|
||||
|
||||
config_t config;
|
||||
static char *cur_key;
|
||||
static bool parsing_bindings;
|
||||
static bool parsing_tray_outputs;
|
||||
static bool parsing_padding;
|
||||
|
||||
/*
|
||||
* Parse a key.
|
||||
@@ -39,12 +39,17 @@ static int config_map_key_cb(void *params_, const unsigned char *keyVal, size_t
|
||||
parsing_tray_outputs = true;
|
||||
}
|
||||
|
||||
if (strcmp(cur_key, "padding") == 0) {
|
||||
parsing_padding = true;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int config_end_array_cb(void *params_) {
|
||||
parsing_bindings = false;
|
||||
parsing_tray_outputs = false;
|
||||
parsing_padding = false;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -71,8 +76,9 @@ static int config_null_cb(void *params_) {
|
||||
static int config_string_cb(void *params_, const unsigned char *val, size_t _len) {
|
||||
int len = (int)_len;
|
||||
/* The id and socket_path are ignored, we already know them. */
|
||||
if (!strcmp(cur_key, "id") || !strcmp(cur_key, "socket_path"))
|
||||
if (!strcmp(cur_key, "id") || !strcmp(cur_key, "socket_path")) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (parsing_bindings) {
|
||||
if (strcmp(cur_key, "command") == 0) {
|
||||
@@ -126,31 +132,31 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
|
||||
}
|
||||
|
||||
if (len == strlen("shift") && !strncmp((const char *)val, "shift", strlen("shift"))) {
|
||||
config.modifier = ShiftMask;
|
||||
config.modifier = XCB_MOD_MASK_SHIFT;
|
||||
return 1;
|
||||
}
|
||||
if (len == strlen("ctrl") && !strncmp((const char *)val, "ctrl", strlen("ctrl"))) {
|
||||
config.modifier = ControlMask;
|
||||
config.modifier = XCB_MOD_MASK_CONTROL;
|
||||
return 1;
|
||||
}
|
||||
if (len == strlen("Mod") + 1 && !strncmp((const char *)val, "Mod", strlen("Mod"))) {
|
||||
switch (val[3]) {
|
||||
case '1':
|
||||
config.modifier = Mod1Mask;
|
||||
config.modifier = XCB_MOD_MASK_1;
|
||||
return 1;
|
||||
case '2':
|
||||
config.modifier = Mod2Mask;
|
||||
config.modifier = XCB_MOD_MASK_2;
|
||||
return 1;
|
||||
case '3':
|
||||
config.modifier = Mod3Mask;
|
||||
config.modifier = XCB_MOD_MASK_3;
|
||||
return 1;
|
||||
case '5':
|
||||
config.modifier = Mod5Mask;
|
||||
config.modifier = XCB_MOD_MASK_5;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
config.modifier = Mod4Mask;
|
||||
config.modifier = XCB_MOD_MASK_4;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -183,12 +189,17 @@ static int config_string_cb(void *params_, const unsigned char *val, size_t _len
|
||||
}
|
||||
|
||||
if (!strcmp(cur_key, "status_command")) {
|
||||
DLOG("command = %.*s\n", len, val);
|
||||
FREE(config.command);
|
||||
DLOG("status_command = %.*s\n", len, val);
|
||||
sasprintf(&config.command, "%.*s", len, val);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcmp(cur_key, "workspace_command")) {
|
||||
DLOG("workspace_command = %.*s\n", len, val);
|
||||
sasprintf(&config.workspace_command, "%.*s", len, val);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcmp(cur_key, "font")) {
|
||||
DLOG("font = %.*s\n", len, val);
|
||||
FREE(config.fontname);
|
||||
@@ -331,6 +342,35 @@ static int config_integer_cb(void *params_, long long val) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (parsing_padding) {
|
||||
if (strcmp(cur_key, "x") == 0) {
|
||||
DLOG("padding.x = %lld\n", val);
|
||||
config.padding.x = (uint32_t)val;
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(cur_key, "y") == 0) {
|
||||
DLOG("padding.y = %lld\n", val);
|
||||
config.padding.y = (uint32_t)val;
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(cur_key, "width") == 0) {
|
||||
DLOG("padding.width = %lld\n", val);
|
||||
config.padding.width = (uint32_t)val;
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(cur_key, "height") == 0) {
|
||||
DLOG("padding.height = %lld\n", val);
|
||||
config.padding.height = (uint32_t)val;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!strcmp(cur_key, "bar_height")) {
|
||||
DLOG("bar_height = %lld\n", val);
|
||||
config.bar_height = (uint32_t)val;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcmp(cur_key, "tray_padding")) {
|
||||
DLOG("tray_padding = %lld\n", val);
|
||||
config.tray_padding = val;
|
||||
@@ -355,26 +395,23 @@ static int config_integer_cb(void *params_, long long val) {
|
||||
/* A datastructure to pass all these callbacks to yajl */
|
||||
static yajl_callbacks outputs_callbacks = {
|
||||
.yajl_null = config_null_cb,
|
||||
.yajl_boolean = config_boolean_cb,
|
||||
.yajl_integer = config_integer_cb,
|
||||
.yajl_boolean = config_boolean_cb,
|
||||
.yajl_string = config_string_cb,
|
||||
.yajl_end_array = config_end_array_cb,
|
||||
.yajl_map_key = config_map_key_cb,
|
||||
};
|
||||
|
||||
/*
|
||||
* Start parsing the received bar configuration JSON string
|
||||
* Parse the received bar configuration JSON string
|
||||
*
|
||||
*/
|
||||
void parse_config_json(char *json) {
|
||||
yajl_handle handle;
|
||||
yajl_status state;
|
||||
handle = yajl_alloc(&outputs_callbacks, NULL, NULL);
|
||||
|
||||
void parse_config_json(const unsigned char *json, size_t size) {
|
||||
TAILQ_INIT(&(config.bindings));
|
||||
TAILQ_INIT(&(config.tray_outputs));
|
||||
|
||||
state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
|
||||
yajl_handle handle = yajl_alloc(&outputs_callbacks, NULL, NULL);
|
||||
yajl_status state = yajl_parse(handle, json, size);
|
||||
|
||||
/* FIXME: Proper error handling for JSON parsing */
|
||||
switch (state) {
|
||||
@@ -387,6 +424,30 @@ void parse_config_json(char *json) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (config.disable_ws && config.workspace_command) {
|
||||
ELOG("You have specified 'workspace_buttons no'. Your 'workspace_command %s' will be ignored.\n", config.workspace_command);
|
||||
FREE(config.workspace_command);
|
||||
}
|
||||
|
||||
yajl_free(handle);
|
||||
}
|
||||
|
||||
static int i3bar_config_string_cb(void *params_, const unsigned char *val, size_t _len) {
|
||||
sasprintf(&config.bar_id, "%.*s", (int)_len, val);
|
||||
return 0; /* Stop parsing */
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse the received bar configuration list. The only usecase right now is to
|
||||
* automatically get the first bar id.
|
||||
*
|
||||
*/
|
||||
void parse_get_first_i3bar_config(const unsigned char *json, size_t size) {
|
||||
yajl_callbacks configs_callbacks = {
|
||||
.yajl_string = i3bar_config_string_cb,
|
||||
};
|
||||
yajl_handle handle = yajl_alloc(&configs_callbacks, NULL, NULL);
|
||||
yajl_parse(handle, json, size);
|
||||
yajl_free(handle);
|
||||
}
|
||||
|
||||
|
||||
132
i3bar/src/ipc.c
@@ -24,14 +24,24 @@ ev_io *i3_connection;
|
||||
|
||||
const char *sock_path;
|
||||
|
||||
typedef void (*handler_t)(char *);
|
||||
typedef void (*handler_t)(const unsigned char *, size_t);
|
||||
|
||||
/*
|
||||
* Returns true when i3bar is configured to read workspace information from i3
|
||||
* via JSON over the i3 IPC interface, as opposed to reading workspace
|
||||
* information from the workspace_command via JSON over stdout.
|
||||
*
|
||||
*/
|
||||
static bool i3_provides_workspaces(void) {
|
||||
return !config.disable_ws && config.workspace_command == NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Called, when we get a reply to a command from i3.
|
||||
* Since i3 does not give us much feedback on commands, we do not much
|
||||
*
|
||||
*/
|
||||
static void got_command_reply(char *reply) {
|
||||
static void got_command_reply(const unsigned char *reply, size_t size) {
|
||||
/* TODO: Error handling for command replies */
|
||||
}
|
||||
|
||||
@@ -39,9 +49,9 @@ static void got_command_reply(char *reply) {
|
||||
* Called, when we get a reply with workspaces data
|
||||
*
|
||||
*/
|
||||
static void got_workspace_reply(char *reply) {
|
||||
static void got_workspace_reply(const unsigned char *reply, size_t size) {
|
||||
DLOG("Got workspace data!\n");
|
||||
parse_workspaces_json(reply);
|
||||
parse_workspaces_json(reply, size);
|
||||
draw_bars(false);
|
||||
}
|
||||
|
||||
@@ -50,7 +60,7 @@ static void got_workspace_reply(char *reply) {
|
||||
* Since i3 does not give us much feedback on commands, we do not much
|
||||
*
|
||||
*/
|
||||
static void got_subscribe_reply(char *reply) {
|
||||
static void got_subscribe_reply(const unsigned char *reply, size_t size) {
|
||||
DLOG("Got subscribe reply: %s\n", reply);
|
||||
/* TODO: Error handling for subscribe commands */
|
||||
}
|
||||
@@ -59,12 +69,12 @@ static void got_subscribe_reply(char *reply) {
|
||||
* Called, when we get a reply with outputs data
|
||||
*
|
||||
*/
|
||||
static void got_output_reply(char *reply) {
|
||||
static void got_output_reply(const unsigned char *reply, size_t size) {
|
||||
DLOG("Clearing old output configuration...\n");
|
||||
free_outputs();
|
||||
|
||||
DLOG("Parsing outputs JSON...\n");
|
||||
parse_outputs_json(reply);
|
||||
parse_outputs_json(reply, size);
|
||||
DLOG("Reconfiguring windows...\n");
|
||||
reconfig_windows(false);
|
||||
|
||||
@@ -73,8 +83,19 @@ static void got_output_reply(char *reply) {
|
||||
kick_tray_clients(o_walk);
|
||||
}
|
||||
|
||||
if (!config.disable_ws) {
|
||||
if (i3_provides_workspaces()) {
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
|
||||
} else if (config.workspace_command) {
|
||||
/* Communication with the workspace child is one-way. Since we called
|
||||
* free_outputs() and free_workspaces() we have lost our workspace
|
||||
* information which will result in no workspace buttons. A
|
||||
* well-behaving client should be subscribed to output events as well
|
||||
* and re-send the output information to i3bar. Even in that case
|
||||
* though there is a race condition where the child can send the new
|
||||
* workspace information after the output change before i3bar receives
|
||||
* the output event from i3. For this reason, we re-parse the latest
|
||||
* received JSON. */
|
||||
repeat_last_ws_json();
|
||||
}
|
||||
|
||||
draw_bars(false);
|
||||
@@ -84,7 +105,21 @@ static void got_output_reply(char *reply) {
|
||||
* Called when we get the configuration for our bar instance
|
||||
*
|
||||
*/
|
||||
static void got_bar_config(char *reply) {
|
||||
static void got_bar_config(const unsigned char *reply, size_t size) {
|
||||
if (!config.bar_id) {
|
||||
DLOG("Received bar list \"%s\"\n", reply);
|
||||
parse_get_first_i3bar_config(reply, size);
|
||||
|
||||
if (!config.bar_id) {
|
||||
ELOG("No bar configuration found, please configure a bar block in your i3 config file.\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
LOG("Using first bar config: %s. Use --bar_id to manually select a different bar configuration.\n", config.bar_id);
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG, config.bar_id);
|
||||
return;
|
||||
}
|
||||
|
||||
DLOG("Received bar config \"%s\"\n", reply);
|
||||
/* We initiate the main function by requesting infos about the outputs and
|
||||
* workspaces. Everything else (creating the bars, showing the right workspace-
|
||||
@@ -92,13 +127,14 @@ static void got_bar_config(char *reply) {
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
|
||||
|
||||
free_colors(&(config.colors));
|
||||
parse_config_json(reply);
|
||||
parse_config_json(reply, size);
|
||||
|
||||
/* Now we can actually use 'config', so let's subscribe to the appropriate
|
||||
* events and request the workspaces if necessary. */
|
||||
subscribe_events();
|
||||
if (!config.disable_ws)
|
||||
if (i3_provides_workspaces()) {
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
|
||||
}
|
||||
|
||||
/* Initialize the rest of XCB */
|
||||
init_xcb_late(config.fontname);
|
||||
@@ -107,6 +143,7 @@ static void got_bar_config(char *reply) {
|
||||
init_colors(&(config.colors));
|
||||
|
||||
start_child(config.command);
|
||||
start_ws_child(config.workspace_command);
|
||||
}
|
||||
|
||||
/* Data structure to easily call the reply handlers later */
|
||||
@@ -129,7 +166,7 @@ handler_t reply_handlers[] = {
|
||||
* Called, when a workspace event arrives (i.e. the user changed the workspace)
|
||||
*
|
||||
*/
|
||||
static void got_workspace_event(char *event) {
|
||||
static void got_workspace_event(const unsigned char *event, size_t size) {
|
||||
DLOG("Got workspace event!\n");
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
|
||||
}
|
||||
@@ -138,36 +175,46 @@ static void got_workspace_event(char *event) {
|
||||
* Called, when an output event arrives (i.e. the screen configuration changed)
|
||||
*
|
||||
*/
|
||||
static void got_output_event(char *event) {
|
||||
static void got_output_event(const unsigned char *event, size_t size) {
|
||||
DLOG("Got output event!\n");
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
|
||||
if (!config.disable_ws) {
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Called, when a mode event arrives (i3 changed binding mode).
|
||||
*
|
||||
*/
|
||||
static void got_mode_event(char *event) {
|
||||
static void got_mode_event(const unsigned char *event, size_t size) {
|
||||
DLOG("Got mode event!\n");
|
||||
parse_mode_json(event);
|
||||
parse_mode_json(event, size);
|
||||
draw_bars(false);
|
||||
}
|
||||
|
||||
static bool strings_differ(char *a, char *b) {
|
||||
const bool a_null = (a == NULL);
|
||||
const bool b_null = (b == NULL);
|
||||
if (a_null != b_null) {
|
||||
return true;
|
||||
}
|
||||
if (a_null && b_null) {
|
||||
return false;
|
||||
}
|
||||
return strcmp(a, b) != 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Called, when a barconfig_update event arrives (i.e. i3 changed the bar hidden_state or mode)
|
||||
*
|
||||
*/
|
||||
static void got_bar_config_update(char *event) {
|
||||
static void got_bar_config_update(const unsigned char *event, size_t size) {
|
||||
/* check whether this affect this bar instance by checking the bar_id */
|
||||
char *expected_id;
|
||||
sasprintf(&expected_id, "\"id\":\"%s\"", config.bar_id);
|
||||
char *found_id = strstr(event, expected_id);
|
||||
char *found_id = strstr((const char *)event, expected_id);
|
||||
FREE(expected_id);
|
||||
if (found_id == NULL)
|
||||
if (found_id == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* reconfigure the bar based on the current outputs */
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_OUTPUTS, NULL);
|
||||
@@ -176,9 +223,14 @@ static void got_bar_config_update(char *event) {
|
||||
|
||||
/* update the configuration with the received settings */
|
||||
DLOG("Received bar config update \"%s\"\n", event);
|
||||
char *old_command = config.command ? sstrdup(config.command) : NULL;
|
||||
|
||||
char *old_command = config.command;
|
||||
char *old_workspace_command = config.workspace_command;
|
||||
config.command = NULL;
|
||||
config.workspace_command = NULL;
|
||||
bar_display_mode_t old_mode = config.hide_on_modifier;
|
||||
parse_config_json(event);
|
||||
|
||||
parse_config_json(event, size);
|
||||
if (old_mode != config.hide_on_modifier) {
|
||||
reconfig_windows(true);
|
||||
}
|
||||
@@ -188,12 +240,21 @@ static void got_bar_config_update(char *event) {
|
||||
init_colors(&(config.colors));
|
||||
|
||||
/* restart status command process */
|
||||
if (old_command && strcmp(old_command, config.command) != 0) {
|
||||
if (!status_child_is_alive() || strings_differ(old_command, config.command)) {
|
||||
kill_child();
|
||||
clear_statusline(&statusline_head, true);
|
||||
start_child(config.command);
|
||||
}
|
||||
free(old_command);
|
||||
|
||||
/* restart workspace command process */
|
||||
if (!ws_child_is_alive() || strings_differ(old_workspace_command, config.workspace_command)) {
|
||||
free_workspaces();
|
||||
kill_ws_child();
|
||||
start_ws_child(config.workspace_command);
|
||||
}
|
||||
free(old_workspace_command);
|
||||
|
||||
draw_bars(false);
|
||||
}
|
||||
|
||||
@@ -257,7 +318,7 @@ static void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
|
||||
|
||||
/* Now that we know, what to expect, we can start read()ing the rest
|
||||
* of the message */
|
||||
char *buffer = smalloc(size + 1);
|
||||
unsigned char *buffer = smalloc(size + 1);
|
||||
rec = 0;
|
||||
|
||||
while (rec < size) {
|
||||
@@ -277,10 +338,11 @@ static void got_data(struct ev_loop *loop, ev_io *watcher, int events) {
|
||||
/* And call the callback (indexed by the type) */
|
||||
if (type & (1UL << 31)) {
|
||||
type ^= 1UL << 31;
|
||||
event_handlers[type](buffer);
|
||||
event_handlers[type](buffer, size);
|
||||
} else {
|
||||
if (reply_handlers[type])
|
||||
reply_handlers[type](buffer);
|
||||
if (reply_handlers[type]) {
|
||||
reply_handlers[type](buffer, size);
|
||||
}
|
||||
}
|
||||
|
||||
FREE(header);
|
||||
@@ -313,8 +375,9 @@ int i3_send_msg(uint32_t type, const char *payload) {
|
||||
memcpy(walk, &type, sizeof(uint32_t));
|
||||
walk += sizeof(uint32_t);
|
||||
|
||||
if (payload != NULL)
|
||||
strncpy(walk, payload, len);
|
||||
if (payload != NULL) {
|
||||
memcpy(walk, payload, len);
|
||||
}
|
||||
|
||||
swrite(i3_connection->fd, buffer, to_write);
|
||||
|
||||
@@ -328,13 +391,12 @@ int i3_send_msg(uint32_t type, const char *payload) {
|
||||
* socket_path must be a valid path to the ipc_socket of i3
|
||||
*
|
||||
*/
|
||||
int init_connection(const char *socket_path) {
|
||||
void init_connection(const char *socket_path) {
|
||||
sock_path = socket_path;
|
||||
int sockfd = ipc_connect(socket_path);
|
||||
i3_connection = smalloc(sizeof(ev_io));
|
||||
ev_io_init(i3_connection, &got_data, sockfd, EV_READ);
|
||||
ev_io_start(main_loop, i3_connection);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -350,9 +412,9 @@ void destroy_connection(void) {
|
||||
*
|
||||
*/
|
||||
void subscribe_events(void) {
|
||||
if (config.disable_ws) {
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"output\", \"mode\", \"barconfig_update\" ]");
|
||||
} else {
|
||||
if (i3_provides_workspaces()) {
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"workspace\", \"output\", \"mode\", \"barconfig_update\" ]");
|
||||
} else {
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[ \"output\", \"mode\", \"barconfig_update\" ]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,9 +56,9 @@ static char *expand_path(char *path) {
|
||||
}
|
||||
|
||||
static void print_usage(char *elf_name) {
|
||||
printf("Usage: %s -b bar_id [-s sock_path] [-t] [-h] [-v]\n", elf_name);
|
||||
printf("Usage: %s [-b bar_id] [-s sock_path] [-t] [-h] [-v] [-V]\n", elf_name);
|
||||
printf("\n");
|
||||
printf("-b, --bar_id <bar_id>\tBar ID for which to get the configuration\n");
|
||||
printf("-b, --bar_id <bar_id>\tBar ID for which to get the configuration, defaults to the first bar from the i3 config\n");
|
||||
printf("-s, --socket <sock_path>\tConnect to i3 via <sock_path>\n");
|
||||
printf("-t, --transparency Enable transparency (RGBA colors)\n");
|
||||
printf("-h, --help Display this help message and exit\n");
|
||||
@@ -128,17 +128,12 @@ int main(int argc, char **argv) {
|
||||
break;
|
||||
default:
|
||||
print_usage(argv[0]);
|
||||
exit(EXIT_SUCCESS);
|
||||
exit(EXIT_FAILURE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.bar_id) {
|
||||
/* TODO: maybe we want -f which will automatically ask i3 for the first
|
||||
* configured bar (and error out if there are too many)? */
|
||||
ELOG("No bar_id passed. Please let i3 start i3bar or specify --bar_id\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
LOG("i3bar version " I3_VERSION "\n");
|
||||
|
||||
main_loop = ev_default_loop(0); /* needed in init_xcb_early */
|
||||
char *atom_sock_path = init_xcb_early();
|
||||
@@ -166,10 +161,13 @@ int main(int argc, char **argv) {
|
||||
init_dpi();
|
||||
|
||||
init_outputs();
|
||||
if (init_connection(socket_path)) {
|
||||
/* Request the bar configuration. When it arrives, we fill the config array. */
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG, config.bar_id);
|
||||
}
|
||||
|
||||
init_connection(socket_path);
|
||||
/* Request the bar configuration. When it arrives, we fill the config
|
||||
* array. In case that config.bar_id is empty, we will receive a list of
|
||||
* available configs and then request the configuration for the first bar.
|
||||
* See got_bar_config for more. */
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_GET_BAR_CONFIG, config.bar_id);
|
||||
free(socket_path);
|
||||
|
||||
/* We listen to SIGTERM/QUIT/INT and try to exit cleanly, by stopping the main loop.
|
||||
@@ -187,12 +185,12 @@ int main(int argc, char **argv) {
|
||||
ev_signal_start(main_loop, sig_int);
|
||||
ev_signal_start(main_loop, sig_hup);
|
||||
|
||||
atexit(kill_children_at_exit);
|
||||
|
||||
/* From here on everything should run smooth for itself, just start listening for
|
||||
* events. We stop simply stop the event loop, when we are finished */
|
||||
ev_loop(main_loop, 0);
|
||||
|
||||
kill_child();
|
||||
|
||||
clean_xcb();
|
||||
ev_default_destroy();
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
/* A datatype to pass through the callbacks to save the state */
|
||||
struct mode_json_params {
|
||||
char *json;
|
||||
char *cur_key;
|
||||
char *name;
|
||||
bool pango_markup;
|
||||
@@ -96,26 +95,17 @@ static yajl_callbacks mode_callbacks = {
|
||||
};
|
||||
|
||||
/*
|
||||
* Start parsing the received JSON string
|
||||
* Parse the received JSON string
|
||||
*
|
||||
*/
|
||||
void parse_mode_json(char *json) {
|
||||
/* FIXME: Fasciliate stream processing, i.e. allow starting to interpret
|
||||
* JSON in chunks */
|
||||
void parse_mode_json(const unsigned char *json, size_t size) {
|
||||
struct mode_json_params params;
|
||||
|
||||
mode binding;
|
||||
|
||||
params.cur_key = NULL;
|
||||
params.json = json;
|
||||
params.mode = &binding;
|
||||
|
||||
yajl_handle handle;
|
||||
yajl_status state;
|
||||
|
||||
handle = yajl_alloc(&mode_callbacks, NULL, (void *)¶ms);
|
||||
|
||||
state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
|
||||
yajl_handle handle = yajl_alloc(&mode_callbacks, NULL, (void *)¶ms);
|
||||
yajl_status state = yajl_parse(handle, json, size);
|
||||
|
||||
/* FIXME: Proper error handling for JSON parsing */
|
||||
switch (state) {
|
||||
@@ -129,8 +119,9 @@ void parse_mode_json(char *json) {
|
||||
}
|
||||
|
||||
/* We don't want to indicate default binding mode */
|
||||
if (strcmp("default", i3string_as_utf8(params.mode->name)) == 0)
|
||||
if (strcmp("default", i3string_as_utf8(params.mode->name)) == 0) {
|
||||
I3STRING_FREE(params.mode->name);
|
||||
}
|
||||
|
||||
/* Set the new binding mode */
|
||||
set_current_mode(&binding);
|
||||
|
||||
@@ -18,10 +18,8 @@
|
||||
|
||||
/* A datatype to pass through the callbacks to save the state */
|
||||
struct outputs_json_params {
|
||||
struct outputs_head *outputs;
|
||||
i3_output *outputs_walk;
|
||||
char *cur_key;
|
||||
char *json;
|
||||
bool in_rect;
|
||||
};
|
||||
|
||||
@@ -114,8 +112,9 @@ static int outputs_string_cb(void *params_, const unsigned char *val, size_t len
|
||||
errno = 0;
|
||||
long parsed_num = strtol(copy, &end, 10);
|
||||
if (errno == 0 &&
|
||||
(end && *end == '\0'))
|
||||
(end && *end == '\0')) {
|
||||
params->outputs_walk->ws = parsed_num;
|
||||
}
|
||||
|
||||
FREE(copy);
|
||||
FREE(params->cur_key);
|
||||
@@ -148,7 +147,6 @@ static int outputs_start_map_cb(void *params_) {
|
||||
new_output->visible = false;
|
||||
new_output->ws = 0,
|
||||
new_output->statusline_width = 0;
|
||||
new_output->statusline_short_text = false;
|
||||
memset(&new_output->rect, 0, sizeof(rect));
|
||||
memset(&new_output->bar, 0, sizeof(surface_t));
|
||||
memset(&new_output->buffer, 0, sizeof(surface_t));
|
||||
@@ -263,21 +261,17 @@ void init_outputs(void) {
|
||||
}
|
||||
|
||||
/*
|
||||
* Start parsing the received JSON string
|
||||
* Parse the received JSON string
|
||||
*
|
||||
*/
|
||||
void parse_outputs_json(char *json) {
|
||||
void parse_outputs_json(const unsigned char *json, size_t size) {
|
||||
struct outputs_json_params params;
|
||||
params.outputs_walk = NULL;
|
||||
params.cur_key = NULL;
|
||||
params.json = json;
|
||||
params.in_rect = false;
|
||||
|
||||
yajl_handle handle;
|
||||
yajl_status state;
|
||||
handle = yajl_alloc(&outputs_callbacks, NULL, (void *)¶ms);
|
||||
|
||||
state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
|
||||
yajl_handle handle = yajl_alloc(&outputs_callbacks, NULL, (void *)¶ms);
|
||||
yajl_status state = yajl_parse(handle, json, size);
|
||||
|
||||
/* FIXME: Proper errorhandling for JSON-parsing */
|
||||
switch (state) {
|
||||
@@ -291,6 +285,7 @@ void parse_outputs_json(char *json) {
|
||||
}
|
||||
|
||||
yajl_free(handle);
|
||||
free(params.cur_key);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -319,12 +314,14 @@ void free_outputs(void) {
|
||||
*
|
||||
*/
|
||||
i3_output *get_output_by_name(char *name) {
|
||||
i3_output *walk;
|
||||
if (name == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
const bool is_primary = !strcasecmp(name, "primary");
|
||||
|
||||
i3_output *walk;
|
||||
SLIST_FOREACH (walk, outputs, slist) {
|
||||
if (!strcmp(walk->name, name)) {
|
||||
if ((is_primary && walk->primary) || !strcmp(walk->name, name)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,11 +106,13 @@ void parse_json_header(i3bar_child *child, const unsigned char *buffer, int leng
|
||||
yajl_status state = yajl_parse(handle, buffer, length);
|
||||
if (state != yajl_status_ok) {
|
||||
child_init(child);
|
||||
if (consumed != NULL)
|
||||
if (consumed != NULL) {
|
||||
*consumed = 0;
|
||||
}
|
||||
} else {
|
||||
if (consumed != NULL)
|
||||
if (consumed != NULL) {
|
||||
*consumed = yajl_get_bytes_consumed(handle);
|
||||
}
|
||||
}
|
||||
|
||||
yajl_free(handle);
|
||||
|
||||
@@ -19,7 +19,8 @@ struct workspaces_json_params {
|
||||
struct ws_head *workspaces;
|
||||
i3_ws *workspaces_walk;
|
||||
char *cur_key;
|
||||
char *json;
|
||||
bool need_output;
|
||||
bool parsing_rect;
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -71,26 +72,23 @@ static int workspaces_integer_cb(void *params_, long long val) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* rect is unused, so we don't bother to save it */
|
||||
if (!strcmp(params->cur_key, "x")) {
|
||||
params->workspaces_walk->rect.x = (int)val;
|
||||
FREE(params->cur_key);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcmp(params->cur_key, "y")) {
|
||||
params->workspaces_walk->rect.y = (int)val;
|
||||
FREE(params->cur_key);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcmp(params->cur_key, "width")) {
|
||||
params->workspaces_walk->rect.w = (int)val;
|
||||
FREE(params->cur_key);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!strcmp(params->cur_key, "height")) {
|
||||
params->workspaces_walk->rect.h = (int)val;
|
||||
FREE(params->cur_key);
|
||||
return 1;
|
||||
}
|
||||
@@ -120,8 +118,9 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t
|
||||
size_t offset = strspn(ws_name, ws_num);
|
||||
|
||||
/* Also strip off the conventional ws name delimiter */
|
||||
if (offset && ws_name[offset] == ':')
|
||||
if (offset && ws_name[offset] == ':') {
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
if (config.strip_ws_numbers) {
|
||||
/* Offset may be equal to length, in which case display the number */
|
||||
@@ -156,15 +155,16 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t
|
||||
sasprintf(&output_name, "%.*s", len, val);
|
||||
|
||||
i3_output *target = get_output_by_name(output_name);
|
||||
i3_ws *ws = params->workspaces_walk;
|
||||
if (target != NULL) {
|
||||
params->workspaces_walk->output = target;
|
||||
|
||||
TAILQ_INSERT_TAIL(params->workspaces_walk->output->workspaces,
|
||||
params->workspaces_walk,
|
||||
tailq);
|
||||
ws->output = target;
|
||||
TAILQ_INSERT_TAIL(ws->output->workspaces, ws, tailq);
|
||||
}
|
||||
|
||||
params->need_output = false;
|
||||
FREE(output_name);
|
||||
FREE(params->cur_key);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -172,28 +172,42 @@ static int workspaces_string_cb(void *params_, const unsigned char *val, size_t
|
||||
}
|
||||
|
||||
/*
|
||||
* We hit the start of a JSON map (rect or a new output)
|
||||
* We hit the start of a JSON map (rect or a new workspace)
|
||||
*
|
||||
*/
|
||||
static int workspaces_start_map_cb(void *params_) {
|
||||
struct workspaces_json_params *params = (struct workspaces_json_params *)params_;
|
||||
|
||||
i3_ws *new_workspace = NULL;
|
||||
|
||||
if (params->cur_key == NULL) {
|
||||
new_workspace = smalloc(sizeof(i3_ws));
|
||||
i3_ws *new_workspace = scalloc(1, sizeof(i3_ws));
|
||||
new_workspace->num = -1;
|
||||
new_workspace->name = NULL;
|
||||
new_workspace->visible = 0;
|
||||
new_workspace->focused = 0;
|
||||
new_workspace->urgent = 0;
|
||||
memset(&new_workspace->rect, 0, sizeof(rect));
|
||||
new_workspace->output = NULL;
|
||||
|
||||
params->workspaces_walk = new_workspace;
|
||||
params->need_output = true;
|
||||
params->parsing_rect = false;
|
||||
} else {
|
||||
params->parsing_rect = true;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int workspaces_end_map_cb(void *params_) {
|
||||
struct workspaces_json_params *params = (struct workspaces_json_params *)params_;
|
||||
i3_ws *ws = params->workspaces_walk;
|
||||
const bool parsing_rect = params->parsing_rect;
|
||||
params->parsing_rect = false;
|
||||
|
||||
if (parsing_rect || !ws || !ws->name || !params->need_output) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
ws->output = get_output_by_name("primary");
|
||||
if (ws->output == NULL) {
|
||||
ws->output = SLIST_FIRST(outputs);
|
||||
}
|
||||
TAILQ_INSERT_TAIL(ws->output->workspaces, ws, tailq);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -216,43 +230,42 @@ static yajl_callbacks workspaces_callbacks = {
|
||||
.yajl_integer = workspaces_integer_cb,
|
||||
.yajl_string = workspaces_string_cb,
|
||||
.yajl_start_map = workspaces_start_map_cb,
|
||||
.yajl_end_map = workspaces_end_map_cb,
|
||||
.yajl_map_key = workspaces_map_key_cb,
|
||||
};
|
||||
|
||||
/*
|
||||
* Start parsing the received JSON string
|
||||
* Parse the received JSON string
|
||||
*
|
||||
*/
|
||||
void parse_workspaces_json(char *json) {
|
||||
/* FIXME: Fasciliate stream processing, i.e. allow starting to interpret
|
||||
* JSON in chunks */
|
||||
struct workspaces_json_params params;
|
||||
|
||||
void parse_workspaces_json(const unsigned char *json, size_t size) {
|
||||
free_workspaces();
|
||||
|
||||
params.workspaces_walk = NULL;
|
||||
params.cur_key = NULL;
|
||||
params.json = json;
|
||||
|
||||
yajl_handle handle;
|
||||
yajl_status state;
|
||||
handle = yajl_alloc(&workspaces_callbacks, NULL, (void *)¶ms);
|
||||
|
||||
state = yajl_parse(handle, (const unsigned char *)json, strlen(json));
|
||||
struct workspaces_json_params params = {0};
|
||||
yajl_handle handle = yajl_alloc(&workspaces_callbacks, NULL, (void *)¶ms);
|
||||
yajl_status state = yajl_parse(handle, json, size);
|
||||
|
||||
/* FIXME: Proper error handling for JSON parsing */
|
||||
switch (state) {
|
||||
case yajl_status_ok:
|
||||
break;
|
||||
case yajl_status_client_canceled:
|
||||
case yajl_status_error:
|
||||
ELOG("Could not parse workspaces reply!\n");
|
||||
exit(EXIT_FAILURE);
|
||||
case yajl_status_error: {
|
||||
unsigned char *err = yajl_get_error(handle, 1, json, size);
|
||||
ELOG("Could not parse workspaces reply, error:\n%s\njson:---%s---\n", err, json);
|
||||
yajl_free_error(handle, err);
|
||||
|
||||
if (config.workspace_command) {
|
||||
kill_ws_child();
|
||||
set_workspace_button_error("Could not parse workspace_command's JSON");
|
||||
} else {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
yajl_free(handle);
|
||||
|
||||
FREE(params.cur_key);
|
||||
}
|
||||
|
||||
@@ -261,14 +274,14 @@ void parse_workspaces_json(char *json) {
|
||||
*
|
||||
*/
|
||||
void free_workspaces(void) {
|
||||
i3_output *outputs_walk;
|
||||
if (outputs == NULL) {
|
||||
return;
|
||||
}
|
||||
i3_ws *ws_walk;
|
||||
|
||||
i3_output *outputs_walk;
|
||||
SLIST_FOREACH (outputs_walk, outputs, slist) {
|
||||
if (outputs_walk->workspaces != NULL && !TAILQ_EMPTY(outputs_walk->workspaces)) {
|
||||
i3_ws *ws_walk;
|
||||
TAILQ_FOREACH (ws_walk, outputs_walk->workspaces, tailq) {
|
||||
I3STRING_FREE(ws_walk->name);
|
||||
FREE(ws_walk->canonical_name);
|
||||
|
||||
374
i3bar/src/xcb.c
@@ -12,6 +12,7 @@
|
||||
#include <err.h>
|
||||
#include <ev.h>
|
||||
#include <i3/ipc.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -59,7 +60,7 @@ xcb_visualtype_t *visual_type;
|
||||
uint8_t depth;
|
||||
xcb_colormap_t colormap;
|
||||
|
||||
/* Overall height of the bar (based on font size) */
|
||||
/* Overall height of the bar */
|
||||
int bar_height;
|
||||
|
||||
/* These are only relevant for XKB, which we only need for grabbing modifiers */
|
||||
@@ -138,8 +139,9 @@ int _xcb_request_failed(xcb_void_cookie_t cookie, char *err_msg, int line) {
|
||||
}
|
||||
|
||||
static uint32_t get_sep_offset(struct status_block *block) {
|
||||
if (!block->no_separator && block->sep_block_width > 0)
|
||||
if (!block->no_separator && block->sep_block_width > 0) {
|
||||
return block->sep_block_width / 2 + block->sep_block_width % 2;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -147,12 +149,14 @@ static int get_tray_width(struct tc_head *trayclients) {
|
||||
trayclient *trayclient;
|
||||
int tray_width = 0;
|
||||
TAILQ_FOREACH_REVERSE (trayclient, trayclients, tc_head, tailq) {
|
||||
if (!trayclient->mapped)
|
||||
if (!trayclient->mapped) {
|
||||
continue;
|
||||
}
|
||||
tray_width += icon_size + logical_px(config.tray_padding);
|
||||
}
|
||||
if (tray_width > 0)
|
||||
if (tray_width > 0) {
|
||||
tray_width += logical_px(tray_loff_px);
|
||||
}
|
||||
return tray_width;
|
||||
}
|
||||
|
||||
@@ -165,8 +169,9 @@ static void draw_separator(i3_output *output, uint32_t x, struct status_block *b
|
||||
color_t bar_bg = (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg);
|
||||
|
||||
uint32_t sep_offset = get_sep_offset(block);
|
||||
if (TAILQ_NEXT(block, blocks) == NULL || sep_offset == 0)
|
||||
if (TAILQ_NEXT(block, blocks) == NULL || sep_offset == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t center_x = x - sep_offset;
|
||||
if (config.separator_symbol == NULL) {
|
||||
@@ -180,54 +185,107 @@ static void draw_separator(i3_output *output, uint32_t x, struct status_block *b
|
||||
/* Draw a custom separator. */
|
||||
uint32_t separator_x = MAX(x - block->sep_block_width, center_x - separator_symbol_width / 2);
|
||||
draw_util_text(config.separator_symbol, &output->statusline_buffer, sep_fg, bar_bg,
|
||||
separator_x, logical_px(ws_voff_px), x - separator_x);
|
||||
separator_x, bar_height / 2 - font.height / 2, x - separator_x);
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t predict_statusline_length(bool use_short_text) {
|
||||
static void predict_block_length(struct status_block *block) {
|
||||
i3String *text = block->full_text;
|
||||
struct status_block_render_desc *render = &block->full_render;
|
||||
if (block->use_short && block->short_text != NULL) {
|
||||
text = block->short_text;
|
||||
render = &block->short_render;
|
||||
}
|
||||
|
||||
if (i3string_get_num_bytes(text) == 0) {
|
||||
block->render_length = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
render->width = predict_text_width(text);
|
||||
if (block->border) {
|
||||
render->width += logical_px(block->border_left + block->border_right);
|
||||
}
|
||||
|
||||
/* Compute offset and append for text alignment in min_width. */
|
||||
if (block->min_width <= render->width) {
|
||||
render->x_offset = 0;
|
||||
render->x_append = 0;
|
||||
} else {
|
||||
uint32_t padding_width = block->min_width - render->width;
|
||||
switch (block->align) {
|
||||
case ALIGN_LEFT:
|
||||
render->x_append = padding_width;
|
||||
break;
|
||||
case ALIGN_RIGHT:
|
||||
render->x_offset = padding_width;
|
||||
break;
|
||||
case ALIGN_CENTER:
|
||||
render->x_offset = padding_width / 2;
|
||||
render->x_append = padding_width / 2 + padding_width % 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
block->render_length = render->width + render->x_offset + render->x_append;
|
||||
}
|
||||
|
||||
static uint32_t predict_statusline_length(void) {
|
||||
uint32_t width = 0;
|
||||
struct status_block *block;
|
||||
|
||||
TAILQ_FOREACH (block, &statusline_head, blocks) {
|
||||
i3String *text = block->full_text;
|
||||
struct status_block_render_desc *render = &block->full_render;
|
||||
if (use_short_text && block->short_text != NULL) {
|
||||
text = block->short_text;
|
||||
render = &block->short_render;
|
||||
}
|
||||
|
||||
if (i3string_get_num_bytes(text) == 0)
|
||||
predict_block_length(block);
|
||||
uint32_t block_width = block->render_length;
|
||||
if (block_width == 0) {
|
||||
continue;
|
||||
|
||||
render->width = predict_text_width(text);
|
||||
if (block->border)
|
||||
render->width += logical_px(block->border_left + block->border_right);
|
||||
|
||||
/* Compute offset and append for text aligment in min_width. */
|
||||
if (block->min_width <= render->width) {
|
||||
render->x_offset = 0;
|
||||
render->x_append = 0;
|
||||
} else {
|
||||
uint32_t padding_width = block->min_width - render->width;
|
||||
switch (block->align) {
|
||||
case ALIGN_LEFT:
|
||||
render->x_append = padding_width;
|
||||
break;
|
||||
case ALIGN_RIGHT:
|
||||
render->x_offset = padding_width;
|
||||
break;
|
||||
case ALIGN_CENTER:
|
||||
render->x_offset = padding_width / 2;
|
||||
render->x_append = padding_width / 2 + padding_width % 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
width += render->width + render->x_offset + render->x_append;
|
||||
width += block_width;
|
||||
|
||||
/* If this is not the last block, add some pixels for a separator. */
|
||||
if (TAILQ_NEXT(block, blocks) != NULL)
|
||||
if (TAILQ_NEXT(block, blocks) != NULL) {
|
||||
width += block->sep_block_width;
|
||||
}
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
static uint32_t switch_block_to_short(struct status_block *block) {
|
||||
/* Skip blocks that have no short form or are already in short form */
|
||||
if (block->short_text == NULL || block->use_short) {
|
||||
return 0;
|
||||
}
|
||||
uint32_t full = block->render_length;
|
||||
block->use_short = true;
|
||||
predict_block_length(block);
|
||||
return full - block->render_length;
|
||||
}
|
||||
|
||||
static uint32_t adjust_statusline_length(uint32_t max_length) {
|
||||
uint32_t width = predict_statusline_length();
|
||||
|
||||
/* Progressively switch the blocks to short mode */
|
||||
struct status_block *block;
|
||||
TAILQ_FOREACH (block, &statusline_head, blocks) {
|
||||
if (width < max_length) {
|
||||
break;
|
||||
}
|
||||
width -= switch_block_to_short(block);
|
||||
|
||||
/* Provide support for representing a single logical block using multiple
|
||||
* JSON blocks: if one block is shortened, ensure that all other blocks
|
||||
* with the same name are also shortened such that the entire logical block uses
|
||||
* the short form text. */
|
||||
if (block->name) {
|
||||
struct status_block *other;
|
||||
TAILQ_FOREACH (other, &statusline_head, blocks) {
|
||||
if (other->name && !strcmp(other->name, block->name)) {
|
||||
width -= switch_block_to_short(other);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return width;
|
||||
@@ -236,7 +294,7 @@ static uint32_t predict_statusline_length(bool use_short_text) {
|
||||
/*
|
||||
* Redraws the statusline to the output's statusline_buffer
|
||||
*/
|
||||
static void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_colors, bool use_short_text) {
|
||||
static void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focus_colors) {
|
||||
struct status_block *block;
|
||||
|
||||
color_t bar_color = (use_focus_colors ? colors.focus_bar_bg : colors.bar_bg);
|
||||
@@ -254,13 +312,14 @@ static void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focu
|
||||
TAILQ_FOREACH (block, &statusline_head, blocks) {
|
||||
i3String *text = block->full_text;
|
||||
struct status_block_render_desc *render = &block->full_render;
|
||||
if (use_short_text && block->short_text != NULL) {
|
||||
if (block->use_short && block->short_text != NULL) {
|
||||
text = block->short_text;
|
||||
render = &block->short_render;
|
||||
}
|
||||
|
||||
if (i3string_get_num_bytes(text) == 0)
|
||||
if (i3string_get_num_bytes(text) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
color_t fg_color;
|
||||
if (block->urgent) {
|
||||
@@ -284,10 +343,12 @@ static void draw_statusline(i3_output *output, uint32_t clip_left, bool use_focu
|
||||
border_color = colors.urgent_ws_border;
|
||||
bg_color = colors.urgent_ws_bg;
|
||||
} else {
|
||||
if (block->border)
|
||||
if (block->border) {
|
||||
border_color = draw_util_hex_to_color(block->border);
|
||||
if (block->background)
|
||||
}
|
||||
if (block->background) {
|
||||
bg_color = draw_util_hex_to_color(block->background);
|
||||
}
|
||||
}
|
||||
|
||||
/* Draw the border. */
|
||||
@@ -334,7 +395,7 @@ static void hide_bars(void) {
|
||||
}
|
||||
xcb_unmap_window(xcb_connection, walk->bar.id);
|
||||
}
|
||||
stop_child();
|
||||
stop_children();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -351,7 +412,7 @@ static void unhide_bars(void) {
|
||||
uint32_t mask;
|
||||
uint32_t values[5];
|
||||
|
||||
cont_child();
|
||||
cont_children();
|
||||
|
||||
SLIST_FOREACH (walk, outputs, slist) {
|
||||
if (walk->bar.id == XCB_NONE) {
|
||||
@@ -363,10 +424,11 @@ static void unhide_bars(void) {
|
||||
XCB_CONFIG_WINDOW_HEIGHT |
|
||||
XCB_CONFIG_WINDOW_STACK_MODE;
|
||||
values[0] = walk->rect.x;
|
||||
if (config.position == POS_TOP)
|
||||
if (config.position == POS_TOP) {
|
||||
values[1] = walk->rect.y;
|
||||
else
|
||||
} else {
|
||||
values[1] = walk->rect.y + walk->rect.h - bar_height;
|
||||
}
|
||||
values[2] = walk->rect.w;
|
||||
values[3] = bar_height;
|
||||
values[4] = XCB_STACK_MODE_ABOVE;
|
||||
@@ -434,8 +496,9 @@ void init_colors(const struct xcb_color_strings_t *new_colors) {
|
||||
static bool execute_custom_command(xcb_keycode_t input_code, bool event_is_release) {
|
||||
binding_t *binding;
|
||||
TAILQ_FOREACH (binding, &(config.bindings), bindings) {
|
||||
if ((binding->input_code != input_code) || (binding->release != event_is_release))
|
||||
if ((binding->input_code != input_code) || (binding->release != event_is_release)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, binding->command);
|
||||
return true;
|
||||
@@ -454,7 +517,7 @@ static void child_handle_button(xcb_button_press_event_t *event, i3_output *outp
|
||||
TAILQ_FOREACH (block, &statusline_head, blocks) {
|
||||
i3String *text;
|
||||
struct status_block_render_desc *render;
|
||||
if (output->statusline_short_text && block->short_text != NULL) {
|
||||
if (block->use_short && block->short_text != NULL) {
|
||||
text = block->short_text;
|
||||
render = &block->short_render;
|
||||
} else {
|
||||
@@ -500,6 +563,49 @@ static int predict_button_width(int name_width) {
|
||||
logical_px(config.ws_min_width));
|
||||
}
|
||||
|
||||
static char *quote_workspace_name(const char *in) {
|
||||
/* To properly handle workspace names with double quotes in them, we need
|
||||
* to escape the double quotes. We allocate a large enough buffer (twice
|
||||
* the unescaped size is always enough), then we copy character by
|
||||
* character. */
|
||||
const size_t namelen = strlen(in);
|
||||
const size_t len = namelen + strlen("workspace \"\"") + 1;
|
||||
char *out = scalloc(2 * len, 1);
|
||||
memcpy(out, "workspace \"", strlen("workspace \""));
|
||||
size_t inpos, outpos;
|
||||
for (inpos = 0, outpos = strlen("workspace \"");
|
||||
inpos < namelen;
|
||||
inpos++, outpos++) {
|
||||
if (in[inpos] == '"' || in[inpos] == '\\') {
|
||||
out[outpos] = '\\';
|
||||
outpos++;
|
||||
}
|
||||
out[outpos] = in[inpos];
|
||||
}
|
||||
out[outpos] = '"';
|
||||
return out;
|
||||
}
|
||||
|
||||
static void focus_workspace(i3_ws *ws) {
|
||||
char *buffer = NULL;
|
||||
if (ws->id != 0) {
|
||||
/* Workspace ID has higher precedence since the workspace_command is
|
||||
* allowed to change workspace names as long as it provides a valid ID. */
|
||||
sasprintf(&buffer, "[con_id=%lld] focus workspace", ws->id);
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (ws->canonical_name == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
buffer = quote_workspace_name(ws->canonical_name);
|
||||
|
||||
done:
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, buffer);
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle a button press event (i.e. a mouse click on one of our bars).
|
||||
* We determine, whether the click occurred on a workspace button or if the scroll-
|
||||
@@ -526,19 +632,27 @@ static void handle_button(xcb_button_press_event_t *event) {
|
||||
/* During button release events, only check for custom commands. */
|
||||
const bool event_is_release = (event->response_type & ~0x80) == XCB_BUTTON_RELEASE;
|
||||
|
||||
int32_t x = event->event_x >= 0 ? event->event_x : 0;
|
||||
const int x = (event->event_x >= 0 ? event->event_x : 0) - logical_px(config.padding.x);
|
||||
if (x < 0) {
|
||||
/* Ignore clicks in padding */
|
||||
return;
|
||||
}
|
||||
|
||||
int workspace_width = 0;
|
||||
i3_ws *cur_ws = NULL, *clicked_ws = NULL, *ws_walk;
|
||||
|
||||
TAILQ_FOREACH (ws_walk, walk->workspaces, tailq) {
|
||||
int w = predict_button_width(ws_walk->name_width);
|
||||
if (x >= workspace_width && x <= workspace_width + w)
|
||||
if (x >= workspace_width && x <= workspace_width + w) {
|
||||
clicked_ws = ws_walk;
|
||||
if (ws_walk->visible)
|
||||
}
|
||||
if (ws_walk->visible) {
|
||||
cur_ws = ws_walk;
|
||||
}
|
||||
workspace_width += w;
|
||||
if (TAILQ_NEXT(ws_walk, tailq) != NULL)
|
||||
if (TAILQ_NEXT(ws_walk, tailq) != NULL) {
|
||||
workspace_width += logical_px(ws_spacing_px);
|
||||
}
|
||||
}
|
||||
|
||||
if (child_want_click_events() && x > workspace_width) {
|
||||
@@ -583,8 +697,9 @@ static void handle_button(xcb_button_press_event_t *event) {
|
||||
* If there is no more workspace, don’t even send the workspace
|
||||
* command, otherwise (with workspace auto_back_and_forth) we’d end
|
||||
* up on the wrong workspace. */
|
||||
if (cur_ws == TAILQ_FIRST(walk->workspaces))
|
||||
if (cur_ws == TAILQ_FIRST(walk->workspaces)) {
|
||||
return;
|
||||
}
|
||||
|
||||
cur_ws = TAILQ_PREV(cur_ws, ws_head, tailq);
|
||||
break;
|
||||
@@ -594,8 +709,9 @@ static void handle_button(xcb_button_press_event_t *event) {
|
||||
* If there is no more workspace, don’t even send the workspace
|
||||
* command, otherwise (with workspace auto_back_and_forth) we’d end
|
||||
* up on the wrong workspace. */
|
||||
if (cur_ws == TAILQ_LAST(walk->workspaces, ws_head))
|
||||
if (cur_ws == TAILQ_LAST(walk->workspaces, ws_head)) {
|
||||
return;
|
||||
}
|
||||
|
||||
cur_ws = TAILQ_NEXT(cur_ws, tailq);
|
||||
break;
|
||||
@@ -606,51 +722,23 @@ static void handle_button(xcb_button_press_event_t *event) {
|
||||
* workspace if it is not already focused */
|
||||
if (cur_ws == NULL) {
|
||||
TAILQ_FOREACH (cur_ws, walk->workspaces, tailq) {
|
||||
if (cur_ws->visible && !cur_ws->focused)
|
||||
if (cur_ws->visible && !cur_ws->focused) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* if there is nothing to focus, we are done */
|
||||
if (cur_ws == NULL)
|
||||
if (cur_ws == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
/* To properly handle workspace names with double quotes in them, we need
|
||||
* to escape the double quotes. Unfortunately, that’s rather ugly in C: We
|
||||
* first count the number of double quotes, then we allocate a large enough
|
||||
* buffer, then we copy character by character. */
|
||||
int num_quotes = 0;
|
||||
size_t namelen = 0;
|
||||
const char *utf8_name = cur_ws->canonical_name;
|
||||
for (const char *walk = utf8_name; *walk != '\0'; walk++) {
|
||||
if (*walk == '"' || *walk == '\\')
|
||||
num_quotes++;
|
||||
/* While we’re looping through the name anyway, we can save one
|
||||
* strlen(). */
|
||||
namelen++;
|
||||
}
|
||||
|
||||
const size_t len = namelen + strlen("workspace \"\"") + 1;
|
||||
char *buffer = scalloc(len + num_quotes, 1);
|
||||
memcpy(buffer, "workspace \"", strlen("workspace \""));
|
||||
size_t inpos, outpos;
|
||||
for (inpos = 0, outpos = strlen("workspace \"");
|
||||
inpos < namelen;
|
||||
inpos++, outpos++) {
|
||||
if (utf8_name[inpos] == '"' || utf8_name[inpos] == '\\') {
|
||||
buffer[outpos] = '\\';
|
||||
outpos++;
|
||||
}
|
||||
buffer[outpos] = utf8_name[inpos];
|
||||
}
|
||||
buffer[outpos] = '"';
|
||||
i3_send_msg(I3_IPC_MESSAGE_TYPE_RUN_COMMAND, buffer);
|
||||
free(buffer);
|
||||
focus_workspace(cur_ws);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -674,9 +762,9 @@ static void handle_visibility_notify(xcb_visibility_notify_event_t *event) {
|
||||
}
|
||||
|
||||
if (num_visible == 0) {
|
||||
stop_child();
|
||||
stop_children();
|
||||
} else {
|
||||
cont_child();
|
||||
cont_children();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -872,8 +960,9 @@ static void handle_client_message(xcb_client_message_event_t *event) {
|
||||
DLOG("xembed flags = %d\n", xembed[1]);
|
||||
map_it = ((xembed[1] & XEMBED_MAPPED) == XEMBED_MAPPED);
|
||||
xe_version = xembed[0];
|
||||
if (xe_version > 1)
|
||||
if (xe_version > 1) {
|
||||
xe_version = 1;
|
||||
}
|
||||
free(xembedr);
|
||||
} else {
|
||||
ELOG("Window %08x violates the XEMBED protocol, _XEMBED_INFO not set\n", client);
|
||||
@@ -891,8 +980,9 @@ static void handle_client_message(xcb_client_message_event_t *event) {
|
||||
output_for_tray->bar.id,
|
||||
output_for_tray->rect.w - icon_size - logical_px(config.tray_padding),
|
||||
logical_px(config.tray_padding));
|
||||
if (xcb_request_failed(rcookie, "Could not reparent window. Maybe it is using an incorrect depth/visual?"))
|
||||
if (xcb_request_failed(rcookie, "Could not reparent window. Maybe it is using an incorrect depth/visual?")) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* We reconfigure the window to use a reasonable size. The systray
|
||||
* specification explicitly says:
|
||||
@@ -1091,17 +1181,20 @@ static void handle_configuration_change(xcb_window_t window) {
|
||||
trayclient *trayclient;
|
||||
i3_output *output;
|
||||
SLIST_FOREACH (output, outputs, slist) {
|
||||
if (!output->active)
|
||||
if (!output->active) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int clients = 0;
|
||||
TAILQ_FOREACH_REVERSE (trayclient, output->trayclients, tc_head, tailq) {
|
||||
if (!trayclient->mapped)
|
||||
if (!trayclient->mapped) {
|
||||
continue;
|
||||
}
|
||||
clients++;
|
||||
|
||||
if (trayclient->win != window)
|
||||
if (trayclient->win != window) {
|
||||
continue;
|
||||
}
|
||||
|
||||
xcb_rectangle_t rect;
|
||||
rect.x = output->rect.w - (clients * (icon_size + logical_px(config.tray_padding)));
|
||||
@@ -1348,23 +1441,45 @@ static void deregister_xkb_keyevents(void) {
|
||||
*
|
||||
*/
|
||||
void init_xcb_late(char *fontname) {
|
||||
if (fontname == NULL)
|
||||
if (fontname == NULL) {
|
||||
fontname = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1";
|
||||
}
|
||||
|
||||
/* Load the font */
|
||||
font = load_font(fontname, true);
|
||||
set_font(&font);
|
||||
DLOG("Calculated font height: %d\n", font.height);
|
||||
bar_height = font.height + 2 * logical_px(ws_voff_px);
|
||||
DLOG("Calculated font-height: %d\n", font.height);
|
||||
|
||||
/*
|
||||
* If the bar height was explicitly set (but padding was not set), use
|
||||
* it. Otherwise, calculate it based on the font size.
|
||||
*/
|
||||
const int default_px = font.height + 2 * logical_px(ws_voff_px);
|
||||
int padding_scaled =
|
||||
logical_px(config.padding.y) +
|
||||
logical_px(config.padding.height);
|
||||
if (config.bar_height > 0 &&
|
||||
config.padding.x == 0 &&
|
||||
config.padding.y == 0 &&
|
||||
config.padding.width == 0 &&
|
||||
config.padding.height == 0) {
|
||||
padding_scaled = config.bar_height - default_px;
|
||||
DLOG("setting padding_scaled=%d based on bar_height=%d\n", padding_scaled, config.bar_height);
|
||||
} else {
|
||||
DLOG("padding: x=%d, y=%d -> padding_scaled=%d\n", config.padding.x, config.padding.y, padding_scaled);
|
||||
}
|
||||
bar_height = default_px + padding_scaled;
|
||||
icon_size = bar_height - 2 * logical_px(config.tray_padding);
|
||||
|
||||
if (config.separator_symbol)
|
||||
if (config.separator_symbol) {
|
||||
separator_symbol_width = predict_text_width(config.separator_symbol);
|
||||
}
|
||||
|
||||
xcb_flush(xcb_connection);
|
||||
|
||||
if (config.hide_on_modifier == M_HIDE)
|
||||
if (config.hide_on_modifier == M_HIDE) {
|
||||
register_xkb_keyevents();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1400,11 +1515,15 @@ static void send_tray_clientmessage(void) {
|
||||
static void init_tray(void) {
|
||||
DLOG("Initializing system tray functionality\n");
|
||||
/* request the tray manager atom for the X11 display we are running on */
|
||||
char atomname[strlen("_NET_SYSTEM_TRAY_S") + 11];
|
||||
snprintf(atomname, strlen("_NET_SYSTEM_TRAY_S") + 11, "_NET_SYSTEM_TRAY_S%d", screen);
|
||||
/* The following line cannot use strlen as that makes compilation fail with
|
||||
* some versions of clang (-Wgnu-folding-constant): */
|
||||
const size_t systray_len = strlen("_NET_SYSTEM_TRAY_S") + 11;
|
||||
char atomname[systray_len];
|
||||
snprintf(atomname, systray_len, "_NET_SYSTEM_TRAY_S%d", screen);
|
||||
xcb_intern_atom_cookie_t tray_cookie;
|
||||
if (tray_reply == NULL)
|
||||
if (tray_reply == NULL) {
|
||||
tray_cookie = xcb_intern_atom(xcb_connection, 0, strlen(atomname), atomname);
|
||||
}
|
||||
|
||||
/* tray support: we need a window to own the selection */
|
||||
selwin = xcb_generate_id(xcb_connection);
|
||||
@@ -1532,7 +1651,6 @@ void clean_xcb(void) {
|
||||
free_font();
|
||||
|
||||
xcb_free_cursor(xcb_connection, cursor);
|
||||
xcb_flush(xcb_connection);
|
||||
xcb_aux_sync(xcb_connection);
|
||||
xcb_disconnect(xcb_connection);
|
||||
|
||||
@@ -1572,8 +1690,9 @@ void get_atoms(void) {
|
||||
*
|
||||
*/
|
||||
void kick_tray_clients(i3_output *output) {
|
||||
if (TAILQ_EMPTY(output->trayclients))
|
||||
if (TAILQ_EMPTY(output->trayclients)) {
|
||||
return;
|
||||
}
|
||||
|
||||
trayclient *trayclient;
|
||||
while (!TAILQ_EMPTY(output->trayclients)) {
|
||||
@@ -1868,10 +1987,11 @@ void reconfig_windows(bool redraw_bars) {
|
||||
XCB_CONFIG_WINDOW_HEIGHT |
|
||||
XCB_CONFIG_WINDOW_STACK_MODE;
|
||||
values[0] = walk->rect.x;
|
||||
if (config.position == POS_TOP)
|
||||
if (config.position == POS_TOP) {
|
||||
values[1] = walk->rect.y;
|
||||
else
|
||||
} else {
|
||||
values[1] = walk->rect.y + walk->rect.h - bar_height;
|
||||
}
|
||||
values[2] = walk->rect.w;
|
||||
values[3] = bar_height;
|
||||
values[4] = XCB_STACK_MODE_ABOVE;
|
||||
@@ -1927,10 +2047,10 @@ void reconfig_windows(bool redraw_bars) {
|
||||
/* Unmap the window, and draw it again when in dock mode */
|
||||
umap_cookie = xcb_unmap_window_checked(xcb_connection, walk->bar.id);
|
||||
if (config.hide_on_modifier == M_DOCK) {
|
||||
cont_child();
|
||||
cont_children();
|
||||
map_cookie = xcb_map_window_checked(xcb_connection, walk->bar.id);
|
||||
} else {
|
||||
stop_child();
|
||||
stop_children();
|
||||
}
|
||||
|
||||
if (config.hide_on_modifier == M_HIDE) {
|
||||
@@ -1974,7 +2094,7 @@ void reconfig_windows(bool redraw_bars) {
|
||||
*/
|
||||
static void draw_button(surface_t *surface, color_t fg_color, color_t bg_color, color_t border_color,
|
||||
int x, int width, int text_width, i3String *text) {
|
||||
int height = font.height + 2 * logical_px(ws_voff_px) - 2 * logical_px(1);
|
||||
int height = bar_height - 2 * logical_px(1);
|
||||
|
||||
/* Draw the border of the button. */
|
||||
draw_util_rectangle(surface, border_color, x, logical_px(1), width, height);
|
||||
@@ -1984,7 +2104,7 @@ static void draw_button(surface_t *surface, color_t fg_color, color_t bg_color,
|
||||
width - 2 * logical_px(1), height - 2 * logical_px(1));
|
||||
|
||||
draw_util_text(text, surface, fg_color, bg_color, x + (width - text_width) / 2,
|
||||
logical_px(ws_voff_px), text_width);
|
||||
bar_height / 2 - font.height / 2, text_width);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1994,12 +2114,9 @@ static void draw_button(surface_t *surface, color_t fg_color, color_t bg_color,
|
||||
void draw_bars(bool unhide) {
|
||||
DLOG("Drawing bars...\n");
|
||||
|
||||
uint32_t full_statusline_width = predict_statusline_length(false);
|
||||
uint32_t short_statusline_width = predict_statusline_length(true);
|
||||
|
||||
i3_output *outputs_walk;
|
||||
SLIST_FOREACH (outputs_walk, outputs, slist) {
|
||||
int workspace_width = 0;
|
||||
int workspace_width = logical_px(config.padding.x);
|
||||
|
||||
if (!outputs_walk->active) {
|
||||
DLOG("Output %s inactive, skipping...\n", outputs_walk->name);
|
||||
@@ -2047,8 +2164,9 @@ void draw_bars(bool unhide) {
|
||||
workspace_width, w, ws_walk->name_width, ws_walk->name);
|
||||
|
||||
workspace_width += w;
|
||||
if (TAILQ_NEXT(ws_walk, tailq) != NULL)
|
||||
if (TAILQ_NEXT(ws_walk, tailq) != NULL) {
|
||||
workspace_width += logical_px(ws_spacing_px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2070,26 +2188,28 @@ void draw_bars(bool unhide) {
|
||||
uint32_t hoff = logical_px(((workspace_width > 0) + (tray_width > 0)) * sb_hoff_px);
|
||||
uint32_t max_statusline_width = outputs_walk->rect.w - workspace_width - tray_width - hoff;
|
||||
uint32_t clip_left = 0;
|
||||
uint32_t statusline_width = full_statusline_width;
|
||||
bool use_short_text = false;
|
||||
|
||||
/* Reset short mode between outputs */
|
||||
struct status_block *block;
|
||||
TAILQ_FOREACH (block, &statusline_head, blocks) {
|
||||
block->use_short = false;
|
||||
}
|
||||
|
||||
uint32_t statusline_width = adjust_statusline_length(max_statusline_width);
|
||||
|
||||
if (statusline_width > max_statusline_width) {
|
||||
statusline_width = short_statusline_width;
|
||||
use_short_text = true;
|
||||
if (statusline_width > max_statusline_width) {
|
||||
clip_left = statusline_width - max_statusline_width;
|
||||
}
|
||||
clip_left = statusline_width - max_statusline_width;
|
||||
}
|
||||
|
||||
int16_t visible_statusline_width = MIN(statusline_width, max_statusline_width);
|
||||
int x_dest = outputs_walk->rect.w - tray_width - logical_px((tray_width > 0) * sb_hoff_px) - visible_statusline_width;
|
||||
x_dest -= logical_px(config.padding.width);
|
||||
|
||||
draw_statusline(outputs_walk, clip_left, use_focus_colors, use_short_text);
|
||||
draw_statusline(outputs_walk, clip_left, use_focus_colors);
|
||||
draw_util_copy_surface(&outputs_walk->statusline_buffer, &outputs_walk->buffer, 0, 0,
|
||||
x_dest, 0, visible_statusline_width, (int16_t)bar_height);
|
||||
|
||||
outputs_walk->statusline_width = statusline_width;
|
||||
outputs_walk->statusline_short_text = use_short_text;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* This header file includes all relevant files of i3 and the most often used
|
||||
@@ -46,6 +46,7 @@
|
||||
#include "click.h"
|
||||
#include "key_press.h"
|
||||
#include "floating.h"
|
||||
#include "gaps.h"
|
||||
#include "drag.h"
|
||||
#include "configuration.h"
|
||||
#include "handlers.h"
|
||||
@@ -56,9 +57,9 @@
|
||||
#include "render.h"
|
||||
#include "window.h"
|
||||
#include "match.h"
|
||||
#include "cmdparse.h"
|
||||
#include "xcursor.h"
|
||||
#include "resize.h"
|
||||
#include "tiling_drag.h"
|
||||
#include "sighandler.h"
|
||||
#include "move.h"
|
||||
#include "output.h"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* assignments.c: Assignments for specific windows (for_window).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* bindings.h: Functions for configuring, finding, and running bindings.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* click.c: Button press (mouse click) events.
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* cmdparse.y: the parser for commands you send to i3 (or bind on keys)
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <config.h>
|
||||
|
||||
char *parse_cmd(const char *new);
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* commands.c: all command functions (see commands_parser.c)
|
||||
@@ -138,7 +138,7 @@ void cmd_mode(I3_CMD, const char *mode);
|
||||
* Implementation of 'move [window|container] [to] output <str>'.
|
||||
*
|
||||
*/
|
||||
void cmd_move_con_to_output(I3_CMD, const char *name);
|
||||
void cmd_move_con_to_output(I3_CMD, const char *name, bool move_workspace);
|
||||
|
||||
/**
|
||||
* Implementation of 'move [window|container] [to] mark <str>'.
|
||||
@@ -152,12 +152,6 @@ void cmd_move_con_to_mark(I3_CMD, const char *mark);
|
||||
*/
|
||||
void cmd_floating(I3_CMD, const char *floating_mode);
|
||||
|
||||
/**
|
||||
* Implementation of 'move workspace to [output] <str>'.
|
||||
*
|
||||
*/
|
||||
void cmd_move_workspace_to_output(I3_CMD, const char *name);
|
||||
|
||||
/**
|
||||
* Implementation of 'split v|h|t|vertical|horizontal|toggle'.
|
||||
*
|
||||
@@ -204,7 +198,7 @@ void cmd_focus_level(I3_CMD, const char *level);
|
||||
* Implementation of 'focus'.
|
||||
*
|
||||
*/
|
||||
void cmd_focus(I3_CMD);
|
||||
void cmd_focus(I3_CMD, bool focus_workspace);
|
||||
|
||||
/**
|
||||
* Implementation of 'fullscreen [enable|disable|toggle] [global]'.
|
||||
@@ -337,3 +331,15 @@ void cmd_shmlog(I3_CMD, const char *argument);
|
||||
*
|
||||
*/
|
||||
void cmd_debuglog(I3_CMD, const char *argument);
|
||||
|
||||
/**
|
||||
* Implementation of 'gaps inner|outer|top|right|bottom|left|horizontal|vertical current|all set|plus|minus|toggle <px>'
|
||||
*
|
||||
*/
|
||||
void cmd_gaps(I3_CMD, const char *type, const char *scope, const char *mode, const char *value);
|
||||
|
||||
/**
|
||||
* Implementation of 'title_window_icon <yes|no|toggle>' and 'title_window_icon <padding|toggle> <px>'
|
||||
*
|
||||
*/
|
||||
void cmd_title_window_icon(I3_CMD, const char *enable, int padding);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* commands.c: all command functions (see commands_parser.c)
|
||||
@@ -14,7 +14,7 @@
|
||||
#include <yajl/yajl_gen.h>
|
||||
|
||||
/**
|
||||
* Holds an intermediate represenation of the result of a call to any command.
|
||||
* Holds an intermediate representation of the result of a call to any command.
|
||||
* When calling parse_command("floating enable, border none"), the parser will
|
||||
* internally use this struct when calling cmd_floating and cmd_border.
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* con.c: Functions which deal with containers directly (creating containers,
|
||||
@@ -83,6 +83,22 @@ bool con_is_split(Con *con);
|
||||
*/
|
||||
bool con_is_hidden(Con *con);
|
||||
|
||||
/**
|
||||
* Returns true if the container is maximized in the given orientation.
|
||||
*
|
||||
* If the container is floating or fullscreen, it is not considered maximized.
|
||||
* Otherwise, it is maximized if it doesn't share space with any other
|
||||
* container in the given orientation. For example, if a workspace contains
|
||||
* a single splitv container with three children, none of them are considered
|
||||
* vertically maximized, but they are all considered horizontally maximized.
|
||||
*
|
||||
* Passing "maximized" hints to the application can help it make the right
|
||||
* choices about how to draw its borders. See discussion in
|
||||
* https://github.com/i3/i3/pull/2380.
|
||||
*
|
||||
*/
|
||||
bool con_is_maximized(Con *con, orientation_t orientation);
|
||||
|
||||
/**
|
||||
* Returns whether the container or any of its children is sticky.
|
||||
*
|
||||
@@ -208,6 +224,14 @@ Con *con_by_frame_id(xcb_window_t frame);
|
||||
*/
|
||||
Con *con_by_mark(const char *mark);
|
||||
|
||||
/**
|
||||
* Start from a container and traverse the transient_for linked list. Returns
|
||||
* true if target window is found in the list. Protects againsts potential
|
||||
* cycles.
|
||||
*
|
||||
*/
|
||||
bool con_find_transient_for_window(Con *start, xcb_window_t target);
|
||||
|
||||
/**
|
||||
* Returns true if and only if the given containers holds the mark.
|
||||
*
|
||||
@@ -363,6 +387,7 @@ void con_move_to_output(Con *con, Output *output, bool fix_coordinates);
|
||||
*/
|
||||
bool con_move_to_output_name(Con *con, const char *name, bool fix_coordinates);
|
||||
|
||||
bool con_move_to_target(Con *con, Con *target);
|
||||
/**
|
||||
* Moves the given container to the given mark.
|
||||
*
|
||||
@@ -411,6 +436,14 @@ Con *con_descend_tiling_focused(Con *con);
|
||||
*/
|
||||
Con *con_descend_direction(Con *con, direction_t direction);
|
||||
|
||||
/**
|
||||
* Returns whether the window decoration (title bar) should be drawn into the
|
||||
* X11 frame window of this container (default) or into the X11 frame window of
|
||||
* the parent container (for stacked/tabbed containers).
|
||||
*
|
||||
*/
|
||||
bool con_draw_decoration_into_frame(Con *con);
|
||||
|
||||
/**
|
||||
* Returns a "relative" Rect which contains the amount of pixels that need to
|
||||
* be added to the original Rect to get the final position (obviously the
|
||||
@@ -442,7 +475,7 @@ int con_border_style(Con *con);
|
||||
* floating window.
|
||||
*
|
||||
*/
|
||||
void con_set_border_style(Con *con, int border_style, int border_width);
|
||||
void con_set_border_style(Con *con, border_style_t border_style, int border_width);
|
||||
|
||||
/**
|
||||
* This function changes the layout of a given container. Use it to handle
|
||||
@@ -548,3 +581,9 @@ uint32_t con_rect_size_in_orientation(Con *con);
|
||||
*
|
||||
*/
|
||||
void con_merge_into(Con *old, Con *new);
|
||||
|
||||
/**
|
||||
* Returns true if the container is within any stacked/tabbed split container.
|
||||
*
|
||||
*/
|
||||
bool con_inside_stacked_or_tabbed(Con *con);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* config_directives.h: all config storing functions (see config_parser.c)
|
||||
@@ -39,9 +39,13 @@ CFGFUN(criteria_init, int _state);
|
||||
CFGFUN(criteria_add, const char *ctype, const char *cvalue);
|
||||
CFGFUN(criteria_pop_state);
|
||||
|
||||
CFGFUN(include, const char *pattern);
|
||||
CFGFUN(font, const char *font);
|
||||
CFGFUN(exec, const char *exectype, const char *no_startup_id, const char *command);
|
||||
CFGFUN(for_window, const char *command);
|
||||
CFGFUN(gaps, const char *workspace, const char *type, const long value);
|
||||
CFGFUN(smart_borders, const char *enable);
|
||||
CFGFUN(smart_gaps, const char *enable);
|
||||
CFGFUN(floating_minimum_size, const long width, const long height);
|
||||
CFGFUN(floating_maximum_size, const long width, const long height);
|
||||
CFGFUN(default_orientation, const char *orientation);
|
||||
@@ -64,6 +68,8 @@ CFGFUN(assign, const char *workspace, bool is_number);
|
||||
CFGFUN(no_focus);
|
||||
CFGFUN(ipc_socket, const char *path);
|
||||
CFGFUN(ipc_kill_timeout, const long timeout_ms);
|
||||
CFGFUN(tiling_drag, const char *value);
|
||||
CFGFUN(tiling_drag_swap_modifier, const char *modifiers);
|
||||
CFGFUN(restart_state, const char *path);
|
||||
CFGFUN(popup_during_fullscreen, const char *value);
|
||||
CFGFUN(color, const char *colorclass, const char *border, const char *background, const char *text, const char *indicator, const char *child_border);
|
||||
@@ -83,6 +89,11 @@ CFGFUN(bar_hidden_state, const char *hidden_state);
|
||||
CFGFUN(bar_id, const char *bar_id);
|
||||
CFGFUN(bar_output, const char *output);
|
||||
CFGFUN(bar_verbose, const char *verbose);
|
||||
CFGFUN(bar_height, const long height);
|
||||
CFGFUN(bar_padding_one, const long all);
|
||||
CFGFUN(bar_padding_two, const long top_and_bottom, const long right_and_left);
|
||||
CFGFUN(bar_padding_three, const long top, const long right_and_left, const long bottom);
|
||||
CFGFUN(bar_padding_four, const long top, const long right, const long bottom, const long left);
|
||||
CFGFUN(bar_modifier, const char *modifiers);
|
||||
CFGFUN(bar_wheel_up_cmd, const char *command);
|
||||
CFGFUN(bar_wheel_down_cmd, const char *command);
|
||||
@@ -95,6 +106,7 @@ CFGFUN(bar_tray_output, const char *output);
|
||||
CFGFUN(bar_tray_padding, const long spacing_px);
|
||||
CFGFUN(bar_color_single, const char *colorclass, const char *color);
|
||||
CFGFUN(bar_status_command, const char *command);
|
||||
CFGFUN(bar_workspace_command, const char *command);
|
||||
CFGFUN(bar_binding_mode_indicator, const char *value);
|
||||
CFGFUN(bar_workspace_buttons, const char *value);
|
||||
CFGFUN(bar_workspace_min_width, const long width);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* config_parser.h: config parser-related definitions
|
||||
@@ -16,29 +16,84 @@
|
||||
SLIST_HEAD(variables_head, Variable);
|
||||
extern pid_t config_error_nagbar_pid;
|
||||
|
||||
struct stack_entry {
|
||||
/* Just a pointer, not dynamically allocated. */
|
||||
const char *identifier;
|
||||
enum {
|
||||
STACK_STR = 0,
|
||||
STACK_LONG = 1,
|
||||
} type;
|
||||
union {
|
||||
char *str;
|
||||
long num;
|
||||
} val;
|
||||
};
|
||||
|
||||
struct stack {
|
||||
struct stack_entry stack[10];
|
||||
};
|
||||
|
||||
struct parser_ctx {
|
||||
bool use_nagbar;
|
||||
|
||||
int state;
|
||||
Match current_match;
|
||||
|
||||
/* A list which contains the states that lead to the current state, e.g.
|
||||
* INITIAL, WORKSPACE_LAYOUT.
|
||||
* When jumping back to INITIAL, statelist_idx will simply be set to 1
|
||||
* (likewise for other states, e.g. MODE or BAR).
|
||||
* This list is used to process the nearest error token. */
|
||||
int statelist[10];
|
||||
/* NB: statelist_idx points to where the next entry will be inserted */
|
||||
int statelist_idx;
|
||||
|
||||
/*******************************************************************************
|
||||
* The (small) stack where identified literals are stored during the parsing
|
||||
* of a single config directive (like $workspace).
|
||||
******************************************************************************/
|
||||
struct stack *stack;
|
||||
|
||||
struct variables_head variables;
|
||||
|
||||
bool has_errors;
|
||||
};
|
||||
|
||||
/**
|
||||
* An intermediate reprsentation of the result of a parse_config call.
|
||||
* An intermediate representation of the result of a parse_config call.
|
||||
* Currently unused, but the JSON output will be useful in the future when we
|
||||
* implement a config parsing IPC command.
|
||||
*
|
||||
*/
|
||||
struct ConfigResultIR {
|
||||
/* The JSON generator to append a reply to. */
|
||||
yajl_gen json_gen;
|
||||
struct parser_ctx *ctx;
|
||||
|
||||
/* The next state to transition to. Passed to the function so that we can
|
||||
* determine the next state as a result of a function call, like
|
||||
* cfg_criteria_pop_state() does. */
|
||||
int next_state;
|
||||
};
|
||||
|
||||
struct ConfigResultIR *parse_config(const char *input, struct context *context);
|
||||
/* Whether any error happened while processing this config directive. */
|
||||
bool has_errors;
|
||||
};
|
||||
|
||||
/**
|
||||
* launch nagbar to indicate errors in the configuration file.
|
||||
*/
|
||||
void start_config_error_nagbar(const char *configpath, bool has_errors);
|
||||
|
||||
/**
|
||||
* Releases the memory of all variables in ctx.
|
||||
*
|
||||
*/
|
||||
void free_variables(struct parser_ctx *ctx);
|
||||
|
||||
typedef enum {
|
||||
PARSE_FILE_FAILED = -1,
|
||||
PARSE_FILE_SUCCESS = 0,
|
||||
PARSE_FILE_CONFIG_ERRORS = 1,
|
||||
} parse_file_result_t;
|
||||
|
||||
/**
|
||||
* Parses the given file by first replacing the variables, then calling
|
||||
* parse_config and launching i3-nagbar if use_nagbar is true.
|
||||
@@ -47,4 +102,4 @@ void start_config_error_nagbar(const char *configpath, bool has_errors);
|
||||
* parsing.
|
||||
*
|
||||
*/
|
||||
bool parse_file(const char *f, bool use_nagbar);
|
||||
parse_file_result_t parse_file(struct parser_ctx *ctx, const char *f, IncludedFile *included_file);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* include/configuration.h: Contains all structs/variables for the configurable
|
||||
@@ -14,7 +14,9 @@
|
||||
|
||||
#include "queue.h"
|
||||
#include "i3.h"
|
||||
#include "tiling_drag.h"
|
||||
|
||||
typedef struct IncludedFile IncludedFile;
|
||||
typedef struct Config Config;
|
||||
typedef struct Barconfig Barconfig;
|
||||
extern char *current_configpath;
|
||||
@@ -22,6 +24,7 @@ extern char *current_config;
|
||||
extern Config config;
|
||||
extern SLIST_HEAD(modes_head, Mode) modes;
|
||||
extern TAILQ_HEAD(barconfig_head, Barconfig) barconfigs;
|
||||
extern TAILQ_HEAD(includedfiles_head, IncludedFile) included_files;
|
||||
|
||||
/**
|
||||
* Used during the config file lexing/parsing to keep the state of the lexer
|
||||
@@ -69,6 +72,18 @@ struct Variable {
|
||||
SLIST_ENTRY(Variable) variables;
|
||||
};
|
||||
|
||||
/**
|
||||
* List entry struct for an included file.
|
||||
*
|
||||
*/
|
||||
struct IncludedFile {
|
||||
char *path;
|
||||
char *raw_contents;
|
||||
char *variable_replaced_contents;
|
||||
|
||||
TAILQ_ENTRY(IncludedFile) files;
|
||||
};
|
||||
|
||||
/**
|
||||
* The configuration file can contain multiple sets of bindings. Apart from the
|
||||
* default set (name == "default"), you can specify other sets and change the
|
||||
@@ -212,6 +227,9 @@ struct Config {
|
||||
/** The modifier which needs to be pressed in combination with your mouse
|
||||
* buttons to do things with floating windows (move, resize) */
|
||||
uint32_t floating_modifier;
|
||||
/** The modifier which needs to be pressed in combination with the floating
|
||||
* modifier and your mouse buttons to swap containers during tiling drag */
|
||||
uint32_t swap_modifier;
|
||||
|
||||
/** Maximum and minimum dimensions of a floating window */
|
||||
int32_t floating_maximum_width;
|
||||
@@ -224,9 +242,11 @@ struct Config {
|
||||
color_t background;
|
||||
struct Colortriple focused;
|
||||
struct Colortriple focused_inactive;
|
||||
struct Colortriple focused_tab_title;
|
||||
struct Colortriple unfocused;
|
||||
struct Colortriple urgent;
|
||||
struct Colortriple placeholder;
|
||||
bool got_focused_tab_title;
|
||||
} client;
|
||||
struct config_bar {
|
||||
struct Colortriple focused;
|
||||
@@ -245,10 +265,21 @@ struct Config {
|
||||
|
||||
/* just ignore the popup, that is, don’t map it */
|
||||
PDF_IGNORE = 2,
|
||||
|
||||
/* display all floating windows */
|
||||
PDF_ALL = 3,
|
||||
} popup_during_fullscreen;
|
||||
|
||||
/* The number of currently parsed barconfigs */
|
||||
int number_barconfigs;
|
||||
|
||||
tiling_drag_t tiling_drag;
|
||||
|
||||
/* Gap sizes */
|
||||
gaps_t gaps;
|
||||
|
||||
/* Disable gaps if there is only one container on the workspace */
|
||||
smart_gaps_t smart_gaps;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -307,6 +338,10 @@ struct Barconfig {
|
||||
* Will be passed to the shell. */
|
||||
char *status_command;
|
||||
|
||||
/** Command that should be run to get the workspace buttons. Will be passed
|
||||
* to the shell. */
|
||||
char *workspace_command;
|
||||
|
||||
/** Font specification for all text rendered on the bar. */
|
||||
char *font;
|
||||
|
||||
@@ -336,6 +371,11 @@ struct Barconfig {
|
||||
/** Enable verbose mode? Useful for debugging purposes. */
|
||||
bool verbose;
|
||||
|
||||
/** Defines the height of the bar in pixels. */
|
||||
uint32_t bar_height;
|
||||
|
||||
struct Rect padding;
|
||||
|
||||
struct bar_colors {
|
||||
char *background;
|
||||
char *statusline;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* include/data.h: This file defines all data structures used by i3
|
||||
@@ -9,12 +9,15 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#define PCRE2_CODE_UNIT_WIDTH 8
|
||||
|
||||
#define SN_API_NOT_YET_FROZEN 1
|
||||
#include <libsn/sn-launcher.h>
|
||||
|
||||
#include <xcb/randr.h>
|
||||
#include <pcre.h>
|
||||
#include <pcre2.h>
|
||||
#include <sys/time.h>
|
||||
#include <cairo/cairo.h>
|
||||
|
||||
#include "queue.h"
|
||||
|
||||
@@ -44,6 +47,7 @@ typedef struct Con Con;
|
||||
typedef struct Match Match;
|
||||
typedef struct Assignment Assignment;
|
||||
typedef struct Window i3Window;
|
||||
typedef struct gaps_t gaps_t;
|
||||
typedef struct mark_t mark_t;
|
||||
|
||||
/******************************************************************************
|
||||
@@ -58,9 +62,11 @@ typedef enum { NO_ORIENTATION = 0,
|
||||
VERT } orientation_t;
|
||||
typedef enum { BEFORE,
|
||||
AFTER } position_t;
|
||||
typedef enum { BS_NORMAL = 0,
|
||||
BS_NONE = 1,
|
||||
BS_PIXEL = 2 } border_style_t;
|
||||
typedef enum {
|
||||
BS_NONE = 0,
|
||||
BS_PIXEL = 1,
|
||||
BS_NORMAL = 2,
|
||||
} border_style_t;
|
||||
|
||||
/** parameter to specify whether tree_close_internal() and x_window_kill() should kill
|
||||
* only this specific window or the whole X11 client */
|
||||
@@ -75,11 +81,16 @@ typedef enum { ADJ_NONE = 0,
|
||||
ADJ_UPPER_SCREEN_EDGE = (1 << 2),
|
||||
ADJ_LOWER_SCREEN_EDGE = (1 << 4) } adjacent_t;
|
||||
|
||||
typedef enum { SMART_GAPS_OFF,
|
||||
SMART_GAPS_ON,
|
||||
SMART_GAPS_INVERSE_OUTER } smart_gaps_t;
|
||||
|
||||
typedef enum { HEBM_NONE = ADJ_NONE,
|
||||
HEBM_VERTICAL = ADJ_LEFT_SCREEN_EDGE | ADJ_RIGHT_SCREEN_EDGE,
|
||||
HEBM_HORIZONTAL = ADJ_UPPER_SCREEN_EDGE | ADJ_LOWER_SCREEN_EDGE,
|
||||
HEBM_BOTH = HEBM_VERTICAL | HEBM_HORIZONTAL,
|
||||
HEBM_SMART = (1 << 5) } hide_edge_borders_mode_t;
|
||||
HEBM_SMART = (1 << 5),
|
||||
HEBM_SMART_NO_GAPS = (1 << 6) } hide_edge_borders_mode_t;
|
||||
|
||||
typedef enum { MM_REPLACE,
|
||||
MM_ADD } mark_mode_t;
|
||||
@@ -132,6 +143,25 @@ typedef enum {
|
||||
POINTER_WARPING_NONE = 1
|
||||
} warping_t;
|
||||
|
||||
struct gaps_t {
|
||||
int inner;
|
||||
int top;
|
||||
int right;
|
||||
int bottom;
|
||||
int left;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
GAPS_INNER = (1 << 0),
|
||||
GAPS_TOP = (1 << 1),
|
||||
GAPS_RIGHT = (1 << 2),
|
||||
GAPS_BOTTOM = (1 << 3),
|
||||
GAPS_LEFT = (1 << 4),
|
||||
GAPS_VERTICAL = (GAPS_TOP | GAPS_BOTTOM),
|
||||
GAPS_HORIZONTAL = (GAPS_RIGHT | GAPS_LEFT),
|
||||
GAPS_OUTER = (GAPS_VERTICAL | GAPS_HORIZONTAL),
|
||||
} gaps_mask_t;
|
||||
|
||||
/**
|
||||
* Focus wrapping modes.
|
||||
*/
|
||||
@@ -199,12 +229,14 @@ struct deco_render_params {
|
||||
};
|
||||
|
||||
/**
|
||||
* Stores which workspace (by name or number) goes to which output.
|
||||
* Stores which workspace (by name or number) goes to which output and its gaps config.
|
||||
*
|
||||
*/
|
||||
struct Workspace_Assignment {
|
||||
char *name;
|
||||
char *output;
|
||||
gaps_t gaps;
|
||||
gaps_mask_t gaps_mask;
|
||||
|
||||
TAILQ_ENTRY(Workspace_Assignment) ws_assignments;
|
||||
};
|
||||
@@ -247,8 +279,7 @@ struct Startup_Sequence {
|
||||
*/
|
||||
struct regex {
|
||||
char *pattern;
|
||||
pcre *regex;
|
||||
pcre_extra *extra;
|
||||
pcre2_code *regex;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -414,6 +445,9 @@ struct Window {
|
||||
* for_window. */
|
||||
char *role;
|
||||
|
||||
/** WM_CLIENT_MACHINE of the window */
|
||||
char *machine;
|
||||
|
||||
/** Flag to force re-rendering the decoration upon changes */
|
||||
bool name_x_changed;
|
||||
|
||||
@@ -468,6 +502,9 @@ struct Window {
|
||||
double min_aspect_ratio;
|
||||
double max_aspect_ratio;
|
||||
|
||||
/** Window icon, as Cairo surface */
|
||||
cairo_surface_t *icon;
|
||||
|
||||
/** The window has a nonrectangular shape. */
|
||||
bool shaped;
|
||||
/** The window has a nonrectangular input shape. */
|
||||
@@ -500,6 +537,7 @@ struct Match {
|
||||
struct regex *mark;
|
||||
struct regex *window_role;
|
||||
struct regex *workspace;
|
||||
struct regex *machine;
|
||||
xcb_atom_t window_type;
|
||||
enum {
|
||||
U_DONTCHECK = -1,
|
||||
@@ -522,6 +560,7 @@ struct Match {
|
||||
WM_FLOATING_USER,
|
||||
WM_FLOATING } window_mode;
|
||||
Con *con_id;
|
||||
bool match_all_windows;
|
||||
|
||||
/* Where the window looking for a match should be inserted:
|
||||
*
|
||||
@@ -633,6 +672,9 @@ struct Con {
|
||||
* workspace is not a named workspace (for named workspaces, num == -1) */
|
||||
int num;
|
||||
|
||||
/** Only applicable for containers of type CT_WORKSPACE. */
|
||||
gaps_t gaps;
|
||||
|
||||
struct Con *parent;
|
||||
|
||||
/* The position and size for this con. These coordinates are absolute. Note
|
||||
@@ -652,6 +694,11 @@ struct Con {
|
||||
/** The format with which the window's name should be displayed. */
|
||||
char *title_format;
|
||||
|
||||
/** Whether the window icon should be displayed, and with what padding. -1
|
||||
* means display no window icon (default behavior), 0 means display without
|
||||
* any padding, 1 means display with 1 pixel of padding and so on. */
|
||||
int window_icon_padding;
|
||||
|
||||
/* a sticky-group is an identifier which bundles several containers to a
|
||||
* group. The contents are shared between all of them, that is they are
|
||||
* displayed on whichever of the containers is currently visible */
|
||||
@@ -706,7 +753,16 @@ struct Con {
|
||||
* layout in workspace_layout and creates a new split container with that
|
||||
* layout whenever a new container is attached to the workspace. */
|
||||
layout_t layout, last_split_layout, workspace_layout;
|
||||
|
||||
border_style_t border_style;
|
||||
/* When the border style of a con changes because of motif hints, we don't
|
||||
* want to set more decoration that the user wants. The user's preference is determined by these:
|
||||
* 1. For new tiling windows, as set by `default_border`
|
||||
* 2. For new floating windows, as set by `default_floating_border`
|
||||
* 3. For all windows that the user runs the `border` command, whatever is
|
||||
* the result of that command for that window. */
|
||||
border_style_t max_user_border_style;
|
||||
|
||||
/** floating? (= not in tiling layout) This cannot be simply a bool
|
||||
* because we want to keep track of whether the status was set by the
|
||||
* application (by setting _NET_WM_WINDOW_TYPE appropriately) or by the
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* display_version.c: displays the running i3 version, runs as part of
|
||||
@@ -14,7 +14,7 @@
|
||||
/**
|
||||
* Connects to i3 to find out the currently running version. Useful since it
|
||||
* might be different from the version compiled into this binary (maybe the
|
||||
* user didn’t correctly install i3 or forgot te restart it).
|
||||
* user didn’t correctly install i3 or forgot to restart it).
|
||||
*
|
||||
* The output looks like this:
|
||||
* Running i3 version: 4.2-202-gb8e782c (2012-08-12, branch "next") (pid 14804)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* drag.c: click and drag.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* ewmh.c: Get/set certain EWMH properties easily.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* Faking outputs is useful in pathological situations (like network X servers
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* floating.c: Floating windows.
|
||||
|
||||
41
include/gaps.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "data.h"
|
||||
|
||||
/**
|
||||
* Calculates the effective gap sizes for a container.
|
||||
*/
|
||||
gaps_t calculate_effective_gaps(Con *con);
|
||||
|
||||
/*
|
||||
* Decides whether the container should be inset.
|
||||
*/
|
||||
bool gaps_should_inset_con(Con *con, int children);
|
||||
|
||||
/*
|
||||
* Returns whether the given container has an adjacent container in the
|
||||
* specified direction. In other words, this returns true if and only if
|
||||
* the container is not touching the edge of the screen in that direction.
|
||||
*/
|
||||
bool gaps_has_adjacent_container(Con *con, direction_t direction);
|
||||
|
||||
/**
|
||||
* Returns the configured gaps for this workspace based on the workspace name,
|
||||
* number, and configured workspace gap assignments.
|
||||
*/
|
||||
gaps_t gaps_for_workspace(Con *ws);
|
||||
|
||||
/**
|
||||
* Re-applies all workspace gap assignments to existing workspaces after
|
||||
* reloading the configuration file.
|
||||
*
|
||||
*/
|
||||
void gaps_reapply_workspace_assignments(void);
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* handlers.c: Small handlers for various events (keypresses, focus changes,
|
||||
|
||||
@@ -11,6 +11,8 @@ xmacro(_NET_WM_STATE_DEMANDS_ATTENTION) \
|
||||
xmacro(_NET_WM_STATE_MODAL) \
|
||||
xmacro(_NET_WM_STATE_HIDDEN) \
|
||||
xmacro(_NET_WM_STATE_FOCUSED) \
|
||||
xmacro(_NET_WM_STATE_MAXIMIZED_VERT) \
|
||||
xmacro(_NET_WM_STATE_MAXIMIZED_HORZ) \
|
||||
xmacro(_NET_WM_STATE) \
|
||||
xmacro(_NET_WM_WINDOW_TYPE) \
|
||||
xmacro(_NET_WM_WINDOW_TYPE_NORMAL) \
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
xmacro(_NET_WM_USER_TIME) \
|
||||
xmacro(_NET_STARTUP_ID) \
|
||||
xmacro(_NET_WORKAREA) \
|
||||
xmacro(_NET_WM_ICON) \
|
||||
xmacro(WM_PROTOCOLS) \
|
||||
xmacro(WM_DELETE_WINDOW) \
|
||||
xmacro(UTF8_STRING) \
|
||||
@@ -15,8 +16,10 @@ xmacro(I3_CONFIG_PATH) \
|
||||
xmacro(I3_SYNC) \
|
||||
xmacro(I3_SHMLOG_PATH) \
|
||||
xmacro(I3_PID) \
|
||||
xmacro(I3_LOG_STREAM_SOCKET_PATH) \
|
||||
xmacro(I3_FLOATING_WINDOW) \
|
||||
xmacro(_NET_REQUEST_FRAME_EXTENTS) \
|
||||
xmacro(_NET_FRAME_EXTENTS) \
|
||||
xmacro(_MOTIF_WM_HINTS) \
|
||||
xmacro(WM_CHANGE_STATE)
|
||||
xmacro(WM_CHANGE_STATE) \
|
||||
xmacro(MANAGER)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* i3.h: global variables that are used all over i3.
|
||||
@@ -39,6 +39,7 @@ extern bool debug_build;
|
||||
/** The number of file descriptors passed via socket activation. */
|
||||
extern int listen_fds;
|
||||
extern int conn_screen;
|
||||
extern xcb_atom_t wm_sn;
|
||||
/**
|
||||
* The EWMH support window that is used to indicate that an EWMH-compliant
|
||||
* window manager is present. This window is created when i3 starts and
|
||||
@@ -55,7 +56,6 @@ extern xcb_timestamp_t last_timestamp;
|
||||
extern SnDisplay *sndisplay;
|
||||
extern xcb_key_symbols_t *keysyms;
|
||||
extern char **start_argv;
|
||||
extern Display *xlibdpy, *xkbdpy;
|
||||
extern int xkb_current_group;
|
||||
extern TAILQ_HEAD(bindings_head, Binding) *bindings;
|
||||
extern const char *current_binding_mode;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* This public header defines the different constants and message types to use
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* ipc.c: UNIX domain socket IPC (initialization, client handling, protocol).
|
||||
@@ -80,13 +80,6 @@ void ipc_new_client(EV_P_ struct ev_io *w, int revents);
|
||||
*/
|
||||
ipc_client *ipc_new_client_on_fd(EV_P_ int fd);
|
||||
|
||||
/**
|
||||
* Creates the UNIX domain socket at the given path, sets it to non-blocking
|
||||
* mode, bind()s and listen()s on it.
|
||||
*
|
||||
*/
|
||||
int ipc_create_socket(const char *filename);
|
||||
|
||||
/**
|
||||
* Sends the specified event to all IPC clients which are currently connected
|
||||
* and subscribed to this kind of event.
|
||||
@@ -140,7 +133,7 @@ void ipc_send_barconfig_update_event(Barconfig *barconfig);
|
||||
/**
|
||||
* For the binding events, we send the serialized binding struct.
|
||||
*/
|
||||
void ipc_send_binding_event(const char *event_type, Binding *bind);
|
||||
void ipc_send_binding_event(const char *event_type, Binding *bind, const char *modename);
|
||||
|
||||
/**
|
||||
* Set the maximum duration that we allow for a connection with an unwriteable
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* vim:ts=4:sw=4:expandtab
|
||||
*
|
||||
* i3 - an improved dynamic tiling window manager
|
||||
* i3 - an improved tiling window manager
|
||||
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
|
||||
*
|
||||
* key_press.c: key press handler
|
||||
|
||||