domferr / domferr/tilingshell

Fix cross-monitor drag/drop tiling issue with graphics tablet on dual monitors

Open
#507 3 comments 0 reactions 0 assignees View on GitHub
enhancement
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.