Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"lint:dist": "../GER/ego-lint ./dist",
"shexli": "make shexli",
"log": "journalctl -f -o cat /usr/bin/gnome-shell | grep o-tiling",
"local-install": "make install"
"local-install": "make install",
"test:decorations": "node tests/floating-decorations.test.mjs"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
Expand Down
35 changes: 31 additions & 4 deletions src/engine/stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as Ecs from '../core/ecs.js';
import * as a from '../core/arena.js';
import * as utils from '../utils/utils.js';
import * as log from '../utils/log.js';
import * as lib from '../utils/lib.js';
import { get_primary_monitor_index } from './fork.js';

const Arena = a.Arena;
Expand All @@ -14,6 +15,7 @@ import GObject from 'gi://GObject';
import St from 'gi://St';
import Meta from 'gi://Meta';
import Pango from 'gi://Pango';
import GLib from 'gi://GLib';

const ACTIVE_TAB = 'o-tiling-tab o-tiling-tab-active';
const INACTIVE_TAB = 'o-tiling-tab o-tiling-tab-inactive';
Expand Down Expand Up @@ -166,6 +168,7 @@ export class Stack {
private rect: Rectangular = { width: 0, height: 0, x: 0, y: 0 };

private restacker: SignalID = (global as any).display.connect('restacked', () => this.restack());
private _restack_id: number | null = null;

private tabs_destroy: SignalID;

Expand Down Expand Up @@ -423,6 +426,10 @@ export class Stack {

/** Disconnects this stack's signal, and destroys its widgets */
destroy() {
if (this._restack_id !== null) {
lib.later_remove(this._restack_id);
this._restack_id = null;
}
(global as any).display.disconnect(this.restacker);
this.active_disconnect();

Expand Down Expand Up @@ -538,10 +545,20 @@ export class Stack {
let idx = 0;
for (const c of this.tabs) {
if (Ecs.entity_eq(c.entity, entity)) {
const was_active = Ecs.entity_eq(this.active, entity);
if (was_active) this.active_disconnect();
this.remove_tab_component(c, idx);
if (this.active_id > idx) {
this.active_id -= 1;
}
if (was_active && this.tabs.length > 0) {
// A detached window may now float above this stack. Stop
// anchoring the tab bar/signals to it before the next reflow.
this.auto_activate();
this.prev_active = null;
this.prev_active_id = 0;
this.restack();
}
return idx;
}
idx += 1;
Expand Down Expand Up @@ -596,12 +613,11 @@ export class Stack {
}

const stack_parent = this.widgets.tabs.get_parent();
if (stack_parent) {
stack_parent.remove_child(this.widgets.tabs);
if (stack_parent !== parent) {
stack_parent?.remove_child(this.widgets.tabs);
parent.add_child(this.widgets.tabs);
}

parent.add_child(this.widgets.tabs);

// Reposition actors on the screen, being careful about not displaying over maximized windows
if (!window.meta.is_fullscreen() && !window.is_maximized() && !this.ext.maximized_on_active_display()) {
parent.set_child_above_sibling(this.widgets.tabs, actor);
Expand Down Expand Up @@ -637,6 +653,17 @@ export class Stack {

/** Repositions the stack, and hides all but the active window in the stack */
restack() {
// Mutter's restacked signal can precede the actor-order update. Reorder
// our tabs after compositor stack synchronization, not against stale siblings.
if (this._restack_id !== null) return;
this._restack_id = lib.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
this._restack_id = null;
if (!this.is_disposed()) this.restack_now();
return GLib.SOURCE_REMOVE;
});
}

private restack_now() {
this.on_grab(() => {
if (!this.widgets) return;

Expand Down
15 changes: 9 additions & 6 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1639,6 +1639,14 @@ export class Ext extends Ecs.System<ExtEvent> {

/** Triggered when a grab operation has been ended */
on_grab_end(meta: Meta.Window, op?: any, drop_cursor?: Rect.Rectangle) {
// Floating windows do not create a tiling grab_op, but still need cleanup.
const win = this.get_window(meta);
if (win?.grab) {
win.grab = false;
// The compositor has already applied a floating drag's final frame.
win.mark_border_settling(false);
}

// Guard: Skip if grab_op is null on a non-overview drag to prevent handling a double-fired drop.
if (this.grab_op === null && op !== undefined) {
return;
Expand All @@ -1649,12 +1657,6 @@ export class Ext extends Ecs.System<ExtEvent> {
drop_cursor = cursor_rect();
}

const win = this.get_window(meta);

if (win !== null) {
win.grab = false;
}

if (null === win || (!win.is_tilable(this) && !win.is_eligible_for_tiling(this))) {
this.unset_grab_op();
return;
Expand Down Expand Up @@ -2044,6 +2046,7 @@ export class Ext extends Ecs.System<ExtEvent> {
const win = this.get_window(meta);
if (win) {
win.grab = true;
win.hide_border();

if (win.is_tilable(this)) {
const entity = win.entity;
Expand Down
23 changes: 20 additions & 3 deletions src/window/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,10 @@ export class ShellWindow {

show_border() {
if (!this.border) return;
if (this.grab) {
this.hide_border();
return;
}

// Bail while the settle timer is active; mark_border_settling() will re-show once geometry is final.
if (this._border_settle_id !== null) return;
Expand All @@ -550,6 +554,9 @@ export class ShellWindow {
const overlay_all_active = this.ext.settings.active_hint_overlay_enabled() && this.ext.settings.active_hint_overlay_all_windows();

const permitted = () =>
!this.grab &&
this._border_settle_id === null &&
!this._border_pending_initial_tile &&
this.actor_exists() &&
this.ext.settings.active_hint() &&
(overlay_all_active || (this.ext.focus_window() == this && this.meta.appears_focused)) &&
Expand Down Expand Up @@ -585,15 +592,17 @@ export class ShellWindow {
}

/** Hides the border and defers show_border() until Mutter commits the post-tile frame rect. */
mark_border_settling() {
mark_border_settling(wait_for_geometry_change: boolean = true) {
// The initial tile pass is underway — the settle re-shows the hint at the final geometry.
this._border_pending_initial_tile = false;

this.hide_border();

// Capture the pre-tile rect so the settle can tell when the window has actually moved.
const rect = this.meta.get_frame_rect();
this._border_settle_pre_rect = { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
this._border_settle_pre_rect = wait_for_geometry_change
? { x: rect.x, y: rect.y, width: rect.width, height: rect.height }
: null;
this.arm_border_settle();
}

Expand Down Expand Up @@ -652,6 +661,10 @@ export class ShellWindow {

/** Sorts the window/always-top group with each window border based on the update state (NORMAL, RAISED, WORKSPACE_CHANGED). */
restack(_updateState: RESTACK_STATE = RESTACK_STATE.NORMAL, immediate: boolean = false) {
if (this.grab) {
this.hide_border();
return;
}
this.update_border_layout();
if (
this.meta.is_fullscreen() ||
Expand All @@ -669,6 +682,10 @@ export class ShellWindow {
}

if (!this.border) return GLib.SOURCE_REMOVE;
if (this.grab) {
this.hide_border();
return GLib.SOURCE_REMOVE;
}

if (!this.actor_exists()) return GLib.SOURCE_REMOVE;

Expand Down Expand Up @@ -1037,4 +1054,4 @@ function pointer_already_on_window(meta: Meta.Window): boolean {
const cursor = lib.cursor_rect();

return cursor.intersects(meta.get_frame_rect());
}
}
Loading