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>
13#include <QHash>
14#include <QObject>
15#include <QRect>
16#include <QSet>
17#include <QSize>
18#include <QString>
19#include <QStringList>
20#include <QTimer>
21#include <cstdint>
22#include <functional>
23#include <memory>
24#include <optional>
25
27
28namespace PhosphorZones {
29class Layout;
30class LayoutRegistry;
31}
32
33namespace PhosphorTileEngine {
34
35class AutotileConfig;
36
37class NavigationController;
38class PerScreenConfigResolver;
39} // namespace PhosphorTileEngine
40
41// ScreenManager lives in libs/phosphor-screens; forward-declared here.
42namespace PhosphorScreens {
43class ScreenManager;
44}
45
46namespace PhosphorTiles {
48class TilingAlgorithm;
49class TilingState;
50}
51
52namespace PhosphorTileEngine {
53
64class PHOSPHORTILEENGINE_EXPORT AutotileEngine : public PhosphorEngine::PlacementEngineBase
65{
66 Q_OBJECT
67 Q_PROPERTY(bool enabled READ isEnabled NOTIFY enabledChanged)
68 Q_PROPERTY(QString algorithm READ algorithm WRITE setAlgorithm NOTIFY algorithmChanged)
69
72
73public:
74 explicit AutotileEngine(PhosphorZones::LayoutRegistry* layoutManager,
75 PhosphorEngine::IWindowTrackingService* windowTracker,
76 PhosphorScreens::ScreenManager* screenManager,
77 PhosphorTiles::ITileAlgorithmRegistry* algorithmRegistry, QObject* parent = nullptr);
78 ~AutotileEngine() override;
79
81 PhosphorTiles::ITileAlgorithmRegistry* algorithmRegistry() const
82 {
83 return m_algorithmRegistry;
84 }
85
102 void setWindowRegistry(QObject* registry) override;
103
104 // ═══════════════════════════════════════════════════════════════════════════
105 // Per-screen autotile state (derived from layout assignments)
106 // ═══════════════════════════════════════════════════════════════════════════
107
112 bool isEnabled() const noexcept override;
113
139 QSet<int> desktopsWithActiveState() const override;
140
146 bool isAutotileScreen(const QString& screenId) const;
147
155 bool isWindowTracked(const QString& windowId) const override
156 {
157 return m_windowToStateKey.contains(windowId);
158 }
159
167 bool isWindowTiled(const QString& rawWindowId) const override;
168
180 bool isWindowFloatingInAutotile(const QString& windowId) const;
181
190 QStringList allFloatingWindows() const;
191
192 // IPlacementEngine
193 bool isActiveOnScreen(const QString& screenId) const override;
194
195 // ═══════════════════════════════════════════════════════════════════════════
196 // IPlacementEngine — generic screen/window management overrides
197 //
198 // Each override delegates to the concrete autotile method below it.
199 // AutotileAdaptor continues to call the concrete methods directly.
200 // ═══════════════════════════════════════════════════════════════════════════
201
202 QSet<QString> activeScreens() const override
203 {
204 return autotileScreens();
205 }
206 void setActiveScreens(const QSet<QString>& screens) override
207 {
208 setAutotileScreens(screens);
209 }
210 QStringList managedWindowOrder(const QString& screenId) const override
211 {
212 return tiledWindowOrder(screenId);
213 }
214 bool isModeSpecificFloated(const QString& windowId) const override
215 {
216 return isAutotileFloated(windowId);
217 }
218 void clearModeSpecificFloatMarker(const QString& windowId) override
219 {
220 clearAutotileFloated(windowId);
221 }
222 bool isWindowManaged(const QString& windowId) const override
223 {
224 return isWindowTiled(windowId);
225 }
226 QString algorithmId() const override
227 {
228 return algorithm();
229 }
230 void markModeSpecificFloated(const QString& windowId) override
231 {
232 markAutotileFloated(windowId);
233 }
234
239 const QSet<QString>& autotileScreens() const
240 {
241 return m_autotileScreens;
242 }
243
248 QString activeScreen() const override
249 {
250 return m_activeScreen;
251 }
252
270 void setAutotileScreens(const QSet<QString>& screens);
271
281 void setCurrentDesktop(int desktop) override;
282
293 void setCurrentDesktopForScreen(const QString& screenId, int desktop) override;
294
296 void clearCurrentDesktopForScreen(const QString& screenId) override;
297
301 {
302 m_crossSurfaceResolver = resolver;
303 }
304
314 void setCurrentActivity(const QString& activity) override;
315
330 void updateStickyScreenPins(const std::function<bool(const QString&)>& isWindowSticky) override;
331
338 void pruneStatesForDesktop(int removedDesktop) override;
339
346 void pruneStatesForActivities(const QStringList& validActivities) override;
347
351 int currentDesktop() const noexcept
352 {
353 return m_currentDesktop;
354 }
355
359 const QString& currentActivity() const noexcept
360 {
361 return m_currentActivity;
362 }
363
364 // ═══════════════════════════════════════════════════════════════════════════
365 // Algorithm selection
366 // ═══════════════════════════════════════════════════════════════════════════
367
372 QString algorithm() const noexcept;
373
381 void setAlgorithm(const QString& algorithmId) override;
382
387 PhosphorTiles::TilingAlgorithm* currentAlgorithm() const;
388
389 // ═══════════════════════════════════════════════════════════════════════════
390 // Tiling state access
391 // ═══════════════════════════════════════════════════════════════════════════
392
401 PhosphorTiles::TilingState* tilingStateForScreen(const QString& screenId);
402
403 PhosphorEngine::IPlacementState* stateForScreen(const QString& screenId) override;
404 const PhosphorEngine::IPlacementState* stateForScreen(const QString& screenId) const override;
405
410 AutotileConfig* config() const noexcept;
411
412 // ═══════════════════════════════════════════════════════════════════════════
413 // Session Persistence
414 // ═══════════════════════════════════════════════════════════════════════════
415
425 void saveState() override;
426
435 void loadState() override;
436
447 void setPersistenceDelegate(std::function<void()> saveFn, std::function<void()> loadFn)
448 {
449 m_persistSaveFn = std::move(saveFn);
450 m_persistLoadFn = std::move(loadFn);
451 }
452
465 using RestorePositionPredicate = std::function<bool(const QString& windowId)>;
466
470 {
471 m_restorePositionPredicate = std::move(predicate);
472 }
473
482 using FloatPredicate = std::function<bool(const QString& windowId)>;
483
485 {
486 m_floatPredicate = std::move(predicate);
487 }
488
489 // Cross-engine handoff (see PhosphorEngine/IPlacementEngine.h for contract)
490 QString engineId() const override
491 {
492 return QStringLiteral("autotile");
493 }
494 void handoffReceive(const HandoffContext& ctx) override;
495 void handoffRelease(const QString& windowId) override;
496 QString screenForTrackedWindow(const QString& windowId) const override
497 {
498 const auto it = m_windowToStateKey.constFind(canonicalizeForLookup(windowId));
499 return it == m_windowToStateKey.constEnd() ? QString() : it.value().screenId;
500 }
501
521 void onWindowResized(const QString& rawWindowId, const QRect& oldFrame, const QRect& newFrame,
522 const QString& screenId) override;
523 // ═══════════════════════════════════════════════════════════════════════════
524 // Settings synchronization
525 // ═══════════════════════════════════════════════════════════════════════════
526
530
531 // Per-screen config — forwarded to PerScreenConfigResolver (IPlacementEngine overrides)
532 void applyPerScreenConfig(const QString& screenId, const QVariantMap& overrides) override;
533 void clearPerScreenConfig(const QString& screenId) override;
534 QVariantMap perScreenOverrides(const QString& screenId) const override;
535 bool hasPerScreenOverride(const QString& screenId, const QString& key) const;
536 void updatePerScreenOverride(const QString& screenId, const QString& key, const QVariant& value);
537
538 // Inject the per-context (window-rule) gap-override provider — forwarded to
539 // PerScreenConfigResolver. The daemon supplies the screen's current-context
540 // gap overrides so tiled windows honour context gap rules like snapping does.
541 void setContextGapProvider(std::function<QVariantMap(const QString& screenId)> provider);
542
543 // Mark the active (screen, desktop, activity) state's split ratio / master
544 // count as user-tuned so propagateGlobalSplitRatio/MasterCount leaves it
545 // alone — the adjustment stays local to that desktop instead of bleeding into
546 // the global config. Called by NavigationController after a shortcut/resize
547 // adjustment in the no-per-screen-override case.
548 void noteSplitRatioUserTuned(const QString& screenId);
549 void noteMasterCountUserTuned(const QString& screenId);
550
551 // Effective per-screen values — forwarded to PerScreenConfigResolver
552 int effectiveInnerGap(const QString& screenId) const;
553 int effectiveOuterGap(const QString& screenId) const;
554 ::PhosphorLayout::EdgeGaps effectiveOuterGaps(const QString& screenId) const;
555 bool effectiveSmartGaps(const QString& screenId) const;
556 bool effectiveRespectMinimumSize(const QString& screenId) const;
557 int effectiveMaxWindows(const QString& screenId) const;
558 qreal effectiveSplitRatioStep(const QString& screenId) const override;
559 int runtimeMaxWindows() const override;
560 QString effectiveAlgorithmId(const QString& screenId) const;
561 PhosphorTiles::TilingAlgorithm* effectiveAlgorithm(const QString& screenId) const;
562
563 // ═══════════════════════════════════════════════════════════════════════════
564 // Manual tiling operations
565 // ═══════════════════════════════════════════════════════════════════════════
566
567 void setInnerGap(int gap);
568 void setOuterGap(int gap);
569 void setSmartGaps(bool enabled);
570 void setFocusNewWindows(bool enabled);
579 Q_INVOKABLE void retile(const QString& screenId = QString()) override;
580
587 Q_INVOKABLE void swapWindows(const QString& windowId1, const QString& windowId2);
588
597 Q_INVOKABLE void promoteToMaster(const QString& windowId);
598
606 Q_INVOKABLE void demoteFromMaster(const QString& windowId);
607
614 Q_INVOKABLE void swapFocusedWithMaster() override;
615
616 // ═══════════════════════════════════════════════════════════════════════════
617 // Focus/window cycling
618 // ═══════════════════════════════════════════════════════════════════════════
619
625 Q_INVOKABLE void focusNext();
626
632 Q_INVOKABLE void focusPrevious();
633
639 Q_INVOKABLE void focusMaster() override;
640
649 void setFocusedWindow(const QString& windowId);
650
660 void setActiveScreenHint(const QString& screenId) override;
661
662 // ═══════════════════════════════════════════════════════════════════════════
663 // Split ratio adjustment
664 // ═══════════════════════════════════════════════════════════════════════════
665
673 Q_INVOKABLE void increaseMasterRatio(qreal delta = 0.05) override;
674
682 Q_INVOKABLE void decreaseMasterRatio(qreal delta = 0.05) override;
683
692 void setGlobalSplitRatio(qreal ratio);
693
702 void setGlobalMasterCount(int count);
703
704 // ═══════════════════════════════════════════════════════════════════════════
705 // Master count adjustment
706 // ═══════════════════════════════════════════════════════════════════════════
707
711 Q_INVOKABLE void increaseMasterCount() override;
712
716 Q_INVOKABLE void decreaseMasterCount() override;
717
718 // ═══════════════════════════════════════════════════════════════════════════
719 // Window rotation and floating (context-aware shortcuts support)
720 // ═══════════════════════════════════════════════════════════════════════════
721
731 Q_INVOKABLE void rotateWindowOrder(bool clockwise = true);
732
742 Q_INVOKABLE void toggleFocusedWindowFloat();
743
755 Q_INVOKABLE void toggleWindowFloat(const QString& windowId, const QString& screenId) override;
756
767 Q_INVOKABLE void swapFocusedInDirection(const QString& direction, const QString& action = QStringLiteral("move"));
768
777 Q_INVOKABLE void focusInDirection(const QString& direction, const QString& action = QStringLiteral("focus"));
778
786 Q_INVOKABLE void moveFocusedToPosition(int position);
787
788 // ═══════════════════════════════════════════════════════════════════════════
789 // IPlacementEngine — navigation overrides
790 //
791 // Each override absorbs what AutotileNavigationAdapter did: translate
792 // the user-intent-shaped IPlacementEngine call into the existing
793 // concrete AutotileEngine method with the right parameters.
794 // ═══════════════════════════════════════════════════════════════════════════
795
796 void focusInDirection(const QString& direction, const PhosphorEngine::NavigationContext& ctx) override;
797 void moveFocusedInDirection(const QString& direction, const PhosphorEngine::NavigationContext& ctx) override;
798 void swapFocusedInDirection(const QString& direction, const PhosphorEngine::NavigationContext& ctx) override;
799 void moveFocusedToPosition(int position, const PhosphorEngine::NavigationContext& ctx) override;
800 void rotateWindows(bool clockwise, const PhosphorEngine::NavigationContext& ctx) override;
801
806 QString entryWindowForCrossing(const QString& screenId, const QString& direction) const;
811 int windowOrderIndexForWindow(const QString& screenId, const QString& windowId) const;
814 std::optional<PhosphorEngine::WindowPlacement> capturePlacement(const QString& windowId) const override;
817 void cycleFocus(bool forward, const PhosphorEngine::NavigationContext& ctx) override;
820
821 // Autotile-specific navigation. Callable directly on the concrete
822 // AutotileEngine pointer from internal callers.
823 void swapInDirection(const QString& direction, const QString& action);
824 void rotateWindows(bool clockwise, const QString& screenId);
825 void moveToPosition(const QString& windowId, int position, const QString& screenId);
826
837 Q_INVOKABLE void setWindowFloat(const QString& windowId, bool shouldFloat) override;
838
842 Q_INVOKABLE void floatWindow(const QString& windowId);
843
847 Q_INVOKABLE void unfloatWindow(const QString& windowId);
848
849 // ═══════════════════════════════════════════════════════════════════════════
850 // PhosphorZones::Zone-ordered window transitions (snapping ↔ autotile)
851 // ═══════════════════════════════════════════════════════════════════════════
852
866 void setInitialWindowOrder(const QString& screenId, const QStringList& windowIds) override;
867
878 QStringList tiledWindowOrder(const QString& screenId) const;
879
880 // ═══════════════════════════════════════════════════════════════════════════
881 // Window event handlers (public API for external notification)
882 // ═══════════════════════════════════════════════════════════════════════════
883
895 using IPlacementEngine::windowOpened;
896 void windowOpened(const QString& windowId, const QString& screenId, int minWidth, int minHeight) override;
897
908 void windowMinSizeUpdated(const QString& windowId, int minWidth, int minHeight);
909
918 void windowClosed(const QString& windowId) override;
919
929 void windowFocused(const QString& windowId, const QString& screenId) override;
930
931 // ═══════════════════════════════════════════════════════════════════════════
932 // Retile helpers (public — used by extracted classes)
933 // ═══════════════════════════════════════════════════════════════════════════
934
942 void scheduleRetileForScreen(const QString& screenId) override;
943
944 // ═══════════════════════════════════════════════════════════════════════════
945 // Drag-insert preview (trigger-held window drag reorders autotile stack)
946 // ═══════════════════════════════════════════════════════════════════════════
947
958 bool beginDragInsertPreview(const QString& windowId, const QString& screenId) override;
959
967 void updateDragInsertPreview(int insertIndex) override;
968
976 void commitDragInsertPreview() override;
977
981 void cancelDragInsertPreview() override;
982
1001 int computeDragInsertIndexAtPoint(const QString& screenId, const QPoint& cursorPos) const override;
1002
1006 bool hasDragInsertPreview() const override
1007 {
1008 return m_dragInsertPreview.has_value();
1009 }
1010
1015 {
1016 return m_dragInsertPreview ? m_dragInsertPreview->windowId : QString();
1017 }
1018
1022 QString dragInsertPreviewScreenId() const override
1023 {
1024 return m_dragInsertPreview ? m_dragInsertPreview->targetScreenId : QString();
1025 }
1026
1036 void retileAfterOperation(const QString& screenId, bool operationSucceeded);
1037
1038Q_SIGNALS:
1043 void enabledChanged(bool enabled);
1044
1057 void autotileScreensChanged(const QStringList& screenIds, bool isDesktopSwitch);
1058
1059 // algorithmChanged(const QString&) — inherited from PlacementEngineBase.
1060 // placementChanged(const QString&) — inherited from PlacementEngineBase.
1061 // Replaces the former tilingChanged signal; all internal emitters now
1062 // emit placementChanged, and callers connect to the base-class signal.
1063 // windowsReleased(const QStringList&, const QSet<QString>&) — inherited
1064 // from PlacementEngineBase. Replaces windowsReleasedFromTiling.
1065
1077 // windowFloatingStateSynced and windowsBatchFloated are inherited from
1078 // PlacementEngineBase. Autotile-specific documentation: windowFloatingStateSynced
1079 // is emitted when the engine's TilingState::isFloating diverges from WTS's view
1080 // (e.g. a newly-inserted window carries stale snap-mode float state). The
1081 // downstream handler updates WTS bookkeeping without geometry restore.
1082 // windowsBatchFloated is emitted when overflow windows are batch-floated
1083 // during applyTiling; the daemon handler updates WTS state directly.
1084
1093 void windowsTiled(const QString& tileRequestsJson);
1094
1095public:
1096 // ═══════════════════════════════════════════════════════════════════════════
1097 // Autotile-float origin tracking (ephemeral, not persisted)
1098 // ═══════════════════════════════════════════════════════════════════════════
1099
1100 void markAutotileFloated(const QString& rawWindowId);
1101 void clearAutotileFloated(const QString& rawWindowId);
1102 bool isAutotileFloated(const QString& rawWindowId) const;
1103
1104 int pruneStaleWindows(const QSet<QString>& aliveWindowIds) override;
1105
1106private Q_SLOTS:
1107 void onWindowZoneChanged(const QString& windowId, const QString& zoneId);
1108 void onWindowAdded(const QString& windowId);
1109 void onWindowRemoved(const QString& windowId);
1110 void onWindowFocused(const QString& windowId);
1111 void onScreenGeometryChanged(const QString& screenId);
1112 void onLayoutChanged(PhosphorZones::Layout* layout);
1113
1114private:
1115 void connectSignals();
1116 bool insertWindow(const QString& windowId, const QString& screenId);
1117 // Passive float-state sync after insertWindow() places a window: notify the
1118 // daemon it opened floating (matched Float rule / restored saved float), or
1119 // clear a stale WTS float when it was placed tiled. Shared by onWindowAdded
1120 // and backfillWindows so the two cannot diverge.
1121 void emitInsertFloatStateSync(const QString& windowId, const QString& screenId);
1125 void insertWindowByConfigOrder(PhosphorTiles::TilingState* state, const QString& windowId);
1126 void removeWindow(const QString& windowId);
1127
1133 QString removeTrackedWindowNoRetile(const QString& windowId);
1134
1139 void dropClosedWindowFromDragPreview(const QString& windowId);
1140 bool storeWindowMinSize(const QString& windowId, int minWidth, int minHeight);
1141 bool recalculateLayout(const QString& screenId);
1142 void applyTiling(const QString& screenId);
1143
1153 bool applyTreeResizeReflow(PhosphorTiles::TilingState* state, const QString& windowId, const QRect& oldFrame,
1154 const QRect& newFrame, const QString& screenId);
1155 bool shouldTileWindow(const QString& windowId) const;
1156 QString screenForWindow(const QString& windowId) const;
1157 QRect screenGeometry(const QString& screenId) const;
1158
1162 bool isKnownScreen(const QString& screenId) const;
1163
1181 void releaseScreenStateForTeardown(const QString& screenId, PhosphorTiles::TilingState* state,
1182 QStringList& releasedWindows, bool drainOverflow = true);
1183
1194 void migrateWindowBetweenKeys(const QString& windowId, const PhosphorEngine::TilingStateKey& oldKey,
1195 const QString& newScreenId);
1196
1207 void revalidateWindowContext(const QString& windowId, const QString& screenId);
1208
1217 PhosphorEngine::TilingStateKey currentKeyForScreen(const QString& screenId) const
1218 {
1219 // Precedence (highest first):
1220 // 1. sticky-pin override (m_screenDesktopOverride) — a CORRECTNESS
1221 // constraint: sticky on-all-desktops windows must keep their state on
1222 // the desktop where they live, so the pin must win;
1223 // 2. per-output virtual desktop (m_screenCurrentDesktop, Plasma 6.7) —
1224 // the normal per-screen input;
1225 // 3. the global current desktop (m_currentDesktop) — fallback.
1226 int desktop = m_currentDesktop;
1227 if (auto perOut = m_screenCurrentDesktop.constFind(screenId); perOut != m_screenCurrentDesktop.constEnd()) {
1228 desktop = perOut.value();
1229 }
1230 if (auto pin = m_screenDesktopOverride.constFind(screenId); pin != m_screenDesktopOverride.constEnd()) {
1231 desktop = pin.value();
1232 }
1233 return PhosphorEngine::TilingStateKey{screenId, desktop, m_currentActivity};
1234 }
1235
1244
1252 void resetMaxWindowsForAlgorithmSwitch(PhosphorTiles::TilingAlgorithm* oldAlgo,
1254
1258 void propagateGlobalSplitRatio();
1259
1263 void propagateGlobalMasterCount();
1264
1276 void backfillWindows();
1277
1290 void retileScreen(const QString& screenId);
1291
1301 void scheduleRetileRetry(const QString& screenId);
1302
1306 void processRetileRetries();
1307
1308 // ═══════════════════════════════════════════════════════════════════════════════
1309 // Helper Methods
1310 // ═══════════════════════════════════════════════════════════════════════════════
1311
1322 bool cleanupPendingOrderIfResolved(const QString& screenId);
1323
1330 bool warnIfEmptyWindowId(const QString& windowId, const char* operation) const;
1331
1347 QString canonicalizeWindowId(const QString& rawWindowId);
1348
1355 void cleanupCanonical(const QString& anyWindowId);
1356
1364 QString canonicalizeForLookup(const QString& rawWindowId) const;
1365
1378 QString currentAppIdFor(const QString& anyWindowId) const;
1379
1385 void performToggleFloat(PhosphorTiles::TilingState* state, const QString& windowId, const QString& screenId);
1386
1396 PhosphorTiles::TilingState* stateForWindow(const QString& windowId, QString* outScreenId = nullptr);
1397
1398 QSet<QString> m_autotileFloatedWindows;
1399
1400 PhosphorZones::LayoutRegistry* m_layoutManager = nullptr;
1401 PhosphorEngine::IWindowTrackingService* m_windowTracker = nullptr;
1402 PhosphorScreens::ScreenManager* m_screenManager = nullptr;
1406 PhosphorEngine::ICrossSurfaceResolver* m_crossSurfaceResolver = nullptr;
1407 PhosphorEngine::IWindowRegistry* m_windowRegistry = nullptr;
1408 PhosphorTiles::ITileAlgorithmRegistry* m_algorithmRegistry = nullptr;
1409 std::unique_ptr<AutotileConfig> m_config;
1410 std::unique_ptr<PerScreenConfigResolver> m_configResolver;
1411 std::unique_ptr<NavigationController> m_navigation;
1412 QTimer m_writeBackGuardTimer;
1413 QTimer m_settingsRetileTimer;
1414
1415 // Persistence delegates (KConfig stays in WTA layer)
1416 std::function<void()> m_persistSaveFn;
1417 std::function<void()> m_persistLoadFn;
1418
1419 // Floated-position-restore gate. Empty until the daemon wires it; while empty
1420 // the engine always re-applies a floated window's recorded position (historical
1421 // behaviour). See RestorePositionPredicate doc above.
1422 RestorePositionPredicate m_restorePositionPredicate{};
1423
1424 // Rule-driven open-floating gate. Empty until the daemon wires it; while empty
1425 // no window is rule-floated. See FloatPredicate doc above.
1426 FloatPredicate m_floatPredicate{};
1427
1428 QSet<QString> m_autotileScreens;
1429 QString m_algorithmId;
1430 bool m_algorithmEverSet = false;
1431 QString m_activeScreen; // Last-focused screen (updated by onWindowFocused)
1432 QHash<PhosphorEngine::TilingStateKey, PhosphorTiles::TilingState*> m_screenStates; // Owned via Qt parent (this)
1433
1434 // Screen+desktop states whose split ratio / master count the user has
1435 // explicitly tuned (keyboard shortcut or interactive resize). propagateGlobal*
1436 // skips these so a per-desktop tweak survives a settings refresh and is never
1437 // written into the global config — keeping the adjustment local to that
1438 // (screen, desktop, activity). Cleared on an algorithm switch and when the
1439 // user changes the corresponding global value in settings. This is
1440 // within-session state only: it is not persisted, so the per-desktop tweak
1441 // does not survive a daemon restart (neither does the value it guards —
1442 // autotile persistence is per-window, not per-desktop ratio/count).
1443 QSet<PhosphorEngine::TilingStateKey> m_userTunedSplitRatio;
1444 QSet<PhosphorEngine::TilingStateKey> m_userTunedMasterCount;
1445
1446 QHash<QString, PhosphorEngine::TilingStateKey> m_windowToStateKey; // windowId -> owning state key
1447 QHash<QString, QSize> m_windowMinSizes; // windowId -> minimum size from KWin
1448
1449 // Instance id → first-seen canonical windowId.
1450 //
1451 // Fallback for when no shared WindowRegistry is attached (unit tests).
1452 // With a registry, canonicalization delegates to it instead. Production
1453 // daemons always take the registry path, so this map stays empty.
1454 //
1455 // The canonical form is the FIRST windowId string we saw for a given
1456 // instance id. Subsequent arrivals with a mutated appId (Electron/CEF
1457 // apps that swap WM_CLASS mid-session) resolve back to that canonical
1458 // form so every map/PhosphorTiles::TilingState key in the engine stays consistent.
1459 QHash<QString, QString> m_canonicalByInstance;
1460
1461 // Current desktop/activity context — used by tilingStateForScreen() to construct
1462 // TilingStateKey. Updated by setCurrentDesktop()/setCurrentActivity() BEFORE
1463 // updateAutotileScreens() runs on desktop/activity switch.
1464 int m_currentDesktop = 1;
1465 QString m_currentActivity;
1466 bool m_isDesktopContextSwitch = false;
1472 bool m_desktopContextEverSet = false;
1477 bool m_activityContextEverSet = false;
1478
1479 // Per-screen desktop override for sticky screens. When the KWin script
1480 // "virtualdesktopsonlyonprimary" pins all secondary-screen windows to all
1481 // desktops, the TilingStateKey desktop dimension becomes meaningless for
1482 // those screens. This map pins such screens to their original desktop so
1483 // currentKeyForScreen() returns the key of the existing PhosphorTiles::TilingState rather
1484 // than a new (empty) key after a desktop switch.
1485 QHash<QString, int> m_screenDesktopOverride;
1486
1487 // Per-screen current virtual desktop under Plasma 6.7 "switch desktops
1488 // independently for each screen" (#648). Fed by setCurrentDesktopForScreen
1489 // from the daemon's per-output desktop reports. Distinct from
1490 // m_screenDesktopOverride (the sticky-pin map): the sticky pin is a
1491 // correctness constraint and wins in currentKeyForScreen; this is the normal
1492 // per-screen input. Empty when per-output desktops aren't in use, so every
1493 // screen falls back to m_currentDesktop.
1494 QHash<QString, int> m_screenCurrentDesktop;
1495
1496 // Pre-seeded window order for snapping → autotile transitions.
1497 // Keyed by stable EDID-based screen ID (PhosphorScreens::ScreenIdentity::identifierFor).
1498 // Consumed by insertWindow() as windows arrive; also cleaned up by
1499 // removeWindow() if a pre-seeded window closes before arriving.
1500 QHash<QString, QStringList> m_pendingInitialOrders;
1501 QHash<QString, uint64_t> m_pendingOrderGeneration;
1502 // Screens whose pendingInitialOrders entry is "strict" — saved order
1503 // wins even when arrival order differs. Set by setInitialWindowOrder
1504 // (mode transition: the daemon intentionally pre-computed an order from
1505 // the previous mode's zones, and that order MUST be preserved).
1506 // Cleared after the order is fully consumed. Entries seeded by
1507 // setInitialWindowOrder (mode transition) are the strict ones; advisory
1508 // entries reconstructed per-window from the placement store are NOT in this
1509 // set — for those the saved position is honored only when it appends at the
1510 // current tail, otherwise insertPosition takes over. This is the behaviour
1511 // users expect from their "After existing" / "After focused" / "As main
1512 // window" preference for new windows.
1513 QSet<QString> m_strictInitialOrderScreens;
1514
1515 // Per-screen overflow tracking with O(1) reverse-index lookups.
1516 OverflowManager m_overflow;
1517
1518 bool m_retiling = false;
1519
1520 // Queued-connection retile coalescing: windowOpened D-Bus calls arriving in
1521 // the same event loop pass are coalesced into a single retile per screen.
1522 // Uses QMetaObject::invokeMethod(Qt::QueuedConnection) which fires after
1523 // all currently-pending events are processed — no fixed delay needed.
1524 QSet<QString> m_pendingRetileScreens;
1525 bool m_retilePending = false;
1526
1527 // Bounded retry for transient screen geometry failures.
1528 // When QScreen is temporarily unavailable (e.g. during Wayland desktop switch),
1529 // recalculateLayout cannot compute zone geometry. Rather than silently dropping
1530 // the retile (leaving stale zones), we retry after a short interval.
1531 // Per-screen retry counts prevent infinite loops; cleared on success or screen removal.
1532 static constexpr int MaxRetileRetries = 3;
1533 static constexpr int RetileRetryIntervalMs = 150;
1534 QTimer m_retileRetryTimer;
1535 QSet<QString> m_retileRetryScreens;
1536 QHash<QString, int> m_retileRetryCount;
1537
1538 // Deferred focus: set by onWindowAdded, emitted after applyTiling so the
1539 // focus request arrives at KWin AFTER windowsTiled (whose onComplete raises
1540 // windows in tiling order). Without this, the raise loop buries the new window.
1541 QString m_pendingFocusWindowId;
1542
1543 // Drag-insert preview state. When set, applyTiling() filters the dragged
1544 // window out of the windowsTiled batch so the KWin interactive move isn't
1545 // fought, while the remaining windows animate into their new positions.
1546 // Supports three entry modes (captured at begin() time for cancel restoration):
1547 // - Same-screen reorder: window was already tiled/floating on targetScreenId
1548 // - Cross-screen adoption: window was tracked on a different autotile screen
1549 // - Fresh adoption: window was not tracked by the engine at all
1550 struct DragInsertPreview
1551 {
1552 QString windowId;
1553 QString targetScreenId;
1554 int lastInsertIndex = -1;
1555
1556 // Prior-state restoration info (used on cancel)
1557 bool hadPriorState = false; // True if m_windowToStateKey contained windowId at begin
1559 priorKey; // Key of the prior PhosphorTiles::TilingState (meaningful iff hadPriorState)
1560 int priorRawIndex = -1; // Raw index in priorState->windowOrder() at begin
1561 bool priorFloating = false; // Prior floating flag in priorState
1562 bool priorSameScreen = false; // priorKey == currentKeyForScreen(targetScreenId)
1563
1564 // Eviction info (used when the target stack is already at maxWindows
1565 // and the dragged window is a new member). The last tiled neighbour
1566 // is floated to make room; on cancel the eviction is undone, on
1567 // commit the evicted window is sent through the batch-float path so
1568 // its pre-tile geometry is restored.
1569 QString evictedWindowId;
1570 };
1571 std::optional<DragInsertPreview> m_dragInsertPreview;
1572
1579 void processPendingRetiles();
1580};
1581
1582} // namespace PhosphorTileEngine
Definition IAutotileSettings.h:15
Resolves the neighbouring surface — output or virtual desktop — in a direction, for cross-surface win...
Definition ICrossSurfaceResolver.h:21
Definition IWindowRegistry.h:12
Definition IWindowTrackingService.h:34
Abstract base class for placement engines.
Definition PlacementEngineBase.h:30
Centralized screen-topology service.
Definition Manager.h:75
Core engine for automatic window tiling.
Definition AutotileEngine.h:65
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:202
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.
QString dragInsertPreviewWindowId() const
Get the window ID of the active drag-insert preview, or empty.
Definition AutotileEngine.h:1014
void clearModeSpecificFloatMarker(const QString &windowId) override
Definition AutotileEngine.h:218
void setFocusNewWindows(bool enabled)
QString algorithmId() const override
Definition AutotileEngine.h:226
void retileAfterOperation(const QString &screenId, bool operationSucceeded)
Helper to retile a screen after a window operation.
void setRestorePositionPredicate(RestorePositionPredicate predicate)
Inject the floated-position-restore gate.
Definition AutotileEngine.h:469
std::function< bool(const QString &windowId)> FloatPredicate
Predicate deciding whether an opening window should start FLOATING because a "Float this app" window ...
Definition AutotileEngine.h:482
void increaseMasterCount() override
Increase the number of master windows.
void setGlobalSplitRatio(qreal ratio)
Set master ratio globally (config + all per-screen states)
PhosphorEngine::IAutotileSettings * autotileSettings() const
void setFloatPredicate(FloatPredicate predicate)
Definition AutotileEngine.h:484
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.
bool isWindowTiled(const QString &rawWindowId) const override
Check if a window is currently tiled (tracked AND not floating).
QString dragInsertPreviewScreenId() const override
Get the target screen ID of the active drag-insert preview, or empty.
Definition AutotileEngine.h:1022
void moveFocusedToPosition(int position)
Move the focused window to a specific position in the tiling order.
void markAutotileFloated(const QString &rawWindowId)
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.
bool isActiveOnScreen(const QString &screenId) const override
Whether this engine is active on the given screen.
void setCrossSurfaceResolver(PhosphorEngine::ICrossSurfaceResolver *resolver) override
Inject the cross-surface resolver (neighbouring output / desktop lookup) used by directional navigati...
Definition AutotileEngine.h:300
int currentDesktop() const noexcept
Get the current virtual desktop tracked by the engine.
Definition AutotileEngine.h:351
void windowOpened(const QString &windowId, const QString &screenId, int minWidth, int minHeight) override
A new window appeared on this engine's screen.
int windowOrderIndexForWindow(const QString &screenId, const QString &windowId) const
The RAW window-order index of windowId on screenId (current desktop; counts floats,...
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.
void scheduleRetileForScreen(const QString &screenId) override
Schedule a deferred retile for a screen.
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
void setCurrentDesktopForScreen(const QString &screenId, int desktop) override
Set a single screen's current virtual desktop (Plasma 6.7 per-output virtual desktops,...
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:1006
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:210
bool isAutotileFloated(const QString &rawWindowId) const
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 noteMasterCountUserTuned(const QString &screenId)
std::function< bool(const QString &windowId)> RestorePositionPredicate
Predicate consulted on reopen to decide whether a FLOATED (untiled) window should have its previous g...
Definition AutotileEngine.h:465
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)
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:222
void updatePerScreenOverride(const QString &screenId, const QString &key, const QVariant &value)
int pruneStaleWindows(const QSet< QString > &aliveWindowIds) override
Drop any per-engine bookkeeping for windows not in aliveWindowIds.
void setActiveScreens(const QSet< QString > &screens) override
Definition AutotileEngine.h:206
QString entryWindowForCrossing(const QString &screenId, const QString &direction) const
Cross-mode swap support (queried by the daemon when THIS engine is the target): the tiled window at s...
bool hasPerScreenOverride(const QString &screenId, const QString &key) const
void focusMaster() override
Focus the master window.
bool isWindowFloatingInAutotile(const QString &windowId) const
Authoritative per-window autotile float state.
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
void setContextGapProvider(std::function< QVariantMap(const QString &screenId)> provider)
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)
QStringList allFloatingWindows() const
All windows currently floating in autotile across every tracked state.
void focusInDirection(const QString &direction, const PhosphorEngine::NavigationContext &ctx) override
Move keyboard focus to the adjacent window.
void clearCurrentDesktopForScreen(const QString &screenId) override
Drop a screen's per-output desktop, reverting it to the global m_currentDesktop.
PhosphorTiles::TilingAlgorithm * effectiveAlgorithm(const QString &screenId) const
const QString & currentActivity() const noexcept
Get the current activity tracked by the engine.
Definition AutotileEngine.h:359
void noteSplitRatioUserTuned(const QString &screenId)
int effectiveMaxWindows(const QString &screenId) const
void onWindowResized(const QString &rawWindowId, const QRect &oldFrame, const QRect &newFrame, const QString &screenId) override
Reflow neighbors after a window was interactively resized.
void retile(const QString &screenId=QString()) override
Force retiling of windows.
bool isModeSpecificFloated(const QString &windowId) const override
Definition AutotileEngine.h:214
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:490
void setWindowRegistry(QObject *registry) override
Wire up the shared WindowRegistry.
void reapplyManagedWindowAppearance() override
Re-drive the compositor's per-window appearance (border, hidden title bar) for every window this engi...
void clearAutotileFloated(const QString &rawWindowId)
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:496
const QSet< QString > & autotileScreens() const
Get the set of screens currently using autotile.
Definition AutotileEngine.h:239
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:230
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.
bool isEnabled() const noexcept override
Check if any screen has autotile enabled.
int runtimeMaxWindows() const override
Runtime max-windows limit.
std::optional< PhosphorEngine::WindowPlacement > capturePlacement(const QString &windowId) const override
Report windowId's CURRENT placement for persistence, or nullopt if this engine does not manage it.
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:248
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:34
Resolves per-screen configuration overrides for autotiling.
Definition PerScreenConfigResolver.h:40
Abstract contract for a tiling-algorithm registry.
Definition ITileAlgorithmRegistry.h:41
Abstract base class for tiling algorithms.
Definition TilingAlgorithm.h:51
Tracks tiling state for a single screen.
Definition TilingState.h:40
Manual zone-layout registry + per-context assignment store.
Definition LayoutRegistry.h:78
Represents a collection of zones that form a layout.
Definition Layout.h:40
Definition EngineTypes.h:13
constexpr QLatin1String LayoutRegistry("org.plasmazones.LayoutRegistry")
Definition IWindowTrackingService.h:27
Definition AutotileConfig.h:13
Definition AutotileEngine.h:46
Definition IWindowTrackingService.h:23
Context for a cross-engine window handoff.
Definition IPlacementEngine.h:385
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:80