Research: how should DropTerm generate tab names?

Date: 2026-10-04 Question: What setting should control how a tab’s name is generated — and what do famous terminals (Guake first) actually expose for automatic tab-title generation, with which precedence rules, defaults, template languages, and rename interactions?

TL;DR

Convergence across every surveyed terminal: the shell’s OSC 0/2 title is the default automatic source; a mode setting decides whether/how it is applied; a manual rename, where it exists, is sticky (never clobbered) and cleared by an explicit empty/sentinel rename; and a length cap with truncation is the one garnish almost everyone ships. Concretely:

Recommended setting shape for DropTerm: one enum dropdown, config key tab_title_mode, variants Shell (default; today’s behavior), ShellDirectory (shell title with its trailing cwd abbreviated / reduced to the last segment, Guake-style), and Numbered (ignore shell titles; always Terminal N) — re-titling live tabs on change like Guake’s recompute_tabs_titles, never touching title_override, keeping the bell/activity prefix outside the mode’s concern, plus an optional max_tab_title_length clamp later if wanted. Details and edge cases in Recommendation.

A mechanism primer first, since every design below rides on it:

Findings

1. Guake — the reference: one checkbox, one dropdown, one spinner, and a sticky rename

From Guake/guake @ master (fetched 2026-10-04; schema guake/data/org.guake.gschema.xml, UI guake/data/prefs.glade, logic in guake/guake_app.py, guake/notebook.py, guake/utils.py, guake/boxes.py, guake/main.py):

Settings exposed (all on the General page of the preferences notebook, prefs.glade — page tab label “General” at L809, widgets L926-1088):

Setting (schema key) Control + exact strings Default
use-vte-titles (b) checkbox “Use VTE titles for tab names” (use_vte_titles, L1078-1088) true
display-tab-names (i) dropdown labeled “Display as tab names:” (tab_name_display, L945-953) with items “Full Path”, “Abbreviated Path”, “Last Segment” 0 (Full Path)
max-tab-name-length (i) spinner “Max tab name length:” (max_tab_name_length, L988-998) 100
set-window-title (b) checkbox “Set window title to current tab name” (L1063) false
rename-current-tab keybinding <Control><Shift>R, “Rename current tab.” —

Schema verbatim: display-tab-names — “Controls how the tab names are displayed. 0: Full Path, 1: Abbreviated Path, 2: Only display the last segment of the path”; use-vte-titles — “Any terminal program can set the terminal’s title via special escape sequences… you can have easy automatically updating meaningful tab titles with this option turned on.” The dropdown, its label, and the spinner are all desensitized when “Use VTE titles” is unchecked (guake/prefs.py:855-864, :1132-1145). The combo maps positionally: set_int("display-tab-names", combo.get_active()) (prefs.py:415-418). Note max-tab-name-length carries literal #TODO PORT markers in summary/description — the key works but was migrated without polish.

Semantics — the title pipeline (guake/guake_app.py):

  1. VTE’s window-title-changed signal (GTK-VTE’s OSC 0/2 feed) fires on_terminal_title_changed (guake_app.py:1195-1225). It returns immediately if use-vte-titles is false (L1204-1206) — with the setting off, the tab keeps whatever name it got at creation and is never retitled automatically.
  2. If the tab has a user rename (custom_label_set), the shell title is dropped (L1217-1218); the existing custom text still updates the window title.
  3. Otherwise compute_tab_title(vte) runs (guake_app.py:1160-1182):
    • a .guake.yml title: in the cwd (when load-guake-yml is on) wins over everything (L1163-1166);
    • the base is vte.get_window_title() or _("Terminal") (L1168) — the i18n’d literal “Terminal” is the no-title fallback;
    • display_tab_names == 1 (Abbreviated Path): if the title ends with the current directory, every parent component is cut to its first character and the last stays whole (L1171-1174) — e.g. ~/src/oss/l1br3/ced → ~/s/o/l/ced;
    • display_tab_names == 2 (Last Segment): the title is replaced by the last path component of the cwd, "(root)" for / (L1175-1178);
    • finally TabNameUtils.shorten applies the length cap.
  4. TabNameUtils.shorten (guake/utils.py:158-167): lengths beyond max-tab-name-length become "..." + text[-max:] — the tail is kept, head elided; 0 disables truncation; and the whole clamp is skipped when use-vte-titles is off (a quirk: the spinner is meaningless then anyway).
  5. A new tab gets this same computation once at creation (guake/notebook.py:438-456, new_page_with_focus → rename_page(…, self.guake.compute_tab_title(terminal), False)).

Rename interaction (sticky + sentinel): rename_page(page_index, new_text, user_set) (guake/notebook.py:459-477) applies a title only when not getattr(page, "custom_label_set", False) or user_set — a user rename permanently blocks VTE-driven retitling; and setattr(page, "custom_label_set", new_text != "-") — renaming to exactly - clears the lock so automatic titles resume. The - reset is documented in the CLI help: --rename-tab / --rename-current-tab — “Reset to default if TITLE is a single dash ‘-‘.” (guake/main.py:347-364). The rename UI is the RenameDialog (guake/dialogs.py:11-41) opened from the tab label (guake/boxes.py:698-707) or the keybind (guake_app.py:1077-1084).

Live re-titling on setting change: the GSettings handler wires max-tab-name-length and display-tab-names (guake/gsettings.py:93-94, :470-474) and recompute_tabs_titles (guake_app.py:1128-1140) re-runs compute_tab_title over all terminals. Window title follow-along is the separate set-window-title bool (update_window_title, guake_app.py:1227-1231: tab name or the saved default_window_title).

What Guake does not have: no per-tab-format template, no process-name mode (the “title” is whatever the shell reports; the dropdown merely reshapes it using the directory), no prepend/append combine mode.

2. GNOME Terminal (vte) — the ancestor dropdown, since removed

From GNOME/gnome-terminal (read-only GitLab mirror; fetched 2026-10-04):

So the vte heritage Guake shares is exactly this initial-title + four-way mode idea — Guake kept a simplified cousin (use/don’t-use + reshape) after GNOME Terminal abandoned it.

3. Konsole — per-session format strings with % placeholders, local and remote

From KDE/konsole @ master (fetched 2026-10-04) and the Konsole Handbook, “Configure Tab Settings Dialog”:

Settings exposed. Two format strings per profile — and per session, overridable: profile keys LocalTabTitleFormat and RemoteTabTitleFormat (src/profile/Profile.cpp:77-79), edited on the profile’s Tabs page (src/widgets/EditProfileDialog.cpp:707-713). The handbook: “The name format, the remote tab title format, and the color of the current tab can be changed from this dialog. The dialog can be displayed via the menu, the shortcut Ctrl+Alt+S or by double-clicking on the tab in the tab bar. These changes are temporary and can be made permanent by editing the current profile.”

Defaults: LocalTabTitleFormat = %d : %n (e.g. src : bash), RemoteTabTitleFormat = (%u) %H (Profile.cpp:77-79).

Placeholder mini-language (handbook table, confirmed against the implementation in src/session/Session.cpp:1351-1430):

Token (local) Meaning Token (remote) Meaning
%n program name (foreground process) %c current program (ssh command)
%d current directory (short) %h remote host (short)
%D current directory (long) %H remote host (long, host:port)
%h local host (short) %u user name
%u user name %U user@ if given
%B Bourne prompt sigil ($/#) %w window title set by shell
%w window title set by shell %# session number
%# session number    

Remote tokens resolve from the parsed ssh command line (src/SSHProcessInfo.cpp:202-244); %u in the local format also arms a “user name required” check (Session.cpp:750-756).

Semantics / precedence:

4. Yakuake — Konsole’s machinery, a thin sticky-rename shell around it

From KDE/yakuake @ master (fetched 2026-10-04):

5. kitty — tab_title_template, the richest template language

From kitty conf docs (fetched 2026-10-04) and kovidgoyal/kitty @ master:

Settings exposed (all in the Tab bar group of kitty.conf): tab_bar_style (default fade; fade/slant/separator/powerline/custom/hidden), tab_bar_min_tabs (default 2), tab_title_template (a template string), active_tab_title_template (default none → falls back to tab_title_template), tab_title_max_length (default 0 = no limit; vertical tab bars size for ~20 title cells when unset), tab_activity_symbol (default none), bell_on_tab (default "🔔 " — “Some text or a Unicode symbol to show on the tab if a window in the tab that does not have focus has a bell”), plus the bar color/style keys.

Default template, verbatim:

tab_title_template "{fmt.fg.red}{bell_symbol}{activity_symbol}{secure_input_symbol}{fmt.fg.tab}{tab.last_focused_progress_percent}{title}"

and the doc’s backward-compat rule: “if {bell_symbol} or {activity_symbol} are not present in the template, they are prepended to it.” So the shipped default title = (bell 🔔) + (activity) + (progress %) + the tab’s title, colored red-then-normal.

Template mini-language. Python format-string machinery with styling directives — “you can use, for instance, {layout_name[:2].upper()}” and {fmt.fg.red}, {fmt.bold}, {fmt.fg.color0..255}, {fmt.bg.tab} etc. Available variables (docs list, verbatim names): title, index (usable with goto_tab N), layout_name, session_name, active_session_name, num_windows, num_window_groups, secure_input_symbol, tab.active_wd, tab.active_exe (and …_oldest_… variants; both marked “expensive, requires syscall”), max_title_length, keyboard_mode, tab.last_focused_progress_percent, tab.progress_percent, tab.children_mem_usage, and {custom} (calls draw_title(data) from tab_bar.py in the config dir). There is also a window-title counterpart (window_title_template, with {title}, {bell_symbol}, {activity_symbol}, {progress_percent}, {custom}, {fmt}, {is_active}) for per-window title bars.

Precedence. A tab’s effective title is self.name or self.title (kitty/tabs.py:561-563): the user title wins, and self.title is the active window’s OSC title (tabs.py:556-559; a window’s own title is override_title or child_title, kitty/window.py:906-908, where the no-OSC default is the program basename — window.py:792). The remote control command kitten @ set-tab-title [options] TITLE ... sets name; “If you do not specify a title, the title of the currently active window in the tab is used” — i.e. an empty call resets to automatic (remote-control docs; Tab::set_title does self.name = title or '', tabs.py:573-574). kitty also implements the xterm title stack per window (10-deep, window.py:794,1933-1942).

6. WezTerm — default “index: active pane title”, full control via the format-tab-title Lua event

From wezterm docs (markdown source at wezterm/wezterm docs/config/lua/window-events/format-tab-title.md @ main, fetched 2026-10-04) and wezterm-gui/src/tabbar.rs @ main:

Default title (no config): tab.tab_title if non-empty, else the active pane’s title (the pane’s OSC title), prefixed "N: " when show_tab_index_in_tab_bar is true (the default) — wezterm-gui/src/tabbar.rs:220-242. Numbering base toggled by tab_and_split_indices_are_zero_based. Progress glyphs (ConEmu-style OSC 9;4 percent) are appended when the pane reports progress (tabbar.rs:244-250).

Settings: show_tab_index_in_tab_bar = true (default), hide_tab_bar_if_only_one_tab = false (default; “If set to true, when there is only a single tab, the tab bar is hidden”), tab_max_width (default 16 glyphs, retro tab bar only), tab_bar_style (styling of new-tab button etc.).

The template escape hatch — format-tab-title: a synchronous Lua event; params tab, tabs, panes, config, hover, max_width; the handler returns a string or a table of FormatItems (styled spans, as in wezterm.format), or nil to fall back to the built-in computation (“If the event encounters an error… the default tab title text will be computed and used instead”). It is called twice per tab (measure pass, then render pass with final max_width). The doc’s canonical helper encodes the precedence explicitly:

function tab_title(tab_info)
  local title = tab_info.tab_title
  if title and #title > 0 then return title end   -- explicit tab title wins
  return tab_info.active_pane.title               -- else the active pane's title
end

Rename: wezterm cli set-tab-title TITLE (with --tab-id/--pane-id; since 20230408-112425) sets tab_title; tab:set_title() in Lua is the in-process equivalent. TabInformation exposes tab_id, tab_index, is_active, is_last_active, active_pane, panes, window_id, window_title, tab_title for template authors.

7. Tilix — vte heritage plus a full ${token} mini-language on the title bar

From gnunn1/tilix @ master (fetched 2026-10-04; schema data/gsettings/com.gexperts.Tilix.gschema.xml, logic source/gx/tilix/terminal/terminal.d, tokens source/gx/tilix/constants.d):

Settings exposed. Profile key terminal-title (schema L753-757): “The string to display as the terminal title … supports tokens which are replaced at runtime, additionally pango markup can be used as well.” — default ${id}: ${title}, edited on the profile’s Title page with a variables-dropdown helper (source/gx/tilix/prefeditor/titleeditor.d:41-77 inserts the chosen token into the entry). Chrome keys: terminal-title-style (normal/small/none, default normal), terminal-title-show-when-single (default true), control-click-titlebar (default false), and global process-monitor (default false) which gates ${process}.

Token mini-language (constants.d:122-133, substitution in terminal.d:1306-1336):

Token Value
${title} the vte window title; empty → i18n “Terminal” (terminal.d:1307-1309)
${iconTitle} the vte icon title (OSC 1)
${id} the terminal’s session-assigned number
${directory} current working directory
${columns} / ${rows} grid size
${hostname} / ${username} resolved per terminal state
${process} foreground process name — only with process-monitor on, else “Not Enabled”
${status.readonly} / ${status.silence} / ${status.input-sync} booleans as strings

Session-level tokens (constants.d:167-174): ${terminalCount}, ${terminalNumber}, ${activeTerminalTitle} — the sidebar session name defaults to ${title} (session-name, schema L205-209), and the window title template app-title defaults to ${appName}: ${sessionName} (L175-179).

Precedence / rename. The computed title is _overrideTitle if set, else the profile terminal-title template, then token substitution (terminal.d:1273-1284). The per-terminal title bar’s menu opens an edit dialog (title/badge/command) that sets _overrideTitle (terminal.d:617-629) — a user override that beats the template until cleared; it is persisted in the session JSON (terminal.d:4086-4088) and can also be set by triggers (TriggerAction.UPDATE_TITLE, terminal.d:1722-1725).

8. Terminator — per-terminal titlebar with an inline-editable custom title

From gnome-terminator/terminator @ master (fetched 2026-10-04):

9. iTerm2 (macOS) — profile Title components + the “apps may change title” toggle

From iterm2.com and Session Title docs (fetched 2026-10-04), plus gnachman/iTerm2 @ master:

10. Windows Terminal — fixed tabTitle vs OSC, gated by suppressApplicationTitle

From learn.microsoft.com and microsoft/terminal @ main (doc/cascadia/profiles.schema.json, fetched 2026-10-04):

11. Counterpoints: Alacritty, foot, Ghostty — minimal or none

What DropTerm has to work with

Verified in this repo:

Recommendation

A single dropdown in the settings window, shaped after Guake (its General page is the direct precedent) but collapsed to what DropTerm can honestly compute today:

  1. Enum + config key: TabTitleMode in src/config.rs (mirror TabPosition), serialized as RON variants, config key tab_title_mode (one file under ~/.config/cosmic/io.github.l1br3_io.CosmicExtDropterm/v1/, listed in README.md:137’s key inventory). Variants and display strings for the dropdown (src/settings.rs option-table pattern):
    • Shell — “Shell title” (default): exactly today’s behavior — OSC 0/2 lands in Tab.title unless a rename is set. This matches the shipped default of every surveyed peer that has a choice (Guake use-vte-titles=true, kitty/WezTerm {title}-centric defaults, GNOME Terminal’s implicit replace).
    • ShellDirectory — “Shell title, shortened path”: keep the shell title, but post-process it Guake-style (compute_tab_title’s display-tab-names logic, guake_app.py:1171-1178): when the title ends with the tab’s cwd (already tracked via check_tab_cwd), abbreviate parent components to one character; when the title has no directory information, reduce to the cwd’s last segment. This gives the “tabs that stay readable at 3-word width” Guake users actually select.
    • Numbered — “Terminal number”: ignore shell titles entirely; Tab.title stays/parks at next_default_title(id) (Terminal N) — Guake’s use-vte-titles=false (title frozen at creation) and GNOME Terminal’s ignore/”Keep initial title” mode; also the suppressApplicationTitle audience (people whose shells spam junk titles). TermEvent::Title still fires check_tab_cwd — the cwd gate is not a title decision and must keep working.
  2. Placement/label: a “Tab titles” row in the Behavior card (Section::Behavior, src/settings.rs:648) — or Geometry next to “Tab bar position”; Behavior fits better since this is semantics, not layout. Label “Tab title source” (keystone: Guake labels the group “Display as tab names:”, kitty “tab_title_template”).
  3. Rename stays orthogonal and sticky: title_override continues to win over every mode (all four peers with renames agree: Guake custom_label_set, kitty name or title, WezTerm tab_title first, Yakuake m_tabTitlesSetInteractive), and the empty-commit clear (commit_rename, src/tabs.rs:325-332) remains the documented reset — no - sentinel needed (empty is already the idiom in Yakuake, Windows Terminal’s renamer, and Terminator).
  4. Live re-title on change like Guake’s recompute_tabs_titles (guake_app.py:1128-1140): on ConfigChanged, recompute every tab’s title from its new-mode rule (for Numbered, snap to next_default_title(id); for ShellDirectory, re-derive from the cached cwd; for Shell, re-apply the last reported OSC title — which means the app must keep storing the raw report even in other modes; the cheapest honest version stores the raw report in a new shell_title field or simply re-derives on next event). Renamed tabs are skipped entirely.
  5. Bell/activity prefix: unchanged — markers are render-time (src/tab_bar.rs:821-827), exactly kitty’s model where bell_symbol is template chrome, not title content.
  6. Edge cases:
    • Empty reported titles: keep the current Terminal N fallback chain (next_default_title); peers hard-fallback too (Guake _("Terminal"), GNOME Terminal _("Terminal"), kitty appname, WezTerm pane title).
    • Truncation/max length: defer. Guake’s max-tab-name-length=100 tail-truncation is the only peer precedent with a char cap, and DropTerm already clips at the button level (src/tab_bar.rs:190-191). If ever added: a max_tab_title_length: u16 with 0 = unlimited and Guake’s "..." + tail semantics, clamped in DropTermConfig::clamped.
    • Session persistence: session.rs keeps writing title + title_override unchanged; a restored tab re-titles per the live mode like any other tab.
    • Remote rename-tab (src/app.rs:945-951,1129-1135) is a rename and keeps its override semantics in all modes.

Not recommended now: a full template mini-language (Konsole %, kitty/foot {}/${} tokens). Every template peer needs data feeds DropTerm doesn’t maintain (foreground process name per tab, host/user state, per-window enumeration); Guake’s evidence says a quake terminal’s users are served by the shell title + directory reshaping + a length cap, and a format-tab-title-class escape hatch can be added later without breaking the enum (it would be a fourth variant, Custom, carrying a template string — the WezTerm/kitty convergence point).

Sources