No description
  • Go 83.7%
  • JavaScript 16.3%
Find a file
Vincent Batts 90726220e5 Report window titles for applications the user opts in
Titles say what you were doing (the file in an editor, the session in a
terminal) but also hold page titles and chat subjects, so nothing is opted
in by default. Applications listed in ~/.config/gosh-gnome/titles get their
window title and pid logged; the event detail becomes "Name: title" and the
session is the window pid. A title that changes while a window keeps focus
is a title event and does not end the span; changes are debounced to one
per 10 seconds because a terminal rewrites its title at every prompt.

collect re-applies the list when reading the log, so removing an
application stops its titles being reported even from an existing log.
Titles are cleaned to one line of at most 120 characters. doctor shows what
is opted in.
2026-10-17 09:30:34 -04:00
extension Report window titles for applications the user opts in 2026-10-17 09:30:34 -04:00
.gitignore Initial gosh-gnome plugin reporting the focused GNOME application 2026-10-17 09:18:03 -04:00
focus.go Report window titles for applications the user opts in 2026-10-17 09:30:34 -04:00
focus_test.go Report window titles for applications the user opts in 2026-10-17 09:30:34 -04:00
go.mod Initial gosh-gnome plugin reporting the focused GNOME application 2026-10-17 09:18:03 -04:00
LICENSE Initial gosh-gnome plugin reporting the focused GNOME application 2026-10-17 09:18:03 -04:00
main.go Report window titles for applications the user opts in 2026-10-17 09:30:34 -04:00
README.md Report window titles for applications the user opts in 2026-10-17 09:30:34 -04:00

gosh-gnome

A gosh plugin that reports which application was in front of you on a GNOME desktop: when you were in your editor, your terminal, your browser, and for how long.

GNOME on Wayland doesn't let a program ask which window has focus (org.gnome.Shell.Introspect is access-denied, Shell.Eval is off); only code running inside the shell can see it. So there are two parts:

  • a small shell extension that appends focus, idle and heartbeat records to ~/.local/state/gosh/gnome-focus.log, next to gosh's own cwd.log;
  • gosh-gnome, which turns that log into gosh events. There is no daemon: gosh collect runs it like any plugin.
kind one event per
focus time an application took focus (detail: its name, e.g. Firefox)
active 5 minutes it stayed in front of you, plus one where the stretch ended
title change of the focused window's title (only for applications you opt in)

gosh infers time from the spacing of events, so a long stretch reports itself every 5 minutes and a two-hour session counts as two hours, not 5 minutes.

Install

go install git.batts.cloud/vbatts/gosh-gnome@latest
gosh-gnome install-extension          # copies the extension to ~/.local/share/gnome-shell/extensions/
gnome-extensions enable gosh-gnome@batts.cloud
# log out and back in: on Wayland the shell only loads new extensions at login
gosh-gnome doctor                     # is the log being written?
gosh plugin add gnome
gosh collect

install-extension doesn't enable anything; that step is yours. The extension is embedded in the binary, so the plugin and the extension are always the same version.

Where activity is reported

gosh plugin protocol 2 lets an event's path be a URI, and each application is app://<desktop file id>: app://org.mozilla.firefox.desktop, app://org.gnome.Terminal.desktop. gosh groups these in the same tree as files and web pages, so:

gosh report -by path -only gnome -since 7d                 # time per application
gosh timeline -prefix app://org.gnome.Terminal.desktop     # when you were in the terminal
gosh report -by path -exclude gnome                        # everything except window focus

Applications with no desktop file are named by their WM_CLASS instead. Windowless time (the overview, the lock screen, nothing focused) is not reported.

Window focus has no git project, so gosh report -by project shows these under (no repo). Use the path views, or -exclude gnome there.

How time is worked out

A stretch counts while a window is focused, you are at the keyboard, and the shell is known to be alive:

  • Idle. After 2 minutes without input the extension logs idle. The stretch ends where your input stopped, not 2 minutes later, and resumes at your next input.
  • Silence. The extension logs a tick every minute. If nothing is written for more than 2.5 minutes the shell wasn't running (suspend, crash), so those hours aren't counted. The next tick resumes the stretch, on the assumption you are back; a long suspend that ends with you away will over-count a little.
  • Still going. A stretch that is still going reports its start and heartbeats but no end yet, and every event's ref depends only on when the stretch began, so collecting repeatedly never duplicates anything.

Window titles

Titles say what you were doing, which an application name can't: the file in an editor, the session or directory in a terminal. They also hold page titles, document names and chat subjects, so nothing is opted in for you. List the applications whose titles may be logged, by desktop file id, one per line, in ~/.config/gosh-gnome/titles:

# terminals
ghostty.desktop

For those applications only, the extension logs the window's title and its process id (pid), and the events carry Name: title as their detail and the pid as their session, so gosh timeline shows @gnome focus Ghostty: tmux: gosh (app://ghostty.desktop) [4239]. A title that changes while the window keeps focus is a title event; it does not end the stretch, and a title that keeps changing, such as a terminal's at every prompt, is logged at most every 10 seconds. Titles are one line of at most 120 characters.

The list is read each time a window takes focus, so editing it needs no restart. gosh-gnome collect applies the same list when it reads the log: take an application off it and its titles stop being reported at once, even from a log that already has them. Events gosh has already stored keep what they were sent. doctor shows what is opted in.

A few things worth knowing:

  • The window pid is the process that owns the window, which is not enough to tell windows apart: Ghostty with --gtk-single-instance (its default) serves every window from one process. The title does that job.
  • A terminal's title is whatever it was told to show. Under tmux that is tmux unless tmux sets it; set -g set-titles on and set -g set-titles-string '#S: #W' in tmux.conf make it name the session and window.
  • Don't opt in a browser. gosh-firefox already reports pages, with detail title if you want titles, and those titles are the ones most worth keeping out.
  • Shells that run inside tmux are children of the tmux server, not of the terminal, so their pid has no relation to the window's.

Privacy

The log is a plain file in your state directory (mode 0600). By default it holds timestamps, the application's desktop file id and name, and its WM_CLASS: nothing about what is inside a window. With titles it also holds the title and pid for the applications you listed, and only those. Without titles the plugin can't tell two terminals, or two documents in one editor, apart; it sees applications, not windows.

Limitations

  • The extension is written against GNOME Shell 48 (metadata.json says "shell-version": ["48"]; the extension API it uses is the same from 45, so widening it is a one-line change once you've tried it). The Go side is tested; the extension's calls were checked against the installed GNOME 48 typelibs and the file syntax-checked, but it has not been loaded into a live shell as part of building this. doctor is how you find out.
  • A shell extension runs inside the shell, so a bug in it is a bug in your desktop. It's about 100 lines and does one synchronous append on each focus change and each minute; read it before enabling it.
  • The log is never rotated and collect reads all of it each run (about 150 KB a day). Truncating it is safe: gosh already has the events.
  • Overlap with other plugins is real: Firefox in front of you is both app://org.mozilla.firefox.desktop here and pages in gosh-firefox. They answer different questions; use -only / -exclude.
  • X11 sessions: nothing here is Wayland-specific, so it should work the same (untested); Wayland is simply why an extension is needed at all.

Protocol

gosh-gnome describe
gosh-gnome collect --since <unix seconds> [--log <file>] [--titles <file>]
gosh-gnome doctor [--log <file>] [--titles <file>]
gosh-gnome install-extension [--dir <extensions dir>]

The plugin declares protocol 2 (paths may be URIs), so it needs a gosh that supports it. Output is JSON lines; see gosh's docs/PLUGINS.md. Only the standard library is used.

License

MIT, see LICENSE.