pax_global_header00006660000000000000000000000064140072435750014521gustar00rootroot0000000000000052 comment=ced1daa89e59ce5a0e42fd77148a8f614bcbc3f7 wlr-randr-0.2.0/000077500000000000000000000000001400724357500134305ustar00rootroot00000000000000wlr-randr-0.2.0/.gitignore000066400000000000000000000000261400724357500154160ustar00rootroot00000000000000/build /build-* *.log wlr-randr-0.2.0/LICENSE000066400000000000000000000021141400724357500144330ustar00rootroot00000000000000Copyright (c) 2019 Purism SPC Copyright (c) 2019 The wlr-randr Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. wlr-randr-0.2.0/README.md000066400000000000000000000003451400724357500147110ustar00rootroot00000000000000# wlr-randr Utility to manage outputs of a Wayland compositor. ## Building Install dependencies: * meson (compile-time dependency) * wayland Then run: meson build ninja -C build build/wlr-randr ## License MIT wlr-randr-0.2.0/main.c000066400000000000000000000424601400724357500145260ustar00rootroot00000000000000#define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include "wlr-output-management-unstable-v1-client-protocol.h" struct randr_state; struct randr_head; struct randr_mode { struct randr_head *head; struct zwlr_output_mode_v1 *wlr_mode; struct wl_list link; int32_t width, height; int32_t refresh; // mHz bool preferred; }; struct randr_head { struct randr_state *state; struct zwlr_output_head_v1 *wlr_head; struct wl_list link; char *name, *description; int32_t phys_width, phys_height; // mm struct wl_list modes; bool enabled; struct randr_mode *mode; struct { int32_t width, height; int32_t refresh; } custom_mode; int32_t x, y; enum wl_output_transform transform; double scale; }; struct randr_state { struct zwlr_output_manager_v1 *output_manager; struct wl_list heads; uint32_t serial; bool running; bool failed; }; static const char *output_transform_map[] = { [WL_OUTPUT_TRANSFORM_NORMAL] = "normal", [WL_OUTPUT_TRANSFORM_90] = "90", [WL_OUTPUT_TRANSFORM_180] = "180", [WL_OUTPUT_TRANSFORM_270] = "270", [WL_OUTPUT_TRANSFORM_FLIPPED] = "flipped", [WL_OUTPUT_TRANSFORM_FLIPPED_90] = "flipped-90", [WL_OUTPUT_TRANSFORM_FLIPPED_180] = "flipped-180", [WL_OUTPUT_TRANSFORM_FLIPPED_270] = "flipped-270", }; static void print_state(struct randr_state *state) { struct randr_head *head; wl_list_for_each(head, &state->heads, link) { printf("%s \"%s\"\n", head->name, head->description); if (head->phys_width > 0 && head->phys_height > 0) { printf(" Physical size: %dx%d mm\n", head->phys_width, head->phys_height); } printf(" Enabled: %s\n", head->enabled ? "yes" : "no"); if (!wl_list_empty(&head->modes)) { printf(" Modes:\n"); struct randr_mode *mode; wl_list_for_each(mode, &head->modes, link) { printf(" %dx%d px", mode->width, mode->height); if (mode->refresh > 0) { printf(", %f Hz", (float)mode->refresh / 1000); } bool current = head->mode == mode; if (current || mode->preferred) { printf(" ("); if (mode->preferred) { printf("preferred"); } if (current && mode->preferred) { printf(", "); } if (current) { printf("current"); } printf(")"); } printf("\n"); } } if (!head->enabled) { continue; } printf(" Position: %d,%d\n", head->x, head->y); printf(" Transform: %s\n", output_transform_map[head->transform]); printf(" Scale: %f\n", head->scale); } state->running = false; } static void config_handle_succeeded(void *data, struct zwlr_output_configuration_v1 *config) { struct randr_state *state = data; zwlr_output_configuration_v1_destroy(config); state->running = false; } static void config_handle_failed(void *data, struct zwlr_output_configuration_v1 *config) { struct randr_state *state = data; zwlr_output_configuration_v1_destroy(config); state->running = false; state->failed = true; fprintf(stderr, "failed to apply configuration\n"); } static void config_handle_cancelled(void *data, struct zwlr_output_configuration_v1 *config) { struct randr_state *state = data; zwlr_output_configuration_v1_destroy(config); state->running = false; state->failed = true; fprintf(stderr, "configuration cancelled, please try again\n"); } static const struct zwlr_output_configuration_v1_listener config_listener = { .succeeded = config_handle_succeeded, .failed = config_handle_failed, .cancelled = config_handle_cancelled, }; static void apply_state(struct randr_state *state, bool dry_run) { struct zwlr_output_configuration_v1 *config = zwlr_output_manager_v1_create_configuration(state->output_manager, state->serial); zwlr_output_configuration_v1_add_listener(config, &config_listener, state); struct randr_head *head; wl_list_for_each(head, &state->heads, link) { if (!head->enabled) { zwlr_output_configuration_v1_disable_head(config, head->wlr_head); continue; } struct zwlr_output_configuration_head_v1 *config_head = zwlr_output_configuration_v1_enable_head(config, head->wlr_head); if (head->mode != NULL) { zwlr_output_configuration_head_v1_set_mode(config_head, head->mode->wlr_mode); } else { zwlr_output_configuration_head_v1_set_custom_mode(config_head, head->custom_mode.width, head->custom_mode.height, head->custom_mode.refresh); } zwlr_output_configuration_head_v1_set_position(config_head, head->x, head->y); zwlr_output_configuration_head_v1_set_transform(config_head, head->transform); zwlr_output_configuration_head_v1_set_scale(config_head, wl_fixed_from_double(head->scale)); } if (dry_run) { zwlr_output_configuration_v1_test(config); } else { zwlr_output_configuration_v1_apply(config); } } static void mode_handle_size(void *data, struct zwlr_output_mode_v1 *wlr_mode, int32_t width, int32_t height) { struct randr_mode *mode = data; mode->width = width; mode->height = height; } static void mode_handle_refresh(void *data, struct zwlr_output_mode_v1 *wlr_mode, int32_t refresh) { struct randr_mode *mode = data; mode->refresh = refresh; } static void mode_handle_preferred(void *data, struct zwlr_output_mode_v1 *wlr_mode) { struct randr_mode *mode = data; mode->preferred = true; } static void mode_handle_finished(void *data, struct zwlr_output_mode_v1 *wlr_mode) { struct randr_mode *mode = data; wl_list_remove(&mode->link); zwlr_output_mode_v1_destroy(mode->wlr_mode); free(mode); } static const struct zwlr_output_mode_v1_listener mode_listener = { .size = mode_handle_size, .refresh = mode_handle_refresh, .preferred = mode_handle_preferred, .finished = mode_handle_finished, }; static void head_handle_name(void *data, struct zwlr_output_head_v1 *wlr_head, const char *name) { struct randr_head *head = data; head->name = strdup(name); } static void head_handle_description(void *data, struct zwlr_output_head_v1 *wlr_head, const char *description) { struct randr_head *head = data; head->description = strdup(description); } static void head_handle_physical_size(void *data, struct zwlr_output_head_v1 *wlr_head, int32_t width, int32_t height) { struct randr_head *head = data; head->phys_width = width; head->phys_height = height; } static void head_handle_mode(void *data, struct zwlr_output_head_v1 *wlr_head, struct zwlr_output_mode_v1 *wlr_mode) { struct randr_head *head = data; struct randr_mode *mode = calloc(1, sizeof(*mode)); mode->head = head; mode->wlr_mode = wlr_mode; wl_list_insert(&head->modes, &mode->link); zwlr_output_mode_v1_add_listener(wlr_mode, &mode_listener, mode); } static void head_handle_enabled(void *data, struct zwlr_output_head_v1 *wlr_head, int32_t enabled) { struct randr_head *head = data; head->enabled = !!enabled; if (!enabled) { head->mode = NULL; } } static void head_handle_current_mode(void *data, struct zwlr_output_head_v1 *wlr_head, struct zwlr_output_mode_v1 *wlr_mode) { struct randr_head *head = data; struct randr_mode *mode; wl_list_for_each(mode, &head->modes, link) { if (mode->wlr_mode == wlr_mode) { head->mode = mode; return; } } fprintf(stderr, "received unknown current_mode\n"); head->mode = NULL; } static void head_handle_position(void *data, struct zwlr_output_head_v1 *wlr_head, int32_t x, int32_t y) { struct randr_head *head = data; head->x = x; head->y = y; } static void head_handle_transform(void *data, struct zwlr_output_head_v1 *wlr_head, int32_t transform) { struct randr_head *head = data; head->transform = transform; } static void head_handle_scale(void *data, struct zwlr_output_head_v1 *wlr_head, wl_fixed_t scale) { struct randr_head *head = data; head->scale = wl_fixed_to_double(scale); } static void head_handle_finished(void *data, struct zwlr_output_head_v1 *wlr_head) { struct randr_head *head = data; wl_list_remove(&head->link); zwlr_output_head_v1_destroy(head->wlr_head); free(head->name); free(head->description); free(head); } static const struct zwlr_output_head_v1_listener head_listener = { .name = head_handle_name, .description = head_handle_description, .physical_size = head_handle_physical_size, .mode = head_handle_mode, .enabled = head_handle_enabled, .current_mode = head_handle_current_mode, .position = head_handle_position, .transform = head_handle_transform, .scale = head_handle_scale, .finished = head_handle_finished, }; static void output_manager_handle_head(void *data, struct zwlr_output_manager_v1 *manager, struct zwlr_output_head_v1 *wlr_head) { struct randr_state *state = data; struct randr_head *head = calloc(1, sizeof(*head)); head->state = state; head->wlr_head = wlr_head; head->scale = 1.0; wl_list_init(&head->modes); wl_list_insert(&state->heads, &head->link); zwlr_output_head_v1_add_listener(wlr_head, &head_listener, head); } static void output_manager_handle_done(void *data, struct zwlr_output_manager_v1 *manager, uint32_t serial) { struct randr_state *state = data; state->serial = serial; } static void output_manager_handle_finished(void *data, struct zwlr_output_manager_v1 *manager) { // This space is intentionally left blank } static const struct zwlr_output_manager_v1_listener output_manager_listener = { .head = output_manager_handle_head, .done = output_manager_handle_done, .finished = output_manager_handle_finished, }; static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { struct randr_state *state = data; if (strcmp(interface, zwlr_output_manager_v1_interface.name) == 0) { state->output_manager = wl_registry_bind(registry, name, &zwlr_output_manager_v1_interface, 1); zwlr_output_manager_v1_add_listener(state->output_manager, &output_manager_listener, state); } } static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { // This space is intentionally left blank } static const struct wl_registry_listener registry_listener = { .global = registry_handle_global, .global_remove = registry_handle_global_remove, }; static const struct option long_options[] = { {"help", no_argument, 0, 'h'}, {"dryrun", no_argument, 0, 0}, {"output", required_argument, 0, 0}, {"on", no_argument, 0, 0}, {"off", no_argument, 0, 0}, {"mode", required_argument, 0, 0}, {"preferred", no_argument, 0, 0}, {"custom-mode", required_argument, 0, 0}, {"pos", required_argument, 0, 0}, {"transform", required_argument, 0, 0}, {"scale", required_argument, 0, 0}, {0}, }; static bool parse_mode(const char *value, int *width, int *height, int *refresh) { *refresh = 0; // width + "x" + height char *cur = (char *)value; char *end; *width = strtol(cur, &end, 10); if (end[0] != 'x' || cur == end) { fprintf(stderr, "invalid mode: invalid width: %s\n", value); return false; } cur = end + 1; *height = strtol(cur, &end, 10); if (cur == end) { fprintf(stderr, "invalid mode: invalid height: %s\n", value); return false; } if (end[0] != '\0') { // whitespace + "px" cur = end; while (cur[0] == ' ') { cur++; } if (strncmp(cur, "px", 2) == 0) { cur += 2; } if (cur[0] != '\0') { // ("," or "@") + whitespace + refresh if (cur[0] == ',' || cur[0] == '@') { cur++; } else { fprintf(stderr, "invalid mode: expected refresh rate: %s\n", value); return false; } while (cur[0] == ' ') { cur++; } double refresh_hz = strtod(cur, &end); if ((end[0] != '\0' && strcmp(end, "Hz") != 0) || cur == end || refresh_hz <= 0) { fprintf(stderr, "invalid mode: invalid refresh rate: %s\n", value); return false; } *refresh = round(refresh_hz * 1000); // Hz → mHz } } return true; } static void fixup_disabled_head(struct randr_head *head) { if (!head->mode && head->custom_mode.refresh == 0 && head->custom_mode.width == 0 && head->custom_mode.height == 0) { struct randr_mode *mode; wl_list_for_each(mode, &head->modes, link) { if (mode->preferred) { head->mode = mode; return; } } /* Pick first element if when there's no preferred mode */ if (!wl_list_empty(&head->modes)) { head->mode = wl_container_of(head->modes.next, mode, link); } } } static bool parse_output_arg(struct randr_head *head, const char *name, const char *value) { if (strcmp(name, "on") == 0) { if (!head->enabled) { fixup_disabled_head(head); } head->enabled = true; } else if (strcmp(name, "off") == 0) { head->enabled = false; } else if (strcmp(name, "mode") == 0) { int width, height, refresh; if (!parse_mode(value, &width, &height, &refresh)) { return false; } bool found = false; struct randr_mode *mode; wl_list_for_each(mode, &head->modes, link) { if (mode->width == width && mode->height == height && (refresh == 0 || mode->refresh == refresh)) { found = true; break; } } if (!found) { fprintf(stderr, "unknown mode: %s\n", value); return false; } head->mode = mode; head->custom_mode.width = 0; head->custom_mode.height = 0; head->custom_mode.refresh = 0; } else if (strcmp(name, "preferred") == 0) { bool found = false; struct randr_mode *mode; wl_list_for_each(mode, &head->modes, link) { if (mode->preferred) { found = true; break; } } if (!found) { fprintf(stderr, "no preferred mode found\n"); return false; } head->mode = mode; head->custom_mode.width = 0; head->custom_mode.height = 0; head->custom_mode.refresh = 0; } else if (strcmp(name, "custom-mode") == 0) { int width, height, refresh; if (!parse_mode(value, &width, &height, &refresh)) { return false; } head->mode = NULL; head->custom_mode.width = width; head->custom_mode.height = height; head->custom_mode.refresh = refresh; } else if (strcmp(name, "pos") == 0) { char *cur = (char *)value; char *end; int x = strtol(cur, &end, 10); if (end[0] != ',' || cur == end) { fprintf(stderr, "invalid position: %s\n", value); return false; } cur = end + 1; int y = strtol(cur, &end, 10); if (end[0] != '\0') { fprintf(stderr, "invalid position: %s\n", value); return false; } head->x = x; head->y = y; } else if (strcmp(name, "transform") == 0) { bool found = false; size_t len = sizeof(output_transform_map) / sizeof(output_transform_map[0]); for (size_t i = 0; i < len; ++i) { if (strcmp(output_transform_map[i], value) == 0) { found = true; head->transform = i; break; } } if (!found) { fprintf(stderr, "invalid transform: %s\n", value); return false; } } else if (strcmp(name, "scale") == 0) { char *end; double scale = strtod(value, &end); if (end[0] != '\0' || value == end) { fprintf(stderr, "invalid scale: %s\n", value); return false; } head->scale = scale; } else { fprintf(stderr, "invalid option: %s\n", name); return false; } return true; } static const char usage[] = "usage: wlr-randr [options…]\n" "--help\n" "--dryrun\n" "--output \n" " --on\n" " --off\n" " --mode|--custom-mode x[@Hz]\n" " --preferred\n" " --pos ,\n" " --transform normal|90|180|270|flipped|flipped-90|flipped-180|flipped-270\n" " --scale \n"; int main(int argc, char *argv[]) { struct randr_state state = { .running = true }; wl_list_init(&state.heads); struct wl_display *display = wl_display_connect(NULL); if (display == NULL) { fprintf(stderr, "failed to connect to display\n"); return EXIT_FAILURE; } struct wl_registry *registry = wl_display_get_registry(display); wl_registry_add_listener(registry, ®istry_listener, &state); wl_display_dispatch(display); wl_display_roundtrip(display); if (state.output_manager == NULL) { fprintf(stderr, "compositor doesn't support " "wlr-output-management-unstable-v1\n"); return EXIT_FAILURE; } while (state.serial == 0) { if (wl_display_dispatch(display) < 0) { fprintf(stderr, "wl_display_dispatch failed\n"); return EXIT_FAILURE; } } bool changed = false, dry_run = false; struct randr_head *current_head = NULL; while (1) { int option_index = -1; int c = getopt_long(argc, argv, "h", long_options, &option_index); if (c < 0) { break; } else if (c == '?') { return EXIT_FAILURE; } else if (c == 'h') { fprintf(stderr, "%s", usage); return EXIT_SUCCESS; } const char *name = long_options[option_index].name; const char *value = optarg; if (strcmp(name, "output") == 0) { bool found = false; wl_list_for_each(current_head, &state.heads, link) { if (strcmp(current_head->name, value) == 0) { found = true; break; } } if (!found) { fprintf(stderr, "unknown output %s\n", value); return EXIT_FAILURE; } } else if (strcmp(name, "dryrun") == 0) { dry_run = true; } else { // output sub-option if (current_head == NULL) { fprintf(stderr, "no --output specified before --%s\n", name); return EXIT_FAILURE; } if (!parse_output_arg(current_head, name, value)) { return EXIT_FAILURE; } changed = true; } } if (changed) { apply_state(&state, dry_run); } else { print_state(&state); } while (state.running && wl_display_dispatch(display) != -1) { // This space intentionally left blank } // TODO: destroy heads zwlr_output_manager_v1_destroy(state.output_manager); wl_registry_destroy(registry); wl_display_disconnect(display); return state.failed ? EXIT_FAILURE : EXIT_SUCCESS; } wlr-randr-0.2.0/meson.build000066400000000000000000000015751400724357500156020ustar00rootroot00000000000000project( 'wlr-randr', 'c', version : '0.2.0', license : 'MIT', meson_version : '>=0.47.0', default_options : ['c_std=c99', 'warning_level=3', 'werror=true'] ) cc = meson.get_compiler('c') add_project_arguments(cc.get_supported_arguments([ '-Wundef', '-Wlogical-op', '-Wmissing-include-dirs', '-Wold-style-definition', '-Wpointer-arith', '-Winit-self', '-Wfloat-equal', '-Wstrict-prototypes', '-Wredundant-decls', '-Wimplicit-fallthrough=2', '-Wendif-labels', '-Wstrict-aliasing=2', '-Woverflow', '-Wformat=2', '-Wno-missing-braces', '-Wno-missing-field-initializers', '-Wno-unused-parameter', ]), language: 'c') wayland_client = dependency('wayland-client') math = cc.find_library('m', required: false) subdir('protocol') wlr_randr_exe = executable( meson.project_name(), files(['main.c']), dependencies: [wayland_client, client_protos, math], install: true, ) wlr-randr-0.2.0/protocol/000077500000000000000000000000001400724357500152715ustar00rootroot00000000000000wlr-randr-0.2.0/protocol/meson.build000066400000000000000000000020731400724357500174350ustar00rootroot00000000000000wayland_scanner = find_program('wayland-scanner') # should check wayland_scanner's version, but it is hard to get if wayland_client.version().version_compare('>=1.14.91') code_type = 'private-code' else code_type = 'code' endif wayland_scanner_code = generator( wayland_scanner, output: '@BASENAME@-protocol.c', arguments: [code_type, '@INPUT@', '@OUTPUT@'], ) wayland_scanner_client = generator( wayland_scanner, output: '@BASENAME@-client-protocol.h', arguments: ['client-header', '@INPUT@', '@OUTPUT@'], ) client_protocols = [ ['wlr-output-management-unstable-v1.xml'], ] client_protos_src = [] client_protos_headers = [] foreach p : client_protocols xml = join_paths(p) client_protos_src += wayland_scanner_code.process(xml) client_protos_headers += wayland_scanner_client.process(xml) endforeach lib_client_protos = static_library( 'client_protos', client_protos_src + client_protos_headers, dependencies: [wayland_client] ) # for the include directory client_protos = declare_dependency( link_with: lib_client_protos, sources: client_protos_headers, ) wlr-randr-0.2.0/protocol/wlr-output-management-unstable-v1.xml000066400000000000000000000503571400724357500244400ustar00rootroot00000000000000 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. 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. 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. 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. 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. Create a new output configuration object. This allows to update head properties. 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. 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. 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. 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. 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. 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). This event introduces a mode for this head. It is sent once per supported mode. 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. This event describes the mode currently in use for this head. It is only sent if the output is enabled. This events describes the position of the head in the global compositor space. It is only sent if the output is enabled. This event describes the transformation currently applied to the head. It is only sent if the output is enabled. This events describes the scale of the head in the global compositor space. It is only sent if the output is enabled. 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. 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. 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. This event describes the mode's fixed vertical refresh rate. It is only sent if the mode has a fixed refresh rate. This event advertises this mode as preferred. 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. 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. Enable a head. This request creates a head configuration object that can be used to change the head's properties. Disable a head. 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. 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. 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. 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. 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. 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. 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. This request sets the head's 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. This request sets the head's position in the global compositor space. This request sets the head's transform. This request sets the head's scale.