gfx.x11.display

gfx.x11

display(canvas, opts) -> display, err

Create an X11 window display for a canvas

opts fielddefaultdescription
filternearestthe look, one of gfx.x11_core.filters: nearest, smooth or scanline
scalesee belowthe integer scale the window opens at
fullscreenfalseopen fullscreen
titlelilgamethe window's title
classlilgameWM_CLASS, instance and class alike
display$DISPLAY:N or :N.S
xauthority$XAUTHORITY, else ~/.Xauthoritythe file the cookie is read from
on_focusnilfn(focused), called when the window gains or loses the keyboard
on_closenilfn(), called when the WM asks the window to close
opengfx.x11_core.connectthe connector; a suite hands in one that answers a fake handle

The window opens at the canvas times scale. The default is one step under the largest scale the screen holds, at least 1, which leaves room for a WM's panels: 5 on a 2880x1800 screen, 2 on 1920x1080. Fullscreen opens at the screen's size. The window asks the WM for the canvas's size as its least.

Returns nil and an error, and opens nothing, when:

The display answers the contract gfx.display states, and adds closed, keyboard and fullscreen. In the window the methods read:

methoddescription
d:update()read the window's events, wait for the previous frame, send the canvas, compose and present it
d:focused()true while the window holds the keyboard
d:closed()true once the WM asked the window to close
d:keyboard()the window's keys as a device, below
d:fullscreen(on)ask the WM for fullscreen, or back
d:activate(n)nil and an error: a window is on no VT
d:destroy()close the connection; the window goes with it. Safe to call twice
d:set_filter(name)switch the look while open, from the next update; true, or nil and an error for an unknown name

d.cfg.scaler is always server. The window adds fullscreen as asked, on_close, and pacing: present when the server has Present, loop when it has not. A resize by the WM lays the canvas out again inside the next call that reads events. A window smaller than the canvas crops it at scale 1, centred, and x and y go below 1.

update composes the frame into a window-sized pixmap and hands it to the window with Present, which copies it at the next vblank, or at the compositor's next frame under Xwayland and gamescope. The copy is never a flip, so the next frame never draws into a buffer on the screen. Each update waits for the previous frame's CompleteNotify before it draws, so the display paces a frame loop at the refresh. A lost CompleteNotify costs one frame: the wait times out after a second. Under Xwayland a window the compositor does not draw, out of sight or in a session paused for another VT, completes a frame about once a second, and so does the loop. Without Present the pixmap is copied onto the window at once, and the frame loop's own deadline paces it.

update also sends a frame of about the canvas's size times four bytes, more than a Unix socket buffers, and waits writable for the rest. Both waits yield to LEV inside lev.run, and block in C before it. The display is the socket's one waiter: LEV holds one per descriptor.

focused starts true and follows the server's focus. A focus change during a grab is not one: a WM's shortcut grabs the keyboard and lets it go. on_focus runs on a change alone.

d:keyboard() returns the one keyboard of the display, {read, close}. read() never waits: it reads the window's events and returns what queued since the last read, as an evdev device does, {type, code, value} records each followed by a SYN_REPORT, {} when none. The code is the evdev key code: the X server's keycode less 8, under Xorg's evdev and libinput drivers and under every Xwayland. A held key repeats with value 2. Every key down is released when the window loses the keyboard. A lost connection or an X error answers nil and the error, and update raises it. Keys queue only while the keyboard is open; close() drops the queue, and keyboard() opens it again. A read from another task while update waits on the socket returns what that wait read.