v0.10.0 d5d679d

main.go RawHistoryFolder

1// Command testime is a minimal zwp_input_method_v2 client for testing text
2// input. Each time a text input is activated it sends a preedit string,
3// then commits the final text:
4//
5//	WAYLAND_DISPLAY=wayland-1 go run ./examples/testime -preedit nihon -commit 日本
6package main
7
8import (
9	"errors"
10	"flag"
11	"fmt"
12	"os"
13	"time"
14
15	"github.com/bnema/go-wayland-bindings/server/inputmethod"
16	"github.com/bnema/wlturbo"
17	clientcore "github.com/bnema/wlturbo/protocol/core"
18	clientime "github.com/bnema/wlturbo/protocol/inputmethod"
19)
20
21func main() {
22	preedit := flag.String("preedit", "nihon", "preedit string sent first")
23	commit := flag.String("commit", "日本", "text committed after the preedit")
24	delay := flag.Duration("delay", 100*time.Millisecond, "time between preedit and commit")
25	count := flag.Int("count", 0, "exit after this many commits; 0 runs until the compositor closes")
26	flag.Parse()
27	if err := run(*preedit, *commit, *delay, *count); err != nil {
28		fmt.Fprintln(os.Stderr, "testime:", err)
29		os.Exit(1)
30	}
31}
32
33// inputMethod follows the activate/done cycle of zwp_input_method_v2.
34type inputMethod struct {
35	wlturbo.BaseProxy
36	c          *wlturbo.Display
37	dones      uint32
38	active     bool // state of the last done
39	activating bool // activate seen, not yet applied by done
40	unavail    bool
41	activated  bool // activate seen since the last done
42	serve      bool // an activation applied by done, not yet served
43}
44
45// EventSignature delegates to the generated client schema, including events
46// this example intentionally ignores and their descriptor ownership.
47func (*inputMethod) EventSignature(op uint16) (string, bool) {
48	return (&clientime.InputMethod{}).EventSignature(op)
49}
50
51func (m *inputMethod) Dispatch(e *wlturbo.Event) {
52	switch uint32(e.Opcode) {
53	case inputmethod.ZwpInputMethodV2EventActivate:
54		// Every activate starts a new text input session, even while
55		// active: an app enabling its text input again gets one.
56		m.activating, m.activated = true, true
57	case inputmethod.ZwpInputMethodV2EventDeactivate:
58		m.activating, m.activated = false, false
59	case inputmethod.ZwpInputMethodV2EventDone:
60		// Requests only count for an activation once its done is in:
61		// the compositor drops commits with an older serial.
62		m.dones++
63		m.active = m.activating
64		m.serve = m.serve && m.active || m.activated
65		m.activated = false
66	case inputmethod.ZwpInputMethodV2EventUnavailable:
67		m.unavail = true
68	}
69}
70
71// ignored drops the events of an object testime does not use.
72type ignored struct{ wlturbo.BaseProxy }
73
74func (*ignored) Dispatch(*wlturbo.Event) {}
75
76func (*ignored) EventSignature(op uint16) (string, bool) {
77	return (&clientcore.Seat{}).EventSignature(op)
78}
79
80func (m *inputMethod) send(op uint32, args ...any) error {
81	return m.c.SendRequest(m.ID(), uint16(op), args...)
82}
83
84func run(preedit, commit string, delay time.Duration, count int) error {
85	c, err := wlturbo.Connect("")
86	if err != nil {
87		return err
88	}
89	defer c.Close()
90	if err := c.Roundtrip(); err != nil {
91		return err
92	}
93	bind := func(iface string) (uint32, error) {
94		g, ok := c.Registry().FindGlobal(iface)
95		if !ok {
96			return 0, fmt.Errorf("compositor has no %s", iface)
97		}
98		return c.Registry().BindID(g.Name, g.Interface, 1)
99	}
100	seat, err := bind("wl_seat")
101	if err != nil {
102		return err
103	}
104	// wlturbo fails on events for unregistered objects: the seat sends some.
105	seatProxy := &ignored{}
106	seatProxy.SetID(seat)
107	seatProxy.SetContext(c.Context())
108	seatProxy.SetVersion(1)
109	c.Context().Register(seatProxy)
110	manager, err := bind("zwp_input_method_manager_v2")
111	if err != nil {
112		return err
113	}
114	m := &inputMethod{c: c}
115	m.SetID(c.AllocateID())
116	m.SetContext(c.Context())
117	m.SetVersion(1)
118	c.Context().Register(m)
119	if err := c.SendRequest(manager, uint16(inputmethod.ZwpInputMethodManagerV2RequestGetInputMethod), seat, m.ID()); err != nil {
120		return err
121	}
122	fmt.Fprintln(os.Stderr, "testime: ready")
123	for commits := 0; ; {
124		if err := c.Dispatch(); err != nil {
125			return err
126		}
127		if m.unavail {
128			return errors.New("another input method is running")
129		}
130		if !m.serve {
131			continue
132		}
133		m.serve = false
134		if err := m.send(inputmethod.ZwpInputMethodV2RequestSetPreeditString, preedit, int32(len(preedit)), int32(len(preedit))); err != nil {
135			return err
136		}
137		if err := m.send(inputmethod.ZwpInputMethodV2RequestCommit, m.dones); err != nil {
138			return err
139		}
140		time.Sleep(delay)
141		// Events that arrived meanwhile update the done count first.
142		if err := c.Roundtrip(); err != nil {
143			return err
144		}
145		if !m.active {
146			continue
147		}
148		if err := m.send(inputmethod.ZwpInputMethodV2RequestCommitString, commit); err != nil {
149			return err
150		}
151		if err := m.send(inputmethod.ZwpInputMethodV2RequestCommit, m.dones); err != nil {
152			return err
153		}
154		if err := c.Roundtrip(); err != nil {
155			return err
156		}
157		fmt.Fprintf(os.Stderr, "testime: committed %q\n", commit)
158		if commits++; count > 0 && commits == count {
159			return nil
160		}
161	}
162}

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