Phosphor
Qt6 / Wayland library suite for window-management tools
 
Loading...
Searching...
No Matches
SnapState.h
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 fuddlesworth
2// SPDX-License-Identifier: LGPL-2.1-or-later
3
4#pragma once
5
6#include <phosphorsnapengine_export.h>
9
10#include <QHash>
11#include <QJsonObject>
12#include <QObject>
13#include <QRect>
14#include <QSet>
15#include <QString>
16#include <QStringList>
17
18#include <optional>
19
20namespace PhosphorSnapEngine {
21
32class PHOSPHORSNAPENGINE_EXPORT SnapState : public QObject, public PhosphorEngine::IPlacementState
33{
34 Q_OBJECT
35
36public:
37 explicit SnapState(const QString& screenId, QObject* parent = nullptr);
38 ~SnapState() override;
39
40 SnapState(const SnapState&) = delete;
41 SnapState& operator=(const SnapState&) = delete;
42
52 {
53 m_windowRegistry = registry;
54 }
55
56 // ═══════════════════════════════════════════════════════════════════════════
57 // IPlacementState
58 // ═══════════════════════════════════════════════════════════════════════════
59
60 QString screenId() const override;
61 int windowCount() const override;
62 QStringList managedWindows() const override;
63 bool containsWindow(const QString& windowId) const override;
64 bool isFloating(const QString& windowId) const override;
65 QStringList floatingWindows() const override;
66 QString placementIdForWindow(const QString& windowId) const override;
67 QJsonObject toJson() const override;
68
69 // ═══════════════════════════════════════════════════════════════════════════
70 // Zone Assignment CRUD
71 // ═══════════════════════════════════════════════════════════════════════════
72
73 void assignWindowToZone(const QString& windowId, const QString& zoneId, const QString& screenId,
74 int virtualDesktop);
75 void assignWindowToZones(const QString& windowId, const QStringList& zoneIds, const QString& screenId,
76 int virtualDesktop);
78 {
79 bool wasAssigned = false;
80 bool lastUsedZoneCleared = false;
81 };
82 UnassignResult unassignWindow(const QString& windowId);
83
84 QString zoneForWindow(const QString& windowId) const;
85 QStringList zonesForWindow(const QString& windowId) const;
86 QStringList windowsInZone(const QString& zoneId) const;
87 QStringList snappedWindows() const;
88 bool isWindowSnapped(const QString& windowId) const;
89
90 QString screenForWindow(const QString& windowId) const;
91 int desktopForWindow(const QString& windowId) const;
92
98 bool reassignDesktop(const QString& windowId, int virtualDesktop);
99
106 QStringList windowsOnScreenAndDesktop(const QString& screenId, int virtualDesktop) const;
107
108 const QHash<QString, QString>& screenAssignments() const
109 {
110 return m_windowScreenAssignments;
111 }
112 const QHash<QString, int>& desktopAssignments() const
113 {
114 return m_windowDesktopAssignments;
115 }
116 void setScreenAssignments(const QHash<QString, QString>& s)
117 {
118 if (m_windowScreenAssignments == s) {
119 return;
120 }
121 m_windowScreenAssignments = s;
122 Q_EMIT stateChanged();
123 }
124 void setDesktopAssignments(const QHash<QString, int>& d)
125 {
126 if (m_windowDesktopAssignments == d) {
127 return;
128 }
129 m_windowDesktopAssignments = d;
130 Q_EMIT stateChanged();
131 }
132
133 // ═══════════════════════════════════════════════════════════════════════════
134 // Floating State
135 // ═══════════════════════════════════════════════════════════════════════════
136
137 void setFloating(const QString& windowId, bool floating);
138
145 void setFloatingOnScreen(const QString& windowId, const QString& screenId, int virtualDesktop);
146
148 UnassignResult unsnapForFloat(const QString& windowId);
149 QString preFloatZone(const QString& windowId) const;
150 QStringList preFloatZones(const QString& windowId) const;
151 QString preFloatScreen(const QString& windowId) const;
152 void clearPreFloatZone(const QString& windowId);
153 void addPreFloatZone(const QString& windowId, const QStringList& zoneIds);
154 void addPreFloatScreen(const QString& windowId, const QString& screenId);
155
156 const QHash<QString, QStringList>& preFloatZoneAssignments() const
157 {
158 return m_preFloatZoneAssignments;
159 }
160 const QHash<QString, QString>& preFloatScreenAssignments() const
161 {
162 return m_preFloatScreenAssignments;
163 }
164 void setPreFloatScreenAssignments(const QHash<QString, QString>& a)
165 {
166 if (m_preFloatScreenAssignments == a) {
167 return;
168 }
169 m_preFloatScreenAssignments = a;
170 Q_EMIT stateChanged();
171 }
172
173 // ═══════════════════════════════════════════════════════════════════════════
174 // Window Lifecycle
175 // ═══════════════════════════════════════════════════════════════════════════
176
177 void windowClosed(const QString& windowId);
178 bool isEmpty() const;
179 void clear();
180
181 // ═══════════════════════════════════════════════════════════════════════════
182 // Rotation
183 // ═══════════════════════════════════════════════════════════════════════════
184
187 QStringList rotateAssignments(bool clockwise);
188
189 // ═══════════════════════════════════════════════════════════════════════════
190 // Last-Used Zone Tracking
191 // ═══════════════════════════════════════════════════════════════════════════
192
194 void updateLastUsedZone(const QString& zoneId, const QString& screenId, const QString& windowClass,
195 int virtualDesktop);
196
198 void restoreLastUsedZone(const QString& zoneId, const QString& screenId, const QString& zoneClass, int desktop);
199
200 QString lastUsedZoneId() const
201 {
202 return m_lastUsedZoneId;
203 }
204 QString lastUsedScreenId() const
205 {
206 return m_lastUsedScreenId;
207 }
208 QString lastUsedZoneClass() const
209 {
210 return m_lastUsedZoneClass;
211 }
212 int lastUsedDesktop() const
213 {
214 return m_lastUsedDesktop;
215 }
216 void retagLastUsedZoneClass(const QString& newClass)
217 {
218 if (m_lastUsedZoneClass == newClass) {
219 return;
220 }
221 m_lastUsedZoneClass = newClass;
222 Q_EMIT stateChanged();
223 }
224
225 // ═══════════════════════════════════════════════════════════════════════════
226 // Auto-Snap Bookkeeping
227 // ═══════════════════════════════════════════════════════════════════════════
228
229 void recordSnapIntent(const QString& windowClass, bool wasUserInitiated);
230 const QSet<QString>& userSnappedClasses() const
231 {
232 return m_userSnappedClasses;
233 }
234 void setUserSnappedClasses(const QSet<QString>& classes)
235 {
236 if (m_userSnappedClasses == classes) {
237 return;
238 }
239 m_userSnappedClasses = classes;
240 Q_EMIT stateChanged();
241 }
242
243 void markAsAutoSnapped(const QString& windowId);
244 bool isAutoSnapped(const QString& windowId) const;
245 bool clearAutoSnapped(const QString& windowId);
246
247 // ═══════════════════════════════════════════════════════════════════════════
248 // Occupied Zone Queries
249 // ═══════════════════════════════════════════════════════════════════════════
250
254 QSet<QString> buildOccupiedZoneSet(const QString& screenFilter = {}, int desktopFilter = 0) const;
255
257 int pruneStaleAssignments(const QSet<QString>& aliveWindowIds);
258
259 // ═══════════════════════════════════════════════════════════════════════════
260 // Deserialization
261 // ═══════════════════════════════════════════════════════════════════════════
262
263 static SnapState* fromJson(const QJsonObject& json, QObject* parent = nullptr);
264
265 // ═══════════════════════════════════════════════════════════════════════════
266 // State Access (for persistence layer)
267 // ═══════════════════════════════════════════════════════════════════════════
268
269 const QHash<QString, QStringList>& zoneAssignments() const
270 {
271 return m_windowZoneAssignments;
272 }
273
274 void setZoneAssignments(const QHash<QString, QStringList>& zones)
275 {
276 if (m_windowZoneAssignments == zones) {
277 return;
278 }
279 m_windowZoneAssignments = zones;
280 Q_EMIT stateChanged();
281 }
282 void setFloatingWindows(const QSet<QString>& windows)
283 {
284 if (m_floatingWindows == windows) {
285 return;
286 }
287 m_floatingWindows = windows;
288 Q_EMIT stateChanged();
289 }
290 void setPreFloatZoneAssignments(const QHash<QString, QStringList>& a)
291 {
292 if (m_preFloatZoneAssignments == a) {
293 return;
294 }
295 m_preFloatZoneAssignments = a;
296 Q_EMIT stateChanged();
297 }
298
299Q_SIGNALS:
300 void windowAssigned(const QString& windowId, const QString& zoneId);
301 void windowUnassigned(const QString& windowId);
302 void floatingChanged(const QString& windowId, bool floating);
304
305private:
313 QString canonicalizeForLookup(const QString& rawWindowId) const;
314
315 bool removeWindowData(const QString& windowId);
316
324 UnassignResult clearZoneAssignment(const QString& windowId, bool preserveScreenAndDesktop);
325
326 QSet<QString> allManagedWindowIds() const
327 {
328 QSet<QString> all;
329 all.reserve(m_windowZoneAssignments.size() + m_floatingWindows.size() + m_autoSnappedWindows.size());
330 for (auto it = m_windowZoneAssignments.constBegin(); it != m_windowZoneAssignments.constEnd(); ++it) {
331 all.insert(it.key());
332 }
333 all.unite(m_floatingWindows);
334 all.unite(m_autoSnappedWindows);
335 return all;
336 }
337
338 QString m_screenId;
339
341 PhosphorEngine::IWindowRegistry* m_windowRegistry = nullptr;
342
343 QHash<QString, QStringList> m_windowZoneAssignments;
344 QHash<QString, QString> m_windowScreenAssignments;
345 QHash<QString, int> m_windowDesktopAssignments;
346 QSet<QString> m_floatingWindows;
347 QHash<QString, QStringList> m_preFloatZoneAssignments;
348 QHash<QString, QString> m_preFloatScreenAssignments;
349
350 QString m_lastUsedZoneId;
351 QString m_lastUsedScreenId;
352 QString m_lastUsedZoneClass;
353 int m_lastUsedDesktop = 0;
354
355 QSet<QString> m_userSnappedClasses;
356 QSet<QString> m_autoSnappedWindows;
357};
358
359} // namespace PhosphorSnapEngine
Per-screen placement state contract.
Definition IPlacementState.h:26
Definition IWindowRegistry.h:12
Per-screen snap placement state.
Definition SnapState.h:33
void setFloatingOnScreen(const QString &windowId, const QString &screenId, int virtualDesktop)
Mark a window floating AND record its screen+desktop without a zone.
void setUserSnappedClasses(const QSet< QString > &classes)
Definition SnapState.h:234
SnapState(const QString &screenId, QObject *parent=nullptr)
void windowClosed(const QString &windowId)
QString preFloatScreen(const QString &windowId) const
bool isFloating(const QString &windowId) const override
Whether the window is floating (excluded from placement).
void markAsAutoSnapped(const QString &windowId)
void assignWindowToZones(const QString &windowId, const QStringList &zoneIds, const QString &screenId, int virtualDesktop)
QStringList zonesForWindow(const QString &windowId) const
QString lastUsedZoneClass() const
Definition SnapState.h:208
SnapState(const SnapState &)=delete
UnassignResult unassignWindow(const QString &windowId)
void setFloatingWindows(const QSet< QString > &windows)
Definition SnapState.h:282
QStringList managedWindows() const override
All windows managed by this state (tiled + floating).
void setScreenAssignments(const QHash< QString, QString > &s)
Definition SnapState.h:116
QString screenId() const override
Screen this state object manages.
QStringList rotateAssignments(bool clockwise)
Rotate zone assignments: each window moves to the next/previous zone in zone-number order.
bool containsWindow(const QString &windowId) const override
Whether the window is in this state's managed set.
QString preFloatZone(const QString &windowId) const
void restoreLastUsedZone(const QString &zoneId, const QString &screenId, const QString &zoneClass, int desktop)
Restore last-used zone fields from persistence without emitting stateChanged.
const QSet< QString > & userSnappedClasses() const
Definition SnapState.h:230
UnassignResult unsnapForFloat(const QString &windowId)
Save zone assignment before floating for later restore.
void setWindowRegistry(PhosphorEngine::IWindowRegistry *registry)
Attach the daemon's shared window registry so every windowId-keyed accessor can canonicalize the inco...
Definition SnapState.h:51
void clearPreFloatZone(const QString &windowId)
QStringList windowsOnScreenAndDesktop(const QString &screenId, int virtualDesktop) const
Windows with a recorded desktop assignment (snapped, or floated-on-screen via setFloatingOnScreen / u...
QString lastUsedScreenId() const
Definition SnapState.h:204
QSet< QString > buildOccupiedZoneSet(const QString &screenFilter={}, int desktopFilter=0) const
Build the set of zone IDs currently occupied by snapped windows.
void addPreFloatZone(const QString &windowId, const QStringList &zoneIds)
const QHash< QString, int > & desktopAssignments() const
Definition SnapState.h:112
int pruneStaleAssignments(const QSet< QString > &aliveWindowIds)
Remove zone/screen/desktop assignments for windows not in the alive set.
void retagLastUsedZoneClass(const QString &newClass)
Definition SnapState.h:216
const QHash< QString, QStringList > & preFloatZoneAssignments() const
Definition SnapState.h:156
QStringList snappedWindows() const
QJsonObject toJson() const override
Serialize to JSON for session persistence.
void setPreFloatScreenAssignments(const QHash< QString, QString > &a)
Definition SnapState.h:164
bool isWindowSnapped(const QString &windowId) const
QStringList windowsInZone(const QString &zoneId) const
QStringList preFloatZones(const QString &windowId) const
void addPreFloatScreen(const QString &windowId, const QString &screenId)
QString placementIdForWindow(const QString &windowId) const override
Opaque placement identifier for the window's current slot.
QString lastUsedZoneId() const
Definition SnapState.h:200
bool reassignDesktop(const QString &windowId, int virtualDesktop)
Re-stamp a snapped window's virtual-desktop membership to virtualDesktop, keeping its zone and screen...
void recordSnapIntent(const QString &windowClass, bool wasUserInitiated)
void updateLastUsedZone(const QString &zoneId, const QString &screenId, const QString &windowClass, int virtualDesktop)
Update last-used zone and emit stateChanged.
QString zoneForWindow(const QString &windowId) const
const QHash< QString, QString > & screenAssignments() const
Definition SnapState.h:108
void assignWindowToZone(const QString &windowId, const QString &zoneId, const QString &screenId, int virtualDesktop)
void windowAssigned(const QString &windowId, const QString &zoneId)
void setZoneAssignments(const QHash< QString, QStringList > &zones)
Definition SnapState.h:274
bool clearAutoSnapped(const QString &windowId)
SnapState & operator=(const SnapState &)=delete
int desktopForWindow(const QString &windowId) const
QStringList floatingWindows() const override
All currently-floating windows.
int windowCount() const override
Total number of managed windows (tiled + floating).
static SnapState * fromJson(const QJsonObject &json, QObject *parent=nullptr)
void setFloating(const QString &windowId, bool floating)
void floatingChanged(const QString &windowId, bool floating)
QString screenForWindow(const QString &windowId) const
const QHash< QString, QString > & preFloatScreenAssignments() const
Definition SnapState.h:160
void windowUnassigned(const QString &windowId)
int lastUsedDesktop() const
Definition SnapState.h:212
const QHash< QString, QStringList > & zoneAssignments() const
Definition SnapState.h:269
void setPreFloatZoneAssignments(const QHash< QString, QStringList > &a)
Definition SnapState.h:290
void setDesktopAssignments(const QHash< QString, int > &d)
Definition SnapState.h:124
bool isAutoSnapped(const QString &windowId) const
Definition WindowTrackingService.h:38