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.
10 # Copyright 2024 Ian Kelling
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
16 # http://www.apache.org/licenses/LICENSE-2.0
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.
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.
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 .
38 from i3ipc
import Connection
, Event
39 from pprint
import pprint
42 def find_parent(i3
, window_id
):
44 Find the parent of a given window id
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
)
56 return finder(i3
.get_tree(), None, None)
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.
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
)
86 def focus_hook(i3
, e
):
88 Set the layout/split for the currently
89 focused window to either vertical or
90 horizontal, depending on its width/height
93 if os
.path
.isfile("/tmp/iank-i3-no-auto"):
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
99 # 'window_type': None,
101 parent
, workspace
= find_parent(i3
, e
.container
.id)
107 #pprint(vars(workspace))
109 for pnode
in workspace
.nodes
:
111 # if (len(pnode.nodes) >= 1):
112 # print("pnodes: ", pnode.nodes)
114 for node
in pnode
.nodes
:
115 kill_single_win_containers(i3
, e
, node
, pnode
)
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
)
129 if __name__
== "__main__":