Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions liveplacement_darwin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,14 @@ func callOnMain(f func()) {
func mainScreens(t *testing.T) []Screen {
t.Helper()
var (
ss []Screen
ss ScreenList
err error
)
callOnMain(func() { ss, err = Screens() })
if err != nil {
t.Fatalf("Screens() = %v", err)
}
return ss
return ss.All()
}

// mainOpen is Open() on the reserved thread.
Expand Down
6 changes: 3 additions & 3 deletions screen_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ func VisibleScreenSize() (w, h int, ok bool) {
// application, and enumerated from a goroutine that is not on the main thread,
// can therefore come back nameless. Everything placement depends on is exact
// regardless.
func Screens() ([]Screen, error) {
func Screens() (ScreenList, error) {
infos, err := cocoa.Screens()
if err != nil {
return nil, err
return ScreenList{}, err
}
out := make([]Screen, len(infos))
for i, s := range infos {
Expand All @@ -64,7 +64,7 @@ func Screens() ([]Screen, error) {
Primary: s.Primary,
}
}
return out, nil
return newScreenList(out)
}

// toCocoa is the reverse projection, used by Open to hand a chosen screen back
Expand Down
8 changes: 4 additions & 4 deletions screen_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ import (
// See [Screen] for what the fields mean; the two back-ends fill them from very
// different protocols and are documented where they do it (screen_wayland.go
// and screen_x11.go).
func Screens() ([]Screen, error) {
func Screens() (ScreenList, error) {
if name := os.Getenv("WAYLAND_DISPLAY"); name != "" {
return waylandScreens(name)
}
disp := os.Getenv("DISPLAY")
if disp == "" {
return nil, fmt.Errorf("window: cannot enumerate screens: neither WAYLAND_DISPLAY nor DISPLAY is set")
return ScreenList{}, fmt.Errorf("window: cannot enumerate screens: neither WAYLAND_DISPLAY nor DISPLAY is set")
}
return x11Screens(disp)
}
Expand All @@ -40,10 +40,10 @@ func Screens() ([]Screen, error) {
// every attached panel, not only the primary one.
func VisibleScreenSize() (w, h int, ok bool) {
screens, err := Screens()
if err != nil || len(screens) == 0 {
if err != nil {
return 0, 0, false
}
s := screens[0]
s := screens.Primary()
if s.VisibleWidth <= 0 || s.VisibleHeight <= 0 {
return 0, 0, false
}
Expand Down
2 changes: 1 addition & 1 deletion screen_live_windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestLiveScreens(t *testing.T) {
if os.Getenv("WINDOW_LIVE_SCREENS") != "1" {
t.Skip("set WINDOW_LIVE_SCREENS=1 to enumerate this machine's displays")
}
screens, err := Screens()
screens, err := allOf(Screens())
if err != nil {
t.Fatalf("Screens: %v", err)
}
Expand Down
4 changes: 2 additions & 2 deletions screen_other.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ func VisibleScreenSize() (w, h int, ok bool) {
// or wl_output and Windows through EnumDisplayMonitors; a browser has the
// Screen Detail API, so this remains a gap to be filled per back-end and not a
// limit of the API.
func Screens() ([]Screen, error) {
return nil, ErrScreensUnsupported
func Screens() (ScreenList, error) {
return ScreenList{}, ErrScreensUnsupported
}
10 changes: 5 additions & 5 deletions screen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,16 +57,16 @@ func TestScreenIsZero(t *testing.T) {
func TestScreens(t *testing.T) {
screens, err := Screens()
if err != nil {
if len(screens) != 0 {
t.Fatalf("Screens() failed (%v) but returned %d screens, want none", err, len(screens))
if screens.Len() != 0 {
t.Fatalf("Screens() failed (%v) but returned %d screens, want none", err, screens.Len())
}
if !errors.Is(err, ErrScreensUnsupported) {
t.Logf("Screens() unavailable for a platform reason: %v", err)
}
return
}
primaries := 0
for i, s := range screens {
for i, s := range screens.All() {
if s.IsZero() {
t.Errorf("screen %d is the zero value", i)
}
Expand All @@ -80,7 +80,7 @@ func TestScreens(t *testing.T) {
primaries++
}
}
if len(screens) > 0 && primaries != 1 {
t.Errorf("got %d primary screens among %d, want exactly 1", primaries, len(screens))
if screens.Len() > 0 && primaries != 1 {
t.Errorf("got %d primary screens among %d, want exactly 1", primaries, screens.Len())
}
}
22 changes: 11 additions & 11 deletions screen_wayland.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,50 +32,50 @@ import (
// It opens its OWN connection and closes it again: enumerating displays is
// something an application does before it has a window, and borrowing a
// window's connection would make the answer depend on having one.
func waylandScreens(name string) ([]Screen, error) {
func waylandScreens(name string) (ScreenList, error) {
path, err := waylandSocketPath(name)
if err != nil {
return nil, err
return ScreenList{}, err
}
nc, err := net.Dial("unix", path)
if err != nil {
return nil, fmt.Errorf("window: cannot connect to Wayland compositor: %w", err)
return ScreenList{}, fmt.Errorf("window: cannot connect to Wayland compositor: %w", err)
}
uc, ok := nc.(*net.UnixConn)
if !ok { // net.Dial("unix", ...) always yields *net.UnixConn
_ = nc.Close()
return nil, fmt.Errorf("window: Wayland dial returned %T, want *net.UnixConn", nc)
return ScreenList{}, fmt.Errorf("window: Wayland dial returned %T, want *net.UnixConn", nc)
}
return screensOnWayland(wayland.New(uc))
}

// screensOnWayland is waylandScreens with the connection already open, which
// is what makes the whole exchange testable against a scripted compositor.
// It closes the connection: it is the only owner of it.
func screensOnWayland(conn *wayland.Conn) ([]Screen, error) {
func screensOnWayland(conn *wayland.Conn) (ScreenList, error) {
defer func() { _ = conn.Close() }()

reg, err := conn.Display().GetRegistry()
if err != nil {
return nil, err
return ScreenList{}, err
}
// One round trip for the globals the compositor advertises, a second for
// the property burst each bound output then sends. Both are needed: an
// output read before its done has no mode and no name.
if err := conn.Roundtrip(); err != nil {
return nil, err
return ScreenList{}, err
}
outs, err := reg.Outputs()
if err != nil {
return nil, err
return ScreenList{}, err
}
if err := conn.Roundtrip(); err != nil {
return nil, err
return ScreenList{}, err
}
if len(outs) == 0 {
return nil, fmt.Errorf("window: the Wayland compositor advertises no output")
return ScreenList{}, fmt.Errorf("window: the Wayland compositor advertises no output: %w", ErrNoScreens)
}
return primaryFirst(waylandScreensOf(outs)), nil
return newScreenList(waylandScreensOf(outs))
}

// waylandScreensOf is the projection onto [Screen], separated from the dialing
Expand Down
16 changes: 8 additions & 8 deletions screen_wayland_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func fakeOutputCompositor(sc *srvConn, outs []outSpec) {

// dialFakeOutputs runs the scripted compositor over a socket pair and returns
// what screensOnWayland made of it.
func dialFakeOutputs(t *testing.T, outs []outSpec) ([]Screen, error) {
func dialFakeOutputs(t *testing.T, outs []outSpec) (ScreenList, error) {
t.Helper()
cli, srv := socketPairWin(t)
t.Cleanup(func() { _ = srv.Close() })
Expand All @@ -96,7 +96,7 @@ func dialFakeOutputs(t *testing.T, outs []outSpec) ([]Screen, error) {
}

func TestWaylandScreensReadTheOutputBurst(t *testing.T) {
screens, err := dialFakeOutputs(t, []outSpec{
screens, err := allOf(dialFakeOutputs(t, []outSpec{
// A 2x laptop panel: 2560x1440 device pixels are 1280x720 points.
{Make: "Sharp", Model: "LQ133M1", Connector: "eDP-1", Descr: "the built-in panel",
PhysWMM: 294, PhysHMM: 165,
Expand All @@ -106,7 +106,7 @@ func TestWaylandScreensReadTheOutputBurst(t *testing.T) {
{X: 1280, Make: "DELL", Model: "U2720Q", Connector: "DP-2",
PhysWMM: 597, PhysHMM: 336,
ModeW: 1920, ModeH: 1080, Refresh: 59951, Scale: 1},
})
}))
if err != nil {
t.Fatalf("screensOnWayland: %v", err)
}
Expand All @@ -130,9 +130,9 @@ func TestWaylandScreensSwapTheAxesOfARotatedPanel(t *testing.T) {
// transform 1 is a quarter turn: a 1080x1920 panel in portrait is a
// 1920x1080 mode with its axes swapped, and reporting it unswapped would
// overlap whatever sits beside it with nothing saying so.
screens, err := dialFakeOutputs(t, []outSpec{
screens, err := allOf(dialFakeOutputs(t, []outSpec{
{Model: "Portrait", Transform: 1, ModeW: 1920, ModeH: 1080, Scale: 1},
})
}))
if err != nil {
t.Fatalf("screensOnWayland: %v", err)
}
Expand Down Expand Up @@ -162,7 +162,7 @@ func TestWaylandScreenNamePrefersTheModel(t *testing.T) {
} {
t.Run(tc.name, func(t *testing.T) {
tc.out.ModeW, tc.out.ModeH, tc.out.Scale = 800, 600, 1
screens, err := dialFakeOutputs(t, []outSpec{tc.out})
screens, err := allOf(dialFakeOutputs(t, []outSpec{tc.out}))
if err != nil {
t.Fatalf("screensOnWayland: %v", err)
}
Expand All @@ -184,9 +184,9 @@ func TestWaylandScreensWithNoOutputAtAll(t *testing.T) {
func TestWaylandScreensIgnoreAnUnfinishedBurst(t *testing.T) {
// Properties published without a closing done describe nothing yet: acting
// on half a burst would place the output where the compositor never said.
screens, err := dialFakeOutputs(t, []outSpec{
screens, err := allOf(dialFakeOutputs(t, []outSpec{
{X: 500, Model: "Half", ModeW: 1920, ModeH: 1080, Scale: 2, SkipDone: true},
})
}))
if err != nil {
t.Fatalf("screensOnWayland: %v", err)
}
Expand Down
12 changes: 6 additions & 6 deletions screen_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ import (
// See [Screen] for what the fields mean, and winScreensOf for the one place
// Windows genuinely differs from the other back-ends: it has no single logical
// coordinate space, so on a mixed-DPI desktop these rectangles do not tile.
func Screens() ([]Screen, error) {
func Screens() (ScreenList, error) {
// Per-Monitor-V2 first, and its result is deliberately ignored: it fails
// when awareness has ALREADY been set, by an earlier call or by the
// application manifest, which is not a problem — the process is aware, it
Expand All @@ -66,7 +66,7 @@ func Screens() ([]Screen, error) {
handles = append(handles, m)
return true
}); err != nil {
return nil, fmt.Errorf("window: cannot enumerate displays: %w", err)
return ScreenList{}, fmt.Errorf("window: cannot enumerate displays: %w", err)
}

// Describing the monitors happens OUTSIDE the enumeration callback. The
Expand Down Expand Up @@ -104,9 +104,9 @@ func Screens() ([]Screen, error) {
})
}
if len(mons) == 0 {
return nil, fmt.Errorf("window: the desktop reports no display")
return ScreenList{}, fmt.Errorf("window: the desktop reports no display: %w", ErrNoScreens)
}
return winScreensOf(mons), nil
return newScreenList(winScreensOf(mons))
}

// VisibleScreenSize returns the usable area of the primary display in LOGICAL
Expand All @@ -116,10 +116,10 @@ func Screens() ([]Screen, error) {
// See [Screens], which supersedes it for anything multi-display.
func VisibleScreenSize() (w, h int, ok bool) {
screens, err := Screens()
if err != nil || len(screens) == 0 {
if err != nil {
return 0, 0, false
}
s := screens[0]
s := screens.Primary()
if s.VisibleWidth <= 0 || s.VisibleHeight <= 0 {
return 0, 0, false
}
Expand Down
14 changes: 7 additions & 7 deletions screen_x11.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,30 +35,30 @@ import (
// has several, but they are separate coordinate spaces that no window can move
// between, so listing them together would describe a desktop that does not
// exist.
func x11Screens(disp string) ([]Screen, error) {
func x11Screens(disp string) (ScreenList, error) {
d, err := parseDisplay(disp)
if err != nil {
return nil, err
return ScreenList{}, err
}
conn, err := dialAuthenticated(disp)
if err != nil {
return nil, err
return ScreenList{}, err
}
defer func() { _ = conn.Close() }()
return screensOn(conn, d.screen)
}

// screensOn is Screens with the connection already open, which is what makes
// the whole projection testable against a scripted server.
func screensOn(conn *x11.Conn, screen int) ([]Screen, error) {
func screensOn(conn *x11.Conn, screen int) (ScreenList, error) {
sc := conn.Setup().ScreenOf(screen)
if sc == nil {
return nil, fmt.Errorf("window: DISPLAY names screen %d, and this server has %d",
return ScreenList{}, fmt.Errorf("window: DISPLAY names screen %d, and this server has %d",
screen, len(conn.Setup().Screens))
}
mons, err := conn.Monitors(screen)
if err != nil {
return nil, err
return ScreenList{}, err
}
// One scale for the whole desktop, because that is all X11 has: Xft.dpi is
// a resource on the root window, not a property of a panel. A mixed-DPI X11
Expand Down Expand Up @@ -100,7 +100,7 @@ func screensOn(conn *x11.Conn, screen int) ([]Screen, error) {
s.VisibleHeight = points(vh, scale)
out = append(out, s)
}
return primaryFirst(out), nil
return newScreenList(out)
}

// points converts device pixels to logical points. The X11 back-end scales by
Expand Down
6 changes: 3 additions & 3 deletions screen_x11_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ func TestScreensOnProjectsPixelsOntoPoints(t *testing.T) {
conn := dialScripted(t, randrScreenScript(mons, "Xft.dpi:\t192\n",
[]uint32{0, 27, 3840, 1053}))

screens, err := screensOn(conn, 0)
screens, err := allOf(screensOn(conn, 0))
if err != nil {
t.Fatalf("screensOn: %v", err)
}
Expand Down Expand Up @@ -240,7 +240,7 @@ func TestScreensOnWithNoWindowManagerAndNoScale(t *testing.T) {
mons := []monSpec{{NameAtom: 0x40, Name: "screen", Width: 1920, Height: 1080}}
conn := dialScripted(t, randrScreenScript(mons, "", nil))

screens, err := screensOn(conn, 0)
screens, err := allOf(screensOn(conn, 0))
if err != nil {
t.Fatalf("screensOn: %v", err)
}
Expand All @@ -256,7 +256,7 @@ func TestScreensOnSurvivesAServerThatAnswersNothing(t *testing.T) {
// itself is still a display, and a caller that asked for a list must not
// get an empty one.
conn := dialScripted(t, nil)
screens, err := screensOn(conn, 0)
screens, err := allOf(screensOn(conn, 0))
if err != nil {
t.Fatalf("screensOn: %v", err)
}
Expand Down
Loading