detached 602baa7

dial.go RawHistoryFolder

1package xturbo
2
3import (
4	"encoding/binary"
5	"errors"
6	"fmt"
7	"io"
8	"os"
9	"path/filepath"
10	"strconv"
11	"strings"
12
13	"golang.org/x/sys/unix"
14)
15
16const (
17	authMagicCookie = "MIT-MAGIC-COOKIE-1"
18	familyLocal     = 256    // FamilyLocal in Xauthority files
19	familyWild      = 0xffff // FamilyWild: matches any address
20	socketDir       = "/tmp/.X11-unix/X"
21)
22
23// Dial connects to a local X server named by a DISPLAY string (":N", ":N.S",
24// "unix:N"; empty means $DISPLAY). It tries the abstract socket
25// "@/tmp/.X11-unix/XN" first, then the filesystem socket, and authenticates
26// with MIT-MAGIC-COOKIE-1 from $XAUTHORITY or ~/.Xauthority when a matching
27// entry exists. Meant for tests and tools; the XWM uses NewConn.
28func Dial(display string) (*Conn, error) {
29	if display == "" {
30		display = os.Getenv("DISPLAY")
31	}
32	num, err := parseDisplay(display)
33	if err != nil {
34		return nil, err
35	}
36	fd, err := dialUnix(num)
37	if err != nil {
38		return nil, err
39	}
40	var cookie []byte
41	if path := xauthorityPath(); path != "" {
42		host, _ := os.Hostname()
43		cookie, err = readCookie(path, host, num)
44		if err != nil && !errors.Is(err, os.ErrNotExist) {
45			_ = unix.Close(fd)
46			return nil, err
47		}
48	}
49	if cookie == nil {
50		return newConn(fd, "", nil)
51	}
52	return newConn(fd, authMagicCookie, cookie)
53}
54
55// parseDisplay returns the display number of a local DISPLAY string. The
56// screen suffix is accepted and ignored (Root is always the first screen).
57func parseDisplay(display string) (int, error) {
58	host, rest, ok := strings.Cut(display, ":")
59	if !ok || (host != "" && host != "unix") || strings.HasPrefix(rest, ":") {
60		return 0, fmt.Errorf("xturbo: unsupported DISPLAY %q (local :N or unix:N only)", display)
61	}
62	numStr, screen, hasScreen := strings.Cut(rest, ".")
63	num, err := strconv.ParseUint(numStr, 10, 31)
64	if err != nil {
65		return 0, fmt.Errorf("xturbo: bad DISPLAY %q: display number", display)
66	}
67	if hasScreen {
68		if _, err := strconv.ParseUint(screen, 10, 31); err != nil {
69			return 0, fmt.Errorf("xturbo: bad DISPLAY %q: screen number", display)
70		}
71	}
72	return int(num), nil
73}
74
75// dialUnix connects a blocking stream socket to display num, abstract
76// namespace first (as libxcb on Linux), then the filesystem path.
77func dialUnix(num int) (int, error) {
78	path := socketDir + strconv.Itoa(num)
79	var firstErr error
80	for _, name := range []string{"@" + path, path} {
81		fd, err := unix.Socket(unix.AF_UNIX, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
82		if err != nil {
83			return -1, os.NewSyscallError("socket", err)
84		}
85		for {
86			err = unix.Connect(fd, &unix.SockaddrUnix{Name: name})
87			if err != unix.EINTR {
88				break
89			}
90		}
91		if err == nil {
92			return fd, nil
93		}
94		_ = unix.Close(fd)
95		if firstErr == nil {
96			firstErr = err
97		}
98	}
99	return -1, fmt.Errorf("xturbo: connect %s: %w", path, firstErr)
100}
101
102func xauthorityPath() string {
103	if p := os.Getenv("XAUTHORITY"); p != "" {
104		return p
105	}
106	if home, err := os.UserHomeDir(); err == nil {
107		return filepath.Join(home, ".Xauthority")
108	}
109	return ""
110}
111
112// readCookie returns the MIT-MAGIC-COOKIE-1 for a local display from an
113// Xauthority file: the first entry of family local with address host (or
114// FamilyWild), display number num (or empty, meaning any). It returns nil,
115// nil when no entry matches.
116func readCookie(path, host string, num int) ([]byte, error) {
117	data, err := os.ReadFile(path)
118	if err != nil {
119		return nil, err
120	}
121	return findCookie(data, host, num)
122}
123
124func findCookie(data []byte, host string, num int) ([]byte, error) {
125	want := strconv.Itoa(num)
126	for len(data) > 0 {
127		if len(data) < 2 {
128			return nil, malformed("Xauthority entry truncated")
129		}
130		family := binary.BigEndian.Uint16(data)
131		data = data[2:]
132		var fields [4][]byte // address, number, name, data
133		for i := range fields {
134			if len(data) < 2 {
135				return nil, fmt.Errorf("%w: Xauthority entry truncated: %w", ErrMalformed, io.ErrUnexpectedEOF)
136			}
137			n := int(binary.BigEndian.Uint16(data))
138			if len(data) < 2+n {
139				return nil, fmt.Errorf("%w: Xauthority field truncated: %w", ErrMalformed, io.ErrUnexpectedEOF)
140			}
141			fields[i] = data[2 : 2+n]
142			data = data[2+n:]
143		}
144		addr, number, name, cookie := fields[0], fields[1], fields[2], fields[3]
145		hostOK := family == familyWild || (family == familyLocal && string(addr) == host)
146		numOK := len(number) == 0 || string(number) == want
147		if hostOK && numOK && string(name) == authMagicCookie {
148			return append([]byte(nil), cookie...), nil
149		}
150	}
151	return nil, nil
152}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 1

↑↓ moveenter openesc close