Commit Graph
5 Commits
Author SHA1 Message Date
LGUG2Z 41c32aad8e feat(wm): layouts
Every workspace started in BSP and stayed there. `komorebic change-layout` gives
the focused workspace one of the layouts of komorebi-layouts, `cycle-layout
next|previous` steps through them, and `flip-layout` mirrors the tiles along an
axis, on top of the flip there is. A flip mirrors the tiles, not the order of
the containers, and the directions of focus and move are flipped with it, so
`focus left` still goes to the tile on the left.

`toggle-tiling` turns the tiling of the focused workspace off and on: while it
is off, no window of the workspace is moved, and the windows that open are taken
in and tiled once it is on again.

`preselect-direction` reserves the place on one side of the focused window for
the next window: an empty container holds it, the layout gives it a tile, and
the next window takes it. `cancel-preselect` gives it back.

A new VM suite, vm-commands, after the spike's m8 script, runs every layout with
five, three, two and one windows against a real KWin with two outputs, and then
every komorebic command there is, checking after each that the daemon is alive,
answers, and has every window KWin has, and that KWin has the windows where the
daemon asked. Every command that comes later adds its line to that sweep.
2026-10-28 07:20:37 -07:00
LGUG2Z 1bbb534844 feat(wm): move and cycle containers
With the focus moving, the next thing a key has to do is move the focused
window. `komorebic move left|right|up|down` swaps the focused container with the
one whose tile is on that side, as the layout sees it, and `komorebic cycle-move
next|previous` with the next or the previous one in the workspace's order. The
workspace is tiled again, the window that moved keeps the focus, and the pointer
follows it.

The swap goes through the lockable sequence, so a lock stays with its index: the
container moved onto a locked index is locked in its place.

Moving towards a side with no window fails and moves nothing. Moving into the
next monitor comes with more than one monitor, and the floating layer's own move
with the floating layer.

The VM test moves a window in a real KWin and back, and checks that KWin has
every window where komorebi asked.
2026-10-28 07:20:37 -07:00
LGUG2Z 20f93ba8c4 feat(wm): focus in a direction
A tiling window manager is driven from the keyboard, and the first thing a key
has to do is move the focus. `komorebic focus left|right|up|down` gives the
focus to the window whose tile is on that side of the focused one, as the layout
sees it, and `komorebic cycle-focus next|previous` to the next or the previous
window in the workspace's order, wrapping around. Neither moves a window: the
focus changes, the tiles stay.

The pointer follows the focus, through KWin's "Move Mouse to Focus", unless
`mouse-follows-focus disable` (or `toggle-mouse-follows-focus`) says not to.

Focusing towards a side with no window fails and changes nothing. Crossing into
the next monitor, as komorebi does on the other platforms, comes with more than
one monitor; so do eager-focus and the parts that depend on the monocle,
maximized and floating windows.

The VM test now drives the focus with komorebic in a real KWin and checks that
KWin and komorebi agree on the focused window afterwards.
2026-10-28 07:20:37 -07:00
LGUG2Z e2d757bbb0 feat(wm): subscribe to notifications
A program such as a status bar has to follow the window manager, and so far it
could only ask: komorebi on Windows and on macOS sends notifications for that.
The daemon now keeps subscribers, each a Unix socket in its data directory that
a program listens on, and after each event and each message it handles, it
connects to every subscriber and writes the event or the message with the state
after it.

A subscriber can ask to be sent only what changed the state; a window that is
shown and a new subscription are always sent, so a subscriber has the state as
soon as it subscribes. A subscriber that cannot be connected to is dropped with
its socket file and not tried again, and a stopping daemon closes an empty
connection to each one.

komorebic gains subscribe-socket and unsubscribe-socket, and komorebi-client
subscribe and subscribe_with_options, which bind the socket and subscribe. The
tests subscribe through real sockets against the fake desktop: a shown window is
sent with the state that has it, a subscriber that filters is not sent a
question that changed nothing, and one that is gone is dropped.
2026-10-28 07:20:37 -07:00
LGUG2Z 6f657e53e3 feat(cli): komorebic
The daemon answers on its socket, but a person has no way to write a message on
it. komorebic is that way: each subcommand that asks something of the daemon is
one message, sent with komorebi-client, and a question's answer is printed.
komorebic is a separate program because the daemon takes no input but its
socket, so a key binding, a script and a terminal all drive it the same way.

It has what the daemon can answer so far: state, monitor-information, query,
retile and stop. Around them, start runs the komorebi on the PATH in the
background and passes it what follows a --, such as --fake-backend to try it
without a compositor; log follows the daemon's log, which the daemon now writes
to a file a day in LOG_DIR as well as to stdout; data-directory shows where the
socket is; completions prints a shell completion script. The hidden docgen
writes each subcommand's help to docs/cli, in fences that name their language
and wrapped at 100 columns whatever the width of the terminal, so that the pages
are the same wherever they are generated; just docgen runs it.

The user guide starts here: docs/index.md for KDE Plasma, and a page on running
komorebi. tests/cli.rs runs each subcommand against a socket the test serves in
place of the daemon. In the VM tests the guests get komorebic next to the
daemon, and vm-core-e2e reads the daemon's state with it (the windows it
manages, the one it focuses, after KWin restarts too) and stops it with
komorebic stop, which unloads the bridge and removes the socket.

In the VM test library, Kde.state asks again, a second (times the scale) after
an empty answer or a failed command, up to 10 times. The daemon drops a query
when it cannot take hold of the window manager within a second, and komorebic
then prints an empty line and succeeds; komorebic itself gives the daemon a
second to answer and fails when that is not enough. A daemon busy with the first
windows does either on a machine that is loaded or slow, and a suite that polled
the state died on JSONDecodeError. An answer that is not JSON still fails at
once.
2026-10-28 07:20:37 -07:00