From dab96f8fa4c701db13ba734fa0c07b5d12fc8fae Mon Sep 17 00:00:00 2001 From: Ian Kelling Date: Wed, 5 Jun 2024 14:32:38 -0400 Subject: [PATCH] i3 improvements wip --- i3-event-hook | 61 +++++++++++++++++ i3-maybe-double-move | 35 ---------- i3-set-layout | 42 ++++++------ i3-split-maybe | 151 ++++++++++++++++++++++++------------------- i3-split-push | 87 +++++++++++++++++++++++++ i3-sway/common.conf | 45 +++++++------ i3-sway/i3.conf | 3 +- script-files | 8 ++- 8 files changed, 289 insertions(+), 143 deletions(-) create mode 100755 i3-event-hook delete mode 100755 i3-maybe-double-move create mode 100755 i3-split-push diff --git a/i3-event-hook b/i3-event-hook new file mode 100755 index 0000000..01514a6 --- /dev/null +++ b/i3-event-hook @@ -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 index 4f45238..0000000 --- a/i3-maybe-double-move +++ /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 diff --git a/i3-set-layout b/i3-set-layout index 89780d1..fc68666 100755 --- a/i3-set-layout +++ b/i3-set-layout @@ -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(): diff --git a/i3-split-maybe b/i3-split-maybe index dcb268a..d8bd54c 100755 --- a/i3-split-maybe +++ b/i3-split-maybe @@ -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 index 0000000..a9f76e5 --- /dev/null +++ b/i3-split-push @@ -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() diff --git a/i3-sway/common.conf b/i3-sway/common.conf index 2cc2d1c..fc3236b 100644 --- a/i3-sway/common.conf +++ b/i3-sway/common.conf @@ -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 diff --git a/i3-sway/i3.conf b/i3-sway/i3.conf index 737acc2..bf1aba8 100644 --- a/i3-sway/i3.conf +++ b/i3-sway/i3.conf @@ -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 diff --git a/script-files b/script-files index 2bc52a0..f4f4b93 100644 --- a/script-files +++ b/script-files @@ -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 -- 2.30.2