feat: equal-sized tabs and constantly updated tab titles

This commit is contained in:
Lucas Oskorep
2026-02-27 12:24:58 -05:00
parent 2ab3822cb6
commit 400ce3a77c
6 changed files with 46 additions and 8 deletions

View File

@@ -377,6 +377,13 @@ export default class WindowContainer {
this._tabBar.show(); 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. * Update tab bar state (active highlight, titles) without a full rebuild.
*/ */

View File

@@ -72,6 +72,13 @@ export default class Monitor {
this._workspaces.push(new WindowContainer(this._workArea)); 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 { hideTabBars(): void {
for (const container of this._workspaces) { for (const container of this._workspaces) {
container.hideTabBar(); container.hideTabBar();

View File

@@ -1,4 +1,5 @@
import Clutter from 'gi://Clutter'; import Clutter from 'gi://Clutter';
import Pango from 'gi://Pango';
import St from 'gi://St'; import St from 'gi://St';
import * as Main from 'resource:///org/gnome/shell/ui/main.js'; import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import {Logger} from "../utils/logger.js"; import {Logger} from "../utils/logger.js";
@@ -25,6 +26,8 @@ export class TabBar {
can_focus: false, can_focus: false,
track_hover: 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 = []; this._buttons = [];
items.forEach((item, index) => { 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({ const button = new St.Button({
style_class: 'aerospike-tab', style_class: 'aerospike-tab',
reactive: true, reactive: true,
can_focus: false, can_focus: false,
track_hover: true, track_hover: true,
x_expand: true, x_expand: true,
child: new St.Label({ child: label,
text: item.getTabLabel(),
style_class: 'aerospike-tab-label',
y_align: Clutter.ActorAlign.CENTER,
x_align: Clutter.ActorAlign.CENTER,
x_expand: true,
}),
}); });
button.connect('clicked', () => { button.connect('clicked', () => {

View File

@@ -48,7 +48,13 @@ export class WindowWrapper {
} }
getTabLabel(): string { 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'; const title = this._window.get_title() ?? 'Untitled';
if (appName && appName.toLowerCase() !== title.toLowerCase()) { if (appName && appName.toLowerCase() !== title.toLowerCase()) {
return `${appName} | ${title}`; return `${appName} | ${title}`;
@@ -108,6 +114,9 @@ export class WindowWrapper {
this._window.connect("size-changed", () => { this._window.connect("size-changed", () => {
windowManager.handleWindowPositionChanged(this); windowManager.handleWindowPositionChanged(this);
}), }),
this._window.connect('notify::title', () => {
windowManager.handleWindowTitleChanged(this);
}),
); );
} }

View File

@@ -22,6 +22,8 @@ export interface IWindowManager {
handleWindowPositionChanged(winWrap: WindowWrapper): void; handleWindowPositionChanged(winWrap: WindowWrapper): void;
handleWindowTitleChanged(winWrap: WindowWrapper): void;
syncActiveWindow(): number | null; syncActiveWindow(): number | null;
} }
@@ -420,6 +422,11 @@ export default class WindowManager implements IWindowManager {
this._tileMonitors(); 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) { public addWindowToMonitor(window: Meta.Window) {
Logger.log("ADDING WINDOW TO MONITOR", window, window); Logger.log("ADDING WINDOW TO MONITOR", window, window);
var wrapper = new WindowWrapper(window, (winWrap) => this.handleWindowMinimized(winWrap)) var wrapper = new WindowWrapper(window, (winWrap) => this.handleWindowMinimized(winWrap))

View File

@@ -13,6 +13,7 @@
color: rgba(255, 255, 255, 0.5); color: rgba(255, 255, 255, 0.5);
font-size: 11px; font-size: 11px;
font-weight: 400; font-weight: 400;
min-width: 0;
} }
.aerospike-tab:hover { .aerospike-tab:hover {
@@ -28,4 +29,5 @@
.aerospike-tab-label { .aerospike-tab-label {
font-size: 11px; font-size: 11px;
min-width: 0;
} }