Skip to main content

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).

Loading live demo…

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:

Loading live demo…

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:

Loading live demo…

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:

Loading live demo…

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:

Loading live demo…

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.

Colour components are floats

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.

Loading live demo…

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:

Loading live demo…

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:

Loading live demo…

Render also provides RadialGradient, ConicalGradient, Triangles, FillRectangles and glyph rendering — see the RENDER reference.