mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-11-14 21:04:04 +01:00
21017b573f73e0b15c0cbeaff153751bc7c46921
56
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
21017b573f |
feat(wm): initial window placement rules
A window that opens goes after the focused container. A workspace can now say
another place for its windows: the primary or the secondary tile, before or
after the focused container, the end of the list, or a place by number, counting
from 1. The place is for every window, or for the windows that match a rule on
their program, app id, title or path; the rules are the conditions the ignore
and manage rules are made of, and the first that matches in key order decides. A
place that does not exist, such as the secondary tile of a workspace with one
container, is the default place.
A place is read from a name, a number or text that is a number. The
documentation of the setting shows a number and a map's keys can only be text,
and komorebi for Windows (
|
||
|
|
e563988258 |
feat(wm): fullscreen is left alone
A fullscreen window covers its monitor on purpose. Moving it would take it out of fullscreen, or be ignored, so Window::set_position does nothing for a fullscreen window, and every move the window manager makes goes through it. The bridge reports KWin's fullScreenChanged as a fullscreen event, and the table keeps what it says, which is what the window manager asks. The window stays in its container, and the others are tiled as if it were not fullscreen. When it comes back from fullscreen, KWin gives it the frame it had before, which may not be its tile any more, so the workspace is tiled again. Section E of the features suite checks it in a real Plasma session: a fullscreen window covers the monitor, a window that opens does not take it out of fullscreen, and it is in a tile that overlaps no other when it comes back. |
||
|
|
d6ad3b4292 |
feat(wm): a window the user maximizes
KWin maximizes a window for the user as well as for komorebi: the title bar, a double click and a shortcut do it, and nobody asks the window manager. A window that stayed a tile would be asked to move at the next retile, so komorebi has to know. The bridge reports KWin's maximizedChanged as a maximized event, true only when the window is maximized both ways. The table turns it into a MaximizeChange, and adopt_maximize_change takes the window out of the layout as toggle-maximize does, or, when the user restores it, puts it back in a container of its own at the index it came from. A window that is already maximized when komorebi starts, or that opens maximized, is the workspace's maximized window from the start. komorebi's own maximize is reported too and finds the workspace as it left it, so it changes nothing. While a window is maximized, what is asked of the layout does nothing, by the same rule as for toggle-maximize. A maximized window of a monitor that is unplugged goes to the monitor that is left and stays maximized: evacuate_monitor makes it the maximized window of the workspace it goes to, and the maximized window that workspace had goes back to its tile. Until now it came back as a tile. vm-user-maximize maximizes windows with kdotool in a headless KWin, and checks the daemon's state, the frames, that the layout commands do nothing, and that toggle-maximize and the user's restore both put the window back in its tile. |
||
|
|
0210ec285a |
feat(kwin): sleep, wake and lock
While the screen is locked or the machine is asleep, KWin sends its clients no frame callbacks, so nothing komorebi asked for in that time is done: a window that something else moved stays where it was moved, and the monitors can come back in another order or at another size. Nothing told komorebi it had happened, and it kept the layout it had until the next event. kwin::power now watches for the two ends of it: logind's PrepareForSleep on the system bus, which only logind may send, and the lock screen's ActiveChanged on the session bus. When the machine wakes (2.5 seconds after it, while the outputs are found one after another) or the screen is unlocked (0.6 seconds after it, once the lock screen has gone), the monitor reconciliator gets a Wake and calls wake_up: it reconciles the monitors as for an unplug, reads their sizes, follows the windows to the monitors they are on and tiles every monitor, unless the window manager is paused. Going to sleep and locking do nothing, and a machine with no system bus or a session with no lock screen runs without them. The lock screen is a window of KWin's that the bridge already does not report as an application window, so it has no tile. The bridge also compares KWin's active window with the one it last announced once a second, because the window that is active again after an unlock is not always announced. vm-lock-sleep locks and unlocks a real Plasma session and checks that the windows come back to their tiles; a real suspend cannot be tested in the QEMU guest, only the signal wiring, which tests/power.rs covers on private buses. vm-lock-sleep waits for the panel's room before it opens the windows, so that they are tiled once, and compares the window that was moved with the tile the daemon has for it when it looks, give or take a cell of the terminal's font, and not with the frames read earlier: the room can come while the screen is locked, and the tiles read before are then not the ones. The wait is not a check, because a session on a machine as slow as TCG may never take the room. |
||
|
|
40eec2f6be |
feat(wm): follow a window moved to another output
A window can be moved to another output by something other than komorebi: a KWin shortcut that sends it to the next screen, the window menu, an application. komorebi had it in a container of the old monitor, whose tile is on a screen the window is not on, so it sat untiled where it landed. The bridge now reports a window's output changing, with the frame the window has by then, since the signal and the frame arrive in no fixed order, and the table makes it an OutputChange event. The window manager handles it with WindowManager::follow_windows_to_their_monitors: every container whose focused window, and every floating window, is on another monitor than the one it has it on moves whole to the workspace of the same number on the monitor that holds the middle of its frame, and the monitors that changed are tiled. Every window is looked at, not only the one in the event, and a move komorebi made itself is in its state already and finds nothing to do. Nothing is followed while the user drags a window: the end of the drag decides where that window goes, so the method returns at once while a move or a resize by the user is going on. KWin also moves windows for a moment after outputs come and go, so the monitor reconciliator looks again 1.5 seconds after each notification, where it used to trust what it found at once. vm-hotplug sends windows over with kdotool as well as with komorebic. |
||
|
|
f530318cee |
feat(wm): outputs unplugged and plugged back
KWin takes the windows of an output that goes to the outputs that are left and puts them where it likes, so komorebi's tiles for them were on a screen that was gone and the windows sat untiled where they landed. The monitor reconciliator now deals with a change in the set of monitors: the table announces it as a display connection change instead of a resize, and reconcile_display_change reads the monitors again, keeps each one that went (workspaces, layouts, paddings, offsets) in a cache, has the window manager move what was on it to the monitors that are left, and removes it. The move is WindowManager::evacuate_monitor, in the new reconcile module: each container and floating window goes to the workspace of the same number on the monitor that holds the middle of its window, or on the first monitor that is left when it is on none, and the workspaces that changed are tiled. A stack goes as one container, and a monocle and a maximized window come as ordinary containers. A monitor that is plugged in takes what was cached under its serial number id, less the windows that closed or that the window manager has elsewhere by now, so it comes back with its settings and none of the windows it had. A monitor komorebi never saw gets workspaces of its own, and when the compositor reports no output at all nothing is done. The workspace rules of a monitor that goes are dropped, and those of the workspaces of one that comes back apply again. komorebi.json fills the cache as well: a monitor that has the entry its display index preference names is kept with it, and so is the entry of each preference whose monitor is not connected, which that monitor takes when it is plugged in. vm-hotplug now sends two windows to the other monitor with komorebic, disables that output, enables it again and sends a window over once more. |
||
|
|
c8dc92a236 |
feat(wm): a monitor changes size
When an output gets another resolution or scale, or a panel starts, grows or hides, the work area the layout fills is not the one komorebi tiled in, and nothing moved the windows until something else tiled them again. The bridge now sends the outputs, with their geometry and the work area of the desktop each shows, when KWin says they changed, when a dock window opens, closes or moves, and once a second, because KWin has no signal for the room a panel leaves. The table turns a monitor whose size or work area changed into a monitor notification. It is not a window event, since it is about no window: it goes to the monitor reconciliator, a thread that takes the window manager's lock as the threads of events and messages do, reads the size and work area of every monitor again and tiles the workspace in front of the focused monitor. Another monitor's windows keep their tiles until a retile. Every subscriber is notified of it, whether or not the state changed. vm-hotplug starts with the scale of one of two outputs changing under a running daemon. The VM library gets kde.outputs, which lists, disables, enables and scales outputs, for the commits that unplug and plug them. |
||
|
|
c03e5d2970 |
feat(wm): the neighbours follow the drag
While the user drags an edge of a tiled window, the tiles next to it follow, and not only once it is let go. KWin reports every step of a resize with the frame the pointer asks for. The bridge passes one on every 16 ms at most, the newest, with a single-shot timer for the last one of a burst, and the window manager keeps at most one step waiting, since each says where the edge is now. ResizeStart keeps the layout as it is when the user takes hold (LiveResize), and every step and the end are worked out from it: a drag ends the same with its steps as without them, and one that is taken back puts the neighbors back. The dragged window is the user's until they let go, so Window::set_position leaves the windows being resized alone, whatever tiles the workspace. vm-live-resize holds the edge part way and all the way and reads the neighbors while the button is down. With the neighbors following, KWin's window snapping pulled a dragged edge 9 pixels past the pointer in vm-drag-resize, since the gap between two tiles is inside its default zone, so both drag suites turn snapping off; PLATFORM.md and drag-resize.md say so. Like vm-drag-resize, it drags against the daemon the module starts with the session, and reads the drags that daemon has handled from the unit's journal. vm-live-resize waits for the panel's room (Kde.wait_for_panel_room) before it opens the windows. The daemon learned of the room while the pointer was down in a first case that had begun without it, and the retile that came with it moved the neighbor, not the pointer. |
||
|
|
af560f47d7 |
feat(wm): resize by dragging an edge
Dragging an edge or a corner of a tiled window resizes its tile when the user lets go: each edge of the tile moves by what the pointer moved it, the neighbors give up or take the room, and the window is put on its tile. A drag is measured from the frame the window had when the user took hold of it, edge by edge, and resizes the container of the window that was dragged, through resize_window and so within its bound (no tile below a tenth of the work area). KWin reports the window's frame all through a drag, so the table keeps the frame of the start for the window manager (Backend::drag_origin). At the end the frame can still be short of the pointer, because KWin changes it once the client has drawn at the new size; the bridge keeps the frame the last step of the drag asked for and sends it as `requested`, and the end of a resize is that frame. The window of a stack has the tile of the stack; a floating window and the window of a monocle have none, and are left as the user made them. The end of a resize is read apart from the window's frame, and that deviates from the spike. The spike stores the frame the last step asked for as the window's own frame, where the next frame event replaces it. KWin sends one a millisecond after the end, with what the client has drawn so far (660 pixels of the 672 asked for), and a window manager that is busy with an earlier event reads that frame when it handles the end: a tile moved by 60 pixels for a drag of 72. Here the table keeps the end apart (Table::drag_end, and Backend::drag_end for the window manager, None by default), ResizeEnd reads it as ResizeStart reads drag_origin, and the window's frame stays the one KWin reports. A resize that sent no step ends at the frame. The order that failed is a test of the KWin backend with the real table and window manager, in which the tile took 140 of the 200 pixels without this. komorebi-layouts at the pinned revision has the fix of LGUG2Z/komorebi#1765, so nothing is vendored, and docs/drag-resize.md says what a drag does and why each rule is there. vm-drag-resize drags edges and corners of three tiles with the pointer that the session node has (komorebi_vm.Pointer), whose moves are batched in the guest. The suite drags against the daemon the module starts with the session, and reads the drags that daemon has handled from the unit's journal. vm-drag-resize waits, for 30 seconds at most, for the daemon to have the room KWin takes for the panel before it opens the windows (Kde.wait_for_panel_room). The panel moved every tile in the middle of a case that began before the daemon learned of it, which showed as an edge that was not dragged. It is a wait and not a check, since a session that is too slow may never take the room. |
||
|
|
f5933d1ff8 |
feat(wm): drag to swap
A window the user drags with the pointer onto the tile of another window swaps places with it, and a window let go where there is no tile goes back to its own. It is the first thing komorebi does about what the user does with the pointer. KWin reports the start and the end of a drag on the window (interactiveMoveResizeStarted and Finished). The bridge sends both with the window's frame, and the end with the position of the pointer: the window manager asks where the pointer is right after a drag, and when the bridge does not answer that in time, the table's last position is the one the drag ended at. The table turns a move into MoveStart and MoveEnd; a resize raises nothing yet. The bridge also stops asking KWin again for the position komorebi gave the window, which is the user's now. Where the window is let go decides the rest. On another monitor it goes there, with its stack, in the place of the container under the pointer. With the window container behavior Append it joins the container under the pointer instead of swapping with it, and with the float override on it floats where it was let go. A terminal that keeps its frame a few pixels short of its tile is not taken for a move on its own monitor, as in the spike; the usage page says so. |
||
|
|
2b116ea2a3 |
feat(kwin): wallpapers for monitors and workspaces
Plasma keeps one wallpaper for each screen, not for each virtual desktop, so a workspace that wants a picture of its own has nowhere to keep it in the shell. komorebi sets the wallpaper of a screen when a workspace comes into view: the workspace's own, else its monitor's. It does so however the workspace came into view (a komorebic command, a pager, an application raising a window) and for the workspace in front when it starts. A monitor and a workspace with no wallpaper are left alone. The Plasma shell sets it over D-Bus, with the call plasma-apply-wallpaperimage makes, but for one screen. Backend::set_wallpaper asks for it every time a workspace comes into view, and the KWin backend asks the shell what the screen shows first, so a picture the user changed by hand is put back and one that is already right is not touched. The request runs in the background, so switching workspaces never waits for the shell, and goes on trying for about half a minute when the shell is not up yet. A wallpaper also gives the borders the colors of the palette made from its picture, unless it says generate_theme is false, and theme_options choose the variant and the color each border takes. The colors change before the workspace is shown, so its borders are right at once. komorebi.json takes the wallpaper of a monitor and the wallpaper of a workspace, and a file that is read again shows the wallpaper of the workspace each monitor has in front at once. vm-wallpaper runs against the real shell: set at start and by workspace, a switch komorebi did not make, the colors of the borders, a wallpaper changed by hand, the palette cache, and komorebi that starts before the shell. |
||
|
|
e6ddb5a7c9 |
feat(wm): themes for the borders
A theme gives the borders their colors, in place of choosing the color of each kind of window: a Catppuccin theme, a Base16 theme or a palette of one's own, with the color it gives each of the six kinds of border and its default for the ones it does not name. The Theme message puts the six colors where the border manager keeps them and asks for the look again. The palette of a picture is made by the same generator as komorebi for Windows, which takes seconds for a large picture, so each one is kept in the cache directory, in a file named for the picture, a hash of its path, size and time of last change, and the variant: the palette of a picture painted over, or of one of the same name in another folder, is made again and not mistaken. The themes come from komorebi-themes at the revision komorebi-layouts is at. It pulls egui for its types, which the license list, the sources and the advisories of deny.toml now have to allow, and the colors the border manager keeps are the themes' own again. A subscriber that asks for the changes of the state only is sent a Theme message all the same: the colors of the borders are not part of the state, so a theme never changes it, and the subscriber would not hear of the colors at all. komorebi.json takes border_colours, a color for each kind of window as red, green and blue or as a hex color, and a theme, which wins over them. |
||
|
|
9a0693f5d1 |
feat(kwin): focus follows mouse through KWin's focus policy
KWin has a focus policy that gives the focus to the window under the pointer, so komorebi asks for it and follows where the focus goes, as it does for any change of focus. Both implementations komorebi for Windows names are the same thing here. The KWin backend sets the focus policy and no delay in the user's kwinrc and has KWin read it, and puts the two keys back as the user had them when it is turned off and, with every other setting, when komorebi exits. The state of the daemon says whether it is on. komorebic gets focus-follows-mouse and toggle-focus-follows-mouse. The session node gets a pointer that a test drives from inside the guest: the program komorebi-pointer makes an absolute device with uinput, and komorebi_vm.Pointer sends it commands. vm-focus-follows-mouse moves it over each of two windows and checks the focus, then the policy that is put back. komorebi.json takes focus_follows_mouse, which is on with Windows or Komorebi, and a file that leaves it out turns it off and gives the user's focus policy back. Loading a configuration asks the backend for the focus policy now, so the tests that load one do it with a backend installed. |
||
|
|
fa1e822e44 |
feat(wm): transparency for the windows without the focus
The window in front of every container is see-through, unless its container has the focus on the focused monitor, so that the window being worked in stands out. The opacity of a window is a property KWin has: komorebi sets it through the bridge, as a fraction of 255, and KWin draws the window so. The window manager keeps the opacity it gave each window and, after every event and message, sends only the difference, makes a window opaque again when it should no longer be see-through, and makes every one opaque when it stops. Windows an ignore rule covers, and workspaces that do not tile, are left as they are. The patched border effect already draws a window again when its opacity changes, so the border follows it. komorebic gets transparency, transparency-alpha and toggle-transparency, and the state of the daemon says what the settings are. vm-features checks the pixels in the middle of a focused window and of one that is not. A monocled window is opaque on the focused monitor and see-through on another. komorebi.json takes transparency, transparency_alpha and transparency_ignore_rules. |
||
|
|
abfa8c6a5c |
feat(kwin): easing curves for animations
An animation that moves a window at one pace all the way looks flat, and the stock Geometry Change has one curve, an exponential ease out. The patched effect reads a Curve setting, the name of one of Qt's easing curves. komorebi's styles are those curves with Ease in front, so the look the backend is asked for names the curve as Qt does, and the effect's settings have it next to the duration. A cubic Bezier, which only a configuration can say, is not one of Qt's curves and is an ease out. With the stock effect every style is that ease out. komorebic gets animation-style, for every animation or the movement of windows alone. vm-features puts two curves a few seconds into a 30 s move of the same window and checks that they are in different places. The animation key of komorebi.json takes a style too, for every kind of animation or for one. |
||
|
|
4fa8108110 |
feat(kwin): animation through the Geometry Change effect
Windows that jump to their new places are hard to follow. komorebi animates nothing itself: KWin's Geometry Change effect animates a window that a script moves, and komorebi tells it how long that takes. The look the window manager asks the backend for now has an animation, a duration that the settings give for every kind of animation or, when one is set for the movement of windows, for that one. The KWin backend writes the effect's settings into the user's kwinrc (a duration, and no cross-fading, since a window going to its place changes where it is and not what it shows), loads the effect when animation is on, and unloads it and puts the settings back when it is off or komorebi exits. komorebic gets animation, animation-duration and animation-fps. The module installs the effect (effects.animation), and the session node sets the variable that makes KWin load effects that animate under software rendering. vm-look reads the screen a moment after a flip of the layout, with a 30 s animation and without one. The commands that change a stack turn the animation off while they run, as komorebi does elsewhere, and komorebi.json takes an animation key (enabled, duration and fps, each for every kind of animation or for one). |
||
|
|
22c14ba5be |
feat(kwin): a border color for each kind of window
The stock KDE Rounded Corners has one color for the active window and one for the others, and komorebi colors a window by what it is to the layout: the focused window, the others, a locked container that does not have the focus, a floating window that has it, a stack, a monocle. komorebi's build of the effect takes a color for each window over D-Bus. After every event and message the border manager works out the kind of each window and tells the backend, which sends the effect what changed, and tells it that no window has a color when borders are off. Without that build the borders are the look's two colors, and the backend says so once. komorebic gets border-colour. The fake desktop keeps the colors it was told, and gets window_commands, which leaves the look out of the list of what the window manager asked for. vm-features reads the colors of the kinds of window in a real session. The colors of a stack and of a monocled window are read from the fake and, in vm-features, from the pixels. The border_colours key of komorebi.json is not here: its colors are written as the Colour of komorebi-themes, which the themes (49) bring, and 49 adds the key. The maximize check of vm-features, which was the whole suite, reads a pixel a quarter of the way into the window and not the middle one: komorebi puts the pointer in the middle of the window it focuses, and a screenshot has the arrow on it, white. It read the arrow on most runs once the border effect was loaded. vm-features waits for the panel's room too, where it starts the daemon for its sections B to E: the border colors of section C are read from the pixels around a window, which the panel can cover. |
||
|
|
2c35e4bb6f |
feat(kwin): borders drawn by the compositor
komorebi draws no border. KWin's KDE Rounded Corners effect does, outside each window in the gap the layout leaves, and komorebi says how it looks. The window manager describes a Look (a border with a width, a radius and two colors) and asks the backend for it with Backend::apply_look after every event, message and retile. The backend answers whether borders are being drawn, and the layout leaves room around the windows only while they are: a border with no effect to draw it takes none. The KWin backend writes the effect's settings into the user's kwinrc, with every inner outline off (the effect's default draws one over the title bar), loads the effect when borders are on and has it read them again. It unloads the effect and puts the user's settings back when the borders go or komorebi exits. The module installs the effect (effects.borders), which is built to stay unloaded until komorebi asks, and vm-look reads the pixels around two windows in a real session. The session library of the VM tests also waits for the panel of the Plasma shell now: a window komorebi tiles before the panel is made is tiled under it, which hides the bottom of its border from a screenshot. Kde.wait_for_panel_room waits until the daemon's work area is not the whole of its monitor, which is the daemon having learned that the panel takes room, and vm-look calls it where the daemon starts, before the windows open. The shell makes its panel and KWin takes room for it a while later, which the daemon learns of up to a second after, so a daemon that tiles in between puts the windows under the panel and tiles them again, and a screenshot taken between the two shows frames as high as the screen. It is a wait and not a check, since a session that is too slow, as TCG often is, may never take the room. komorebic gets border, border-width and border-offset, and visible-windows. komorebi.json takes the same settings as border, border_width and border_offset, and border_radius, which has no command. The module warns when config asks for borders and the effect is not installed. |
||
|
|
efcc90d279 |
feat(wm): workspace rules
A workspace rule sends the windows of an application to a workspace: an initial rule once, when komorebi first sees the window, and a permanent rule every time, so that a window the user takes away goes back. A rule is a rule on the window's executable, class, title or path, with a monitor and a workspace index, or the name of a workspace. It comes from komorebi.json (workspace_rules and initial_workspace_rules of a workspace, made again from the file each time it is read) or from komorebic (workspace-rule, initial-workspace-rule, their named forms, and the commands that clear and enforce them). The window manager looks at the rules before it handles each event about a window it manages, and again when the configuration is loaded, so the windows that were open when the daemon started are sent too. A window that leaves the workspace in front is taken out of the layout and hidden, as the back of a stack is, and it is put on the desktop of its new workspace, which is what it comes back to and is tiled on when that workspace is shown. A window that moves between workspaces that are not in front is put on the desktop of its new workspace as well. A floating window that goes to another monitor is moved onto it, and a workspace that does not exist yet is made. |
||
|
|
c6874ff5db |
feat(cli): quickstart, autostart and the config commands
komorebic quickstart writes an example komorebi.json and applications.json into the config home, asking before it writes over a file, so a new user has a configuration to start from. komorebic enable-autostart writes the XDG autostart entry that has Plasma start komorebi at login, with the komorebi.json it is given if any, and disable-autostart takes it away; the NixOS module does the same job for a machine that has it. komorebic replace-configuration makes the running daemon start over from another komorebi.json: it pauses its window manager and brings back the windows it hid, the back of a stack, so that none is out of sight for the new one to miss, then makes the window manager again from the file, with the monitors and the windows the backend has, and watches the new file in place of the old one. A file that does not read is logged and the daemon carries on as it was. |
||
|
|
3a3a0f8f24 |
feat(wm): JSON schemas, and the module's typed options
komorebi.json is described as a JSON Schema, made from the Rust types that read it: komorebic prints the schema of komorebi.json, of an applications.json, of a notification and of a message, and the daemon answers the same four questions on its socket. schema.json and schema.asc.json are committed (just jsonschema). The module's config option is typed from the schema: schemars-nixgen makes nix/komorebi-options.nix from schema.json (just nixgen), so a layout komorebi does not have, or a value of the wrong kind, does not evaluate. The module writes config as a komorebi.json and gives it to the daemon with --config. configFile and the file the module writes are checked against the schema when the system is built. applicationSpecificConfiguration takes files from GitHub, and there are none by default, since no community file for Plasma is known. Two checks keep the generated files honest without the tools that make them. schema-fresh compares the schemas with what komorebic prints, and the hash on the first line of the options with the schema. nix-options-validation evaluates the options against the example configuration, written in Nix and in JSON. nixos-module-eval also evaluates the module with the sources turned off: with applicationSpecificConfiguration.enable = false the sources are left out of the config that is written, and with no config either there is nothing to write and the unit passes no --config. ARCHITECTURE has the guarantee row that names the two checks. |
||
|
|
ed34286bca |
feat(wm): application specific configuration
Rules for particular applications can be kept in files of their own, applications.json, which komorebi.json names with app_specific_configuration_path: one path or a list. The ignore, manage and floating rules of every application in a file are added to the lists the configuration's own rules go in, so a file can be shared or kept apart from the settings. A path with no file behind it is skipped with a warning, since one komorebi.json is meant to work on every platform and may name a file that only another platform has; a file that is there and is not an applications.json is an error. The titleless and tabbed rules of other platforms are read and do nothing. komorebic/applications.json is a start for Plasma: it floats the calculators, the screenshot tool, the authentication dialog and System Settings. A test opens the windows it names. |
||
|
|
5eaa613f50 |
feat(wm): configuration in komorebi.json
The daemon takes its settings from a komorebi.json: the file --config names, or the one in the config home ($KOMOREBI_CONFIG_HOME, else ~/.config/komorebi). Without a file it runs on its defaults, as before. StaticConfig reads the file in three steps, because the monitors are known only after the backend has been asked. preload makes the window manager and applies the settings that are process-wide, postload gives each monitor and workspace the entry of its place (by the index display_index_preferences gives a monitor's serial number id, else in order), and main asks KWin for a desktop for each workspace the file lists. A watcher on the file sends the daemon a ReloadStaticConfiguration about two seconds after it changes, whether an editor wrote the file in place or renamed a new one over it, and a file that does not parse changes nothing. A workspace can change its layout, its work area offset and its layout options with the number of its containers, from the file or with komorebic (workspace-layout-rule, its named form and the two that clear), and layout_defaults gives every workspace of a layout the options it sets none of. One file is meant to serve every platform, so a key that does nothing on Plasma is accepted and ignored. config_parity checks the example and the files of other platforms; the keys of features that are not in yet are listed there, and the test fails when a list names a key that is read. The file also sets what the stacks, the float override, the floating layer and monocle set while the daemon runs: window_container_behaviour, with its rules by container count in a workspace, float_override, float_override_placement, floating_layer_behaviour (for a monitor and a workspace too), floating_layer_placement and monocle_focus_behaviour. Their features come before this commit, but a file to put the keys in does not, so they are part of this one. komorebic gains configuration, generate-static-config and start --config. The NixOS module gets configFile, which the unit gives the daemon as --config. |
||
|
|
12b0aa9301 |
feat(wm): the floating layer
A workspace with floating windows had no way to say which of the two kinds of windows a focus or move command was about, so the commands always acted on the tiles. A workspace is now on one of two layers, tiling or floating, and `komorebic focus`, `cycle-focus`, `move` and the resize commands act on the windows of that layer. Floating a window puts the workspace on the floating layer, and so does a window that a float rule floats when it opens and a focus change to a floating window; putting the window back, tiling a new window and a focus change to a tile put it on the tiling layer. `komorebic toggle-workspace-layer` switches it by hand: the windows of the layer it goes to are raised, the focused one last, and one of them is given the focus. Going to the tiles hides the floating windows, and going back to the floating layer shows them. On the floating layer, focusing in a direction goes to the nearest floating window on that side and, where there is none, crosses to the next workspace or the monitor on that side as it does for tiles, and fails where there is none. Cycling goes round the floating windows in the order they were floated, `move` moves the focused floating window by the resize delta without leaving the work area, the resize commands change its frame, and `preselect-direction` does nothing. A floating window that is maximized leaves the floating windows. The layer is in the state of each workspace. A window that opens while a workspace is on its floating layer floats when the floating layer behavior says so: the workspace's own, else its monitor's, else the window manager's. All three tile by default, and only the configuration file, which is not here yet, sets the first two. komorebi/tests/floating_layer.rs drives the layers against the fake desktop, with the floating layer behavior as well, and the vm-commands sweep runs the new command. |
||
|
|
05de20749b |
feat(wm): maximize
A window could be given the work area only as a tile of the layout, which has padding around it. `komorebic toggle-maximize` has KWin maximize the focused window instead: it gets the work area of its monitor with the panels left alone, and it leaves the layout, so that it is in no container. Toggled again, the window is restored, put in a container of its own at the index it was taken from, and the workspace is tiled again. The window is kept in the workspace as its maximized window, with the index it goes back to, and the state shows both. While it is maximized the workspace asks KWin to maximize it again on every tiling, and does not move the containers behind it. The messages that are about the tiles (move, stack, resize, promote, preselect, lock, float, monocle, and the ones that send a container to another monitor or workspace) are dropped before they are handled, since there is no tile to act on. Focusing in a direction finds no container on any side of it, so the focus goes to the monitor on that side, where a maximized window or a monocle is given the focus whole. Showing the workspace again restores the maximized window and focuses it ahead of the containers' windows. A window in monocle that is maximized leaves the monocle, and one that closes takes the maximize with it. Backend gets a maximize command, which does nothing unless a backend implements it; the fake desktop and the KWin backend do, and the bridge carries it out with KWin's own `setMaximize`. A maximize also makes the bridge forget the position it was last asked to put the window at, which it would otherwise set again on the work area's size when the window's frame changed within a second of a tiling. komorebi/tests/maximize.rs and maximize_commands.rs drive it against the fake desktop, with one monitor and with two. The bridge's tests and the backend's tests read the maximize command from the same fixture, so that what one sends is what the other carries out. vm-features is a new session suite that reads screenshots: the first thing it checks is that the maximized window fills the work area, is the one on top, and goes back to its tile; the vm-commands sweep runs the new command. |
||
|
|
0d21dd332c |
feat(wm): monocle
A workspace could show its windows only side by side. Monocle gives one container the whole work area: the focused container is taken out of the workspace's containers, so the layout gives no container a tile, and the next tiling puts its window on the work area less the padding. Turning it off puts the container back at the index it came from, and the tiles come back as they were resized: the container keeps its resize adjustment, and the workspace does not shorten the list of adjustments while a monocle is on. The windows of the other containers are hidden on the parking desktop, as the windows behind the front one of a stack are, and the front window of each is shown again when the monocle is turned off or the window in monocle closes. A stack in monocle stays a stack. `komorebic toggle-monocle` turns it on and off. What focusing in a direction does meanwhile is set with `monocle-focus-behaviour` or switched with `toggle-monocle-focus-behaviour`: with `cycle` the monocle goes to the previous or the next container, with `no-op`, the default, no container is on any side and the focus falls through to the monitor on that side, where the monocle window of its workspace, if it has one, is given the focus, and the command fails where there is no monitor. A container that is moved to a monitor or sent to a workspace in monocle turns that monocle off first. `cycle-focus` always carries the monocle along. Giving the focus to another window gives it back to the monocle window, so that it stays in front. `move` and `cycle-move` are refused and `toggle-float` is ignored while a window is in monocle, since the focused container is not the window in front. The monocle container is in the state, with the index it goes back to, and a window in it that closes takes the monocle with it. komorebi/tests/monocle.rs drives it against the fake desktop, with one monitor and with two. vm-core-e2e checks in a real KWin that the focused window fills the work area, that the state has the monocle container, that toggling back restores the tile, that the cycle behavior gives another window the work area, and that the windows behind a monocle are parked, which a clean stop and the next start after a crash put right; vm-stacks checks a monocle over a stack; the vm-commands sweep runs the new commands. |
||
|
|
2e439414db |
feat(wm): cycle and focus in a stack
A stack shows one window, so there has to be a way to choose which. komorebic cycle-stack brings the next or the previous window of the focused stack to the front, wrapping around, and hides the one that was in front. focus-stack-window brings the window at an index, on the monitor the pointer is on, so that a status bar on another monitor can act on its own stack. cycle-stack-index moves the window in front one place along the stack, which changes the order the other two count by, and keeps it in front. Each command shows the window it chooses before it gives it the focus: KWin follows the focus of a window on another desktop to that desktop, and a hidden window is on the parking desktop. vm-stacks cycles a stack in a headless KWin and checks after each command that KWin and komorebi agree on the window in view and that exactly one window of the stack is out of sight. |
||
|
|
331db681e2 |
feat(wm): stack out of sight on a parking desktop
A container has one tile, so the windows of a stack that are not in front have to be out of sight. KWin has nothing a script can set to hide a window except a virtual desktop nobody looks at, which the first spike measured to cost no focus change where minimizing a window cost two. Backend::hide takes a window out of sight and Backend::restore brings it back; the KWin backend parks the window on a desktop named komorebi-park, which the table keeps out of the workspaces, and remembers where each hidden window came from so that it is still on its workspace. KWin saves a desktop in kwinrc as soon as it is made, so komorebi removes the parking desktop when it stops, after the windows are back, and the first snapshot of the next run finds one a killed run left, sends its windows home and removes it. The windows komorebi hid are recorded, so that a minimize the compositor reports for one is not mistaken for the user's. komorebic stack, unstack, stack-all and unstack-all stack and unstack windows. The window container behavior and the float override decide whether a window that opens joins the focused container or floats, for every workspace or for one. vm-stacks stacks windows in a headless KWin, shows a workspace and goes back, stops, and kills the daemon with a window parked. |
||
|
|
ac265de1e3 |
feat(wm): padding for named workspaces
workspace-padding and container-padding take the index of a workspace's monitor and its own index, so a script that names its workspaces still has to count them to pad one, while focus-named-workspace, named-workspace-layout and named-workspace-tiling already take the name. named-workspace-padding and named-workspace-container-padding take the name instead. They look the workspace up on every monitor the way the other named commands do, set the same padding the commands by index set, and change nothing for a name no workspace has. The tests check the messages the subcommands send, that a named workspace takes its paddings and the tile in front follows, and that a name nobody has changes nothing. The commands sweep of the VM suite sends both commands to a real session. |
||
|
|
189aeb1005 |
feat(wm): more than one monitor
Every monitor has workspaces of its own, which KWin's per-output desktops show independently, so a second monitor is mostly the window manager's business: which monitor has the focus, and how a window gets from one to the other. At the edge of a monitor, focus and move now cross to the monitor on that side, where they used to fail with "there is no container in this direction": the focus lands on the container nearest the monitor it came from, and a move swaps with the container at that edge (cross-monitor-move-behaviour insert puts it in among the containers there, no-op keeps it). A window that opens or gets the focus on a monitor that is not the focused one focuses that monitor, which also makes the scrolling test of a strip next to another monitor guard real code. The monitor commands come with it: focusing a monitor by index, in order or under the pointer, a workspace of another monitor, moving and sending the focused window to another monitor, to a workspace of it or to a named workspace (in the window manager these are moves between monitors, so they were left out of the workspace commands), moving and swapping workspaces between monitors, and a display index preference that keeps a monitor at the index the user gives it. eager-focus focuses the first window of an executable on any workspace of any monitor; its window is found in a container or among the floating windows, the two places a window can be until stacks, monocle and maximize exist. |
||
|
|
8467b03ac2 |
feat(wm): named workspaces and the other workspace commands
Workspace i is KWin's desktop i, and switching and moving by index came with the workspaces. What a user binds keys to besides is the rest of komorebi's workspace commands: going to the next or the previous workspace (or the next empty one), back to the last one, adding and closing workspaces, showing one index on every monitor, and setting the layout or the tiling of a workspace that need not be in front. They are made of the same two operations, showing a workspace and moving a container to one, so KWin keeps doing the showing and the hiding. A workspace gets a name, which focus-named-workspace, named-workspace-layout and named-workspace-tiling find it by on any monitor; focusing a named workspace focuses its monitor too, so focus_monitor comes with it. The name is komorebi's: naming a workspace does not rename KWin's desktop. The query focused-workspace-name answers with the name, or the index of a workspace that has none. The moves to a named workspace, and everything that addresses a monitor, are moves between monitors in the window manager and come with more than one monitor. |
||
|
|
62e37e44cf |
feat(kwin): workspaces are KWin's own desktops
komorebi's workspaces exist on Windows and macOS because neither has a virtual desktop API: switching hides every window of the workspace being left. KWin has virtual desktops, per output since 6.8, so here workspace i is KWin's desktop i on every monitor, and each monitor shows one. KWin shows, hides and focuses the windows of a desktop; komorebi asks for a switch, puts a window on a desktop, and follows what anything else does. The Backend gets the workspace methods (ensure_workspaces, switch_workspace, set_window_workspace, window_workspace, current_workspaces), and the fake its desktops. The bridge reports the desktops, the desktop each output shows and a window's desktops, and carries out output_desktop and desktop commands, typed and tested with the fixtures like the rest. The table turns a switch or a move komorebi did not make into a WorkspaceChange or a WorkspaceMove, and records its own when it sends them, so that KWin's report of those raises nothing. The window manager follows a switch with no command in reply (adopt_workspace_change), puts windows on their desktops before a desktop is shown (focusing a window first would take the monitor back), and loads the windows open at startup onto the workspace of their desktop. Per-output desktops are a kwinrc setting that is off by default, and kwriteconfig6 was not on the PATH of the user service in a live session, so komorebi edits kwinrc itself (kwin::kwinrc) and puts the user's value back when it stops, also after a crash. focus-workspace, move-to-workspace and send-to-workspace come with this commit, since the tests of switching and moving, and vm-workspace-switch, need them; the named and cycling workspace commands follow. vm-workspace-switch runs the daemon against a real KWin; vm-kwin-contract checks what komorebi relies on from KWin's per-output desktops, on a headless KWin with two outputs and no komorebi. |
||
|
|
1b10611022 |
feat(wm): ignore, manage and float rules
komorebi managed every normal window that is alive, and there was no way to keep an application out of the layout. The rule engine decides whether a window is managed: an ignore rule keeps the windows it matches out, unless a manage rule matches them too, and a float rule puts a window in the floating windows of its workspace when it opens. A rule matches a window by its title, its executable, its class or the path of its executable. Plasma's shell and KRunner are ignored from the start. `komorebic ignore-rule` and `manage-rule` add a rule, `session-float-rule` floats the focused window and the windows like it until the daemon stops, `clear-session-float-rules` removes those rules, and `global-state` shows the rules. The `DebugWindow` message says why a window is managed or not. Applications often open a window before they give it its title, so the bridge now reports a title change, and a window komorebi does not have is asked about again when its title changes, for a rule on the title to decide. The vm-commands sweep adds and shows rules, and a section of its own checks that a window an ignore rule matches by its first title is tiled once it has another. vm-kwin-events checks that a window giving itself another title reaches the daemon as a title update. |
||
|
|
c95be9391e |
feat(wm): padding and work-area offsets
Every workspace was tiled with the default padding of 5 pixels around and between its tiles, and the work area offsets could not be set. The padding commands set a workspace's own workspace padding and container padding, by the indices of its monitor and itself or for the focused workspace, and the adjust commands make the focused workspace's larger or smaller. `global-work-area-offset`, `monitor-work-area-offset` and `workspace-work-area-offset` keep a part of the work area free of tiles, and a workspace's offset now wins over its monitor's, which wins over the global one. A monitor's window-based offset applies while the workspace in front has no more windows than the monitor's limit, and `toggle-window-based-work-area-offset` turns it off and on for the focused workspace. The vm-commands sweep sets and resets every offset and padding. |
||
|
|
56294e1cd3 |
feat(wm): manage, minimize and close
Every normal window KWin reports is tiled, and the only way to take one out of the layout was to float it, which keeps it a window komorebi manages. `komorebic unmanage` takes the focused window out of the layout, and komorebi leaves it alone until `komorebic manage` takes it back or the window closes. Both are events komorebi raises itself on the channel KWin's events go through, so they are handled in order with what KWin reports. `komorebic minimize` and `close` ask KWin to minimize or close the focused window, which leaves the layout when KWin reports that it did. `komorebic toggle-pause` pauses the daemon: it acts on no event, and takes only `toggle-pause`, `state` and `stop` on its socket until it is resumed. With `unmanaged-window-operation-behaviour no-op`, the daemon refuses every message while the focused window is not one it tiles. The vm-commands sweep pauses and resumes, sets the operation behavior, manages and closes the focused window, and a section of its own minimizes, unmanages and manages a window. |
||
|
|
3cc5dceb7d |
feat(wm): lock
Containers can be locked since the model came in, and a locked container keeps its index while others are inserted, removed or swapped around it; nothing could lock one. `komorebic toggle-lock` locks the focused container, or unlocks it, and the `LockMonitorWorkspaceContainer` and `UnlockMonitorWorkspaceContainer` messages lock and unlock a container by the index of its monitor, its workspace and itself, for a program such as a status bar. The vm-commands sweep locks and unlocks a container. |
||
|
|
062fe57fac |
feat(wm): float
Some windows are better left out of the layout. `komorebic toggle-float` takes the window that has the focus out of its container into the floating windows of the workspace, which komorebi gives no tile and does not move, and the other windows share the work area; on a floating window it puts it back in a container at the focused place. A floating window is in the index of managed windows like any other, so its events find it, and it is in the state as the workspace's `floating_windows`. Where a floated window goes is the `toggle_float_placement` of the window management behaviour: by default it stays where it is, since the spike's centering of floated windows waited for a configuration file. Centering and resizing to the floating aspect ratio come with it. Opening windows floating (the float override and the float rules), the floating layer and its own focus and move commands come later. The vm-commands sweep floats a window and puts it back, and its health check counts the floating windows. |
||
|
|
554d77aed9 |
feat(wm): promote
The first tile of a layout is its largest, and the place for the window being worked on. `komorebic promote` moves the focused window there, with the windows that were before it each moving one tile on; `promote-swap` swaps it with the window that has the first tile, and from the first tile swaps it back with the window it was last swapped with; `promote-window left|right|up|down` promotes the window on that side of the focused one; and `promote-focus` gives the focus to the first tile and moves nothing. The grid layout has no largest tile, so the four do nothing there. The vm-commands sweep gains the four. |
||
|
|
95eb4d5039 |
feat(wm): resize from the CLI
`komorebic resize-edge left|right|up|down increase|decrease` moves one edge of the focused tile, and `resize-axis` both edges along an axis, by the delta that `resize-delta` sets (50 pixels to start with; komorebi-layouts moves an edge by half of it). The neighbors give up the room. A resize is an adjustment of the container's tile that the workspace keeps, one per container, and that the layout applies whenever it tiles. Before each tiling, the workspace clears the parts its layout cannot apply, such as the height of a column. No tile may end up narrower or shorter than a tenth of the work area, or than it already is: a resize that would make one so is granted as far as it can go, not dropped. `retile` drops the adjustments, and the `RetileWithResizeDimensions` message tiles with them. `save-resize` and `load-resize` write them to a file and read them back, and `quick-save-resize` and `quick-load-resize` do the same with one file in the daemon's temporary directory. The vm-commands sweep gains every resize command, and the saving and loading. |
||
|
|
37b06993d2 |
feat(wm): tune layouts, and the scrolling layout
`komorebic layout-ratios --columns ... --rows ...` sets the share of the work area the first columns and rows of the focused workspace's layout take, and `scrolling-layout-columns N` how many columns of the scrolling layout are in view. Both are layout options of the workspace, which komorebi-layouts reads. The scrolling layout is a strip of columns that runs past the edges of the work area, so its tiles are not clamped to it: the windows of the columns out of view are off the screen. When a window of a scrolling workspace gets the focus, the workspace is tiled after the focused container changed, which scrolls the strip to it. KWin moves a window anywhere, but keeps 100 pixels of a window it resizes on an output once the client has drawn at the new size, which pulls the columns out of view back onto the screen. The bridge remembers the position it was asked for, for a second, and asks again for the position alone when KWin moved the window elsewhere, three times at most. komorebi-layouts compiles `Rect::percentage_within_horizontal_bounds` only with its darwin feature, which this repository does not enable, so the workspace has its own copy of it. A new VM suite, vm-scrolling, after the spike's m2-scrolling script, checks in a real KWin that the columns out of view stay where komorebi put them and that the strip follows `komorebic focus`; vm-commands sweeps the two new commands. |
||
|
|
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. |
||
|
|
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. |
||
|
|
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. |
||
|
|
a86e2bdc63 |
build(nix): a NixOS module for komorebi
On NixOS, running komorebi should take one flake input and one module line. The flake exports `services.komorebi` as nixosModules.default (and .komorebi). It adds the flake's overlay, which gains komorebi-full, installs komorebi and komorebic, and runs the daemon in every Plasma session as a systemd user unit, wanted by plasma-workspace.target and ordered after plasma-core.target, which Plasma orders after KWin. systemd starts a daemon that fails again; the exit status of a SIGTERM (130) counts as success, so a stopped unit is not failed. The unit's PATH is the one a terminal in the session has, instead of the few store paths NixOS gives a unit. The options are enable, package, logLevel and a read-only execStart, the command line the unit runs. There is no configFile: the daemon reads no configuration file yet, and an option that linked one would do nothing. nixos-module-eval evaluates seven scenarios as real NixOS systems and checks their assertions, the unit and execStart, with builtins.tryEval for the two that must not evaluate. Nothing in it builds a Linux package, so it runs on a Mac as well. The VM nodes import the module, so every suite runs komorebi as the module installs it. The headless node's komorebi@NAME runs execStart, which has --log-level, so the nodes set logLevel to debug and the suites stop passing it; latency, which times the daemon, keeps the default level. The session node starts the module's unit at login; render-smoke turns it off, since a tiled terminal would cover the desktop it looks at. A new fast suite, vm-module, logs in to Plasma and checks that the unit runs the daemon with the module's log level and PATH, that a window opened in the session is tiled, that a killed daemon is started again, and that a stop is not a failure. docs/installation.md covers NixOS first, and building from source on other distributions. There is no binary cache: the Komorebi license does not allow third parties to redistribute binaries. |
||
|
|
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. |
||
|
|
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. |
||
|
|
88aa78b874 |
feat(wm): a command socket and the first messages
Until now the daemon tiled what KWin reported and nothing else could reach it. komorebi takes its orders on a socket, as it does on Windows and on macOS: it handles no key itself, and whatever binds keys runs a command that writes a message. The daemon binds komorebi.sock in $XDG_RUNTIME_DIR/komorebi (DATA_DIR), reads one JSON SocketMessage per line on a thread per connection, and handles each message with the window manager locked, as the thread that handles events does. The first messages are the ones the first komorebic needs: State (the window manager as JSON, from state.rs), MonitorInformation, Query with a StateQuery, Retile and Stop. A question is answered on the same connection, and the answer ends when the daemon closes it. Stop unloads the bridge from KWin, removes the socket and exits; so does a stop signal. The socket is bound once the daemon is connected to KWin, so a komorebi that cannot connect, such as a second one in the same session, which the bus refuses, leaves the socket of the one that runs alone. With --fake-backend the daemon answers on its socket with no compositor at all, which is how the tests run it. komorebi-client is the other end: send_message, send_batch and send_query, and the types the messages are made of. Its doctests serve the socket themselves, tests/daemon.rs runs it against a window manager over the fake desktop, and komorebi/tests/socket.rs runs the daemon binary from its start to a Stop. |
||
|
|
4386003e30 |
feat(kwin): survive KWin restarting
KWin can go away under a running daemon while the session bus stays: it crashed, or kwin_wayland --replace took its place. Its windows go with it, and so does the bridge, which would have reported them closing; in the spikes the old windows vanished without a single removed event. What the daemon does see is the bus, so the thread that serves the connection watches NameOwnerChanged for org.kde.KWin. When the name loses its owner, Table::compositor_lost marks every window closed and returns a Destroy for each, so the window manager takes them out of its layout, and their ids are not given out again. When the name has an owner again, the daemon loads the bridge into the new KWin, trying for up to 15 seconds while KWin is not ready for it, and the bridge reports the new desktop in a snapshot. zbus does not end a held call when its caller leaves the bus, so the Poll of the old bridge is still waiting when the new bridge calls, and it was woken first: it took the commands for the new KWin and answered them to nobody. vm-kwin-restart lost the tile of a window opened in the third KWin that way in one run of three, and a recording of the bus showed the batch going to the old bridge. A Poll held since before KWin last left the bus is answered with nothing and passes its wake-up on to the next call, which a unit test holds. The VM suite vm-kwin-restart (from the spike's spike7-resync) restarts the headless KWin twice under a running daemon, through Kde.restart_kwin in the VM library, and checks that the daemon sees KWin go, takes the old windows out of its layout, loads the bridge into the new KWin, and tiles the windows that open there. |
||
|
|
b463191d00 |
feat(kwin): move and focus windows through a held call
With the commands of the Backend trait carried out, the daemon tiles a real KWin. A command looks up the UUID KWin knows the window by, is queued as JSON for the bridge, and returns; the held Poll call is answered with the queue, one millisecond after the first command, so that a retile reaches KWin as one batch. The bridge sets the frame, activates, raises, minimizes, restores or closes the window, and reports what it could not do. set_rect records the frame it asked for in the table, so the window manager sees it at once, and the geometry event that follows says what the client made of it. A KWin script cannot move the pointer, so the pointer follows the focus through KWin's own "Move Mouse to Focus" action, invoked over kglobalaccel once KWin reports the window active. The commands are typed in bridge.js, and the declarations of KWin's API let the bridge set a window's frame, its minimized state and the active window, raise and close it, and nothing else. The session the tests of both sides share grows a second batch: the backend's tests check that set_rect, focus, raise, minimize, unminimize and close send it, and the bridge's tests answer the bridge with it and check what KWin is asked to do, down to the command for a window that has closed, which comes back as failed. The bridge's tests also cover findWindow's fallback to KWin's list, a frame set as a plain object, and a command KWin throws on. Three VM suites check it against a real KWin. vm-core-e2e (from the spike's m2-e2e, the sections that need no komorebic) compares what the daemon asked for, read from its debug log, with what KWin has, read with kdotool: three windows inside the screen, not overlapping, sharing it, the same focused window on both sides, and the same again after a minimize, a restore and a close. vm-client-geometry (slow) has eight real clients answer the frames komorebi asks for: Qt programs and xterm take them exactly, foot sizes itself to whole cells, GTK 4 programs and Firefox keep to their minimum sizes. vm-latency (slow) times retiles from a recording of the session bus: on HVF, from the daemon's answer to the last frame KWin reports, p50 17.3 ms, p95 27.7 ms. vm-kwin-events no longer checks that nothing moves. The VM library gives session commands the test user's home and data directories rather than root's, which a GTK client was refused. The bridge's second request for a position after a resize, which matters for windows off the screen, comes with the scrolling layout. |
||
|
|
15ec3c1274 |
feat(kwin): read the desktop through a script in KWin
KWin has no protocol that lets another process read or set the geometry of a window, but its scripting API has both, and a signal for every change the window manager follows. So the KWin backend is in two parts: bridge.js, a script the daemon loads into KWin, which reports what KWin sees and holds no window manager logic, and a D-Bus service in the daemon, org.komorebi.Bridge, which the script calls with every event as a JSON string. Every decision about what an event means is made by the table in kwin::state, which is pure and is unit-tested with the JSON the script sends: which windows may be managed, the ids komorebi gives KWin's windows, and the Show, Destroy, FocusChange, Minimize and Restore events of the window manager. Events reach the window manager in the order KWin raised them: the Event interface handles its calls inline (zbus reordered them otherwise), and one channel and one thread, window_manager_event_listener and listen_for_events, carry them on. Commands go to the script as the answer to a Poll call that the script always has open and the daemon holds until it has something to send: in the spikes this was as fast as any other channel, and it makes one call every 10 seconds when nothing happens. Here the only command is the cursor query. Every command of the Backend trait is refused with "the KWin backend only reads the desktop", so the daemon follows KWin and moves nothing. The bridge is JavaScript that KWin runs as it is, and it gets types and tests without a JavaScript build chain. tsc checks it as strict TypeScript through JSDoc types of every message, against kwin-api.d.ts, a declaration of the part of KWin's scripting API it uses, in which every property is read-only and callDBus takes one string. bridge.test.js runs it under node --test against a fake KWin in a node:vm context. The messages of a session are JSON fixtures that both sides' tests read: the bridge's tests check that it sends those events, and the table's and the backend's tests read the same events and commands, so a message cannot change on one side alone. just check-js and the flake's komorebi-bridge-js check run both, with nodejs and typescript from nixpkgs; there is no package.json. The daemon now connects to KWin by default, and --fake-backend is hidden. The VM suite vm-kwin-events starts it against a headless KWin and checks, from its debug log, that the script loads and reports what was open, that KWin's signals arrive as events and in order, and that stopping the daemon unloads the script. docs/dev/PLATFORM.md starts with what komorebi needs from Plasma. zbus 5's zvariant, zbus_names and zvariant_derive are flagged unmaintained, since their code moved into zbus 6; deny.toml ignores the three advisories. |