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:

  1. set $<var> <output-name> — output aliases
  2. set $<var> <digit…> — workspace-name aliases (quoted or unquoted)
  3. 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.

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
--force off Overwrite existing files

Re-run init --force after plugging or unplugging a monitor to regenerate the per-output folders.

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).

Description
PyOutputHandler
Readme Apache-2.0 89 KiB
Languages
Python 97.9%
Shell 2.1%