Windows & drawing
Creating a window
CreateWindow takes the new window id, the parent window, geometry, and a
few optional wire fields. Only the first six arguments are usually needed —
border width, depth, class and visual all default to "inherit from parent"
(CopyFromParent):
const x11 = require('x11');
x11.createClient((err, display) => {
const X = display.client;
const wid = X.AllocID();
X.CreateWindow(wid, display.screen[0].root, 100, 100, 400, 300);
X.MapWindow(wid);
X.on('error', err => { console.log(err); });
});
The full signature is
CreateWindow(wid, parent, x, y, width, height, borderWidth, depth, class, visual, values).
The trailing values object sets window attributes at creation time — the
same set ChangeWindowAttributes accepts — for example:
X.CreateWindow(wid, root, 0, 0, 500, 500, 0, 0, 0, 0, {
backgroundPixel: display.screen[0].white_pixel,
eventMask: x11.eventMask.Exposure | x11.eventMask.ButtonPress,
});
A window becomes visible only after X.MapWindow(wid).
Graphics contexts
All core drawing requests take a graphics context (GC) that carries pen
state: foreground/background pixels, line width, font, raster operation and
so on. Create one with CreateGC(gc, drawable, values):
const gc = X.AllocID();
X.CreateGC(gc, wid, {
foreground: display.screen[0].black_pixel,
background: display.screen[0].white_pixel,
});
GCs can be mutated later with ChangeGC(gc, values) and freed with
FreeGC(gc).
The GC is where nearly all drawing state lives, which is why the same request
can produce very different ink. Below, PolyFillRectangle is called four
times with identical arguments — only ChangeGC differs between them, and the
last one switches the raster operation to GXxor so it combines with what is
already on screen instead of replacing it:
Drawing requests
Core drawing requests target any drawable (a window or a pixmap):
X.on('event', ev => {
if (ev.name === 'Expose') {
X.PolyFillRectangle(wid, gc, [0, 0, 500, 500]); // x, y, w, h
X.PolyLine(0, wid, gc, [10, 10, 100, 100, 10, 100]); // coordMode, points
X.PolyText8(wid, gc, 50, 50, ['Hello, Node.JS!']);
}
});
Draw from an Expose handler — the server does not preserve window contents
for you, and Expose tells you when (part of) the window needs repainting.
There are no paths and no curves beyond the ellipse: every shape is one
request carrying a flat list of 16-bit coordinates. Note that PolyLine and
PolyPoint take coordMode as their first argument while every other
drawing request takes the drawable first — an inconsistency worth knowing
before it costs you an afternoon:
Drawing can also be masked. A clip list installed on the GC with
SetClipRectangles turns it into a stencil, which is how core X11 produces
shapes it has no request for — the fill is ordinary, the rectangle list is
the shape:
Pixmaps are off-screen drawables, useful for double buffering:
const pixmap = X.AllocID();
X.CreatePixmap(pixmap, wid, depth, width, height);
// ... draw into the pixmap ...
X.CopyArea(pixmap, wid, gc, 0, 0, 0, 0, width, height);
CopyArea is a server-side blit: no pixel data crosses the connection, which
makes "draw once into a pixmap, then move it" the cheapest animation the core
protocol offers. The demo below scrolls a 960×200 pixmap drawn a single time,
at two requests per frame:
The complete list of drawing requests — PolyPoint, PolySegment,
PolyArc, FillPoly, PutImage, GetImage, CopyPlane and friends — is
in the core requests reference.
Anti-aliased drawing and gradients
The core protocol only does flat 1-bit-coverage drawing. For anti-aliasing, alpha blending and gradients, load the RENDER extension. Its unit of work is the Picture: a drawable wrapped in a pixel format, or — for gradients and solid fills — no drawable at all.
Everywhere in the RENDER binding, colour components are 0..1 and clamped,
not the 16-bit values the protocol carries on the wire; the client scales them
for you. Passing 0xffff gets you 1.0, and passing 0x3000 for alpha gets you
1.0 as well, silently — so a stop list written in 16-bit values comes out fully
opaque rather than translucent.
Gradients are Pictures with nothing behind them: the client sends a few hundred bytes describing the ramp, and the server evaluates it for every sample the composite needs.
Because a Picture's source can be transformed, a small pixmap goes a long way.
SetPictureTransform takes a 3×3 matrix mapping destination coordinates back
to source coordinates — the inverse of how you would write it for a canvas —
and SetPictureFilter chooses the resampling:
Compositing itself is Porter-Duff, and the operator is an argument to every RENDER drawing request. The fourteen operators only differ from one another where both sides carry alpha, which is what this grid is arranged to show:
Render also provides RadialGradient, ConicalGradient, Triangles,
FillRectangles and glyph rendering — see the
RENDER reference.