Skip to main content

Outputs (Displays)

COMPOSITOR.output controls monitor layout — resolution, refresh rate, scale, position, mirroring, and enabling/disabling outputs. You both read the current state and register a factory that produces the desired layout.

Configuring outputs

COMPOSITOR.output.configure(factory) registers a function the compositor calls every time the set of connected outputs changes (hotplug, dock, undock). The factory returns a map of output name → config entry.

import {COMPOSITOR, type DisplayConfigDraft} from 'shoji_wm';

COMPOSITOR.output.configure((context) => {
const display: DisplayConfigDraft = {};

display['DP-1'] = {
mode: 'extend',
resolution: {width: 2560, height: 1440, refreshRate: 144},
position: 'auto',
scale: 1.5,
};
display['eDP-1'] = {mode: 'extend', resolution: 'best', scale: 1.8};

// Turn off the laptop panel while docked
const docked = context.connected.some((o) => o.name === 'HDMI-A-1');
if (docked) {
display['eDP-1'] = {mode: 'disabled'};
}

return display;
});

The output name ("DP-1", "eDP-1", "HDMI-A-1", …) is the DRM connector name. List the connected names by reading context.connected or COMPOSITOR.output.list.

Config entry: mode

Each entry has a mode that selects one of three shapes:

modeMeaningExtra fields
"extend" (default)Use the output as part of the desktopresolution, position, scale
"disabled"Turn the output off
"mirror"Mirror another outputsource (name of the output to mirror)
display['HDMI-A-1'] = {mode: 'mirror', source: 'eDP-1'};
display['eDP-2'] = {mode: 'disabled'};

mode may be omitted for an extend entry (it is the default).

resolution

Selects the DRM mode (size + refresh rate).

ValueMeaning
"best"Highest resolution + refresh rate the output advertises
{width, height}Pick a mode of that size (highest matching refresh rate)
{width, height, refreshRate}Pick that exact mode
display['DP-1'] = {resolution: 'best'};
display['DP-2'] = {resolution: {width: 1920, height: 1080}};
display['DP-3'] = {resolution: {width: 2560, height: 1440, refreshRate: 165}};

Inspect what a monitor supports with COMPOSITOR.output.availableModes(name).

position

Where the output sits in the global coordinate space.

ValueMeaning
"auto" (default)Compositor places it automatically (left-to-right)
{x, y}Explicit top-left corner in logical pixels
display['DP-1'] = {position: {x: 0, y: 0}};
display['DP-2'] = {position: {x: 2560, y: 0}}; // to the right of DP-1

scale

Fractional scale factor (HiDPI). 1.0 is native; 2.0 doubles UI size; the default config uses values like 1.51.8.

display['eDP-1'] = {resolution: 'best', scale: 1.8};

Reading output state

The controller is also a read-only view, useful inside event handlers and the composition function.

MemberReturns
liststring[] — names of connected, enabled outputs
outputsOutputInfo[] — snapshot of every output
currentRecord<string, OutputInfo> — snapshots keyed by name
get(name)OutputInfo | undefined
find(predicate)first matching OutputInfo
availableModes(name)OutputMode[] reported by the driver
configure(factory)register a layout factory (above)
reconfigure()re-run all registered factories now

OutputInfo includes name, enabled, resolution ({width, height, refreshRate}), position ({x, y}), scale, availableModes, and identification fields (make, model, serial, connector).

const hz = COMPOSITOR.output.get('DP-1')?.resolution?.refreshRate;

// Logical size of an output (resolution divided by its scale)
const out = COMPOSITOR.output.get('DP-1');
if (out?.resolution) {
const widthLogical = out.resolution.width / out.scale;
const heightLogical = out.resolution.height / out.scale;
}
tip

COMPOSITOR.output.configure is for hardware layout. To place windows so they don't overlap bars/docks, use COMPOSITOR.layer.usableArea(name) instead, which subtracts exclusive-zone layer surfaces.