package main import ( "fmt" "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_management" wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" ) type randrOutput struct { Name string `json:"name"` Scale float64 `json:"scale"` Width int32 `json:"width"` Height int32 `json:"height"` Refresh int32 `json:"refresh"` Enabled bool `json:"enabled"` } type randrHead struct { name string enabled bool scale float64 currentModeID uint32 modeIDs []uint32 } type randrMode struct { width int32 height int32 refresh int32 } type randrClient struct { display *wlclient.Display ctx *wlclient.Context manager *wlr_output_management.ZwlrOutputManagerV1 heads map[uint32]*randrHead modes map[uint32]*randrMode done bool err error } func queryRandr() ([]randrOutput, error) { display, err := wlclient.Connect("") if err != nil { return nil, fmt.Errorf("failed to connect to Wayland: %w", err) } c := &randrClient{ display: display, ctx: display.Context(), heads: make(map[uint32]*randrHead), modes: make(map[uint32]*randrMode), } defer c.ctx.Close() registry, err := display.GetRegistry() if err != nil { return nil, fmt.Errorf("failed to get registry: %w", err) } registry.SetGlobalHandler(func(e wlclient.RegistryGlobalEvent) { if e.Interface == wlr_output_management.ZwlrOutputManagerV1InterfaceName { mgr := wlr_output_management.NewZwlrOutputManagerV1(c.ctx) version := min(e.Version, 4) mgr.SetHeadHandler(func(e wlr_output_management.ZwlrOutputManagerV1HeadEvent) { c.handleHead(e) }) mgr.SetDoneHandler(func(e wlr_output_management.ZwlrOutputManagerV1DoneEvent) { c.done = true }) if err := registry.Bind(e.Name, e.Interface, version, mgr); err == nil { c.manager = mgr } } }) // First roundtrip: discover globals and bind manager syncCallback, err := display.Sync() if err != nil { return nil, fmt.Errorf("failed to sync display: %w", err) } syncCallback.SetDoneHandler(func(e wlclient.CallbackDoneEvent) { if c.manager == nil { c.err = fmt.Errorf("zwlr_output_manager_v1 protocol not supported by compositor") c.done = true } // Otherwise wait for manager's DoneHandler }) for !c.done { if err := c.ctx.Dispatch(); err != nil { return nil, fmt.Errorf("dispatch error: %w", err) } } if c.err != nil { return nil, c.err } return c.buildOutputs(), nil } func (c *randrClient) handleHead(e wlr_output_management.ZwlrOutputManagerV1HeadEvent) { handle := e.Head headID := handle.ID() head := &randrHead{ modeIDs: make([]uint32, 0), } c.heads[headID] = head handle.SetNameHandler(func(e wlr_output_management.ZwlrOutputHeadV1NameEvent) { head.name = e.Name }) handle.SetEnabledHandler(func(e wlr_output_management.ZwlrOutputHeadV1EnabledEvent) { head.enabled = e.Enabled != 0 }) handle.SetScaleHandler(func(e wlr_output_management.ZwlrOutputHeadV1ScaleEvent) { head.scale = e.Scale }) handle.SetCurrentModeHandler(func(e wlr_output_management.ZwlrOutputHeadV1CurrentModeEvent) { head.currentModeID = e.Mode.ID() }) handle.SetModeHandler(func(e wlr_output_management.ZwlrOutputHeadV1ModeEvent) { modeHandle := e.Mode modeID := modeHandle.ID() head.modeIDs = append(head.modeIDs, modeID) mode := &randrMode{} c.modes[modeID] = mode modeHandle.SetSizeHandler(func(e wlr_output_management.ZwlrOutputModeV1SizeEvent) { mode.width = e.Width mode.height = e.Height }) modeHandle.SetRefreshHandler(func(e wlr_output_management.ZwlrOutputModeV1RefreshEvent) { mode.refresh = e.Refresh }) }) } func (c *randrClient) buildOutputs() []randrOutput { outputs := make([]randrOutput, 0, len(c.heads)) for _, head := range c.heads { out := randrOutput{ Name: head.name, Scale: head.scale, Enabled: head.enabled, } if mode, ok := c.modes[head.currentModeID]; ok { out.Width = mode.width out.Height = mode.height out.Refresh = mode.refresh } outputs = append(outputs, out) } return outputs }