docs/dev/core-model.md: per-file measured differences between the Windows, KDE and macOS container, monitor, workspace, window and window_manager (stripped code lines, changed lines, identical lines), the platform-specific fields and methods of each layer, a proposed split into a komorebi-core crate generic over a Platform (Backend plus a Copy-or-Clone Window and a state type) with the exact order of layers (container, monitor, workspace, window_manager, router), what the KDE stack of wip-u-kfde already did, what Windows must do before the crate can be created, and the list of KDE process_command arms over 20 lines with the method each became. Measurements are from diff of the files at this tip; nothing was run on Windows or macOS. Verified: not applicable (documentation); the numbers were produced by diff and wc on the files at wip-u-kfde.
11 KiB
The core model: KDE and Windows compared, and the split into komorebi-core
Status: measurement and proposal (plan step 9 preparation, KDE side). Measured at the tip of
wip-u-kfde (based on wip-main 633e7453); the Windows tree is komorebi/src, KDE is
komorebi-for-kde/komorebi/src, macOS (komorebi-for-mac/komorebi/src) is listed only where it
helps. Nothing here has been run on Windows or macOS.
1. Measured differences
"Code lines" drop // lines and blank lines and strip indentation, so KDE's much heavier doc
comments do not count; the inline #[cfg(test)] modules are included. "Changed" is the number of
</> lines of diff between the two stripped files (an insertion of n lines counts n, a
replacement of n lines counts 2n). "Identical" is the number of stripped lines present in both in
the same order (a common subsequence, so it is a lower bound on shared text).
| file | code lines Windows / KDE / macOS | changed W vs K | changed K vs M | changed W vs M | identical W&K |
|---|---|---|---|---|---|
container.rs |
230 / 254 / 241 | 196 | 119 | 185 | 144 |
monitor.rs |
643 / 666 / 675 | 613 | 205 | 616 | 348 |
workspace.rs |
2203 / 2491 / 2196 | 2314 | 1097 | 2113 | 1190 |
window.rs |
1134 / 916 / 1641 | 1168 | 1413 | 1767 | 441 |
window_manager.rs |
4716 / 4023 / 4896 | 4543 | 1597 | 5434 | 2098 |
Whole-file raw diff sizes (comments included): container 271, monitor 752, workspace 3167,
window 1520, window_manager 6358 changed lines (Windows vs KDE). ring.rs is already shared
(crates/komorebi-collections, Ring, Lockable).
Two things stand out. First, every file KDE and macOS share is much closer to each other than
either is to Windows (K vs M changed is a third to a half of W vs K for monitor, workspace,
window_manager, static_config, state). Windows is the odd one out, mostly because it carries older
and larger machinery (see section 2). Second, the structs themselves are almost the same:
Workspace has 31 fields on both sides and they differ only in the order of
work_area_offset_rules/work_area_offset; the differences are in the methods around them.
2. Platform-specific fields and methods, per layer
container (smallest)
- Same fields (
id,locked,windows: Ring<Window>). Windows'windowsis private andlockedhas#[serde(default)]; KDE'swindowsispub. - Window id type:
hwnd: isize(Windows) vsid: u32(KDE) incontains_window,idx_for_window,hide(omit). - Windows only:
hwnd_from_exe(KDE keeps onlyidx_from_exe),Window::restore_with_border/hide_with_borderinload_focused_window(Windows' border and stackbar are separate windows).exe()returnsResulton Windows,Optionon KDE. - After the converge revisions of this stack the signatures are the same (
add_window(Window),hide(&self, Option<id>),restore(&self),load_focused_window(&mut self), noResult) and the item order is the same. What is left is the id type and the three Windows-only lines above.
monitor
- Windows only fields:
name,device_id,workspace_names: HashMap<usize, String>;serial_number_idisOption<String>(KDE:String);idisisize(KDEu32). - Windows only methods: none in the public API; KDE has
assign_windows_to_workspaces(it pushes the workspace of each window to the compositor's desktops,Backend::set_window_workspace). - Behaviour that differs: Windows
load_focused_workspacerestores/hides workspaces itself, KDE asks the compositor to switch desktops (Backend::switch_workspace) and tiles what is in front; the Windowsupdate_workspaces_globalspushes padding/border/work area into every workspace (WorkspaceGlobals), KDE callsupdate_workspace_globals(idx, offset)for the focused one. KDE has aMonitorInformationwith 4 fields, Windows 6. BORDER_ENABLED(Windows) vsBORDER_DRAWN(KDE): the compositor draws borders.
workspace
- Windows only methods:
windows_on_show,border_kinds_on_show,hwnd_from_exe. - KDE only methods:
adopt_maximized_window,focus_floating_window,focus_previous_floating_window,prune_duplicate_windows,reap_invalid_windows,remove_floating_window(my reading, not verified: KDE learns of closes and moves through the bridge snapshot, so the workspace reaps and prunes). - 42 of 45/48
pub fnnames are common. Windowsrestore/hidetake the monitor handle and wallpaper and do the showing and hiding (WindowsApi, 8 mentions in the file); KDE leaves showing to the compositor (5backend()calls). - Scrolling layout glue (
scrolling.rs): 1328 lines KDE, 1315 macOS, ~2200 Windows; measured first, not part of this comparison.
window (not shared: the platform handle)
Window { hwnd: isize }(Windows, methods that callWindowsApi, 1134 code lines) vsWindow { id: u32 }(KDE,Copy, every method callsbackend(), 916 code lines) vs macOS (AX element, application, observer; notCopy, 1641 code lines).- Both Windows and KDE
WindowareCopyhandles, which is what lets the container and workspace code hold windows by value. macOS is not (clone). Windowis where the split goes: the core must not call a method ofWindowthat does not exist on all three.
window_manager
- Windows only fields:
known_hwnds,virtual_desktop_id,uncloack_to_ignore,has_pending_raise_op,pending_move_opwith a handle and rect,already_moved_window_handlesofisize. - KDE only fields:
opacity_applied,minimized_windows,pending_resize_op,live_resize,known_window_ids(au32map);pending_move_ophas no rect. - 43
WindowsApimentions in Windows'window_manager.rs, 15backend()in KDE's. Every one of them becomes aBackendcall or aWindowmethod in the core. process_commandandprocess_eventare not in this table (see section 4 and the plan).
3. Proposed split: komorebi-core
komorebi-core (new crate, no platform dependency)
ring, container, monitor, workspace, window_manager (generic)
trait Platform: Backend + { type Window; hooks } (what is not in Backend)
komorebi-backend (exists: Backend, WindowId, MonitorId, WindowInfo, Error, Capability)
komorebi (windows) / (kde) / (mac)
Window (the platform handle), the Backend implementation, event sources, borders, ...
Generic over what:
WindowManager<P: Platform>, withPlatform::Window: Copy + Eq + Hash + Serialize + Deserializeandfn id(&self) -> WindowId. The three id types (isize,u32,u32) disappear intoWindowId(WindowId::from_hwnd/from_u32, already inkomorebi-backend). macOS needsCloneinstead ofCopy: the generic code has to take&P::Windowand clone, or macOS keeps aCopyhandle and an id registry as the seam doc proposes (docs/dev/seam.md section 7, consequence 1).Backendis a trait object or a type parameter;docs/dev/seam.mdand the KDE conversion show it can be the shared trait unchanged, plus KDE's extension (drag_*, look, wallpaper) behindPlatformhooks.- Methods that are still platform behaviour (
Window::hide_with_border, showing and hiding a workspace on Windows vs asking for a desktop on KDE) move behind two hooks:Platform::show_workspace(monitor, idx)andPlatform::hide_workspace(...), with the Windows implementation hiding/restoring windows and the KDE one callingBackend::switch_workspace.
Exact order of layers (bottom-up, as in the plan)
- ring is done (
komorebi-collections). - container (230 lines): needs
Windowgeneric andid(). Smallest; do first. Windows must renamehwndparameters toWindowIdor anid()accessor and giveexe()the KDE return type (Option<String>) or both useResult. - monitor (643): needs
Workspacegeneric,MonitorId, and thePlatformshow/hide hooks. Windows' extra fields (name,device_id,workspace_names) stay in the shared struct asOption/default fields or become aP::MonitorExtraassociated type; KDEserial_number_idbecomesOption<String>with""mapped toNone. - workspace (2203): by far the biggest; the platform split is
restore/hide/windows_on_show/ border_kinds_on_show(Windows) andreap/prune/adopt_maximized(KDE). Scrolling glue is measured before it moves. - window_manager (4716): after the first three are shared. The platform fields (section 2) go
in a
P::Stateassociated type. - the flat router and
process_event(separate steps in the plan).
4. What KDE has done (this stack) and what Windows must do before the crate exists
Landed on wip-u-kfde (all native tests green, none of it run on a VM):
- KDE's
Backendextends the shared trait; ids, errors and info types are the shared ones at the trait; the core still holdsWindow { id: u32 }and converts at the call. container.rsandmonitor.rsare in the same order as Windows', andContainerhas the Windows signatures (add_window(Window), infalliblehide,restore,load_focused_window).- All
process_commandarms longer than 20 lines are named methods (list in section 5).
I did not create the shared crate: the Windows tree still has isize handles in Container,
Monitor::id: isize, and calls WindowsApi directly in 43 places of window_manager.rs.
Windows (kfw) has to:
- Finish adopting
Backendinwindow_manager.rs,workspace.rs,monitor.rs(the 8 + 43 + 2WindowsApicall sites there), so no core file namesWindowsApi. - Give
Containerthe same API KDE now has: accept aWindowId(orWindow::id()), returnOptionfromexe(), and drophwnd_from_exeor move it to a platform extension. - Make
Monitor.idandserial_number_idthe shared types (MonitorId,Option<String>), which changes whatkomorebic stateprints for Windows (a BEHAVIOUR change for the JSON; a test must pin it). - Convert its own multi-line
process_commandarms (the plan counts 22-23 per platform) into named methods with the same names KDE used (section 5), so the router can later be shared.
5. process_command: arms over 20 lines (KDE, before conversion)
| arm | lines | now |
|---|---|---|
| ToggleWorkspaceLayer | 90 | toggle_workspace_layer() |
| EagerFocus | 75 | eager_focus(exe) |
| ResizeWindowAxis | 60 | resize_window_axis(axis, sizing) |
| IgnoreRule | 55 | add_ignore_rule(identifier, id) |
| Query | 51 | answer_query(query, reply) |
| SessionFloatRule | 39 | add_session_float_rule() |
| ReplaceConfiguration | 34 | replace_configuration(config) |
| CycleFocusEmptyWorkspace | 32 | cycle_focus_empty_workspace(direction) |
| CloseWorkspace | 29 | close_workspace() |
| MoveContainerToLastWorkspace | 28 | move_container_to_last_workspace(follow) |
| SendContainerToLastWorkspace | 27 | same method, follow = false |
| CycleFocusWorkspace | 26 | cycle_focus_workspace(direction) |
| LayoutRatios | 23 | set_layout_ratios(columns, rows) |
| BorderColour | 21 | set_border_colour(kind, r, g, b) |
| NamedWorkspaceRule | 21 | add_named_workspace_rule(identifier, id, workspace) |
| InitialNamedWorkspaceRule | 21 | add_initial_named_workspace_rule(identifier, id, workspace) |
| ToggleWorkspaceWindowContainerBehaviour | 21 | toggle_workspace_window_container_behaviour() |
All are pub fn name(&mut self, ...) -> eyre::Result<()> in impl WindowManager
(process_command.rs), except set_border_colour, which cannot fail.