v0.1.0 4a0dffe

core.go RawHistoryFolder

1package wlturbo
2
3import (
4	"encoding/binary"
5	"errors"
6
7	// "log"
8	"sync"
9	"sync/atomic"
10)
11
12// Context provides a compatibility layer for wl.Context
13type Context struct {
14	display *Display
15	proxies sync.Map // map[uint32]Proxy
16	closed  atomic.Bool
17}
18
19// Proxy interface for Wayland protocol objects
20type Proxy interface {
21	Object
22	SetID(uint32)
23	Context() *Context
24	Dispatch(*Event)
25}
26
27// BaseProxy provides base implementation for protocol objects
28type BaseProxy struct {
29	id      uint32
30	context *Context
31}
32
33// Event represents a Wayland protocol event
34type Event struct {
35	ProxyID uint32
36	Opcode  uint16
37	data    []byte
38	offset  int
39}
40
41// Data returns the raw event data
42func (e *Event) Data() []byte {
43	return e.data
44}
45
46// Offset returns the current read offset
47func (e *Event) Offset() int {
48	return e.offset
49}
50
51// NewContext creates a new context from a display
52func NewContext(display *Display) *Context {
53	return &Context{
54		display: display,
55	}
56}
57
58// SendRequest sends a request through the context
59func (c *Context) SendRequest(proxy Proxy, opcode uint32, args ...interface{}) error {
60	if c.closed.Load() {
61		return errors.New("context is closed")
62	}
63	return c.display.SendRequest(proxy.ID(), uint16(opcode), args...)
64}
65
66// SendRequestWithFDs sends a request with file descriptors through the context
67func (c *Context) SendRequestWithFDs(proxy Proxy, opcode uint32, fds []int, args ...interface{}) error {
68	if c.closed.Load() {
69		return errors.New("context is closed")
70	}
71	return c.display.SendRequestWithFDs(proxy.ID(), uint16(opcode), fds, args...)
72}
73
74// Register registers a proxy object
75func (c *Context) Register(proxy Proxy) {
76	if proxy != nil && proxy.ID() != 0 {
77		c.proxies.Store(proxy.ID(), proxy)
78		c.display.objects.Store(proxy.ID(), proxy)
79		// Debug large IDs
80		// if proxy.ID() > 1000000 {
81		// 	log.Printf("wlclient: WARNING: Registering proxy with suspicious ID %d (0x%x)", proxy.ID(), proxy.ID())
82		// } else if proxy.ID() == 5 {
83		// 	log.Printf("wlclient: Registered output manager proxy with ID 5")
84		// }
85	}
86}
87
88// Unregister removes a proxy object
89func (c *Context) Unregister(proxy Proxy) {
90	if proxy != nil {
91		c.proxies.Delete(proxy.ID())
92		c.display.objects.Delete(proxy.ID())
93	}
94}
95
96// UnregisterID removes a proxy object by ID (overloaded for compatibility)
97func (c *Context) UnregisterID(id uint32) {
98	c.proxies.Delete(id)
99	c.display.objects.Delete(id)
100}
101
102// AllocateID allocates a new object ID
103func (c *Context) AllocateID() uint32 {
104	return c.display.AllocateID()
105}
106
107// Close closes the context
108func (c *Context) Close() error {
109	c.closed.Store(true)
110	return c.display.Close()
111}
112
113// RunTill runs the event loop until the callback fires
114func (c *Context) RunTill(callback Object) error {
115	if c.closed.Load() {
116		return errors.New("context is closed")
117	}
118
119	// Special case: if callback is a sync object, do a roundtrip
120	if _, ok := callback.(*callbackObject); ok {
121		return c.display.Roundtrip()
122	}
123
124	// Otherwise, process events until the callback is unregistered
125	// This assumes the callback will unregister itself when done
126	for {
127		if err := c.display.Dispatch(); err != nil {
128			return err
129		}
130
131		// Check if callback is still registered
132		if _, ok := c.proxies.Load(callback.ID()); !ok {
133			// Callback has been unregistered, we're done
134			return nil
135		}
136	}
137}
138
139// BaseProxy methods
140
141// ID returns the proxy's object ID
142func (p *BaseProxy) ID() uint32 {
143	return p.id
144}
145
146// SetId sets the proxy's object ID
147func (p *BaseProxy) SetID(id uint32) {
148	p.id = id
149}
150
151// Context returns the proxy's context
152func (p *BaseProxy) Context() *Context {
153	return p.context
154}
155
156// SetContext sets the proxy's context
157func (p *BaseProxy) SetContext(ctx *Context) {
158	p.context = ctx
159}
160
161// Dispatch default implementation (does nothing)
162func (p *BaseProxy) Dispatch(event *Event) {
163	// Default implementation does nothing
164}
165
166// Event methods for extracting data
167
168// Uint32 reads a uint32 from the event
169func (e *Event) Uint32() uint32 {
170	if e.offset+4 > len(e.data) {
171		return 0
172	}
173	val := binary.LittleEndian.Uint32(e.data[e.offset:])
174	e.offset += 4
175	return val
176}
177
178// Int32 reads an int32 from the event
179func (e *Event) Int32() int32 {
180	return int32(e.Uint32())
181}
182
183// Fixed reads a fixed-point value from the event
184func (e *Event) Fixed() Fixed {
185	return Fixed(e.Int32())
186}
187
188// String reads a string from the event
189func (e *Event) String() string {
190	if e.offset+4 > len(e.data) {
191		return ""
192	}
193	strlen := e.Uint32()
194	if strlen == 0 || e.offset+int(strlen) > len(e.data) {
195		return ""
196	}
197	// String includes null terminator in length
198	str := string(e.data[e.offset : e.offset+int(strlen)-1])
199	// Advance offset including padding
200	totalLen := strlen
201	padding := (4 - (totalLen % 4)) % 4
202	e.offset += int(totalLen + padding)
203	return str
204}
205
206// Array reads a byte array from the event
207func (e *Event) Array() []byte {
208	if e.offset+4 > len(e.data) {
209		return nil
210	}
211	arrlen := e.Uint32()
212	if arrlen == 0 || e.offset+int(arrlen) > len(e.data) {
213		return nil
214	}
215	arr := make([]byte, arrlen)
216	copy(arr, e.data[e.offset:e.offset+int(arrlen)])
217	// Advance offset including padding
218	padding := (4 - (arrlen % 4)) % 4
219	e.offset += int(arrlen + padding)
220	return arr
221}
222
223// Fd reads a file descriptor from the event
224func (e *Event) Fd() uintptr {
225	// File descriptors are passed out-of-band via SCM_RIGHTS
226	// We read them from our lock-free queue
227	if fd, ok := GetNextFD(); ok {
228		return uintptr(fd)
229	}
230	// Fallback: read placeholder value from message
231	_ = e.Uint32()
232	return 0
233}
234
235// NewId reads a new object ID from the event
236func (e *Event) NewID() Proxy {
237	id := e.Uint32()
238	// Return a basic proxy with just the ID
239	return &BaseProxy{id: id}
240}
241
242// Proxy reads an existing proxy reference from the event
243func (e *Event) Proxy() Proxy {
244	id := e.Uint32()
245	// Would need context reference to look up actual proxy
246	return &BaseProxy{id: id}
247}
248
249// Registry handler interfaces for compatibility
250
251// RegistryGlobalHandler interface
252type RegistryGlobalHandler interface {
253	HandleRegistryGlobal(event RegistryGlobalEvent)
254}
255
256// RegistryGlobalRemoveHandler interface
257type RegistryGlobalRemoveHandler interface {
258	HandleRegistryGlobalRemove(event RegistryGlobalRemoveEvent)
259}
260
261// RegistryGlobalEvent represents a registry global announcement
262type RegistryGlobalEvent struct {
263	Registry  *Registry
264	Name      uint32
265	Interface string
266	Version   uint32
267}
268
269// RegistryGlobalRemoveEvent represents a registry global removal
270type RegistryGlobalRemoveEvent struct {
271	Registry *Registry
272	Name     uint32
273}
274
275// AddGlobalHandler adds a global handler to the registry
276func (r *Registry) AddGlobalHandler(handler RegistryGlobalHandler) {
277	// Store the handler to be called for ALL globals
278	r.mu.Lock()
279	defer r.mu.Unlock()
280
281	// Initialize handlers map if needed
282	if r.handlers == nil {
283		r.handlers = make(map[string]GlobalHandler)
284	}
285
286	r.AddHandler("*", func(registry *Registry, name uint32, version uint32) {
287		global, ok := r.FindGlobalByName(name)
288		if ok {
289			event := RegistryGlobalEvent{
290				Registry:  r,
291				Name:      name,
292				Interface: global.Interface,
293				Version:   version,
294			}
295			handler.HandleRegistryGlobal(event)
296		}
297	})
298}
299
300// AddGlobalRemoveHandler adds a global remove handler (placeholder)
301func (r *Registry) AddGlobalRemoveHandler(handler RegistryGlobalRemoveHandler) {
302	// Would need to implement removal handling
303}
304
305// FindGlobalByName finds a global by its name ID
306func (r *Registry) FindGlobalByName(name uint32) (Global, bool) {
307	r.mu.RLock()
308	defer r.mu.RUnlock()
309
310	if global, ok := r.globals[name]; ok {
311		return global, true
312	}
313	return Global{}, false
314}
315
316// Display compatibility methods
317
318// Context returns a context for this display
319func (d *Display) Context() *Context {
320	return d.context
321}
322
323// GetRegistry returns the registry (compatibility)
324func (d *Display) GetRegistry() *Registry {
325	return d.registry
326}
327
328// Sync creates a sync callback
329func (d *Display) Sync() (Object, error) {
330	callbackID := d.allocateID()
331	callback := &callbackObject{
332		BaseProxy: BaseProxy{
333			context: d.context,
334			id:      callbackID,
335		},
336		display: d,
337	}
338
339	// Send sync request (opcode 0)
340	if err := d.SendRequest(1, 0, callbackID); err != nil {
341		return nil, err
342	}
343
344	// Store the callback object
345	d.objects.Store(callbackID, callback)
346
347	return callback, nil
348}
349
350// Additional protocol object types needed for compatibility
351
352// Seat capability constants
353const (
354	SeatCapabilityPointer  = 1
355	SeatCapabilityKeyboard = 2
356	SeatCapabilityTouch    = 4
357)
358
359// Seat represents a wl_seat
360type Seat struct {
361	BaseProxy
362	capabilities uint32
363	name         string
364}
365
366// SeatCapabilitiesHandler handles seat capabilities events
367type SeatCapabilitiesHandler interface {
368	HandleSeatCapabilities(seat *Seat, capabilities uint32)
369}
370
371// SeatNameHandler handles seat name events
372type SeatNameHandler interface {
373	HandleSeatName(seat *Seat, name string)
374}
375
376// NewSeat creates a new seat proxy
377func NewSeat(ctx *Context) *Seat {
378	return &Seat{
379		BaseProxy: BaseProxy{
380			context: ctx,
381		},
382	}
383}
384
385// GetPointer gets the pointer device
386func (s *Seat) GetPointer() (*Pointer, error) {
387	pointer := &Pointer{
388		BaseProxy: BaseProxy{
389			context: s.context,
390			id:      s.context.display.allocateID(),
391		},
392	}
393
394	// Register pointer before sending request
395	s.context.Register(pointer)
396
397	// Send get_pointer request (opcode 0)
398	err := s.context.SendRequest(s, 0, pointer.id)
399	if err != nil {
400		s.context.Unregister(pointer)
401		return nil, err
402	}
403
404	return pointer, nil
405}
406
407// GetKeyboard gets the keyboard device
408func (s *Seat) GetKeyboard() (*Keyboard, error) {
409	keyboard := &Keyboard{
410		BaseProxy: BaseProxy{
411			context: s.context,
412			id:      s.context.display.allocateID(),
413		},
414	}
415
416	// Register keyboard before sending request
417	s.context.Register(keyboard)
418
419	// Send get_keyboard request (opcode 1)
420	err := s.context.SendRequest(s, 1, keyboard.id)
421	if err != nil {
422		s.context.Unregister(keyboard)
423		return nil, err
424	}
425
426	return keyboard, nil
427}
428
429// GetTouch gets the touch device
430func (s *Seat) GetTouch() (*Touch, error) {
431	touch := &Touch{
432		BaseProxy: BaseProxy{
433			context: s.context,
434			id:      s.context.display.allocateID(),
435		},
436	}
437
438	// Register touch before sending request
439	s.context.Register(touch)
440
441	// Send get_touch request (opcode 2)
442	err := s.context.SendRequest(s, 2, touch.id)
443	if err != nil {
444		s.context.Unregister(touch)
445		return nil, err
446	}
447
448	return touch, nil
449}
450
451// Release releases the seat
452func (s *Seat) Release() error {
453	err := s.context.SendRequest(s, 3) // opcode 3
454	if err == nil {
455		s.context.Unregister(s)
456	}
457	return err
458}
459
460// Capabilities returns the seat capabilities
461func (s *Seat) Capabilities() uint32 {
462	return s.capabilities
463}
464
465// Name returns the seat name
466func (s *Seat) Name() string {
467	return s.name
468}
469
470// Dispatch handles events for the seat
471func (s *Seat) Dispatch(event *Event) {
472	// Handle seat events
473	switch event.Opcode {
474	case 0: // capabilities
475		s.capabilities = event.Uint32()
476		// TODO: Call registered handlers
477	case 1: // name
478		s.name = event.String()
479		// TODO: Call registered handlers
480	}
481}
482
483// Surface represents a wl_surface
484type Surface struct {
485	BaseProxy
486}
487
488// NewSurface creates a new surface proxy
489func NewSurface(ctx *Context) *Surface {
490	return &Surface{
491		BaseProxy: BaseProxy{
492			context: ctx,
493		},
494	}
495}
496
497// Destroy destroys the surface
498func (s *Surface) Destroy() error {
499	err := s.context.SendRequest(s, 0) // opcode 0
500	if err == nil {
501		s.context.Unregister(s)
502	}
503	return err
504}
505
506// Attach attaches a buffer to the surface
507func (s *Surface) Attach(buffer Object, x, y int32) error {
508	return s.context.SendRequest(s, 1, buffer, x, y) // opcode 1
509}
510
511// Damage marks a region of the surface as damaged
512func (s *Surface) Damage(x, y, width, height int32) error {
513	return s.context.SendRequest(s, 2, x, y, width, height) // opcode 2
514}
515
516// Frame requests a frame callback
517func (s *Surface) Frame() (Object, error) {
518	callback := &callbackObject{
519		BaseProxy: BaseProxy{
520			context: s.context,
521			id:      s.context.display.allocateID(),
522		},
523		display: s.context.display,
524	}
525	s.context.Register(callback)
526
527	err := s.context.SendRequest(s, 3, callback.id) // opcode 3
528	if err != nil {
529		s.context.Unregister(callback)
530		return nil, err
531	}
532
533	return callback, nil
534}
535
536// SetOpaqueRegion sets the opaque region
537func (s *Surface) SetOpaqueRegion(region *Region) error {
538	return s.context.SendRequest(s, 4, region) // opcode 4
539}
540
541// SetInputRegion sets the input region
542func (s *Surface) SetInputRegion(region *Region) error {
543	return s.context.SendRequest(s, 5, region) // opcode 5
544}
545
546// Commit commits pending surface state
547func (s *Surface) Commit() error {
548	return s.context.SendRequest(s, 6) // opcode 6
549}
550
551// SetBufferTransform sets the buffer transform
552func (s *Surface) SetBufferTransform(transform int32) error {
553	return s.context.SendRequest(s, 7, transform) // opcode 7
554}
555
556// SetBufferScale sets the buffer scale
557func (s *Surface) SetBufferScale(scale int32) error {
558	return s.context.SendRequest(s, 8, scale) // opcode 8
559}
560
561// DamageBuffer marks a region of the buffer as damaged
562func (s *Surface) DamageBuffer(x, y, width, height int32) error {
563	return s.context.SendRequest(s, 9, x, y, width, height) // opcode 9
564}
565
566// Offset sets the buffer offset
567func (s *Surface) Offset(x, y int32) error {
568	return s.context.SendRequest(s, 10, x, y) // opcode 10
569}
570
571// Dispatch handles surface events
572func (s *Surface) Dispatch(event *Event) {
573	// Surface can receive enter/leave/preferred_buffer_scale/preferred_buffer_transform events
574	// But for now we don't handle them
575}
576
577// Pointer represents a wl_pointer
578type Pointer struct {
579	BaseProxy
580}
581
582// Keyboard represents a wl_keyboard
583type Keyboard struct {
584	BaseProxy
585}
586
587// Touch represents a wl_touch
588type Touch struct {
589	BaseProxy
590}
591
592// Output represents a wl_output
593type Output struct {
594	BaseProxy
595}
596
597// Region represents a wl_region
598type Region struct {
599	BaseProxy
600}
601
602// Add adds a rectangle to the region
603func (r *Region) Add(x, y, width, height int32) error {
604	return r.context.SendRequest(r, 0, x, y, width, height) // opcode 0
605}
606
607// Subtract subtracts a rectangle from the region
608func (r *Region) Subtract(x, y, width, height int32) error {
609	return r.context.SendRequest(r, 1, x, y, width, height) // opcode 1
610}
611
612// Destroy destroys the region
613func (r *Region) Destroy() error {
614	err := r.context.SendRequest(r, 2) // opcode 2
615	if err == nil {
616		r.context.Unregister(r)
617	}
618	return err
619}
620
621// Compositor represents a wl_compositor
622type Compositor struct {
623	BaseProxy
624}
625
626// NewCompositor creates a new compositor proxy
627func NewCompositor(ctx *Context) *Compositor {
628	return &Compositor{
629		BaseProxy: BaseProxy{
630			context: ctx,
631		},
632	}
633}
634
635// CreateSurface creates a new surface
636func (c *Compositor) CreateSurface() (*Surface, error) {
637	surface := &Surface{
638		BaseProxy: BaseProxy{
639			context: c.context,
640			id:      c.context.display.allocateID(),
641		},
642	}
643
644	// Register surface before sending request
645	c.context.Register(surface)
646
647	// Send create_surface request (opcode 0)
648	err := c.context.SendRequest(c, 0, surface.id)
649	if err != nil {
650		c.context.Unregister(surface)
651		return nil, err
652	}
653
654	return surface, nil
655}
656
657// CreateRegion creates a new region
658func (c *Compositor) CreateRegion() (*Region, error) {
659	region := &Region{
660		BaseProxy: BaseProxy{
661			context: c.context,
662			id:      c.context.display.allocateID(),
663		},
664	}
665
666	// Register region before sending request
667	c.context.Register(region)
668
669	// Send create_region request (opcode 1)
670	err := c.context.SendRequest(c, 1, region.id)
671	if err != nil {
672		c.context.Unregister(region)
673		return nil, err
674	}
675
676	return region, nil
677}

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 8

↑↓ moveenter openesc close