1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-25 14:02:53 -05:00
Files
DankMaterialShell/core/internal/server/wlcontext/context.go

188 lines
3.5 KiB
Go

package wlcontext
import (
"fmt"
"os"
"sync"
"golang.org/x/sys/unix"
"github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
)
type WaylandContext interface {
Display() *wlclient.Display
Post(fn func())
FatalError() <-chan error
Start()
Close()
}
var _ WaylandContext = (*SharedContext)(nil)
type SharedContext struct {
display *wlclient.Display
stopChan chan struct{}
fatalError chan error
cmdQueue chan func()
wakeR int
wakeW int
wg sync.WaitGroup
mu sync.Mutex
started bool
}
func New() (*SharedContext, error) {
display, err := wlclient.Connect("")
if err != nil {
return nil, fmt.Errorf("%w: %v", errdefs.ErrNoWaylandDisplay, err)
}
fds := make([]int, 2)
if err := unix.Pipe(fds); err != nil {
display.Context().Close()
return nil, fmt.Errorf("failed to create wake pipe: %w", err)
}
if err := unix.SetNonblock(fds[0], true); err != nil {
unix.Close(fds[0])
unix.Close(fds[1])
display.Context().Close()
return nil, fmt.Errorf("failed to set wake pipe nonblock: %w", err)
}
if err := unix.SetNonblock(fds[1], true); err != nil {
unix.Close(fds[0])
unix.Close(fds[1])
display.Context().Close()
return nil, fmt.Errorf("failed to set wake pipe nonblock: %w", err)
}
sc := &SharedContext{
display: display,
stopChan: make(chan struct{}),
fatalError: make(chan error, 1),
cmdQueue: make(chan func(), 256),
wakeR: fds[0],
wakeW: fds[1],
started: false,
}
return sc, nil
}
func (sc *SharedContext) Start() {
sc.mu.Lock()
defer sc.mu.Unlock()
if sc.started {
return
}
sc.started = true
sc.wg.Add(1)
go sc.eventDispatcher()
}
func (sc *SharedContext) Display() *wlclient.Display {
return sc.display
}
func (sc *SharedContext) Post(fn func()) {
select {
case sc.cmdQueue <- fn:
if _, err := unix.Write(sc.wakeW, []byte{1}); err != nil && err != unix.EAGAIN {
log.Errorf("wake pipe write error: %v", err)
}
default:
}
}
func (sc *SharedContext) FatalError() <-chan error {
return sc.fatalError
}
func (sc *SharedContext) eventDispatcher() {
defer sc.wg.Done()
defer func() {
if r := recover(); r != nil {
err := fmt.Errorf("FATAL: Wayland event dispatcher panic: %v", r)
log.Error(err)
select {
case sc.fatalError <- err:
default:
}
}
}()
ctx := sc.display.Context()
wlFd := ctx.Fd()
pollFds := []unix.PollFd{
{Fd: int32(wlFd), Events: unix.POLLIN},
{Fd: int32(sc.wakeR), Events: unix.POLLIN},
}
for {
select {
case <-sc.stopChan:
return
default:
}
sc.drainCmdQueue()
n, err := unix.Poll(pollFds, 50)
if err != nil {
if err == unix.EINTR {
continue
}
log.Errorf("Poll error: %v", err)
return
}
if n == 0 {
continue
}
if pollFds[1].Revents&unix.POLLIN != 0 {
var buf [64]byte
if _, err := unix.Read(sc.wakeR, buf[:]); err != nil && err != unix.EAGAIN {
log.Errorf("wake pipe read error: %v", err)
}
}
if pollFds[0].Revents&unix.POLLIN != 0 {
if err := ctx.Dispatch(); err != nil {
if !os.IsTimeout(err) {
log.Errorf("Wayland connection error: %v", err)
return
}
}
}
}
}
func (sc *SharedContext) drainCmdQueue() {
for {
select {
case fn := <-sc.cmdQueue:
fn()
default:
return
}
}
}
func (sc *SharedContext) Close() {
close(sc.stopChan)
sc.wg.Wait()
unix.Close(sc.wakeR)
unix.Close(sc.wakeW)
if sc.display != nil {
sc.display.Context().Close()
}
}