Fix cross-monitor drag/drop tiling issue with graphics tablet on dual monitors
- Dominant language
- TypeScript
- Stars
- 2k
- Forks
- 116
- PR merge metrics
- No merged PRs in 30d
Description
Hi Domenico, good news!
__Graphics tablet behavior is now fully supported in dual-monitor setups.__
With AI assistance, I identified and fixed a cross-monitor drag/drop issue that occurred when a window was dragged directly from one monitor to another, by improving drop finalization in `tilingManager` (`_onWindowGrabEnd`).
- ✅ Tested on Fedora 43 (Wayland). X11 compatibility is expected but not yet verified.
- Tiling Shell version: 17.3
- GNOME version: 49
---
## Technical summary of the fix
- ✅ Prepared with AI assistance and validated through manual testing.
---
The change is localized to `src/components/tilingsystem/tilingManager.ts`, inside:
`private _onWindowGrabEnd(window: Meta.Window)`
No input pipeline was removed (mouse/touch/tablet handling in `_onWindowGrabBegin` remains intact). The fix adjusts drop/finalization logic for tiling decisions.
## Problem in the original flow
At grab end, the original implementation used early-captured flags (`hadValidSelection`, `wasSnapAssisting`, `wasEdgeTiling`) to decide whether to proceed. In multi-monitor drag scenarios (especially with tablet pointer behavior), this could produce stale/ambiguous state at release time, causing incorrect tile application on drop.
## What changed
1. **Gate condition refactor at drop time**
- Replaced reliance on `hadValidSelection` for early return with an explicit runtime activation check:
- `isTilingSystemActivated = _activationKeyStatus(global.get_pointer()[2], Settings.TILING_SYSTEM_ACTIVATION_KEY)`
- Continue only if one of these is active at release:
- tiling system active, or
- snap-assist active, or
- edge-tiling active.
2. **State evaluation ordering for edge-tiling**
- `wasEdgeTiling` is captured immediately before `abortEdgeTiling()` and used consistently for suggestion logic.
- Maximize-on-edge path now checks current edge-tiling state directly before abort.
3. **Cross-monitor safety guard added**
- New early return:
- `if (!this._isPointerInsideThisMonitor(window)) return;`
- Prevents a monitor-specific `TilingManager` from applying/drop-finalizing a tile when pointer release is on a different monitor.
4. **Suggestion visibility condition adjusted**
- Tiling suggestions are now gated by `isTilingSystemActivated` (instead of `hadValidSelection`) for the tiling-system branch.
## Behavioral impact
- Improves correctness when dragging windows across monitor boundaries.
- Prevents wrong-manager tile application on release.
- Makes drop behavior more deterministic for tablet + dual-monitor setups.
- Also improves mouse path stability (same pointer detection, better finalization decisions).
---
The Code Changes:
```diff
--- a/src/components/tilingsystem/tilingManager.ts
+++ b/src/components/tilingsystem/tilingManager.ts
@@ -804,13 +804,6 @@ private _onWindowGrabEnd(window: Meta.Window) {
this._signals.disconnect(window);
TouchPointer.get().reset();
- const hadValidSelection =
- this._selectedTilesPreview.showing &&
- this._selectedTilesPreview.innerWidth > 0 &&
- this._selectedTilesPreview.innerHeight > 0;
- const wasSnapAssisting = this._snapAssistingInfo.isSnapAssisting;
- const wasEdgeTiling = this._edgeTilingManager.isPerformingEdgeTiling();
-
const currentWs = window.get_workspace();
const tilingLayout = this._workspaceTilingLayout.get(currentWs);
if (tilingLayout) tilingLayout.close();
@@ -825,10 +818,18 @@ private _onWindowGrabEnd(window: Meta.Window) {
this._snapAssist.close(true);
this._lastCursorPos = null;
- if (!hadValidSelection && !wasSnapAssisting && !wasEdgeTiling)
+ const isTilingSystemActivated = this._activationKeyStatus(
+ global.get_pointer()[2],
+ Settings.TILING_SYSTEM_ACTIVATION_KEY,
+ );
+ if (
+ !isTilingSystemActivated &&
+ !this._snapAssistingInfo.isSnapAssisting &&
+ !this._edgeTilingManager.isPerformingEdgeTiling()
+ )
return;
- const wasSnapAssistingLayout = wasSnapAssisting
+ const wasSnapAssistingLayout = this._snapAssistingInfo.isSnapAssisting
? GlobalState.get().layouts.find(
(lay) => lay.id === this._snapAssistingInfo.layoutId,
)
@@ -837,10 +838,15 @@ private _onWindowGrabEnd(window: Meta.Window) {
// disable snap assistance
this._snapAssistingInfo.update(undefined);
- if (wasEdgeTiling && this._edgeTilingManager.needMaximize() && window.can_maximize())
+ if (
+ this._edgeTilingManager.isPerformingEdgeTiling() &&
+ this._edgeTilingManager.needMaximize() &&
+ window.can_maximize()
+ )
maximizeWindow(window);
// disable edge-tiling
+ const wasEdgeTiling = this._edgeTilingManager.isPerformingEdgeTiling();
this._edgeTilingManager.abortEdgeTiling();
const canShowTilingSuggestions =
@@ -848,9 +854,13 @@ private _onWindowGrabEnd(window: Meta.Window) {
Settings.ENABLE_SNAP_ASSISTANT_WINDOWS_SUGGESTIONS) ||
(wasEdgeTiling &&
Settings.ENABLE_SCREEN_EDGES_WINDOWS_SUGGESTIONS) ||
- (hadValidSelection &&
+ (isTilingSystemActivated &&
Settings.ENABLE_TILING_SYSTEM_WINDOWS_SUGGESTIONS);
+ // abort if the pointer is moving on another monitor: the user moved
+ // the window to another monitor not handled by this tiling manager
+ if (!this._isPointerInsideThisMonitor(window)) return;
+
// abort if there is an invalid selection
if (desiredWindowRect.width <= 0 || desiredWindowRect.height <= 0)
return;
```
I’m sharing this patch for your review, and I hope it can be useful for the project roadmap.
Thanks for your work on TilingShell.
Best regards,
Marinho P.da Silva
Contributor guide
Assessment
This issue has not been assessed yet.