v0.1.0 47aaa0f

events.go RawHistoryFolder

1//go:build linux
2
3package wayland
4
5import (
6	"fmt"
7	"github.com/bnema/wlturbo/protocol/linuxdmabuf"
8	"net"
9	"time"
10)
11
12type EventKind uint8
13
14const (
15	ConfigureSize EventKind = iota + 1
16	ConfigureSerial
17	PreferredScale
18	BufferScale
19	CloseEvent
20	FrameEvent
21	BufferRelease
22	TransportError
23	SeatCapabilities
24	SeatRemoved
25	InputFrame
26	InputKeymap
27	InputFocusIn
28	InputFocusOut
29	InputKey
30	InputModifiers
31	InputRepeatInfo
32	FeedbackError
33	FeedbackMainDevice
34	FeedbackTranche
35	FeedbackFormats
36	ClipboardInput
37	IMEInput
38)
39
40type Event struct {
41	Kind                             EventKind
42	Width, Height                    int32
43	Serial, Scale                    uint32
44	Factor                           int32
45	BufferID                         uint64
46	Err                              error
47	Device                           uint64
48	Tranche, Formats                 []linuxdmabuf.FormatEntry
49	Inputs                           []Input
50	Clipboard                        ClipboardEvent
51	IME                              IMEEvent
52	Capabilities, Format, Size, Code uint32
53	FD                               int // owned by ApplyInput or CloseEventFD when Kind == InputKeymap
54	Pressed                          bool
55	Mods                             [4]uint32
56	Rate, Delay                      int32
57}
58
59// StartReader launches exactly one blocking Wayland reader. Registered callbacks
60// only send typed values to Events; Apply is called by the session loop alone.
61func (w *Window) StartReader() <-chan Event {
62	w.readerOnce.Do(func() {
63		w.readerStarted.Store(true)
64		w.readerDone = make(chan struct{})
65		go func() {
66			defer close(w.readerDone)
67			for {
68				err := w.Display.Dispatch()
69				if err != nil {
70					select {
71					case <-w.readerStop:
72						return
73					case w.events <- Event{Kind: TransportError, Err: err}:
74						return
75					}
76				}
77			}
78		}()
79	})
80	return w.events
81}
82
83// Events exposes the reader's typed event queue to the session owner.
84func (w *Window) Events() <-chan Event { return w.events }
85
86// PostClipboard and PostIME enqueue transport callbacks for the owner loop.
87func (w *Window) PostClipboard(ev ClipboardEvent) { w.post(Event{Kind: ClipboardInput, Clipboard: ev}) }
88func (w *Window) PostIME(ev IMEEvent)             { w.post(Event{Kind: IMEInput, IME: ev}) }
89
90func (w *Window) post(ev Event) {
91	select {
92	case <-w.readerStop:
93		return
94	case w.events <- ev:
95	}
96}
97
98// postFD closes an undeliverable keymap descriptor rather than leaking it.
99func (w *Window) postFD(ev Event) {
100	select {
101	case <-w.readerStop:
102		CloseEventFD(ev)
103	case w.events <- ev:
104	}
105}
106
107// BufferReleased is safe to call from the reader callback: it never touches
108// Pool, Session, or Vulkan. The serial loop records the optional event.
109func (w *Window) BufferReleased(id uint64) {
110	w.post(Event{Kind: BufferRelease, BufferID: id})
111}
112func (w *Window) Apply(ev Event) error {
113	switch ev.Kind {
114	case ConfigureSize:
115		if ev.Width > 0 && ev.Height > 0 && (w.Width != ev.Width || w.Height != ev.Height) {
116			w.Width, w.Height, w.Dirty = ev.Width, ev.Height, true
117		}
118	case ConfigureSerial:
119		if err := w.XdgSurface.AckConfigure(ev.Serial); err != nil {
120			return err
121		}
122		w.Configured, w.FrameReady = true, true
123	case PreferredScale:
124		if ev.Scale == 0 {
125			return fmt.Errorf("invalid preferred scale zero")
126		}
127		if ev.Scale%120 != 0 && w.Viewport == nil {
128			return &CapabilityError{Name: "wp_viewporter", Cause: fmt.Errorf("fractional scale %d/120 requires viewporter", ev.Scale)}
129		}
130		if scale := float64(ev.Scale) / 120; w.Scale != scale {
131			w.Scale = scale
132			w.Dirty = true
133		}
134	case BufferScale:
135		if w.Fractional == nil && ev.Factor > 0 && w.Scale != float64(ev.Factor) {
136			w.Scale = float64(ev.Factor)
137			w.Dirty = true
138		}
139	case CloseEvent:
140		w.Closed = true
141	case FrameEvent:
142		w.FrameReady = true
143	case FeedbackError:
144		if w.feedbackErr == nil {
145			w.feedbackErr = ev.Err
146		}
147		return ev.Err
148	case FeedbackMainDevice:
149		w.MainDevice = ev.Device
150	case FeedbackTranche:
151		w.Tranches = append(w.Tranches, ev.Tranche)
152	case FeedbackFormats:
153		w.Formats = ev.Formats
154	case SeatCapabilities, SeatRemoved:
155		return w.ApplySeat(ev)
156	case ClipboardInput, IMEInput:
157		// These are applied by the session owner after Window.Apply.
158	case InputFrame, InputKeymap, InputFocusIn, InputFocusOut, InputKey, InputModifiers, InputRepeatInfo:
159		if w.inputAdapter == nil {
160			var err error
161			w.inputAdapter, err = NewInputAdapter("")
162			if err != nil {
163				CloseEventFD(ev)
164				return err
165			}
166		}
167		inputs, err := w.inputAdapter.ApplyInput(ev, time.Now())
168		w.queueInput(inputs)
169		return err
170	case BufferRelease: // consumed by the session owner; no pixel ownership implication
171	case TransportError:
172		if ev.Err != nil {
173			return ev.Err
174		}
175		return net.ErrClosed
176	default:
177		return fmt.Errorf("unknown Wayland event %d", ev.Kind)
178	}
179	return nil
180}

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 12

↑↓ moveenter openesc close