Help us shape the future of Go! Take the 2026 Go Developer Survey

launcher

package module
v0.2.1 Latest Latest
Warning

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

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

README

sysc-launch

sysc-launch is a Go launcher engine and diagnostic CLI for Niri. It is a presentation-neutral library consumed by sysc-shell.

The module discovers desktop applications, ranks queries, and activates entries through Niri. It does not provide a standalone Wayland UI or daemon; sysc-shell owns the launcher panel and all presentation.


Ranked searchThe sysc-shell launcher browsing all applications
Browse

The sysc-shell launcher, powered by sysc-launch (fixture data).

Documentation site · Requirements · CLI · Library · Providers

Requirements

  • Go 1.26.4
  • Niri in PATH for activation

Desktop scanning and query work without Niri.

Build and install

Go installation

From a clone of this repository:

go build -o ./bin/sysc-launch ./cmd/sysc-launch
go install ./cmd/sysc-launch

go install writes the command to GOBIN, or to Go's default binary directory when GOBIN is unset.

AUR

On Arch, install sysc-launch with your AUR helper:

yay -S sysc-launch

This installs the diagnostic command. sysc-shell compiles the launcher library into its own binary and does not need this package to show its launcher.

Documentation.

CLI

sysc-launch query [QUERY]
sysc-launch launch DESKTOP_ID [ACTION_ID]

query writes one JSON array of results to standard output. Each result has an Entry containing the desktop ID, display metadata, expanded argument vector, terminal flag, and desktop actions, plus its numeric Score. With no query it returns the current application list. A query beginning with / uses the provider-prefix routing; /apps selects installed applications.

launch activates the identified application or optional desktop action via:

niri msg action spawn -- PROGRAM [ARG...]

Arguments come from the desktop entry and are passed without a shell. An entry with a Path= working directory is instead spawned as sh -c 'cd "$1" && shift && exec "$@"' sh PATH PROGRAM [ARG...] because Niri's spawn action takes no directory.

Library

Import the module as:

import launcher "github.com/Nomadcxx/sysc-launch"

A service scans immediately. Wait for that initial result before submitting a query so the response is based on the discovered entries:

package main

import (
	"fmt"
	"log"
	"time"

	launcher "github.com/Nomadcxx/sysc-launch"
)

func main() {
	service := launcher.NewService(launcher.ServiceConfig{
		History: launcher.DefaultHistory(log.Printf),
		Logf:    log.Printf,
	})
	defer service.Close()

	wait := func() ([]launcher.Result, error) {
		select {
		case results := <-service.Results():
			return results, nil
		case <-time.After(5 * time.Second):
			return nil, fmt.Errorf("timed out waiting for launcher results")
		}
	}

	if _, err := wait(); err != nil { // initial XDG scan
		log.Print(err)
		return
	}
	service.Query("terminal")
	results, err := wait()
	if err != nil {
		log.Print(err)
		return
	}
	fmt.Println(results)
}

Query and Open are asynchronous. Activate is synchronous and records usage only after Niri reports a successful spawn. Call Close when the service is no longer needed.

Providers

Applications is the default provider, reached by bare text or /apps. Append more through ServiceConfig.Providers:

launcher.Provider{
	Name:        "Calculator",
	Prefix:      "/calc",
	Glyph:       "glyph:calculate",
	Description: "Arithmetic",
	Query:       func(query string) []launcher.Result { /* ... */ },
	Activate:    func(query, id, action string) error { /* ... */ },
	Inline:      true,
}
  • Prefix must start with / and be unique; /apps is taken. A provider with a bad or repeated prefix, or no Query, is logged through Logf and skipped.
  • /<prefix> text sends text to that provider. A bare / (or an unknown prefix) lists the registered providers, Applications first; ApplicationsGlyph sets its glyph.
  • An Inline provider is also asked about bare text, and its rows are placed above the application rows.
  • Each row belongs to the provider that produced it in the last published result set. Activate(id, action) hands the row to that provider's Activate with the routed query; a provider without one, or a row that is no longer published, takes the spawn path. Result.Action names a desktop action of Result.Entry. Successful activations are recorded in history whichever provider ran them.
  • Provider functions run on the service goroutine and block queries and activation while they run. They must not call back into the Service.

Discovery and ranking

The scanner reads .desktop files from $XDG_DATA_HOME/applications and each applications directory under $XDG_DATA_DIRS; the standard HOME and system fallbacks apply when those variables are unset. User entries take precedence. It excludes entries marked Hidden or NoDisplay, entries rejected by OnlyShowIn or NotShowIn, and entries whose TryExec is unavailable.

Terminal=true entries use $TERMINAL when configured (the value may include arguments), then try kitty, foot, alacritty, wezterm, and ghostty. Such an entry is excluded when no terminal can be resolved. Valid desktop actions are exposed separately and can be selected by action ID.

Application names, generic names, keywords, commands, comments, and categories are fuzzy matched with fzf's ranking algorithm. Recent and repeated successful launches add a usage boost capped at 25 points, so history influences ranking without overwhelming a clearly better text match.

History and privacy

The default history file is:

$XDG_STATE_HOME/sysc-launch/history.gob

If XDG_STATE_HOME is unset, it falls back to:

$HOME/.local/state/sysc-launch/history.gob

History is local gob data written with owner-only file permissions. Successful launches persist the associated query prefixes and desktop IDs, so the file can reveal application-launch habits. Delete it to reset ranking history.

sysc-shell deliberately opens its legacy independent path, $XDG_STATE_HOME/sysc-shell/launcher/history.gob (with the corresponding $HOME/.local/state fallback), instead of this module's default. The two applications therefore do not merge history.

Checks

go test -count=1 ./...
go test -race -count=1 ./...
go vet ./...
go build ./...

License

GPL-3.0-only. See LICENSE.

Documentation

Index

Constants

View Source
const PlaceholderGlyph = "□"

Variables

View Source
var ErrNoTerminal = errors.New("launcher: no terminal found")
View Source
var ErrServiceClosed = errors.New("launcher: service closed")

Functions

This section is empty.

Types

type Action

type Action struct {
	ID, Name, IconName string
	Argv               []string
}

type Entry

type Entry struct {
	ID, Name, GenericName string
	Keywords              []string
	Categories            []string
	Argv                  []string
	// ExecArgv holds the desktop Exec tokens before terminal wrapping and is
	// what ranking searches; empty falls back to Argv (provider entries).
	ExecArgv          []string
	Comment, IconName string
	// WorkDir is the desktop entry's Path= working directory, empty when unset.
	WorkDir  string
	Terminal bool
	Actions  []Action
}

type History

type History struct {
	// contains filtered or unexported fields
}

History stores launcher usage for ranking and persists successful activations.

func DefaultHistory

func DefaultHistory(logf func(string, ...any)) *History

DefaultHistory loads usage history from $XDG_STATE_HOME/sysc-launch/history.gob, falling back to $HOME/.local/state/sysc-launch/history.gob. The logf argument receives history load and save diagnostics; nil suppresses those diagnostics.

func OpenHistory

func OpenHistory(path string, logf func(string, ...any)) *History

OpenHistory loads the on-disk usage history at path. The logf argument receives history load and save diagnostics; nil suppresses those diagnostics.

type Provider

type Provider struct {
	Name        string
	Prefix      string
	Glyph       string
	Description string
	Query       func(query string) []Result
	// Activate runs a row this provider produced, given the routed query.
	// Nil means the Applications behaviour: spawn the entry (or its action).
	Activate func(query, id, action string) error
	// Inline providers are also queried for bare (unprefixed) text; their
	// rows are placed above the default provider's.
	Inline bool
}

Provider is one launcher source behind a "/" prefix. The registry is an ordered slice; the first entry is the default provider for bare queries. Later providers append without any layout or routing change.

type Result

type Result struct {
	Entry Entry
	Score int
	// Action is a desktop action ID when this row is one of Entry's actions.
	Action string
}

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service owns the collector and query goroutines (D12). All state crosses goroutine boundaries as immutable snapshots through channels; no Wayland types appear in this package.

func NewService

func NewService(cfg ServiceConfig) *Service

func (*Service) Activate

func (s *Service) Activate(id, action string) error

Activate runs a row of the last published result set on the service goroutine. A row whose provider has an Activate function goes to it; anything else spawns the entry (or one of its desktop actions) through `niri msg action spawn` (D6/D9). Usage is recorded only on success.

func (*Service) Close

func (s *Service) Close()

func (*Service) Open

func (s *Service) Open()

Open triggers a rescan when the entry snapshot is stale.

func (*Service) Query

func (s *Service) Query(text string)

Query submits a new query; it supersedes any queued or in-flight older one.

func (*Service) Results

func (s *Service) Results() <-chan []Result

Results publishes ranked results as immutable slices, latest wins.

type ServiceConfig

type ServiceConfig struct {
	Scan            func() []Entry
	History         *History
	Rank            func(entries []Entry, query string, boost func(query, identifier string) int) []Result
	Run             func(ctx context.Context, argv []string) error
	Getenv          func(string) string
	LookPath        func(string) (string, error)
	Now             func() time.Time
	Logf            func(string, ...any)
	StaleAfter      time.Duration
	ActivateTimeout time.Duration

	// Providers are appended after Applications, in order. A provider whose
	// prefix is empty, lacks the leading "/", repeats an earlier one, or is
	// "/apps" is logged and skipped. Provider functions run on the service
	// goroutine and block queries and activation while they run, so they
	// must not call back into the Service.
	Providers []Provider
	// ApplicationsGlyph is the Applications row's overview glyph; empty
	// uses PlaceholderGlyph.
	ApplicationsGlyph string
}

ServiceConfig wires the launcher service. Nil Scan, Rank, Run, Getenv, LookPath, and Now use the XDG desktop scanner, built-in ranker, Niri spawn, os.Getenv, exec.LookPath, and time.Now. Non-positive StaleAfter and ActivateTimeout use one minute and five seconds. Nil History disables usage persistence and boosting; nil Logf suppresses scanner and provider diagnostics.

Directories

Path Synopsis
cmd
sysc-launch command

Jump to

Keyboard shortcuts

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