-#!/bin/bash
+#!/usr/bin/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 switch
-# its license to GPL.
+# programs. If a small program grows beyond 300 lines, I plan to change
+# to a recommended GPL license.
# Copyright 2024 Ian Kelling
# limitations under the License.
-set -e; . /usr/local/lib/bash-bear; set +e
-
-# We use this along with
-# /a/opt/i3-alternating-layout/alternating_layouts.py to anticipate when
-# we want to split/tab windows. There are 2 options of when to do it:
-# just after a window is created, or just before a window is
-# created.
+# This anticipates when we want to tab windows.
#
-# *Doing it after a window is created allows you to move a window into
+# There are 2 options of when to do it: just after a window is created,
+# or just before a window is created.
+#
+# * Doing it after a window is created allows you to move a window into
# the split that only has 1 window, whereas the other way doesn't. For
# my use cases, I think I don't really want to move it into the split if
-# it is a tabbed split.
+# it is a tabbed split. upon further reflection, I've determined that
+# single window containers are inherently confusing because they tend to
+# exist and get nested at unexpected times and then it is unclear how to
+# get rid of them and what is going on and the benefit is generally not
+# worth it. This command helps identify single window containers during
+# testing: /a/opt/i3ipc-python/examples/i3-debug-console.py
+#
+# * Doing it just before a windows is created, you need to call this
+# script, which means wrapping launch of a program, which I have no way
+# to do for all cases, I just do it for the common programs I have bound
+# to keys in i3.
#
-# *Doing it just before a windows is created, you need to
-# call this script, which means wrapping launch of a program, which I
-# have no way to do for all cases, I just do it for the common programs
-# I have bound to keys in i3.
+# * Note: doing it just before a window is created also leaves that split behind if
+# the window is closed, and I don't want single window splits hanging around,
+# so I close them out in
#
-# * Doing it after a window is created also leaves that split behind if
-# * the window is closed. I partially deal with that below.
+# I have a keybind which disables both, super+shift+u, it runs
+# /b/ds/i3-auto-layout-toggle
#
-# I have a keybind which disables both, it runs /b/ds/i3-auto-layout-toggle
-if [[ -e /tmp/iank-i3-no-auto ]]; then
- exit 0
-fi
+import sys
+import os
+from i3ipc import Connection, Event
+# for debugging
+#from pprint import pprint
+
+
+def find_parent(i3, window_id):
+ """
+ Find the parent of a given window id
+ """
+
+ def finder(con, parent, gp):
+ if con.id == window_id:
+ return (parent, gp)
+ for node in con.nodes:
+ res = finder(node, con, parent)
+ if res:
+ return res
+ return None
+
+ return finder(i3.get_tree(), None, None)
+
+
+def set_layout(i3):
+ """
+ Set the layout/split for the currently
+ focused window to either vertical or
+ horizontal, depending on its width/height
+ """
+
+ if os.path.isfile("/tmp/iank-i3-no-auto"):
+ return
+
+ win = i3.get_tree().find_focused()
+ parent, gp = find_parent(i3, win.id)
+
+
+ workspace = win.workspace()
+ #pprint(vars(workspace.rect))
+
+ screen_w = workspace.rect.width
+ screen_h = workspace.rect.height
+ half_w = screen_w / 2 + 1
+ half_h = screen_h / 2 + 1
+
+ w = win.rect.width
+ h = win.rect.height
+ ph = parent.rect.height
+ pw = parent.rect.width
+ if ( parent.layout == 'tabbed' or gp.layout == 'tabbed'):
+ return
-tmp=$(mktemp)
+ # debug
+ print('d2: len(parent.nodes):', len(parent.nodes),' > 1',
+ 'and ( ph:',ph,' > h + 10:',h + 10,' or pw:',pw,' > w:',w,' )',
+ 'and (screen_w:',screen_w,' < screen_h:',screen_h,' or w <= half_w:',half_w,')',
+ 'and h <= half_h:',half_h,')')
-i3-msg -t get_workspaces | jq ".[]| select(.focused==true) | .rect | .width, .height" >$tmp
+ # h + 10 because a tabbed window loses high compared to its parent.
+ # Note, it is redundant since we check above if the parent is tabbed,
+ # but just being cautious.
+ #
+ # condition in english
+ # the parent container is bigger
+ # and we are on a vertical screen or our window is <= half the screen width
+ # and we are <= half the screen height
+ if (len(parent.nodes) > 1
+ and ( ph > h + 10 or pw > w )
+ and ( screen_w < screen_h or w <= half_w )
+ and h <= half_h ):
+ i3.command('split vertical, layout tabbed')
+# print('d1: tabbed')
-{ read -r screen_width; read -r screen_height; } <$tmp
-i3-msg -t get_tree | jq -r ".. | select(.focused? == true).rect | .width, .height" >$tmp
-half_w=$(( screen_width / 2 + 100 ))
-half_h=$(( screen_height / 2 + 100 ))
+### further potential use cases:
+# We could automatically do a vertical split when there are 2 or 3
+# horizontal windows.
-{ read -r w; read -r h; } <$tmp
+# We could undo a vertical split when we close out windows.
+# elif (( w == screen_width )); then
+# # if we had 2 windows on screen, made them vertical splits, then
+# # closed one, it stays vertical split, but we want it horizontal at
+# # that point. So, make it horizontal here.
+# m i3-msg "split horizontal"
-if (( screen_width < 1920 )); then
- # haven't considered this case yet
- exit 0
-fi
+def main():
+ i3 = Connection()
+ set_layout(i3)
-if (( w < half_w && h < half_h )); then
- i3-msg "split vertical, layout tabbed"
-elif (( w == screen_width )); then
- :
- # if we had 2 windows on screen, made them vertical splits, then
- # closed one, it stays vertical split, but we want it horizontal at
- # that point. So, make it horizontal here.
- i3-msg "split horizontal"
-fi
-rm -f $tmp
+if __name__ == "__main__":
+ main()