distro specific fixes
[distro-setup] / i3-event-hook
1 #!/usr/bin/env python3
2
3 # I, Ian Kelling, follow the GNU license recommendations at
4 # https://www.gnu.org/licenses/license-recommendations.en.html. They
5 # recommend that small programs, < 300 lines, be licensed under the
6 # Apache License 2.0. This file contains or is part of one or more small
7 # programs. If a small program grows beyond 300 lines, I plan to change
8 # to a recommended GPL license.
9
10 # Copyright 2024 Ian Kelling
11
12 # Licensed under the Apache License, Version 2.0 (the "License");
13 # you may not use this file except in compliance with the License.
14 # You may obtain a copy of the License at
15
16 # http://www.apache.org/licenses/LICENSE-2.0
17
18 # Unless required by applicable law or agreed to in writing, software
19 # distributed under the License is distributed on an "AS IS" BASIS,
20 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 # See the License for the specific language governing permissions and
22 # limitations under the License.
23
24
25
26 # This gets rid of all single window containers.
27 # If a single window container was nested in another,
28 # it ignores that, but I don't generally expect to create them and
29 # we change focus enough that we would kill them off.
30
31 # Note: I spent a lot of time figuring out how to do this properly,
32 # https://github.com/i3/i3/issues/3808 there are a bunch of links
33 # which suggest either float toggle; float toggle, which doesn't put windows back in the same place, or doing a move, which only actually works if you are moving in a direction which does not have a container there, else your window joins the container, and .
34
35
36 import sys
37 import os
38 from i3ipc import Connection, Event
39 from pprint import pprint
40
41
42 def find_parent(i3, window_id):
43 """
44 Find the parent of a given window id
45 """
46
47 def finder(con, parent, workspace):
48 if con.id == window_id:
49 return (parent, workspace)
50 for node in con.nodes:
51 res = finder(node, con, con if con and con.type == 'workspace' else workspace)
52 if res:
53 return res
54 return None
55
56 return finder(i3.get_tree(), None, None)
57
58
59 def kill_single_win_containers(i3, e, node, parent):
60 if len(parent.nodes) == 1 and len(node.nodes) == 0:
61 print("d1: killing parent")
62 # parent is a single window container, kill it.
63
64 # Note: based on testing,
65 # i3 takes care of not calling this program for
66 # events which we create within it. Otherwise,
67 # we could create our disabling file here
68 # and delete it later if it wasn't already there.
69 i3.command('[con_id=%s] focus' % node.id)
70 i3.command('mark i3ha')
71 i3.command('focus parent')
72 i3.command('focus parent')
73 i3.command('mark i3hb')
74 i3.command('[con_mark="i3ha"] focus')
75 i3.command('move window to mark i3hb')
76 i3.command('unmark i3ha')
77 i3.command('unmark i3hb')
78 # back to our original focus
79 i3.command('[con_id=%s] focus' % e.container.id)
80 elif len(node.nodes) >= 1:
81 for child in node.nodes:
82 kill_single_win_containers(i3, e, child, node)
83
84
85
86 def focus_hook(i3, e):
87 """
88 Set the layout/split for the currently
89 focused window to either vertical or
90 horizontal, depending on its width/height
91 """
92
93 if os.path.isfile("/tmp/iank-i3-no-auto"):
94 return
95
96 # I identify container vs a real windows by the fact that it has nodes.
97 # looking through the data, another notable difference is that it has
98 # 'window': None,
99 # 'window_type': None,
100
101 parent, workspace = find_parent(i3, e.container.id)
102 # debugging
103 #exit(0)
104
105 if not workspace:
106 return
107 #pprint(vars(workspace))
108 #print()
109 for pnode in workspace.nodes:
110 # debugging
111 # if (len(pnode.nodes) >= 1):
112 # print("pnodes: ", pnode.nodes)
113
114 for node in pnode.nodes:
115 kill_single_win_containers(i3, e, node, pnode)
116
117 def main():
118 i3 = Connection()
119 i3.on(Event.WINDOW_FOCUS, focus_hook)
120 # if we don't have move, and we move a window out of a container,
121 # leaving behind a single window container, then we move it back, it
122 # will go into the container. We could expect that if we do it
123 # quickly, but it would be unexpected after a few seconds and we
124 # forget that it was a container.
125 i3.on(Event.WINDOW_MOVE, focus_hook)
126 i3.main()
127
128
129 if __name__ == "__main__":
130 main()