The single source of truth for window restore state in the unified model. More...
#include <phosphor-engine/include/PhosphorEngine/WindowPlacementStore.h>
Public Member Functions | |
| WindowPlacementStore ()=default | |
| bool | record (WindowPlacement placement) |
| Record / MERGE this window's placement. | |
| std::optional< WindowPlacement > | take (const QString &windowId, const QString &appId, const std::function< bool(const WindowPlacement &)> &accept={}, const std::function< bool(const WindowPlacement &)> &preferred={}) |
Restore lookup: the first record whose accept predicate passes, trying the same-instance match before the appId FIFO (oldest first). | |
| std::optional< WindowPlacement > | takeForReopen (const QString &engineId, const QString &windowId, const QString &appId, const QString &screenId) |
| Reopen resolve: the shared consumption pattern the TILING engines' open-time restores use (SnapEngine::resolveWindowRestore keeps its own take + re-bind: its snapped records restore cross-screen and its accept depends on a mode-defer bypass, so rule 1 below does not fit it — and note the snap path therefore also keeps take()'s oldest-first order WITHOUT the live-instance exclusion below; that asymmetry is a documented property of the snap flow, not an oversight) — take() wrapped in the accept predicate both tiling engines share and the two rules that make a close/reopen (fresh uuid, appId-FIFO match) behave correctly. | |
| std::optional< WindowPlacement > | peek (const QString &windowId, const QString &appId, const std::function< bool(const WindowPlacement &)> &accept={}) const |
Non-consuming lookup (unlike take): the record for the same live instance, else the NEWEST record in the appId bucket whose accept passes. | |
| std::optional< WindowPlacement > | peekExact (const QString &windowId) const |
| Same-instance peek: branch 1 of peek() only, never the appId-FIFO fallback. | |
| std::optional< WindowPlacement > | peekForReclaim (const QString &windowId, const QString &appId, const std::function< bool(const WindowPlacement &)> &accept={}) const |
| Non-consuming lookup for the cross-screen reclaim (IPlacementEngine::claimCrossScreenReopen). | |
| bool | contains (const QString &windowId, const QString &appId=QString()) const |
True if a record exists for the same live instance, or (if appId non-empty) any record in that appId bucket. | |
| void | setLiveInstanceProbe (std::function< bool(const QString &windowId)> probe) |
| Inject the live-window probe takeForReopen's appId fallback uses to skip records bound to a still-open window (see its doc). | |
| bool | collapsePureFloatSiblings (const QString &appId, const QString &keepWindowId) |
Collapse stale pure-float duplicates for an app, keeping keepWindowId. | |
| bool | clear (const QString &windowId) |
| Drop any record for the same live instance (and prune the empty bucket). | |
| bool | clearFreeGeometry (const QString &windowId) |
| Clear ONLY the shared free/float geometry for the same live instance, leaving the engine slots and context intact. | |
| bool | clearFreeGeometry (const QString &windowId, const QString &screenId) |
| bool | releaseEngineSlot (const QString &windowId, const QString &engineId) |
| DOWNGRADE one engine's slot to WindowPlacement::stateReleased() on every record for the same live instance, leaving the other engines' slots, the context and the shared geometry intact. | |
| int | transform (const std::function< bool(WindowPlacement &)> &fn) |
Apply an in-place mutation to every record; fn returns true when it changed the record. | |
| int | removeIf (const std::function< bool(const WindowPlacement &)> &pred) |
Remove every record matching pred. Returns the count removed. | |
| QList< WindowPlacement > | records () const |
| All records, in no particular order. | |
| QJsonObject | serialize (const std::function< bool(const WindowPlacement &)> &keep={}) const |
| JSON shape: { appId: [ record, ... ] }. | |
| void | deserialize (const QJsonObject &obj) |
| int | size () const |
Static Public Attributes | |
| static constexpr int | MaxPerApp = 16 |
| Per-app record cap (public so tests can pin the eviction contract). | |
The single source of truth for window restore state in the unified model.
Holds AT MOST ONE WindowPlacement record PER WINDOW INSTANCE (not per engine), captured live by the engines. Records are keyed two ways so they survive both a daemon restart (the instance component is stable while the window stays open) and a close→reopen (uuid changes, so the appId FIFO carries it).
The core invariant — record() MERGES into the single record for the same instance — gives per-mode state independence with a shared free/float geometry: each engine updates only its OWN slot (in engines, keyed by engineId()) plus any free-geometry change, so a window may be snapped in the snap engine AND floating in the autotile engine at once, each engine remembering the window's state in its own mode, while the un-managed position lives once in freeGeometryByScreen (shared across modes, keyed per screen).
|
default |
| bool PhosphorEngine::WindowPlacementStore::clear | ( | const QString & | windowId | ) |
Drop any record for the same live instance (and prune the empty bucket).
Returns true if a record was actually removed.
| bool PhosphorEngine::WindowPlacementStore::clearFreeGeometry | ( | const QString & | windowId | ) |
Clear ONLY the shared free/float geometry for the same live instance, leaving the engine slots and context intact.
Returns true if anything was cleared. The all-screens form is for wholesale invalidation (virtual-screen remap); the screen-scoped overload is for consume-once paths (drag-out, drop-snap), which must not destroy the float-back remembered for other monitors.
| bool PhosphorEngine::WindowPlacementStore::clearFreeGeometry | ( | const QString & | windowId, |
| const QString & | screenId | ||
| ) |
| bool PhosphorEngine::WindowPlacementStore::collapsePureFloatSiblings | ( | const QString & | appId, |
| const QString & | keepWindowId | ||
| ) |
Collapse stale pure-float duplicates for an app, keeping keepWindowId.
A "pure-float" record carries float-back geometry but NO managed (snapped/tiled) engine slot. When keepWindowId names a pure-float record, every OTHER pure-float record in the same appId bucket that remembers a float position on a screen keepWindowId also covers is removed. Records carrying a snapped/tiled slot are never touched (managed placements whose multi-instance distribution must survive). No-op when the kept record is absent or itself managed.
Called ONLY from close-capture paths: a window closing floating is the freshest authority for its app's float-back on that screen, so duplicate siblings (left by rapid open/close or overlapping short-lived instances) are stale. Records bound to a still-OPEN window (per the live-instance probe) are never pruned, and a pruned sibling's engine slots and other-screen geometry are absorbed fill-gaps-only. Without the collapse, a consuming reopen — take()'s oldest-first for snap, or a probe-excluded tail for the tiling engines — can rotate a reopening window between the duplicates: it "opens in a different spot each time."
Returns true if at least one sibling was removed, so the caller can mark its persistence dirty: the preceding record() may have been a content-identical no-op, in which case this prune is the only mutation and would otherwise not reach disk until an incidental save.
| bool PhosphorEngine::WindowPlacementStore::contains | ( | const QString & | windowId, |
| const QString & | appId = QString() |
||
| ) | const |
True if a record exists for the same live instance, or (if appId non-empty) any record in that appId bucket.
| void PhosphorEngine::WindowPlacementStore::deserialize | ( | const QJsonObject & | obj | ) |
| std::optional< WindowPlacement > PhosphorEngine::WindowPlacementStore::peek | ( | const QString & | windowId, |
| const QString & | appId, | ||
| const std::function< bool(const WindowPlacement &)> & | accept = {} |
||
| ) | const |
Non-consuming lookup (unlike take): the record for the same live instance, else the NEWEST record in the appId bucket whose accept passes.
Leaves the store unchanged — for live reads such as the float-back geometry lookup, where the record must stay put for the eventual restore/capture.
|
inline |
Same-instance peek: branch 1 of peek() only, never the appId-FIFO fallback.
The instance component remains stable if a live window's appId prefix changes, while still distinguishing same-app siblings. The appId fallback exists for close/reopen paths where the instance changes.
| std::optional< WindowPlacement > PhosphorEngine::WindowPlacementStore::peekForReclaim | ( | const QString & | windowId, |
| const QString & | appId, | ||
| const std::function< bool(const WindowPlacement &)> & | accept = {} |
||
| ) | const |
Non-consuming lookup for the cross-screen reclaim (IPlacementEngine::claimCrossScreenReopen).
Differs from peek() in the two ways that make a reclaim verdict sound:
appId bucket (the window's own record included — a same-instance match wins outright, live or not, since the window's own record IS its history). peek()'s same-instance branch walks every bucket in the store; on the per-open reclaim path that cost is paid per engine per open. The narrowing is safe because record() RE-BUCKETS on an appId change, so a record follows the window's current appId — if that ever stops holding, this scan starts missing records peek() would find. It fails SAFE either way: a miss means no reclaim, never a wrong one. Returns nullopt when appId is empty — a bare id has no bucket, so a reclaim verdict is impossible. | bool PhosphorEngine::WindowPlacementStore::record | ( | WindowPlacement | placement | ) |
Record / MERGE this window's placement.
The incoming record supplies only the calling engine's slot (in engines) and any free-geometry update; if a record for the same live instance already exists (in any appId bucket) the incoming engine slot(s) and free-geometry screen(s) are merged in, leaving the other engine's slot and other screens' free geometry intact. Otherwise the record is appended to its appId's FIFO. No-op on an invalid record. Returns true if the store actually changed — false when the merge produced a content-identical record, so callers can skip marking state dirty and avoid a self-perpetuating save loop. Stamps a fresh monotonic sequence whenever it changes anything (the content-identical short-circuit leaves the existing record, sequence included, untouched).
| QList< WindowPlacement > PhosphorEngine::WindowPlacementStore::records | ( | ) | const |
All records, in no particular order.
For read-only sweeps (e.g. building the effect's instant-restore cache from the snapped records).
| bool PhosphorEngine::WindowPlacementStore::releaseEngineSlot | ( | const QString & | windowId, |
| const QString & | engineId | ||
| ) |
DOWNGRADE one engine's slot to WindowPlacement::stateReleased() on every record for the same live instance, leaving the other engines' slots, the context and the shared geometry intact.
Returns true if any record changed.
This is the release-path counterpart of record()'s merge-never-clear: slots accumulate as cross-mode memory, and for RESTORE that is right — but a slot on a window an engine has knowingly GIVEN UP (cross-mode handoff) is not memory, it is a stale ownership claim. Since the cross-screen reclaim (pendingCrossScreenManagedRestore) treats a managed slot plus the record-level screenId as evidence of a home to pull the window back to, a stale slot that outlives its engine's ownership can yank a window out from under its CURRENT engine whenever the new owner's capture misses (the record()-overwrite of screenId is not guaranteed on every path).
DOWNGRADE, not remove, and the difference is load-bearing on both sides — see stateReleased()'s contract. Removing the slot would also risk emptying the engines map, which record()'s merge reads as "no managed context to adopt", freezing the record's screenId against every later geometry-only write.
Sweeps ALL records matching the instance rather than stopping at the first: appId drift (the Electron/CEF case this codebase canonicalizes for) can leave records for one instance in two buckets, and the one carrying the stale slot is not necessarily the one QHash order reaches first.
Callers: the tiling engines' handoffRelease, through WindowTrackingService::releaseEngineSlot (which marks the store dirty). NOT called on ordinary close — a window that CLOSED tiled keeps its slot; that persistence is exactly what login restore reads.
| int PhosphorEngine::WindowPlacementStore::removeIf | ( | const std::function< bool(const WindowPlacement &)> & | pred | ) |
Remove every record matching pred. Returns the count removed.
| QJsonObject PhosphorEngine::WindowPlacementStore::serialize | ( | const std::function< bool(const WindowPlacement &)> & | keep = {} | ) | const |
JSON shape: { appId: [ record, ... ] }.
keep filters out entries that should not persist (e.g. disabled-context). Empty buckets are dropped.
|
inline |
Inject the live-window probe takeForReopen's appId fallback uses to skip records bound to a still-open window (see its doc).
Answers per full windowId; evaluated at consume time. Unwired (tests) means no exclusion.
| int PhosphorEngine::WindowPlacementStore::size | ( | ) | const |
| std::optional< WindowPlacement > PhosphorEngine::WindowPlacementStore::take | ( | const QString & | windowId, |
| const QString & | appId, | ||
| const std::function< bool(const WindowPlacement &)> & | accept = {}, |
||
| const std::function< bool(const WindowPlacement &)> & | preferred = {} |
||
| ) |
Restore lookup: the first record whose accept predicate passes, trying the same-instance match before the appId FIFO (oldest first).
The matched record is REMOVED (consumed) and returned. accept lets the caller reject cross-screen / disabled-context / wrong-kind candidates.
preferred (optional) ranks the appId-FIFO branch ONLY: when supplied, the oldest entry satisfying BOTH accept and preferred is consumed first, and only if none qualifies does the oldest merely-accepted entry win. The same-instance match is unaffected — a window's own record is always used in whatever state it holds. Lets a caller restore the most meaningful record (e.g. a snapped placement) ahead of a contentless free/floating sibling that is merely older in the FIFO.
| std::optional< WindowPlacement > PhosphorEngine::WindowPlacementStore::takeForReopen | ( | const QString & | engineId, |
| const QString & | windowId, | ||
| const QString & | appId, | ||
| const QString & | screenId | ||
| ) |
Reopen resolve: the shared consumption pattern the TILING engines' open-time restores use (SnapEngine::resolveWindowRestore keeps its own take + re-bind: its snapped records restore cross-screen and its accept depends on a mode-defer bypass, so rule 1 below does not fit it — and note the snap path therefore also keeps take()'s oldest-first order WITHOUT the live-instance exclusion below; that asymmetry is a documented property of the snap flow, not an oversight) — take() wrapped in the accept predicate both tiling engines share and the two rules that make a close/reopen (fresh uuid, appId-FIFO match) behave correctly.
The accept predicate, hoisted here so the two engines cannot drift: a record whose engineId slot is FLOATING restores when its screen matches screenId (or is empty), and FIFO consumption by a DIFFERENT instance additionally requires a valid anyFreeGeometry (a geometry-less floating record is meaningful only same-instance — consumed by a sibling it floats a fresh window at its spawn rect for no reason while burning a FIFO slot). FLOATING slots only, deliberately: a TILED record is never consumed and restores no position — its role is the exact-final verdict below (the window closed tiled, so the reopen must not float it).
windowId and re-recorded, so the other engines' slots + per-screen free/float geometry survive the reopen.The appId fallback consumes the NEWEST accepted record — matching peek()'s "the most recent placement is current truth" — and never one whose window instance is still LIVE (per the live-instance probe): the last close is the state the user expects back, and consuming oldest first handed a reopen whichever stale record had sat unconsumed longest (the octopi graveyard: eleven leftover tiled records shadowing the fresh floating one, and colliding months-old column ranks on a compositor-restart restore). The live exclusion is what makes newest-first safe for multi-instance apps: a record just re-bound to an OPEN sibling is the newest in the bucket, and without the probe the next reopen would steal it, leaving the sibling recordless.
Returns the consumed record (already re-recorded), or nullopt when no record passed.
| int PhosphorEngine::WindowPlacementStore::transform | ( | const std::function< bool(WindowPlacement &)> & | fn | ) |
Apply an in-place mutation to every record; fn returns true when it changed the record.
Returns the number changed. For bulk rewrites that keep the appId bucketing (e.g. virtual-screen id remap of freeGeometryByScreen keys). Does NOT move records between buckets — only mutate fields other than appId.
|
staticconstexpr |
Per-app record cap (public so tests can pin the eviction contract).