From a2b924d7e9492b978ad6dc33b6c1ffcb304b4bc5 Mon Sep 17 00:00:00 2001 From: Nippy Date: Sat, 9 May 2026 13:33:09 +0200 Subject: raveos update --- .../core/internal/server/wlroutput/handlers.go | 282 ------------ .../core/internal/server/wlroutput/manager.go | 492 --------------------- .../core/internal/server/wlroutput/manager_test.go | 414 ----------------- .../core/internal/server/wlroutput/types.go | 192 -------- 4 files changed, 1380 deletions(-) delete mode 100644 raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/handlers.go delete mode 100644 raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/manager.go delete mode 100644 raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/manager_test.go delete mode 100644 raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/types.go (limited to 'raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput') diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/handlers.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/handlers.go deleted file mode 100644 index c885e13..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/handlers.go +++ /dev/null @@ -1,282 +0,0 @@ -package wlroutput - -import ( - "encoding/json" - "fmt" - "net" - "time" - - "github.com/AvengeMedia/DankMaterialShell/core/internal/log" - "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_management" - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" -) - -type HeadConfig struct { - Name string `json:"name"` - Enabled bool `json:"enabled"` - ModeID *uint32 `json:"modeId,omitempty"` - CustomMode *struct { - Width int32 `json:"width"` - Height int32 `json:"height"` - Refresh int32 `json:"refresh"` - } `json:"customMode,omitempty"` - Position *struct{ X, Y int32 } `json:"position,omitempty"` - Transform *int32 `json:"transform,omitempty"` - Scale *float64 `json:"scale,omitempty"` - AdaptiveSync *uint32 `json:"adaptiveSync,omitempty"` -} - -type ConfigurationRequest struct { - Heads []HeadConfig `json:"heads"` - Test bool `json:"test"` -} - -func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { - if manager == nil { - models.RespondError(conn, req.ID, "wlroutput manager not initialized") - return - } - - switch req.Method { - case "wlroutput.getState": - handleGetState(conn, req, manager) - case "wlroutput.applyConfiguration": - handleApplyConfiguration(conn, req, manager, false) - case "wlroutput.testConfiguration": - handleApplyConfiguration(conn, req, manager, true) - case "wlroutput.subscribe": - handleSubscribe(conn, req, manager) - default: - models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method)) - } -} - -func handleGetState(conn net.Conn, req models.Request, manager *Manager) { - models.Respond(conn, req.ID, manager.GetState()) -} - -func handleApplyConfiguration(conn net.Conn, req models.Request, manager *Manager, test bool) { - headsParam, ok := models.Get[any](req, "heads") - if !ok { - models.RespondError(conn, req.ID, "missing 'heads' parameter") - return - } - - headsJSON, err := json.Marshal(headsParam) - if err != nil { - models.RespondError(conn, req.ID, "invalid 'heads' parameter format") - return - } - - var heads []HeadConfig - if err := json.Unmarshal(headsJSON, &heads); err != nil { - models.RespondError(conn, req.ID, fmt.Sprintf("invalid heads configuration: %v", err)) - return - } - - if err := manager.ApplyConfiguration(heads, test); err != nil { - models.RespondError(conn, req.ID, err.Error()) - return - } - - msg := "configuration applied" - if test { - msg = "configuration test succeeded" - } - models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: msg}) -} - -func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { - clientID := fmt.Sprintf("client-%p", conn) - stateChan := manager.Subscribe(clientID) - defer manager.Unsubscribe(clientID) - - initialState := manager.GetState() - if err := json.NewEncoder(conn).Encode(models.Response[State]{ - ID: req.ID, - Result: &initialState, - }); err != nil { - return - } - - for state := range stateChan { - if err := json.NewEncoder(conn).Encode(models.Response[State]{ - Result: &state, - }); err != nil { - return - } - } -} - -func (m *Manager) ApplyConfiguration(heads []HeadConfig, test bool) error { - if m.manager == nil { - return fmt.Errorf("output manager not initialized") - } - - resultChan := make(chan error, 1) - - m.post(func() { - m.wlMutex.Lock() - defer m.wlMutex.Unlock() - - config, err := m.manager.CreateConfiguration(m.serial) - if err != nil { - resultChan <- fmt.Errorf("failed to create configuration: %w", err) - return - } - - responded := false - - config.SetSucceededHandler(func(e wlr_output_management.ZwlrOutputConfigurationV1SucceededEvent) { - if responded { - return - } - responded = true - log.Info("WlrOutput: configuration succeeded") - config.Destroy() - resultChan <- nil - }) - - config.SetFailedHandler(func(e wlr_output_management.ZwlrOutputConfigurationV1FailedEvent) { - if responded { - return - } - responded = true - log.Warn("WlrOutput: configuration failed") - config.Destroy() - resultChan <- fmt.Errorf("compositor rejected configuration") - }) - - config.SetCancelledHandler(func(e wlr_output_management.ZwlrOutputConfigurationV1CancelledEvent) { - if responded { - return - } - responded = true - log.Warn("WlrOutput: configuration cancelled") - config.Destroy() - resultChan <- fmt.Errorf("configuration cancelled (outdated serial)") - }) - - time.AfterFunc(time.Second, func() { - m.post(func() { - if responded { - return - } - responded = true - config.Destroy() - resultChan <- fmt.Errorf("timeout waiting for configuration response") - }) - }) - - headsByName := make(map[string]*headState) - m.heads.Range(func(key uint32, head *headState) bool { - if !head.finished { - headsByName[head.name] = head - } - return true - }) - - for _, headCfg := range heads { - head, exists := headsByName[headCfg.Name] - if !exists { - config.Destroy() - resultChan <- fmt.Errorf("head not found: %s", headCfg.Name) - return - } - - if !headCfg.Enabled { - if err := config.DisableHead(head.handle); err != nil { - config.Destroy() - resultChan <- fmt.Errorf("failed to disable head %s: %w", headCfg.Name, err) - return - } - continue - } - - headConfig, err := config.EnableHead(head.handle) - if err != nil { - config.Destroy() - resultChan <- fmt.Errorf("failed to enable head %s: %w", headCfg.Name, err) - return - } - - if headCfg.ModeID != nil { - mode, exists := m.modes.Load(*headCfg.ModeID) - - if !exists { - config.Destroy() - resultChan <- fmt.Errorf("mode not found: %d", *headCfg.ModeID) - return - } - - if err := headConfig.SetMode(mode.handle); err != nil { - config.Destroy() - resultChan <- fmt.Errorf("failed to set mode for %s: %w", headCfg.Name, err) - return - } - } else if headCfg.CustomMode != nil { - if err := headConfig.SetCustomMode( - headCfg.CustomMode.Width, - headCfg.CustomMode.Height, - headCfg.CustomMode.Refresh, - ); err != nil { - config.Destroy() - resultChan <- fmt.Errorf("failed to set custom mode for %s: %w", headCfg.Name, err) - return - } - } - - if headCfg.Position != nil { - if err := headConfig.SetPosition(headCfg.Position.X, headCfg.Position.Y); err != nil { - config.Destroy() - resultChan <- fmt.Errorf("failed to set position for %s: %w", headCfg.Name, err) - return - } - } - - if headCfg.Transform != nil { - if err := headConfig.SetTransform(*headCfg.Transform); err != nil { - config.Destroy() - resultChan <- fmt.Errorf("failed to set transform for %s: %w", headCfg.Name, err) - return - } - } - - if headCfg.Scale != nil { - if err := headConfig.SetScale(*headCfg.Scale); err != nil { - config.Destroy() - resultChan <- fmt.Errorf("failed to set scale for %s: %w", headCfg.Name, err) - return - } - } - - if headCfg.AdaptiveSync != nil { - if err := headConfig.SetAdaptiveSync(*headCfg.AdaptiveSync); err != nil { - config.Destroy() - resultChan <- fmt.Errorf("failed to set adaptive sync for %s: %w", headCfg.Name, err) - return - } - } - } - - var applyErr error - if test { - applyErr = config.Test() - } else { - applyErr = config.Apply() - } - - if applyErr != nil { - responded = true - config.Destroy() - action := "apply" - if test { - action = "test" - } - resultChan <- fmt.Errorf("failed to %s configuration: %w", action, applyErr) - return - } - }) - - return <-resultChan -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/manager.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/manager.go deleted file mode 100644 index cbd89a3..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/manager.go +++ /dev/null @@ -1,492 +0,0 @@ -package wlroutput - -import ( - "fmt" - "time" - - "github.com/AvengeMedia/DankMaterialShell/core/internal/log" - "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_management" - wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" -) - -func NewManager(display wlclient.WaylandDisplay) (*Manager, error) { - m := &Manager{ - display: display, - ctx: display.Context(), - cmdq: make(chan cmd, 512), - stopChan: make(chan struct{}), - dirty: make(chan struct{}, 1), - fatalError: make(chan error, 1), - } - - m.wg.Add(1) - go m.waylandActor() - - if err := m.setupRegistry(); err != nil { - close(m.stopChan) - m.wg.Wait() - return nil, err - } - - m.updateState() - - m.notifierWg.Add(1) - go m.notifier() - - return m, nil -} - -func (m *Manager) post(fn func()) { - select { - case m.cmdq <- cmd{fn: fn}: - default: - log.Warn("WlrOutput actor command queue full, dropping command") - } -} - -func (m *Manager) waylandActor() { - defer m.wg.Done() - defer func() { - if r := recover(); r != nil { - err := fmt.Errorf("waylandActor panic: %v", r) - log.Errorf("WlrOutput: %v", err) - - select { - case m.fatalError <- err: - default: - } - - select { - case <-m.stopChan: - default: - close(m.stopChan) - } - } - }() - - for { - select { - case <-m.stopChan: - return - case c := <-m.cmdq: - func() { - defer func() { - if r := recover(); r != nil { - log.Errorf("WlrOutput: command execution panic: %v", r) - } - }() - c.fn() - }() - } - } -} - -func (m *Manager) setupRegistry() error { - log.Info("WlrOutput: starting registry setup") - - registry, err := m.display.GetRegistry() - if err != nil { - return fmt.Errorf("failed to get registry: %w", err) - } - m.registry = registry - - registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) { - if e.Interface == wlr_output_management.ZwlrOutputManagerV1InterfaceName { - log.Infof("WlrOutput: found %s", wlr_output_management.ZwlrOutputManagerV1InterfaceName) - manager := wlr_output_management.NewZwlrOutputManagerV1(m.ctx) - version := e.Version - if version > 4 { - version = 4 - } - - manager.SetHeadHandler(func(e wlr_output_management.ZwlrOutputManagerV1HeadEvent) { - m.handleHead(e) - }) - - manager.SetDoneHandler(func(e wlr_output_management.ZwlrOutputManagerV1DoneEvent) { - log.Debugf("WlrOutput: done event received (serial=%d)", e.Serial) - m.serial = e.Serial - m.post(func() { - m.updateState() - }) - }) - - manager.SetFinishedHandler(func(e wlr_output_management.ZwlrOutputManagerV1FinishedEvent) { - log.Info("WlrOutput: finished event received") - }) - - if err := registry.Bind(e.Name, e.Interface, version, manager); err == nil { - m.manager = manager - log.Info("WlrOutput: manager bound successfully") - } else { - log.Errorf("WlrOutput: failed to bind manager: %v", err) - } - } - }) - - log.Info("WlrOutput: registry setup complete (events will be processed async)") - return nil -} - -func (m *Manager) handleHead(e wlr_output_management.ZwlrOutputManagerV1HeadEvent) { - handle := e.Head - headID := handle.ID() - - log.Debugf("WlrOutput: New head (id=%d)", headID) - - head := &headState{ - id: headID, - handle: handle, - modeIDs: make([]uint32, 0), - } - - m.heads.Store(headID, head) - - handle.SetNameHandler(func(e wlr_output_management.ZwlrOutputHeadV1NameEvent) { - log.Debugf("WlrOutput: Head %d name: %s", headID, e.Name) - head.name = e.Name - head.ready = true - m.post(func() { - m.updateState() - }) - }) - - handle.SetDescriptionHandler(func(e wlr_output_management.ZwlrOutputHeadV1DescriptionEvent) { - log.Debugf("WlrOutput: Head %d description: %s", headID, e.Description) - head.description = e.Description - m.post(func() { - m.updateState() - }) - }) - - handle.SetPhysicalSizeHandler(func(e wlr_output_management.ZwlrOutputHeadV1PhysicalSizeEvent) { - log.Debugf("WlrOutput: Head %d physical size: %dx%d", headID, e.Width, e.Height) - head.physicalWidth = e.Width - head.physicalHeight = e.Height - m.post(func() { - m.updateState() - }) - }) - - handle.SetModeHandler(func(e wlr_output_management.ZwlrOutputHeadV1ModeEvent) { - m.handleMode(headID, e) - }) - - handle.SetEnabledHandler(func(e wlr_output_management.ZwlrOutputHeadV1EnabledEvent) { - log.Debugf("WlrOutput: Head %d enabled: %d", headID, e.Enabled) - head.enabled = e.Enabled != 0 - m.post(func() { - m.updateState() - }) - }) - - handle.SetCurrentModeHandler(func(e wlr_output_management.ZwlrOutputHeadV1CurrentModeEvent) { - modeID := e.Mode.ID() - log.Debugf("WlrOutput: Head %d current mode: %d", headID, modeID) - head.currentModeID = modeID - m.post(func() { - m.updateState() - }) - }) - - handle.SetPositionHandler(func(e wlr_output_management.ZwlrOutputHeadV1PositionEvent) { - log.Debugf("WlrOutput: Head %d position: %d,%d", headID, e.X, e.Y) - head.x = e.X - head.y = e.Y - m.post(func() { - m.updateState() - }) - }) - - handle.SetTransformHandler(func(e wlr_output_management.ZwlrOutputHeadV1TransformEvent) { - log.Debugf("WlrOutput: Head %d transform: %d", headID, e.Transform) - head.transform = e.Transform - m.post(func() { - m.updateState() - }) - }) - - handle.SetScaleHandler(func(e wlr_output_management.ZwlrOutputHeadV1ScaleEvent) { - log.Debugf("WlrOutput: Head %d scale: %f", headID, e.Scale) - head.scale = e.Scale - m.post(func() { - m.updateState() - }) - }) - - handle.SetMakeHandler(func(e wlr_output_management.ZwlrOutputHeadV1MakeEvent) { - log.Debugf("WlrOutput: Head %d make: %s", headID, e.Make) - head.make = e.Make - m.post(func() { - m.updateState() - }) - }) - - handle.SetModelHandler(func(e wlr_output_management.ZwlrOutputHeadV1ModelEvent) { - log.Debugf("WlrOutput: Head %d model: %s", headID, e.Model) - head.model = e.Model - m.post(func() { - m.updateState() - }) - }) - - handle.SetSerialNumberHandler(func(e wlr_output_management.ZwlrOutputHeadV1SerialNumberEvent) { - log.Debugf("WlrOutput: Head %d serial: %s", headID, e.SerialNumber) - head.serialNumber = e.SerialNumber - m.post(func() { - m.updateState() - }) - }) - - handle.SetAdaptiveSyncHandler(func(e wlr_output_management.ZwlrOutputHeadV1AdaptiveSyncEvent) { - log.Debugf("WlrOutput: Head %d adaptive sync: %d", headID, e.State) - head.adaptiveSync = e.State - head.adaptiveSyncSupported = true - m.post(func() { - m.updateState() - }) - }) - - handle.SetFinishedHandler(func(e wlr_output_management.ZwlrOutputHeadV1FinishedEvent) { - log.Debugf("WlrOutput: Head %d finished", headID) - head.finished = true - - m.heads.Delete(headID) - - m.wlMutex.Lock() - handle.Release() - m.wlMutex.Unlock() - - m.post(func() { - m.updateState() - }) - }) -} - -func (m *Manager) handleMode(headID uint32, e wlr_output_management.ZwlrOutputHeadV1ModeEvent) { - handle := e.Mode - modeID := handle.ID() - - log.Debugf("WlrOutput: Head %d new mode (id=%d)", headID, modeID) - - mode := &modeState{ - id: modeID, - handle: handle, - } - - m.modes.Store(modeID, mode) - - if head, ok := m.heads.Load(headID); ok { - head.modeIDs = append(head.modeIDs, modeID) - m.heads.Store(headID, head) - } - - handle.SetSizeHandler(func(e wlr_output_management.ZwlrOutputModeV1SizeEvent) { - log.Debugf("WlrOutput: Mode %d size: %dx%d", modeID, e.Width, e.Height) - mode.width = e.Width - mode.height = e.Height - m.post(func() { - m.updateState() - }) - }) - - handle.SetRefreshHandler(func(e wlr_output_management.ZwlrOutputModeV1RefreshEvent) { - log.Debugf("WlrOutput: Mode %d refresh: %d", modeID, e.Refresh) - mode.refresh = e.Refresh - m.post(func() { - m.updateState() - }) - }) - - handle.SetPreferredHandler(func(e wlr_output_management.ZwlrOutputModeV1PreferredEvent) { - log.Debugf("WlrOutput: Mode %d preferred", modeID) - mode.preferred = true - m.post(func() { - m.updateState() - }) - }) - - handle.SetFinishedHandler(func(e wlr_output_management.ZwlrOutputModeV1FinishedEvent) { - log.Debugf("WlrOutput: Mode %d finished", modeID) - mode.finished = true - - m.modes.Delete(modeID) - - m.wlMutex.Lock() - handle.Release() - m.wlMutex.Unlock() - - m.post(func() { - m.updateState() - }) - }) -} - -func (m *Manager) updateState() { - outputs := make([]Output, 0) - - m.heads.Range(func(key uint32, head *headState) bool { - if head.finished { - return true - } - - if !head.ready { - return true - } - - modes := make([]OutputMode, 0) - var currentMode *OutputMode - - for _, modeID := range head.modeIDs { - mode, exists := m.modes.Load(modeID) - if !exists { - continue - } - if mode.finished { - continue - } - - outMode := OutputMode{ - Width: mode.width, - Height: mode.height, - Refresh: mode.refresh, - Preferred: mode.preferred, - ID: modeID, - } - modes = append(modes, outMode) - - if modeID == head.currentModeID { - currentMode = &outMode - } - } - - output := Output{ - Name: head.name, - Description: head.description, - Make: head.make, - Model: head.model, - SerialNumber: head.serialNumber, - PhysicalWidth: head.physicalWidth, - PhysicalHeight: head.physicalHeight, - Enabled: head.enabled, - X: head.x, - Y: head.y, - Transform: head.transform, - Scale: head.scale, - CurrentMode: currentMode, - Modes: modes, - AdaptiveSync: head.adaptiveSync, - AdaptiveSyncSupported: head.adaptiveSyncSupported, - ID: head.id, - } - outputs = append(outputs, output) - return true - }) - - newState := State{ - Outputs: outputs, - Serial: m.serial, - } - - m.stateMutex.Lock() - m.state = &newState - m.stateMutex.Unlock() - - m.notifySubscribers() -} - -func (m *Manager) notifier() { - defer m.notifierWg.Done() - defer func() { - if r := recover(); r != nil { - err := fmt.Errorf("notifier panic: %v", r) - log.Errorf("WlrOutput: %v", err) - - select { - case m.fatalError <- err: - default: - } - - select { - case <-m.stopChan: - default: - close(m.stopChan) - } - } - }() - - const minGap = 100 * time.Millisecond - timer := time.NewTimer(minGap) - timer.Stop() - var pending bool - - for { - select { - case <-m.stopChan: - timer.Stop() - return - case <-m.dirty: - if pending { - continue - } - pending = true - timer.Reset(minGap) - case <-timer.C: - if !pending { - continue - } - - currentState := m.GetState() - - if m.lastNotified != nil && !stateChanged(m.lastNotified, ¤tState) { - pending = false - continue - } - - m.subscribers.Range(func(key string, ch chan State) bool { - select { - case ch <- currentState: - default: - log.Warn("WlrOutput: subscriber channel full, dropping update") - } - return true - }) - - stateCopy := currentState - m.lastNotified = &stateCopy - pending = false - } - } -} - -func (m *Manager) Close() { - close(m.stopChan) - m.wg.Wait() - m.notifierWg.Wait() - - m.subscribers.Range(func(key string, ch chan State) bool { - close(ch) - m.subscribers.Delete(key) - return true - }) - - m.modes.Range(func(key uint32, mode *modeState) bool { - if mode.handle != nil { - mode.handle.Release() - } - m.modes.Delete(key) - return true - }) - - m.heads.Range(func(key uint32, head *headState) bool { - if head.handle != nil { - head.handle.Release() - } - m.heads.Delete(key) - return true - }) - - if m.manager != nil { - m.manager.Stop() - } -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/manager_test.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/manager_test.go deleted file mode 100644 index e90c70c..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/manager_test.go +++ /dev/null @@ -1,414 +0,0 @@ -package wlroutput - -import ( - "errors" - "sync" - "testing" - "time" - - "github.com/stretchr/testify/assert" - - mocks_wlclient "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/wlclient" -) - -func TestStateChanged_BothNil(t *testing.T) { - assert.True(t, stateChanged(nil, nil)) -} - -func TestStateChanged_OneNil(t *testing.T) { - s := &State{Serial: 1} - assert.True(t, stateChanged(s, nil)) - assert.True(t, stateChanged(nil, s)) -} - -func TestStateChanged_SerialDiffers(t *testing.T) { - a := &State{Serial: 1, Outputs: []Output{}} - b := &State{Serial: 2, Outputs: []Output{}} - assert.True(t, stateChanged(a, b)) -} - -func TestStateChanged_OutputCountDiffers(t *testing.T) { - a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1"}}} - b := &State{Serial: 1, Outputs: []Output{}} - assert.True(t, stateChanged(a, b)) -} - -func TestStateChanged_OutputNameDiffers(t *testing.T) { - a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Enabled: true}}} - b := &State{Serial: 1, Outputs: []Output{{Name: "HDMI-A-1", Enabled: true}}} - assert.True(t, stateChanged(a, b)) -} - -func TestStateChanged_OutputEnabledDiffers(t *testing.T) { - a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Enabled: true}}} - b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Enabled: false}}} - assert.True(t, stateChanged(a, b)) -} - -func TestStateChanged_OutputPositionDiffers(t *testing.T) { - a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", X: 0, Y: 0}}} - b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", X: 1920, Y: 0}}} - assert.True(t, stateChanged(a, b)) -} - -func TestStateChanged_OutputTransformDiffers(t *testing.T) { - a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Transform: 0}}} - b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Transform: 1}}} - assert.True(t, stateChanged(a, b)) -} - -func TestStateChanged_OutputScaleDiffers(t *testing.T) { - a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Scale: 1.0}}} - b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Scale: 2.0}}} - assert.True(t, stateChanged(a, b)) -} - -func TestStateChanged_OutputAdaptiveSyncDiffers(t *testing.T) { - a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", AdaptiveSync: 0}}} - b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", AdaptiveSync: 1}}} - assert.True(t, stateChanged(a, b)) -} - -func TestStateChanged_CurrentModeNilVsNonNil(t *testing.T) { - a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", CurrentMode: nil}}} - b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", CurrentMode: &OutputMode{Width: 1920}}}} - assert.True(t, stateChanged(a, b)) -} - -func TestStateChanged_CurrentModeDiffers(t *testing.T) { - a := &State{Serial: 1, Outputs: []Output{{ - Name: "eDP-1", - CurrentMode: &OutputMode{Width: 1920, Height: 1080, Refresh: 60000}, - }}} - b := &State{Serial: 1, Outputs: []Output{{ - Name: "eDP-1", - CurrentMode: &OutputMode{Width: 2560, Height: 1440, Refresh: 60000}, - }}} - assert.True(t, stateChanged(a, b)) - - b.Outputs[0].CurrentMode.Width = 1920 - b.Outputs[0].CurrentMode.Height = 1080 - b.Outputs[0].CurrentMode.Refresh = 144000 - assert.True(t, stateChanged(a, b)) -} - -func TestStateChanged_ModesLengthDiffers(t *testing.T) { - a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Modes: []OutputMode{{Width: 1920}}}}} - b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", Modes: []OutputMode{{Width: 1920}, {Width: 1280}}}}} - assert.True(t, stateChanged(a, b)) -} - -func TestStateChanged_Equal(t *testing.T) { - mode := OutputMode{Width: 1920, Height: 1080, Refresh: 60000, Preferred: true} - a := &State{ - Serial: 5, - Outputs: []Output{{ - Name: "eDP-1", - Description: "Built-in display", - Make: "BOE", - Model: "0x0ABC", - SerialNumber: "12345", - PhysicalWidth: 309, - PhysicalHeight: 174, - Enabled: true, - X: 0, - Y: 0, - Transform: 0, - Scale: 1.0, - CurrentMode: &mode, - Modes: []OutputMode{mode}, - AdaptiveSync: 0, - }}, - } - b := &State{ - Serial: 5, - Outputs: []Output{{ - Name: "eDP-1", - Description: "Built-in display", - Make: "BOE", - Model: "0x0ABC", - SerialNumber: "12345", - PhysicalWidth: 309, - PhysicalHeight: 174, - Enabled: true, - X: 0, - Y: 0, - Transform: 0, - Scale: 1.0, - CurrentMode: &mode, - Modes: []OutputMode{mode}, - AdaptiveSync: 0, - }}, - } - assert.False(t, stateChanged(a, b)) -} - -func TestManager_ConcurrentGetState(t *testing.T) { - m := &Manager{ - state: &State{ - Serial: 1, - Outputs: []Output{{Name: "eDP-1", Enabled: true}}, - }, - } - - var wg sync.WaitGroup - const goroutines = 50 - const iterations = 100 - - for i := 0; i < goroutines/2; i++ { - wg.Add(1) - go func() { - defer wg.Done() - for j := 0; j < iterations; j++ { - s := m.GetState() - _ = s.Serial - _ = s.Outputs - } - }() - } - - for i := 0; i < goroutines/2; i++ { - wg.Add(1) - go func(i int) { - defer wg.Done() - for j := 0; j < iterations; j++ { - m.stateMutex.Lock() - m.state = &State{ - Serial: uint32(j), - Outputs: []Output{{Name: "eDP-1", Scale: float64(j % 3)}}, - } - m.stateMutex.Unlock() - } - }(i) - } - - wg.Wait() -} - -func TestManager_ConcurrentSubscriberAccess(t *testing.T) { - m := &Manager{ - stopChan: make(chan struct{}), - dirty: make(chan struct{}, 1), - } - - var wg sync.WaitGroup - const goroutines = 20 - - for i := 0; i < goroutines; i++ { - wg.Add(1) - go func(id int) { - defer wg.Done() - subID := string(rune('a' + id)) - ch := m.Subscribe(subID) - assert.NotNil(t, ch) - time.Sleep(time.Millisecond) - m.Unsubscribe(subID) - }(i) - } - - wg.Wait() -} - -func TestManager_SyncmapHeadsConcurrentAccess(t *testing.T) { - m := &Manager{} - - var wg sync.WaitGroup - const goroutines = 30 - const iterations = 50 - - for i := 0; i < goroutines; i++ { - wg.Add(1) - go func(id int) { - defer wg.Done() - key := uint32(id) - - for j := 0; j < iterations; j++ { - state := &headState{ - id: key, - name: "test-head", - enabled: j%2 == 0, - scale: float64(j % 3), - modeIDs: []uint32{uint32(j)}, - } - m.heads.Store(key, state) - - if loaded, ok := m.heads.Load(key); ok { - assert.Equal(t, key, loaded.id) - } - - m.heads.Range(func(k uint32, v *headState) bool { - _ = v.name - _ = v.enabled - return true - }) - } - - m.heads.Delete(key) - }(i) - } - - wg.Wait() -} - -func TestManager_SyncmapModesConcurrentAccess(t *testing.T) { - m := &Manager{} - - var wg sync.WaitGroup - const goroutines = 30 - const iterations = 50 - - for i := 0; i < goroutines; i++ { - wg.Add(1) - go func(id int) { - defer wg.Done() - key := uint32(id) - - for j := 0; j < iterations; j++ { - state := &modeState{ - id: key, - width: int32(1920 + j), - height: int32(1080 + j), - refresh: 60000, - preferred: j == 0, - } - m.modes.Store(key, state) - - if loaded, ok := m.modes.Load(key); ok { - assert.Equal(t, key, loaded.id) - } - - m.modes.Range(func(k uint32, v *modeState) bool { - _ = v.width - _ = v.height - return true - }) - } - - m.modes.Delete(key) - }(i) - } - - wg.Wait() -} - -func TestManager_NotifySubscribersNonBlocking(t *testing.T) { - m := &Manager{ - dirty: make(chan struct{}, 1), - } - - for i := 0; i < 10; i++ { - m.notifySubscribers() - } - - assert.Len(t, m.dirty, 1) -} - -func TestManager_PostQueueFull(t *testing.T) { - m := &Manager{ - cmdq: make(chan cmd, 2), - stopChan: make(chan struct{}), - } - - m.post(func() {}) - m.post(func() {}) - m.post(func() {}) - m.post(func() {}) - - assert.Len(t, m.cmdq, 2) -} - -func TestManager_GetStateNilState(t *testing.T) { - m := &Manager{} - - s := m.GetState() - assert.NotNil(t, s.Outputs) - assert.Equal(t, uint32(0), s.Serial) -} - -func TestManager_FatalErrorChannel(t *testing.T) { - m := &Manager{ - fatalError: make(chan error, 1), - } - - ch := m.FatalError() - assert.NotNil(t, ch) - - m.fatalError <- assert.AnError - err := <-ch - assert.Error(t, err) -} - -func TestOutputMode_Fields(t *testing.T) { - mode := OutputMode{ - Width: 1920, - Height: 1080, - Refresh: 60000, - Preferred: true, - ID: 42, - } - - assert.Equal(t, int32(1920), mode.Width) - assert.Equal(t, int32(1080), mode.Height) - assert.Equal(t, int32(60000), mode.Refresh) - assert.True(t, mode.Preferred) - assert.Equal(t, uint32(42), mode.ID) -} - -func TestOutput_Fields(t *testing.T) { - out := Output{ - Name: "eDP-1", - Description: "Built-in display", - Make: "BOE", - Model: "0x0ABC", - SerialNumber: "12345", - PhysicalWidth: 309, - PhysicalHeight: 174, - Enabled: true, - X: 0, - Y: 0, - Transform: 0, - Scale: 1.5, - AdaptiveSync: 1, - ID: 1, - } - - assert.Equal(t, "eDP-1", out.Name) - assert.Equal(t, "Built-in display", out.Description) - assert.True(t, out.Enabled) - assert.Equal(t, float64(1.5), out.Scale) - assert.Equal(t, uint32(1), out.AdaptiveSync) -} - -func TestHeadState_ModeIDsSlice(t *testing.T) { - head := &headState{ - id: 1, - modeIDs: make([]uint32, 0), - } - - head.modeIDs = append(head.modeIDs, 1, 2, 3) - assert.Len(t, head.modeIDs, 3) - assert.Equal(t, uint32(1), head.modeIDs[0]) -} - -func TestStateChanged_BothCurrentModeNil(t *testing.T) { - a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", CurrentMode: nil}}} - b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1", CurrentMode: nil}}} - assert.False(t, stateChanged(a, b)) -} - -func TestStateChanged_IndexOutOfBounds(t *testing.T) { - a := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1"}}} - b := &State{Serial: 1, Outputs: []Output{{Name: "eDP-1"}, {Name: "HDMI-A-1"}}} - assert.True(t, stateChanged(a, b)) -} - -func TestNewManager_GetRegistryError(t *testing.T) { - mockDisplay := mocks_wlclient.NewMockWaylandDisplay(t) - - mockDisplay.EXPECT().Context().Return(nil) - mockDisplay.EXPECT().GetRegistry().Return(nil, errors.New("failed to get registry")) - - _, err := NewManager(mockDisplay) - assert.Error(t, err) - assert.Contains(t, err.Error(), "failed to get registry") -} diff --git a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/types.go b/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/types.go deleted file mode 100644 index 9c79b1a..0000000 --- a/raveos-hyprland-theme/theme-data/DankMaterialShell/core/internal/server/wlroutput/types.go +++ /dev/null @@ -1,192 +0,0 @@ -package wlroutput - -import ( - "sync" - - "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_management" - wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" -) - -type OutputMode struct { - Width int32 `json:"width"` - Height int32 `json:"height"` - Refresh int32 `json:"refresh"` - Preferred bool `json:"preferred"` - ID uint32 `json:"id"` -} - -type Output struct { - Name string `json:"name"` - Description string `json:"description"` - Make string `json:"make"` - Model string `json:"model"` - SerialNumber string `json:"serialNumber"` - PhysicalWidth int32 `json:"physicalWidth"` - PhysicalHeight int32 `json:"physicalHeight"` - Enabled bool `json:"enabled"` - X int32 `json:"x"` - Y int32 `json:"y"` - Transform int32 `json:"transform"` - Scale float64 `json:"scale"` - CurrentMode *OutputMode `json:"currentMode"` - Modes []OutputMode `json:"modes"` - AdaptiveSync uint32 `json:"adaptiveSync"` - AdaptiveSyncSupported bool `json:"adaptiveSyncSupported"` - ID uint32 `json:"id"` -} - -type State struct { - Outputs []Output `json:"outputs"` - Serial uint32 `json:"serial"` -} - -type cmd struct { - fn func() -} - -type Manager struct { - display wlclient.WaylandDisplay - ctx *wlclient.Context - registry *wlclient.Registry - manager *wlr_output_management.ZwlrOutputManagerV1 - - heads syncmap.Map[uint32, *headState] - modes syncmap.Map[uint32, *modeState] - - serial uint32 - - wlMutex sync.Mutex - cmdq chan cmd - stopChan chan struct{} - wg sync.WaitGroup - - subscribers syncmap.Map[string, chan State] - dirty chan struct{} - notifierWg sync.WaitGroup - lastNotified *State - - stateMutex sync.RWMutex - state *State - - fatalError chan error -} - -type headState struct { - id uint32 - handle *wlr_output_management.ZwlrOutputHeadV1 - name string - description string - make string - model string - serialNumber string - physicalWidth int32 - physicalHeight int32 - enabled bool - x int32 - y int32 - transform int32 - scale float64 - currentModeID uint32 - modeIDs []uint32 - adaptiveSync uint32 - adaptiveSyncSupported bool - finished bool - ready bool -} - -type modeState struct { - id uint32 - handle *wlr_output_management.ZwlrOutputModeV1 - width int32 - height int32 - refresh int32 - preferred bool - finished bool -} - -func (m *Manager) GetState() State { - m.stateMutex.RLock() - defer m.stateMutex.RUnlock() - if m.state == nil { - return State{ - Outputs: []Output{}, - Serial: 0, - } - } - stateCopy := *m.state - return stateCopy -} - -func (m *Manager) Subscribe(id string) chan State { - ch := make(chan State, 64) - - m.subscribers.Store(id, ch) - - return ch -} - -func (m *Manager) Unsubscribe(id string) { - - if val, ok := m.subscribers.LoadAndDelete(id); ok { - close(val) - - } - -} - -func (m *Manager) notifySubscribers() { - select { - case m.dirty <- struct{}{}: - default: - } -} - -func (m *Manager) FatalError() <-chan error { - return m.fatalError -} - -func stateChanged(old, new *State) bool { - if old == nil || new == nil { - return true - } - if old.Serial != new.Serial { - return true - } - if len(old.Outputs) != len(new.Outputs) { - return true - } - for i := range new.Outputs { - if i >= len(old.Outputs) { - return true - } - oldOut := &old.Outputs[i] - newOut := &new.Outputs[i] - if oldOut.Name != newOut.Name || oldOut.Enabled != newOut.Enabled { - return true - } - if oldOut.X != newOut.X || oldOut.Y != newOut.Y { - return true - } - if oldOut.Transform != newOut.Transform || oldOut.Scale != newOut.Scale { - return true - } - if oldOut.AdaptiveSync != newOut.AdaptiveSync || oldOut.AdaptiveSyncSupported != newOut.AdaptiveSyncSupported { - return true - } - if (oldOut.CurrentMode == nil) != (newOut.CurrentMode == nil) { - return true - } - if oldOut.CurrentMode != nil && newOut.CurrentMode != nil { - if oldOut.CurrentMode.Width != newOut.CurrentMode.Width || - oldOut.CurrentMode.Height != newOut.CurrentMode.Height || - oldOut.CurrentMode.Refresh != newOut.CurrentMode.Refresh { - return true - } - } - if len(oldOut.Modes) != len(newOut.Modes) { - return true - } - } - return false -} -- cgit v1.3