From cbaa8027970226f92b5e5de1724043c118ee5efc Mon Sep 17 00:00:00 2001 From: Lucas Oskorep Date: Fri, 27 Feb 2026 12:24:58 -0500 Subject: [PATCH] feat: equal-sized tabs and constantly updated tab titles --- src/wm/container.ts | 7 +++++++ src/wm/monitor.ts | 7 +++++++ src/wm/tabBar.ts | 20 +++++++++++++------- src/wm/window.ts | 11 ++++++++++- src/wm/windowManager.ts | 7 +++++++ stylesheet.css | 2 ++ 6 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/wm/container.ts b/src/wm/container.ts index 896ab62..8335680 100644 --- a/src/wm/container.ts +++ b/src/wm/container.ts @@ -377,6 +377,13 @@ export default class WindowContainer { this._tabBar.show(); } + /** + * Public entry point to refresh tab titles (e.g. when a window title changes). + */ + refreshTabTitles(): void { + this._updateTabBar(); + } + /** * Update tab bar state (active highlight, titles) without a full rebuild. */ diff --git a/src/wm/monitor.ts b/src/wm/monitor.ts index 63d430a..d4f0953 100644 --- a/src/wm/monitor.ts +++ b/src/wm/monitor.ts @@ -72,6 +72,13 @@ export default class Monitor { this._workspaces.push(new WindowContainer(this._workArea)); } + refreshTabTitlesForWindow(winWrap: WindowWrapper): void { + const wsId = winWrap.getWorkspace(); + if (wsId >= 0 && wsId < this._workspaces.length) { + this._workspaces[wsId].refreshTabTitles(); + } + } + hideTabBars(): void { for (const container of this._workspaces) { container.hideTabBar(); diff --git a/src/wm/tabBar.ts b/src/wm/tabBar.ts index b324ad1..21ef116 100644 --- a/src/wm/tabBar.ts +++ b/src/wm/tabBar.ts @@ -1,4 +1,5 @@ import Clutter from 'gi://Clutter'; +import Pango from 'gi://Pango'; import St from 'gi://St'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import {Logger} from "../utils/logger.js"; @@ -25,6 +26,8 @@ export class TabBar { can_focus: false, track_hover: false, }); + // Force all tabs to equal width regardless of text length + (this._bar.layout_manager as Clutter.BoxLayout).homogeneous = true; } /** @@ -36,19 +39,22 @@ export class TabBar { this._buttons = []; items.forEach((item, index) => { + const label = new St.Label({ + text: item.getTabLabel(), + style_class: 'aerospike-tab-label', + y_align: Clutter.ActorAlign.CENTER, + x_align: Clutter.ActorAlign.CENTER, + x_expand: true, + }); + label.clutter_text.ellipsize = Pango.EllipsizeMode.END; + const button = new St.Button({ style_class: 'aerospike-tab', reactive: true, can_focus: false, track_hover: true, x_expand: true, - child: new St.Label({ - text: item.getTabLabel(), - style_class: 'aerospike-tab-label', - y_align: Clutter.ActorAlign.CENTER, - x_align: Clutter.ActorAlign.CENTER, - x_expand: true, - }), + child: label, }); button.connect('clicked', () => { diff --git a/src/wm/window.ts b/src/wm/window.ts index da1406a..dd6042c 100644 --- a/src/wm/window.ts +++ b/src/wm/window.ts @@ -48,7 +48,13 @@ export class WindowWrapper { } getTabLabel(): string { - const appName = this._window.get_wm_class() ?? ''; + const rawAppName = this._window.get_wm_class() ?? ''; + // Strip reverse-domain prefix (e.g. "org.gnome.Nautilus" -> "Nautilus") + const lastName = rawAppName.includes('.') + ? (rawAppName.split('.').pop() ?? rawAppName) + : rawAppName; + // Capitalize first letter + const appName = lastName.charAt(0).toUpperCase() + lastName.slice(1); const title = this._window.get_title() ?? 'Untitled'; if (appName && appName.toLowerCase() !== title.toLowerCase()) { return `${appName} | ${title}`; @@ -108,6 +114,9 @@ export class WindowWrapper { this._window.connect("size-changed", () => { windowManager.handleWindowPositionChanged(this); }), + this._window.connect('notify::title', () => { + windowManager.handleWindowTitleChanged(this); + }), ); } diff --git a/src/wm/windowManager.ts b/src/wm/windowManager.ts index 48fa199..19aebeb 100644 --- a/src/wm/windowManager.ts +++ b/src/wm/windowManager.ts @@ -22,6 +22,8 @@ export interface IWindowManager { handleWindowPositionChanged(winWrap: WindowWrapper): void; + handleWindowTitleChanged(winWrap: WindowWrapper): void; + syncActiveWindow(): number | null; } @@ -420,6 +422,11 @@ export default class WindowManager implements IWindowManager { this._tileMonitors(); } + handleWindowTitleChanged(window: WindowWrapper): void { + const mon_id = window._window.get_monitor(); + this._monitors.get(mon_id)?.refreshTabTitlesForWindow(window); + } + public addWindowToMonitor(window: Meta.Window) { Logger.log("ADDING WINDOW TO MONITOR", window, window); var wrapper = new WindowWrapper(window, (winWrap) => this.handleWindowMinimized(winWrap)) diff --git a/stylesheet.css b/stylesheet.css index b1f894e..d1e3e01 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -13,6 +13,7 @@ color: rgba(255, 255, 255, 0.5); font-size: 11px; font-weight: 400; + min-width: 0; } .aerospike-tab:hover { @@ -28,4 +29,5 @@ .aerospike-tab-label { font-size: 11px; + min-width: 0; }