ws-direction

A picom plugin that tells your animation scripts which way a workspace switch went, which monitor the workspace is on, and where each workspace lies. Windows slide the right way, stay on their own monitor, and can show the workspaces a switch passes.

Workspace transitions are the smaller part of this project, and the part I'd ask picom about first. The plugin reads only the standard window manager properties (EWMH), so it isn't tied to i3. Every switch style needs five changes to picom: plugin variables (proposed as #1550), a small xrender fix (#1551), three fixes to script expressions, functions such as min, max, sin and cos, and the state an interrupted animation left. The last three are written and tested, not proposed yet. The 3D styles also need windows in perspective, and the overview and the switches that pass workspaces a few more general hooks. See the backlog.

For now it needs the picom fork; see Install. The source is on GitHub. The code was written with AI; see a note before you read on.

Two monitors under i3, in the slide style, on a real screen. Going to a higher workspace brings it in from the right on the portrait monitor, and from below on the landscape one. Going back reverses the slide. Pauses between switches are trimmed to the same length; the animations themselves are unedited.

Styles

The plugin only hands picom numbers. The animation itself is a picom animation script, so it can look however you like. Each style is one file, and one line in picom.conf picks it. Change the name and save; picom reloads by itself.

Recorded at 60 fps on my real screen, on a 16:9 monitor beside a vertical one, each window in a colour of its own: the vertical monitor switches to another workspace and back, then the landscape one. Pauses between switches are trimmed to the same length; the animations themselves are unedited.

Each style sets var-length, in seconds; make it larger for a slower switch. By default the workspaces lie along the short side of each monitor: up and down on a landscape monitor, sideways on a portrait one.

More styles

Not recorded yet. Each is picked the same way, by its name in the @include line. In every switch style, a switch that passes workspaces shows them on the way: going from 1 to 4, the windows of 2 and 3 come by.

pass-slide, pass-zoom-pan
Like slide and zoom-pan, but each workspace a switch passes adds a fifth of var-length, so a long switch doesn't become a blur.
pass-zoom-cascade
pass-zoom-pan, but the windows of the new workspace trail behind it one after another and land as the view zooms back in.

Cube, flip, paper and twirl are best with picom's glx or egl backend. xrender draws them too, but slower, and squeezed text can shimmer.

The overview

A key unfolds the monitor's workspaces into a strip of tiles that fits on the monitor. The arrow keys move through them, and a click on a window picks it. There are two overview styles: wall, which zooms out along the strip and dims the workspaces you aren't on, and unfold, which opens cube's box up into a flat strip. An overview style goes in a second rule, next to the switch style:

rules = ({
  match = "!override_redirect && window_type = 'normal'";
  @include "ws-direction/pass-zoom-pan.conf"
}, {
  match = "!override_redirect && window_type = 'normal'";
  @include "ws-direction/wall.conf"
});

The ws-direction command opens and closes it, from a key in i3. The lines are on the Install page.

What your scripts get

The plugin adds variables that scripts read like window-width. picom asks the plugin for their values when an animation starts, and only for scripts that use them.

plugin-ws-direction-direction
1 if the new workspace comes after the old one, -1 if it comes before, and 0 if the animation isn't part of a workspace switch on that monitor: a new window, the scratchpad, fullscreen, a window moved to another workspace.
plugin-ws-direction-distance
How many of the monitor's workspaces the switch moved: 1 to the next one, 2 when it passes one, and so on.
plugin-ws-direction-monitor-x, -y, -width, -height
The monitor that shows the window's workspace. Crop a slide to it, so it doesn't spill onto your other monitor.
-faces, -face, -index, -shown
How many workspaces the monitor has, how far the window's workspace is from the one the switch started on, where it is among them, and which one the monitor shows now. Styles that draw hidden workspaces, like cube, place windows by these.
-selected, -placed, -unfolded, -backdrop
Whether the monitor shows the focused workspace, whether the window really sits on its workspace's monitor, whether the monitor is unfolded into the overview, and whether the window is one of the plugin's backdrops.
-monitor-width-mm, -monitor-height-mm
The monitor's size in millimetres, so a style can set its perspective for the distance you really sit from the screen.

The plugin also starts three animation triggers of its own: ws-pass on every window of a monitor at a switch, so the workspaces in between can move too, and ws-unfold and ws-fold when the overview opens and closes.

A simplified part of the slide style, for a window on a landscape monitor:

# Start one monitor height away, on the side the switch
# came from, and move back to 0 in a quarter second.
var-pos = {
  duration = 0.25;
  start = "plugin-ws-direction-direction";
  end = 0;
};
offset-y = "var-pos * plugin-ws-direction-monitor-height";

# Cut the window off at the edges of its own monitor,
# so it never slides onto the other one.
crop-x = "plugin-ws-direction-monitor-x";
crop-y = "plugin-ws-direction-monitor-y";
crop-width = "plugin-ws-direction-monitor-width";
crop-height = "plugin-ws-direction-monitor-height";

The full list, and how to write a style, are in the README. Every style file explains how it works.

How it works

picom knows when a window shows or hides, but not why. The plugin fills that gap from outside picom. It reads the window manager's EWMH properties (_NET_CURRENT_DESKTOP, _NET_DESKTOP_NAMES and _NET_DESKTOP_VIEWPORT on the root window, _NET_WM_DESKTOP on each window) and the monitors from RandR, and it remembers the recent switches of every monitor.

Window managers don't follow a fixed order when they change these properties and show or hide windows. So the plugin doesn't guess from single events: when a window shows or hides, it matches that to a recent switch on the window's monitor. It reads the root window at most once per picom update, however many windows start an animation in it.

To draw workspaces that aren't shown, as cube and the overview do, picom keeps the last picture of each hidden window. After picom starts, it has none until a workspace has been shown once; ws-direction warm shows each one briefly to fill them in.

Limits

  • Tested with i3. The plugin uses only EWMH, so other window managers may work, but nobody has checked.
  • A hidden window is drawn as it looked when its workspace was last shown, and each kept picture takes as much memory as the window on screen.
  • When picom stops compositing for a fullscreen window (unredir-if-possible), it drops the kept pictures, and a switch made then has no animation. Set unredir-if-possible = false for the styles that draw hidden workspaces.
  • With picom's xrender backend, cascade can flicker: a window that is already moving in is sometimes not drawn for a frame or two. It comes from the xrender backend: glx doesn't have it, and neither does use-damage = false.