From ed3e1a0ef3211bb66064613b7bb7e12e00ad11e6 Mon Sep 17 00:00:00 2001 From: Siddh Raman Pant <25429745+siddhpant@users.noreply.github.com> Date: Wed, 17 Jun 2026 16:19:04 +0530 Subject: [PATCH 1/7] src/auto_tiler: Sync smart_gapped on each tiling attempt. The first window is fully maximized, and then later when a second window is opened, we don't get border for the first window when it is in focus because after tiling the first window has stale value for smart_gapped, and thus is_single_max_screen() returns true, causing permitted in show_border() to be false. Let's set smart_gapped for each window in a tree each time we tile. Closes: #1758 Signed-off-by: Siddh Raman Pant <25429745+siddhpant@users.noreply.github.com> --- src/auto_tiler.ts | 46 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 8 deletions(-) diff --git a/src/auto_tiler.ts b/src/auto_tiler.ts index 2a5cc51f..848847fa 100644 --- a/src/auto_tiler.ts +++ b/src/auto_tiler.ts @@ -29,6 +29,43 @@ export class AutoTiler { this.attached = attached; } + private sync_smart_gapped(ext: Ext, fork: Fork) { + let toplevel = fork; + let parent = this.forest.parents.get(toplevel.entity); + + /* Walk upwards to get the actual top-level root from the fork. */ + while (parent) { + const next = this.forest.forks.get(parent); + if (!next) + break; + + toplevel = next; + parent = this.forest.parents.get(toplevel.entity); + } + + const smart_gapped = ( + toplevel.is_toplevel && + toplevel.smart_gapped && + toplevel.right === null + ); + + /* Set smart_gapped for each window. */ + for (const branch of this.forest.iter(toplevel.entity)) { + let entities: Entity[] = []; + + if (branch.inner.kind === NodeKind.WINDOW) + entities = [branch.inner.entity]; + else if (branch.inner.kind === NodeKind.STACK) + entities = branch.inner.entities; + + for (const entity of entities) { + const win = ext.windows.get(entity); + if (win) + win.smart_gapped = smart_gapped; + } + } + } + /** Swap window associations in the auto-tiler * * Call this when a window has swapped positions with another, so that we @@ -106,13 +143,6 @@ export class AutoTiler { rect.height -= ext.gap_outer * 2; } - if (fork.left.inner.kind === 2) { - const win = ext.windows.get(fork.left.inner.entity); - if (win) { - win.smart_gapped = fork.smart_gapped; - } - } - fork.area = fork.set_area(rect.clone()); fork.length_left = Math.round(fork.prev_ratio * fork.length()); this.tile(ext, fork, fork.area); @@ -132,7 +162,6 @@ export class AutoTiler { const [entity, fork] = this.forest.create_toplevel(win.entity, rect.clone(), workspace_id); this.forest.on_attach(entity, win.entity); fork.smart_gapped = smart_gaps; - win.smart_gapped = smart_gaps; this.tile(ext, fork, rect); } @@ -490,6 +519,7 @@ export class AutoTiler { } tile(ext: Ext, fork: Fork, area: Rectangle) { + this.sync_smart_gapped(ext, fork); this.forest.tile(ext, fork, area); } From cfb5ee123da903d4a4b5c1fcba1135cfd796c704 Mon Sep 17 00:00:00 2001 From: Siddh Raman Pant <25429745+siddhpant@users.noreply.github.com> Date: Wed, 17 Jun 2026 11:25:43 +0530 Subject: [PATCH 2/7] src/stack: Prevent duplicate signals on close button spam. It caused GNOME crash due to bug in mutter, which was fixed in v49. But regardless, we should not spam the close signal. Also people might be using versions before 49. Closes: #1794 Signed-off-by: Siddh Raman Pant <25429745+siddhpant@users.noreply.github.com> --- src/stack.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/stack.ts b/src/stack.ts index 60d553bb..5667d3e3 100644 --- a/src/stack.ts +++ b/src/stack.ts @@ -91,8 +91,14 @@ const TabButton = GObject.registerClass( }), ); - close_button.connect('clicked', () => { - window.meta.delete(global.get_current_time()); + this._running_close = false; + + close_button.connect('clicked', () => { + if (!this._running_close) { + this._running_close = true; + window.meta.delete(global.get_current_time()); + this._running_close = false; + } }); close_button.set_x_align(Clutter.ActorAlign.END); From 335d3f173bc3cc8ae0d598827110ffb103a88df5 Mon Sep 17 00:00:00 2001 From: Siddh Raman Pant <25429745+siddhpant@users.noreply.github.com> Date: Fri, 3 Jul 2026 16:09:48 +0530 Subject: [PATCH 3/7] src/config: Add exceptions for wl-clipboard. wl-clipboard invisible window (literally just a dot shows up) can cause the tiler to make it take an entire window worth of space, and it is supposed to be immediately closed so we see a tiling flicker. And also sometimes the window dangles (there is an open bug for it), so it takes up unnecessary space. This window is not supposed to be tiled, in fact it has skip-taskbar flag set. Let's add exceptions for it. Closes: #1819 Signed-off-by: Siddh Raman Pant <25429745+siddhpant@users.noreply.github.com> --- src/config.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/config.ts b/src/config.ts index 440d4ec0..9db10cf5 100644 --- a/src/config.ts +++ b/src/config.ts @@ -59,6 +59,7 @@ export const DEFAULT_FLOAT_RULES: Array = [ { class: 'zoom' }, { class: '^.*action=join.*$' }, { class: 'gjs' }, + { class: 'io.github.bugaevc.wl-clipboard' }, ]; export interface WindowRule { @@ -77,6 +78,7 @@ export const SKIPTASKBAR_EXCEPTIONS: Array = [ { class: 'Guake' }, { class: 'Com.github.amezin.ddterm' }, { class: 'plank' }, + { class: 'io.github.bugaevc.wl-clipboard' }, ]; export interface FloatRule { From 0402a4bef28dc6490f8b2b06928e51ef7e47832b Mon Sep 17 00:00:00 2001 From: Siddh Raman Pant <25429745+siddhpant@users.noreply.github.com> Date: Sat, 4 Jul 2026 16:48:11 +0530 Subject: [PATCH 4/7] src/config: Add exceptions for nautilus file preview. Nautilus file previews, like pressing space on a PDF file, is a temporary window and it can't zoom in, it is supposed to be overlaid. Let's add exception for it. Signed-off-by: Siddh Raman Pant <25429745+siddhpant@users.noreply.github.com> --- src/config.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/config.ts b/src/config.ts index 9db10cf5..29ba761c 100644 --- a/src/config.ts +++ b/src/config.ts @@ -60,6 +60,7 @@ export const DEFAULT_FLOAT_RULES: Array = [ { class: '^.*action=join.*$' }, { class: 'gjs' }, { class: 'io.github.bugaevc.wl-clipboard' }, + { class: 'org.gnome.NautilusPreviewer' }, ]; export interface WindowRule { @@ -79,6 +80,7 @@ export const SKIPTASKBAR_EXCEPTIONS: Array = [ { class: 'Com.github.amezin.ddterm' }, { class: 'plank' }, { class: 'io.github.bugaevc.wl-clipboard' }, + { class: 'org.gnome.NautilusPreviewer' }, ]; export interface FloatRule { From 0b9e00ce0ee7892127f3f37914f062d832010233 Mon Sep 17 00:00:00 2001 From: Siddh Raman Pant <25429745+siddhpant@users.noreply.github.com> Date: Wed, 17 Jun 2026 16:57:25 +0530 Subject: [PATCH 5/7] src/keybindings: Avoid re-registering window-management mode bindings. Entering window-management mode removed the normal bindings and re-added the management bindings, then did the inverse on exit. This leads to journalctl spam since we overwrite a keybinding, and mutter logs overwrite warnings. Register the window-management mode bindings once with it being inactive initially (Shell.ActionMode.NONE), and later enable it (Shell.ActionMode.NORMAL) when entering the management mode, and then disabling on exit. Closes: #1820 Signed-off-by: Siddh Raman Pant <25429745+siddhpant@users.noreply.github.com> --- src/extension.ts | 2 ++ src/keybindings.ts | 26 ++++++++++++++++++++++++-- src/tiling.ts | 17 +++++++++++++++-- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 503407f3..c7599b7d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2715,6 +2715,7 @@ export default class PopShellExtension extends Extension { } ext.keybindings.enable(ext.keybindings.global).enable(ext.keybindings.window_focus); + ext.tiler.enable_keybindings(ext); if (ext.settings.tile_by_default()) { ext.auto_tile_on(); @@ -2740,6 +2741,7 @@ export default class PopShellExtension extends Extension { layoutManager.removeChrome(ext.overlay); ext.keybindings.disable(ext.keybindings.global).disable(ext.keybindings.window_focus); + ext.tiler.disable_keybindings(ext); if (ext.auto_tiler) { ext.auto_tiler.destroy(ext); diff --git a/src/keybindings.ts b/src/keybindings.ts index 394ea4e6..69cb9752 100644 --- a/src/keybindings.ts +++ b/src/keybindings.ts @@ -8,6 +8,8 @@ export class Keybindings { global: Object; window_focus: Object; + private active: Set = new Set(); + private ext: Ext; constructor(ext: Ext) { @@ -62,15 +64,22 @@ export class Keybindings { }; } - enable(keybindings: any) { + enable(keybindings: any, modes: number = Shell.ActionMode.NORMAL) { for (const name in keybindings) { + if (this.active.has(name)) { + wm.allowKeybinding(name, modes); + continue; + } + wm.addKeybinding( name, this.ext.settings.ext, Meta.KeyBindingFlags.NONE, - Shell.ActionMode.NORMAL, + modes, keybindings[name], ); + + this.active.add(name); } return this; @@ -78,7 +87,20 @@ export class Keybindings { disable(keybindings: Object) { for (const name in keybindings) { + if (!this.active.has(name)) + continue; + wm.removeKeybinding(name); + this.active.delete(name); + } + + return this; + } + + allow(keybindings: Object, modes: number) { + for (const name in keybindings) { + if (this.active.has(name)) + wm.allowKeybinding(name, modes); } return this; diff --git a/src/tiling.ts b/src/tiling.ts index 8ea72562..7577f435 100644 --- a/src/tiling.ts +++ b/src/tiling.ts @@ -17,6 +17,7 @@ import { AutoTiler } from './auto_tiler.js'; import { Fork } from './fork.js'; import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; const { ShellWindow } = window; @@ -60,6 +61,14 @@ export class Tiler { }; } + enable_keybindings(ext: Ext) { + ext.keybindings.enable(this.keybindings, Shell.ActionMode.NONE); + } + + disable_keybindings(ext: Ext) { + ext.keybindings.disable(this.keybindings); + } + toggle_orientation(ext: Ext) { const window = ext.focus_window(); if (window && ext.auto_tiler) { @@ -763,7 +772,9 @@ export class Tiler { }); } - ext.keybindings.disable(ext.keybindings.window_focus).enable(this.keybindings); + ext.keybindings + .allow(ext.keybindings.window_focus, Shell.ActionMode.NONE) + .allow(this.keybindings, Shell.ActionMode.NORMAL); } } @@ -817,7 +828,9 @@ export class Tiler { ext.overlay.visible = false; // Disable tiling keybindings - ext.keybindings.disable(this.keybindings).enable(ext.keybindings.window_focus); + ext.keybindings + .allow(this.keybindings, Shell.ActionMode.NONE) + .allow(ext.keybindings.window_focus, Shell.ActionMode.NORMAL); } } From 08227c5acbca4fc64b3a655670cfa67b5599b1ad Mon Sep 17 00:00:00 2001 From: Siddh Raman Pant <25429745+siddhpant@users.noreply.github.com> Date: Wed, 17 Jun 2026 12:06:46 +0530 Subject: [PATCH 6/7] src: Fix (un)maximize usage for GNOME 49. The API had changed here: https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4415 Thus we had warnings being logged when in stacked mode. The commit which added support for GNOME 49 forgot to change that. Also, to be in line with the fixes commit, we remove the unmaxize(HORIZONTAL); unmaximize(VERTICAL); unmaximize(BOTH); code to checking for whether the window is maximized and then using BOTH (i.e. its replacement) directly. Closes: #1801 Fixes: d22d2ff05989 ("fix: update window maximization logic to use new properties and added gnome 49 shell version in metadata") Signed-off-by: Siddh Raman Pant <25429745+siddhpant@users.noreply.github.com> --- src/extension.ts | 14 ++++++-------- src/mod.d.ts | 8 +++++--- src/tiling.ts | 2 +- src/window.ts | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index c7599b7d..ed3c9664 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -909,10 +909,10 @@ export class Ext extends Ecs.System { ) { if (prev.rect().contains(win.rect())) { if (prev.is_maximized()) { - prev.meta.unmaximize(Meta.MaximizeFlags.BOTH); + prev.meta.unmaximize(); } } else if (prev.stack) { - prev.meta.unmaximize(Meta.MaximizeFlags.BOTH); + prev.meta.unmaximize(); this.auto_tiler.forest.stacks.get(prev.stack)?.restack(); } } @@ -1034,7 +1034,7 @@ export class Ext extends Ecs.System { compare.is_maximized() && win.entity[0] !== compare.entity[0] ) { - compare.meta.unmaximize(Meta.MaximizeFlags.BOTH); + compare.meta.unmaximize(); } } } @@ -1257,10 +1257,8 @@ export class Ext extends Ecs.System { } if (this.auto_tiler) { - if (this.is_floating(win)) { - win.meta.unmaximize(Meta.MaximizeFlags.HORIZONTAL); - win.meta.unmaximize(Meta.MaximizeFlags.VERTICAL); - win.meta.unmaximize(Meta.MaximizeFlags.BOTH); + if (this.is_floating(win) && win.is_maximized()) { + win.meta.unmaximize(); } this.register(Events.window_move(this, win, rect)); @@ -1268,7 +1266,7 @@ export class Ext extends Ecs.System { win.move(this, rect, () => { }); // if the resulting dimensions of rect == next if (rect.width == next_area.width && rect.height == next_area.height) { - win.meta.maximize(Meta.MaximizeFlags.BOTH); + win.meta.maximize(); } } } diff --git a/src/mod.d.ts b/src/mod.d.ts index e1ac012f..9b039dad 100644 --- a/src/mod.d.ts +++ b/src/mod.d.ts @@ -306,13 +306,15 @@ declare namespace Meta { is_skip_taskbar(): boolean; make_above(): void; make_fullscreen(): void; - maximize(flags: MaximizeFlags): void; + maximize(): void; + unmaximize(): void; + unminimize(): void; + set_maximize_flags(flags: MaximizeFlags): void; + set_unmaximize_flags(flags: MaximizeFlags): void; move_frame(user_op: boolean, x: number, y: number): void; move_resize_frame(user_op: boolean, x: number, y: number, w: number, h: number): boolean; raise(): void; skip_taskbar: boolean; - unmaximize(flags: any): void; - unminimize(): void; } interface WindowActor extends Clutter.Actor { diff --git a/src/tiling.ts b/src/tiling.ts index 7577f435..58f1dd0d 100644 --- a/src/tiling.ts +++ b/src/tiling.ts @@ -758,7 +758,7 @@ export class Tiler { this.window = win.entity; if (win.is_maximized()) { - win.meta.unmaximize(Meta.MaximizeFlags.BOTH); + win.meta.unmaximize(); } // Set overlay to match window diff --git a/src/window.ts b/src/window.ts index 2fbd2928..0adf2d5b 100644 --- a/src/window.ts +++ b/src/window.ts @@ -373,7 +373,7 @@ export class ShellWindow { if (actor) { if (this.is_maximized()) { - meta.unmaximize(Meta.MaximizeFlags.BOTH); + meta.unmaximize(); } actor.remove_all_transitions(); From e17ba31854cb9cfa63d4e3bcccef66a0f09d8f95 Mon Sep 17 00:00:00 2001 From: Siddh Raman Pant <25429745+siddhpant@users.noreply.github.com> Date: Tue, 7 Jul 2026 11:43:39 +0530 Subject: [PATCH 7/7] README: Add new branch. Signed-off-by: Siddh Raman Pant <25429745+siddhpant@users.noreply.github.com> --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a8d76ebd..4f2c6ef8 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,8 @@ Use the branch corresponding to your GNOME Shell version (`git checkout branch_n - **GNOME 3.36 through 41:** Use the `master_focal` branch. - **GNOME 42 through 44:** Use the `master_jammy` branch. - **GNOME 45:** Use the `master_mantic` branch. -- **GNOME 46+:** Use the `master_noble` branch. +- **GNOME 46 through 48:** Use the `master_noble` branch. +- **GNOME 49+:** Use the `master_resolute` branch. GNU Make and TypeScript are also required to build the project.