package niri import ( "errors" "fmt" "os" "path/filepath" "regexp" "strconv" "git.tordarus.net/tordarus/slices" "github.com/adrg/xdg" ) var niriSocketFilenameFormat = "niri.wayland-%d.%d.sock" var niriSocketFilenamePattern = regexp.MustCompile(`^niri\.wayland-(\d+?)\.(\d+?)\.sock$`) type Client struct { display int pid int } var ErrClientNotFound = errors.New("client not found") func GetClientByPID(pid int) (*Client, error) { clients, err := GetAllClients() if err != nil { return nil, err } for _, c := range clients { if c.pid == pid { return c, nil } } return nil, ErrClientNotFound } func GetClientByDisplay(display int) (*Client, error) { clients, err := GetAllClients() if err != nil { return nil, err } for _, c := range clients { if c.display == display { return c, nil } } return nil, ErrClientNotFound } func GetDefaultClient() (*Client, error) { if niriSocket, ok := os.LookupEnv("NIRI_SOCKET"); ok { return GetClientBySocketPath(niriSocket) } if display, ok := os.LookupEnv("WAYLAND_DISPLAY"); ok { displayInt, err := strconv.Atoi(display) if err != nil { return nil, err } return GetClientByDisplay(displayInt) } return nil, ErrClientNotFound } func GetAllClients() ([]*Client, error) { runtimeDir := xdg.RuntimeDir entries, err := os.ReadDir(runtimeDir) if err != nil { return nil, err } return slices.Filter(slices.Map(entries, parseEntryAsClient), notNull), nil } func GetClientBySocketPath(socketPath string) (*Client, error) { baseName := filepath.Base(socketPath) matches := niriSocketFilenamePattern.FindStringSubmatch(baseName) if matches == nil { return nil, errors.New("invalid niri socket path") } display, err := strconv.Atoi(matches[1]) if err != nil { return nil, err } pid, err := strconv.Atoi(matches[2]) if err != nil { return nil, err } return &Client{ display: display, pid: pid, }, nil } func (c Client) GetSocketPath() string { return filepath.Join(xdg.RuntimeDir, fmt.Sprintf(niriSocketFilenameFormat, c.display, c.pid)) }