v0.2.0 ab733e5

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

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