Documentation
¶
Overview ¶
ABOUTME: Client library for communicating with the notification daemon. ABOUTME: Provides functions to check daemon status, start on-demand, and send notifications.
ABOUTME: Window focus methods for Linux desktop environments. ABOUTME: Implements a fallback chain to focus windows on GNOME, KDE, Sway, and other compositors.
ABOUTME: IPC protocol types for communication between daemon client and server. ABOUTME: Uses JSON-over-Unix-socket for simple, reliable inter-process communication.
ABOUTME: Daemon server that maintains persistent D-Bus connection for click-to-focus notifications. ABOUTME: Listens on Unix socket for IPC requests and handles notification action callbacks.
ABOUTME: Zellij pane focus for Linux click-to-focus. ABOUTME: Targets the exact pane that produced the notification, across tabs.
ABOUTME: What the environment says about the surrounding zellij pane. ABOUTME: Untagged so the notifier and the daemon read it the same way on every platform.
ABOUTME: Zellij focus strategy names: the notifier picks one, the click carries it out. ABOUTME: Untagged because the notifier half is compiled on every platform.
Index ¶
- Constants
- Variables
- func DetectFocusTools() map[string]bool
- func GetAppID(terminalName string) string
- func GetDaemonPID() int
- func GetDesktopEntryID(terminalName string) string
- func GetExactWindowTitle(terminalName string) string
- func GetGnomeWmClass(terminalName string) string
- func GetKdotoolClass(terminalName string) string
- func GetNotificationDesktopEntryID(terminalName string) string
- func GetPidFilePath() string
- func GetSearchTerm(terminalName string) string
- func GetSearchTermWithFolder(terminalName, folderName string) string
- func GetSocketPath() string
- func GetTerminalName() string
- func GetWezTermFocusHints(terminalName string) (paneID, socketPath string)
- func GetWezTermPaneID() string
- func GetWezTermSocketPath() string
- func GetWlrctlAppID(terminalName string) string
- func GetX11WindowID() string
- func GetXdotoolClass(terminalName string) string
- func GetZellijFocusHints() (sessionName, paneID string)
- func InZellij() bool
- func IsDaemonRunning() bool
- func IsWezTermTerminalName(terminalName string) bool
- func StartDaemonOnDemand() bool
- func StopDaemon() error
- func TryActivateWindowByTitle(terminalName, folderName string) error
- func TryFocus(terminalName, folderName string) error
- func TryFocusWithHints(hints FocusHints) error
- func TryFocusWithWindowID(terminalName, folderName, windowID string) error
- func TryGnomeFocusApp(terminalName, folderName string) error
- func TryGnomeShellEval(terminalName, folderName string) error
- func TryGnomeShellEvalByTitle(terminalName, folderName string) error
- func TryKdotool(terminalName, folderName string) error
- func TryWezTermPane(paneID, socketPath string) error
- func TryWlrctl(terminalName, folderName string) error
- func TryXdotool(terminalName, folderName string) error
- func TryZellijPane(sessionName, paneID string) error
- func TryZellijTab(sessionName, tabName string) error
- type Client
- type FocusHints
- type FocusMethod
- type MessageType
- type NotifyRequest
- type NotifyResponse
- type PingResponse
- type Request
- type Response
- type Server
- type ServerConfig
Constants ¶
const ( // ZellijFocusModePane targets the exact pane via focus-pane-id (zellij 0.44.1+). ZellijFocusModePane = "pane" // ZellijFocusModeTab targets a tab by name via go-to-tab-name. Approximate: // names are neither unique nor immutable, and a tab holds many panes. ZellijFocusModeTab = "tab" // ZellijFocusModeOff skips the zellij step and only raises the window. ZellijFocusModeOff = "off" )
How a zellij session is brought to the front. The strategy is decided in the hook process, which reads the config and can interrogate the local zellij, and travels with the notification so the click only has to carry it out.
const ProtocolVersion = "1.0"
Protocol version for compatibility checking
Variables ¶
var ( ErrDaemonNotAvailable = errors.New("daemon not available") ErrDaemonNotRunning = errors.New("daemon not running") )
Common errors
Functions ¶
func DetectFocusTools ¶
DetectFocusTools returns a map of available focus tools.
func GetDaemonPID ¶
func GetDaemonPID() int
GetDaemonPID returns the PID of the running daemon, or 0 if not running
func GetDesktopEntryID ¶
GetDesktopEntryID returns the desktop entry ID (without .desktop suffix) for a terminal. This is the value expected by the freedesktop "desktop-entry" notification hint.
func GetExactWindowTitle ¶
GetExactWindowTitle returns an exact top-level window title for terminals that expose a reliable per-terminal identifier. Currently Terminator can provide this via TERMINATOR_UUID + remotinator.
func GetGnomeWmClass ¶
GetGnomeWmClass returns the WM_CLASS used by the activate-window-by-title GNOME Shell extension for activateByWmClass calls. For Wayland-native apps this is the app_id in reverse-domain format.
func GetKdotoolClass ¶
GetKdotoolClass returns the window class for kdotool search.
func GetNotificationDesktopEntryID ¶
GetNotificationDesktopEntryID returns the desktop-entry hint value to use for notifications. GNOME on Wayland shows a long-running loading cursor when the clicked notification advertises the terminal/editor desktop entry and the app never consumes the generated activation token. A dedicated hidden desktop file with StartupNotify=false avoids that spinner while preserving click handling via our daemon.
func GetPidFilePath ¶
func GetPidFilePath() string
GetPidFilePath returns the path to the daemon's PID file.
func GetSearchTerm ¶
GetSearchTerm returns a window title search term for a terminal name.
func GetSearchTermWithFolder ¶
GetSearchTermWithFolder returns the window title search term, using the project folder name for VS Code when available (more specific than "Visual Studio Code").
func GetSocketPath ¶
func GetSocketPath() string
GetSocketPath returns the Unix socket path for the daemon. Uses XDG_RUNTIME_DIR if available, falls back to /tmp with UID suffix.
func GetTerminalName ¶
func GetTerminalName() string
GetTerminalName detects the current terminal from environment variables.
func GetWezTermFocusHints ¶
GetWezTermFocusHints returns WezTerm-specific focus hints only when the detected focus target is WezTerm. WEZTERM_* variables are often inherited by GUI apps launched from WezTerm, so they must not be trusted for other targets.
func GetWezTermPaneID ¶
func GetWezTermPaneID() string
GetWezTermPaneID returns the WezTerm pane ID from the environment.
func GetWezTermSocketPath ¶
func GetWezTermSocketPath() string
GetWezTermSocketPath returns the WezTerm Unix socket path from the environment.
func GetWlrctlAppID ¶
GetWlrctlAppID returns the wlroots app_id for a terminal name.
func GetX11WindowID ¶
func GetX11WindowID() string
GetX11WindowID returns the current terminal window's X11 window ID when available. It is captured in the hook process and later used by the daemon for exact focus on X11.
func GetXdotoolClass ¶
GetXdotoolClass returns the X11 WM_CLASS for xdotool search.
func GetZellijFocusHints ¶ added in v1.43.1
func GetZellijFocusHints() (sessionName, paneID string)
GetZellijFocusHints returns the zellij session and pane that produced the notification, or empty strings when not running under zellij.
The pane is read from the environment rather than asked of zellij when the click arrives, because by then "the focused pane" is whatever the user is looking at — precisely the pane they are not trying to get back to.
func InZellij ¶ added in v1.43.1
func InZellij() bool
InZellij reports whether this process is running inside a zellij pane.
$ZELLIJ is zellij's own marker, but it does not always survive: a process started outside the pane's shell — a background job, a supervisor, anything that rebuilds the environment — can inherit ZELLIJ_SESSION_NAME and ZELLIJ_PANE_ID without it. Those two name the pane on their own, which is everything a focus request needs, so accept them as sufficient rather than discarding a usable target for want of a marker that carries no extra information.
func IsDaemonRunning ¶
func IsDaemonRunning() bool
IsDaemonRunning checks if the daemon is running and responsive
func IsWezTermTerminalName ¶
IsWezTermTerminalName reports whether terminalName identifies WezTerm.
func StartDaemonOnDemand ¶
func StartDaemonOnDemand() bool
StartDaemonOnDemand starts the daemon if it's not already running. Returns true if daemon is running (either started now or was already running).
func TryActivateWindowByTitle ¶
TryActivateWindowByTitle uses the activate-window-by-title GNOME extension. https://extensions.gnome.org/extension/5021/activate-window-by-title/ This method does NOT require unsafe_mode and works on GNOME 42+.
Search order:
- activateBySubstring with the folder-specific term, when available — ensures the correct project window is focused when multiple windows of the same app are open (e.g. two VS Code windows for different projects).
- activateByWmClass — reliable for Wayland-native terminals whose WM class is a reverse-domain app ID (e.g. com.mitchellh.ghostty, org.wezfurlong.wezterm) and whose window title does not contain the app name.
- activateBySubstring with the generic terminal name as a final fallback.
func TryFocus ¶
TryFocus attempts to focus a window using available tools. folderName is the project folder name used for title-based window search (may be empty). It tries each method in order until one succeeds.
func TryFocusWithHints ¶
func TryFocusWithHints(hints FocusHints) error
TryFocusWithHints attempts exact focus using hook-time hints first, then falls back to compositor-specific methods. wezTermPaneID and wezTermSocket enable tab-level focus for WezTerm. warpFocusURL is a Warp session deep link that focuses the originating window/tab/pane.
For WezTerm, window-level focus runs first, then the pane switch runs after a short delay. This ordering matters: GNOME's XDG Activation Token is processed asynchronously after the window-level call and may restore the previously active tab if the pane switch runs first. Running the pane switch last ensures it wins. If all window-level methods fail but a pane ID is available, TryWezTermPane is tried as a last resort (activate-pane also raises the window on WezTerm).
func TryFocusWithWindowID ¶
TryFocusWithWindowID preserves the previous API for callers that only have an exact X11 window ID.
func TryGnomeFocusApp ¶
TryGnomeFocusApp uses GNOME Shell's FocusApp method (available since GNOME 45).
func TryGnomeShellEval ¶
TryGnomeShellEval uses GNOME Shell's Eval method to activate an app. Requires unsafe_mode or development-tools enabled.
func TryGnomeShellEvalByTitle ¶
TryGnomeShellEvalByTitle uses GNOME Shell's Eval to find and focus window by title. Requires unsafe_mode or development-tools enabled.
func TryKdotool ¶
TryKdotool uses kdotool for KDE Plasma.
func TryWezTermPane ¶
TryWezTermPane activates a specific WezTerm pane by ID using the WezTerm CLI. This switches to the exact tab/pane where Claude is running. socketPath is passed via WEZTERM_UNIX_SOCKET env var (the CLI has no --unix-socket flag).
func TryXdotool ¶
TryXdotool uses xdotool for X11-based desktop environments (XFCE, MATE, Cinnamon, i3, bspwm, and X11 sessions of GNOME/KDE).
func TryZellijPane ¶ added in v1.43.1
TryZellijPane focuses paneID inside sessionName, switching tabs if the pane lives in one that is not current.
focus-pane-id arrived in zellij 0.44.1; older versions reject the subcommand and the caller degrades to a raised window with no pane switch.
func TryZellijTab ¶ added in v1.43.1
TryZellijTab brings the tab named tabName to the front. It is the fallback for zellij older than 0.44.1, and lands on whichever pane that tab last had focused, which need not be the one that raised the notification.
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client communicates with the daemon via Unix socket
func (*Client) Ping ¶
func (c *Client) Ping() (*PingResponse, error)
Ping checks if the daemon is responding and returns status info
func (*Client) SendNotification ¶
func (c *Client) SendNotification( title, body string, hints FocusHints, timeout int, ) (*NotifyResponse, error)
SendNotification sends a notification request to the daemon. hints identify the session to focus when the notification is clicked; every field is optional and an empty one simply narrows the daemon's search less.
type FocusHints ¶ added in v1.43.1
type FocusHints struct {
TerminalName string
FolderName string
WindowID string
WindowTitle string
WezTermPaneID string
WezTermSocket string
WarpFocusURL string
ZellijSession string
ZellijPaneID string
ZellijTabName string
ZellijMode string
}
FocusHints identifies the session that produced a notification: its terminal, project folder, window, and multiplexer pane. Every field is captured in the hook process, where the environment still describes that session. The daemon is shared by every session on the machine and outlives each of them, so it cannot re-derive any of this when the click eventually arrives.
type FocusMethod ¶
FocusMethod represents a method for focusing a window
func GetFocusMethods ¶
func GetFocusMethods() []FocusMethod
GetFocusMethods returns the ordered list of focus methods to try
type MessageType ¶
type MessageType string
MessageType identifies the type of IPC message
const ( MessageTypeNotify MessageType = "notify" MessageTypePing MessageType = "ping" MessageTypeStop MessageType = "stop" )
type NotifyRequest ¶
type NotifyRequest struct {
Title string `json:"title"`
Body string `json:"body"`
FocusTarget string `json:"focus_target"` // Terminal identifier (empty = auto-detect)
FocusFolder string `json:"focus_folder,omitempty"` // Project folder name for window-specific focus
FocusWindowID string `json:"focus_window_id,omitempty"` // Exact X11 window ID captured in the hook process
FocusWindowTitle string `json:"focus_window_title,omitempty"` // Exact window title captured in the hook process when available
FocusWezTermPaneID string `json:"focus_wezterm_pane_id,omitempty"` // WezTerm pane ID ($WEZTERM_PANE)
FocusWezTermSocket string `json:"focus_wezterm_socket,omitempty"` // WezTerm unix socket ($WEZTERM_UNIX_SOCKET)
FocusZellijSession string `json:"focus_zellij_session,omitempty"` // Zellij session name ($ZELLIJ_SESSION_NAME)
FocusZellijPaneID string `json:"focus_zellij_pane_id,omitempty"` // Zellij pane ID ($ZELLIJ_PANE_ID)
FocusZellijTabName string `json:"focus_zellij_tab_name,omitempty"` // Zellij tab name, captured only for the tab fallback
FocusZellijMode string `json:"focus_zellij_mode,omitempty"` // "pane", "tab" or "off", resolved in the hook process
FocusWarpURL string `json:"focus_warp_url,omitempty"` // Warp pane deep link ($WARP_FOCUS_URL)
Timeout int `json:"timeout"` // Notification timeout in seconds
}
NotifyRequest contains notification details sent to the daemon
type NotifyResponse ¶
type NotifyResponse struct {
Success bool `json:"success"`
NotificationID uint32 `json:"notification_id"`
Error string `json:"error,omitempty"`
}
NotifyResponse contains the result of a notification request
type PingResponse ¶
type PingResponse struct {
Version string `json:"version"`
Uptime int64 `json:"uptime"` // Seconds since daemon started
}
PingResponse contains daemon status information
type Request ¶
type Request struct {
Type MessageType `json:"type"`
Notify *NotifyRequest `json:"notify,omitempty"`
Version string `json:"version"`
}
Request is the wrapper for all IPC requests
type Response ¶
type Response struct {
Type MessageType `json:"type"`
Notify *NotifyResponse `json:"notify,omitempty"`
Ping *PingResponse `json:"ping,omitempty"`
Error string `json:"error,omitempty"`
}
Response is the wrapper for all IPC responses
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server is the notification daemon server
func NewServer ¶
func NewServer(cfg ServerConfig) (*Server, error)
NewServer creates a new daemon server
type ServerConfig ¶
type ServerConfig struct {
IdleTimeout time.Duration // Auto-shutdown after this duration of inactivity (0 = disabled)
}
ServerConfig contains server configuration options
func DefaultServerConfig ¶
func DefaultServerConfig() ServerConfig
DefaultServerConfig returns the default server configuration