Run logs of a public repository are public, and Gitea prints variables in them in clear text; only secrets are masked. Claude-Session: https://claude.ai/code/session_01GGe7kUhooMFuzY6GaDb2w9
pi3-smart-workspace
A small CLI helper for the i3 window manager. Bound to
$mod+<n> keys, it switches (or moves a container to) the N-th workspace
declared for the output your mouse cursor is currently on, rather than
i3's default behaviour of jumping to a globally-numbered workspace.
This makes multi-monitor i3 setups feel a lot more seamless: pressing
$mod+1 always lands you on the first workspace of whichever screen you're
pointing at.
Usage
usage: pi3-smart-workspace [-h] [-d] -i INDEX [-s] [-k]
Switch (or shift) to the N-th workspace on the output your cursor is on.
options:
-h, --help show this help message and exit
-d, --debug Print parsed state and re-raise exceptions.
Required:
-i INDEX, --index INDEX
The 1-based index into the workspaces declared for
the cursor's output.
Shift:
manipulate the active window
-s, --shift Move the focused container to the indexed workspace.
-k, --keep-with-it With --shift, also follow the container to the new
workspace.
Installation
Install from PyPI:
pip install pi3-smart-workspace
For local development, clone the repo and install in editable mode with the dev extras:
pip install -e ".[dev]"
pytest
Requires Python 3.14+ and a running i3 session.
i3 configuration
pi3-smart-workspace parses your live i3 config and looks for three
specific patterns:
set $<var> <output-name>— output aliasesset $<var> <digit…>— workspace-name aliases (quoted or unquoted)workspace $<var> output $<var>— bindings of workspaces to outputs
The order in which workspace … output … lines appear in your config
determines the N-th workspace for that output.
Bootstrap with init (recommended)
Instead of writing the config by hand, run:
pi3-smart-workspace init
This detects every active monitor via i3 and writes:
~/.config/i3/pi3-smart-workspace/
├── bindings.conf # $mod+N, $mod+Shift+N, $mod+Ctrl+N bindings
├── eDP-1/
│ └── workspaces.conf # workspaces 1..8 bound to eDP-1
├── HDMI-A-0/
│ └── workspaces.conf # workspaces 9..16 bound to HDMI-A-0
└── DP-1/
└── workspaces.conf # workspaces 17..24 bound to DP-1
Each output gets a contiguous range of global workspace numbers; the
local index (the number after the colon, e.g. 9:1) tells you "first
workspace on this monitor". Then add the include lines init prints to
your main i3 config and reload ($mod+Shift+r):
include ~/.config/i3/pi3-smart-workspace/bindings.conf
include ~/.config/i3/pi3-smart-workspace/*/workspaces.conf
i3's include directive needs i3 ≥ 4.20.
Useful flags:
| Flag | Default | Effect |
|---|---|---|
-n N / --count N |
8 | Workspaces per output |
--config-dir PATH |
~/.config/i3/pi3-smart-workspace |
Where to write |
--dry-run |
off | Print to stdout, don't touch the filesystem |
--sync |
off | Only write configs for outputs that don't have one yet |
--force |
off | Regenerate every file from scratch (renumbers workspaces) |
--prune |
off | Delete generated configs for outputs that are no longer active |
Plugging in a new monitor
Run:
pi3-smart-workspace init --sync
--sync writes a workspaces.conf only for the outputs that don't have
one yet, and gives the newcomer the lowest block of global workspace
numbers that isn't already taken. The monitors you already use keep the
numbers they have, so your muscle memory survives — which a plain
--force regeneration would not guarantee, since that allocates numbers
by i3's current output order and can shuffle everything.
Configs for monitors that are merely switched off are left in place and
their numbers stay reserved. Add --prune to delete them and free those
numbers for a new screen:
pi3-smart-workspace init --sync --prune
Files that don't carry the # Auto-generated by pi3-smart-workspace init
header are never overwritten or deleted, not even with --force — so
anything you hand-tune in that directory is safe.
Example config (manual)
# Displays
set $primary eDP
set $top HDMI-A-0
set $bottom HDMI2
# Workspaces
set $ws1 1:1
# ... and so on
set $ws{n} {n}:{n}
set $TopWs1 {n+1}:1
# ... and so on
set $TopWs{k} {n+1+k}:{k}
set $BottomWs1 {k+1}:1
# ... and so on
set $BottomWs{q} {k+1+q}:{q}
workspace $ws1 output $primary
# ... and so on
workspace $ws{n} output $primary
workspace $TopWs1 output $top
# ... and so on
workspace $TopWs{k} output $top
workspace $BottomWs1 output $bottom
# ... and so on
workspace $BottomWs{q} output $bottom
# Switch to workspace
bindsym $mod+1 exec --no-startup-id pi3-smart-workspace -i 1
bindsym $mod+2 exec --no-startup-id pi3-smart-workspace -i 2
bindsym $mod+3 exec --no-startup-id pi3-smart-workspace -i 3
bindsym $mod+4 exec --no-startup-id pi3-smart-workspace -i 4
bindsym $mod+5 exec --no-startup-id pi3-smart-workspace -i 5
bindsym $mod+6 exec --no-startup-id pi3-smart-workspace -i 6
bindsym $mod+7 exec --no-startup-id pi3-smart-workspace -i 7
bindsym $mod+8 exec --no-startup-id pi3-smart-workspace -i 8
# Move focused container to workspace
bindsym $mod+Shift+1 exec --no-startup-id pi3-smart-workspace -i 1 -s
bindsym $mod+Shift+2 exec --no-startup-id pi3-smart-workspace -i 2 -s
bindsym $mod+Shift+3 exec --no-startup-id pi3-smart-workspace -i 3 -s
bindsym $mod+Shift+4 exec --no-startup-id pi3-smart-workspace -i 4 -s
bindsym $mod+Shift+5 exec --no-startup-id pi3-smart-workspace -i 5 -s
bindsym $mod+Shift+6 exec --no-startup-id pi3-smart-workspace -i 6 -s
bindsym $mod+Shift+7 exec --no-startup-id pi3-smart-workspace -i 7 -s
bindsym $mod+Shift+8 exec --no-startup-id pi3-smart-workspace -i 8 -s
# Move to workspace with focused container
bindsym $mod+Ctrl+1 exec --no-startup-id pi3-smart-workspace -i 1 -sk
bindsym $mod+Ctrl+2 exec --no-startup-id pi3-smart-workspace -i 2 -sk
bindsym $mod+Ctrl+3 exec --no-startup-id pi3-smart-workspace -i 3 -sk
bindsym $mod+Ctrl+4 exec --no-startup-id pi3-smart-workspace -i 4 -sk
bindsym $mod+Ctrl+5 exec --no-startup-id pi3-smart-workspace -i 5 -sk
bindsym $mod+Ctrl+6 exec --no-startup-id pi3-smart-workspace -i 6 -sk
bindsym $mod+Ctrl+7 exec --no-startup-id pi3-smart-workspace -i 7 -sk
bindsym $mod+Ctrl+8 exec --no-startup-id pi3-smart-workspace -i 8 -sk
Development
pip install -e ".[dev]"
pytest # run the unit tests
python -m build # build sdist + wheel into dist/
twine check dist/* # validate the distributions
The unit tests cover the two pure functions (parse_workspaces_per_output
and find_output_at_cursor) and run without an X display, so they work in
CI. The i3-dispatch path needs a real multi-monitor session and is only
exercised manually.
Credits
Thanks to Michał Wieluński for the inspiration (pi3-switch) and Tony Crisci for the easy-to-use i3 Python library (i3ipc-python).