Scroll-engine-specific surface. Window lifecycle and tile-request traffic for scrolling screens rides org.plasmazones.Tiling (the shared tiling-family pipeline, which routes per screen). This interface carries the scrolling screen set, the strip snapshot the settings app renders as its preview surface (plus the preset vocabulary beside it), the wheel-driven focusColumn and scrollView verbs, the four absolute width/height setters for external scripting, the clearWindowedFullscreen reconciliation call the KWin effect sends when a client leaves fullscreen on its own, the reapplyWindowGeometry repair call it sends on a fullscreen exit whose strip rects never moved, the blueprintProgressJson template-seed report, the stripChanged wake-up that tells a renderer its strip is worth re-reading, the focus-follows-mouse scroll cap's blocked-window list, and the scrollingScreensChanged / scrollEffectBehaviourChanged / scrollFocusScrollBlockedWindowsChanged change announcements for the three properties.
Methods
<tt>focusColumn()</tt>
Focus the adjacent column on a scrolling screen. Called by the KWin effect's Meta+wheel axis shortcut with the cursor's screen. A press at the strip's edge CROSSES to the adjacent output when one exists — including a neighbour running a different mode, where the daemon activates that engine's entry-edge window — and a press on an EMPTY scrolling screen crosses the same way instead of dead-ending, so a wheel notch can move focus to another monitor. Every rejected call is a silent no-op with no error reply: an empty screenId, a screen the scrolling engine does not own, a screen whose current scrolling context is disabled (the same per-context gate the keyboard verbs pass through), and any delta other than -1 or +1 are all ignored, so a caller cannot tell a refusal from a call that landed on an empty strip.
| Arg | Direction | Type | Description |
screenId | in | s *(string)* | Screen whose strip should move (the cursor's screen). An empty string is ignored. |
delta | in | i *(int32)* | -1 focuses the previous column along the strip, +1 the next one (left/right on a horizontal strip, up/down on a vertical one). Any other value is ignored. |
<tt>scrollView()</tt>
Scroll the view along a scrolling screen's strip by one configured step WITHOUT moving focus. Called by the KWin effect's Meta+Shift+wheel axis shortcut with the cursor's screen, one call per wheel notch, the pointer-gesture twin of focusColumn. The view stays where the scroll leaves it until the next focus change or centering verb, and a notch at the strip's end never crosses to another output, unlike focusColumn: it answers with the no-target navigation OSD (when that OSD is on) like the sibling verbs, and no error reply. A step that rounds to zero pixels answers the same way under its own no_movement reason. Same silent ownership and per-context gates as focusColumn: an empty screenId, a screen the scrolling engine does not own, a screen whose current scrolling context is disabled, and any delta other than -1 or +1 are ignored.
| Arg | Direction | Type | Description |
screenId | in | s *(string)* | Screen whose strip should move (the cursor's screen). An empty string is ignored. |
delta | in | i *(int32)* | -1 scrolls the view one step toward the strip's start, +1 one step toward its end. The step is the Scrolling view scroll step setting. Any other value is ignored. |
<tt>setColumnWidthProportion()</tt>
Set the focused column's width to a fraction of the work area (niri set-column-width with an absolute value; the shortcut surface carries no value argument, so the absolute setters live here). Same silent ownership and per-context gates as focusColumn, and the value is refused silently outside the proportion range the settings UI enforces (0.05 to 1.0). A value equal to the current intent answers with a no-target OSD rather than acting, like the step verbs, and a successful call also raises the navigation OSD — every one of the four setters reports through it when that setting is on, so a scripted caller should expect on-screen feedback per call.
| Arg | Direction | Type | Description |
screenId | in | s *(string)* | Screen whose focused column to resize. An empty string is ignored. |
proportion | in | d *(double)* | Work-area fraction, 0.05 to 1.0. |
<tt>setColumnWidthPixels()</tt>
Set the focused column's width to a fixed pixel value. Same gates, equal-value no-target answer and OSD feedback as setColumnWidthProportion; the value is refused silently outside the fixed range the settings UI enforces (100 to 10000). The relayout still caps a rendered column at the work area's extent along the strip.
| Arg | Direction | Type | Description |
screenId | in | s *(string)* | Screen whose focused column to resize. An empty string is ignored. |
px | in | i *(int32)* | Width in pixels, 100 to 10000. |
<tt>setWindowHeightProportion()</tt>
Set the focused window's height intent to a fraction of the work area ACROSS the strip (niri set-window-height with an absolute value; on a vertical strip that extent is the work area's width). Same gates, proportion range, equal-value no-target answer and OSD feedback as setColumnWidthProportion. Heights have no exact-proportion kind in the strip model: the value is stored as a fraction anchor that snaps to the NEAREST entry of the screen's effective height preset list at relayout, the same value-anchored contract the cycle-height shortcut walks. For an exact height use setWindowHeightPixels. The relayout renormalizes heights into the column budget, so an oversized value degrades rather than overflowing.
| Arg | Direction | Type | Description |
screenId | in | s *(string)* | Screen whose focused window to resize. An empty string is ignored. |
proportion | in | d *(double)* | Fraction of the work area's extent across the strip, 0.05 to 1.0. |
<tt>setWindowHeightPixels()</tt>
Set the focused window's height to a fixed pixel value. Same gates, equal-value no-target answer and OSD feedback as setWindowHeightProportion; the value is refused silently outside the fixed range the settings UI enforces (100 to 10000).
| Arg | Direction | Type | Description |
screenId | in | s *(string)* | Screen whose focused window to resize. An empty string is ignored. |
px | in | i *(int32)* | Height in pixels, 100 to 10000. |
<tt>clearWindowedFullscreen()</tt>
Drops a window's windowed-fullscreen flag. Called by the KWin effect when a windowed-fullscreen client leaves fullscreen on its own, so the strip's flag follows reality. Silent no-op for an unknown window or one whose flag is not set.
| Arg | Direction | Type | Description |
windowId | in | s *(string)* | Window whose flag to clear. An empty string is ignored. |
<tt>reapplyWindowGeometry()</tt>
Re-emits a window's true strip rect. Called by the KWin effect when the compositor moved the window behind the engine's back (KWin restores a fullscreen-exiting window to its pre-fullscreen rect one client round-trip after the strip already placed it, and the engine's emit-on-change gate would otherwise stay silent forever). The engine evicts the window's emit-gate memory and relayouts its screen so the next batch re-carries the rect. Silent no-op for an unknown window.
| Arg | Direction | Type | Description |
windowId | in | s *(string)* | Window to re-emit. An empty string is ignored. |
<tt>visibleStripJson()</tt>
The strip as it currently looks on a screen: a JSON array of visible tile rects plus zoneNumber, the tile's 1-based visible slot in strip order. Each rect is {x, y, width, height}, 0.0 to 1.0 per axis, normalized against the screen's FULL geometry. The tiles themselves are clipped to the work area after the outer gaps are subtracted, so the fractions show the panel gap and a consumer maps back to pixels by scaling against the screen rectangle. Same basis as the daemon's own OSD strip card. Only what is on screen is listed. Hidden tabs of a tabbed column, minimized tiles, and tiles whose intersection with the work area is empty (a stack whose minimum sizes overflow the work area resolves its tail past the work area's far edge across the strip) are all left out and carry no number. A partially visible column is clipped to the work area rather than dropped, with no minimum-visibility threshold: an arbitrarily thin sliver still gets its own number, and a barely-visible column sitting parked below its peek floor keeps its number too, which is what lets a digit press scroll it back into view. zoneNumber is the scroll zone number, and the Snap-to-Zone shortcuts cover 1 to 9, so a strip showing more than nine tiles numbers them all but only the first nine have a digit that reaches them. The settings app renders the array where the other modes show a layout thumbnail. A tile whose column draws a tab indicator carries four more keys describing it, and a tile whose column draws none carries no tab key at all: tabCount, how many tabs the indicator shows; activeTab, this tile's own 0-based tab within them; tabPosition, the edge the indicator runs along (0 left, 1 right, 2 top, 3 bottom); and tabLength, how much of that edge it covers as a 0.0 to 1.0 proportion, centered on it. Only the shown tab of a tabbed column is listed at all, so those keys are the only thing telling a reader that the tile stands for a stack rather than a single window. No thickness rides along: the configured width is a few pixels, which is sub-pixel in any thumbnail of a whole screen, so a renderer floors it at whatever it can draw. Empty array when the screen has no strip or is not scrolling.
| Arg | Direction | Type | Description |
screenId | in | s *(string)* | Screen whose strip to describe. An empty string yields an empty array. |
stripJson | out | s *(string)* | JSON array string. |
<tt>presetVocabularyJson()</tt>
The screen's effective preset vocabulary as a JSON object {"columnWidths": [...], "windowHeights": [...]} of 0.0 to 1.0 fractions. Each list resolves independently: a list the context's resolved scrolling template supplies comes from that template's own preset list, and a list it does not supply falls back to the configured preset list, so a template that defines widths but no heights yields template widths beside the configured heights. This is the vocabulary the cycle-preset-width and cycle-preset-height shortcuts walk on that screen. Same ownership gate as focusColumn (reads are not context-gated): an empty object when the screen is not scrolling.
| Arg | Direction | Type | Description |
screenId | in | s *(string)* | Screen whose vocabulary to describe. An empty string yields an empty object. |
vocabularyJson | out | s *(string)* | JSON object string. |
<tt>blueprintProgressJson()</tt>
How far the screen has worked through its scrolling template's seed blueprint, as a JSON object {"total": n, "used": n}. total is how many starting columns the context's resolved template declares, and used is how many of them the strip has already taken. An entry is spent once a column has taken it, so closing a column does not return its entry to the pool, and used == total means further columns open at the template's ordinary width and display instead. The count restarts when the strip empties or the screen is given a different template. Reports the current desktop and activity context, matching visibleStripJson. Same ownership gate as focusColumn (reads are not context-gated): an empty object when the screen is not scrolling.
| Arg | Direction | Type | Description |
screenId | in | s *(string)* | Screen whose progress to describe. An empty string yields an empty object. |
progressJson | out | s *(string)* | JSON object string. |
Signals
<tt>scrollingScreensChanged</tt>
Emitted when the set of screens using the scrolling engine changes.
| Arg | Direction | Type | Description |
screenIds | out | as *(array<string>)* | List of screen IDs currently using the scrolling engine. |
<tt>scrollEffectBehaviourChanged</tt>
Emitted when the resolved per-screen behaviour the compositor owns changes. The payload is the WHOLE map, never a delta, so a receiver that missed an earlier emission never has to merge against a stale copy.
| Arg | Direction | Type | Description |
behaviour | out | a{sv} *(dict<string,variant>)* | Same shape as the scrollEffectBehaviour property. |
<tt>scrollFocusScrollBlockedWindowsChanged</tt>
Emitted when the set of windows the focus-follows-mouse scroll cap refuses changes. The payload is the WHOLE list, so a receiver never merges a delta against a copy it may have missed an update to. Separate from scrollEffectBehaviourChanged because it fires on every relayout that moves the answer, while that one fires when settings or rules change.
| Arg | Direction | Type | Description |
windowIds | out | as *(array<string>)* | Same shape as the scrollFocusScrollBlockedWindows property. |
<tt>stripChanged</tt>
A screen's strip changed shape. This is a wake-up for anyone rendering the strip, not a payload: re-read visibleStripJson to find out what it looks like now. It relays the engine's own placement-change signal, so it fires on a placement or focus change rather than on every relayout, but it is NOT gated against the strip payload — payload-gating would cost a full relayout on every placement change, paid whether or not anything is listening, to suppress wake-ups whose only cost to a receiver is one read it discards as identical. Treat it as 're-read soon', never as 'the strip differs': a placement change that moves a floating window, or one on a context the screen is not currently showing, fires it without changing a single visible tile. The gate also runs one way only: a settings, rule or template push re-resolves the strip through applyLayout and emits this only if the view anchor moved, so a relayout that changes column widths while the anchor stays put changes the strip without waking anyone. Keep a periodic re-read as the backstop; this signal only makes the common cases prompt. Coalesce it, since a drag or a burst of window opens emits per step. Never emitted for a screen the scrolling engine does not currently own, since visibleStripJson answers empty for those anyway.
| Arg | Direction | Type | Description |
screenId | out | s *(string)* | Screen whose strip to re-read. |
Properties
| Property | Access | Type | Description |
scrollingScreens | read | as *(array<string>)* | List of screen IDs currently using the scrolling engine, sorted, matching the ordering of the scrollingScreensChanged payload so a consumer can compare the two for the same set. The KWin effect uses it as the discriminator that stamps Mode 'scrolling' rather than 'tiling' in window rule queries, but it does not answer from this raw set: it intersects it at read time with the engine-managed union published on org.plasmazones.Tiling, because the two sets arrive as independent signals with no ordering guarantee and this one can transiently name a screen the union has already dropped. Changes are announced on the scrollingScreensChanged signal below, not through org.freedesktop.DBus.Properties.PropertiesChanged, which this property does not emit. |
scrollEffectBehaviour | read | a{sv} *(dict<string,variant>)* | The scrolling facts the COMPOSITOR owns, already resolved as three screen-id lists, {'focusFollowsMouse': as, 'cropStraddlers': as, 'verticalAxis': as}. The first two are per-context rule slots (SetScrollFocusFollowsMouse / SetScrollCropStraddlers) whose consumer lives in the KWin effect rather than the scroll engine, so unlike their four sibling behaviour slots they cannot ride the engine's per-screen override map. verticalAxis is the resolved face of the SetScrollStripAxis rule slot and the per-screen/global StripAxis settings: the daemon reads the verdict back from the scroll engine's merged per-screen map (rule over per-screen setting over global, with Auto resolved by the engine from the screen's work area), so this list can never disagree with the axis the strip is actually laid out on. It is membership rather than a per-screen value: a screen IN that list runs its strip VERTICALLY, and absence means horizontal, so an absent key and an empty list mean the same safe thing and a compositor that predates the axis reads the whole desktop as horizontal. The daemon resolves rule-then-config per scrolling screen and publishes the RESOLVED membership, which keeps the effect free of any config knowledge: it answers each question with a set lookup and holds no fallback of its own. A screen absent from a list has that behaviour off (or, for verticalAxis, a horizontal strip); a screen that is not scrolling appears in none of the three. The focus-follows-mouse scroll cap's blocked-window list is NOT on this map: it rides scrollFocusScrollBlockedWindows below, because it changes on every relayout while these three change only when settings or rules do. All three lists are sorted, matching the scrollEffectBehaviourChanged payload. One map rather than three list properties so the compositor pays a single bring-up query and a single subscription for what is really one push. Changes are announced on scrollEffectBehaviourChanged below, not through org.freedesktop.DBus.Properties.PropertiesChanged, which this property does not emit. |
scrollFocusScrollBlockedWindows | read | as *(array<string>)* | The windows focus-follows-mouse must REFUSE to focus, as WINDOW ids rather than screen ids: activating one would scroll the strip further than the per-context cap allows (the SetScrollFocusFollowsMouseMaxScroll rule slot over the Scrolling.Behavior FocusFollowsMouseMaxScroll setting). The daemon resolves the cap and asks the scroll engine which windows exceed it, because the answer depends on the strip's layout and where its view sits and the compositor cannot recompute that per pointer event; it is republished on every strip layout change of a capped screen. Its own property rather than a fourth key on scrollEffectBehaviour for exactly that reason: sharing one property made a strip that merely scrolled re-publish the three screen lists too, and the reader re-parse them to discover they had not moved. An absent property, an empty list and an absent window all mean the same thing, that nothing is refused, which is what the default cap of 100 percent resolves to, so a reader that misses this update fails OPEN with focus following the pointer as it did before the setting existed. The list is sorted, matching the scrollFocusScrollBlockedWindowsChanged payload. Changes are announced on that signal, not through org.freedesktop.DBus.Properties.PropertiesChanged, which this property does not emit. |