commit 5f933d96fd3836a0e622e933d5201172f05b797e Author: emersion Date: Fri Mar 8 16:15:21 2019 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6b71db5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/build +/build-* +*.log diff --git a/main.c b/main.c new file mode 100644 index 0000000..25c7e4e --- /dev/null +++ b/main.c @@ -0,0 +1,170 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include "wlr-output-management-unstable-v1-client-protocol.h" + +struct randr_state; + +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; + + bool enabled; +}; + +struct randr_state { + struct zwlr_output_manager_v1 *output_manager; + + struct wl_list heads; + uint32_t serial; + bool running; +}; + +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\n", + head->phys_width, head->phys_height); + } + printf(" Enabled: %s\n", head->enabled ? "yes" : "no"); + } +} + +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_enabled(void *data, + struct zwlr_output_head_v1 *wlr_head, int32_t enabled) { + struct randr_head *head = data; + head->enabled = !!enabled; +} + +static void head_handle_finished(void *data, + struct zwlr_output_head_v1 *wlr_head) { + struct randr_head *head = data; + wl_list_remove(&head->link); + 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, + .enabled = head_handle_enabled, + .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; + 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; + + print_state(state); + state->running = false; +} + +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, +}; + +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.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 EXIT_SUCCESS; +} diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..654e389 --- /dev/null +++ b/meson.build @@ -0,0 +1,48 @@ +project( + 'wlr-randr', + 'c', + version: '0.0.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') + +subdir('protocol') + +wlr_randr_inc = include_directories('include') + +wlr_randr_exe = executable( + meson.project_name(), + files(['main.c']), + dependencies: [wayland_client, client_protos], + install: true, +) diff --git a/protocol/meson.build b/protocol/meson.build new file mode 100644 index 0000000..80c04fc --- /dev/null +++ b/protocol/meson.build @@ -0,0 +1,44 @@ +wayland_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, +) diff --git a/protocol/wlr-output-management-unstable-v1.xml b/protocol/wlr-output-management-unstable-v1.xml new file mode 100644 index 0000000..07586ca --- /dev/null +++ b/protocol/wlr-output-management-unstable-v1.xml @@ -0,0 +1,444 @@ + + + + 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 get and change 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, but they can be enabled or disabled and their properties can be + changed. Each head may have one or more available modes. + + Heads are advertised when the output manager is bound, and whenever they + appear. + + Whenever the number of heads or modes changes, the done event will be + sent. It carries a serial which can be used in a create_configuration + request to change 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 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. + + 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 with wl_output is that heads + are advertized even if they are turned off. A head object only advertises + properties and cannot be used directly to change them. In order to update + some properties, one needs to create a wlr_output_configuration object. + + A head has some read-only properties: mode, name, description and + physical_size. These cannot be changed by clients. + + enabled and current_mode are physical properties. Updating them might take + some time, depending on hardware limitations. + + position, transform and scale are logical properties. They describe how + the output is mapped in the global compositor space. + + + + + If the head supports modes, this event is sent once per supported mode. + + + + + + + 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'. + + 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 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 and supports modes. + + + + + + + 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. + + + + + 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, if any. + + + + + + + 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. The head's properties are irrelevant in this case. + + + + + + + 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. + + + + + + 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. + + + + + + + + + + + This request sets the head's 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. + + + + +