A canvas shown in an X11 window, on a desktop.
The display speaks the X11 protocol on the server's socket itself
(gfx.x11_core). Each update packs the canvas once at its own size onto
a pixmap on the server. RENDER composes that pixmap into a window-sized
one at the largest integer scale the window holds, centred on black, so a
pixel stays square. The server scales under every filter: nearest
replicates each pixel, smooth is RENDER's bilinear filter, and
scanline draws the last row of each pixel's band at half brightness. So
the socket carries the canvas at its own size, whatever the window's.
Present hands the composed frame to the window at the next vblank, or at
the compositor's next frame, and update waits for the previous one to
land before it draws, as the console waits for its flip.
The window brings its own keys: the X server sends them to the window in
focus, and d:keyboard() hands them on in the shape an evdev device reads.
| Name | Signature |
|---|---|
| parse_display | parse_display(name) -> number, screen |
| auth | auth(bytes, host, number) -> name, data |
| layout | layout(w, h, cw, ch) -> k, dx, dy |
| display | display(canvas, opts) -> display, err |
parse_display(
name) ->number,screen
Read a display name
Takes :N and :N.S. A host part is refused: the display connects to
the local socket alone, no TCP. Returns nil and an error for a name it
does not take.
auth(
bytes,host,number) ->name,data
Find a display's cookie in an Xauthority file
bytes is the file's content. Returns the first MIT-MAGIC-COOKIE-1 entry
for the display: one on host or on any host, for display number or
for every display. Returns nil, for empty auth, when none matches or the
file is cut short. A server with no cookie takes empty auth from a local
client of its own user.
layout(
w,h,cw,ch) ->k,dx,dy
Compute the integer-upscale layout of a canvas in a window
The console's layout (gfx.display), with one difference: a window
smaller than the canvas gets scale 1 and negative offsets, and crops the
canvas centred. A WM may hand a window any size, and the display draws
whatever it is given.
display(
canvas,opts) ->display,err
Create an X11 window display for a canvas
| opts field | default | description |
|---|---|---|
filter | nearest | the look, one of gfx.x11_core.filters: nearest, smooth or scanline |
scale | see below | the integer scale the window opens at |
fullscreen | false | open fullscreen |
title | lilgame | the window's title |
class | lilgame | WM_CLASS, instance and class alike |
display | $DISPLAY | :N or :N.S |
xauthority | $XAUTHORITY, else ~/.Xauthority | the file the cookie is read from |
on_focus | nil | fn(focused), called when the window gains or loses the keyboard |
on_close | nil | fn(), called when the WM asks the window to close |
open | gfx.x11_core.connect | the 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:
scaler is given, or scale is not a
whole number from 1;$DISPLAY is unset or names a host;The display answers the contract gfx.display states, and adds closed,
keyboard and fullscreen. In the window the methods read:
| method | description |
|---|---|
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.