protocols: expose output-management, honor resizes standalone

Advertise zwlr_output_manager_v1 with the single head, published on
setup and re-published from the commit path whenever the size lands
(resizes apply lazily, so the commit -- not the request -- is where
the new size becomes observable).

Client sizes apply standalone through the normal resize path (tabs
reflow, swapchain follows); nested refuses anything but the live
size since the host owns the geometry. Disabling, scale and
transform changes are refused; superseded configs are destroyed
after feedback.

Standalone output_resize now owns the canvas size so client resizes
reach the lazy modeset. wltest gains --output-mgmt; smoke tests
cover the global, head listing, and an 800x600 apply with tab
reflow.
This commit is contained in:
Andrew Hamilton 2026-09-04 10:52:16 +10:00
parent fcd3bca53b
commit d8164ce315
6 changed files with 1158 additions and 3 deletions

View file

@ -422,6 +422,15 @@ artwork shows where clients provide it. The guesser stays as the fallback
icon: clearing the icon drops back to guessing, and windows that never icon: clearing the icon drops back to guessing, and windows that never
set one behave exactly as before. set one behave exactly as before.
## Output management (`wlr-output-management`)
Kick advertises `zwlr_output_manager_v1`, so display tools (kanshi,
wdisplays) can list the single head and request sizes. Standalone honors
custom sizes through the normal resize path (tabs reflow, clients learn
the new size); nested refuses anything but the live size, because the
host window owns the geometry there. Disabling, scale and transform
changes are refused everywhere: one fixed output per session.
## Logging (`KICK_LOG`) ## Logging (`KICK_LOG`)
Quiet by default: a plain launch prints nothing to the launching terminal Quiet by default: a plain launch prints nothing to the launching terminal

171
kick.c
View file

@ -96,6 +96,7 @@
#include <wlr/types/wlr_output.h> #include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_output_layout.h> #include <wlr/types/wlr_output_layout.h>
#include <wlr/types/wlr_output_power_management_v1.h> #include <wlr/types/wlr_output_power_management_v1.h>
#include <wlr/types/wlr_output_management_v1.h>
#include <wlr/types/wlr_pointer.h> #include <wlr/types/wlr_pointer.h>
#include <wlr/types/wlr_pointer_constraints_v1.h> #include <wlr/types/wlr_pointer_constraints_v1.h>
#include <wlr/types/wlr_pointer_gestures_v1.h> #include <wlr/types/wlr_pointer_gestures_v1.h>
@ -393,6 +394,13 @@ struct kick_server {
struct wlr_output_power_manager_v1 *output_power_mgr; struct wlr_output_power_manager_v1 *output_power_mgr;
struct wl_listener output_power_set_mode; struct wl_listener output_power_set_mode;
bool output_powered; bool output_powered;
/* Output management (kanshi/wdisplays reconfigure the output): the
single server output is published as a head; client size requests
are honored standalone and refused nested (the host owns the size).
Scale/transform changes and disabling are unsupported. */
struct wlr_output_manager_v1 *output_mgr;
struct wl_listener output_mgr_apply;
struct wl_listener output_mgr_test;
/* Gamma control (night-light tools like wlsunset/gammastep): a client /* Gamma control (night-light tools like wlsunset/gammastep): a client
sets a per-output ramp; wlroots rejects outputs with no LUT (e.g. sets a per-output ramp; wlroots rejects outputs with no LUT (e.g.
the headless output) itself, so the handler only commits ramps for the headless output) itself, so the handler only commits ramps for
@ -870,6 +878,9 @@ static const char *foreign_str(const char *s);
static struct kick_window *tab_current_window(struct kick_tab *tab); static struct kick_window *tab_current_window(struct kick_tab *tab);
/* Toplevel-icon body lives with the overlay code; called from teardown. */ /* Toplevel-icon body lives with the overlay code; called from teardown. */
static void icon_proto_release(struct kick_window *w); static void icon_proto_release(struct kick_window *w);
/* Output-management publish lives with the modeset handlers; called from
output setup and every resize. */
static void output_mgmt_publish(void);
static struct kick_tab *pick_successor(struct kick_tab *tab, static struct kick_tab *pick_successor(struct kick_tab *tab,
bool prefer_right); bool prefer_right);
static void remove_tab(struct kick_tab *tab); static void remove_tab(struct kick_tab *tab);
@ -2479,6 +2490,15 @@ static void output_resize(int width, int height) {
if (height < 50) height = 50; if (height < 50) height = 50;
if (!server.output || !server.allocator) return; if (!server.output || !server.allocator) return;
/* Standalone has no host window tracking the size: the canvas follows
the request so the lazy modeset in apply_pending_output_resize
picks it up at render (nested is a no-op: the args already are the
host window's size). */
if (server.standalone) {
host.width = width;
host.height = height;
}
/* The new size is applied to the output lazily at render time (see /* The new size is applied to the output lazily at render time (see
apply_pending_output_resize); here only the scene geometry and the apply_pending_output_resize); here only the scene geometry and the
clients are told, so configure-time stays free of modesets and clients are told, so configure-time stays free of modesets and
@ -2706,6 +2726,10 @@ server.output = output;
server.overlay_damage_rect = wlr_scene_rect_create( server.overlay_damage_rect = wlr_scene_rect_create(
&server.scene->tree, w, h, (float[]){ 0.0f, 0.0f, 0.0f, 0.0f }); &server.scene->tree, w, h, (float[]){ 0.0f, 0.0f, 0.0f, 0.0f });
} }
/* Publish the fresh output to management clients (kanshi/wdisplays
learn the head and its modes/size on bind). */
output_mgmt_publish();
} }
/* Nearest live tab, scanning right first (prefer_right) or left first. /* Nearest live tab, scanning right first (prefer_right) or left first.
@ -6193,6 +6217,141 @@ static void handle_output_power_set_mode(struct wl_listener *listener,
kick_request_redraw(); kick_request_redraw();
} }
/* ===================== Output management =====================
kanshi/wdisplays-style clients list the single head and request sizes.
Standalone honors custom sizes through the normal resize path (tabs
reflow, swapchain follows lazily at render); nested refuses anything
but the live size because the host window owns the geometry. Anything
beyond size (disabling, scale, transform) is refused everywhere: one
fixed output per session. Successful applies republish so listening
clients converge. */
/* Broadcast the live output as the manager's current configuration;
wlroots creates/updates/destroys heads from it and only notifies on
change. No-op until both manager and output exist. */
static void output_mgmt_publish(void) {
if (!server.output_mgr || !server.output)
return;
struct wlr_output_configuration_v1 *config =
wlr_output_configuration_v1_create();
if (!config)
return;
if (!wlr_output_configuration_head_v1_create(config, server.output)) {
wlr_output_configuration_v1_destroy(config);
return;
}
wlr_output_manager_v1_set_configuration(server.output_mgr, config);
}
/* Re-publish after a commit changed the output size. Resizes land
lazily (host configures, client applies), so the commit -- not the
request -- is where the new size becomes observable. */
static void output_mgmt_sync(void) {
if (!server.output_mgr || !server.output)
return;
static int last_w = -1, last_h = -1;
if (server.output->width == last_w && server.output->height == last_h)
return;
last_w = server.output->width;
last_h = server.output->height;
output_mgmt_publish();
}
/* The config head addressing our output, if the client included it. */
static struct wlr_output_configuration_head_v1 *mgmt_head_for_output(
struct wlr_output_configuration_v1 *config) {
if (!server.output)
return NULL;
struct wlr_output_configuration_head_v1 *h;
wl_list_for_each(h, &config->heads, link) {
if (h->state.output == server.output)
return h;
}
return NULL;
}
/* Requested size, preferring an explicit mode over a custom one. */
static void mgmt_head_size(struct wlr_output_configuration_head_v1 *h,
int *w, int *hh) {
if (h->state.mode) {
*w = h->state.mode->width;
*hh = h->state.mode->height;
} else {
*w = h->state.custom_mode.width;
*hh = h->state.custom_mode.height;
}
}
/* Whether a client config is one we can honor: our output, still
enabled, same scale/transform, sane size. Nested additionally
requires the live size (the host owns it). */
static bool mgmt_config_ok(struct wlr_output_configuration_v1 *config,
int *w, int *hh) {
struct wlr_output_configuration_head_v1 *h =
mgmt_head_for_output(config);
if (!h || !h->state.enabled || !server.output)
return false;
mgmt_head_size(h, w, hh);
if (*w < 50 || *hh < 50 || *w > 16384 || *hh > 16384)
return false;
if (h->state.scale != (float)server.output->scale ||
h->state.transform != server.output->transform)
return false;
if (!server.standalone &&
(*w != server.output->width || *hh != server.output->height))
return false;
return true;
}
static void handle_output_mgr_test(struct wl_listener *listener,
void *data) {
(void)listener;
struct wlr_output_configuration_v1 *config = data;
int w = 0, hh = 0;
bool ok = mgmt_config_ok(config, &w, &hh);
if (ok && server.standalone) {
/* Honest modeset probe on top of the sanity check. */
struct wlr_output_state state;
wlr_output_state_init(&state);
wlr_output_state_set_custom_mode(&state, w, hh, 0);
ok = wlr_output_test_state(server.output, &state);
wlr_output_state_finish(&state);
}
kick_log(KICK_LOG_DEBUG, "[output] test %dx%d %s\n", w, hh,
ok ? "ok" : "failed");
if (ok)
wlr_output_configuration_v1_send_succeeded(config);
else
wlr_output_configuration_v1_send_failed(config);
wlr_output_configuration_v1_destroy(config);
}
static void handle_output_mgr_apply(struct wl_listener *listener,
void *data) {
(void)listener;
struct wlr_output_configuration_v1 *config = data;
int w = 0, hh = 0;
bool ok = mgmt_config_ok(config, &w, &hh);
if (ok && server.standalone) {
struct wlr_output_state state;
wlr_output_state_init(&state);
wlr_output_state_set_custom_mode(&state, w, hh, 0);
ok = wlr_output_test_state(server.output, &state);
wlr_output_state_finish(&state);
}
if (ok) {
if (server.standalone)
output_resize(w, hh);
output_mgmt_publish();
kick_log(KICK_LOG_INFO, "[output] apply %dx%d\n", w, hh);
wlr_output_configuration_v1_send_succeeded(config);
} else {
kick_log(KICK_LOG_INFO, "[output] apply %dx%d refused\n", w, hh);
wlr_output_configuration_v1_send_failed(config);
}
wlr_output_configuration_v1_destroy(config);
}
/* Gamma control: apply the client's ramp to the single output (or reset it /* Gamma control: apply the client's ramp to the single output (or reset it
when the control went away: wlroots emits set_gamma with NULL then). A when the control went away: wlroots emits set_gamma with NULL then). A
failed commit fails the control so the client (wlsunset) retries later failed commit fails the control so the client (wlsunset) retries later
@ -10063,6 +10222,8 @@ static void render_standalone(void) {
apply_tearing_hint(&state); apply_tearing_hint(&state);
if (!wlr_output_commit_state(server.output, &state)) if (!wlr_output_commit_state(server.output, &state))
kick_log(KICK_LOG_DEBUG, "[render] output commit failed\n"); kick_log(KICK_LOG_DEBUG, "[render] output commit failed\n");
else
output_mgmt_sync();
frame_done: frame_done:
struct timespec now; struct timespec now;
@ -10147,6 +10308,8 @@ static void render_and_present(void) {
apply_tearing_hint(&state); apply_tearing_hint(&state);
if (!wlr_output_commit_state(server.output, &state)) if (!wlr_output_commit_state(server.output, &state))
kick_log(KICK_LOG_DEBUG, "[render] output commit failed\n"); kick_log(KICK_LOG_DEBUG, "[render] output commit failed\n");
else
output_mgmt_sync();
frame_done: frame_done:
struct timespec now; struct timespec now;
@ -11189,6 +11352,14 @@ int main(int argc, char *argv[]) {
wl_signal_add(&server.output_power_mgr->events.set_mode, wl_signal_add(&server.output_power_mgr->events.set_mode,
&server.output_power_set_mode); &server.output_power_set_mode);
/* Output management: publish the single head; client resizes apply
standalone and are refused nested (see the section). */
server.output_mgr = wlr_output_manager_v1_create(server.wl_display);
server.output_mgr_apply.notify = handle_output_mgr_apply;
wl_signal_add(&server.output_mgr->events.apply, &server.output_mgr_apply);
server.output_mgr_test.notify = handle_output_mgr_test;
wl_signal_add(&server.output_mgr->events.test, &server.output_mgr_test);
/* Gamma control: night-light clients set the output ramp (see /* Gamma control: night-light clients set the output ramp (see
handle_gamma_set); wlroots itself fails controls for outputs with handle_gamma_set); wlroots itself fails controls for outputs with
no LUT, so headless/nested clients learn it is unsupported. */ no LUT, so headless/nested clients learn it is unsupported. */

View file

@ -257,6 +257,12 @@ protocols = {
wl_protocols_dir / 'staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml', wl_protocols_dir / 'staging/xdg-toplevel-icon/xdg-toplevel-icon-v1.xml',
'xdg-toplevel-icon-v1', 'xdg-toplevel-icon-v1',
], ],
# Output management (kanshi/wdisplays reconfigure the output):
# wlroots extension, vendored like the other wlr-protocols XMLs.
'output-management': [
'wlr-protocols/wlr-output-management-unstable-v1.xml',
'wlr-output-management-unstable-v1',
],
# Content hints (photo/video/game) + single-pixel buffers (1x1 solid # Content hints (photo/video/game) + single-pixel buffers (1x1 solid
# color without a shm pool). Both are staging protocols from the # color without a shm pool). Both are staging protocols from the
# system wayland-protocols, like wlroots itself uses. # system wayland-protocols, like wlroots itself uses.
@ -396,6 +402,7 @@ executable('wltest',
code_by_name['xdg-dialog'], code_by_name['xdg-dialog'],
code_by_name['pointer-gestures'], code_by_name['pointer-gestures'],
code_by_name['toplevel-icon'], code_by_name['toplevel-icon'],
code_by_name['output-management'],
client_headers['xdg-shell'], client_headers['xdg-shell'],
client_headers['cursor-shape'], client_headers['cursor-shape'],
client_headers['wlr-layer-shell-unstable-v1'], client_headers['wlr-layer-shell-unstable-v1'],
@ -420,6 +427,7 @@ executable('wltest',
client_headers['tearing-control'], client_headers['tearing-control'],
client_headers['fractional-scale'], client_headers['fractional-scale'],
client_headers['toplevel-icon'], client_headers['toplevel-icon'],
client_headers['output-management'],
dependencies: [dependency('wayland-client'), dependencies: [dependency('wayland-client'),
dependency('xkbcommon')]) dependency('xkbcommon')])

View file

@ -1516,6 +1516,85 @@ def test_standalone_headless_toplevel_icon_preferred(tmp_path):
s.close() s.close()
def test_standalone_headless_output_mgmt_globals(tmp_path):
"""The standalone compositor advertises zwlr_output_manager_v1 so
display tools can list and reconfigure the output."""
out = tmp_path / "globals.txt"
s = _spawn_standalone({}, "%s --globals-out '%s' '#112233'" % (WL, out))
try:
deadline = time.monotonic() + 15.0
while time.monotonic() < deadline:
if out.exists():
break
time.sleep(0.1)
assert out.exists(), "wltest --globals-out never wrote its file"
names = {line.split()[0] for line in out.read_text().splitlines()}
assert "zwlr_output_manager_v1" in names, \
f"missing output-mgmt global; advertised: {sorted(names)}"
finally:
s.close()
def test_standalone_headless_output_mgmt_list(tmp_path):
"""A management client learns the head (name + live size); the plain
color tab keeps the session up after the lister exits."""
out = tmp_path / "om.out"
s = _spawn_standalone(
{}, "%s --output-mgmt --output-mgmt-out '%s'" % (WL, out),
"%s '#ff0000'" % WL)
try:
s.wait_any_active()
deadline = time.monotonic() + 15.0
while time.monotonic() < deadline:
if out.exists():
break
time.sleep(0.1)
assert out.exists(), "output-mgmt lister never wrote its file"
assert re.search(r"^head \S+ \d+x\d+$", out.read_text(),
re.MULTILINE), \
f"no head line in: {out.read_text()!r}"
assert s.proc.poll() is None, f"log tail:\n{s.read_log()[-400:]}"
finally:
s.close()
def test_standalone_headless_output_mgmt_apply(tmp_path):
"""Applying 800x600 through output-management resizes the session:
the client sees before/applied/after and a tab commits at 800x600."""
out = tmp_path / "om.out"
s = _spawn_standalone(
{},
"%s --output-mgmt --output-mgmt-out '%s' --output-mgmt-size 800x600"
% (WL, out),
"%s '#ff0000'" % WL)
try:
s.wait_any_active()
deadline = time.monotonic() + 20.0
while time.monotonic() < deadline:
if out.exists() and "after " in out.read_text():
break
time.sleep(0.2)
text = out.read_text() if out.exists() else ""
assert "before " in text, f"no before line in: {text!r}"
assert "applied" in text, f"apply not acked in: {text!r}"
m = re.search(r"^after \S+ (\d+)x(\d+)$", text, re.MULTILINE)
assert m and (m.group(1), m.group(2)) == ("800", "600"), \
f"no 800x600 after line in: {text!r}"
# A live tab reflows into the new output size.
deadline = time.monotonic() + 15.0
geom = None
while time.monotonic() < deadline:
if re.search(r"\[diag\] commit app=\d+ geom=800x600",
s.read_log()):
geom = True
break
time.sleep(0.2)
assert geom, "no tab committed at 800x600"
assert s.proc.poll() is None, f"log tail:\n{s.read_log()[-400:]}"
finally:
s.close()
def test_standalone_headless_gestures_globals(tmp_path): def test_standalone_headless_gestures_globals(tmp_path):
"""The standalone compositor advertises zwp_pointer_gestures_v1 so """The standalone compositor advertises zwp_pointer_gestures_v1 so
touchpad clients can bind swipe/pinch/hold.""" touchpad clients can bind swipe/pinch/hold."""

View file

@ -43,6 +43,7 @@
#include "tearing-control-v1-client.h" #include "tearing-control-v1-client.h"
#include "fractional-scale-v1-client.h" #include "fractional-scale-v1-client.h"
#include "xdg-toplevel-icon-v1-client.h" #include "xdg-toplevel-icon-v1-client.h"
#include "wlr-output-management-unstable-v1-client.h"
/* Minimal deterministic xdg toplevel test client: shows a solid-color shm /* Minimal deterministic xdg toplevel test client: shows a solid-color shm
buffer and runs until killed. Used by test.sh to give the smoke test two buffer and runs until killed. Used by test.sh to give the smoke test two
@ -152,6 +153,7 @@ static struct xdg_activation_v1 *activation_mgr;
static struct xdg_wm_dialog_v1 *dialog_mgr; static struct xdg_wm_dialog_v1 *dialog_mgr;
static struct zwp_pointer_gestures_v1 *gestures_mgr; static struct zwp_pointer_gestures_v1 *gestures_mgr;
static struct xdg_toplevel_icon_manager_v1 *toplevel_icon_mgr; static struct xdg_toplevel_icon_manager_v1 *toplevel_icon_mgr;
static struct zwlr_output_manager_v1 *output_mgr;
static struct wp_tearing_control_manager_v1 *tearing_mgr; static struct wp_tearing_control_manager_v1 *tearing_mgr;
static struct wp_fractional_scale_manager_v1 *fractional_mgr; static struct wp_fractional_scale_manager_v1 *fractional_mgr;
static struct zwp_text_input_manager_v3 *text_input_mgr; static struct zwp_text_input_manager_v3 *text_input_mgr;
@ -839,6 +841,11 @@ static void registry_global(void *data, struct wl_registry *reg, uint32_t name,
toplevel_icon_mgr = wl_registry_bind(reg, name, toplevel_icon_mgr = wl_registry_bind(reg, name,
&xdg_toplevel_icon_manager_v1_interface, &xdg_toplevel_icon_manager_v1_interface,
version < 1 ? version : 1); version < 1 ? version : 1);
} else if (strcmp(interface,
zwlr_output_manager_v1_interface.name) == 0) {
output_mgr = wl_registry_bind(reg, name,
&zwlr_output_manager_v1_interface,
version < 4 ? version : 4);
} else if (strcmp(interface, } else if (strcmp(interface,
wp_tearing_control_manager_v1_interface.name) == 0) { wp_tearing_control_manager_v1_interface.name) == 0) {
tearing_mgr = wl_registry_bind(reg, name, tearing_mgr = wl_registry_bind(reg, name,
@ -1701,10 +1708,255 @@ static void gesture_bind_all(void) {
zwp_pointer_gesture_hold_v1_add_listener(ho, &hold_listener, NULL); zwp_pointer_gesture_hold_v1_add_listener(ho, &hold_listener, NULL);
} }
/* --- Output management (wlr-output-management) ---
--output-mgmt --output-mgmt-out <file> [--output-mgmt-size WxH]:
list the head (name + current size) into <file>; with a size, apply
it as a custom mode first and record before/after/applied. No window
is opened. */
static bool output_mgmt_mode;
static const char *output_mgmt_out;static int output_mgmt_w, output_mgmt_h;
static bool output_mgmt_want_size;
static struct zwlr_output_head_v1 *om_head;
static char om_name[64];
static bool om_applied;
static struct {
struct zwlr_output_mode_v1 *res;
int w, h;
} om_modes[32];
static int om_nmodes;
static struct zwlr_output_mode_v1 *om_current;
static const struct zwlr_output_head_v1_listener om_head_listener;
static const struct zwlr_output_mode_v1_listener om_mode_listener;
static void om_note(const char *fmt, ...) {
if (!output_mgmt_out)
return;
FILE *f = fopen(output_mgmt_out, "a");
if (!f)
return;
va_list ap;
va_start(ap, fmt);
vfprintf(f, fmt, ap);
va_end(ap);
fputc('\n', f);
fclose(f);
}
static int om_mode_size(struct zwlr_output_mode_v1 *res, int *w, int *h) {
for (int i = 0; i < om_nmodes; i++)
if (om_modes[i].res == res) {
*w = om_modes[i].w;
*h = om_modes[i].h;
return 0;
}
return -1;
}
static void om_report_head(const char *tag) {
int w = 0, h = 0;
if (om_mode_size(om_current, &w, &h) == 0)
om_note("%s %s %dx%d", tag, om_name[0] ? om_name : "?", w, h);
else
om_note("%s %s ?", tag, om_name[0] ? om_name : "?");
}
static void om_config_succeeded(void *data,
struct zwlr_output_configuration_v1 *cfg) {
(void)data; (void)cfg;
om_note("applied");
om_applied = true;
}
static void om_config_failed(void *data,
struct zwlr_output_configuration_v1 *cfg) {
(void)data; (void)cfg;
om_note("failed");
exit(0);
}
static void om_config_cancelled(void *data,
struct zwlr_output_configuration_v1 *cfg) {
(void)data; (void)cfg;
/* Superseded by a newer configuration (e.g. a concurrent modeset):
wait for the fresh done and retry with its serial, bounded. */
static int retries;
if (++retries > 3) {
om_note("cancelled");
exit(1);
}
om_applied = false;
}
static const struct zwlr_output_configuration_v1_listener om_config_listener = {
.succeeded = om_config_succeeded,
.failed = om_config_failed,
.cancelled = om_config_cancelled,
};
static void om_mgr_head(void *data, struct zwlr_output_manager_v1 *mgr,
struct zwlr_output_head_v1 *head) {
(void)data; (void)mgr;
if (!om_head) {
om_head = head;
zwlr_output_head_v1_add_listener(head, &om_head_listener, NULL);
}
}
static void om_head_name(void *data, struct zwlr_output_head_v1 *head,
const char *name) {
(void)data; (void)head;
snprintf(om_name, sizeof(om_name), "%s", name ? name : "");
}
static void om_noop_head_str(void *data, struct zwlr_output_head_v1 *head,
const char *s) {
(void)data; (void)head; (void)s;
}
static void om_noop_head_u32(void *data, struct zwlr_output_head_v1 *head,
uint32_t v) {
(void)data; (void)head; (void)v;
}
static void om_noop_head_i32x2(void *data, struct zwlr_output_head_v1 *head,
int32_t a, int32_t b) {
(void)data; (void)head; (void)a; (void)b;
}
static void om_noop_head_i32(void *data, struct zwlr_output_head_v1 *head,
int32_t v) {
(void)data; (void)head; (void)v;
}
static void om_head_mode(void *data, struct zwlr_output_head_v1 *head,
struct zwlr_output_mode_v1 *mode) {
(void)data; (void)head;
if (om_nmodes < 32) {
om_modes[om_nmodes].res = mode;
om_modes[om_nmodes].w = 0;
om_modes[om_nmodes].h = 0;
om_nmodes++;
zwlr_output_mode_v1_add_listener(mode, &om_mode_listener, NULL);
}
}
static void om_head_current_mode(void *data,
struct zwlr_output_head_v1 *head,
struct zwlr_output_mode_v1 *mode) {
(void)data; (void)head;
om_current = mode;
}
static void om_head_finished(void *data, struct zwlr_output_head_v1 *head) {
(void)data; (void)head;
}
static const struct zwlr_output_head_v1_listener om_head_listener = {
.name = om_head_name,
.description = om_noop_head_str,
.physical_size = om_noop_head_i32x2,
.mode = om_head_mode,
.enabled = om_noop_head_i32,
.current_mode = om_head_current_mode,
.position = om_noop_head_i32x2,
.transform = om_noop_head_i32,
.scale = om_noop_head_i32,
.finished = om_head_finished,
.make = om_noop_head_str,
.model = om_noop_head_str,
.serial_number = om_noop_head_str,
.adaptive_sync = om_noop_head_u32,
};
static void om_mode_size_ev(void *data, struct zwlr_output_mode_v1 *mode,
int32_t w, int32_t h) {
(void)data;
for (int i = 0; i < om_nmodes; i++)
if (om_modes[i].res == mode) {
om_modes[i].w = w;
om_modes[i].h = h;
return;
}
}
static void om_noop_mode_i32(void *data, struct zwlr_output_mode_v1 *mode,
int32_t v) {
(void)data; (void)mode; (void)v;
}
static void om_noop_mode_void(void *data, struct zwlr_output_mode_v1 *mode) {
(void)data; (void)mode;
}
static const struct zwlr_output_mode_v1_listener om_mode_listener = {
.size = om_mode_size_ev,
.refresh = om_noop_mode_i32,
.preferred = om_noop_mode_void,
.finished = om_noop_mode_void,
};
static void om_mgr_done(void *data, struct zwlr_output_manager_v1 *mgr,
uint32_t serial) {
(void)data; (void)mgr;
if (output_mgmt_want_size && !om_applied && om_head) {
int w = 0, h = 0;
if (om_mode_size(om_current, &w, &h) == 0 &&
(w != output_mgmt_w || h != output_mgmt_h)) {
om_report_head("before");
struct zwlr_output_configuration_v1 *cfg =
zwlr_output_manager_v1_create_configuration(output_mgr,
serial);
zwlr_output_configuration_v1_add_listener(cfg,
&om_config_listener, NULL);
struct zwlr_output_configuration_head_v1 *chead =
zwlr_output_configuration_v1_enable_head(cfg, om_head);
zwlr_output_configuration_head_v1_set_custom_mode(
chead, output_mgmt_w, output_mgmt_h, 0);
zwlr_output_configuration_v1_apply(cfg);
wl_display_flush(display);
return;
}
}
om_report_head(om_applied ? "after" : "head");
exit(0);
}
static void om_mgr_finished(void *data,
struct zwlr_output_manager_v1 *mgr) {
(void)data; (void)mgr;
}
static const struct zwlr_output_manager_v1_listener om_mgr_listener = {
.head = om_mgr_head,
.done = om_mgr_done,
.finished = om_mgr_finished,
};
static void output_mgmt_run(void) {
display = wl_display_connect(NULL);
if (!display) {
fprintf(stderr, "wltest: output-mgmt: cannot connect\n");
exit(1);
}
struct wl_registry *reg = wl_display_get_registry(display);
wl_registry_add_listener(reg, &registry_listener, NULL);
wl_display_roundtrip(display);
if (!output_mgr) {
fprintf(stderr, "wltest: output-mgmt: manager missing\n");
exit(1);
}
zwlr_output_manager_v1_add_listener(output_mgr, &om_mgr_listener, NULL);
wl_display_roundtrip(display);
wl_display_roundtrip(display);
for (;;) {
if (wl_display_dispatch(display) < 0)
break;
}
}
/* --- Toplevel icon --- /* --- Toplevel icon ---
--icon <#RRGGBB> [SIZE]: upload a SIZE×SIZE (default 8) solid-color --icon <#RRGGBB> [SIZE]: upload a SIZE×SIZE (default 8) solid-color
icon buffer and set it on the toplevel via xdg-toplevel-icon-v1. */ icon buffer and set it on the toplevel via xdg-toplevel-icon-v1. */static uint8_t ICON_PIXEL[4] = { 0x00, 0xff, 0x00, 0xff }; /* RGBA */
static uint8_t ICON_PIXEL[4] = { 0x00, 0xff, 0x00, 0xff }; /* RGBA */
static int icon_size; static int icon_size;
static bool want_icon; static bool want_icon;
@ -2464,6 +2716,8 @@ static void print_usage(const char *argv0) {
"[--dialog-modal] [--extra-modal] [--extra-delay <ms>] " "[--dialog-modal] [--extra-modal] [--extra-delay <ms>] "
"[--tearing <vsync|async>] [--fractional-out <file>] " "[--tearing <vsync|async>] [--fractional-out <file>] "
"[--icon <#RRGGBB> [SIZE]] " "[--icon <#RRGGBB> [SIZE]] "
"[--output-mgmt [--output-mgmt-out <file>] "
"[--output-mgmt-size <W>x<H>]] "
"#RRGGBB [WIDTHxHEIGHT]\n" "#RRGGBB [WIDTHxHEIGHT]\n"
" %s shows a solid-color xdg_toplevel, e.g. #ff0000\n" " %s shows a solid-color xdg_toplevel, e.g. #ff0000\n"
" --extra also opens a second toplevel from the same client\n" " --extra also opens a second toplevel from the same client\n"
@ -2505,7 +2759,10 @@ static void print_usage(const char *argv0) {
" on the toplevel; --fractional-out writes the\n" " on the toplevel; --fractional-out writes the\n"
" compositor's preferred_scale and exits\n" " compositor's preferred_scale and exits\n"
" --icon uploads a SIZE×SIZE (default 8) solid-color\n" " --icon uploads a SIZE×SIZE (default 8) solid-color\n"
" icon and sets it via xdg-toplevel-icon-v1", " icon and sets it via xdg-toplevel-icon-v1\n"
" --output-mgmt lists the output head (name + size)\n"
" to --output-mgmt-out, applying --output-mgmt-size\n"
" first when given",
argv0, argv0); argv0, argv0);
} }
@ -2576,6 +2833,30 @@ int main(int argc, char **argv) {
} }
fractional_out = argv[i + 1]; fractional_out = argv[i + 1];
i += 2; i += 2;
} else if (strcmp(argv[i], "--output-mgmt") == 0) {
output_mgmt_mode = true;
i += 1;
} else if (strcmp(argv[i], "--output-mgmt-out") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "wltest: --output-mgmt-out needs a path\n");
return 1;
}
output_mgmt_out = argv[i + 1];
i += 2;
} else if (strcmp(argv[i], "--output-mgmt-size") == 0) {
if (i + 1 >= argc) {
fprintf(stderr, "wltest: --output-mgmt-size needs WxH\n");
return 1;
}
if (sscanf(argv[i + 1], "%dx%d", &output_mgmt_w,
&output_mgmt_h) != 2 ||
output_mgmt_w <= 0 || output_mgmt_h <= 0) {
fprintf(stderr, "wltest: bad --output-mgmt-size '%s'\n",
argv[i + 1]);
return 1;
}
output_mgmt_want_size = true;
i += 2;
} else if (strcmp(argv[i], "--icon") == 0) { } else if (strcmp(argv[i], "--icon") == 0) {
if (i + 1 >= argc) { if (i + 1 >= argc) {
fprintf(stderr, "wltest: --icon needs a color\n"); fprintf(stderr, "wltest: --icon needs a color\n");
@ -2981,6 +3262,12 @@ int main(int argc, char **argv) {
gamma_run(); gamma_run();
return 0; return 0;
} }
/* Output-management mode: no color/size argument. List the head
and optionally apply a custom size, then exit. */
if (output_mgmt_mode) {
output_mgmt_run();
return 0;
}
/* Shortcuts-inhibit mode: act as a game window that grabs the /* Shortcuts-inhibit mode: act as a game window that grabs the
compositor's shortcuts. Creates its own focused window. */ compositor's shortcuts. Creates its own focused window. */
if (inhibit_mode) { if (inhibit_mode) {

View file

@ -0,0 +1,601 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="wlr_output_management_unstable_v1">
<copyright>
Copyright © 2019 Purism SPC
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
</copyright>
<description summary="protocol to configure output devices">
This protocol exposes interfaces to obtain and modify output device
configuration.
Warning! The protocol described in this file is experimental and
backward incompatible changes may be made. Backward compatible changes
may be added together with the corresponding interface version bump.
Backward incompatible changes are done by bumping the version number in
the protocol and interface names and resetting the interface version.
Once the protocol is to be declared stable, the 'z' prefix and the
version number in the protocol and interface names are removed and the
interface version number is reset.
</description>
<interface name="zwlr_output_manager_v1" version="4">
<description summary="output device configuration manager">
This interface is a manager that allows reading and writing the current
output device configuration.
Output devices that display pixels (e.g. a physical monitor or a virtual
output in a window) are represented as heads. Heads cannot be created nor
destroyed by the client, but they can be enabled or disabled and their
properties can be changed. Each head may have one or more available modes.
Whenever a head appears (e.g. a monitor is plugged in), it will be
advertised via the head event. Immediately after the output manager is
bound, all current heads are advertised.
Whenever a head's properties change, the relevant wlr_output_head events
will be sent. Not all head properties will be sent: only properties that
have changed need to.
Whenever a head disappears (e.g. a monitor is unplugged), a
wlr_output_head.finished event will be sent.
After one or more heads appear, change or disappear, the done event will
be sent. It carries a serial which can be used in a create_configuration
request to update heads properties.
The information obtained from this protocol should only be used for output
configuration purposes. This protocol is not designed to be a generic
output property advertisement protocol for regular clients. Instead,
protocols such as xdg-output should be used.
</description>
<event name="head">
<description summary="introduce a new head">
This event introduces a new head. This happens whenever a new head
appears (e.g. a monitor is plugged in) or after the output manager is
bound.
</description>
<arg name="head" type="new_id" interface="zwlr_output_head_v1"/>
</event>
<event name="done">
<description summary="sent all information about current configuration">
This event is sent after all information has been sent after binding to
the output manager object and after any subsequent changes. This applies
to child head and mode objects as well. In other words, this event is
sent whenever a head or mode is created or destroyed and whenever one of
their properties has been changed. Not all state is re-sent each time
the current configuration changes: only the actual changes are sent.
This allows changes to the output configuration to be seen as atomic,
even if they happen via multiple events.
A serial is sent to be used in a future create_configuration request.
</description>
<arg name="serial" type="uint" summary="current configuration serial"/>
</event>
<request name="create_configuration">
<description summary="create a new output configuration object">
Create a new output configuration object. This allows to update head
properties.
</description>
<arg name="id" type="new_id" interface="zwlr_output_configuration_v1"/>
<arg name="serial" type="uint"/>
</request>
<request name="stop">
<description summary="stop sending events">
Indicates the client no longer wishes to receive events for output
configuration changes. However the compositor may emit further events,
until the finished event is emitted.
The client must not send any more requests after this one.
</description>
</request>
<event name="finished" type="destructor">
<description summary="the compositor has finished with the manager">
This event indicates that the compositor is done sending manager events.
The compositor will destroy the object immediately after sending this
event, so it will become invalid and the client should release any
resources associated with it.
</description>
</event>
</interface>
<interface name="zwlr_output_head_v1" version="4">
<description summary="output device">
A head is an output device. The difference between a wl_output object and
a head is that heads are advertised even if they are turned off. A head
object only advertises properties and cannot be used directly to change
them.
A head has some read-only properties: modes, name, description and
physical_size. These cannot be changed by clients.
Other properties can be updated via a wlr_output_configuration object.
Properties sent via this interface are applied atomically via the
wlr_output_manager.done event. No guarantees are made regarding the order
in which properties are sent.
</description>
<event name="name">
<description summary="head name">
This event describes the head name.
The naming convention is compositor defined, but limited to alphanumeric
characters and dashes (-). Each name is unique among all wlr_output_head
objects, but if a wlr_output_head object is destroyed the same name may
be reused later. The names will also remain consistent across sessions
with the same hardware and software configuration.
Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc. However, do
not assume that the name is a reflection of an underlying DRM
connector, X11 connection, etc.
If the compositor implements the xdg-output protocol and this head is
enabled, the xdg_output.name event must report the same name.
The name event is sent after a wlr_output_head object is created. This
event is only sent once per object, and the name does not change over
the lifetime of the wlr_output_head object.
</description>
<arg name="name" type="string"/>
</event>
<event name="description">
<description summary="head description">
This event describes a human-readable description of the head.
The description is a UTF-8 string with no convention defined for its
contents. Examples might include 'Foocorp 11" Display' or 'Virtual X11
output via :1'. However, do not assume that the name is a reflection of
the make, model, serial of the underlying DRM connector or the display
name of the underlying X11 connection, etc.
If the compositor implements xdg-output and this head is enabled,
the xdg_output.description must report the same description.
The description event is sent after a wlr_output_head object is created.
This event is only sent once per object, and the description does not
change over the lifetime of the wlr_output_head object.
</description>
<arg name="description" type="string"/>
</event>
<event name="physical_size">
<description summary="head physical size">
This event describes the physical size of the head. This event is only
sent if the head has a physical size (e.g. is not a projector or a
virtual device).
</description>
<arg name="width" type="int" summary="width in millimeters of the output"/>
<arg name="height" type="int" summary="height in millimeters of the output"/>
</event>
<event name="mode">
<description summary="introduce a mode">
This event introduces a mode for this head. It is sent once per
supported mode.
</description>
<arg name="mode" type="new_id" interface="zwlr_output_mode_v1"/>
</event>
<event name="enabled">
<description summary="head is enabled or disabled">
This event describes whether the head is enabled. A disabled head is not
mapped to a region of the global compositor space.
When a head is disabled, some properties (current_mode, position,
transform and scale) are irrelevant.
</description>
<arg name="enabled" type="int" summary="zero if disabled, non-zero if enabled"/>
</event>
<event name="current_mode">
<description summary="current mode">
This event describes the mode currently in use for this head. It is only
sent if the output is enabled.
</description>
<arg name="mode" type="object" interface="zwlr_output_mode_v1"/>
</event>
<event name="position">
<description summary="current position">
This events describes the position of the head in the global compositor
space. It is only sent if the output is enabled.
</description>
<arg name="x" type="int"
summary="x position within the global compositor space"/>
<arg name="y" type="int"
summary="y position within the global compositor space"/>
</event>
<event name="transform">
<description summary="current transformation">
This event describes the transformation currently applied to the head.
It is only sent if the output is enabled.
</description>
<arg name="transform" type="int" enum="wl_output.transform"/>
</event>
<event name="scale">
<description summary="current scale">
This events describes the scale of the head in the global compositor
space. It is only sent if the output is enabled.
</description>
<arg name="scale" type="fixed"/>
</event>
<event name="finished">
<description summary="the head has disappeared">
This event indicates that the head is no longer available. The head
object becomes inert. Clients should send a destroy request and release
any resources associated with it.
</description>
</event>
<!-- Version 2 additions -->
<event name="make" since="2">
<description summary="head manufacturer">
This event describes the manufacturer of the head.
This must report the same make as the wl_output interface does in its
geometry event.
Together with the model and serial_number events the purpose is to
allow clients to recognize heads from previous sessions and for example
load head-specific configurations back.
It is not guaranteed this event will be ever sent. A reason for that
can be that the compositor does not have information about the make of
the head or the definition of a make is not sensible in the current
setup, for example in a virtual session. Clients can still try to
identify the head by available information from other events but should
be aware that there is an increased risk of false positives.
It is not recommended to display the make string in UI to users. For
that the string provided by the description event should be preferred.
</description>
<arg name="make" type="string"/>
</event>
<event name="model" since="2">
<description summary="head model">
This event describes the model of the head.
This must report the same model as the wl_output interface does in its
geometry event.
Together with the make and serial_number events the purpose is to
allow clients to recognize heads from previous sessions and for example
load head-specific configurations back.
It is not guaranteed this event will be ever sent. A reason for that
can be that the compositor does not have information about the model of
the head or the definition of a model is not sensible in the current
setup, for example in a virtual session. Clients can still try to
identify the head by available information from other events but should
be aware that there is an increased risk of false positives.
It is not recommended to display the model string in UI to users. For
that the string provided by the description event should be preferred.
</description>
<arg name="model" type="string"/>
</event>
<event name="serial_number" since="2">
<description summary="head serial number">
This event describes the serial number of the head.
Together with the make and model events the purpose is to allow clients
to recognize heads from previous sessions and for example load head-
specific configurations back.
It is not guaranteed this event will be ever sent. A reason for that
can be that the compositor does not have information about the serial
number of the head or the definition of a serial number is not sensible
in the current setup. Clients can still try to identify the head by
available information from other events but should be aware that there
is an increased risk of false positives.
It is not recommended to display the serial_number string in UI to
users. For that the string provided by the description event should be
preferred.
</description>
<arg name="serial_number" type="string"/>
</event>
<!-- Version 3 additions -->
<request name="release" type="destructor" since="3">
<description summary="destroy the head object">
This request indicates that the client will no longer use this head
object.
</description>
</request>
<!-- Version 4 additions -->
<enum name="adaptive_sync_state" since="4">
<entry name="disabled" value="0" summary="adaptive sync is disabled"/>
<entry name="enabled" value="1" summary="adaptive sync is enabled"/>
</enum>
<event name="adaptive_sync" since="4">
<description summary="current adaptive sync state">
This event describes whether adaptive sync is currently enabled for
the head or not. Adaptive sync is also known as Variable Refresh
Rate or VRR.
</description>
<arg name="state" type="uint" enum="adaptive_sync_state"/>
</event>
</interface>
<interface name="zwlr_output_mode_v1" version="3">
<description summary="output mode">
This object describes an output mode.
Some heads don't support output modes, in which case modes won't be
advertised.
Properties sent via this interface are applied atomically via the
wlr_output_manager.done event. No guarantees are made regarding the order
in which properties are sent.
</description>
<event name="size">
<description summary="mode size">
This event describes the mode size. The size is given in physical
hardware units of the output device. This is not necessarily the same as
the output size in the global compositor space. For instance, the output
may be scaled or transformed.
</description>
<arg name="width" type="int" summary="width of the mode in hardware units"/>
<arg name="height" type="int" summary="height of the mode in hardware units"/>
</event>
<event name="refresh">
<description summary="mode refresh rate">
This event describes the mode's fixed vertical refresh rate. It is only
sent if the mode has a fixed refresh rate.
</description>
<arg name="refresh" type="int" summary="vertical refresh rate in mHz"/>
</event>
<event name="preferred">
<description summary="mode is preferred">
This event advertises this mode as preferred.
</description>
</event>
<event name="finished">
<description summary="the mode has disappeared">
This event indicates that the mode is no longer available. The mode
object becomes inert. Clients should send a destroy request and release
any resources associated with it.
</description>
</event>
<!-- Version 3 additions -->
<request name="release" type="destructor" since="3">
<description summary="destroy the mode object">
This request indicates that the client will no longer use this mode
object.
</description>
</request>
</interface>
<interface name="zwlr_output_configuration_v1" version="4">
<description summary="output configuration">
This object is used by the client to describe a full output configuration.
First, the client needs to setup the output configuration. Each head can
be either enabled (and configured) or disabled. It is a protocol error to
send two enable_head or disable_head requests with the same head. It is a
protocol error to omit a head in a configuration.
Then, the client can apply or test the configuration. The compositor will
then reply with a succeeded, failed or cancelled event. Finally the client
should destroy the configuration object.
</description>
<enum name="error">
<entry name="already_configured_head" value="1"
summary="head has been configured twice"/>
<entry name="unconfigured_head" value="2"
summary="head has not been configured"/>
<entry name="already_used" value="3"
summary="request sent after configuration has been applied or tested"/>
</enum>
<request name="enable_head">
<description summary="enable and configure a head">
Enable a head. This request creates a head configuration object that can
be used to change the head's properties.
</description>
<arg name="id" type="new_id" interface="zwlr_output_configuration_head_v1"
summary="a new object to configure the head"/>
<arg name="head" type="object" interface="zwlr_output_head_v1"
summary="the head to be enabled"/>
</request>
<request name="disable_head">
<description summary="disable a head">
Disable a head.
</description>
<arg name="head" type="object" interface="zwlr_output_head_v1"
summary="the head to be disabled"/>
</request>
<request name="apply">
<description summary="apply the configuration">
Apply the new output configuration.
In case the configuration is successfully applied, there is no guarantee
that the new output state matches completely the requested
configuration. For instance, a compositor might round the scale if it
doesn't support fractional scaling.
After this request has been sent, the compositor must respond with an
succeeded, failed or cancelled event. Sending a request that isn't the
destructor is a protocol error.
</description>
</request>
<request name="test">
<description summary="test the configuration">
Test the new output configuration. The configuration won't be applied,
but will only be validated.
Even if the compositor succeeds to test a configuration, applying it may
fail.
After this request has been sent, the compositor must respond with an
succeeded, failed or cancelled event. Sending a request that isn't the
destructor is a protocol error.
</description>
</request>
<event name="succeeded">
<description summary="configuration changes succeeded">
Sent after the compositor has successfully applied the changes or
tested them.
Upon receiving this event, the client should destroy this object.
If the current configuration has changed, events to describe the changes
will be sent followed by a wlr_output_manager.done event.
</description>
</event>
<event name="failed">
<description summary="configuration changes failed">
Sent if the compositor rejects the changes or failed to apply them. The
compositor should revert any changes made by the apply request that
triggered this event.
Upon receiving this event, the client should destroy this object.
</description>
</event>
<event name="cancelled">
<description summary="configuration has been cancelled">
Sent if the compositor cancels the configuration because the state of an
output changed and the client has outdated information (e.g. after an
output has been hotplugged).
The client can create a new configuration with a newer serial and try
again.
Upon receiving this event, the client should destroy this object.
</description>
</event>
<request name="destroy" type="destructor">
<description summary="destroy the output configuration">
Using this request a client can tell the compositor that it is not going
to use the configuration object anymore. Any changes to the outputs
that have not been applied will be discarded.
This request also destroys wlr_output_configuration_head objects created
via this object.
</description>
</request>
</interface>
<interface name="zwlr_output_configuration_head_v1" version="4">
<description summary="head configuration">
This object is used by the client to update a single head's configuration.
It is a protocol error to set the same property twice.
</description>
<enum name="error">
<entry name="already_set" value="1" summary="property has already been set"/>
<entry name="invalid_mode" value="2" summary="mode doesn't belong to head"/>
<entry name="invalid_custom_mode" value="3" summary="mode is invalid"/>
<entry name="invalid_transform" value="4" summary="transform value outside enum"/>
<entry name="invalid_scale" value="5" summary="scale negative or zero"/>
<entry name="invalid_adaptive_sync_state" value="6" since="4"
summary="invalid enum value used in the set_adaptive_sync request"/>
</enum>
<request name="set_mode">
<description summary="set the mode">
This request sets the head's mode.
</description>
<arg name="mode" type="object" interface="zwlr_output_mode_v1"/>
</request>
<request name="set_custom_mode">
<description summary="set a custom mode">
This request assigns a custom mode to the head. The size is given in
physical hardware units of the output device. If set to zero, the
refresh rate is unspecified.
It is a protocol error to set both a mode and a custom mode.
</description>
<arg name="width" type="int" summary="width of the mode in hardware units"/>
<arg name="height" type="int" summary="height of the mode in hardware units"/>
<arg name="refresh" type="int" summary="vertical refresh rate in mHz or zero"/>
</request>
<request name="set_position">
<description summary="set the position">
This request sets the head's position in the global compositor space.
</description>
<arg name="x" type="int" summary="x position in the global compositor space"/>
<arg name="y" type="int" summary="y position in the global compositor space"/>
</request>
<request name="set_transform">
<description summary="set the transform">
This request sets the head's transform.
</description>
<arg name="transform" type="int" enum="wl_output.transform"/>
</request>
<request name="set_scale">
<description summary="set the scale">
This request sets the head's scale.
</description>
<arg name="scale" type="fixed"/>
</request>
<!-- Version 4 additions -->
<request name="set_adaptive_sync" since="4">
<description summary="enable/disable adaptive sync">
This request enables/disables adaptive sync. Adaptive sync is also
known as Variable Refresh Rate or VRR.
</description>
<arg name="state" type="uint" enum="zwlr_output_head_v1.adaptive_sync_state"/>
</request>
</interface>
</protocol>