# pi3-smart-workspace A small CLI helper for the [i3 window manager](https://i3wm.org/). Bound to `$mod+` 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: ```sh pip install pi3-smart-workspace ``` For local development, clone the repo and install in editable mode with the dev extras: ```sh 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 $ ` — output aliases 2. `set $ ` — workspace-name aliases (quoted or unquoted) 3. `workspace $ output $` — 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: ```sh 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`): ```i3config 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: ```sh 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: ```sh 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) ```i3config # 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 ```sh 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](https://github.com/landmaj/pi3-switch)) and Tony Crisci for the easy-to-use i3 Python library ([i3ipc-python](https://github.com/acrisci/i3ipc-python)).