Commit 0de2957646aa
tarball1 file changed+153-0
@@ -0,0 +1,153 @@
1
+# Splitting `State`2
+3
+A sketch for review item 1. Nothing here is built yet.4
+5
+## Shape6
+7
+```rust8
+pub struct State {9
+ pub backend: Backend,10
+ pub renderer: VulkanRenderer,11
+ pub wm: Wm,12
+ pub input: Input,13
+ pub services: Services,14
+ pub protocols: Protocols,15
+ pub config: Settings,16
+ pub dirty: Dirty,17
+ pub core: Core,18
+}19
+```20
+21
+smithay requires every handler trait on `State`, and that stays. The `*_state(&mut self)` accessors just reach one level deeper. What changes is that functions take the sub-structs they need instead of `&mut State`, and disjoint borrows stop needing a 15-field destructure.22
+23
+### `Core`24
+25
+`display`, `handle`, `signal`, `socket_name`, `start`. These are the event loop and Wayland display plumbing that everything needs and nothing owns.26
+27
+### `Backend`28
+29
+```rust30
+pub enum Backend {31
+ Drm(Drm),32
+ Nested(Nested),33
+ // Headless(Headless) later, for tests and the smoke harness.34
+}35
+```36
+37
+What shared code needs from it:38
+39
+- `output(OutputId) -> Option<&Output>` and `outputs()`. This replaces `smithay_output`, the nested-only `output_id` field and `drm::output_for`. Each backend keeps its own `OutputId -> Output` map.40
+- `render_node() -> Option<DrmNode>`, replacing the `render_node` field.41
+- `syncobj()`, replacing the `syncobj` field. It exists only on DRM.42
+- `session()` for VT switching and the "is the session active" check. It returns `None` when nested.43
+- `render(state)`. Each backend keeps its own pacing and presentation (deadline scheduler or host frame callbacks) and calls the shared frame path for content.44
+45
+`after_dispatch` matches once instead of calling both backends' render and letting one no-op.46
+47
+### `Wm`, the window model48
+49
+```rust50
+pub struct Wm {51
+ pub layout: Layout,52
+ pub windows: SecondaryMap<WindowId, Managed>,53
+ by_surface: HashMap<ObjectId, WindowId>,54
+ pub unmapped: Vec<Window>,55
+ pub space: Space<Window>,56
+ pub popups: PopupManager,57
+ pub anim: Animator,58
+ pub layer_focus: Option<WlSurface>,59
+}60
+61
+pub struct Managed {62
+ pub window: Window,63
+ /// What the last layout pass configured, to spot tiled, floating and64
+ /// fullscreen switches.65
+ pub placed: Option<(Kind, Rect, OutputId)>,66
+ pub awaiting_buffer: bool,67
+ pub placed_by_rule: bool,68
+ pub list_handle: ForeignToplevelHandle,69
+ pub borders: [SolidColorBuffer; 4],70
+ /// Cached so publishers notice changes on commit instead of polling.71
+ pub app_id: String,72
+ pub title: String,73
+}74
+```75
+76
+- **`SecondaryMap` keyed by the layout's `WindowId`.** Layout already owns that key space, and the window model becomes a typed extension of it, not a parallel `HashMap`.77
+- **`by_surface` gives O(1) lookup by root surface.** It replaces `window_id`, `x11_window_id`, the toplevel key search and `ListHandles::window`. X11 windows join it when xwayland-shell associates their surface.78
+- **One method each for adding and removing.** `Wm::insert` and `Wm::remove(id) -> Option<Managed>` are the only ways in and out. Removal drops the index entry, sends the list handle `closed`, and tells the animator to forget the window. Nothing can be left behind in another map.79
+- **Kept per window:**80
+ - `last_kind`, now `placed`81
+ - `awaiting`82
+ - `placed_by_rule`83
+ - `scene::Borders`84
+ - `ListHandles`85
+ - the polled title and app id86
+- **Not moved into the record:** the animator's per-output tracks are multi-window plans, so they stay in `Animator`. Its per-window glides and spawns could move into the record, but keeping all motion state in one place is easier to reason about. Ghost textures move from `Animator` to the scene, so the animator holds only geometry and time.87
+- `unmapped` stays a plain `Vec<Window>`. These windows have no `WindowId` until their first commit, which is when rules can see their app id.88
+- Methods that are about windows move from `state.rs` to `wm.rs`: `map_window`, `remove_window`, the pure half of `apply_layout` (diffing placements into glides, retargets and configures), `configure`, `root`, `identity` and close.89
+90
+### `Input`91
+92
+`seat`, `seat_state`, `pointer_pos`, `suppressed`, `drag`, `move_inverted`, `cursor` (the xcursor images) and `cursor_status`.93
+94
+### `Services`95
+96
+The subsystems that already have their own types, gathered so their fields stop sprawling:97
+98
+- `lock`99
+- `captures`100
+- `imports`101
+- `xwayland`102
+- `launches`103
+- `ipc`104
+- `idle`, a new struct of `notifier`, `inhibitors` and `inhibited`105
+106
+### `Protocols`107
+108
+The inert globals: `compositor`, `xdg_shell`, `layer_shell`, `xdg_decoration`, `shm`, `dmabuf` plus its global, `output_manager`, the three capture states, `activation`, the three selection states, `session_lock`, `idle_inhibit`, `cursor_shape`, `xwayland_shell`, `data_device`.109
+110
+The publishers go here too: `workspaces`, `foreign_toplevels` and `toplevel_list`. Their sync code moves to a `publish.rs` that reads `Wm` (item 3).111
+112
+### `Settings`113
+114
+`config`, `config_path`, `binds`. Reload swaps this whole struct.115
+116
+### `Dirty`117
+118
+```rust119
+pub struct Dirty {120
+ pub redraw: bool,121
+ pub cursor: bool,122
+ /// Something clients see changed: windows, titles, tags, focus.123
+ pub snapshot: bool,124
+}125
+```126
+127
+`needs_redraw` and `cursor_moved` move here. `snapshot` gates the three publishers (item 3).128
+129
+## The frame path this enables (item 2)130
+131
+```rust132
+pub fn compose(ctx: FrameCtx<'_>, output: &Output, o: OutputId, now: u64) -> Vec<Draw>133
+pub fn send_frame_callbacks(wm: &Wm, services: &Services, output: &Output, elapsed: Duration)134
+```135
+136
+`FrameCtx` borrows `&mut renderer`, `&mut wm` (for the border buffers), `&input` (for the cursor), `&services.lock`, `&config.decoration` and the unmanaged X11 list. DRM, nested and capture all call `compose`. `anim.prune` moves to `after_dispatch`, after every output has drawn.137
+138
+## Migration139
+140
+Every step compiles and passes `nix flake check` on its own.141
+142
+1. `Protocols`, `Core`, `Settings`. This is pure field moves and accessor updates, with no logic changes.143
+2. `Services` and `Idle`.144
+3. `Backend` enum, folding in `output_id`, `render_node` and `syncobj`.145
+4. `Wm` with `Managed` and `by_surface`. This is the one with logic: insert and remove, and lookups through the index.146
+5. `Input` and `Dirty`.147
+6. The frame path (item 2) and publishers (item 3) on top.148
+149
+## Open questions150
+151
+- **Name the record `Managed` or `WindowState`?** smithay already has a `Window`, and layout has `layout::Window`. `Managed` avoids a third `Window`.152
+- **Keep `Space<Window>`?** It is used for hit testing, output enter and leave, and `element_under`. monolith positions everything itself, so Space mostly mirrors layout. Replacing it with our own hit test over the layout arrangement is possible, but not part of this split.153
+- **Does `layer_focus` belong in `Wm` or `Input`?** It is keyboard focus policy, which sits next to `sync_focus` in `Wm`, so this sketch puts it there.