unmanaged.go RawHistoryFolder

1package core
2
3import (
4	"context"
5	"slices"
6
7	"github.com/bnema/neferwl/internal/ports"
8)
9
10// unmanagedState is a mapped override-redirect X11 window: an absolute layout
11// rect chosen by the client, no parent, never keyboard-focused. rect is in
12// global logical coordinates.
13type unmanagedState struct {
14	id      WindowID
15	rect    Rect
16	noInput bool
17}
18
19// unmanagedEvent applies an unmanaged window event. changed is true when the
20// scene or the pointer target may differ.
21func (c *Core) unmanagedEvent(ctx context.Context, ev ports.ClientEvent) (changed bool, err error) {
22	switch v := ev.(type) {
23	case ports.UnmanagedMapped:
24		r := Rect{X: v.X, Y: v.Y, W: v.Width, H: v.Height}
25		if u := c.unmanaged[v.ID]; u != nil {
26			if u.rect == r && u.noInput == v.NoInput {
27				return false, nil
28			}
29			u.rect, u.noInput = r, v.NoInput
30		} else {
31			c.unmanaged[v.ID] = &unmanagedState{id: v.ID, rect: r, noInput: v.NoInput}
32			c.unmanagedOrder = append(c.unmanagedOrder, v.ID)
33		}
34	case ports.UnmanagedMoved:
35		u := c.unmanaged[v.ID]
36		r := Rect{X: v.X, Y: v.Y, W: v.Width, H: v.Height}
37		if u == nil || u.rect == r {
38			return false, nil
39		}
40		u.rect = r
41	case ports.UnmanagedUnmapped:
42		if c.unmanaged[v.ID] == nil {
43			return false, nil
44		}
45		delete(c.unmanaged, v.ID)
46		c.unmanagedOrder = slices.DeleteFunc(c.unmanagedOrder, func(id WindowID) bool { return id == v.ID })
47		c.windows.drop(v.ID)
48		if err := c.dataDragGone(ctx, v.ID); err != nil {
49			return false, err
50		}
51		// rehit does nothing while a button grab or drag is held: do not
52		// leave the pointer and grab on the dead window.
53		if c.grab == v.ID {
54			c.grab = 0
55		}
56		if c.pointer == v.ID {
57			c.pointer = 0
58			if err := c.pointerFocus(ctx, ports.PointerFocus{}); err != nil {
59				return false, err
60			}
61		}
62	}
63	// The window under a still pointer may have changed.
64	return true, c.rehit(ctx)
65}
66
67// unmanagedScreen is the screen showing u: the one containing its origin, or
68// the one the pointer clamp would pick when the origin is in a hole.
69func (c *Core) unmanagedScreen(u *unmanagedState) *screen {
70	x, y := float64(u.rect.X), float64(u.rect.Y)
71	i := c.screenAt(x, y)
72	if i < 0 {
73		cx, cy := c.clampPointer(x, y, x, y)
74		i = c.screenAt(cx, cy)
75	}
76	if i < 0 {
77		return nil
78	}
79	return c.screens[i]
80}
81
82// sceneUnmanaged appends the unmanaged windows shown on sc, bottom to top,
83// drawn as popups over the layers. They are hidden while sc previews.
84func (c *Core) sceneUnmanaged(out []ports.SceneWindow, sc *screen) []ports.SceneWindow {
85	if len(c.unmanagedOrder) == 0 || sc.mon.previewing() {
86		return out
87	}
88	for _, id := range c.unmanagedOrder {
89		u := c.unmanaged[id]
90		if u == nil || c.unmanagedScreen(u) != sc {
91			continue
92		}
93		r := u.rect
94		r.X, r.Y = r.X-sc.x, r.Y-sc.y
95		out = append(out, ports.SceneWindow{ID: id, Rect: r, Popup: true, OverLayers: true})
96	}
97	return out
98}
99
100// unmanagedAt is the topmost unmanaged window taking the pointer at the
101// screen-local point. None while the pointer is locked or the screen previews.
102func (c *Core) unmanagedAt(sc *screen, lx, ly float64) (WindowID, float64, float64) {
103	if len(c.unmanagedOrder) == 0 || c.constraint.Mode == ports.ConstraintLock || sc.mon.previewing() {
104		return 0, 0, 0
105	}
106	for i := len(c.unmanagedOrder) - 1; i >= 0; i-- {
107		u := c.unmanaged[c.unmanagedOrder[i]]
108		if u == nil || u.noInput || c.unmanagedScreen(u) != sc {
109			continue
110		}
111		x, y := lx-float64(u.rect.X-sc.x), ly-float64(u.rect.Y-sc.y)
112		if x >= 0 && x < float64(u.rect.W) && y >= 0 && y < float64(u.rect.H) && c.acceptsInput(u.id, x, y) {
113			return u.id, x, y
114		}
115	}
116	return 0, 0, 0
117}

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 23

Tags 10

↑↓ moveenter openesc close