Skip to content

Instantly share code, notes, and snippets.

@budRich
Last active October 23, 2021 17:06
Show Gist options
  • Save budRich/16765b5468201aa734d0ec1c0870fd0c to your computer and use it in GitHub Desktop.
Save budRich/16765b5468201aa734d0ec1c0870fd0c to your computer and use it in GitHub Desktop.
zen mode in i3wm

2021.10.23

Make windows floating before moving them to origin workspace.

2021.10.19

Use workspace name when appropriate.

2021.10.18

Fixed issue with calculating the next free workspace. When the zencontainer is created, if the last window of a workspace is used to create it. The current workspace will be the zen workspace.

2021.10.17

This script depends on i3list and i3var from i3ass.

When the command is triggered it will move the current window to a zen container. The zen container is a floating, tabbed, and zentered container on a clean workspace. If no zen container exist it will be created.

If the current window already is in the zen container it will get moved to the workspace it originally came from.


The previous version used two "ghost windows" to make a tiled container appear centered. But i figured why not use a floating container instead, script is a lot faster and experience better. Now it is also possible to set the height (also in percentage) of the container.


Available environment variables:

ZEN_WIDTH , ZEN_HEIGHT, ZEN_WORKSPACE, ZEN_VERBOSE

#!/bin/bash
# Copyright (C) 2017-2021 by budRich
#
# Permission to use, copy, modify, and/or
# distribute this software for any purpose with or
# without fee is hereby granted.
#
# i3zen - move current window to a "clean" workspace,
# put it in a centered, floating tabbed container.
#
# triggering the command on a window that is already
# "zen" will move it back to the workspace it came
# from.
#
# https://www.reddit.com/r/i3wm/comments/6x8ajm/oc_i3zen/
# https://www.reddit.com/r/unixporn/comments/6xbdtk/oci3_i3zen/
# https://gist.github.com/budRich/16765b5468201aa734d0ec1c0870fd0c
: "${ZEN_VERBOSE:=0}"
# ZEN_VERBOSE=1
# ZEN_WORKSPACE - the workspace number you want to
# use for zen leave it empty if you want the
# script to use the next empty ws.
: "${ZEN_WORKSPACE:=}"
# percentage of screen zen container will be when
# it is created
: "${ZEN_WIDTH:=60}"
: "${ZEN_HEIGHT:=90}"
ERM() { >&2 echo "$*" ;}
messy() {
((ZEN_VERBOSE)) && ERM "m $*"
_msgstring+="$*;"
}
declare -A i3list
eval "$(i3list -m centerzen)"
ws_zen=${i3list[WST]}
if [[ ! $ws_zen ]]; then
ws_raw=$(i3-msg -t get_workspaces)
new_zen=1
re='"num":([0-9]+)'
while [[ $ws_raw =~ $re ]]; do
ws_temp=${BASH_REMATCH[1]}
[[ $ZEN_WORKSPACE = "$ws_temp" ]] && taken=1
ws_raw=${ws_raw/\"num\":$ws_temp/}
((ws_temp > ws_free)) && ws_free=$ws_temp
done
[[ $ZEN_WORKSPACE && taken -ne 1 ]] \
&& ws_zen=$ZEN_WORKSPACE \
|| ws_zen=$((ws_free+1))
messy "[con_id=${i3list[AWC]}]" \
"move to workspace number $ws_zen," \
"floating disable," \
"split v, layout tabbed," \
"focus, focus parent"
messy "mark centerzen"
((ZEN_WIDTH < 0 || ZEN_WIDTH > 100)) && ZEN_WIDTH=100
((ZEN_HEIGHT < 0 || ZEN_HEIGHT > 100)) && ZEN_HEIGHT=100
width=$(( (i3list[WAW] * ZEN_WIDTH) / 100 ))
height=$(( (i3list[WAH] * ZEN_HEIGHT) / 100 ))
x=$(( i3list[WAX] + (i3list[WAW]-width) / 2 ))
y=$(( i3list[WAY] + (i3list[WAH]-height) / 2 ))
messy "[con_mark=centerzen] floating enable, workspace number $ws_zen"
messy "[con_id=${i3list[AWC]}] focus"
messy "[con_mark=centerzen]" \
"resize set $width $height ," \
"move position $x $y"
i3var set "zen${i3list[AWC]}" "${i3list[AWF]}:${i3list[WAN]}"
elif ((i3list[WSA] == ws_zen)); then
var_data=$(i3var get "zen${i3list[AWC]}")
[[ $var_data =~ (0|1):(.+) ]] && {
trg_ws=${BASH_REMATCH[2]}
((BASH_REMATCH[1])) \
&& trg_float_state=enable \
|| trg_float_state=disable
messy "[con_id=${i3list[AWC]}]" \
floating enable, \
"move to workspace $trg_ws," \
"floating $trg_float_state," \
"workspace $trg_ws"
i3var set "zen${i3list[AWC]}"
}
else
messy "[con_id=${i3list[AWC]}]" \
"floating disable," \
"move to mark centerzen," \
"focus, workspace number $ws_zen"
i3var set "zen${i3list[AWC]}" "${i3list[AWF]}:${i3list[WAN]}"
fi
((ZEN_VERBOSE)) || qflag=-q
[[ $_msgstring ]] && i3-msg ${qflag:-} "$_msgstring"
unset _msgstring
# the variable new_zen is only set when the zen container is created.
# here we test if that workspace still exist. If it doesn't we move
# the zencontainer back to that workspace.
((new_zen)) && {
re='"num":'"${i3list[WSA]}",
[[ $(i3-msg -t get_workspaces) =~ $re ]] || {
messy "[con_mark=centerzen]" \
move to workspace "${i3list[WAN]}", \
workspace "${i3list[WAN]}"
i3-msg ${qflag:-} "$_msgstring"
}
}
@budRich
Copy link
Author

budRich commented Oct 23, 2021

No worries ben. I am glad you gave me a reason to revisit this script, and I think I will add it back to i3ass . Regarding i3/i3gaps, I think the differences that matter for my scripts to work are all minor diffs between the output of i3-msg -t get_tree . For example. in i3-gaps there are some properties named "gaps": 15, or similar which ofc are not in the json from i3wm, but i think that there might be other differences as well, like the order of properties. as an example: in i3wm, "name":..., comes before "instance":... where it could be the otherway around in i3-gaps.

I usually just pipe the json through jq to prettify it and open it in a text editor to examine stuff like this, but i guess another good tool in this case would be diff. But it is somewhat tedious to work with this, since it would mean i need to have both versions of i3 installed, toggle between them to gather the output i need and stuff like that. The json is also a bit, awkward, since some properties if they are not set has a null value, f.i. "marks": while other properties are simply not present in the json if they are not set, f.i. "title_format":.

So getting i3ass to work on i3gaps is mostly a matter of finding these differences in the json output.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment