Commit Graph
56 Commits
Author SHA1 Message Date
LGUG2Z 83819b22b1 docs(backend): focus is one call on Windows and macOS too
The doc of Backend::focus said Windows and macOS do focus and then center_cursor_in; writing the adapters showed that Window::focus on both takes mouse_follows_focus and centres the pointer itself (Windows also defers the focus while the window slides in). Doc comment only.

Verified: see the gates below; native cargo test -p komorebi-backend passes (doctests unchanged).
2026-10-31 01:37:54 -07:00
LGUG2Z 998396dfc5 test(backend): contract tests that any Backend must pass
komorebi_backend::contract: a Harness trait (how a test puts a live window on the desktop of the backend under test) and 14 checks, run by check_all::<H>(): windows listed by id; window agrees with windows/is_window/window_rect; an unknown window is not a window; every window command on an unknown or on a destroyed window fails with WindowGone naming that window; a destroyed window is neither alive nor listed; set_rect on a live window succeeds and the frame stays readable (a request: the value read is not checked); hide/restore tolerate repeats; focus names a known window; capabilities and Unsupported agree (supported means never Unsupported, unsupported means always Unsupported, with a live or unknown window); monitors are sane and found by point; Batch scopes nest around writes; the backend works from another thread; it is usable as a trait object.

Only what the Backend documentation promises is checked, because a write is a request and platforms act at different speeds. The fake passes under three configurations (plain, with a frame offset, with no capability at all, as macOS would be) and a deliberately broken backend (set_rect answers Ok for a gone window) is shown to fail exactly the two checks that cover it.

Verified: native cargo test -p komorebi-backend: 29 unit tests passed, 6 doctests passed, 1 compile_fail doctest passed, 0 failed; clippy --all-targets -D warnings clean.
2026-10-31 01:37:54 -07:00
LGUG2Z 6874a51769 feat(backend): FakeBackend with failure injection and frame offsets
The in-memory desktop of komorebi-for-kde (backend/fake.rs) moved into the shared crate, rewritten against WindowId/MonitorId/Result and the generic Backend only (look, border colours, wallpaper and drag frames stay out: they are KDE's).

Same behaviour as the KDE fake (permissive mode, minimum sizes, user_* helpers, maximize, fullscreen, workspaces, the ordered Command log with 15 of KDE's 18 variants; close() leaves the foreground window alone, as KDE's did), plus:
- failure injection: fail_next(Op, Error) and fail_next_for(Op, WindowId, Error), oldest first, one per call; the failed call is still logged as a command and changes nothing else (none existed on KDE);
- frame offsets: set_frame_offset / set_window_frame_offset make window_rect differ from the request by a constant, as the Windows shadow border does;
- capabilities: all four on by default, set_supported switches one off and its operations return Unsupported without being logged;
- batches: begin/end are counted (open_batches) and logged as BatchBegin/BatchEnd;
- FakeBackend<X>: a platform extension logs its own commands in order with record_ext (Command::Ext(X)); the shared fake is FakeBackend<NoExt> (new/permissive/with_one_monitor), an extension uses empty/permissive_ext/one_monitor because default type parameters are not inferred.

Verified: native cargo test -p komorebi-backend: 24 unit tests and 6 doctests passed, 0 failed (the KDE fake tests were ported and 6 failure/offset/capability/batch/extension tests added); clippy --all-targets -D warnings clean; cargo fmt applied.
2026-10-31 01:37:54 -07:00
LGUG2Z f08237d402 feat(backend): the shared Backend trait, WindowId, plain-data infos and the error type
New crate crates/komorebi-backend (edition 2024, root workspace member, depends only on komorebi-layouts for Rect).

- WindowId and MonitorId: opaque u64 newtypes. KDE and macOS ids convert with from_u32/to_u32; a Windows HWND/HMONITOR goes through from_hwnd/to_hwnd, which keep the bits of the isize unchanged (isize as usize as u64), so no handle is lost whether or not it fits in 32 bits (the e2e samples stay below 2^32, which is an observation, not a guarantee).
- WindowInfo and MonitorInfo: plain data; only id, pid, rect, minimized and alive are required, the rest (title, class, exe, path, fullscreen, maximized, normal; name and serial id of a monitor) are Option (seam.md consequence 6).
- Error: WindowGone(id) (finding 9: macOS uses a failed write as its signal that a window died), Unsupported(Capability) and Platform(cause).
- Backend: only the operations of seam.md section 2.1 that at least two platforms have (5 queries, 9 commands), a Capability query for workspaces, maximize, opacity and focus-follows-mouse (their methods default to Unsupported), begin_batch/end_batch with a no-op default plus the Batch guard (finding 8), the documented set_rect contract (a request; reads after a write return what the platform defines, consequence 2 as corrected) and the thread contract (any non-main thread, no waiting for the platform's main thread: consequences 3 and 4, written as TARGET rules). Every doc comment says which platforms implement the method.

Verified: native cargo test -p komorebi-backend (2 unit tests, 3 doctests, 0 failed) and cargo clippy -p komorebi-backend --all-targets -- -D warnings clean. The Windows/KDE/macOS gates are not affected yet (new crate, nothing depends on it).
2026-10-31 01:37:54 -07:00
LGUG2Z 6da27dda3c refactor(bar-core): move the memory, date and update widgets into komorebi-bar-core
Pure move of the three widget files that are byte-identical in the two bars since the previous revision. Each bar's widgets/mod.rs re-exports them (pub use komorebi_bar_core::widgets::memory / date / update). The core takes chrono, chrono-tz, reqwest (blocking) and serde_json (now a normal dependency, it was only a dev-dependency).

Removed: 541 lines from each bar (138 + 245 + 158), 541 added once.

Verified: Windows cargo clippy --workspace --locked --all-targets -D warnings clean; macOS fake-CC cargo check --locked --workspace --all-targets finished, 0 errors; native cargo test -p komorebi-bar-core 3 unit + 2 oracle passed; harness regeneration byte-identical for both (macOS 3,774 lines, Windows 9,091 lines). Not verified: rendering.
2026-10-31 01:37:54 -07:00
LGUG2Z b8de056b28 refactor(bar-core): the cpu and battery widgets open their tools through the platform hook
The click on the cpu widget (task manager) and on the battery widget (power settings) now calls platform().open_system_tool(...) instead of spawning cmd.exe in shared code. The Windows hook spawns the same commands as before; the macOS hook still spawns the same (broken there) cmd.exe commands, so neither platform's behaviour changes. Fixing macOS is now a change to one function of komorebi-for-mac/komorebi-bar/src/platform.rs.

Verified: Windows cargo clippy --workspace --locked --all-targets -D warnings clean; macOS fake-CC cargo check --locked --workspace --all-targets finished, 0 errors; native cargo test -p komorebi-bar-core 3 unit + 2 oracle passed; harness regeneration byte-identical for both. Not verified: that a click still opens the tool (no screen).
2026-10-31 01:37:54 -07:00
LGUG2Z 086833af96 refactor(bar-core): add the BarPlatform hooks with the Windows and macOS implementations
komorebi_bar_core::platform holds what the widgets need from the operating system and differs between the bars: BarPlatform { open_system_tool, latest_release_api_url, calendar_icon }, the SystemTool enum (task manager, power settings, network settings, a URL) and install()/platform(), a once-set global the shell fills first thing in main() (a widget calls platform() when it is clicked or built, never before main). Each bar gets an os.rs with its implementation (main.rs re-exports the core module as crate::platform, so shared files can say crate::platform::platform()): Windows spawns exactly the commands the widgets spawn today (cmd.exe /C taskmgr.exe, cmd.exe /C start ms-settings:batterysaver, cmd.exe /C ncpa, explorer.exe <url>); macOS maps TaskManager to open -a 'Activity Monitor' and network settings to open x-apple.systempreferences:... as memory.rs and network.rs do today, and keeps the commands the shared files spawn on macOS today for the rest (cmd.exe taskmgr.exe for cpu, cmd.exe start ms-settings:batterysaver for battery, explorer.exe for a URL), which are broken there: a one-line fix per tool in os.rs, not part of this change.

Nothing calls the hooks yet: the next revisions switch the widgets over, one concern each.

Verified: Windows cargo clippy --workspace --locked --all-targets -D warnings clean; macOS fake-CC cargo check --locked --workspace --all-targets finished, 0 errors (6 bar warnings, unchanged); native cargo test -p komorebi-bar-core 3 unit + 2 oracle passed; rustfmt --check clean on the new files and both main.rs. No widget calls the hooks yet, so no behaviour changes.
2026-10-31 01:37:54 -07:00
LGUG2Z 98f9e184a2 refactor(bar-core): move the battery and cpu widgets into komorebi-bar-core
Pure move of the two widget files that are byte-identical in the Windows and macOS bars. Each bar's widgets/mod.rs re-exports them (pub use komorebi_bar_core::widgets::battery / cpu), so crate::widgets::battery::... resolves as before; the files themselves are unchanged.

Dependencies: the core takes sysinfo from the root workspace (0.39) and starship-battery 0.11, the versions the Windows bar already used. The macOS bar therefore now gets these two widgets built against sysinfo 0.39 (its own workspace pins 0.38, still used by its other widgets until they move) and starship-battery 0.11 (was 0.10; its own dependency on the crate is removed). Both compile unchanged; behaviour of the libraries is not verified on macOS.

Not changed on purpose: the macOS bug that these two files spawn cmd.exe (taskmgr.exe, explorer.exe) on click is still there, identically; it is fixed behind the platform hook, not inside a move.

Removed: 325 lines from each bar (191 + 134), 325 added once.

Verified: Windows cargo clippy --workspace --locked --all-targets -D warnings clean; macOS fake-CC cargo check --locked --workspace --all-targets finished, 0 errors (bar warnings 6, unchanged; macOS Cargo.lock: starship-battery 0.10.3 -> 0.11.1 with its dependencies (uom, plist, nix), sysinfo 0.39.6 added next to 0.38.4); native cargo test -p komorebi-bar-core 3 unit + 2 oracle passed; harness regeneration (S/bar2/regen.sh, which now fails when a harness does not build): macOS byte-identical (3,774 lines), Windows byte-identical (9,091 lines). Not verified: rendering, sysinfo and starship-battery behaviour on macOS.
2026-10-31 01:37:54 -07:00
LGUG2Z c05ef98d77 refactor(bar-core): the BarWidget trait moves into komorebi-bar-core
The three-line trait every widget implements is split off widgets/widget.rs (the WidgetConfig enum stays there until every widget it names has moved) and becomes komorebi_bar_core::widgets::widget::BarWidget; each bar's widget.rs re-exports it, so crate::widgets::widget::BarWidget resolves as before. The core's config module re-exports the leaf types (crate::config::LabelPrefix) so that the widgets can later move without a text edit.

Verified: Windows cargo clippy --workspace --locked --all-targets -D warnings clean; macOS fake-CC cargo check --locked --workspace --all-targets finished, 0 errors; native cargo test -p komorebi-bar-core 3 unit + 2 oracle passed. No schema type changed. Not verified: rendering.
2026-10-31 01:37:54 -07:00
LGUG2Z d5ad916ff1 refactor(bar-core): move render.rs into komorebi-bar-core
Pure move of render.rs (RenderConfig, Grouping, GroupingConfig, GroupingStyle, RoundingConfig and the helpers), byte-identical in both bars since the three previous revisions, with the one path edit the move needs: Alignment is imported from komorebi_bar_core::alignment instead of crate::bar. Each bar's main.rs re-exports the module (pub use komorebi_bar_core::render), so crate::render::... resolves as before.

Removed: 382 lines from each bar, 382 added once.

Note: the one added line is #[allow(clippy::new_without_default)] on RenderConfig::new, which clippy now asks for because the type is public API of a library crate.

Verified: Windows cargo clippy --workspace --locked --all-targets -D warnings clean; macOS fake-CC cargo check --locked --workspace --all-targets finished, 0 errors; native cargo test -p komorebi-bar-core 3 unit + 2 oracle passed; harness regeneration: macOS byte-identical (3,774 lines), Windows byte-identical (9,091 lines). Not verified: rendering.
2026-10-31 01:37:54 -07:00
LGUG2Z a3afb3a94b refactor(bar): GroupingConfig.transparency_alpha has one spelling in render.rs
The two render.rs differed in two places. The Windows bar documented transparency_alpha with '(default: 200)' in its text; the macOS bar documented it without and put the default into the schema (schemars extend default 200), so the macOS nix options file gets default = 200. The macOS spelling is taken for both, because a change to the macOS schema.bar.json would also change the generated nix/komorebi-bar-options.nix, which cannot be regenerated here. Windows: the description loses the '(default: 200)' text and the property gains "default": 200 (reviewed diff of schema.bar.json below, pin updated in tests/schema_oracle.rs); the one comment on RenderConfig that only Windows had ('/// Render configuration') is added to the macOS copy. The two render.rs are then byte-identical.

The schema diff is the two lines of the property: description 'Alpha value for the color transparency [[0-255]] (default: 200)' -> 'Alpha value for the color transparency [[0-255]]', and one added "default": 200. macOS schema.bar.json is untouched (its harness regeneration is byte-identical).

Verified: Windows cargo clippy --workspace --locked --all-targets -D warnings clean; macOS fake-CC cargo check --locked --workspace --all-targets finished, 0 errors; native cargo test -p komorebi-bar-core: 3 unit + 2 oracle passed, 0 failed; harness regeneration: macOS byte-identical (3,774 lines), Windows byte-identical to the updated schema.bar.json (9,091 lines, diff exactly the two lines above). Not verified: rendering.
2026-10-31 01:37:54 -07:00
LGUG2Z 3b641c04ce refactor(bar-core): move the leaf types of config.rs and their three tests into komorebi-bar-core
Pure move of the tail of config.rs, which both bars have byte-identical: Position (with its Vec2 and Pos2 conversions), LabelPrefix, DisplayFormat, the extend_enum! macro, WorkspacesDisplayFormat, MediaDisplayFormat and the three extend_* unit tests, to komorebi_bar_core::config::types. Each config.rs re-exports the module (pub use komorebi_bar_core::config::types::*), so crate::config::LabelPrefix and the others resolve as before; the Windows config.rs keeps its own pub use of KomobarTheme.

Two differences, neither visible in a schema: the macOS bar had no MediaDisplayFormat (it has no media widget), so it now has the unused type; the unit tests count as 3 in komorebi-bar-core instead of 3 in each bar, and run natively on Linux. The bars take the crate's schemars feature from their own (komorebi-bar-core/schemars).

Removed: 170 lines from the Windows config.rs and 149 from the macOS one; 174 added once (170 moved, 4 import lines).

Verified: Windows cargo clippy --workspace --locked --all-targets -D warnings clean; macOS fake-CC cargo check --locked --workspace --all-targets finished, 0 errors (bar warnings 6, unchanged); native cargo test -p komorebi-bar-core: 3 unit tests (the moved extend_* tests) + 2 schema oracle passed, 0 failed; harness regeneration (S/bar2/regen.sh): macOS byte-identical (3,774 lines), Windows byte-identical (9,090 lines). Not verified: rendering.
2026-10-31 01:37:54 -07:00
LGUG2Z c9d03e2ff4 refactor(bar-core): move the shared statics and Alignment into komorebi-bar-core
Pure move of what every bar module reads and nothing in it depends on: the monitor and label statics of main.rs (MAX_LABEL_WIDTH, MONITOR_LEFT/TOP/RIGHT/INDEX, BAR_HEIGHT, DEFAULT_PADDING, AUTO_SELECT_FILL/TEXT_COLOUR, and the Windows WIDGET_CLICKED flag with its two functions) and the three-variant Alignment of bar.rs. Each bar re-exports them (pub use komorebi_bar_core::statics::*, pub use komorebi_bar_core::alignment::Alignment), so crate::MAX_LABEL_WIDTH and crate::bar::Alignment resolve as before.

The only difference between the trees is that the macOS bar now also has the WIDGET_CLICKED flag and its two functions; nothing in the macOS bar calls them (only the Windows systray widget does), so there is no behaviour change.

Verified: Windows cargo clippy --workspace --locked --all-targets -D warnings clean; macOS fake-CC cargo check --locked --workspace --all-targets finished, 0 errors; native cargo test -p komorebi-bar-core 2 passed, 0 failed. Not verified: rendering.
2026-10-31 01:37:54 -07:00
LGUG2Z 8ad6213b04 refactor(bar-core): move selected_frame.rs and ui.rs into komorebi-bar-core
Pure move of the two files of the bar that depend on nothing else in it, byte-identical in the Windows and macOS bars (selected_frame.rs since the previous revision). Each bar keeps its paths: main.rs re-exports the modules (pub use komorebi_bar_core::selected_frame / ui), so every crate::selected_frame::... and crate::ui::... in the widgets and bar.rs resolves as before. The crate takes eframe from the workspace; the macOS bar takes the crate by path.

Removed: 111 lines (89 + 22) from each of the two bars, 111 added once.

Verified: Windows cargo clippy --workspace --locked --all-targets -D warnings clean; macOS fake-CC cargo check --locked --workspace --all-targets finished, 0 errors (bar warnings 10 -> 6, the four strokes of the previous revision); native cargo test -p komorebi-bar-core 2 passed, 0 failed; harness schema regeneration (S/bar2/regen.sh): macOS byte-identical to its schema.bar.json (3,774 lines), Windows byte-identical to the regenerated schema.bar.json (9,090 lines). Not verified: rendering.
2026-10-31 01:37:54 -07:00
LGUG2Z eaf43a2ca4 build(bar-core): add the komorebi-bar-core crate with the schema oracle test
Skeleton of the shared bar crate (D25/D26): an empty library and the oracle that every later move of bar code is held to. tests/schema_oracle.rs pins the checked-in schema.bar.json of the Windows bar (9,090 lines, FNV-1a 0x557301341968bff9) and of the macOS bar (3,774 lines, 0xc99e02949424e5d7), so any change to either file, intended or not, fails the test until the pin is updated in a reviewed revision. Once config.rs moves into the crate the test generates the schema and compares it with the file instead.

The pins are of the files as they are after the previous revision (the regenerated Windows file) and are the values the out-of-tree harness (S/bar2/regen.sh over the real sources) reproduces: macOS byte-identical, Windows byte-identical to the regenerated file.

Verified: native cargo test -p komorebi-bar-core: 2 passed, 0 failed; Windows cargo clippy --workspace --locked --all-targets -D warnings clean; macOS fake-CC cargo check --locked --workspace --all-targets finished, 0 errors.
2026-10-31 01:37:54 -07:00
LGUG2Z e950c8f967 chore(repo): combine step 3 with the integration line
Merge revision: wip-main (history merges, fixes, step 0, steps 1, 2, 4 and 5, flake work, seam document) and wip-step3 (komorebi-config: matching types, behaviour enums, AspectRatio/PerAnimationPrefixConfig, asc and the application configuration data, placement types, the one StaticConfig with per-platform schema views, the daemons wired to it). Conflicts between step 3's edits and step 4's restructuring of komorebic (the schema-printing arms moved into the WindowsCommands/MacosCommands/KdeCommands matches) and in the Cargo.lock files are resolved here once, instead of through 35 separate rebases.
2026-10-31 01:37:54 -07:00
LGUG2Z fb545cf7a4 test(komorebic-core): the macOS pages in docs/cli are what the command line renders
Renders the macOS command line through komorebic-core (it needs no macOS to do it) and compares every page with komorebi-for-mac/docs/cli after the page transform macOS uses (a heading, 'Usage: komorebic <name>'), and the number of pages. It found the four macOS pages that step 2 left stale (the previous revision regenerates them) and keeps all 158 in step from now on. The test returns early when the macOS tree is not in the source (the nix build of the Windows workspace).

Verified: native cargo test -p komorebic-core --test docs_cli: 1 passed.
2026-10-31 01:37:54 -07:00
LGUG2Z 5763df9f73 feat(config): the platform table, read from the markers of the one StaticConfig
platforms::reads(platform, key) says whether a platform reads a top-level key and platforms::unsupported_keys(raw, platform) lists the keys of a configuration text that are keys of komorebi.json but that the platform does not read, which is what a daemon can say at load time instead of dropping them silently. The table is the x-komorebi-platforms markers themselves, so a key cannot be added without saying who reads it. The daemons do not call it yet: reporting at load time is a visible change of behaviour for the user's decision.

The oracle test checks the table against the three checked-in schemas (every key of a tree's schema.json is read by that platform) and on examples.

Verified: native cargo test -p komorebi-config: 4 unit + 9 schemas + 1 doctest passed, 0 failed (tier 1).
2026-10-31 01:37:54 -07:00
LGUG2Z 6dbcfdf1f3 refactor(komorebic-core): fifteen subcommands whose help differed join Messages with one help text
border-colour, monocle-focus-behaviour, toggle-monocle-focus-behaviour, toggle-window-container-behaviour, toggle-workspace-window-container-behaviour, unmanaged-window-operation-behaviour, scrolling-layout-columns, scrolling-layout-full-width, expel, retile, center-focus, consume, toggle-column-stack, eager-focus and promote-focus had the same arguments, flags and messages in the three trees and only their help text differed. They move into Messages (and out of WindowsCommands, MacosCommands and the KDE komorebic with their arms and argument structs) with ONE text each: the Windows text, except for eager-focus and promote-focus, where the wording two of the trees shared wins ('executable', 'largest window container').

The page diff against the pre-move output is docs only (reviewed, kept in the progress log): KDE says colour and behaviour instead of color and behavior in six pages; Windows eager-focus and promote-focus change a phrase; macOS loses the longer descriptions it had for center-focus, consume, expel, retile and toggle-column-stack and the three of its scrolling pages that differ from the Windows wording get the Windows text. Nothing that changes behaviour (flags, defaults, possible values, aliases) differs. The longer macOS descriptions make claims about the other layouts and about retile that were not checked for the Windows and KDE daemons, so the shorter text was kept; restoring them is a one-revision change to messages.rs. data-directory, disable-autostart, log (platform facts: paths, shortcuts, komorebi.exe), enable-autostart, start, stop and pan stay in the platform enums.

Verified: oracle compare: only the pages above differ (windows 2, macos 7, kde 6 of 187/158/166); native cargo test -p komorebic-core 34 passed; KDE cargo test -p komorebic --locked 80 passed; macOS fake-CC cargo check --locked 0 errors; Windows clippy --workspace --locked --all-targets -D warnings clean.
2026-10-31 01:37:54 -07:00
LGUG2Z a686b61916 refactor(komorebic-core): animation, animation-duration and animation-style join Messages
The three animation subcommands were listed as differing in arguments between the trees because the possible values of --animation-type came from three different AnimationPrefix enums. Since step 2 there is one, and the pages of the three trees are now the same text (checked by comparing the rendered pages without fences and widths), so the Windows definitions move into Messages and the copies in WindowsCommands, MacosCommands and the KDE komorebic go, with their arms. The pages are byte-identical to the oracle: this is a move, not a docs change.

Verified: oracle: windows 187, macos 158, kde 166 pages byte-identical; native cargo test -p komorebic-core: 31 + 3 passed; KDE cargo test -p komorebic --locked: 80 passed; macOS fake-CC cargo check --locked: 0 errors; Windows clippy --workspace --locked --all-targets -D warnings clean.
2026-10-31 01:37:54 -07:00
LGUG2Z f605a555e7 refactor(config): add read, read_raw and the load notices to StaticConfig
read_raw and read are identical in the three daemons (KDE's docs and doctest are taken); end_of_life, aliases and deprecated are the Windows pure string checks that print what a configuration file should change. None of them touches a window manager, so they live next to the type; the daemons' own inherent impls cannot (the orphan rule), they become an extension trait in the wiring revisions.

Verified: native cargo test -p komorebi-config: 4 unit + 8 schemas + 1 doctest passed, 0 failed (tier 1).
2026-10-31 01:37:54 -07:00
LGUG2Z be3d1cd6b6 refactor(komorebic-core): the Launcher skeleton for start, and the stop message
start is the same dance in all three komorebic: look whether komorebi is running, start it, wait three seconds and look again, give up after three attempts by running it in front of the user so its stderr can be read. launcher::start runs that loop over a Launcher (daemon_name, spawn, is_running, diagnose) that each platform implements by hand; what follows a successful start (companions, the licence text) stays in each komorebic, as does stop's handling of companions, whose order differs per platform; only stop_message(ignore_restore) is shared. The loop flushes standard output after 'Waiting for ... to start...' (KDE did, macOS and Windows did not) and puts the same space after it; both are invisible in the text printed. Nothing uses it yet.

Verified: native cargo test -p komorebic-core: 31 passed, 0 failed (4 new launcher tests with a fake launcher: already running, second try, three tries then diagnose, stop message).
2026-10-31 01:37:54 -07:00
LGUG2Z 2200bec995 refactor(config): add the one StaticConfig and its schema views to komorebi-config
static_config.rs holds the union: StaticConfig with all 56 keys and no cfg on a field, BorderColours, ThemeOptions, Wallpaper, WorkspaceConfig, MonitorConfig, AppSpecificConfigurationPath, AnimationsConfig, TabsConfig and StackbarConfig, taken from the Windows text (the superset) plus the KDE/macOS keys (border_radius, titleless_applications, tabbed_applications, window_hiding_position). A key that not every platform reads carries x-komorebi-platforms (22 Windows-only, 5 Windows+KDE, 4 KDE+macOS); the five #[deprecated] attributes became schemars deprecated markers so no daemon gets a deprecation warning for a key it still reads. The schema defaults that read daemon run-time statics are constants in defaults.rs (Windows values).

changes.rs holds what a platform's schema says differently from the union as data: descriptions (spelling and platform sentences), the defaults that are platform facts (paddings 10/5/5, border width 8/10/10, offset -1/0/0, no transparency default on KDE) and the nine komorebi-themes stackbar fields KDE and macOS never had. It was generated from the difference between the union view and each tree's checked-in schema.json; every entry is a candidate for a converge revision. schema::view applies it after pruning.

The oracle test builds the KDE and macOS views of StaticConfig and compares them with schema.json byte for byte: both identical (KDE 127164 bytes, macOS 120057 bytes). Windows is added when komorebi-themes drops its target_os cfgs (a Linux build omits those nine fields). No tree uses the new types yet.

Verified: native cargo test -p komorebi-config: 4 unit + 8 schemas passed, 0 failed (tier 1).
2026-10-31 01:37:54 -07:00
LGUG2Z 6d35c0c365 refactor(komorebic-core): add pan, the reader of the gesture stream, from the Windows komorebic
komorebic/src/pan.rs (parse_line, Forwarder, next_merged, the Sink and Link traits, Socket and run, with their 16 tests) is copied into the crate with its imports pointed at komorebi-layouts and komorebi-protocol. The one impl that needs the client, Link for komorebi_client::Connection, is left to each komorebic (an orphan); the tests read the transcripts of komorebic/fixtures, which the pan tests of both daemons use as well. The 16 tests could not run on Linux before and now do. Nothing uses the copy yet.

Verified: native cargo test -p komorebic-core --lib: 27 passed, 0 failed (11 + the 16 pan tests).
2026-10-31 01:37:54 -07:00
LGUG2Z 788265481e test(komorebic-core): every platform command line builds and keeps its subcommand count
Adds crates/komorebic-core/tests/platforms.rs (49 lines). It builds the clap command for the Windows, KDE and macOS option sets and asserts each keeps its subcommand count, so a variant dropped from the shared Messages enum fails loudly. Verified: cargo test -p komorebic-core passes natively on Linux.
2026-10-31 01:37:54 -07:00
LGUG2Z b0e4479723 refactor(config): move the placement types into komorebi-config
WindowPlacement, PlacementTarget (with its Display, FromStr, Ord, Serialize, Deserialize and hand-written JsonSchema impls), PlacementMatchingRules and InitialWindowPlacementRules, from the KDE copy: Windows and KDE have identical WindowPlacement, PlacementMatchingRules and InitialWindowPlacementRules, and PlacementTarget differs (Windows accepts the same values through a visitor and writes a different hand-made schema). The KDE copy is the union; Windows' schema text for PlacementTarget is carried as a per-platform difference of the view in the StaticConfig stage, so the Windows tree switches to these types in that stage, not before (switching earlier would change what its own derived schema prints).

The oracle checks the generated definitions of the three enums against Windows and KDE schema.json and PlacementTarget against KDE's.

Verified: native cargo test -p komorebi-config: 4 unit + 7 schemas passed, 0 failed (tier 1).
2026-10-31 01:37:54 -07:00
LGUG2Z 64457a97a3 refactor(komorebic-core): move the Windows subcommand definitions into the crate
WindowsCommands and the argument structs of the subcommands that are not in Messages (start, stop, check, kill, the border, stackbar and custom-layout commands, the app-specific-configuration converters and the ones whose help still differs between the trees) move from komorebic/src/main.rs to komorebic-core::platform::windows with their fields pub; the Windows main.rs keeps the handlers (2164 to 1520 lines). Three fields named after a protocol type that a struct of the same name shadows (BorderStyle, BorderImplementation, StackbarMode) are written with their full path. docgen-all now writes the Windows pages too, so no harness is needed any more.

Verified: oracle (step4/oracle-check.sh, Windows pages from the docgen-all example): windows 187 of 187 pages byte-identical, macos 158 of 158, kde 166 of 166; Windows cargo clippy --workspace --locked --all-targets -D warnings clean.
2026-10-31 01:37:54 -07:00
LGUG2Z 001b7c98cd refactor(komorebic-core): move the macOS subcommand definitions into the crate
MacosCommands and the argument structs of the subcommands that are not in Messages (start, stop, quickstart, permissions, pan, the animation commands and the 18 whose help still differs between the trees) move from komorebi-for-mac/komorebic/src/main.rs to komorebic-core::platform::macos with their fields made pub; the macOS main.rs keeps the handlers (1293 to about 600 lines). The definitions have no macOS-only dependency, so the pages of the macOS command line can now be written on any host: the docgen-all example of the crate does it, and replaces the harness that compiled the clap section of main.rs. clap_complete joins the crate for Completions.

Verified: oracle (step4/oracle-check.sh, macOS pages now from the docgen-all example): macos 158 of 158 pages byte-identical (windows 187 and kde 166 still pass); macOS fake-CC cargo check --locked --workspace --all-targets: 0 errors; KDE cargo check -p komorebic: ok.
2026-10-31 01:37:54 -07:00
LGUG2Z 89c729e731 refactor(config): move asc and the application configuration data into komorebi-config
ApplicationSpecificConfiguration, AscApplicationRulesOrSchema and AscApplicationRules are code-identical in the trees except that AscApplicationRules differs in its rule kinds: five Windows-only kinds, titleless and tabbed on KDE and macOS. The crate holds the union (declaration order floating-first as before; the platform-only fields carry x-komorebi-platforms, so a view prunes them) and, for the Windows From<Vec<ApplicationConfiguration>> conversion that the orphan rule keeps next to its types, ApplicationOptions and ApplicationConfiguration (data only: the YAML generator stays in the Windows daemon). The crate gains the color-eyre dependency.

The oracle test builds the view of ApplicationSpecificConfiguration for Windows, KDE and macOS and compares it with each tree's checked-in schema.asc.json byte for byte: all three are identical. This is the first proof that per-platform views of one union reproduce the checked-in files exactly (D17/D18); the three trees still use their own copies.

Verified: native cargo test -p komorebi-config: 4 unit + 6 schemas passed, 0 failed (tier 1).
2026-10-31 01:37:54 -07:00
LGUG2Z befbc200d0 feat(config): generate a platform's schema as a view of the union schema
schema::view prunes the schema of a type to what one platform reads: properties whose x-komorebi-platforms list does not name the platform are dropped (and removed from required), the markers are stripped, and the definitions that nothing refers to any more are removed, in the original order. Descriptions that komorebi-protocol writes differently per compile target (ApplicationIdentifier::Class, PathBuf, FocusFollowsMouseImplementation) are set per platform from a table, so a view is the same on every host. The approach is the one of D17/D18; the next revision proves it on the applications schema.

Verified: native cargo test -p komorebi-config compiles (tier 1); exercised by the next revision's test.
2026-10-31 01:37:54 -07:00
LGUG2Z a23cd0a78e refactor(komorebic-core): the Messages enum with one exhaustive conversion to SocketMessage
Messages holds the 121 subcommands that are identical in the three komorebic (same arguments, same help, one SocketMessage each; the six that ask something are named by Messages::is_query). Every variant carries the doc comment and attributes it had, so the help is unchanged, and one exhaustive match turns a Messages into the SocketMessage it names, so a subcommand added to the enum cannot be left without its message. The argument structs those subcommands used (about 20 of them) join the blocks in args.rs; the ones that repeated the monitor/workspace pair, the application identifier or the four sides of an offset flatten the shared block.

cli.rs adds Opts<P>, the whole command line generic over the subcommands P of a platform, with Messages and P flattened side by side (so every subcommand stays top level, and no cfg is needed to pick the platform), plus Meta for the texts at the top of the help and command()/parse() so each komorebic passes its own about, author and version. Nothing uses them yet.

Verified: native cargo test -p komorebic-core: 11 passed, 0 failed (includes clap's debug_assert of the command built with Messages and a platform enum).
2026-10-31 01:37:54 -07:00
LGUG2Z 44e0b32459 refactor(config): move AspectRatio and PerAnimationPrefixConfig into komorebi-config
AspectRatio and PredefinedAspectRatio (with the Default and width_and_height impls) and the generic PerAnimationPrefixConfig<T> have identical code in the three trees and, after the previous converge revisions, identical docs; the crate takes the Windows copy. The trees keep their copies until the next revisions.

The oracle gains a check that the generated AspectRatio and PredefinedAspectRatio definitions equal those of all three checked-in schema.json files. PerAnimationPrefixConfig has no check of its own: its definitions are numbered per instantiation, so the AnimationsConfig move covers it.

Verified: native cargo test -p komorebi-config: 4 unit + 5 schemas passed, 0 failed (tier 1).
2026-10-31 01:37:54 -07:00
LGUG2Z c5e13a2d91 refactor(komorebic-core): replace the gen_* macros by generic clap argument blocks
The ten gen_* macros of komorebic (textually the same in the three trees) become plain structs in komorebic-core: Target, NamedTarget, WorkspaceTarget, Size, NamedPadding, WorkspacePadding, PaddingAdjustment, ApplicationTarget and the generic OneEnum<T>, WorkspaceEnum<T> and NamedWorkspaceEnum<T>, which flatten the shared blocks instead of repeating them. A generic positional would be named VALUE, so ValueName gives OneEnum<T> the name the field of the macro struct had (OPERATION_DIRECTION, CYCLE_DIRECTION, ...) and the help stays the same. Limits of generic clap structs (T needs Send + Sync + 'static; a doc comment on a generic field is the help of every instantiation, so the String workspace name has its own struct; flattening two instantiations into one command panics at run time) are in the module docs. The blocks carry plain comments, not doc comments: clap takes the doc comment of an Args struct as the about of the command it is flattened into. Nothing uses the blocks yet.

Verified: native cargo test -p komorebic-core: 6 passed, 0 failed.
2026-10-31 01:37:54 -07:00
LGUG2Z e09f3591bd build(komorebic-core): add the crate with the docgen renderer
A new workspace member crates/komorebic-core, the home of the clap definitions the three komorebic packages share (step 4, D20-D22). This revision only has the docgen renderer: DocFormat keeps what differs between the trees (KDE pins the width at 100, fences with ```text, ends with a newline and writes a page for a subcommand of a subcommand; Windows and macOS set no width) and the list of commands that get no page, which is the same in all three. Nothing uses it yet.

Verified: native cargo test -p komorebic-core: 3 passed, 0 failed.
2026-10-31 01:37:54 -07:00
LGUG2Z d55b6daeb7 refactor(transport): add EventChannel
EventChannel<E> is the window-event channel that KDE and macOS each write by hand in window_manager_event_listener.rs: bounded, send is try_send and never blocks, an event that does not fit is dropped with the warning 'channel is full; dropping notification', and an optional predicate (EventChannel::coalescing) lets one coalesced event (the ResizeStep of a drag) wait at a time, released by coalesced_taken(), also when the send of the claiming event fails. The channel also records that an event was dropped (take_overflow); no tree reads it (R6 is a behaviour change, out of scope here). The flag orderings are SeqCst as in the KDE copy (macOS used AcqRel/Release; stronger on one variable, no visible difference). Nothing uses it yet. Logs use the target komorebi::transport (D29).

Verified: cargo test -p komorebi-transport: 13 passed, 0 failed (5 new, the three macOS coalescing tests rewritten against the API, plus no-predicate and drop/overflow); cargo clippy -p komorebi-transport --all-targets -D warnings: clean.
2026-10-31 01:37:54 -07:00
LGUG2Z 79c9af95d1 refactor(config): move the behaviour enums into komorebi-config
WindowContainerBehaviour, FloatingLayerBehaviour (with should_float), Placement (with should_center and should_resize), CrossBoundaryBehaviour, WindowHidingPosition (KDE and macOS), WindowHandlingBehaviour and ZOrder (with its From<ZOrder> for isize; Windows) in behaviour.rs. The text is the KDE copy for the five that all trees share (identical to the Windows and macOS copies after the previous converge revision) and the Windows copy for the two Windows-only enums. A platform-only enum is defined here without a cfg: the daemon that does not read the key says so at load time. The trees keep their copies until the next revisions. The crate gains the clap dependency (ValueEnum) and the three Cargo.lock files the package entry.

The oracle gains a check that the generated definition of each enum equals the $defs entry of every checked-in schema.json that has it (all three trees for four of them, KDE and macOS for WindowHidingPosition, Windows for the other two).

Verified: native cargo test -p komorebi-config: 4 unit + 4 schemas passed, 0 failed (tier 1).
2026-10-31 01:37:54 -07:00
LGUG2Z 2f682ae6bf refactor(transport): add the subscriber push
komorebi_transport::notify_sockets is the mechanics of notify_subscribers that the three daemons each copy: for each subscriber socket, skip it if it wants state changes only and nothing changed and the event is not an override event, connect, write the notification, collect the sockets that cannot be connected to, then remove those from the registry and delete their socket file (errors logged). The daemon keeps the registry statics and their locks (the public SUBSCRIPTION_SOCKETS path is used by the bar, main and a KDE doctest, and the Mutex type is the daemon's own parking_lot) and passes what differs per platform: the registry, the push_unchanged decision (state modified or one of its platform's override events), the data dir and the connect function (uds_windows on Windows). Nothing uses it yet. Logs use the target komorebi::transport (D29).

Verified: cargo test -p komorebi-transport: 8 passed, 0 failed (2 new: push and stale removal, state-change filter); cargo clippy -p komorebi-transport --all-targets -D warnings: clean.
2026-10-31 01:37:54 -07:00
LGUG2Z 5294f20278 refactor(transport): add the generic command listener
komorebi-transport gets the accept loop and the read loop that the three daemons each carry a copy of: listen_for_commands (restart-on-panic outer thread, listener cloned under the window manager lock, one thread per client) and read_commands (one message per line, the 1 s try_lock_for on the window manager with the warn-and-drop when it is not got, the pause filter, return after a paused message). The window manager is reached through small traits: CommandTarget (shared handle type, listener handle, try_lock_for, is_paused, process_command), CommandListener/CommandStream (implemented here for std::os::unix::net and, on Windows, uds_windows), and a ConnectionPolicy hook (on_accept, on_message; NoPolicy by default) for the Windows read timeouts. The lock itself is taken by the daemon (CommandTarget::try_lock_for) because KDE uses parking_lot 0.13 and the other trees 0.12, so the crate cannot name the Mutex type (D30). This is a behaviour-preserving generalisation of the loops, not a literal text move. Nothing uses it yet.

The crate logs under the target komorebi::transport: the KDE and macOS daemons filter with RUST_LOG=komorebi=<level>, which would silence a target named komorebi_transport (D29).

Verified: cargo test -p komorebi-transport: 6 passed, 0 failed (5 new: messages in order, paused ends the connection, invalid line is an error, policy sees messages, listener serves a connection over a real socket); cargo clippy -p komorebi-transport --all-targets -D warnings: clean.
2026-10-31 01:37:54 -07:00
LGUG2Z 504290e5a8 refactor(config): move the matching types into komorebi-config
MatchingRule, WorkspaceMatchingRule, IdWithIdentifier, MatchingStrategy and IdWithIdentifierAndComment (with the From impl) are byte-identical, docs included, in config_generation.rs of the three trees apart from imports. The crate takes the KDE copy (module docs and its four tests) with ApplicationIdentifier from komorebi-protocol. The trees still carry their own copies; they switch in the next revisions.

The oracle test gains a check that the generated definitions of MatchingRule, IdWithIdentifier and MatchingStrategy equal the $defs of all three checked-in schema.json files (ApplicationIdentifier is the protocol crate's and differs per platform by design).

Verified: native cargo test -p komorebi-config: 4 unit + 3 schemas passed, 0 failed (tier 1).
2026-10-31 01:37:53 -07:00
LGUG2Z 06436c98fe test(config): pin the checked-in schemas of the three trees as the oracle
tests/schemas.rs reads schema.json and schema.asc.json of the Windows tree, komorebi-for-kde and komorebi-for-mac by path and checks that every file is in the canonical form the generator prints (so a regenerated schema compares with == on text) and that each keeps today's keys: 53/37/33 properties, 60/52/47 definitions, the six definitions of the applications schema. Nothing is generated from the crate yet, so at this point the test guards the three files against accidental edits; each following stage adds a comparison of the types it moves with these definitions.

Verified: native cargo test -p komorebi-config: 2 passed, 0 failed (tier 1). The three checked-in files were byte-identical to their regenerated forms before this stage (Windows 141169/5945 bytes, KDE 127164/5051, macOS 120057/5051; S/step3/regen.sh).
2026-10-31 01:37:53 -07:00
LGUG2Z 9e39fcbbf4 build(transport): add the komorebi-transport crate with the pause filter
A new platform-neutral crate, crates/komorebi-transport (edition 2024, member of the root workspace), that will hold the command transport shared by the three daemons (D23/D24). It starts with allowed_while_paused(&SocketMessage) -> bool: the four messages (TogglePause, State, GlobalState, Stop) a daemon still handles while it is paused. It is a free function and not a method of the protocol enum because it is daemon policy, not wire format. Nothing calls it yet. A unit test lists the four and checks that other messages are refused.

Verified: cargo test -p komorebi-transport: 1 passed, 0 failed (native, tier 1). The tree gates run in the revisions that adopt it.
2026-10-31 01:37:53 -07:00
LGUG2Z 67c1189bd8 build(config): add the komorebi-config crate skeleton
Add crates/komorebi-config (edition 2024, root workspace member) with the feature-gated schema module that prints a schema the way komorebic does (pretty JSON plus a newline). No type has moved yet: the crate depends on komorebi-layouts, komorebi-themes and komorebi-protocol, which every type that follows needs.

Verified: native cargo test -p komorebi-config compiles (tier 1). Cargo.lock gains only the new package. Windows, KDE and macOS trees do not depend on the crate yet.
2026-10-31 01:37:53 -07:00
LGUG2Z 374ad1bad5 chore(repo): combine step 2 with the integration line
Merge revision: wip-main (history merges, fixes, step 0 path dependencies, step 1 komorebi-collections, KDE A3 fixes, flake phase 2) and wip-step2 (the komorebi-protocol crate, goldens and the unified SocketMessage used by all three trees). Conflicts in the root Cargo.toml members line, the trees' Cargo.toml dependency tables and the Cargo.lock files are resolved in this revision by keeping both sides' additions.
2026-10-31 01:37:53 -07:00
LGUG2Z 68c3133d4e refactor(protocol): use SocketMessage from komorebi-protocol in the Windows daemon
Delete the Windows SocketMessage, its as_bytes and FromStr (now in the crate) and re-export komorebi_protocol::message::SocketMessage from core, so komorebi::SocketMessage and komorebi_client::SocketMessage keep working. process_command stays an exhaustive match: the two KDE-only variants (ReloadHotkeys, HotkeysState) get one explicit arm that writes the 'not supported on this platform' reply (SocketMessage::not_supported_reply) and returns before anything else happens, so subscribers are not notified either. Behaviour change: those two used to fail to parse on Windows and now parse and are answered that way.

Verified: Windows clippy --workspace --locked --all-targets -D warnings clean; native cargo test -p komorebi-protocol passes. Windows unit tests and e2e not run (cannot on Linux).
2026-10-31 01:37:53 -07:00
LGUG2Z 7e1db827bd feat(protocol): add the unified SocketMessage and the platforms() support table
message.rs holds the one SocketMessage with the 197 variants of the three trees: the Windows enum verbatim (195 variants, in its order, with ApplyState(StateBlob) and DebugWindow(i64) as converged in the previous revisions) plus the two KDE-only variants (ReloadHotkeys, HotkeysState) at the end, as_bytes and FromStr. platforms.rs has Platform, Platforms and SocketMessage::platforms(), one exhaustive match generated from tests/golden/variants-table.tsv (155 on all three, 33 Windows only, 6 Windows and KDE, 1 Windows and macOS, 2 KDE only). No tree uses it yet.

The golden test now runs the real enum: every golden instance of each platform is accepted with identical wire text (path-expansion instances only with STRICT_PATHS=1, which is meaningful on the target platform only; ApplyState on Windows by tag), every variant of every platform is in the unified schema and every generated instance round-trips, and platforms() matches each platform's variant list both ways.

Verified: native cargo test -p komorebi-protocol: 5 unit + 6 golden + 2 doctests passed, 0 failed. Not run: STRICT_PATHS=1 (fails on Linux for the Windows goldens by design).
2026-10-31 01:37:53 -07:00
LGUG2Z 4a5733eb1d refactor(collections): move ring.rs and lockable_sequence.rs into komorebi-collections
Moves the identical ring.rs and lockable_sequence.rs out of komorebi/src into the crate (rename). Necessary edits: lib.rs with pub use Lockable and a hidden pastey re-export; #[macro_export] on impl_ring_elements! using $crate::pastey::paste!; the doc test now says komorebi_collections::ring::Ring. The Windows tree gets the path dependency, re-exports (pub use komorebi_collections::{ring, lockable_sequence, Lockable}, #[macro_use] extern crate), forwards its schemars feature, and drops pastey again. Windows is bundled here because it is the tree the files move out of.

Verified: fast-check native, Windows clippy --all-targets -D warnings, KDE and macOS checks: PASS.
2026-10-31 01:37:53 -07:00
LGUG2Z d3ecfb0acb feat(protocol): add StateBlob, the opaque payload of ApplyState
A transparent wrapper of serde_json::Value with a hand written schema (an object, named State), so that SocketMessage can carry ApplyState without the crate knowing the daemon's State type (D15/D16). On the wire it is exactly the state JSON.

Verified: native cargo test -p komorebi-protocol: 5 unit + 3 golden + 1 doctest passed, 0 failed.
2026-10-31 01:37:53 -07:00
LGUG2Z 11e38c8c8a build(collections): add the komorebi-collections crate
New crates/komorebi-collections (edition 2024; deps pastey, serde, optional schemars feature, dev serde_json_lenient) added to the root workspace members, with an empty lib.rs. Cargo.lock for this revision alone is not refreshed (it is in the next revision).

Verified: cargo test -p komorebi-collections --offline built (0 tests yet).
2026-10-31 01:37:53 -07:00
LGUG2Z 9d8d36ac8b refactor(protocol): move the Windows-only payload enums and FocusFollowsMouseImplementation into komorebi-protocol (Windows)
StackbarMode, StackbarLabel, BorderStyle, BorderImplementation, HidingBehaviour and FocusFollowsMouseImplementation move verbatim from core/mod.rs to the end of crates/komorebi-protocol/src/types.rs, because the unified SocketMessage (stage 2e) carries them; they keep their platform-only status in the support table. The crate allows the deprecated HidingBehaviour::Hide that the derives touch. core re-exports all six.

Verified: Windows clippy --workspace --locked --all-targets -D warnings clean; native cargo test -p komorebi-protocol passes.
2026-10-31 01:37:53 -07:00
LGUG2Z 9483f849f8 test(protocol): restore the SubscribeOptions doctest in the protocol crate
KDE's SubscribeOptions carried a doctest that used SocketMessage; it was dropped from the shared text so the three trees could converge before the move. The crate now has a doctest that checks the wire form of the options ({"filter_state_changes":true}) and the default. The SocketMessage example returns when SocketMessage moves (2e).

Verified: native cargo test -p komorebi-protocol: 4 + 3 passed, doctest 1 passed, 0 failed.
2026-10-31 01:37:53 -07:00