feat(wayland): track surface buffers and complete frame callbacks
- commit
- f46a2b0dc219dd4abbc9a609baf7ff41f8f552fb
- author
- brice · · 2026-09-23 12:14 UTC
- parent
- 4fb1824
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
5 files · +278 -23
M internal/adapters/wayland/globals.go +10 -20
| @@ -6,10 +6,11 @@ import ( | |||
| 6 | 6 | "golang.org/x/sys/unix" | |
| 7 | 7 | ) | |
| 8 | 8 | ||
| 9 | - | func registerGlobals(d *server.Display, o Options) error { | |
| 9 | + | func registerGlobals(d *server.Display, o Options, s *Server) error { | |
| 10 | 10 | for _, register := range []func() error{ | |
| 11 | + | func() error { return registerXDG(d, s) }, | |
| 11 | 12 | func() error { | |
| 12 | - | return wayland.NewCompositorGlobal(d, 6, func(c server.Client, v, id uint32) { wayland.NewCompositor(c, int32(v), id, compositor{}) }) | |
| 13 | + | return wayland.NewCompositorGlobal(d, 6, func(c server.Client, v, id uint32) { wayland.NewCompositor(c, int32(v), id, compositor{s}) }) | |
| 13 | 14 | }, | |
| 14 | 15 | func() error { | |
| 15 | 16 | return wayland.NewShmGlobal(d, 1, func(c server.Client, v, id uint32) { | |
| @@ -62,31 +63,20 @@ func registerGlobals(d *server.Display, o Options) error { | |||
| 62 | 63 | return nil | |
| 63 | 64 | } | |
| 64 | 65 | ||
| 65 | - | type compositor struct{} | |
| 66 | + | type compositor struct{ server *Server } | |
| 66 | 67 | ||
| 67 | - | func (compositor) CreateSurface(r *wayland.Compositor, id uint32) { | |
| 68 | - | wayland.NewSurface(r.Client(), r.Version(), id, surface{}) | |
| 68 | + | func (c compositor) CreateSurface(r *wayland.Compositor, id uint32) { | |
| 69 | + | state := &surface{server: c.server} | |
| 70 | + | if w, err := wayland.NewSurface(r.Client(), r.Version(), id, state); err == nil { | |
| 71 | + | c.server.surfaces[w.Resource] = state | |
| 72 | + | w.OnDestroy = func() { delete(c.server.surfaces, w.Resource); state.Destroy(w) } | |
| 73 | + | } | |
| 69 | 74 | } | |
| 70 | 75 | func (compositor) CreateRegion(r *wayland.Compositor, id uint32) { | |
| 71 | 76 | wayland.NewRegion(r.Client(), r.Version(), id, region{}) | |
| 72 | 77 | } | |
| 73 | 78 | func (compositor) Release(*wayland.Compositor) {} | |
| 74 | 79 | ||
| 75 | - | type surface struct{} | |
| 76 | - | ||
| 77 | - | func (surface) Destroy(*wayland.Surface) {} | |
| 78 | - | func (surface) Attach(*wayland.Surface, *wayland.Buffer, int32, int32) {} | |
| 79 | - | func (surface) Damage(*wayland.Surface, int32, int32, int32, int32) {} | |
| 80 | - | func (surface) Frame(*wayland.Surface, uint32) {} | |
| 81 | - | func (surface) SetOpaqueRegion(*wayland.Surface, *wayland.Region) {} | |
| 82 | - | func (surface) SetInputRegion(*wayland.Surface, *wayland.Region) {} | |
| 83 | - | func (surface) Commit(*wayland.Surface) {} | |
| 84 | - | func (surface) SetBufferTransform(*wayland.Surface, int32) {} | |
| 85 | - | func (surface) SetBufferScale(*wayland.Surface, int32) {} | |
| 86 | - | func (surface) DamageBuffer(*wayland.Surface, int32, int32, int32, int32) {} | |
| 87 | - | func (surface) Offset(*wayland.Surface, int32, int32) {} | |
| 88 | - | func (surface) GetRelease(*wayland.Surface, uint32) {} | |
| 89 | - | ||
| 90 | 80 | type region struct{} | |
| 91 | 81 | ||
| 92 | 82 | func (region) Destroy(*wayland.Region) {} | |
| @@ -6,10 +6,11 @@ import ( | |||||
| 6 | "golang.org/x/sys/unix" | 6 | "golang.org/x/sys/unix" | ||
| 7 | ) | 7 | ) | ||
| 8 | 8 | ||||
| 9 | - | func registerGlobals(d *server.Display, o Options) error { | 9 | + | func registerGlobals(d *server.Display, o Options, s *Server) error { |
| 10 | for _, register := range []func() error{ | 10 | for _, register := range []func() error{ | ||
| 11 | + | func() error { return registerXDG(d, s) }, | |||
| 11 | func() error { | 12 | func() error { | ||
| 12 | - | return wayland.NewCompositorGlobal(d, 6, func(c server.Client, v, id uint32) { wayland.NewCompositor(c, int32(v), id, compositor{}) }) | 13 | + | return wayland.NewCompositorGlobal(d, 6, func(c server.Client, v, id uint32) { wayland.NewCompositor(c, int32(v), id, compositor{s}) }) |
| 13 | }, | 14 | }, | ||
| 14 | func() error { | 15 | func() error { | ||
| 15 | return wayland.NewShmGlobal(d, 1, func(c server.Client, v, id uint32) { | 16 | return wayland.NewShmGlobal(d, 1, func(c server.Client, v, id uint32) { | ||
| @@ -62,31 +63,20 @@ func registerGlobals(d *server.Display, o Options) error { | |||||
| 62 | return nil | 63 | return nil | ||
| 63 | } | 64 | } | ||
| 64 | 65 | ||||
| 65 | - | type compositor struct{} | 66 | + | type compositor struct{ server *Server } |
| 66 | 67 | ||||
| 67 | - | func (compositor) CreateSurface(r *wayland.Compositor, id uint32) { | 68 | + | func (c compositor) CreateSurface(r *wayland.Compositor, id uint32) { |
| 68 | - | wayland.NewSurface(r.Client(), r.Version(), id, surface{}) | 69 | + | state := &surface{server: c.server} |
| 70 | + | if w, err := wayland.NewSurface(r.Client(), r.Version(), id, state); err == nil { | |||
| 71 | + | c.server.surfaces[w.Resource] = state | |||
| 72 | + | w.OnDestroy = func() { delete(c.server.surfaces, w.Resource); state.Destroy(w) } | |||
| 73 | + | } | |||
| 69 | } | 74 | } | ||
| 70 | func (compositor) CreateRegion(r *wayland.Compositor, id uint32) { | 75 | func (compositor) CreateRegion(r *wayland.Compositor, id uint32) { | ||
| 71 | wayland.NewRegion(r.Client(), r.Version(), id, region{}) | 76 | wayland.NewRegion(r.Client(), r.Version(), id, region{}) | ||
| 72 | } | 77 | } | ||
| 73 | func (compositor) Release(*wayland.Compositor) {} | 78 | func (compositor) Release(*wayland.Compositor) {} | ||
| 74 | 79 | ||||
| 75 | - | type surface struct{} | |||
| 76 | - | ||||
| 77 | - | func (surface) Destroy(*wayland.Surface) {} | |||
| 78 | - | func (surface) Attach(*wayland.Surface, *wayland.Buffer, int32, int32) {} | |||
| 79 | - | func (surface) Damage(*wayland.Surface, int32, int32, int32, int32) {} | |||
| 80 | - | func (surface) Frame(*wayland.Surface, uint32) {} | |||
| 81 | - | func (surface) SetOpaqueRegion(*wayland.Surface, *wayland.Region) {} | |||
| 82 | - | func (surface) SetInputRegion(*wayland.Surface, *wayland.Region) {} | |||
| 83 | - | func (surface) Commit(*wayland.Surface) {} | |||
| 84 | - | func (surface) SetBufferTransform(*wayland.Surface, int32) {} | |||
| 85 | - | func (surface) SetBufferScale(*wayland.Surface, int32) {} | |||
| 86 | - | func (surface) DamageBuffer(*wayland.Surface, int32, int32, int32, int32) {} | |||
| 87 | - | func (surface) Offset(*wayland.Surface, int32, int32) {} | |||
| 88 | - | func (surface) GetRelease(*wayland.Surface, uint32) {} | |||
| 89 | - | ||||
| 90 | type region struct{} | 80 | type region struct{} | ||
| 91 | 81 | ||||
| 92 | func (region) Destroy(*wayland.Region) {} | 82 | func (region) Destroy(*wayland.Region) {} | ||
M internal/adapters/wayland/server.go +47 -3
| @@ -3,7 +3,10 @@ package wayland | |||
| 3 | 3 | import ( | |
| 4 | 4 | "context" | |
| 5 | 5 | "fmt" | |
| 6 | + | "github.com/bnema/purego-libwayland/protocol/wayland" | |
| 6 | 7 | "os" | |
| 8 | + | "time" | |
| 9 | + | "sync" | |
| 7 | 10 | ||
| 8 | 11 | "github.com/bnema/nefertty/internal/ports" | |
| 9 | 12 | "github.com/bnema/purego-libwayland/server" | |
| @@ -25,6 +28,11 @@ type Server struct { | |||
| 25 | 28 | cleanup func() | |
| 26 | 29 | log zerowrap.Logger | |
| 27 | 30 | channels Channels | |
| 31 | + | awaiting []*wayland.Callback | |
| 32 | + | frames uint64 | |
| 33 | + | started time.Time | |
| 34 | + | surfaces map[*server.Resource]*surface | |
| 35 | + | serial uint32 | |
| 28 | 36 | } | |
| 29 | 37 | ||
| 30 | 38 | func New(opts Options, ch Channels, log zerowrap.Logger) (*Server, error) { | |
| @@ -47,15 +55,51 @@ func New(opts Options, ch Channels, log zerowrap.Logger) (*Server, error) { | |||
| 47 | 55 | cleanup() | |
| 48 | 56 | return nil, err | |
| 49 | 57 | } | |
| 50 | - | if err = registerGlobals(d, opts); err != nil { | |
| 58 | + | s := &Server{display: d, name: name, cleanup: cleanup, log: log, channels: ch, surfaces: make(map[*server.Resource]*surface)} | |
| 59 | + | if err = registerGlobals(d, opts, s); err != nil { | |
| 51 | 60 | cleanup() | |
| 52 | 61 | return nil, err | |
| 53 | 62 | } | |
| 54 | - | return &Server{display: d, name: name, cleanup: cleanup, log: log, channels: ch}, nil | |
| 63 | + | return s, nil | |
| 55 | 64 | } | |
| 56 | 65 | func (s *Server) SocketName() string { return s.name } | |
| 57 | 66 | func (s *Server) Run(ctx context.Context) error { | |
| 58 | 67 | defer s.cleanup() | |
| 59 | 68 | s.log.Info().Str("socket", s.name).Msg("starting wayland") | |
| 60 | - | return s.display.Run(ctx) | |
| 69 | + | s.started = time.Now() | |
| 70 | + | var wg sync.WaitGroup | |
| 71 | + | wg.Add(1) | |
| 72 | + | go func() { | |
| 73 | + | defer wg.Done() | |
| 74 | + | ticker := time.NewTicker(16 * time.Millisecond) | |
| 75 | + | defer ticker.Stop() | |
| 76 | + | for { | |
| 77 | + | select { | |
| 78 | + | case <-ctx.Done(): | |
| 79 | + | return | |
| 80 | + | case <-ticker.C: | |
| 81 | + | if ctx.Err() != nil { | |
| 82 | + | return | |
| 83 | + | } | |
| 84 | + | s.display.Do(func() { | |
| 85 | + | if ctx.Err() != nil { | |
| 86 | + | return | |
| 87 | + | } | |
| 88 | + | callbacks := s.awaiting | |
| 89 | + | s.awaiting = nil | |
| 90 | + | for _, cb := range callbacks { | |
| 91 | + | if !cb.Resource.Alive() { | |
| 92 | + | continue | |
| 93 | + | } | |
| 94 | + | cb.SendDone(uint32(time.Since(s.started).Milliseconds())) | |
| 95 | + | cb.Destroy() | |
| 96 | + | s.frames++ | |
| 97 | + | } | |
| 98 | + | }) | |
| 99 | + | } | |
| 100 | + | } | |
| 101 | + | }() | |
| 102 | + | err := s.display.Run(ctx) | |
| 103 | + | wg.Wait() | |
| 104 | + | return err | |
| 61 | 105 | } | |
| @@ -3,7 +3,10 @@ package wayland | |||||
| 3 | import ( | 3 | import ( | ||
| 4 | "context" | 4 | "context" | ||
| 5 | "fmt" | 5 | "fmt" | ||
| 6 | + | "github.com/bnema/purego-libwayland/protocol/wayland" | |||
| 6 | "os" | 7 | "os" | ||
| 8 | + | "time" | |||
| 9 | + | "sync" | |||
| 7 | 10 | ||||
| 8 | "github.com/bnema/nefertty/internal/ports" | 11 | "github.com/bnema/nefertty/internal/ports" | ||
| 9 | "github.com/bnema/purego-libwayland/server" | 12 | "github.com/bnema/purego-libwayland/server" | ||
| @@ -25,6 +28,11 @@ type Server struct { | |||||
| 25 | cleanup func() | 28 | cleanup func() | ||
| 26 | log zerowrap.Logger | 29 | log zerowrap.Logger | ||
| 27 | channels Channels | 30 | channels Channels | ||
| 31 | + | awaiting []*wayland.Callback | |||
| 32 | + | frames uint64 | |||
| 33 | + | started time.Time | |||
| 34 | + | surfaces map[*server.Resource]*surface | |||
| 35 | + | serial uint32 | |||
| 28 | } | 36 | } | ||
| 29 | 37 | ||||
| 30 | func New(opts Options, ch Channels, log zerowrap.Logger) (*Server, error) { | 38 | func New(opts Options, ch Channels, log zerowrap.Logger) (*Server, error) { | ||
| @@ -47,15 +55,51 @@ func New(opts Options, ch Channels, log zerowrap.Logger) (*Server, error) { | |||||
| 47 | cleanup() | 55 | cleanup() | ||
| 48 | return nil, err | 56 | return nil, err | ||
| 49 | } | 57 | } | ||
| 50 | - | if err = registerGlobals(d, opts); err != nil { | 58 | + | s := &Server{display: d, name: name, cleanup: cleanup, log: log, channels: ch, surfaces: make(map[*server.Resource]*surface)} |
| 59 | + | if err = registerGlobals(d, opts, s); err != nil { | |||
| 51 | cleanup() | 60 | cleanup() | ||
| 52 | return nil, err | 61 | return nil, err | ||
| 53 | } | 62 | } | ||
| 54 | - | return &Server{display: d, name: name, cleanup: cleanup, log: log, channels: ch}, nil | 63 | + | return s, nil |
| 55 | } | 64 | } | ||
| 56 | func (s *Server) SocketName() string { return s.name } | 65 | func (s *Server) SocketName() string { return s.name } | ||
| 57 | func (s *Server) Run(ctx context.Context) error { | 66 | func (s *Server) Run(ctx context.Context) error { | ||
| 58 | defer s.cleanup() | 67 | defer s.cleanup() | ||
| 59 | s.log.Info().Str("socket", s.name).Msg("starting wayland") | 68 | s.log.Info().Str("socket", s.name).Msg("starting wayland") | ||
| 60 | - | return s.display.Run(ctx) | 69 | + | s.started = time.Now() |
| 70 | + | var wg sync.WaitGroup | |||
| 71 | + | wg.Add(1) | |||
| 72 | + | go func() { | |||
| 73 | + | defer wg.Done() | |||
| 74 | + | ticker := time.NewTicker(16 * time.Millisecond) | |||
| 75 | + | defer ticker.Stop() | |||
| 76 | + | for { | |||
| 77 | + | select { | |||
| 78 | + | case <-ctx.Done(): | |||
| 79 | + | return | |||
| 80 | + | case <-ticker.C: | |||
| 81 | + | if ctx.Err() != nil { | |||
| 82 | + | return | |||
| 83 | + | } | |||
| 84 | + | s.display.Do(func() { | |||
| 85 | + | if ctx.Err() != nil { | |||
| 86 | + | return | |||
| 87 | + | } | |||
| 88 | + | callbacks := s.awaiting | |||
| 89 | + | s.awaiting = nil | |||
| 90 | + | for _, cb := range callbacks { | |||
| 91 | + | if !cb.Resource.Alive() { | |||
| 92 | + | continue | |||
| 93 | + | } | |||
| 94 | + | cb.SendDone(uint32(time.Since(s.started).Milliseconds())) | |||
| 95 | + | cb.Destroy() | |||
| 96 | + | s.frames++ | |||
| 97 | + | } | |||
| 98 | + | }) | |||
| 99 | + | } | |||
| 100 | + | } | |||
| 101 | + | }() | |||
| 102 | + | err := s.display.Run(ctx) | |||
| 103 | + | wg.Wait() | |||
| 104 | + | return err | |||
| 61 | } | 105 | } | ||
M internal/adapters/wayland/server_test.go +62 -0
| @@ -53,3 +53,65 @@ func TestWaylandInfo(t *testing.T) { | |||
| 53 | 53 | } | |
| 54 | 54 | } | |
| 55 | 55 | } | |
| 56 | + | ||
| 57 | + | func TestWestonFrames(t *testing.T) { | |
| 58 | + | tool, err := exec.LookPath("weston-simple-shm") | |
| 59 | + | if err != nil { | |
| 60 | + | t.Skip("weston-simple-shm not installed") | |
| 61 | + | } | |
| 62 | + | dir := t.TempDir() | |
| 63 | + | s, err := New(Options{RuntimeDir: dir, OutputWidth: 1920, OutputHeight: 1080}, Channels{}, logging.For(context.Background(), "wayland")) | |
| 64 | + | if err != nil { | |
| 65 | + | t.Fatal(err) | |
| 66 | + | } | |
| 67 | + | ctx, cancel := context.WithCancel(context.Background()) | |
| 68 | + | done := make(chan error, 1) | |
| 69 | + | go func() { done <- s.Run(ctx) }() | |
| 70 | + | t.Cleanup(func() { | |
| 71 | + | cancel() | |
| 72 | + | select { | |
| 73 | + | case err := <-done: | |
| 74 | + | if err != nil { | |
| 75 | + | t.Errorf("Run: %v", err) | |
| 76 | + | } | |
| 77 | + | case <-time.After(2 * time.Second): | |
| 78 | + | t.Error("Run hung") | |
| 79 | + | } | |
| 80 | + | }) | |
| 81 | + | clientCtx, stop := context.WithCancel(context.Background()) | |
| 82 | + | cmd := exec.CommandContext(clientCtx, tool) | |
| 83 | + | cmd.Env = append(os.Environ(), "XDG_RUNTIME_DIR="+dir, "WAYLAND_DISPLAY="+s.SocketName()) | |
| 84 | + | var stderr strings.Builder | |
| 85 | + | cmd.Stderr = &stderr | |
| 86 | + | if err := cmd.Start(); err != nil { | |
| 87 | + | stop() | |
| 88 | + | t.Fatal(err) | |
| 89 | + | } | |
| 90 | + | exited := make(chan error, 1) | |
| 91 | + | go func() { exited <- cmd.Wait() }() | |
| 92 | + | t.Cleanup(func() { | |
| 93 | + | stop() | |
| 94 | + | select { | |
| 95 | + | case <-exited: | |
| 96 | + | case <-time.After(2 * time.Second): | |
| 97 | + | t.Error("client did not exit") | |
| 98 | + | } | |
| 99 | + | }) | |
| 100 | + | deadline := time.After(2 * time.Second) | |
| 101 | + | tick := time.NewTicker(25 * time.Millisecond) | |
| 102 | + | defer tick.Stop() | |
| 103 | + | for { | |
| 104 | + | select { | |
| 105 | + | case err := <-exited: | |
| 106 | + | t.Fatalf("client exited: %v: %s", err, stderr.String()) | |
| 107 | + | case <-deadline: | |
| 108 | + | t.Fatalf("only %d frames; stderr: %s", s.frames, stderr.String()) | |
| 109 | + | case <-tick.C: | |
| 110 | + | var n uint64 | |
| 111 | + | s.display.Do(func() { n = s.frames }) | |
| 112 | + | if n >= 10 { | |
| 113 | + | return | |
| 114 | + | } | |
| 115 | + | } | |
| 116 | + | } | |
| 117 | + | } | |
| @@ -53,3 +53,65 @@ func TestWaylandInfo(t *testing.T) { | |||||
| 53 | } | 53 | } | ||
| 54 | } | 54 | } | ||
| 55 | } | 55 | } | ||
| 56 | + | ||||
| 57 | + | func TestWestonFrames(t *testing.T) { | |||
| 58 | + | tool, err := exec.LookPath("weston-simple-shm") | |||
| 59 | + | if err != nil { | |||
| 60 | + | t.Skip("weston-simple-shm not installed") | |||
| 61 | + | } | |||
| 62 | + | dir := t.TempDir() | |||
| 63 | + | s, err := New(Options{RuntimeDir: dir, OutputWidth: 1920, OutputHeight: 1080}, Channels{}, logging.For(context.Background(), "wayland")) | |||
| 64 | + | if err != nil { | |||
| 65 | + | t.Fatal(err) | |||
| 66 | + | } | |||
| 67 | + | ctx, cancel := context.WithCancel(context.Background()) | |||
| 68 | + | done := make(chan error, 1) | |||
| 69 | + | go func() { done <- s.Run(ctx) }() | |||
| 70 | + | t.Cleanup(func() { | |||
| 71 | + | cancel() | |||
| 72 | + | select { | |||
| 73 | + | case err := <-done: | |||
| 74 | + | if err != nil { | |||
| 75 | + | t.Errorf("Run: %v", err) | |||
| 76 | + | } | |||
| 77 | + | case <-time.After(2 * time.Second): | |||
| 78 | + | t.Error("Run hung") | |||
| 79 | + | } | |||
| 80 | + | }) | |||
| 81 | + | clientCtx, stop := context.WithCancel(context.Background()) | |||
| 82 | + | cmd := exec.CommandContext(clientCtx, tool) | |||
| 83 | + | cmd.Env = append(os.Environ(), "XDG_RUNTIME_DIR="+dir, "WAYLAND_DISPLAY="+s.SocketName()) | |||
| 84 | + | var stderr strings.Builder | |||
| 85 | + | cmd.Stderr = &stderr | |||
| 86 | + | if err := cmd.Start(); err != nil { | |||
| 87 | + | stop() | |||
| 88 | + | t.Fatal(err) | |||
| 89 | + | } | |||
| 90 | + | exited := make(chan error, 1) | |||
| 91 | + | go func() { exited <- cmd.Wait() }() | |||
| 92 | + | t.Cleanup(func() { | |||
| 93 | + | stop() | |||
| 94 | + | select { | |||
| 95 | + | case <-exited: | |||
| 96 | + | case <-time.After(2 * time.Second): | |||
| 97 | + | t.Error("client did not exit") | |||
| 98 | + | } | |||
| 99 | + | }) | |||
| 100 | + | deadline := time.After(2 * time.Second) | |||
| 101 | + | tick := time.NewTicker(25 * time.Millisecond) | |||
| 102 | + | defer tick.Stop() | |||
| 103 | + | for { | |||
| 104 | + | select { | |||
| 105 | + | case err := <-exited: | |||
| 106 | + | t.Fatalf("client exited: %v: %s", err, stderr.String()) | |||
| 107 | + | case <-deadline: | |||
| 108 | + | t.Fatalf("only %d frames; stderr: %s", s.frames, stderr.String()) | |||
| 109 | + | case <-tick.C: | |||
| 110 | + | var n uint64 | |||
| 111 | + | s.display.Do(func() { n = s.frames }) | |||
| 112 | + | if n >= 10 { | |||
| 113 | + | return | |||
| 114 | + | } | |||
| 115 | + | } | |||
| 116 | + | } | |||
| 117 | + | } | |||
A internal/adapters/wayland/surface.go +53 -0
| @@ -0,0 +1,53 @@ | |||
| 1 | + | package wayland | |
| 2 | + | ||
| 3 | + | import "github.com/bnema/purego-libwayland/protocol/wayland" | |
| 4 | + | ||
| 5 | + | type surface struct { | |
| 6 | + | server *Server | |
| 7 | + | current, pending *wayland.Buffer | |
| 8 | + | attached bool | |
| 9 | + | callbacks []*wayland.Callback | |
| 10 | + | role func(bool) | |
| 11 | + | destroyed bool | |
| 12 | + | } | |
| 13 | + | ||
| 14 | + | func (s *surface) Destroy(*wayland.Surface) { | |
| 15 | + | s.destroyed = true | |
| 16 | + | s.callbacks = nil | |
| 17 | + | if s.role != nil { | |
| 18 | + | s.role(false) | |
| 19 | + | } | |
| 20 | + | } | |
| 21 | + | func (s *surface) Attach(_ *wayland.Surface, b *wayland.Buffer, _, _ int32) { | |
| 22 | + | s.pending = b | |
| 23 | + | s.attached = true | |
| 24 | + | } | |
| 25 | + | func (s *surface) Frame(r *wayland.Surface, id uint32) { | |
| 26 | + | cb, err := wayland.NewCallback(r.Client(), 1, id, struct{}{}) | |
| 27 | + | if err == nil { | |
| 28 | + | s.callbacks = append(s.callbacks, cb) | |
| 29 | + | } | |
| 30 | + | } | |
| 31 | + | func (s *surface) Commit(*wayland.Surface) { | |
| 32 | + | if s.attached { | |
| 33 | + | if s.current != nil && (s.pending == nil || s.current.Resource != s.pending.Resource) { | |
| 34 | + | s.current.SendRelease() | |
| 35 | + | } | |
| 36 | + | s.current = s.pending | |
| 37 | + | s.pending = nil | |
| 38 | + | s.attached = false | |
| 39 | + | } | |
| 40 | + | s.server.awaiting = append(s.server.awaiting, s.callbacks...) | |
| 41 | + | s.callbacks = nil | |
| 42 | + | if s.role != nil { | |
| 43 | + | s.role(s.current != nil) | |
| 44 | + | } | |
| 45 | + | } | |
| 46 | + | func (*surface) Damage(*wayland.Surface, int32, int32, int32, int32) {} | |
| 47 | + | func (*surface) DamageBuffer(*wayland.Surface, int32, int32, int32, int32) {} | |
| 48 | + | func (*surface) SetOpaqueRegion(*wayland.Surface, *wayland.Region) {} | |
| 49 | + | func (*surface) SetInputRegion(*wayland.Surface, *wayland.Region) {} | |
| 50 | + | func (*surface) SetBufferTransform(*wayland.Surface, int32) {} | |
| 51 | + | func (*surface) SetBufferScale(*wayland.Surface, int32) {} | |
| 52 | + | func (*surface) Offset(*wayland.Surface, int32, int32) {} | |
| 53 | + | func (*surface) GetRelease(*wayland.Surface, uint32) {} | |
| @@ -0,0 +1,53 @@ | |||||
| 1 | + | package wayland | |||
| 2 | + | ||||
| 3 | + | import "github.com/bnema/purego-libwayland/protocol/wayland" | |||
| 4 | + | ||||
| 5 | + | type surface struct { | |||
| 6 | + | server *Server | |||
| 7 | + | current, pending *wayland.Buffer | |||
| 8 | + | attached bool | |||
| 9 | + | callbacks []*wayland.Callback | |||
| 10 | + | role func(bool) | |||
| 11 | + | destroyed bool | |||
| 12 | + | } | |||
| 13 | + | ||||
| 14 | + | func (s *surface) Destroy(*wayland.Surface) { | |||
| 15 | + | s.destroyed = true | |||
| 16 | + | s.callbacks = nil | |||
| 17 | + | if s.role != nil { | |||
| 18 | + | s.role(false) | |||
| 19 | + | } | |||
| 20 | + | } | |||
| 21 | + | func (s *surface) Attach(_ *wayland.Surface, b *wayland.Buffer, _, _ int32) { | |||
| 22 | + | s.pending = b | |||
| 23 | + | s.attached = true | |||
| 24 | + | } | |||
| 25 | + | func (s *surface) Frame(r *wayland.Surface, id uint32) { | |||
| 26 | + | cb, err := wayland.NewCallback(r.Client(), 1, id, struct{}{}) | |||
| 27 | + | if err == nil { | |||
| 28 | + | s.callbacks = append(s.callbacks, cb) | |||
| 29 | + | } | |||
| 30 | + | } | |||
| 31 | + | func (s *surface) Commit(*wayland.Surface) { | |||
| 32 | + | if s.attached { | |||
| 33 | + | if s.current != nil && (s.pending == nil || s.current.Resource != s.pending.Resource) { | |||
| 34 | + | s.current.SendRelease() | |||
| 35 | + | } | |||
| 36 | + | s.current = s.pending | |||
| 37 | + | s.pending = nil | |||
| 38 | + | s.attached = false | |||
| 39 | + | } | |||
| 40 | + | s.server.awaiting = append(s.server.awaiting, s.callbacks...) | |||
| 41 | + | s.callbacks = nil | |||
| 42 | + | if s.role != nil { | |||
| 43 | + | s.role(s.current != nil) | |||
| 44 | + | } | |||
| 45 | + | } | |||
| 46 | + | func (*surface) Damage(*wayland.Surface, int32, int32, int32, int32) {} | |||
| 47 | + | func (*surface) DamageBuffer(*wayland.Surface, int32, int32, int32, int32) {} | |||
| 48 | + | func (*surface) SetOpaqueRegion(*wayland.Surface, *wayland.Region) {} | |||
| 49 | + | func (*surface) SetInputRegion(*wayland.Surface, *wayland.Region) {} | |||
| 50 | + | func (*surface) SetBufferTransform(*wayland.Surface, int32) {} | |||
| 51 | + | func (*surface) SetBufferScale(*wayland.Surface, int32) {} | |||
| 52 | + | func (*surface) Offset(*wayland.Surface, int32, int32) {} | |||
| 53 | + | func (*surface) GetRelease(*wayland.Surface, uint32) {} | |||
A internal/adapters/wayland/xdg.go +106 -0
| @@ -0,0 +1,106 @@ | |||
| 1 | + | package wayland | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "github.com/bnema/purego-libwayland/protocol/wayland" | |
| 5 | + | "github.com/bnema/purego-libwayland/protocol/xdgshell" | |
| 6 | + | "github.com/bnema/purego-libwayland/server" | |
| 7 | + | ) | |
| 8 | + | ||
| 9 | + | func registerXDG(d *server.Display, s *Server) error { | |
| 10 | + | return xdgshell.NewWmBaseGlobal(d, 6, func(c server.Client, v, id uint32) { xdgshell.NewWmBase(c, int32(v), id, wm{s}) }) | |
| 11 | + | } | |
| 12 | + | ||
| 13 | + | type wm struct{ server *Server } | |
| 14 | + | ||
| 15 | + | func (wm) Destroy(*xdgshell.WmBase) {} | |
| 16 | + | func (wm) Pong(*xdgshell.WmBase, uint32) {} | |
| 17 | + | func (wm) CreatePositioner(r *xdgshell.WmBase, id uint32) { | |
| 18 | + | xdgshell.NewPositioner(r.Client(), r.Version(), id, positioner{}) | |
| 19 | + | } | |
| 20 | + | func (m wm) GetXdgSurface(r *xdgshell.WmBase, id uint32, w *wayland.Surface) { | |
| 21 | + | if w == nil { | |
| 22 | + | return | |
| 23 | + | } | |
| 24 | + | state := m.server.surfaces[w.Resource] | |
| 25 | + | if state == nil { | |
| 26 | + | return | |
| 27 | + | } | |
| 28 | + | if state.role != nil { | |
| 29 | + | r.PostError(0, "surface already has role") | |
| 30 | + | return | |
| 31 | + | } | |
| 32 | + | x := &xdgSurface{server: m.server, surface: state} | |
| 33 | + | resource, err := xdgshell.NewSurface(r.Client(), r.Version(), id, x) | |
| 34 | + | if err == nil { | |
| 35 | + | x.resource = resource | |
| 36 | + | } | |
| 37 | + | ||
| 38 | + | } | |
| 39 | + | ||
| 40 | + | type positioner struct{} | |
| 41 | + | ||
| 42 | + | func (positioner) Destroy(*xdgshell.Positioner) {} | |
| 43 | + | func (positioner) SetSize(*xdgshell.Positioner, int32, int32) {} | |
| 44 | + | func (positioner) SetAnchorRect(*xdgshell.Positioner, int32, int32, int32, int32) {} | |
| 45 | + | func (positioner) SetAnchor(*xdgshell.Positioner, uint32) {} | |
| 46 | + | func (positioner) SetGravity(*xdgshell.Positioner, uint32) {} | |
| 47 | + | func (positioner) SetConstraintAdjustment(*xdgshell.Positioner, uint32) {} | |
| 48 | + | func (positioner) SetOffset(*xdgshell.Positioner, int32, int32) {} | |
| 49 | + | func (positioner) SetReactive(*xdgshell.Positioner) {} | |
| 50 | + | func (positioner) SetParentSize(*xdgshell.Positioner, int32, int32) {} | |
| 51 | + | func (positioner) SetParentConfigure(*xdgshell.Positioner, uint32) {} | |
| 52 | + | ||
| 53 | + | type xdgSurface struct { | |
| 54 | + | resource *xdgshell.Surface | |
| 55 | + | server *Server | |
| 56 | + | surface *surface | |
| 57 | + | configured bool | |
| 58 | + | serial uint32 | |
| 59 | + | } | |
| 60 | + | ||
| 61 | + | func (x *xdgSurface) Destroy(*xdgshell.Surface) {} | |
| 62 | + | func (x *xdgSurface) GetToplevel(r *xdgshell.Surface, id uint32) { | |
| 63 | + | t, err := xdgshell.NewToplevel(r.Client(), r.Version(), id, &top{x}) | |
| 64 | + | if err != nil { | |
| 65 | + | return | |
| 66 | + | } | |
| 67 | + | x.surface.role = func(buffer bool) { | |
| 68 | + | if !buffer && !x.configured { | |
| 69 | + | x.configured = true | |
| 70 | + | x.server.serial++ | |
| 71 | + | t.SendConfigure(0, 0, nil) | |
| 72 | + | r.SendConfigure(x.server.serial) | |
| 73 | + | } | |
| 74 | + | } | |
| 75 | + | t.OnDestroy = func() { x.surface.role = nil } | |
| 76 | + | } | |
| 77 | + | func (x *xdgSurface) GetPopup(r *xdgshell.Surface, id uint32, _ *xdgshell.Surface, _ *xdgshell.Positioner) { | |
| 78 | + | if p, e := xdgshell.NewPopup(r.Client(), r.Version(), id, popup{}); e == nil { | |
| 79 | + | p.SendPopupDone() | |
| 80 | + | } | |
| 81 | + | } | |
| 82 | + | func (x *xdgSurface) SetWindowGeometry(*xdgshell.Surface, int32, int32, int32, int32) {} | |
| 83 | + | func (x *xdgSurface) AckConfigure(_ *xdgshell.Surface, serial uint32) { x.serial = serial } | |
| 84 | + | ||
| 85 | + | type top struct{ x *xdgSurface } | |
| 86 | + | ||
| 87 | + | func (top) Destroy(*xdgshell.Toplevel) {} | |
| 88 | + | func (top) SetParent(*xdgshell.Toplevel, *xdgshell.Toplevel) {} | |
| 89 | + | func (top) SetTitle(*xdgshell.Toplevel, string) {} | |
| 90 | + | func (top) SetAppId(*xdgshell.Toplevel, string) {} | |
| 91 | + | func (top) ShowWindowMenu(*xdgshell.Toplevel, *wayland.Seat, uint32, int32, int32) {} | |
| 92 | + | func (top) Move(*xdgshell.Toplevel, *wayland.Seat, uint32) {} | |
| 93 | + | func (top) Resize(*xdgshell.Toplevel, *wayland.Seat, uint32, uint32) {} | |
| 94 | + | func (top) SetMaxSize(*xdgshell.Toplevel, int32, int32) {} | |
| 95 | + | func (top) SetMinSize(*xdgshell.Toplevel, int32, int32) {} | |
| 96 | + | func (top) SetMaximized(*xdgshell.Toplevel) {} | |
| 97 | + | func (top) UnsetMaximized(*xdgshell.Toplevel) {} | |
| 98 | + | func (top) SetFullscreen(*xdgshell.Toplevel, *wayland.Output) {} | |
| 99 | + | func (top) UnsetFullscreen(*xdgshell.Toplevel) {} | |
| 100 | + | func (top) SetMinimized(*xdgshell.Toplevel) {} | |
| 101 | + | ||
| 102 | + | type popup struct{} | |
| 103 | + | ||
| 104 | + | func (popup) Destroy(*xdgshell.Popup) {} | |
| 105 | + | func (popup) Grab(*xdgshell.Popup, *wayland.Seat, uint32) {} | |
| 106 | + | func (popup) Reposition(*xdgshell.Popup, *xdgshell.Positioner, uint32) {} | |
| @@ -0,0 +1,106 @@ | |||||
| 1 | + | package wayland | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "github.com/bnema/purego-libwayland/protocol/wayland" | |||
| 5 | + | "github.com/bnema/purego-libwayland/protocol/xdgshell" | |||
| 6 | + | "github.com/bnema/purego-libwayland/server" | |||
| 7 | + | ) | |||
| 8 | + | ||||
| 9 | + | func registerXDG(d *server.Display, s *Server) error { | |||
| 10 | + | return xdgshell.NewWmBaseGlobal(d, 6, func(c server.Client, v, id uint32) { xdgshell.NewWmBase(c, int32(v), id, wm{s}) }) | |||
| 11 | + | } | |||
| 12 | + | ||||
| 13 | + | type wm struct{ server *Server } | |||
| 14 | + | ||||
| 15 | + | func (wm) Destroy(*xdgshell.WmBase) {} | |||
| 16 | + | func (wm) Pong(*xdgshell.WmBase, uint32) {} | |||
| 17 | + | func (wm) CreatePositioner(r *xdgshell.WmBase, id uint32) { | |||
| 18 | + | xdgshell.NewPositioner(r.Client(), r.Version(), id, positioner{}) | |||
| 19 | + | } | |||
| 20 | + | func (m wm) GetXdgSurface(r *xdgshell.WmBase, id uint32, w *wayland.Surface) { | |||
| 21 | + | if w == nil { | |||
| 22 | + | return | |||
| 23 | + | } | |||
| 24 | + | state := m.server.surfaces[w.Resource] | |||
| 25 | + | if state == nil { | |||
| 26 | + | return | |||
| 27 | + | } | |||
| 28 | + | if state.role != nil { | |||
| 29 | + | r.PostError(0, "surface already has role") | |||
| 30 | + | return | |||
| 31 | + | } | |||
| 32 | + | x := &xdgSurface{server: m.server, surface: state} | |||
| 33 | + | resource, err := xdgshell.NewSurface(r.Client(), r.Version(), id, x) | |||
| 34 | + | if err == nil { | |||
| 35 | + | x.resource = resource | |||
| 36 | + | } | |||
| 37 | + | ||||
| 38 | + | } | |||
| 39 | + | ||||
| 40 | + | type positioner struct{} | |||
| 41 | + | ||||
| 42 | + | func (positioner) Destroy(*xdgshell.Positioner) {} | |||
| 43 | + | func (positioner) SetSize(*xdgshell.Positioner, int32, int32) {} | |||
| 44 | + | func (positioner) SetAnchorRect(*xdgshell.Positioner, int32, int32, int32, int32) {} | |||
| 45 | + | func (positioner) SetAnchor(*xdgshell.Positioner, uint32) {} | |||
| 46 | + | func (positioner) SetGravity(*xdgshell.Positioner, uint32) {} | |||
| 47 | + | func (positioner) SetConstraintAdjustment(*xdgshell.Positioner, uint32) {} | |||
| 48 | + | func (positioner) SetOffset(*xdgshell.Positioner, int32, int32) {} | |||
| 49 | + | func (positioner) SetReactive(*xdgshell.Positioner) {} | |||
| 50 | + | func (positioner) SetParentSize(*xdgshell.Positioner, int32, int32) {} | |||
| 51 | + | func (positioner) SetParentConfigure(*xdgshell.Positioner, uint32) {} | |||
| 52 | + | ||||
| 53 | + | type xdgSurface struct { | |||
| 54 | + | resource *xdgshell.Surface | |||
| 55 | + | server *Server | |||
| 56 | + | surface *surface | |||
| 57 | + | configured bool | |||
| 58 | + | serial uint32 | |||
| 59 | + | } | |||
| 60 | + | ||||
| 61 | + | func (x *xdgSurface) Destroy(*xdgshell.Surface) {} | |||
| 62 | + | func (x *xdgSurface) GetToplevel(r *xdgshell.Surface, id uint32) { | |||
| 63 | + | t, err := xdgshell.NewToplevel(r.Client(), r.Version(), id, &top{x}) | |||
| 64 | + | if err != nil { | |||
| 65 | + | return | |||
| 66 | + | } | |||
| 67 | + | x.surface.role = func(buffer bool) { | |||
| 68 | + | if !buffer && !x.configured { | |||
| 69 | + | x.configured = true | |||
| 70 | + | x.server.serial++ | |||
| 71 | + | t.SendConfigure(0, 0, nil) | |||
| 72 | + | r.SendConfigure(x.server.serial) | |||
| 73 | + | } | |||
| 74 | + | } | |||
| 75 | + | t.OnDestroy = func() { x.surface.role = nil } | |||
| 76 | + | } | |||
| 77 | + | func (x *xdgSurface) GetPopup(r *xdgshell.Surface, id uint32, _ *xdgshell.Surface, _ *xdgshell.Positioner) { | |||
| 78 | + | if p, e := xdgshell.NewPopup(r.Client(), r.Version(), id, popup{}); e == nil { | |||
| 79 | + | p.SendPopupDone() | |||
| 80 | + | } | |||
| 81 | + | } | |||
| 82 | + | func (x *xdgSurface) SetWindowGeometry(*xdgshell.Surface, int32, int32, int32, int32) {} | |||
| 83 | + | func (x *xdgSurface) AckConfigure(_ *xdgshell.Surface, serial uint32) { x.serial = serial } | |||
| 84 | + | ||||
| 85 | + | type top struct{ x *xdgSurface } | |||
| 86 | + | ||||
| 87 | + | func (top) Destroy(*xdgshell.Toplevel) {} | |||
| 88 | + | func (top) SetParent(*xdgshell.Toplevel, *xdgshell.Toplevel) {} | |||
| 89 | + | func (top) SetTitle(*xdgshell.Toplevel, string) {} | |||
| 90 | + | func (top) SetAppId(*xdgshell.Toplevel, string) {} | |||
| 91 | + | func (top) ShowWindowMenu(*xdgshell.Toplevel, *wayland.Seat, uint32, int32, int32) {} | |||
| 92 | + | func (top) Move(*xdgshell.Toplevel, *wayland.Seat, uint32) {} | |||
| 93 | + | func (top) Resize(*xdgshell.Toplevel, *wayland.Seat, uint32, uint32) {} | |||
| 94 | + | func (top) SetMaxSize(*xdgshell.Toplevel, int32, int32) {} | |||
| 95 | + | func (top) SetMinSize(*xdgshell.Toplevel, int32, int32) {} | |||
| 96 | + | func (top) SetMaximized(*xdgshell.Toplevel) {} | |||
| 97 | + | func (top) UnsetMaximized(*xdgshell.Toplevel) {} | |||
| 98 | + | func (top) SetFullscreen(*xdgshell.Toplevel, *wayland.Output) {} | |||
| 99 | + | func (top) UnsetFullscreen(*xdgshell.Toplevel) {} | |||
| 100 | + | func (top) SetMinimized(*xdgshell.Toplevel) {} | |||
| 101 | + | ||||
| 102 | + | type popup struct{} | |||
| 103 | + | ||||
| 104 | + | func (popup) Destroy(*xdgshell.Popup) {} | |||
| 105 | + | func (popup) Grab(*xdgshell.Popup, *wayland.Seat, uint32) {} | |||
| 106 | + | func (popup) Reposition(*xdgshell.Popup, *xdgshell.Positioner, uint32) {} | |||