Files
pi3-smart-workspace/README.md
T
Asger WeirsøeandClaude fa1de7fe16 Add init --sync and --prune for incremental monitor setup
`init` planned every file on every run and refused if any of them
existed, so attaching a new monitor could not be configured without
--force — which reallocated global workspace numbers by i3's current
output order and renumbered the monitors already in use.

- allocate_global_starts() keeps the block each known output already
  holds and gives new outputs the lowest free block; blocks of inactive
  outputs stay reserved.
- read_allocations()/parse_allocation() read those blocks back out of
  the generated files (inverse of render_workspaces_for_output).
- --sync writes only the missing files, --prune drops configs for
  outputs that are gone and frees their numbers.
- Files without the auto-generated header are never written or
  deleted, --force included.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-11 12:58:05 +02:00

221 lines
7.0 KiB
Markdown

# pi3-smart-workspace
A small CLI helper for the [i3 window manager](https://i3wm.org/). 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:
```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 $<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.
### 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)).