daemon

package
v1.47.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 2, 2026 License: GPL-2.0 Imports: 19 Imported by: 0

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: Detects JetBrains IDE terminals (JediTerm) and the IDE project they belong to. ABOUTME: Only Linux exposes /proc; on other platforms detection finds nothing.

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

View Source
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.

View Source
const ProtocolVersion = "1.0"

Protocol version for compatibility checking

Variables

View Source
var (
	ErrDaemonNotAvailable = errors.New("daemon not available")
	ErrDaemonNotRunning   = errors.New("daemon not running")
)

Common errors

Functions

func DetectFocusTools

func DetectFocusTools() map[string]bool

DetectFocusTools returns a map of available focus tools.

func DetectJetBrainsIDE added in v1.45.18

func DetectJetBrainsIDE() (class string, pid int, ok bool)

DetectJetBrainsIDE returns the window class of the JetBrains IDE whose terminal runs this process, e.g. "jetbrains-phpstorm", and the IDE's PID, which owns its windows. The class comes from the IDE's product-info.json, so every product and edition is covered.

func GetAppID

func GetAppID(terminalName string) string

GetAppID returns the .desktop app ID for a terminal name.

func GetDaemonPID

func GetDaemonPID() int

GetDaemonPID returns the PID of the running daemon, or 0 if not running

func GetDesktopEntryID

func GetDesktopEntryID(terminalName string) string

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

func GetExactWindowTitle(terminalName string) string

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 GetFocusFolderName added in v1.45.17

func GetFocusFolderName(terminalName, cwd string) string

GetFocusFolderName returns the folder name used to pick this session's window by title. JetBrains IDEs title windows by project, which may enclose cwd or be renamed in .idea/.name; everything else uses the cwd base name.

func GetFocusIDEPID added in v1.45.18

func GetFocusIDEPID(terminalName string) int

GetFocusIDEPID returns the PID of the JetBrains IDE running this session when it is terminalName, which tells its windows apart from those of another process of the same IDE. It is 0 otherwise.

func GetFocusProjectPath added in v1.45.18

func GetFocusProjectPath(terminalName, cwd string) string

GetFocusProjectPath returns the JetBrains project root for cwd, which tells apart open projects with the same name. It is "" for other terminals.

func GetGnomeWmClass

func GetGnomeWmClass(terminalName string) string

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

func GetKdotoolClass(terminalName string) string

GetKdotoolClass returns the window class for kdotool search.

func GetNotificationDesktopEntryID

func GetNotificationDesktopEntryID(terminalName string) string

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

func GetSearchTerm(terminalName string) string

GetSearchTerm returns a window title search term for a terminal name.

func GetSearchTermWithFolder

func GetSearchTermWithFolder(terminalName, folderName string) string

GetSearchTermWithFolder returns the window title search term, using the project folder name for VS Code and JetBrains IDEs when available (more specific than the app name).

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

func GetWezTermFocusHints(terminalName string) (paneID, socketPath string)

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

func GetWlrctlAppID(terminalName string) string

GetWlrctlAppID returns the wlroots app_id for a terminal name.

func GetX11WindowID

func GetX11WindowID(terminalName string) 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. JetBrains terminals have no X11 window of their own: a $WINDOWID there was inherited from whatever launched the IDE, so it is ignored.

func GetXdotoolClass

func GetXdotoolClass(terminalName string) string

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

func IsWezTermTerminalName(terminalName string) bool

IsWezTermTerminalName reports whether terminalName identifies WezTerm.

func JetBrainsTitleMatches added in v1.45.18

func JetBrainsTitleMatches(title, project, projectPath string) bool

JetBrainsTitleMatches reports whether a JetBrains window title belongs to project, whose root is projectPath. Titles are "<project>" or "<project> – <file>" (en dash), so a plain substring check would let "agent" match "agent-notifications". When another open project has the same name, JetBrains adds its location: "<project> [<location>] – <file>" (PlatformFrameTitleBuilder). Without projectPath (older hooks) any location is accepted.

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 StopDaemon

func StopDaemon() error

StopDaemon stops the running daemon

func TryActivateWindowByTitle

func TryActivateWindowByTitle(terminalName, folderName string) error

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:

  1. 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).
  2. 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.
  3. activateBySubstring with the generic terminal name as a final fallback.

func TryFocus

func TryFocus(terminalName, folderName string) error

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

func TryFocusWithWindowID(terminalName, folderName, windowID string) error

TryFocusWithWindowID preserves the previous API for callers that only have an exact X11 window ID.

func TryGnomeFocusApp

func TryGnomeFocusApp(terminalName, folderName string) error

TryGnomeFocusApp uses GNOME Shell's FocusApp method (available since GNOME 45).

func TryGnomeShellEval

func TryGnomeShellEval(terminalName, folderName string) error

TryGnomeShellEval uses GNOME Shell's Eval method to activate an app. Requires unsafe_mode or development-tools enabled.

func TryGnomeShellEvalByTitle

func TryGnomeShellEvalByTitle(terminalName, folderName string) error

TryGnomeShellEvalByTitle uses GNOME Shell's Eval to find and focus window by title. Requires unsafe_mode or development-tools enabled.

func TryKdotool

func TryKdotool(terminalName, folderName string) error

TryKdotool uses kdotool for KDE Plasma.

func TryWezTermPane

func TryWezTermPane(paneID, socketPath string) error

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 TryWlrctl

func TryWlrctl(terminalName, folderName string) error

TryWlrctl uses wlrctl for wlroots-based compositors (Sway, etc.).

func TryXdotool

func TryXdotool(terminalName, folderName string) error

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

func TryZellijPane(sessionName, paneID string) error

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

func TryZellijTab(sessionName, tabName string) error

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 NewClient

func NewClient() (*Client, error)

NewClient creates a new daemon client

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,
	appIcon 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.

func (*Client) Stop

func (c *Client) Stop() error

Stop requests the daemon to shut down

type FocusHints added in v1.43.1

type FocusHints struct {
	TerminalName  string
	FolderName    string
	ProjectPath   string // JetBrains project root; tells same-named projects apart
	IDEPID        int    // JetBrains IDE process; tells apart IDE processes with same-named projects
	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

type FocusMethod struct {
	Name string
	Fn   func(hints FocusHints) error
}

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"`
	AppIcon            string `json:"app_icon,omitempty"`              // Icon path or name shown by the notification server
	FocusTarget        string `json:"focus_target"`                    // Terminal identifier (empty = auto-detect)
	FocusFolder        string `json:"focus_folder,omitempty"`          // Project folder name for window-specific focus
	FocusProjectPath   string `json:"focus_project_path,omitempty"`    // JetBrains project root, to tell same-named projects apart
	FocusIDEPID        int    `json:"focus_ide_pid,omitempty"`         // JetBrains IDE process, to tell its windows from another process's
	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

func (*Server) Run

func (s *Server) Run() error

Run starts the daemon server

func (*Server) Shutdown

func (s *Server) Shutdown() error

Shutdown gracefully shuts down the 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

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL