diff options
| author | AlexanderCurl <alexc@alexc.hu> | 2026-04-18 17:46:06 +0100 |
|---|---|---|
| committer | AlexanderCurl <alexc@alexc.hu> | 2026-04-18 17:46:06 +0100 |
| commit | 70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69 (patch) | |
| tree | ab1123d4067c1b086dd6faa7ee4ea643236b565a /raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlcontext | |
| parent | 5d94c0a7d44a2255b81815a52a7056a94a39842d (diff) | |
| download | RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.tar.gz RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.zip | |
Replaced file structures for themes in the correct format
Diffstat (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlcontext')
2 files changed, 328 insertions, 0 deletions
diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlcontext/context.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlcontext/context.go new file mode 100644 index 0000000..9bbb30a --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlcontext/context.go @@ -0,0 +1,200 @@ +package wlcontext + +import ( + "fmt" + "golang.org/x/sys/unix" + "os" + "sync" + "time" + + "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}, + } + + consecutiveErrors := 0 + const maxConsecutiveErrors = 20 + + for { + sc.drainCmdQueue() + + select { + case <-sc.stopChan: + return + default: + } + + _, err := unix.Poll(pollFds, -1) + switch { + case err == unix.EINTR: + continue + case err != nil: + log.Errorf("Poll error: %v", err) + return + } + + 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 { + continue + } + + if err := ctx.Dispatch(); err != nil && !os.IsTimeout(err) { + consecutiveErrors++ + log.Warnf("Wayland connection error (%d/%d): %v", consecutiveErrors, maxConsecutiveErrors, err) + + if consecutiveErrors >= maxConsecutiveErrors { + log.Errorf("Fatal: Wayland connection unrecoverable after %d attempts. Exiting dispatcher.", maxConsecutiveErrors) + return + } + + time.Sleep(100 * time.Millisecond * time.Duration(consecutiveErrors)) + continue + } + + consecutiveErrors = 0 + } +} + +func (sc *SharedContext) drainCmdQueue() { + for { + select { + case fn := <-sc.cmdQueue: + fn() + default: + return + } + } +} + +func (sc *SharedContext) Close() { + close(sc.stopChan) + if _, err := unix.Write(sc.wakeW, []byte{1}); err != nil && err != unix.EAGAIN { + log.Errorf("wake pipe write error on close: %v", err) + } + sc.wg.Wait() + + unix.Close(sc.wakeR) + unix.Close(sc.wakeW) + + if sc.display == nil { + return + } + sc.display.Context().Close() +} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlcontext/context_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlcontext/context_test.go new file mode 100644 index 0000000..11ecfbf --- /dev/null +++ b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlcontext/context_test.go @@ -0,0 +1,128 @@ +package wlcontext + +import ( + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "golang.org/x/sys/unix" +) + +func newTestSharedContext(t *testing.T, queueSize int) *SharedContext { + t.Helper() + fds := make([]int, 2) + if err := unix.Pipe(fds); err != nil { + t.Fatalf("failed to create test pipe: %v", err) + } + t.Cleanup(func() { + unix.Close(fds[0]) + unix.Close(fds[1]) + }) + return &SharedContext{ + cmdQueue: make(chan func(), queueSize), + stopChan: make(chan struct{}), + wakeR: fds[0], + wakeW: fds[1], + } +} + +func TestSharedContext_ConcurrentPostNonBlocking(t *testing.T) { + sc := newTestSharedContext(t, 256) + + var wg sync.WaitGroup + const goroutines = 100 + const iterations = 50 + + for i := 0; i < goroutines; i++ { + wg.Add(1) + go func(id int) { + defer wg.Done() + for j := 0; j < iterations; j++ { + sc.Post(func() { + _ = id + j + }) + } + }(i) + } + + wg.Wait() +} + +func TestSharedContext_PostQueueFull(t *testing.T) { + sc := newTestSharedContext(t, 2) + + sc.Post(func() {}) + sc.Post(func() {}) + sc.Post(func() {}) + sc.Post(func() {}) + + assert.Len(t, sc.cmdQueue, 2) +} + +func TestSharedContext_StartMultipleTimes(t *testing.T) { + sc := newTestSharedContext(t, 256) + sc.started = true + + var wg sync.WaitGroup + const goroutines = 10 + + for i := 0; i < goroutines; i++ { + wg.Add(1) + go func() { + defer wg.Done() + sc.Start() + }() + } + + wg.Wait() + + assert.True(t, sc.started) +} + +func TestSharedContext_DrainCmdQueue(t *testing.T) { + sc := newTestSharedContext(t, 256) + + counter := 0 + for i := 0; i < 10; i++ { + sc.cmdQueue <- func() { + counter++ + } + } + + sc.drainCmdQueue() + + assert.Equal(t, 10, counter) + assert.Len(t, sc.cmdQueue, 0) +} + +func TestSharedContext_DrainCmdQueueEmpty(t *testing.T) { + sc := newTestSharedContext(t, 256) + + sc.drainCmdQueue() + + assert.Len(t, sc.cmdQueue, 0) +} + +func TestSharedContext_ConcurrentDrainAndPost(t *testing.T) { + sc := newTestSharedContext(t, 256) + + var wg sync.WaitGroup + + wg.Add(1) + go func() { + defer wg.Done() + for i := 0; i < 100; i++ { + sc.Post(func() {}) + } + }() + + wg.Add(1) + go func() { + defer wg.Done() + for i := 0; i < 50; i++ { + sc.drainCmdQueue() + } + }() + + wg.Wait() +} |