Skip to content

Generate screen modes at the exact requested size and refresh rate - #31

Open
masnwilliams wants to merge 1 commit into
masterfrom
hypeship/exact-generated-screen-modes
Open

masnwilliams wants to merge 1 commit into
masterfrom
hypeship/exact-generated-screen-modes

Conversation

@masnwilliams

@masnwilliams masnwilliams commented Nov 2, 2026 •

Copy link
Copy Markdown

Summary

When no existing mode matches a requested screen size, ChangeScreenSize creates one with libxcvt and then sets it with XRRSetScreenConfigAndRate at the requested integer rate. For some sizes the generated mode can't be set at that rate, so neko returns 422 unsupported screen rate 60. Mostly these are short or small screens: 1008x384, 976x384, 1024x384, 1200x384, 1008x368, 788x300, 630x240 and 379x480 all fail at 60 Hz, and 1008x384 also fails at 25 Hz.

There are two causes, both in how libxcvt output is used:

  1. Refresh rate. libxcvt rounds the pixel clock down to a multiple of 250 kHz (libxcvt.c). On small modes that cuts the real rate by up to about 1%. For example, 1008x384@60 comes out at 29.750 MHz, htotal 1248 and vtotal 401, which is 59.45 Hz. X lists a mode's rate rounded to the nearest integer (RRVerticalRefresh), so this mode is listed at 59 and a request for 60 fails with BadValue.
  2. Width. libxcvt rounds hdisplay down to a multiple of 8. A request for 1003x500 therefore gets a 1000x500 screen, and 788x300 gets 784x300.

Change

XCreateScreenModeInfo:

  • sets the dot clock to htotal * vtotal * vrefresh, so X lists the mode at exactly the requested rate.
  • generates the timings for the width rounded up to a multiple of 8, then uses the requested width as the active area. All sync positions stay valid, and the mode has the exact requested size. This also means 1360x768 is no longer widened to 1366x768.
  • names the mode after the requested size (%dx%d_%d), the same as the build without libxcvt already did.

Modes defined in xorg.conf are untouched: ChangeScreenSize still tries an existing mode first.

Testing

  • Unit test: TestNewScreenModeInfo checks the generated size, the rate as X lists it, and the ordering of the timings for 18 sizes × 5 rates. On the old code it fails with 80 errors, for example 1008x384@60: X lists the mode at 59 Hz. On this change it passes.
  • Wider sweep (not committed): widths 64–3840 and heights 64–2160 at 10/25/30/60 Hz, 907,200 modes in total. Every mode had the exact size, the exact rate and valid timings.
  • Local headful image: I swapped in this neko binary and drove PATCH /display and /configure (with --kiosk) through kernel-images-api. Every size above went from 422 to 200. xrandr reports the exact size at 60.00Hz, and Chromium's screen.width/height match.
  • Production browser (hot-patched binary, throwaway session): the same sizes all returned 200 in about 0.2s, and the kiosk page fills the screen exactly, e.g. 788x300 screen 788x300. The live view <video> played at 1003x500 and 630x240. Recording at an odd size (379x481) produced a valid mp4, because the recorder already pads odd dimensions.

Notes

  • Sizes below the X server's 64x64 minimum still fail, as before.
  • Inside one X server, a mode created by the old binary keeps its name and its slow clock, so that size keeps failing until Xorg restarts. This only matters when hot-swapping the binary. Fresh images start with no generated modes.
  • Images pick this up once it is tagged and the neko base image is bumped in kernel-images.

Note

Medium Risk
Changes low-level X RandR timing and dot-clock math used whenever dynamic display sizes are created; incorrect values could break display configuration, though behavior is covered by new unit tests.

Overview
Fixes 422 unsupported screen rate and wrong dimensions when ChangeScreenSize creates a custom RandR mode via libxcvt for sizes that are not already in X.

XCreateScreenModeInfo now always uses the requested width and height for the mode geometry and name, while libxcvt timings are computed on a width rounded up to a multiple of 8 so odd widths (e.g. 1003×500) are no longer shrunk and cases like 1360×768 are not widened to 1366. The pixel clock is set to htotal × vtotal × vrefresh instead of libxcvt’s 250 kHz–quantized clock, so X lists the mode at the requested integer Hz and XRRSetScreenConfigAndRate accepts the rate (e.g. 1008×384@60 no longer rounds to 59 Hz).

Go adds newScreenModeInfo / screenModeInfo so tests can inspect generated modes without registering them on the X server. TestNewScreenModeInfo checks exact size, X-style refresh rounding, and valid sync/total ordering across many previously failing sizes and rates.

Reviewed by Cursor Bugbot for commit a37cfdb. Bugbot is set up for automated code reviews on this repo. Configure here.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant