From 9fc8c79e8a6fe36a58106e63b1fd036bfd30afcf Mon Sep 17 00:00:00 2001 From: Hiro Tamada Date: Thu, 12 Feb 2026 18:29:36 -0500 Subject: [PATCH 1/2] fix: remove unnecessary screen width rounding in ChangeScreenSize MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Go-level `s.Width = s.Width - (s.Width % 8)` rounding was silently truncating requested widths (e.g. 390 → 384) before attempting to find or create a screen mode. This made it impossible to use resolutions like 392x844 for mobile viewports even when valid modelines existed. The rounding was originally added to match libxcvt's CVT convention of producing widths as multiples of 8, but this is a timing standard for physical monitors — not a constraint of the Xorg dummy driver used for virtual displays. Now ChangeScreenSize follows a 3-step fallback: 1. Try the exact requested dimensions (e.g. 390x844) 2. If no match, round width up to the nearest multiple of 8 and retry (e.g. 392x844 — matches predefined modelines in xorg.conf) 3. If still no match, dynamically create a new mode via libxcvt The C layer is also updated so XCreateScreenMode returns the actual dimensions that libxcvt produced (via in/out pointer parameters), and XCreateScreenModeInfo uses the actual libxcvt output dimensions for the mode name so it matches the real mode geometry. Co-authored-by: Cursor --- server/pkg/xorg/xorg.c | 25 +++++++++++++++++++------ server/pkg/xorg/xorg.go | 34 ++++++++++++++++++++++++++++------ server/pkg/xorg/xorg.h | 2 +- 3 files changed, 48 insertions(+), 13 deletions(-) diff --git a/server/pkg/xorg/xorg.c b/server/pkg/xorg/xorg.c index e2c04dcc..8542a018 100644 --- a/server/pkg/xorg/xorg.c +++ b/server/pkg/xorg/xorg.c @@ -302,12 +302,18 @@ void XGetScreenConfigurations() { } // Inspired by https://github.com/raboof/xrandr/blob/master/xrandr.c -void XCreateScreenMode(int width, int height, short rate) { +// width and height are in/out: on return they contain the actual dimensions +// of the created mode (libxcvt may round width to a multiple of 8). +void XCreateScreenMode(int *width, int *height, short rate) { Display *display = getXDisplay(); Window root = DefaultRootWindow(display); // create new mode info - XRRModeInfo *mode_info = XCreateScreenModeInfo(width, height, rate); + XRRModeInfo *mode_info = XCreateScreenModeInfo(*width, *height, rate); + + // write back the actual dimensions that were created + *width = mode_info->width; + *height = mode_info->height; // create new mode RRMode mode = XRRCreateMode(display, root, mode_info); @@ -325,16 +331,19 @@ void XCreateScreenMode(int width, int height, short rate) { // Inspired by https://fossies.org/linux/xwayland/hw/xwayland/xwayland-cvt.c XRRModeInfo *XCreateScreenModeInfo(int hdisplay, int vdisplay, short vrefresh) { - char name[128]; - snprintf(name, sizeof name, "%dx%d_%d", hdisplay, vdisplay, vrefresh); - XRRModeInfo *modeinfo = XRRAllocModeInfo(name, strlen(name)); - #ifdef _LIBCVT_H_ struct libxcvt_mode_info *mode_info; // get screen mode from libxcvt, if available + // NOTE: libxcvt may round hdisplay up to a multiple of 8 (CVT convention). + // We use the actual output dimensions for both the mode name and geometry + // so that they are consistent. mode_info = libxcvt_gen_mode_info(hdisplay, vdisplay, vrefresh, false, false); + char name[128]; + snprintf(name, sizeof name, "%dx%d_%d", mode_info->hdisplay, mode_info->vdisplay, vrefresh); + XRRModeInfo *modeinfo = XRRAllocModeInfo(name, strlen(name)); + modeinfo->width = mode_info->hdisplay; modeinfo->height = mode_info->vdisplay; modeinfo->dotClock = mode_info->dot_clock * 1000; @@ -349,6 +358,10 @@ XRRModeInfo *XCreateScreenModeInfo(int hdisplay, int vdisplay, short vrefresh) { free(mode_info); #else // fallback to a simple mode without refresh rate + char name[128]; + snprintf(name, sizeof name, "%dx%d_%d", hdisplay, vdisplay, vrefresh); + XRRModeInfo *modeinfo = XRRAllocModeInfo(name, strlen(name)); + modeinfo->width = hdisplay; modeinfo->height = vdisplay; #endif diff --git a/server/pkg/xorg/xorg.go b/server/pkg/xorg/xorg.go index a6a38799..cdaf993f 100644 --- a/server/pkg/xorg/xorg.go +++ b/server/pkg/xorg/xorg.go @@ -201,9 +201,6 @@ func ChangeScreenSize(s types.ScreenSize) (types.ScreenSize, error) { mu.Lock() defer mu.Unlock() - // round width to 8, because of Xorg - s.Width = s.Width - (s.Width % 8) - // if rate is 0, set it to 60 if s.Rate == 0 { s.Rate = 60 @@ -212,16 +209,33 @@ func ChangeScreenSize(s types.ScreenSize) (types.ScreenSize, error) { // convert variables to C types c_width, c_height, c_rate := C.int(s.Width), C.int(s.Height), C.short(s.Rate) - // if screen configuration already exists, just set it + // try to set the screen configuration with the exact requested dimensions status := C.XSetScreenConfiguration(c_width, c_height, c_rate) + + // if the exact dimensions don't match an existing mode, the width may not + // be a multiple of 8 (Xorg/CVT convention). Try rounding up to the nearest + // multiple of 8 before creating a new mode, since predefined modelines in + // xorg.conf typically use aligned widths (e.g. 390 -> 392). + if status != C.RRSetConfigSuccess { + alignedWidth := roundUpTo8(s.Width) + if alignedWidth != s.Width { + c_width = C.int(alignedWidth) + status = C.XSetScreenConfiguration(c_width, c_height, c_rate) + } + } + + // if still no match, dynamically create a new mode via libxcvt if status != C.RRSetConfigSuccess { - // create new screen configuration - C.XCreateScreenMode(c_width, c_height, c_rate) + C.XCreateScreenMode(&c_width, &c_height, c_rate) // screen configuration should exist now, set it status = C.XSetScreenConfiguration(c_width, c_height, c_rate) } + // update s with the actual dimensions that were set + s.Width = int(c_width) + s.Height = int(c_height) + var err error // if screen configuration was not set successfully, return error @@ -237,6 +251,14 @@ func ChangeScreenSize(s types.ScreenSize) (types.ScreenSize, error) { return s, err } +// roundUpTo8 rounds n up to the nearest multiple of 8. +func roundUpTo8(n int) int { + if r := n % 8; r != 0 { + return n + (8 - r) + } + return n +} + func GetScreenSize() types.ScreenSize { mu.Lock() defer mu.Unlock() diff --git a/server/pkg/xorg/xorg.h b/server/pkg/xorg/xorg.h index 781bcef8..92d834ee 100644 --- a/server/pkg/xorg/xorg.h +++ b/server/pkg/xorg/xorg.h @@ -39,7 +39,7 @@ void XKey(KeySym keysym, int down); Status XSetScreenConfiguration(int width, int height, short rate); void XGetScreenConfiguration(int *width, int *height, short *rate); void XGetScreenConfigurations(); -void XCreateScreenMode(int width, int height, short rate); +void XCreateScreenMode(int *width, int *height, short rate); XRRModeInfo *XCreateScreenModeInfo(int hdisplay, int vdisplay, short vrefresh); void XSetKeyboardModifier(unsigned char mod, int on); From 3f90ca61f777dcfca35fa7d3538e365c3ad84cd5 Mon Sep 17 00:00:00 2001 From: Hiro Tamada Date: Thu, 12 Feb 2026 20:54:52 -0500 Subject: [PATCH 2/2] refactor: remove unnecessary roundUpTo8 fallback step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 3-step resolution flow (exact → round-up → libxcvt) had no practical benefit from the middle step. If an exact modeline exists in xorg.conf, step 1 finds it. If not, libxcvt creates one. The round-up retry only helped in the contrived case where a rounded modeline existed but the exact one didn't. Simplify to: try exact match, then create via libxcvt. Co-authored-by: Cursor --- server/pkg/xorg/xorg.go | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/server/pkg/xorg/xorg.go b/server/pkg/xorg/xorg.go index cdaf993f..8d5ee050 100644 --- a/server/pkg/xorg/xorg.go +++ b/server/pkg/xorg/xorg.go @@ -212,19 +212,7 @@ func ChangeScreenSize(s types.ScreenSize) (types.ScreenSize, error) { // try to set the screen configuration with the exact requested dimensions status := C.XSetScreenConfiguration(c_width, c_height, c_rate) - // if the exact dimensions don't match an existing mode, the width may not - // be a multiple of 8 (Xorg/CVT convention). Try rounding up to the nearest - // multiple of 8 before creating a new mode, since predefined modelines in - // xorg.conf typically use aligned widths (e.g. 390 -> 392). - if status != C.RRSetConfigSuccess { - alignedWidth := roundUpTo8(s.Width) - if alignedWidth != s.Width { - c_width = C.int(alignedWidth) - status = C.XSetScreenConfiguration(c_width, c_height, c_rate) - } - } - - // if still no match, dynamically create a new mode via libxcvt + // if no existing mode matches, dynamically create one via libxcvt if status != C.RRSetConfigSuccess { C.XCreateScreenMode(&c_width, &c_height, c_rate) @@ -251,14 +239,6 @@ func ChangeScreenSize(s types.ScreenSize) (types.ScreenSize, error) { return s, err } -// roundUpTo8 rounds n up to the nearest multiple of 8. -func roundUpTo8(n int) int { - if r := n % 8; r != 0 { - return n + (8 - r) - } - return n -} - func GetScreenSize() types.ScreenSize { mu.Lock() defer mu.Unlock()