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 e554feca92
commit e33b057c10
6 changed files with 46 additions and 8 deletions

View File

@@ -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.
*/

View File

@@ -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();

View File

@@ -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', () => {

View File

@@ -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);
}),
);
}

View File

@@ -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))

View File

@@ -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;
}