v0.3.0 cf370a0

popup.go RawHistoryFolder

1package wayland
2
3import (
4	"github.com/bnema/go-wayland-bindings/server/wayland"
5	"github.com/bnema/go-wayland-bindings/server/xdgshell"
6	"github.com/bnema/neferwl/internal/ports"
7	"github.com/bnema/purego-libwayland/server"
8)
9
10// popup is the xdg_popup role of a window. Core places it (PopupRequest →
11// ConfigurePopup); it maps on the first buffer after its configure is
12// acked, and is drawn over its parent like a window without border.
13type popup struct {
14	w        *window
15	resource *xdgshell.Popup
16	parent   *window
17	// layer is the parent set through zwlr_layer_surface_v1.get_popup,
18	// for a popup created without an xdg parent.
19	layer *layerSurface
20	pos   ports.Positioner
21	grab  bool
22	// sent is set once core got the PopupRequest; done once dismissed.
23	sent, done bool
24}
25
26func (s *Server) newPopup(x *xdgSurface, r *xdgshell.Surface, id uint32, parentXDG *xdgshell.Surface, pos *xdgshell.Positioner) {
27	p, ok := s.positionerOf(x.wm, pos)
28	if !ok {
29		return
30	}
31	var parent *window
32	if parentXDG != nil {
33		for _, w := range s.windows {
34			if w.xdg.resource != nil && w.xdg.resource.Resource == parentXDG.Resource {
35				parent = w
36			}
37		}
38	}
39	w := &window{id: s.nextWindow, xdg: x}
40	pp := &popup{w: w, parent: parent, pos: p}
41	res, err := xdgshell.NewPopup(r.Client(), r.Version(), id, pp)
42	if err != nil {
43		return
44	}
45	pp.resource, w.popup = res, pp
46	s.nextWindow++
47	s.windows[w.id] = w
48	x.window = w
49	x.surface.role = func(buffer bool) {
50		if x.surface.destroyed {
51			w.unmap()
52			return
53		}
54		switch {
55		case !buffer && !w.mapped && !x.configured && !pp.done:
56			// Without a parent (xdg-shell lets clients set it through
57			// layer-shell before the first commit) the popup has nowhere
58			// to go.
59			if pp.parentID() == 0 {
60				pp.dismiss()
61				return
62			}
63			if !pp.sent {
64				pp.sent = true
65				s.emit(ports.PopupRequest{ID: w.id, Parent: pp.parentID(), Positioner: pp.pos, Grab: pp.grab})
66			}
67		case buffer && !w.mapped && x.acked && !pp.done:
68			w.mapped = true
69			s.emit(ports.PopupMapped{ID: w.id})
70			s.log.Debug().Uint64("id", uint64(w.id)).Uint64("parent", uint64(pp.parentID())).Msg("popup mapped")
71		case !buffer && w.mapped:
72			// Unmapped, it may map again after a new initial commit.
73			w.unmap()
74			pp.sent, pp.grab = false, false
75		}
76	}
77	res.OnDestroy = func() {
78		// Core forgets the popup, placed or mapped.
79		if !w.mapped && pp.sent && !pp.done {
80			s.emit(ports.WindowUnmapped{ID: w.id})
81		}
82		w.unmap()
83		pp.done = true
84		x.surface.dropQueue()
85		x.surface.current, x.surface.next.buffer = nil, nil
86		x.surface.next.attached = false
87		delete(s.windows, w.id)
88		x.window = nil
89		x.surface.role = nil
90		x.configured, x.acked, x.serials = false, false, nil
91		x.geometry, x.pendingGeometry = ports.Rect{}, ports.Rect{}
92	}
93}
94
95func (*popup) Destroy(*xdgshell.Popup) {}
96
97// parentID is the window or layer surface the popup hangs from, 0 if none.
98func (p *popup) parentID() ports.WindowID {
99	switch {
100	case p.parent != nil:
101		return p.parent.id
102	case p.layer != nil:
103		return p.layer.id
104	}
105	return 0
106}
107
108// Grab is only valid before the first commit; core then gives the popup the
109// keyboard and closes it on a click outside its popup chain.
110func (p *popup) Grab(r *xdgshell.Popup, _ *wayland.Seat, serial uint32) {
111	if p.sent || p.w.mapped {
112		r.PostError(uint32(xdgshell.PopupErrorInvalidGrab), "grab after the popup was committed")
113		return
114	}
115	// Only a grab answering the client's last press may take the keyboard,
116	// and a popup over a popup grabs only if its parent does.
117	s := p.w.xdg.server
118	fresh := serial == s.seat.press && s.seat.pressClient == r.Client()
119	if !fresh || p.parentID() == 0 || !s.topGrab(r.Client(), p.parent) {
120		p.dismiss()
121		return
122	}
123	p.grab = true
124}
125
126func (p *popup) Reposition(r *xdgshell.Popup, pos *xdgshell.Positioner, token uint32) {
127	s := p.w.xdg.server
128	next, ok := s.positionerOf(p.w.xdg.wm, pos)
129	if !ok || p.done {
130		return
131	}
132	p.pos = next
133	if p.sent {
134		s.emit(ports.PopupRequest{ID: p.w.id, Parent: p.parentID(), Positioner: next, Grab: p.grab, Reposition: true, Token: token})
135	}
136}
137
138// configure sends core's placement: xdg_popup.configure then
139// xdg_surface.configure.
140func (p *popup) configure(c ports.ConfigurePopup) {
141	if p.done || !p.resource.Resource.Alive() || !p.w.xdg.resource.Resource.Alive() {
142		return
143	}
144	if c.Reposition && p.resource.Version() >= 3 {
145		p.resource.SendRepositioned(c.Token)
146	}
147	p.resource.SendConfigure(int32(c.Rect.X), int32(c.Rect.Y), int32(c.Rect.W), int32(c.Rect.H))
148	s := p.w.xdg.server
149	s.serial++
150	p.w.xdg.resource.SendConfigure(s.serial)
151	p.w.xdg.serials = append(p.w.xdg.serials, s.serial)
152	p.w.xdg.configured = true
153	if surf := p.w.xdg.surface; surf != nil {
154		surf.sendTreeScale()
155	}
156}
157
158// topGrab reports whether a grabbing popup of client may go on parent:
159// parent is the client's newest live grabbing popup, or a toplevel (nil
160// for a layer surface) when it has none.
161func (s *Server) topGrab(client server.Client, parent *window) bool {
162	var top *window
163	for _, w := range s.windows {
164		if w.popup != nil && w.popup.grab && !w.popup.done && w.xdg.resource.Client() == client && (top == nil || w.id > top.id) {
165			top = w
166		}
167	}
168	if top == nil {
169		return parent == nil || parent.popup == nil
170	}
171	return top == parent
172}
173
174// dismiss sends popup_done once; the client then destroys the popup.
175func (p *popup) dismiss() {
176	if p.done {
177		return
178	}
179	p.done = true
180	if p.resource.Resource.Alive() {
181		p.resource.SendPopupDone()
182	}
183	p.w.unmap()
184}

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 25

Tags 10

↑↓ moveenter openesc close