emacs update. improvements
[distro-setup] / i3-event-hook
index 01514a648d6017aa0b2f5bf58f9c6b38433946d7..bcee20e9d6077bd3aecb9cc5664a8842f606a533 100755 (executable)
@@ -1,26 +1,86 @@
 #!/usr/bin/env python3
 
+# I, Ian Kelling, follow the GNU license recommendations at
+# https://www.gnu.org/licenses/license-recommendations.en.html. They
+# recommend that small programs, < 300 lines, be licensed under the
+# Apache License 2.0. This file contains or is part of one or more small
+# programs. If a small program grows beyond 300 lines, I plan to change
+# to a recommended GPL license.
+
+# Copyright 2024 Ian Kelling
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+#     http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+
+# This gets rid of all single window containers.
+# If a single window container was nested in another,
+# it ignores that, but I don't generally expect to create them and
+# we change focus enough that we would kill them off.
+
+# Note: I spent a lot of time figuring out how to do this properly,
+# https://github.com/i3/i3/issues/3808 there are a bunch of links
+# 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 .
+
+
 import sys
 import os
 from i3ipc import Connection, Event
 from pprint import pprint
 
 
-def find_parent(i3, window_id):
+def find_workspace(i3, window_id):
     """
-        Find the parent of a given window id
+        Find the workspace of a given window id
     """
 
-    def finder(con, parent, gp):
+    def finder(con, workspace):
         if con.id == window_id:
-            return (parent, gp)
+            return (workspace)
         for node in con.nodes:
-            res = finder(node, con, parent)
+            res = finder(node, con if con and con.type == 'workspace' else workspace)
             if res:
                 return res
         return None
 
-    return finder(i3.get_tree(), None, None)
+    return finder(i3.get_tree(), None)
+
+
+def kill_single_win_containers(i3, e, node, parent):
+    if len(parent.nodes) == 1 and len(node.nodes) == 0:
+        print("d1: killing parent")
+        # parent is a single window container, kill it.
+
+        # Note: based on testing,
+        # i3 takes care of not calling this program for
+        # events which we create within it. Otherwise,
+        # we could create our disabling file here
+        # and delete it later if it wasn't already there.
+        i3.command('[con_id=%s] focus' % node.id)
+        i3.command('mark i3ha')
+        i3.command('focus parent')
+        i3.command('focus parent')
+        i3.command('mark i3hb')
+        i3.command('[con_mark="i3ha"] focus')
+        i3.command('move window to mark i3hb')
+        i3.command('unmark i3ha')
+        i3.command('unmark i3hb')
+        # back to our original focus
+        i3.command('[con_id=%s] focus' % e.container.id)
+    elif len(node.nodes) >= 1:
+        for child in node.nodes:
+            kill_single_win_containers(i3, e, child, node)
+
 
 
 def focus_hook(i3, e):
@@ -33,27 +93,36 @@ def focus_hook(i3, e):
     if os.path.isfile("/tmp/iank-i3-no-auto"):
         return
 
+    # I identify container vs a real windows by the fact that it has nodes.
+    # looking through the data, another notable difference is that it has
+    # 'window': None,
+    # 'window_type': None,
+
+    workspace = find_workspace(i3, e.container.id)
     # debugging
-    #pprint(vars(e))
+    #exit(0)
 
-    parent, gp = find_parent(i3, e.container.id)
+    if not workspace:
+        return
+    #pprint(vars(workspace))
+    #print()
+    for pnode in workspace.nodes:
+        # debugging
+        # if (len(pnode.nodes) >= 1):
+        #     print("pnodes: ", pnode.nodes)
 
-    # This gets rid of tabbed container with single windows.
-    #if (parent and gp and parent.layout == 'tabbed' and len(parent.nodes) == 1):
-    # This gets rid of all single window containers.
-    if (parent and gp and len(parent.nodes) == 1):
-        i3.command('mark i3ha')
-        i3.command('focus parent')
-        i3.command('focus parent')
-        i3.command('mark i3hb')
-        i3.command('[con_mark="i3ha"] focus')
-        i3.command('move window to mark i3hb')
-        i3.command('unmark i3ha')
-        i3.command('unmark i3hb')
+        for node in pnode.nodes:
+            kill_single_win_containers(i3, e, node, pnode)
 
 def main():
     i3 = Connection()
     i3.on(Event.WINDOW_FOCUS, focus_hook)
+    # if we don't have move, and we move a window out of a container,
+    # leaving behind a single window container, then we move it back, it
+    # will go into the container. We could expect that if we do it
+    # quickly, but it would be unexpected after a few seconds and we
+    # forget that it was a container.
+    i3.on(Event.WINDOW_MOVE, focus_hook)
     i3.main()