From 5db2a77260c0a45588f3bdfabe77d2ea08668453 Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Thu, 4 Dec 2025 20:27:46 -0800 Subject: [PATCH 1/4] Support getScreens on X11 Return the default X11 screen as the primary screen so Linux callers can use the same API as the other platforms. --- examples/screens.nim | 8 ++------ src/windy/platforms/linux/x11.nim | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/examples/screens.nim b/examples/screens.nim index f8edd0be..1b7c22af 100644 --- a/examples/screens.nim +++ b/examples/screens.nim @@ -1,8 +1,4 @@ import windy -when defined(windows) or defined(macosx): - # Screens API only currently supported on Windows and macOS - - let screens = getScreens() - for screen in screens: - echo screen +for screen in getScreens(): + echo screen diff --git a/src/windy/platforms/linux/x11.nim b/src/windy/platforms/linux/x11.nim index 5f84b366..bc0206a1 100644 --- a/src/windy/platforms/linux/x11.nim +++ b/src/windy/platforms/linux/x11.nim @@ -625,6 +625,23 @@ proc `title=`*(window: Window, v: string) = window.handle.setProperty(xaNetWMIconName, xaUTF8String, 8, v) display.Xutf8SetWMProperties(window.handle, v, v, nil, 0, nil, nil, nil) +proc getScreens*(): seq[common.Screen] = + ## Returns the default X11 screen as primary. + init() + + let screen = display.screen(display.defaultScreen) + if screen == nil: + return + + let size = screen.size + result.add common.Screen( + left: 0, + top: 0, + right: size.x, + bottom: size.y, + primary: true + ) + proc contentScale*(window: Window): float32 = const defaultScreenDpi = 96.0 From eacc207545890db194cc6f54ddf2f0c58c5a8705 Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Wed, 9 Sep 2026 13:15:59 -0700 Subject: [PATCH 2/4] Return individual X11 monitors and test their bounds --- .github/workflows/build.yml | 9 +++++++ src/windy/platforms/linux/x11.nim | 32 +++++++++++++----------- src/windy/platforms/linux/x11/xrandr.nim | 15 +++++++++++ tests/test_screens.nim | 10 ++++++++ 4 files changed, 52 insertions(+), 14 deletions(-) create mode 100644 src/windy/platforms/linux/x11/xrandr.nim create mode 100644 tests/test_screens.nim diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a7f2123c..98047ada 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,6 +23,15 @@ jobs: - run: nim r -d:useCpu tests/test_cpu_pixels.nim if: matrix.os == 'windows-latest' + - name: Check two X11 monitors + if: matrix.os == 'ubuntu-latest' + run: | + xvfb-run -a -s "-noreset -screen 0 1600x600x24" sh -ec ' + xrandr --setmonitor LEFT 800/200x600/150+0+0 screen + xrandr --setmonitor RIGHT 800/200x600/150+800+0 none + nim r --parallelBuild:1 tests/test_screens.nim + ' + # Build native examples. - run: nim c examples/basic.nim - run: nim c examples/basic_boxy.nim diff --git a/src/windy/platforms/linux/x11.nim b/src/windy/platforms/linux/x11.nim index bc0206a1..26fa5aae 100644 --- a/src/windy/platforms/linux/x11.nim +++ b/src/windy/platforms/linux/x11.nim @@ -2,7 +2,7 @@ import std/[os, osproc, sequtils, sets, strformat, strutils, times, unicode, uri, pathnorm], ../../[common, internal], vmath, pixie, - x11/[glx, keysym, x, xevent, xlib, xcursor] + x11/[glx, keysym, x, xevent, xlib, xcursor, xrandr] import ../../http export http @@ -626,21 +626,25 @@ proc `title=`*(window: Window, v: string) = display.Xutf8SetWMProperties(window.handle, v, v, nil, 0, nil, nil, nil) proc getScreens*(): seq[common.Screen] = - ## Returns the default X11 screen as primary. + ## Returns active X11 monitors and their desktop positions (RandR 1.5). init() - - let screen = display.screen(display.defaultScreen) - if screen == nil: + var major, minor, count: cint + if display.XRRQueryVersion(major.addr, minor.addr) == 0 or + (major == 1 and minor < 5) or major < 1: + raise WindyError.newException("Screen enumeration requires RandR 1.5") + let monitors = display.XRRGetMonitors(display.defaultRootWindow, 1, count.addr) + if monitors == nil: return - - let size = screen.size - result.add common.Screen( - left: 0, - top: 0, - right: size.x, - bottom: size.y, - primary: true - ) + defer: XRRFreeMonitors(monitors) + for i in 0 ..< count: + let monitor = monitors[i] + result.add common.Screen( + left: monitor.x, + top: monitor.y, + right: monitor.x + monitor.width, + bottom: monitor.y + monitor.height, + primary: monitor.primary != 0 + ) proc contentScale*(window: Window): float32 = const defaultScreenDpi = 96.0 diff --git a/src/windy/platforms/linux/x11/xrandr.nim b/src/windy/platforms/linux/x11/xrandr.nim new file mode 100644 index 00000000..ccd0c941 --- /dev/null +++ b/src/windy/platforms/linux/x11/xrandr.nim @@ -0,0 +1,15 @@ +import x, xlib + +type + XRRMonitorInfo* {.bycopy.} = object + name*: Atom + primary*, automatic*, noutput*: cint + x*, y*, width*, height*, mwidth*, mheight*: cint + outputs*: ptr culong + +{.push cdecl, dynlib: "libXrandr.so.2", importc.} +proc XRRQueryVersion*(display: Display, major, minor: ptr cint): cint +proc XRRGetMonitors*(display: Display, window: Window, active: cint, + count: ptr cint): ptr UncheckedArray[XRRMonitorInfo] +proc XRRFreeMonitors*(monitors: ptr UncheckedArray[XRRMonitorInfo]) +{.pop.} diff --git a/tests/test_screens.nim b/tests/test_screens.nim new file mode 100644 index 00000000..84816c23 --- /dev/null +++ b/tests/test_screens.nim @@ -0,0 +1,10 @@ +## Run on the two-monitor Xvfb layout configured by the build workflow. +import windy, std/algorithm +var screens = getScreens() +screens.sort(proc(a, b: Screen): int = cmp(a.left, b.left)) +doAssert screens.len == 2, "each monitor must be returned separately" +doAssert screens[0].left == 0 and screens[0].right == 800 +doAssert screens[1].left == 800 and screens[1].right == 1600 +for screen in screens: + doAssert screen.top == 0 and screen.bottom == 600 +echo "Two distinct X11 monitor bounds passed" From 114581184bf24fa9ef5c98c1c23ee04913cd6113 Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Wed, 9 Sep 2026 13:20:36 -0700 Subject: [PATCH 3/4] Install the X11 monitor tool used by the runtime test --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6a06e307..33bceb19 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -38,6 +38,8 @@ jobs: - name: Check two X11 monitors if: matrix.os == 'ubuntu-latest' run: | + sudo apt-get update -qq + sudo apt-get install -y x11-xserver-utils xvfb-run -a -s "-noreset -screen 0 1600x600x24" sh -ec ' xrandr --setmonitor LEFT 800/200x600/150+0+0 screen xrandr --setmonitor RIGHT 800/200x600/150+800+0 none From 164219e0c08236d7f97ee0ca8b6ad34ec879b8ed Mon Sep 17 00:00:00 2001 From: Richard Higgins Date: Wed, 9 Sep 2026 13:29:07 -0700 Subject: [PATCH 4/4] Keep the screen test clear of the pending VSync CI additions --- .github/workflows/build.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 33bceb19..5757a44a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,17 +35,6 @@ jobs: - run: nim r -d:useCpu tests/test_cpu_pixels.nim if: matrix.os == 'windows-latest' - - name: Check two X11 monitors - if: matrix.os == 'ubuntu-latest' - run: | - sudo apt-get update -qq - sudo apt-get install -y x11-xserver-utils - xvfb-run -a -s "-noreset -screen 0 1600x600x24" sh -ec ' - xrandr --setmonitor LEFT 800/200x600/150+0+0 screen - xrandr --setmonitor RIGHT 800/200x600/150+800+0 none - nim r --parallelBuild:1 tests/test_screens.nim - ' - # Build native examples. - run: nim c examples/basic.nim - run: nim c examples/basic_boxy.nim @@ -65,6 +54,17 @@ jobs: - run: nim c examples/openurl.nim - run: nim c examples/property_changes.nim - run: nim c examples/screens.nim + - name: Check two X11 monitors + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt-get update -qq + sudo apt-get install -y x11-xserver-utils + xvfb-run -a -s "-noreset -screen 0 1600x600x24" sh -ec ' + xrandr --setmonitor LEFT 800/200x600/150+0+0 screen + xrandr --setmonitor RIGHT 800/200x600/150+800+0 none + nim r --parallelBuild:1 tests/test_screens.nim + ' + - run: nim c examples/scrollwheel.nim - run: nim c examples/system_cursors.nim - run: nim c examples/tray.nim