Skip to content
Merged
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
20 changes: 20 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1504,6 +1504,21 @@ export class Ext extends Ecs.System<ExtEvent> {
}
}

private reassert_focused_hint_stack() {
if (!this.settings.active_hint()) return;
if (this._bordered_entity === null) return;

const win = this.windows.get(this._bordered_entity);
if (!win || !win.border || !win.border.visible) return;
if (win.meta.is_fullscreen() || win.meta.minimized) return;

const actor = win.meta.get_compositor_private() as any;
const parent = actor?.get_parent();
if (!actor || !parent) return;

win.stack_border_above_window(actor, parent, win.border);
}

hide_all_borders(instant: boolean = false) {
for (const window of this.windows.values()) {
window.hide_border();
Expand Down Expand Up @@ -2824,6 +2839,11 @@ export class Ext extends Ecs.System<ExtEvent> {
this.register({ tag: 3, window });
});

// Non-grabbing popups are not raised above the hint; re-stack after ordering.
this.connect(display as any, 'restacked', () => {
this.reassert_focused_hint_stack();
});

// GNOME 40 removed the first argument of the callback
this.connect(display as any, 'grab-op-begin', (_display, win, op) => {
this.on_grab_start(win, op);
Expand Down
127 changes: 65 additions & 62 deletions src/window/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -680,81 +680,84 @@ export class ShellWindow {
if (!parent) return GLib.SOURCE_REMOVE;

this.update_border_layout();
this.stack_border_above_window(actor, parent, border);

if (border.get_parent() !== parent) {
if (border.get_parent()) {
border.get_parent()!.remove_child(border);
}
parent.add_child(border);
}
return GLib.SOURCE_REMOVE;
};

const old_id = this._restack_id;
this._restack_id = null;
if (old_id !== null) lib.later_remove(old_id);
if (immediate) {
action();
} else {
id = lib.later_add(Meta.LaterType.BEFORE_REDRAW, action);
this._restack_id = id;
}
}

parent.set_child_above_sibling(border, actor);
/** Places the border above its window, below always-on-top windows and managed popups. */
stack_border_above_window(actor: any, parent: any, border: any) {
if (border.get_parent() !== parent) {
if (border.get_parent()) {
border.get_parent()!.remove_child(border);
}
parent.add_child(border);
}

parent.set_child_above_sibling(border, actor);

for (const above_actor of (global as any).get_window_actors()) {
const meta = above_actor.get_meta_window();
if (!meta || !meta.is_above()) continue;
if (above_actor !== actor && above_actor.get_parent() === parent) {
parent.set_child_below_sibling(border, above_actor);
break;
}
for (const above_actor of (global as any).get_window_actors()) {
const meta = above_actor.get_meta_window();
if (!meta || !meta.is_above()) continue;
if (above_actor !== actor && above_actor.get_parent() === parent) {
parent.set_child_below_sibling(border, above_actor);
break;
}
}

const siblings: Array<any> = parent.get_children();
const candidates: Set<any> = new Set();

for (const window of this.ext.windows.values()) {
const trans_parent = window.meta.get_transient_for();
if (!trans_parent) continue;
const parent_actor = trans_parent.get_compositor_private() as any;
if (parent_actor !== actor) continue;
const window_actor = window.meta.get_compositor_private() as any;
if (window_actor && window_actor.get_parent() === parent) {
candidates.add(window_actor);
}
const siblings: Array<any> = parent.get_children();
const candidates: Set<any> = new Set();

for (const window of this.ext.windows.values()) {
const trans_parent = window.meta.get_transient_for();
if (!trans_parent) continue;
const parent_actor = trans_parent.get_compositor_private() as any;
if (parent_actor !== actor) continue;
const window_actor = window.meta.get_compositor_private() as any;
if (window_actor && window_actor.get_parent() === parent) {
candidates.add(window_actor);
}
}

for (const popup_actor of (global as any).get_window_actors()) {
const wtype = popup_actor.get_meta_window()?.get_window_type();
if (
wtype === Meta.WindowType.DROPDOWN_MENU ||
wtype === Meta.WindowType.POPUP_MENU ||
wtype === Meta.WindowType.COMBO ||
wtype === Meta.WindowType.TOOLTIP
) {
if (popup_actor.get_parent() === parent) {
candidates.add(popup_actor);
}
for (const popup_actor of (global as any).get_window_actors()) {
const wtype = popup_actor.get_meta_window()?.get_window_type();
if (
wtype === Meta.WindowType.DROPDOWN_MENU ||
wtype === Meta.WindowType.POPUP_MENU ||
wtype === Meta.WindowType.COMBO ||
wtype === Meta.WindowType.TOOLTIP
) {
if (popup_actor.get_parent() === parent) {
candidates.add(popup_actor);
}
}
}

if (candidates.size > 0) {
// Find the candidate with the lowest index among siblings (closest to bottom).
let lowest: any = null;
let lowest_idx = siblings.length;
for (const candidate of candidates) {
const idx = siblings.indexOf(candidate);
if (idx !== -1 && idx < lowest_idx) {
lowest_idx = idx;
lowest = candidate;
}
}
if (lowest !== null) {
parent.set_child_below_sibling(border, lowest);
if (candidates.size > 0) {
// Find the candidate with the lowest index among siblings (closest to bottom).
let lowest: any = null;
let lowest_idx = siblings.length;
for (const candidate of candidates) {
const idx = siblings.indexOf(candidate);
if (idx !== -1 && idx < lowest_idx) {
lowest_idx = idx;
lowest = candidate;
}
}

return GLib.SOURCE_REMOVE;
};

const old_id = this._restack_id;
this._restack_id = null;
if (old_id !== null) lib.later_remove(old_id);
if (immediate) {
action();
} else {
id = lib.later_add(Meta.LaterType.BEFORE_REDRAW, action);
this._restack_id = id;
if (lowest !== null) {
parent.set_child_below_sibling(border, lowest);
}
}
}

Expand Down
Loading