Phosphor
Qt6 / Wayland library suite for window-management tools
 
Loading...
Searching...
No Matches
AutotileEngine.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 <phosphortileengine_export.h>
14#include <QHash>
15#include <QJsonArray>
16#include <QObject>
17#include <QRect>
18#include <QSet>
19#include <QSize>
20#include <QString>
21#include <QStringList>
22#include <QTimer>
23#include <functional>
24#include <memory>
25#include <optional>
26
28
29namespace PhosphorZones {
30class Layout;
31class LayoutRegistry;
32}
33
34namespace PhosphorTileEngine {
35
44{
47 : position(pos)
48 , context(std::move(ctx))
49 , wasFloating(floating)
50 {
51 }
52
53 int position = -1;
55 bool wasFloating = false;
56};
57
59constexpr int MaxPendingRestoresPerApp = 16;
60
61class AutotileConfig;
62
65// Phosphor::Screens::ScreenManager moved to libs/phosphor-screens (Phosphor::Screens::ScreenManager).
66} // namespace PhosphorTileEngine
67namespace Phosphor::Screens {
68class ScreenManager;
69}
70
71namespace PhosphorTiles {
73class TilingAlgorithm;
74class TilingState;
75}
76
77namespace PhosphorTileEngine {
78
88class PHOSPHORTILEENGINE_EXPORT AutotileEngine : public PhosphorEngine::PlacementEngineBase
89{
90 Q_OBJECT
91 Q_PROPERTY(bool enabled READ isEnabled NOTIFY enabledChanged)
92 Q_PROPERTY(QString algorithm READ algorithm WRITE setAlgorithm NOTIFY algorithmChanged)
93
96
97public:
98 explicit AutotileEngine(PhosphorZones::LayoutRegistry* layoutManager,
99 PhosphorEngine::IWindowTrackingService* windowTracker,
100 Phosphor::Screens::ScreenManager* screenManager,
101 PhosphorTiles::ITileAlgorithmRegistry* algorithmRegistry, QObject* parent = nullptr);
102 ~AutotileEngine() override;
103
105 PhosphorTiles::ITileAlgorithmRegistry* algorithmRegistry() const
106 {
107 return m_algorithmRegistry;
108 }
109
126 void setWindowRegistry(QObject* registry) override;
127
128 // ═══════════════════════════════════════════════════════════════════════════
129 // Per-screen autotile state (derived from layout assignments)
130 // ═══════════════════════════════════════════════════════════════════════════
131
136 bool isEnabled() const noexcept override;
137
157 QSet<int> desktopsWithActiveState() const override;
158
164 bool isAutotileScreen(const QString& screenId) const;
165
173 bool isWindowTracked(const QString& windowId) const override
174 {
175 return m_windowToStateKey.contains(windowId);
176 }
177
185 bool isWindowTiled(const QString& windowId) const override;
186
187 // IPlacementEngine
188 bool isActiveOnScreen(const QString& screenId) const override;
189
190 // ═══════════════════════════════════════════════════════════════════════════
191 // IPlacementEngine — generic screen/window management overrides
192 //
193 // Each override delegates to the concrete autotile method below it.
194 // AutotileAdaptor continues to call the concrete methods directly.
195 // ═══════════════════════════════════════════════════════════════════════════
196
197 QSet<QString> activeScreens() const override
198 {
199 return autotileScreens();
200 }
201 void setActiveScreens(const QSet<QString>& screens) override
202 {
203 setAutotileScreens(screens);
204 }
205 QStringList managedWindowOrder(const QString& screenId) const override
206 {
207 return tiledWindowOrder(screenId);
208 }
209 bool isModeSpecificFloated(const QString& windowId) const override
210 {
211 return isAutotileFloated(windowId);
212 }
213 void clearModeSpecificFloatMarker(const QString& windowId) override
214 {
215 clearAutotileFloated(windowId);
216 }
217 bool isWindowManaged(const QString& windowId) const override
218 {
219 return isWindowTiled(windowId);
220 }
221 QString algorithmId() const override
222 {
223 return algorithm();
224 }
225 void markModeSpecificFloated(const QString& windowId) override
226 {
227 markAutotileFloated(windowId);
228 }
229
234 const QSet<QString>& autotileScreens() const
235 {
236 return m_autotileScreens;
237 }
238
243 QString activeScreen() const override
244 {
245 return m_activeScreen;
246 }
247
257 void setAutotileScreens(const QSet<QString>& screens);
258
268 void setCurrentDesktop(int desktop) override;
269
279 void setCurrentActivity(const QString& activity) override;
280
295 void updateStickyScreenPins(const std::function<bool(const QString&)>& isWindowSticky) override;
296
303 void pruneStatesForDesktop(int removedDesktop) override;
304
311 void pruneStatesForActivities(const QStringList& validActivities) override;
312
316 int currentDesktop() const noexcept
317 {
318 return m_currentDesktop;
319 }
320
324 const QString& currentActivity() const noexcept
325 {
326 return m_currentActivity;
327 }
328
329 // ═══════════════════════════════════════════════════════════════════════════
330 // Algorithm selection
331 // ═══════════════════════════════════════════════════════════════════════════
332
337 QString algorithm() const noexcept;
338
346 void setAlgorithm(const QString& algorithmId) override;
347
352 PhosphorTiles::TilingAlgorithm* currentAlgorithm() const;
353
354 // ═══════════════════════════════════════════════════════════════════════════
355 // Tiling state access
356 // ═══════════════════════════════════════════════════════════════════════════
357
366 PhosphorTiles::TilingState* tilingStateForScreen(const QString& screenId);
367
368 PhosphorEngine::IPlacementState* stateForScreen(const QString& screenId) override;
369 const PhosphorEngine::IPlacementState* stateForScreen(const QString& screenId) const override;
370
375 AutotileConfig* config() const noexcept;
376
377 // ═══════════════════════════════════════════════════════════════════════════
378 // Session Persistence
379 // ═══════════════════════════════════════════════════════════════════════════
380
392 void saveState() override;
393
401 void loadState() override;
402
413 void setPersistenceDelegate(std::function<void()> saveFn, std::function<void()> loadFn)
414 {
415 m_persistSaveFn = std::move(saveFn);
416 m_persistLoadFn = std::move(loadFn);
417 }
418
424 void setIsWindowFloatingFn(std::function<bool(const QString&)> fn) override
425 {
426 m_isWindowFloatingFn = std::move(fn);
427 }
428
429 // Cross-engine handoff (see PhosphorEngine/IPlacementEngine.h for contract)
430 QString engineId() const override
431 {
432 return QStringLiteral("autotile");
433 }
434 void handoffReceive(const HandoffContext& ctx) override;
435 void handoffRelease(const QString& windowId) override;
436 QString screenForTrackedWindow(const QString& windowId) const override
437 {
438 const auto it = m_windowToStateKey.constFind(canonicalizeForLookup(windowId));
439 return it == m_windowToStateKey.constEnd() ? QString() : it.value().screenId;
440 }
441
448 QJsonArray serializeWindowOrders() const override;
449
457 void deserializeWindowOrders(const QJsonArray& orders) override;
458
464 QJsonObject serializePendingRestores() const override;
465
471 void deserializePendingRestores(const QJsonObject& obj) override;
472
500 int pruneExcludedPendingRestores(const QStringList& exclusionPatterns);
501
525 std::function<bool(const QString& screenId, int desktop, const QString& activity)>;
526
538 {
539 m_shouldPersistRestorePredicate = std::move(predicate);
540 }
541
542 // ═══════════════════════════════════════════════════════════════════════════
543 // Settings synchronization
544 // ═══════════════════════════════════════════════════════════════════════════
545
549
550 // Per-screen config — forwarded to PerScreenConfigResolver (IPlacementEngine overrides)
551 void applyPerScreenConfig(const QString& screenId, const QVariantMap& overrides) override;
552 void clearPerScreenConfig(const QString& screenId) override;
553 QVariantMap perScreenOverrides(const QString& screenId) const override;
554 bool hasPerScreenOverride(const QString& screenId, const QString& key) const;
555 void updatePerScreenOverride(const QString& screenId, const QString& key, const QVariant& value);
556
557 // Effective per-screen values — forwarded to PerScreenConfigResolver
558 int effectiveInnerGap(const QString& screenId) const;
559 int effectiveOuterGap(const QString& screenId) const;
560 ::PhosphorLayout::EdgeGaps effectiveOuterGaps(const QString& screenId) const;
561 bool effectiveSmartGaps(const QString& screenId) const;
562 bool effectiveRespectMinimumSize(const QString& screenId) const;
563 int effectiveMaxWindows(const QString& screenId) const;
564 qreal effectiveSplitRatioStep(const QString& screenId) const override;
565 int runtimeMaxWindows() const override;
566 QString effectiveAlgorithmId(const QString& screenId) const;
567 PhosphorTiles::TilingAlgorithm* effectiveAlgorithm(const QString& screenId) const;
568
569 // ═══════════════════════════════════════════════════════════════════════════
570 // Manual tiling operations
571 // ═══════════════════════════════════════════════════════════════════════════
572
573 void setInnerGap(int gap);
574 void setOuterGap(int gap);
575 void setSmartGaps(bool enabled);
576 void setFocusNewWindows(bool enabled);
585 Q_INVOKABLE void retile(const QString& screenId = QString()) override;
586
593 Q_INVOKABLE void swapWindows(const QString& windowId1, const QString& windowId2);
594
603 Q_INVOKABLE void promoteToMaster(const QString& windowId);
604
612 Q_INVOKABLE void demoteFromMaster(const QString& windowId);
613
620 Q_INVOKABLE void swapFocusedWithMaster() override;
621
622 // ═══════════════════════════════════════════════════════════════════════════
623 // Focus/window cycling
624 // ═══════════════════════════════════════════════════════════════════════════
625
631 Q_INVOKABLE void focusNext();
632
638 Q_INVOKABLE void focusPrevious();
639
645 Q_INVOKABLE void focusMaster() override;
646
655 void setFocusedWindow(const QString& windowId);
656
666 void setActiveScreenHint(const QString& screenId) override;
667
668 // ═══════════════════════════════════════════════════════════════════════════
669 // Split ratio adjustment
670 // ═══════════════════════════════════════════════════════════════════════════
671
679 Q_INVOKABLE void increaseMasterRatio(qreal delta = 0.05) override;
680
688 Q_INVOKABLE void decreaseMasterRatio(qreal delta = 0.05) override;
689
698 void setGlobalSplitRatio(qreal ratio);
699
708 void setGlobalMasterCount(int count);
709
710 // ═══════════════════════════════════════════════════════════════════════════
711 // Master count adjustment
712 // ═══════════════════════════════════════════════════════════════════════════
713
717 Q_INVOKABLE void increaseMasterCount() override;
718
722 Q_INVOKABLE void decreaseMasterCount() override;
723
724 // ═══════════════════════════════════════════════════════════════════════════
725 // Window rotation and floating (context-aware shortcuts support)
726 // ═══════════════════════════════════════════════════════════════════════════
727
737 Q_INVOKABLE void rotateWindowOrder(bool clockwise = true);
738
748 Q_INVOKABLE void toggleFocusedWindowFloat();
749
761 Q_INVOKABLE void toggleWindowFloat(const QString& windowId, const QString& screenId) override;
762
773 Q_INVOKABLE void swapFocusedInDirection(const QString& direction, const QString& action = QStringLiteral("move"));
774
783 Q_INVOKABLE void focusInDirection(const QString& direction, const QString& action = QStringLiteral("focus"));
784
792 Q_INVOKABLE void moveFocusedToPosition(int position);
793
794 // ═══════════════════════════════════════════════════════════════════════════
795 // IPlacementEngine — navigation overrides
796 //
797 // Each override absorbs what AutotileNavigationAdapter did: translate
798 // the user-intent-shaped IPlacementEngine call into the existing
799 // concrete AutotileEngine method with the right parameters.
800 // ═══════════════════════════════════════════════════════════════════════════
801
802 void focusInDirection(const QString& direction, const PhosphorEngine::NavigationContext& ctx) override;
803 void moveFocusedInDirection(const QString& direction, const PhosphorEngine::NavigationContext& ctx) override;
804 void swapFocusedInDirection(const QString& direction, const PhosphorEngine::NavigationContext& ctx) override;
805 void moveFocusedToPosition(int position, const PhosphorEngine::NavigationContext& ctx) override;
806 void rotateWindows(bool clockwise, const PhosphorEngine::NavigationContext& ctx) override;
810 void cycleFocus(bool forward, const PhosphorEngine::NavigationContext& ctx) override;
813
814 // Autotile-specific navigation. Callable directly on the concrete
815 // AutotileEngine pointer from internal callers.
816 void swapInDirection(const QString& direction, const QString& action);
817 void rotateWindows(bool clockwise, const QString& screenId);
818 void moveToPosition(const QString& windowId, int position, const QString& screenId);
819
830 Q_INVOKABLE void setWindowFloat(const QString& windowId, bool shouldFloat) override;
831
835 Q_INVOKABLE void floatWindow(const QString& windowId);
836
840 Q_INVOKABLE void unfloatWindow(const QString& windowId);
841
842 // ═══════════════════════════════════════════════════════════════════════════
843 // PhosphorZones::Zone-ordered window transitions (snapping ↔ autotile)
844 // ═══════════════════════════════════════════════════════════════════════════
845
859 void setInitialWindowOrder(const QString& screenId, const QStringList& windowIds) override;
860
869 void clearSavedFloatingForWindows(const QStringList& windowIds) override;
870
876 void clearAllSavedFloating() override;
877
888 QStringList tiledWindowOrder(const QString& screenId) const;
889
890 // ═══════════════════════════════════════════════════════════════════════════
891 // Window event handlers (public API for external notification)
892 // ═══════════════════════════════════════════════════════════════════════════
893
905 using IPlacementEngine::windowOpened;
906 void windowOpened(const QString& windowId, const QString& screenId, int minWidth, int minHeight) override;
907
918 void windowMinSizeUpdated(const QString& windowId, int minWidth, int minHeight);
919
928 void windowClosed(const QString& windowId) override;
929
939 void windowFocused(const QString& windowId, const QString& screenId) override;
940
941 // ═══════════════════════════════════════════════════════════════════════════
942 // Retile helpers (public — used by extracted classes)
943 // ═══════════════════════════════════════════════════════════════════════════
944
952 void scheduleRetileForScreen(const QString& screenId) override;
953
954 // ═══════════════════════════════════════════════════════════════════════════
955 // Drag-insert preview (trigger-held window drag reorders autotile stack)
956 // ═══════════════════════════════════════════════════════════════════════════
957
968 bool beginDragInsertPreview(const QString& windowId, const QString& screenId) override;
969
977 void updateDragInsertPreview(int insertIndex) override;
978
986 void commitDragInsertPreview() override;
987
991 void cancelDragInsertPreview() override;
992
1007 int computeDragInsertIndexAtPoint(const QString& screenId, const QPoint& cursorPos) const override;
1008
1012 bool hasDragInsertPreview() const override
1013 {
1014 return m_dragInsertPreview.has_value();
1015 }
1016
1021 {
1022 return m_dragInsertPreview ? m_dragInsertPreview->windowId : QString();
1023 }
1024
1028 QString dragInsertPreviewScreenId() const override
1029 {
1030 return m_dragInsertPreview ? m_dragInsertPreview->targetScreenId : QString();
1031 }
1032
1042 void retileAfterOperation(const QString& screenId, bool operationSucceeded);
1043
1044Q_SIGNALS:
1049 void enabledChanged(bool enabled);
1050
1056 void autotileScreensChanged(const QStringList& screenIds, bool isDesktopSwitch);
1057
1058 // algorithmChanged(const QString&) — inherited from PlacementEngineBase.
1059 // placementChanged(const QString&) — inherited from PlacementEngineBase.
1060 // Replaces the former tilingChanged signal; all internal emitters now
1061 // emit placementChanged, and callers connect to the base-class signal.
1062 // windowsReleased(const QStringList&, const QSet<QString>&) — inherited
1063 // from PlacementEngineBase. Replaces windowsReleasedFromTiling.
1064
1076 // windowFloatingStateSynced and windowsBatchFloated are inherited from
1077 // PlacementEngineBase. Autotile-specific documentation: windowFloatingStateSynced
1078 // is emitted when the engine's TilingState::isFloating diverges from WTS's view
1079 // (e.g. a newly-inserted window carries stale snap-mode float state). The
1080 // downstream handler updates WTS bookkeeping without geometry restore.
1081 // windowsBatchFloated is emitted when overflow windows are batch-floated
1082 // during applyTiling; the daemon handler updates WTS state directly.
1083
1092 void windowsTiled(const QString& tileRequestsJson);
1093
1094public:
1095 // ═══════════════════════════════════════════════════════════════════════════
1096 // Autotile-float origin tracking (ephemeral, not persisted)
1097 // ═══════════════════════════════════════════════════════════════════════════
1098
1099 void markAutotileFloated(const QString& windowId);
1100 void clearAutotileFloated(const QString& windowId);
1101 bool isAutotileFloated(const QString& windowId) const;
1102
1103 int pruneStaleWindows(const QSet<QString>& aliveWindowIds) override;
1104
1105private Q_SLOTS:
1106 void onWindowZoneChanged(const QString& windowId, const QString& zoneId);
1107 void onWindowAdded(const QString& windowId);
1108 void onWindowRemoved(const QString& windowId);
1109 void onWindowFocused(const QString& windowId);
1110 void onScreenGeometryChanged(const QString& screenId);
1111 void onLayoutChanged(PhosphorZones::Layout* layout);
1112
1113protected:
1114 void onWindowClaimed(const QString& windowId) override;
1115 void onWindowReleased(const QString& windowId) override;
1116 void onWindowFloated(const QString& windowId) override;
1117 void onWindowUnfloated(const QString& windowId) override;
1118
1119private:
1120 void connectSignals();
1121 bool insertWindow(const QString& windowId, const QString& screenId);
1122 void removeWindow(const QString& windowId);
1123 void removeSavedFloatingEntry(const QString& windowId);
1124 void pruneStaleRestores(const QString& appId);
1125 bool storeWindowMinSize(const QString& windowId, int minWidth, int minHeight);
1126 bool recalculateLayout(const QString& screenId);
1127 void applyTiling(const QString& screenId);
1128 bool shouldTileWindow(const QString& windowId) const;
1129 QString screenForWindow(const QString& windowId) const;
1130 QRect screenGeometry(const QString& screenId) const;
1131
1135 bool isKnownScreen(const QString& screenId) const;
1136
1145 PhosphorEngine::TilingStateKey currentKeyForScreen(const QString& screenId) const
1146 {
1147 auto it = m_screenDesktopOverride.constFind(screenId);
1148 int desktop = (it != m_screenDesktopOverride.constEnd()) ? it.value() : m_currentDesktop;
1149 return PhosphorEngine::TilingStateKey{screenId, desktop, m_currentActivity};
1150 }
1151
1160
1168 void resetMaxWindowsForAlgorithmSwitch(PhosphorTiles::TilingAlgorithm* oldAlgo,
1170
1174 void propagateGlobalSplitRatio();
1175
1179 void propagateGlobalMasterCount();
1180
1192 void backfillWindows();
1193
1206 void retileScreen(const QString& screenId);
1207
1217 void scheduleRetileRetry(const QString& screenId);
1218
1222 void processRetileRetries();
1223
1224 // ═══════════════════════════════════════════════════════════════════════════════
1225 // Helper Methods
1226 // ═══════════════════════════════════════════════════════════════════════════════
1227
1238 bool cleanupPendingOrderIfResolved(const QString& screenId);
1239
1247 void schedulePromoteSavedWindowOrders();
1248
1256 void promoteSavedWindowOrders();
1257
1264 bool warnIfEmptyWindowId(const QString& windowId, const char* operation) const;
1265
1281 QString canonicalizeWindowId(const QString& rawWindowId);
1282
1289 void cleanupCanonical(const QString& anyWindowId);
1290
1298 QString canonicalizeForLookup(const QString& rawWindowId) const;
1299
1312 QString currentAppIdFor(const QString& anyWindowId) const;
1313
1322 void syncShortcutAdjustmentToSettings();
1323
1329 void performToggleFloat(PhosphorTiles::TilingState* state, const QString& windowId, const QString& screenId);
1330
1340 PhosphorTiles::TilingState* stateForWindow(const QString& windowId, QString* outScreenId = nullptr);
1341
1342 QSet<QString> m_autotileFloatedWindows;
1343
1344 PhosphorZones::LayoutRegistry* m_layoutManager = nullptr;
1345 PhosphorEngine::IWindowTrackingService* m_windowTracker = nullptr;
1346 Phosphor::Screens::ScreenManager* m_screenManager = nullptr;
1347 PhosphorEngine::IWindowRegistry* m_windowRegistry = nullptr;
1348 PhosphorTiles::ITileAlgorithmRegistry* m_algorithmRegistry = nullptr;
1349 std::unique_ptr<AutotileConfig> m_config;
1350 std::unique_ptr<PerScreenConfigResolver> m_configResolver;
1351 std::unique_ptr<NavigationController> m_navigation;
1352 QTimer m_writeBackGuardTimer;
1353 QTimer m_settingsRetileTimer;
1354
1355 // Persistence delegates (KConfig stays in WTA layer)
1356 std::function<void()> m_persistSaveFn;
1357 std::function<void()> m_persistLoadFn;
1358 std::function<bool(const QString&)> m_isWindowFloatingFn;
1359
1360 QSet<QString> m_autotileScreens;
1361 QString m_algorithmId;
1362 bool m_algorithmEverSet = false;
1363 QString m_activeScreen; // Last-focused screen (updated by onWindowFocused)
1364 QHash<PhosphorEngine::TilingStateKey, PhosphorTiles::TilingState*> m_screenStates; // Owned via Qt parent (this)
1365
1366 QHash<QString, PhosphorEngine::TilingStateKey> m_windowToStateKey; // windowId -> owning state key
1367 QHash<QString, QSize> m_windowMinSizes; // windowId -> minimum size from KWin
1368
1369 // Instance id → first-seen canonical windowId.
1370 //
1371 // Fallback for when no shared WindowRegistry is attached (unit tests).
1372 // With a registry, canonicalization delegates to it instead. Production
1373 // daemons always take the registry path, so this map stays empty.
1374 //
1375 // The canonical form is the FIRST windowId string we saw for a given
1376 // instance id. Subsequent arrivals with a mutated appId (Electron/CEF
1377 // apps that swap WM_CLASS mid-session) resolve back to that canonical
1378 // form so every map/PhosphorTiles::TilingState key in the engine stays consistent.
1379 QHash<QString, QString> m_canonicalByInstance;
1380
1381 // Current desktop/activity context — used by tilingStateForScreen() to construct
1382 // TilingStateKey. Updated by setCurrentDesktop()/setCurrentActivity() BEFORE
1383 // updateAutotileScreens() runs on desktop/activity switch.
1384 int m_currentDesktop = 1;
1385 QString m_currentActivity;
1386 bool m_isDesktopContextSwitch = false;
1387
1388 // Per-screen desktop override for sticky screens. When the KWin script
1389 // "virtualdesktopsonlyonprimary" pins all secondary-screen windows to all
1390 // desktops, the TilingStateKey desktop dimension becomes meaningless for
1391 // those screens. This map pins such screens to their original desktop so
1392 // currentKeyForScreen() returns the key of the existing PhosphorTiles::TilingState rather
1393 // than a new (empty) key after a desktop switch.
1394 QHash<QString, int> m_screenDesktopOverride;
1395
1396 // Floating window IDs preserved across mode switches, per desktop/activity.
1397 // When autotile is deactivated, floated windows are saved here so that
1398 // re-enabling autotile restores them as floating regardless of screen.
1399 QHash<PhosphorEngine::TilingStateKey, QSet<QString>> m_savedFloatingWindows;
1400
1401 // Pre-seeded window order for snapping → autotile transitions.
1402 // Keyed by stable EDID-based screen ID (Phosphor::Screens::ScreenIdentity::identifierFor).
1403 // Consumed by insertWindow() as windows arrive; also cleaned up by
1404 // removeWindow() if a pre-seeded window closes before arriving.
1405 QHash<QString, QStringList> m_pendingInitialOrders;
1406 QHash<QString, uint64_t> m_pendingOrderGeneration;
1407 // Screens whose pendingInitialOrders entry is "strict" — saved order
1408 // wins even when arrival order differs. Set by setInitialWindowOrder
1409 // (mode transition: the daemon intentionally pre-computed an order from
1410 // the previous mode's zones, and that order MUST be preserved).
1411 // Cleared after the order is fully consumed. Entries from the
1412 // deserialize / promoteSavedWindowOrders paths are NOT in this set —
1413 // for those, the saved position is "advisory": honored only when it
1414 // appends at the current tail, otherwise insertPosition takes over.
1415 // This is the behaviour users expect from their "After existing" /
1416 // "After focused" / "As main window" preference for new windows.
1417 QSet<QString> m_strictInitialOrderScreens;
1418
1419 // Saved window orders from session persistence, keyed by full context.
1420 // On desktop/activity switch, orders for the new context are promoted into
1421 // m_pendingInitialOrders so windows arriving on the new desktop get their
1422 // saved ordering. Consumed once per context (removed after promotion).
1423 QHash<PhosphorEngine::TilingStateKey, QStringList> m_savedWindowOrders;
1424
1425 // Pending restore queue for windows removed from autotile (close/reopen).
1426 // Keyed by appId (stable across KWin restarts). Multiple entries per appId
1427 // support multi-instance apps; consumed FIFO by insertWindow().
1428 // Analogous to snapping's PendingRestoreQueues.
1429 QHash<QString, QList<PendingAutotileRestore>> m_pendingAutotileRestores;
1430
1431 // Disabled-context gate injected by the daemon adaptor. See
1432 // ShouldPersistRestorePredicate. Empty until the daemon wires it; while
1433 // empty every (screen, desktop, activity) tuple is treated as active —
1434 // the behaviour unit tests rely on.
1435 ShouldPersistRestorePredicate m_shouldPersistRestorePredicate{};
1436
1437 // Zero-delay timer to coalesce promoteSavedWindowOrders() calls during
1438 // simultaneous desktop+activity switches. Without coalescing, the first
1439 // call (after setCurrentDesktop) would promote using the stale activity,
1440 // potentially consuming the wrong specific-activity entry.
1441 QTimer m_promoteOrdersTimer;
1442
1443 // Per-screen overflow tracking with O(1) reverse-index lookups.
1444 OverflowManager m_overflow;
1445
1446 bool m_retiling = false;
1447
1448 // Queued-connection retile coalescing: windowOpened D-Bus calls arriving in
1449 // the same event loop pass are coalesced into a single retile per screen.
1450 // Uses QMetaObject::invokeMethod(Qt::QueuedConnection) which fires after
1451 // all currently-pending events are processed — no fixed delay needed.
1452 QSet<QString> m_pendingRetileScreens;
1453 bool m_retilePending = false;
1454
1455 // Bounded retry for transient screen geometry failures.
1456 // When QScreen is temporarily unavailable (e.g. during Wayland desktop switch),
1457 // recalculateLayout cannot compute zone geometry. Rather than silently dropping
1458 // the retile (leaving stale zones), we retry after a short interval.
1459 // Per-screen retry counts prevent infinite loops; cleared on success or screen removal.
1460 static constexpr int MaxRetileRetries = 3;
1461 static constexpr int RetileRetryIntervalMs = 150;
1462 QTimer m_retileRetryTimer;
1463 QSet<QString> m_retileRetryScreens;
1464 QHash<QString, int> m_retileRetryCount;
1465
1466 // Deferred focus: set by onWindowAdded, emitted after applyTiling so the
1467 // focus request arrives at KWin AFTER windowsTiled (whose onComplete raises
1468 // windows in tiling order). Without this, the raise loop buries the new window.
1469 QString m_pendingFocusWindowId;
1470
1471 // Drag-insert preview state. When set, applyTiling() filters the dragged
1472 // window out of the windowsTiled batch so the KWin interactive move isn't
1473 // fought, while the remaining windows animate into their new positions.
1474 // Supports three entry modes (captured at begin() time for cancel restoration):
1475 // - Same-screen reorder: window was already tiled/floating on targetScreenId
1476 // - Cross-screen adoption: window was tracked on a different autotile screen
1477 // - Fresh adoption: window was not tracked by the engine at all
1478 struct DragInsertPreview
1479 {
1480 QString windowId;
1481 QString targetScreenId;
1482 int lastInsertIndex = -1;
1483
1484 // Prior-state restoration info (used on cancel)
1485 bool hadPriorState = false; // True if m_windowToStateKey contained windowId at begin
1487 priorKey; // Key of the prior PhosphorTiles::TilingState (meaningful iff hadPriorState)
1488 int priorRawIndex = -1; // Raw index in priorState->windowOrder() at begin
1489 bool priorFloating = false; // Prior floating flag in priorState
1490 bool priorSameScreen = false; // priorKey == currentKeyForScreen(targetScreenId)
1491
1492 // Eviction info (used when the target stack is already at maxWindows
1493 // and the dragged window is a new member). The last tiled neighbour
1494 // is floated to make room; on cancel the eviction is undone, on
1495 // commit the evicted window is sent through the batch-float path so
1496 // its pre-tile geometry is restored.
1497 QString evictedWindowId;
1498 };
1499 std::optional<DragInsertPreview> m_dragInsertPreview;
1500
1507 void processPendingRetiles();
1508};
1509
1510} // namespace PhosphorTileEngine
Definition IAutotileSettings.h:15
Definition IWindowRegistry.h:12
Definition IWindowTrackingService.h:33
Abstract base class for placement engines.
Definition PlacementEngineBase.h:37
Core engine for automatic window tiling.
Definition AutotileEngine.h:89
void snapAllWindows(const PhosphorEngine::NavigationContext &ctx) override
Snap every unmanaged window on the screen to the current layout.
void setGlobalMasterCount(int count)
Set master count globally (config + all per-screen states)
QSet< QString > activeScreens() const override
Definition AutotileEngine.h:197
void setCurrentDesktop(int desktop) override
Set the current virtual desktop for per-desktop tiling state.
void promoteToMaster(const QString &windowId)
Promote a window to the master area.
void windowMinSizeUpdated(const QString &windowId, int minWidth, int minHeight)
Update a window's minimum size at runtime.
void pruneStatesForActivities(const QStringList &validActivities) override
Prune PhosphorTiles::TilingState entries for activities not in the given set.
bool effectiveSmartGaps(const QString &screenId) const
void updateStickyScreenPins(const std::function< bool(const QString &)> &isWindowSticky) override
Pin screens where all autotiled windows are sticky (on all desktops)
void swapFocusedInDirection(const QString &direction, const PhosphorEngine::NavigationContext &ctx) override
Swap the focused window with the adjacent window.
void markAutotileFloated(const QString &windowId)
QString dragInsertPreviewWindowId() const
Get the window ID of the active drag-insert preview, or empty.
Definition AutotileEngine.h:1020
void clearModeSpecificFloatMarker(const QString &windowId) override
Definition AutotileEngine.h:213
void setFocusNewWindows(bool enabled)
QString algorithmId() const override
Definition AutotileEngine.h:221
int pruneExcludedPendingRestores(const QStringList &exclusionPatterns)
Drop pending-restore queues whose appId matches any exclusion pattern.
void retileAfterOperation(const QString &screenId, bool operationSucceeded)
Helper to retile a screen after a window operation.
void increaseMasterCount() override
Increase the number of master windows.
void onWindowUnfloated(const QString &windowId) override
void setGlobalSplitRatio(qreal ratio)
Set master ratio globally (config + all per-screen states)
bool isWindowTiled(const QString &windowId) const override
Check if a window is currently tiled (tracked AND not floating).
PhosphorEngine::IAutotileSettings * autotileSettings() const
QStringList tiledWindowOrder(const QString &screenId) const
Get the current tiled window order for a screen.
void reapplyLayout(const PhosphorEngine::NavigationContext &ctx) override
Re-apply the current layout to all managed windows.
bool effectiveRespectMinimumSize(const QString &screenId) const
void applyPerScreenConfig(const QString &screenId, const QVariantMap &overrides) override
void windowFocused(const QString &windowId, const QString &screenId) override
Notify the engine that a window was focused.
void toggleFocusedWindowFloat()
Toggle the focused window between tiled and floating states.
void commitDragInsertPreview() override
Commit the active drag-insert preview.
QString dragInsertPreviewScreenId() const override
Get the target screen ID of the active drag-insert preview, or empty.
Definition AutotileEngine.h:1028
void moveFocusedToPosition(int position)
Move the focused window to a specific position in the tiling order.
QJsonArray serializeWindowOrders() const override
Serialize per-context autotile window orders to JSON.
void clearAllSavedFloating() override
Clear ALL saved floating state (used when autotile is disabled globally)
void setWindowFloat(const QString &windowId, bool shouldFloat) override
Set the floating state of a specific window.
void refreshConfigFromSettings() override
Re-read all tuning values from the engine's settings interface.
void moveFocusedInDirection(const QString &direction, const PhosphorEngine::NavigationContext &ctx) override
Move the focused window to the adjacent slot.
void clearSavedFloatingForWindows(const QStringList &windowIds) override
Clear saved floating state for windows that are actively zone-snapped.
bool isActiveOnScreen(const QString &screenId) const override
Whether this engine is active on the given screen.
int currentDesktop() const noexcept
Get the current virtual desktop tracked by the engine.
Definition AutotileEngine.h:316
void windowOpened(const QString &windowId, const QString &screenId, int minWidth, int minHeight) override
A new window appeared on this engine's screen.
void windowsTiled(const QString &tileRequestsJson)
Emitted when a window's floating state changes due to a user action.
void rotateWindowOrder(bool clockwise=true)
Rotate all tiled windows by one position.
bool isAutotileFloated(const QString &windowId) const
void scheduleRetileForScreen(const QString &screenId) override
Schedule a deferred retile for a screen.
void onWindowClaimed(const QString &windowId) override
void onWindowReleased(const QString &windowId) override
void increaseMasterRatio(qreal delta=0.05) override
Increase the master area ratio.
void pushToEmptyZone(const PhosphorEngine::NavigationContext &ctx) override
Move the focused window to the first empty slot.
void swapWindows(const QString &windowId1, const QString &windowId2)
Swap positions of two tiled windows.
void toggleFocusedFloat(const PhosphorEngine::NavigationContext &ctx) override
Toggle the focused window between managed and floating.
int effectiveOuterGap(const QString &screenId) const
QJsonObject serializePendingRestores() const override
Serialize pending autotile restore queues to JSON.
void focusPrevious()
Focus the previous tiled window.
void toggleWindowFloat(const QString &windowId, const QString &screenId) override
Toggle a specific window between tiled and floating states.
void updateDragInsertPreview(int insertIndex) override
Update the target insert index for the active drag preview.
bool hasDragInsertPreview() const override
Query whether a drag-insert preview is currently active.
Definition AutotileEngine.h:1012
void deserializeWindowOrders(const QJsonArray &orders) override
Deserialize per-context autotile window orders from JSON.
bool beginDragInsertPreview(const QString &windowId, const QString &screenId) override
Begin a drag-insert preview for a window already tiled on a screen.
QStringList managedWindowOrder(const QString &screenId) const override
Definition AutotileEngine.h:205
void clearPerScreenConfig(const QString &screenId) override
void setInitialWindowOrder(const QString &screenId, const QStringList &windowIds) override
Pre-seed initial window order for deterministic snapping → autotile transitions.
void cycleFocus(bool forward, const PhosphorEngine::NavigationContext &ctx) override
Cycle keyboard focus through managed windows.
void moveFocusedToPosition(int position, const PhosphorEngine::NavigationContext &ctx) override
Move the focused window to the Nth position.
void decreaseMasterCount() override
Decrease the number of master windows.
void focusNext()
Focus the next tiled window.
void rotateWindows(bool clockwise, const QString &screenId)
int effectiveInnerGap(const QString &screenId) const
void floatWindow(const QString &windowId)
Float a specific window by its ID (convenience forwarder)
void setShouldPersistRestorePredicate(ShouldPersistRestorePredicate predicate)
Inject the persist-restore gate.
Definition AutotileEngine.h:537
QString algorithm() const noexcept
Get current algorithm ID.
void autotileScreensChanged(const QStringList &screenIds, bool isDesktopSwitch)
Emitted when the set of autotile screens changes.
void cancelDragInsertPreview() override
Cancel the active drag-insert preview, restoring the original order.
void focusInDirection(const QString &direction, const QString &action=QStringLiteral("focus"))
Focus the adjacent window in tiling order with OSD feedback.
bool isWindowManaged(const QString &windowId) const override
Whether the engine considers the window "managed" (eligible for layout operations).
Definition AutotileEngine.h:217
void updatePerScreenOverride(const QString &screenId, const QString &key, const QVariant &value)
int pruneStaleWindows(const QSet< QString > &aliveWindowIds) override
void setActiveScreens(const QSet< QString > &screens) override
Definition AutotileEngine.h:201
bool hasPerScreenOverride(const QString &screenId, const QString &key) const
void focusMaster() override
Focus the master window.
void decreaseMasterRatio(qreal delta=0.05) override
Decrease the master area ratio.
void setAutotileScreens(const QSet< QString > &screens)
Set which screens use autotile (derived from layout assignments)
void demoteFromMaster(const QString &windowId)
Demote a window from the master area.
void swapFocusedWithMaster() override
Swap the currently focused window with the master window.
QVariantMap perScreenOverrides(const QString &screenId) const override
qreal effectiveSplitRatioStep(const QString &screenId) const override
int computeDragInsertIndexAtPoint(const QString &screenId, const QPoint &cursorPos) const override
Compute the insert index for a cursor position on an autotile screen.
void handoffRelease(const QString &windowId) override
Release ownership of a window WITHOUT modifying its geometry.
void restoreFocusedWindow(const PhosphorEngine::NavigationContext &ctx) override
Restore the focused window out of its managed state.
void moveToPosition(const QString &windowId, int position, const QString &screenId)
void focusInDirection(const QString &direction, const PhosphorEngine::NavigationContext &ctx) override
Move keyboard focus to the adjacent window.
PhosphorTiles::TilingAlgorithm * effectiveAlgorithm(const QString &screenId) const
const QString & currentActivity() const noexcept
Get the current activity tracked by the engine.
Definition AutotileEngine.h:324
void setIsWindowFloatingFn(std::function< bool(const QString &)> fn) override
Set callback to query daemon-side window floating state.
Definition AutotileEngine.h:424
int effectiveMaxWindows(const QString &screenId) const
void retile(const QString &screenId=QString()) override
Force retiling of windows.
bool isModeSpecificFloated(const QString &windowId) const override
Definition AutotileEngine.h:209
void rotateWindows(bool clockwise, const PhosphorEngine::NavigationContext &ctx) override
Rotate all managed windows on the screen.
QString engineId() const override
Stable engine identity for HandoffContext.fromEngineId.
Definition AutotileEngine.h:430
void setWindowRegistry(QObject *registry) override
Wire up the shared WindowRegistry.
void setFocusedWindow(const QString &windowId)
Notify the engine that a window has been focused.
QString screenForTrackedWindow(const QString &windowId) const override
Return the screen this engine considers the window to be on, or empty if the window isn't tracked by ...
Definition AutotileEngine.h:436
void deserializePendingRestores(const QJsonObject &obj) override
Deserialize pending autotile restore queues from JSON.
const QSet< QString > & autotileScreens() const
Get the set of screens currently using autotile.
Definition AutotileEngine.h:234
void swapInDirection(const QString &direction, const QString &action)
void unfloatWindow(const QString &windowId)
Unfloat a specific window by its ID (convenience forwarder)
::PhosphorLayout::EdgeGaps effectiveOuterGaps(const QString &screenId) const
void windowClosed(const QString &windowId) override
Notify the engine that a window was closed.
void markModeSpecificFloated(const QString &windowId) override
Definition AutotileEngine.h:225
void enabledChanged(bool enabled)
Emitted when the enabled state changes.
void setActiveScreenHint(const QString &screenId) override
Set the active screen hint for keyboard shortcut handlers.
void clearAutotileFloated(const QString &windowId)
bool isEnabled() const noexcept override
Check if any screen has autotile enabled.
std::function< bool(const QString &screenId, int desktop, const QString &activity)> ShouldPersistRestorePredicate
Predicate consulted before persisting or honoring a pending restore.
Definition AutotileEngine.h:525
void onWindowFloated(const QString &windowId) override
int runtimeMaxWindows() const override
Runtime max-windows limit.
void handoffReceive(const HandoffContext &ctx) override
Receive ownership of a window from another engine.
QString effectiveAlgorithmId(const QString &screenId) const
QString activeScreen() const override
Get the last-focused screen (updated by onWindowFocused)
Definition AutotileEngine.h:243
void swapFocusedInDirection(const QString &direction, const QString &action=QStringLiteral("move"))
Swap the focused window with the adjacent window in tiling order.
void setCurrentActivity(const QString &activity) override
Set the current activity for per-activity tiling state.
void pruneStatesForDesktop(int removedDesktop) override
Prune PhosphorTiles::TilingState and saved floating entries for a removed desktop.
Handles navigation, focus cycling, and ratio/count adjustments.
Definition NavigationController.h:33
Resolves per-screen configuration overrides for autotiling.
Definition PerScreenConfigResolver.h:38
Abstract contract for a tiling-algorithm registry.
Definition ITileAlgorithmRegistry.h:41
Abstract base class for tiling algorithms.
Definition TilingAlgorithm.h:56
Tracks tiling state for a single screen.
Definition TilingState.h:40
Manual zone-layout registry + per-context assignment store.
Definition LayoutRegistry.h:48
Represents a collection of zones that form a layout.
Definition Layout.h:74
Centralized screen-topology service.
Definition Manager.h:75
Definition EngineTypes.h:13
constexpr QLatin1String LayoutRegistry("org.plasmazones.LayoutRegistry")
Definition AutotileConfig.h:14
constexpr int MaxPendingRestoresPerApp
Maximum pending restore entries per appId (prevents unbounded growth).
Definition AutotileEngine.h:59
Definition AutotileEngine.h:71
Definition IWindowTrackingService.h:22
Definition IWindowTrackingService.h:26
Definition IWindowTrackingService.h:26
Context for a cross-engine window handoff.
Definition IPlacementEngine.h:310
Target window + screen for a navigation or lifecycle operation.
Definition NavigationContext.h:18
Definition EngineTypes.h:16
Per-side edge gap values (resolved, non-negative pixel values)
Definition EdgeGaps.h:27
Configuration for autotiling behavior.
Definition AutotileConfig.h:81
Saved position for a window removed from autotile, keyed by appId.
Definition AutotileEngine.h:44
bool wasFloating
Whether the window was floating when removed.
Definition AutotileEngine.h:55
int position
Index in window order at time of removal.
Definition AutotileEngine.h:53
PendingAutotileRestore(int pos, PhosphorEngine::TilingStateKey ctx, bool floating)
Definition AutotileEngine.h:46
PhosphorEngine::TilingStateKey context
Screen/desktop/activity where the window was tiled.
Definition AutotileEngine.h:54