i3 improvements wip
authorIan Kelling <ian@iankelling.org>
Wed, 5 Jun 2024 18:32:38 +0000 (14:32 -0400)
committerIan Kelling <ian@iankelling.org>
Wed, 5 Jun 2024 18:32:38 +0000 (14:32 -0400)
i3-event-hook [new file with mode: 0755]
i3-maybe-double-move [deleted file]
i3-set-layout
i3-split-maybe
i3-split-push [new file with mode: 0755]
i3-sway/common.conf
i3-sway/i3.conf
script-files

diff --git a/i3-event-hook b/i3-event-hook
new file mode 100755 (executable)
index 0000000..01514a6
--- /dev/null
@@ -0,0 +1,61 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+from i3ipc import Connection, Event
+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 focus_hook(i3, e):
+    """
+        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
+
+    # debugging
+    #pprint(vars(e))
+
+    parent, gp = find_parent(i3, e.container.id)
+
+    # 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')
+
+def main():
+    i3 = Connection()
+    i3.on(Event.WINDOW_FOCUS, focus_hook)
+    i3.main()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/i3-maybe-double-move b/i3-maybe-double-move
deleted file mode 100755 (executable)
index 4f45238..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/bin/bash
-# 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.
-
-# 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.
-
-set -e; . /usr/local/lib/bash-bear; set +e
-
-direction="$1"
-
-# for testing, always do normal move
-i3-msg "move $direction"
-exit 0
-
-if i3-msg -t get_tree | jq -e -C '.nodes[].nodes[].nodes[].nodes[] | select((.nodes| length == 1) and (.nodes[0].focused == true))' &>/dev/null; then
-  i3-msg "move $direction; move $direction"
-else
-  i3-msg "move $direction"
-fi
index 89780d17be88643f7d6dc34f78738e3dc0a0b7ec..fc6866668a2480e7282046a9b12c318d8618b6ca 100755 (executable)
@@ -9,16 +9,16 @@ def find_parent(i3, window_id):
         Find the parent of a given window id
     """
 
-    def finder(con, parent):
+    def finder(con, parent, gp):
         if con.id == window_id:
-            return parent
+            return (parent, gp)
         for node in con.nodes:
-            res = finder(node, con)
+            res = finder(node, con, parent)
             if res:
                 return res
         return None
 
-    return finder(i3.get_tree(), None)
+    return finder(i3.get_tree(), None, None)
 
 
 def set_layout(i3):
@@ -28,10 +28,8 @@ def set_layout(i3):
         horizontal, depending on its width/height
     """
 
-
-
     win = i3.get_tree().find_focused()
-    parent = find_parent(i3, win.id)
+    parent, gp = find_parent(i3, win.id)
 
 
     # We never want to set the layout of a single window container,
@@ -39,21 +37,25 @@ def set_layout(i3):
     # this, it is stupid. So, eliminate single window container if we
     # are focused on one.
     #
-    # Alternatively, it could first focus the parent, but I think when
+    # Alternatively, we could first focus the parent, but I think when
     # layout changes, we expect new windows to be created within that
     # layout.
-    if (parent and len(parent.nodes) == 1):
-        gp = find_parent(i3, parent.id)
-        if (gp.nodes[0].id == parent.id):
-            if (gp.layout == 'splitv'):
-                i3.command('move down')
-            else: # splith or tabbed
-                i3.command('move right')
-        else:
-            if (gp.layout == 'splitv'):
-                i3.command('move up')
-            else:
-                i3.command('move left')
+    #
+    # Todo: if the direction we are moving has a split/tabbed container
+    # as a peer to parent, this will move our window into that container
+    # instead of what we want. And in fact, the whole logic below is
+    # incorrect and based on testing which did not realize that fact.
+    #
+    if (parent and gp and len(parent.nodes) == 1):
+        # https://unix.stackexchange.com/questions/173754/how-to-move-a-window-up-to-the-level-of-its-parent-window-in-i3wm
+        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')
     i3.command('layout ' + sys.argv[1])
 
 def main():
index dcb268a6bf13034315acf4d2fe6e0a3b1e1100d3..d8bd54c67745bfb06b073d6e8165b8395d7a68ec 100755 (executable)
@@ -1,33 +1,8 @@
-#!/bin/bash
-# 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.
+#!/usr/bin/python3
 
-# 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.
-
-
-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. 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
@@ -44,60 +19,100 @@ set -e; . /usr/local/lib/bash-bear; set +e
 # to do for all cases, I just do it for the common programs I have bound
 # to keys in i3.
 #
-# * Doing it after a window is created also leaves that split behind if
-#   the window is closed. I partially deal with that below.
+# * 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
 #
 # I have a keybind which disables both, it runs /b/ds/i3-auto-layout-toggle
 
 
-dry_run=false
-m() { "$@"; }
-d() {
-  if $dry_run; then
-    printf "%s\n" "$*"
-  fi
-}
-case $1 in
-  -n)
-    dry_run=true
-    m() { printf "%s\n" "$*"; }
-    ;;
-esac
+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)
+
 
-if [[ -e /tmp/iank-i3-no-auto ]]; then
-  exit 0
-fi
+    workspace = win.workspace()
+    #pprint(vars(workspace.rect))
 
+    screen_width = workspace.rect.width
+    screen_height = workspace.rect.height
+    half_w =  screen_width / 2 + 1
+    half_h = screen_height / 2 + 1
 
-tmp=$(mktemp)
+    w = win.rect.width
+    h = win.rect.height
+    ph = parent.rect.height
+    pw = parent.rect.width
 
-i3-msg -t get_workspaces | jq ".[]| select(.focused==true) | .rect | .width, .height" >$tmp
+    # There is potential for future use with < 1920, but I'm
+    # not thinking about it yet.
+    if ( screen_width < 1920 or parent.layout == 'tabbed' or gp.layout == 'tabbed'):
+        return
 
-{ read -r screen_width; read -r screen_height; } <$tmp
+    # print('d2: len(parent.nodes)', len(parent.nodes),' > 1',
+    #       'and ( ph ',ph,' > h + 10',h + 10,' or pw',pw,' > w',w,' )',
+    #       'and w <= half_w',half_w,'+ and h <= half_h',half_h)
 
-i3-msg -t get_tree | jq -r ".. | 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.
+    if (len(parent.nodes) > 1
+        and ( ph > h + 10 or pw > w )
+        and w <= half_w and h <= half_h ):
+       i3.command('split vertical, layout tabbed')
+#       print('d1: tabbed')
 
-half_w=$(( screen_width / 2  ))
-half_h=$(( screen_height / 2  ))
 
+### further potential use cases:
 
-{ read -r w; read -r h; } <$tmp
+# We could automatically do a vertical split when there are 2 or 3
+# horizontal windows.
 
-d w=$w , h=$h , half_w=$half_w , half_h=$half_h
+# 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
-  m 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.
-  m i3-msg "split horizontal"
-fi
 
-rm -f $tmp
+if __name__ == "__main__":
+    main()
diff --git a/i3-split-push b/i3-split-push
new file mode 100755 (executable)
index 0000000..a9f76e5
--- /dev/null
@@ -0,0 +1,87 @@
+#!/usr/bin/python3
+
+# There are only 2 cases where I want single window split containers.
+#
+# * just before creating a new window in it.
+#
+# * When I want to make 1 window a split container and bring an existing
+# window into it. In vanilla i3, this is super awkward. Usually, you are
+# starting out focused on the window you want to move into the
+# container. So, you focus the window which is to become a container,
+# split it, focus the window you want to join the container, move it
+# into that container. 4 actions, totally annoying. Lets simplify this
+# to 2 actions, a key to say what split we want, then a key to say which
+# direction to move the current window. Since we have a hook that erases
+# all single window split containers on focus change, we can consider a
+# single window split container to indicate the split we want.
+
+import sys
+from i3ipc import Connection, Event
+# for debugging
+from pprint import pprint
+import os
+
+
+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
+    """
+
+    direction = sys.argv[1]
+
+    win = i3.get_tree().find_focused()
+    parent, gp = find_parent(i3, win.id)
+    layout = parent.layout
+
+    if (parent and gp and len(parent.nodes) == 1):
+        i3.command('focus ' + direction)
+
+        exists = False
+        if os.path.exists('/tmp/iank-i3-no-auto'):
+            exists = True
+        else:
+            open('/tmp/iank-i3-no-auto', 'a')
+
+        if (layout == 'splith'):
+            i3.command('split horizontal')
+        elif (layout == 'splitv'):
+            i3.command('split vertical')
+        elif (layout == 'tabbed'):
+            i3.command('split vertical')
+            i3.command('layout tabbed')
+
+        i3.command('[con_id=%s] focus' % win.id)
+        i3.command('move ' + direction)
+        if (not exists):
+            os.remove('/tmp/iank-i3-no-auto')
+    else:
+        i3.command('move ' + direction)
+
+
+
+def main():
+    i3 = Connection()
+    set_layout(i3)
+
+
+if __name__ == "__main__":
+    main()
index 2cc2d1c881e756b48982f53ccc01a5d39bc2b957..fc3236be81cf7af0215fb5d3c5267759389889ec 100644 (file)
@@ -19,20 +19,20 @@ set $mod Mod4
 # for non-gui apps, use this.
 set $ex exec --no-startup-id
 
-bindsym $mod+2 $ex "/b/ds/i3-split-maybe"; exec "pavucontrol"
+bindsym $mod+2 $ex "i3-split-maybe"; exec "pavucontrol"
 # calling without -no-remote makes this to be the instance that links
 # will open in from other applications.
-bindsym $mod+3 $ex "/b/ds/i3-split-maybe"; exec "abrowser"
+bindsym $mod+3 $ex "i3-split-maybe"; exec "abrowser"
 # calling just abrowser mysteriously stopped working,
 # so I figured out this is how to get output, but then
 # it suddenly started working again.
 #bindsym $mod+3 exec "abrowser 2>&1 >/tmp/l"
 #bindsym $mod+3 exec "abrowser -no-remote -P sfw"
-bindsym $mod+4 $ex "/b/ds/i3-split-maybe"; exec "abrowser -no-remote -P firefox-main-profile"
+bindsym $mod+4 $ex "i3-split-maybe"; exec "abrowser -no-remote -P firefox-main-profile"
 # todo: figure out a stream delay & way to cut the stream.
 # settings, advanced, stream delay
 bindsym $mod+5 $ex "/a/bin/ds/stream-interlude"
-bindsym $mod+6 $ex "/b/ds/i3-split-maybe"; exec "/usr/local/bin/start-tor-browser"
+bindsym $mod+6 $ex "i3-split-maybe"; exec "/usr/local/bin/start-tor-browser"
 bindsym $mod+7 $ex "/a/bin/ds/laptop-xrandr"
 #bindsym $mod+6 $ex "/a/bin/redshift.sh"
 # bindsym $mod+equal $ex "t s w; t in"
@@ -45,7 +45,7 @@ bindsym $mod+1 focus parent
 bindsym $mod+shift+1 focus child
 # undo split: https://github.com/i3/i3/issues/3808
 bindsym $mod+grave floating toggle; floating toggle
-bindsym $mod+equal $ex "/a/exe/i3-set-layout splith"
+bindsym $mod+equal $ex "i3-set-layout splith"
 # move firefox to current workspace.
 # https://i3wm.org/docs/userguide.html#keybindings
 # get class with xprop, example output
@@ -58,37 +58,42 @@ bindsym $mod+e $ex i3-pull emacs
 bindsym $mod+shift+e unmark emacs; mark emacs
 bindsym $mod+r $ex "/a/bin/ds/xl"
 
-# todo, in newer i3, make this toggle split tabbed.
-bindsym $mod+t $ex "/a/exe/i3-set-layout splitv"
+bindsym $mod+t $ex "i3-set-layout splitv"
 #bindsym $mod+Shift+t move workspace to output up
 bindsym $mod+Shift+t move workspace to output right
 
 # todo: consider a command that moves a window, and erases any single
 # container window left behind.
 
-# todo: port /b/ds/i3-maybe-double-move into python.
-
 # todo: consider a command which alters things as if the current window
 # had been created into a single window split. For horizontal split,
 # this would be like: focus left, split vertical, focus right, move
 # left. With that, we could totally eliminate single window containers.
 
-bindsym $mod+g $ex "/a/exe/i3-set-layout tabbed"
+bindsym $mod+g $ex "i3-set-layout tabbed"
 
-bindsym $mod+shift+g $ex "/b/ds/i3-auto-layout-toggle"
 
 # Use Mouse+$mod to drag floating windows to their wanted position
 floating_modifier $mod
 
 bindsym $mod+u focus left; $ex "i3-mouse-warp"
+# i dont expect to use this much
+bindsym $mod+shift+u $ex "i3-auto-layout-toggle"
 bindsym $mod+i focus right; $ex "i3-mouse-warp"
 bindsym $mod+o focus up; $ex "i3-mouse-warp"
 bindsym $mod+p focus down; $ex "i3-mouse-warp"
 
-bindsym $mod+Left $ex "/a/exe/i3-maybe-double-move left"
-bindsym $mod+Right $ex "i3-maybe-double-move right"
-bindsym $mod+Up $ex "i3-maybe-double-move up"
-bindsym $mod+Down $ex "i3-maybe-double-move down"
+bindsym $mod+Left $ex "i3-split-push left"
+bindsym $mod+Right $ex "i3-split-push right"
+bindsym $mod+Up $ex "i3-split-push up"
+bindsym $mod+Down $ex "i3-split-push down"
+
+# for testing in case there is a problem with above.
+# these could be rebound to other things.
+bindsym $mod+shift+Left move left
+bindsym $mod+shift+Right move right
+bindsym $mod+shift+Up move up
+bindsym $mod+shift+Down move down
 
 bindsym $mod+Shift+a move container to workspace 4
 bindsym $mod+a workspace 4
@@ -114,7 +119,11 @@ bindsym $mod+v split vertical
 bindsym $mod+Shift+v split horizontal
 #
 ## temp for testing, add antying here
-##bindsym $mod+shift+g
+#bindsym $mod+shift+5
+
+
+
+
 bindsym $mod+b $ex i3-pull term
 bindsym $mod+shift+b unmark term; mark term
 # for use to cleanup extra emacs windows
@@ -161,9 +170,9 @@ bindcode $mod+shift+65 focus mode_toggle
 floating_modifier $mod
 
 bindsym $mod+shift+h $ex /b/ds/stream-clip hc
-bindsym $mod+j $ex "/b/ds/i3-split-maybe"; exec emacsclient -c
+bindsym $mod+j $ex "i3-split-maybe"; exec emacsclient -c
 bindsym $mod+shift+j $ex /b/ds/stream-clip up
-bindsym $mod+k $ex "/b/ds/i3-split-maybe"; exec konsole
+bindsym $mod+k $ex "i3-split-maybe"; exec konsole
 bindsym $mod+shift+k $ex /b/ds/stream-clip intro
 bindsym $mod+l $ex dmenu_run
 bindsym $mod+shift+l $ex /b/ds/stream-clip steady
index 737acc21eac1885606b66e94e33d96ad12ff01e9..bf1aba8a2011ab441375fd0983531e42a612a47d 100644 (file)
@@ -1,6 +1,7 @@
 # exit i3 (logs you out of your X session)
 bindsym $mod+Shift+o exec "i3-nagbar -t warning -m 'You pressed the exit shortcut. Do you really want to exit i3? This will end your X session.' -b 'Yes, exit i3' 'i3-msg exit'"
 
+bindsym $mod+Shift+i reload
 bindsym $mod+Shift+p restart
 
 
@@ -8,4 +9,4 @@ $ex copyq
 $ex dunst
 $ex /usr/lib/x86_64-linux-gnu/libexec/kdeconnectd
 # this dies when we restart i3.
-exec_always --no-startup-id alternating_layouts.py
+exec_always --no-startup-id i3-event-hook
index 2bc52a0c78e7f6abdb9979c1baa6654e777aed45..f4f4b9367501049b772eeed5bf879c2e276773b5 100644 (file)
@@ -46,7 +46,13 @@ my_bin_files=(
   prof-notify
   /a/bin/newns/newns
   /a/bin/fai/fai/config/distro-install-common/ethusb-static
-  /a/opt/i3-alternating-layout/alternating_layouts.py
+  i3-auto-layout-toggle
+  i3-event-hook
+  i3-mouse-warp
+  i3-pull
+  i3-set-layout
+  i3-split-maybe
+  i3-split-push
 )
 
 for f in /b/log-quiet/*; do