| // Copyright 2018 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <assert.h> |
| #include <memory.h> |
| #include <stdbool.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <poll.h> |
| #include <sys/ioctl.h> |
| #include <sys/mman.h> |
| #include <sys/socket.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| #include "aura-shell.h" |
| #include "linux-dmabuf-unstable-v1.h" |
| #include "viewporter.h" |
| #include "xdg-shell.h" |
| #include "virtio-gpu-metadata-v1.h" |
| #include <wayland-client-core.h> |
| #include <wayland-client-protocol.h> |
| #include <wayland-client.h> |
| |
| // BTN_LEFT is copied from linux/input-event-codes.h because the kernel headers |
| // aren't readily available in some downstream projects. |
| #define BTN_LEFT 0x110 |
| |
| #define DEFAULT_SCALE 2 |
| #define MAX_BUFFER_COUNT 64 |
| #define EVENT_BUF_SIZE 256 |
| |
| const int32_t DWL_KEYBOARD_KEY_STATE_RELEASED = WL_KEYBOARD_KEY_STATE_RELEASED; |
| const int32_t DWL_KEYBOARD_KEY_STATE_PRESSED = WL_KEYBOARD_KEY_STATE_PRESSED; |
| |
| const uint32_t DWL_EVENT_TYPE_KEYBOARD_ENTER = 0x00; |
| const uint32_t DWL_EVENT_TYPE_KEYBOARD_LEAVE = 0x01; |
| const uint32_t DWL_EVENT_TYPE_KEYBOARD_KEY = 0x02; |
| const uint32_t DWL_EVENT_TYPE_POINTER_ENTER = 0x10; |
| const uint32_t DWL_EVENT_TYPE_POINTER_LEAVE = 0x11; |
| const uint32_t DWL_EVENT_TYPE_POINTER_MOVE = 0x12; |
| const uint32_t DWL_EVENT_TYPE_POINTER_BUTTON = 0x13; |
| const uint32_t DWL_EVENT_TYPE_TOUCH_DOWN = 0x20; |
| const uint32_t DWL_EVENT_TYPE_TOUCH_UP = 0x21; |
| const uint32_t DWL_EVENT_TYPE_TOUCH_MOTION = 0x22; |
| |
| const uint32_t DWL_SURFACE_FLAG_RECEIVE_INPUT = 1 << 0; |
| const uint32_t DWL_SURFACE_FLAG_HAS_ALPHA = 1 << 1; |
| |
| struct dwl_event { |
| const void *surface_descriptor; |
| uint32_t event_type; |
| int32_t params[3]; |
| }; |
| |
| struct dwl_context; |
| |
| struct interfaces { |
| struct dwl_context *context; |
| struct wl_compositor *compositor; |
| struct wl_subcompositor *subcompositor; |
| struct wl_shm *shm; |
| struct wl_seat *seat; |
| struct zaura_shell *aura; // optional |
| struct zwp_linux_dmabuf_v1 *linux_dmabuf; |
| struct xdg_wm_base *xdg_wm_base; |
| struct wp_viewporter *viewporter; // optional |
| struct wp_virtio_gpu_metadata_v1 *virtio_gpu_metadata; // optional |
| }; |
| |
| struct output { |
| struct wl_output *output; |
| struct zaura_output *aura_output; |
| struct dwl_context *context; |
| uint32_t id; |
| uint32_t current_scale; |
| uint32_t device_scale_factor; |
| bool internal; |
| }; |
| |
| struct input { |
| struct wl_keyboard *wl_keyboard; |
| struct wl_pointer *wl_pointer; |
| struct wl_surface *keyboard_input_surface; |
| struct wl_surface *pointer_input_surface; |
| int32_t pointer_x; |
| int32_t pointer_y; |
| bool pointer_lbutton_state; |
| }; |
| |
| typedef void (*dwl_error_callback_type)(const char *message); |
| |
| struct dwl_context { |
| struct wl_display *display; |
| struct dwl_surface *surfaces[MAX_BUFFER_COUNT]; |
| struct dwl_dmabuf *dmabufs[MAX_BUFFER_COUNT]; |
| struct interfaces ifaces; |
| struct input input; |
| bool output_added; |
| struct output outputs[8]; |
| |
| struct dwl_event event_cbuf[EVENT_BUF_SIZE]; |
| size_t event_read_pos; |
| size_t event_write_pos; |
| |
| dwl_error_callback_type error_callback; |
| }; |
| |
| #define outputs_for_each(context, pos, output) \ |
| for (pos = 0, output = &context->outputs[pos]; \ |
| pos < (sizeof(context->outputs) / sizeof(context->outputs[0])); \ |
| pos++, output = &context->outputs[pos]) |
| |
| struct dwl_dmabuf { |
| uint32_t width; |
| uint32_t height; |
| uint32_t import_id; |
| bool in_use; |
| struct wl_buffer *buffer; |
| struct dwl_context *context; |
| }; |
| |
| struct dwl_surface { |
| struct dwl_context *context; |
| struct wl_surface *wl_surface; |
| struct zaura_surface *aura; |
| struct xdg_surface *xdg_surface; |
| struct xdg_toplevel *xdg_toplevel; |
| struct wp_viewport *viewport; |
| struct wp_virtio_gpu_surface_metadata_v1 *virtio_gpu_surface_metadata; |
| struct wl_subsurface *subsurface; |
| uint32_t width; |
| uint32_t height; |
| uint32_t surface_id; |
| double scale; |
| bool close_requested; |
| size_t buffer_count; |
| uint64_t buffer_use_bit_mask; |
| struct wl_buffer *buffers[0]; |
| }; |
| |
| static_assert(sizeof(((struct dwl_surface *)0)->buffer_use_bit_mask) * 8 >= |
| MAX_BUFFER_COUNT, |
| "not enough bits in buffer_use_bit_mask"); |
| |
| static void output_geometry(void *data, struct wl_output *output, int x, int y, |
| int physical_width, int physical_height, |
| int subpixel, const char *make, const char *model, |
| int transform) |
| { |
| (void)data; |
| (void)output; |
| (void)x; |
| (void)y; |
| (void)physical_width; |
| (void)physical_height; |
| (void)subpixel; |
| (void)make; |
| (void)model; |
| (void)transform; |
| } |
| |
| static void output_mode(void *data, struct wl_output *output, uint32_t flags, |
| int width, int height, int refresh) |
| { |
| (void)data; |
| (void)output; |
| (void)flags; |
| (void)width; |
| (void)height; |
| (void)refresh; |
| } |
| |
| static void output_done(void *data, struct wl_output *output) |
| { |
| (void)data; |
| (void)output; |
| } |
| |
| static void output_scale(void *data, struct wl_output *wl_output, |
| int32_t scale_factor) |
| { |
| (void)wl_output; |
| struct output *output = (struct output *)data; |
| struct dwl_context *context = output->context; |
| |
| // If the aura interface is available, we prefer the scale factor |
| // reported by that. |
| if (context->ifaces.aura) |
| return; |
| |
| output->current_scale = 1000 * scale_factor; |
| } |
| |
| static const struct wl_output_listener output_listener = { |
| .geometry = output_geometry, |
| .mode = output_mode, |
| .done = output_done, |
| .scale = output_scale}; |
| |
| static void aura_output_scale(void *data, struct zaura_output *aura_output, |
| uint32_t flags, uint32_t scale) |
| { |
| (void)aura_output; |
| struct output *output = (struct output *)data; |
| if (flags & ZAURA_OUTPUT_SCALE_PROPERTY_CURRENT) { |
| output->current_scale = scale; |
| } |
| } |
| |
| static void aura_output_connection(void *data, struct zaura_output *aura_output, |
| uint32_t connection) |
| { |
| (void)aura_output; |
| struct output *output = (struct output *)data; |
| output->internal = connection == ZAURA_OUTPUT_CONNECTION_TYPE_INTERNAL; |
| } |
| |
| static void aura_output_device_scale_factor(void *data, |
| struct zaura_output *aura_output, |
| uint32_t device_scale_factor) |
| { |
| (void)aura_output; |
| struct output *output = (struct output *)data; |
| output->device_scale_factor = device_scale_factor; |
| } |
| |
| static const struct zaura_output_listener aura_output_listener = { |
| .scale = aura_output_scale, |
| .connection = aura_output_connection, |
| .device_scale_factor = aura_output_device_scale_factor}; |
| |
| static void xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, |
| uint32_t serial) |
| { |
| (void)data; |
| xdg_wm_base_pong(xdg_wm_base, serial); |
| } |
| |
| static const struct xdg_wm_base_listener xdg_wm_base_listener = { |
| .ping = xdg_wm_base_ping, |
| }; |
| |
| |
| static void wl_keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, |
| uint32_t format, int32_t fd, uint32_t size) |
| { |
| struct dwl_context *context = (struct dwl_context*)data; |
| (void)wl_keyboard; |
| (void)fd; |
| (void)size; |
| if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { |
| context->error_callback("wl_keyboard: invalid keymap format"); |
| } |
| } |
| |
| static void dwl_context_push_event(struct dwl_context *self, |
| struct dwl_event *event) |
| { |
| if (!self) |
| return; |
| |
| memcpy(self->event_cbuf + self->event_write_pos, event, |
| sizeof(struct dwl_event)); |
| |
| if (++self->event_write_pos == EVENT_BUF_SIZE) |
| self->event_write_pos = 0; |
| } |
| |
| static void wl_keyboard_enter(void *data, struct wl_keyboard *wl_keyboard, |
| uint32_t serial, struct wl_surface *surface, |
| struct wl_array *keys) |
| { |
| (void)wl_keyboard; |
| (void)serial; |
| (void)surface; |
| struct dwl_context *context = (struct dwl_context*)data; |
| struct input *input = &context->input; |
| uint32_t *key; |
| struct dwl_event event = {0}; |
| input->keyboard_input_surface = surface; |
| wl_array_for_each(key, keys) { |
| event.surface_descriptor = input->keyboard_input_surface; |
| event.event_type = DWL_EVENT_TYPE_KEYBOARD_KEY; |
| event.params[0] = (int32_t)*key; |
| event.params[1] = DWL_KEYBOARD_KEY_STATE_PRESSED; |
| dwl_context_push_event(context, &event); |
| } |
| } |
| |
| static void wl_keyboard_key(void *data, struct wl_keyboard *wl_keyboard, |
| uint32_t serial, uint32_t time, uint32_t key, |
| uint32_t state) |
| { |
| struct dwl_context *context = (struct dwl_context*)data; |
| struct input *input = &context->input; |
| (void)wl_keyboard; |
| (void)serial; |
| (void)time; |
| struct dwl_event event = {0}; |
| event.surface_descriptor = input->keyboard_input_surface; |
| event.event_type = DWL_EVENT_TYPE_KEYBOARD_KEY; |
| event.params[0] = (int32_t)key; |
| event.params[1] = state; |
| dwl_context_push_event(context, &event); |
| } |
| |
| static void wl_keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, |
| uint32_t serial, struct wl_surface *surface) |
| { |
| struct dwl_context *context = (struct dwl_context*)data; |
| struct input *input = &context->input; |
| struct dwl_event event = {0}; |
| (void)wl_keyboard; |
| (void)serial; |
| (void)surface; |
| |
| event.surface_descriptor = input->keyboard_input_surface; |
| event.event_type = DWL_EVENT_TYPE_KEYBOARD_LEAVE; |
| dwl_context_push_event(context, &event); |
| |
| input->keyboard_input_surface = NULL; |
| } |
| |
| static void wl_keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, |
| uint32_t serial, uint32_t mods_depressed, |
| uint32_t mods_latched, uint32_t mods_locked, |
| uint32_t group) |
| { |
| (void)data; |
| (void)wl_keyboard; |
| (void)serial; |
| (void)mods_depressed; |
| (void)mods_latched; |
| (void)mods_locked; |
| (void)group; |
| } |
| |
| static void wl_keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard, |
| int32_t rate, int32_t delay) |
| { |
| (void)data; |
| (void)wl_keyboard; |
| (void)rate; |
| (void)delay; |
| } |
| |
| static const struct wl_keyboard_listener wl_keyboard_listener = { |
| .keymap = wl_keyboard_keymap, |
| .enter = wl_keyboard_enter, |
| .leave = wl_keyboard_leave, |
| .key = wl_keyboard_key, |
| .modifiers = wl_keyboard_modifiers, |
| .repeat_info = wl_keyboard_repeat_info, |
| }; |
| |
| static void pointer_enter_handler(void *data, struct wl_pointer *wl_pointer, |
| uint32_t serial, struct wl_surface *surface, |
| wl_fixed_t x, wl_fixed_t y) |
| { |
| struct dwl_context *context = (struct dwl_context*)data; |
| struct input *input = &context->input; |
| (void)wl_pointer; |
| (void)serial; |
| |
| input->pointer_input_surface = surface; |
| input->pointer_x = wl_fixed_to_int(x); |
| input->pointer_y = wl_fixed_to_int(y); |
| } |
| |
| static void pointer_leave_handler(void *data, struct wl_pointer *wl_pointer, |
| uint32_t serial, struct wl_surface *surface) |
| { |
| struct dwl_context *context = (struct dwl_context*)data; |
| struct input *input = &context->input; |
| (void)wl_pointer; |
| (void)serial; |
| (void)surface; |
| |
| input->pointer_input_surface = NULL; |
| } |
| |
| static void pointer_motion_handler(void *data, struct wl_pointer *wl_pointer, |
| uint32_t time, wl_fixed_t x, wl_fixed_t y) |
| { |
| struct dwl_context *context = (struct dwl_context*)data; |
| struct input *input = &context->input; |
| struct dwl_event event = {0}; |
| (void)wl_pointer; |
| (void)time; |
| |
| input->pointer_x = wl_fixed_to_int(x); |
| input->pointer_y = wl_fixed_to_int(y); |
| if (input->pointer_lbutton_state) { |
| event.surface_descriptor = input->pointer_input_surface; |
| event.event_type = DWL_EVENT_TYPE_TOUCH_MOTION; |
| event.params[0] = input->pointer_x; |
| event.params[1] = input->pointer_y; |
| dwl_context_push_event(context, &event); |
| } |
| } |
| |
| static void pointer_button_handler(void *data, struct wl_pointer *wl_pointer, |
| uint32_t serial, uint32_t time, uint32_t button, |
| uint32_t state) |
| { |
| struct dwl_context *context = (struct dwl_context*)data; |
| struct input *input = &context->input; |
| (void)wl_pointer; |
| (void)time; |
| (void)serial; |
| |
| // we track only the left mouse button since we emulate a single touch device |
| if (button == BTN_LEFT) { |
| input->pointer_lbutton_state = state != 0; |
| struct dwl_event event = {0}; |
| event.surface_descriptor = input->pointer_input_surface; |
| event.event_type = (state != 0)? |
| DWL_EVENT_TYPE_TOUCH_DOWN:DWL_EVENT_TYPE_TOUCH_UP; |
| event.params[0] = input->pointer_x; |
| event.params[1] = input->pointer_y; |
| dwl_context_push_event(context, &event); |
| } |
| } |
| |
| static void wl_pointer_frame(void *data, struct wl_pointer *wl_pointer) |
| { |
| (void)data; |
| (void)wl_pointer; |
| } |
| |
| static void pointer_axis_handler(void *data, struct wl_pointer *wl_pointer, |
| uint32_t time, uint32_t axis, wl_fixed_t value) |
| { |
| (void)data; |
| (void)wl_pointer; |
| (void)time; |
| (void)axis; |
| (void)value; |
| } |
| |
| static void wl_pointer_axis_source(void *data, struct wl_pointer *wl_pointer, |
| uint32_t axis_source) |
| { |
| (void)data; |
| (void)wl_pointer; |
| (void)axis_source; |
| } |
| |
| static void wl_pointer_axis_stop(void *data, struct wl_pointer *wl_pointer, |
| uint32_t time, uint32_t axis) |
| { |
| (void)data; |
| (void)wl_pointer; |
| (void)time; |
| (void)axis; |
| } |
| |
| static void wl_pointer_axis_discrete(void *data, struct wl_pointer *wl_pointer, |
| uint32_t axis, int32_t discrete) |
| { |
| (void)data; |
| (void)wl_pointer; |
| (void)axis; |
| (void)discrete; |
| } |
| |
| const struct wl_pointer_listener wl_pointer_listener = { |
| .enter = pointer_enter_handler, |
| .leave = pointer_leave_handler, |
| .motion = pointer_motion_handler, |
| .button = pointer_button_handler, |
| .axis = pointer_axis_handler, |
| .frame = wl_pointer_frame, |
| .axis_source = wl_pointer_axis_source, |
| .axis_stop = wl_pointer_axis_stop, |
| .axis_discrete = wl_pointer_axis_discrete, |
| }; |
| |
| static void wl_seat_capabilities(void *data, struct wl_seat *wl_seat, |
| uint32_t capabilities) |
| { |
| struct dwl_context *context = (struct dwl_context*)data; |
| struct input *input = &context->input; |
| bool have_keyboard = capabilities & WL_SEAT_CAPABILITY_KEYBOARD; |
| bool have_pointer = capabilities & WL_SEAT_CAPABILITY_POINTER; |
| |
| if (have_keyboard && input->wl_keyboard == NULL) { |
| input->wl_keyboard = wl_seat_get_keyboard(wl_seat); |
| wl_keyboard_add_listener(input->wl_keyboard, &wl_keyboard_listener, context); |
| } else if (!have_keyboard && input->wl_keyboard != NULL) { |
| wl_keyboard_release(input->wl_keyboard); |
| input->wl_keyboard = NULL; |
| } |
| |
| if (have_pointer && input->wl_pointer == NULL) { |
| input->wl_pointer = wl_seat_get_pointer(wl_seat); |
| wl_pointer_add_listener(input->wl_pointer, &wl_pointer_listener, context); |
| } else if (!have_pointer && input->wl_pointer != NULL) { |
| wl_pointer_release(input->wl_pointer); |
| input->wl_pointer = NULL; |
| } |
| } |
| |
| static void wl_seat_name(void *data, struct wl_seat *wl_seat, const char *name) |
| { |
| (void)data; |
| (void)wl_seat; |
| (void)name; |
| } |
| |
| static const struct wl_seat_listener wl_seat_listener = { |
| .capabilities = wl_seat_capabilities, |
| .name = wl_seat_name, |
| }; |
| |
| static void dwl_context_output_add(struct dwl_context *context, |
| struct wl_output *wl_output, uint32_t id) |
| { |
| size_t i; |
| struct output *output; |
| outputs_for_each(context, i, output) |
| { |
| if (output->output == NULL) { |
| context->output_added = true; |
| output->id = id; |
| output->output = wl_output; |
| output->context = context; |
| output->current_scale = 1000; |
| output->device_scale_factor = 1000; |
| // This is a fun little hack from reveman. The idea is |
| // that the first display will be internal and never get |
| // removed. |
| output->internal = i == 0; |
| wl_output_add_listener(output->output, &output_listener, |
| output); |
| return; |
| } |
| } |
| } |
| |
| static void dwl_context_output_remove_destroy(struct dwl_context *context, |
| uint32_t id) |
| { |
| size_t i; |
| struct output *output; |
| outputs_for_each(context, i, output) |
| { |
| if (output->id == id) { |
| if (output->aura_output) |
| zaura_output_destroy(output->aura_output); |
| wl_output_destroy(output->output); |
| memset(output, 0, sizeof(struct output)); |
| return; |
| } |
| } |
| } |
| |
| static void dwl_context_output_get_aura(struct dwl_context *context) |
| { |
| if (!context->ifaces.aura) |
| return; |
| |
| size_t i; |
| struct output *output; |
| outputs_for_each(context, i, output) |
| { |
| if (output->output != NULL && output->aura_output == NULL) { |
| output->aura_output = zaura_shell_get_aura_output( |
| context->ifaces.aura, output->output); |
| zaura_output_add_listener( |
| output->aura_output, &aura_output_listener, output); |
| } |
| } |
| } |
| |
| static void registry_global(void *data, struct wl_registry *registry, |
| uint32_t id, const char *interface, |
| uint32_t version) |
| { |
| (void)version; |
| struct interfaces *ifaces = (struct interfaces *)data; |
| if (strcmp(interface, wl_compositor_interface.name) == 0) { |
| ifaces->compositor = (struct wl_compositor *)wl_registry_bind( |
| registry, id, &wl_compositor_interface, 3); |
| } else if (strcmp(interface, wl_subcompositor_interface.name) == 0) { |
| ifaces->subcompositor = |
| (struct wl_subcompositor *)wl_registry_bind( |
| registry, id, &wl_subcompositor_interface, 1); |
| } else if (strcmp(interface, wl_shm_interface.name) == 0) { |
| ifaces->shm = (struct wl_shm *)wl_registry_bind( |
| registry, id, &wl_shm_interface, 1); |
| } else if (strcmp(interface, wl_seat_interface.name) == 0) { |
| ifaces->seat = (struct wl_seat *)wl_registry_bind( |
| registry, id, &wl_seat_interface, 5); |
| wl_seat_add_listener(ifaces->seat, &wl_seat_listener, ifaces->context); |
| } else if (strcmp(interface, wl_output_interface.name) == 0) { |
| struct wl_output *output = (struct wl_output *)wl_registry_bind( |
| registry, id, &wl_output_interface, 2); |
| dwl_context_output_add(ifaces->context, output, id); |
| } else if (strcmp(interface, "zaura_shell") == 0 && version >= 6) { |
| ifaces->aura = (struct zaura_shell *)wl_registry_bind( |
| registry, id, &zaura_shell_interface, 6); |
| } else if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0) { |
| ifaces->linux_dmabuf = |
| (struct zwp_linux_dmabuf_v1 *)wl_registry_bind( |
| registry, id, &zwp_linux_dmabuf_v1_interface, 1); |
| } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { |
| ifaces->xdg_wm_base = (struct xdg_wm_base *)wl_registry_bind( |
| registry, id, &xdg_wm_base_interface, 1); |
| xdg_wm_base_add_listener(ifaces->xdg_wm_base, &xdg_wm_base_listener, |
| NULL); |
| } else if (strcmp(interface, "wp_viewporter") == 0) { |
| ifaces->viewporter = (struct wp_viewporter *)wl_registry_bind( |
| registry, id, &wp_viewporter_interface, 1); |
| } else if (strcmp(interface, "wp_virtio_gpu_metadata_v1") == 0) { |
| ifaces->virtio_gpu_metadata = |
| (struct wp_virtio_gpu_metadata_v1 *)wl_registry_bind( |
| registry, id, &wp_virtio_gpu_metadata_v1_interface, 1); |
| } |
| } |
| |
| static void global_remove(void *data, struct wl_registry *registry, uint32_t id) |
| { |
| (void)registry; |
| |
| struct interfaces *ifaces = (struct interfaces *)data; |
| // If the ID matches any output, this will remove it. Otherwise, this is |
| // a no-op. |
| dwl_context_output_remove_destroy(ifaces->context, id); |
| |
| if (ifaces->aura && |
| wl_proxy_get_id((struct wl_proxy *)ifaces->aura) == id) { |
| zaura_shell_destroy(ifaces->aura); |
| ifaces->aura = NULL; |
| } |
| |
| // TODO(zachr): deal with the removal of some of the required |
| // interfaces. |
| } |
| |
| static const struct wl_registry_listener registry_listener = { |
| .global = registry_global, .global_remove = global_remove}; |
| |
| static void toplevel_configure(void *data, |
| struct xdg_toplevel *xdg_toplevel, |
| int32_t width, int32_t height, |
| struct wl_array *states) |
| { |
| (void)data; |
| (void)xdg_toplevel; |
| (void)width; |
| (void)height; |
| (void)states; |
| } |
| |
| static void toplevel_close(void *data, |
| struct xdg_toplevel *xdg_toplevel) |
| { |
| (void)xdg_toplevel; |
| struct dwl_surface *surface = (struct dwl_surface *)data; |
| surface->close_requested = true; |
| } |
| |
| static const struct xdg_toplevel_listener toplevel_listener = { |
| .configure = toplevel_configure, .close = toplevel_close}; |
| |
| static void xdg_surface_configure_handler(void *data, |
| struct xdg_surface *xdg_surface, |
| uint32_t serial) |
| { |
| (void)data; |
| xdg_surface_ack_configure(xdg_surface, serial); |
| } |
| |
| static const struct xdg_surface_listener xdg_surface_listener = { |
| .configure = xdg_surface_configure_handler |
| }; |
| |
| static void surface_enter(void *data, struct wl_surface *wl_surface, |
| struct wl_output *wl_output) |
| { |
| struct dwl_surface *surface = (struct dwl_surface *)data; |
| |
| struct output *output = |
| (struct output *)wl_output_get_user_data(wl_output); |
| |
| surface->scale = (output->device_scale_factor / 1000.0) * |
| (output->current_scale / 1000.0); |
| |
| if (surface->viewport) { |
| wp_viewport_set_destination( |
| surface->viewport, ceil(surface->width / surface->scale), |
| ceil(surface->height / surface->scale)); |
| } else { |
| wl_surface_set_buffer_scale(wl_surface, surface->scale); |
| } |
| |
| wl_surface_commit(wl_surface); |
| } |
| |
| static void surface_leave(void *data, struct wl_surface *wl_surface, |
| struct wl_output *output) |
| { |
| (void)data; |
| (void)wl_surface; |
| (void)output; |
| } |
| |
| static const struct wl_surface_listener surface_listener = { |
| .enter = surface_enter, .leave = surface_leave}; |
| |
| static void error_callback_stub(const char *message) { |
| (void)message; |
| } |
| |
| struct dwl_context *dwl_context_new(dwl_error_callback_type error_callback) |
| { |
| struct dwl_context *ctx = calloc(1, sizeof(struct dwl_context)); |
| if (!ctx) |
| return NULL; |
| ctx->ifaces.context = ctx; |
| ctx->error_callback = error_callback ? error_callback : error_callback_stub; |
| return ctx; |
| } |
| |
| void dwl_context_destroy(struct dwl_context **self) |
| { |
| if ((*self)->display) |
| wl_display_disconnect((*self)->display); |
| free(*self); |
| *self = NULL; |
| } |
| |
| bool dwl_context_setup(struct dwl_context *self, const char *socket_path) |
| { |
| struct wl_display *display = wl_display_connect(socket_path); |
| if (!display) { |
| self->error_callback("failed to connect to display"); |
| return false; |
| } |
| self->display = display; |
| wl_display_set_user_data(display, self); |
| |
| struct wl_registry *registry = wl_display_get_registry(display); |
| if (!registry) { |
| self->error_callback("failed to get registry"); |
| goto fail; |
| } |
| |
| struct interfaces *ifaces = &self->ifaces; |
| wl_registry_add_listener(registry, ®istry_listener, ifaces); |
| wl_display_roundtrip(display); |
| dwl_context_output_get_aura(self); |
| |
| if (!ifaces->shm) { |
| self->error_callback("missing interface shm"); |
| goto fail; |
| } |
| if (!ifaces->compositor) { |
| self->error_callback("missing interface compositor"); |
| goto fail; |
| } |
| if (!ifaces->subcompositor) { |
| self->error_callback("missing interface subcompositor"); |
| goto fail; |
| } |
| if (!ifaces->seat) { |
| self->error_callback("missing interface seat"); |
| goto fail; |
| } |
| if (!ifaces->linux_dmabuf) { |
| self->error_callback("missing interface linux_dmabuf"); |
| goto fail; |
| } |
| if (!ifaces->xdg_wm_base) { |
| self->error_callback("missing interface xdg_wm_base"); |
| goto fail; |
| } |
| |
| return true; |
| |
| fail: |
| wl_display_disconnect(display); |
| self->display = NULL; |
| return false; |
| } |
| |
| int dwl_context_fd(struct dwl_context *self) |
| { |
| return wl_display_get_fd(self->display); |
| } |
| |
| void dwl_context_dispatch(struct dwl_context *self) |
| { |
| wl_display_dispatch(self->display); |
| if (self->output_added) { |
| self->output_added = false; |
| dwl_context_output_get_aura(self); |
| wl_display_roundtrip(self->display); |
| } |
| } |
| |
| static void linux_buffer_created( |
| void *data, struct zwp_linux_buffer_params_v1 *zwp_linux_buffer_params_v1, |
| struct wl_buffer *buffer) |
| { |
| (void)zwp_linux_buffer_params_v1; |
| struct dwl_dmabuf *dmabuf = (struct dwl_dmabuf *)data; |
| dmabuf->buffer = buffer; |
| } |
| |
| static void linux_buffer_failed( |
| void *data, struct zwp_linux_buffer_params_v1 *zwp_linux_buffer_params_v1) |
| { |
| (void)data; |
| (void)zwp_linux_buffer_params_v1; |
| } |
| |
| static const struct zwp_linux_buffer_params_v1_listener linux_buffer_listener = |
| {.created = linux_buffer_created, .failed = linux_buffer_failed}; |
| |
| static void dmabuf_buffer_release(void *data, struct wl_buffer *buffer) |
| { |
| struct dwl_dmabuf *dmabuf = (struct dwl_dmabuf *)data; |
| (void)buffer; |
| |
| dmabuf->in_use = false; |
| } |
| |
| static const struct wl_buffer_listener dmabuf_buffer_listener = { |
| .release = dmabuf_buffer_release}; |
| |
| static bool dwl_context_add_dmabuf(struct dwl_context *self, |
| struct dwl_dmabuf *dmabuf) |
| { |
| size_t i; |
| for (i = 0; i < MAX_BUFFER_COUNT; i++) { |
| if (!self->dmabufs[i]) { |
| self->dmabufs[i] = dmabuf; |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| static void dwl_context_remove_dmabuf(struct dwl_context *self, |
| uint32_t import_id) |
| { |
| size_t i; |
| for (i = 0; i < MAX_BUFFER_COUNT; i++) { |
| if (self->dmabufs[i] && |
| self->dmabufs[i]->import_id == import_id) { |
| self->dmabufs[i] = NULL; |
| } |
| } |
| } |
| |
| static struct dwl_dmabuf *dwl_context_get_dmabuf(struct dwl_context *self, |
| uint32_t import_id) |
| { |
| size_t i; |
| for (i = 0; i < MAX_BUFFER_COUNT; i++) { |
| if (self->dmabufs[i] && |
| self->dmabufs[i]->import_id == import_id) { |
| return self->dmabufs[i]; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| struct dwl_dmabuf *dwl_context_dmabuf_new(struct dwl_context *self, |
| uint32_t import_id, |
| int fd, uint32_t offset, |
| uint32_t stride, uint64_t modifier, |
| uint32_t width, uint32_t height, |
| uint32_t fourcc) |
| { |
| struct dwl_dmabuf *dmabuf = calloc(1, sizeof(struct dwl_dmabuf)); |
| if (!dmabuf) { |
| self->error_callback("failed to allocate dwl_dmabuf"); |
| return NULL; |
| } |
| dmabuf->width = width; |
| dmabuf->height = height; |
| |
| struct zwp_linux_buffer_params_v1 *params = |
| zwp_linux_dmabuf_v1_create_params(self->ifaces.linux_dmabuf); |
| if (!params) { |
| self->error_callback("failed to allocate zwp_linux_buffer_params_v1"); |
| free(dmabuf); |
| return NULL; |
| } |
| |
| zwp_linux_buffer_params_v1_add_listener(params, &linux_buffer_listener, |
| dmabuf); |
| zwp_linux_buffer_params_v1_add(params, fd, 0 /* plane_idx */, offset, |
| stride, modifier >> 32, |
| (uint32_t)modifier); |
| zwp_linux_buffer_params_v1_create(params, width, height, fourcc, 0); |
| wl_display_roundtrip(self->display); |
| zwp_linux_buffer_params_v1_destroy(params); |
| |
| if (!dmabuf->buffer) { |
| self->error_callback("failed to get wl_buffer for dmabuf"); |
| free(dmabuf); |
| return NULL; |
| } |
| |
| wl_buffer_add_listener(dmabuf->buffer, &dmabuf_buffer_listener, dmabuf); |
| |
| dmabuf->import_id = import_id; |
| dmabuf->context = self; |
| if (!dwl_context_add_dmabuf(self, dmabuf)) { |
| self->error_callback("failed to add dmabuf to context"); |
| free(dmabuf); |
| return NULL; |
| } |
| |
| return dmabuf; |
| } |
| |
| void dwl_dmabuf_destroy(struct dwl_dmabuf **self) |
| { |
| dwl_context_remove_dmabuf((*self)->context, (*self)->import_id); |
| wl_buffer_destroy((*self)->buffer); |
| free(*self); |
| *self = NULL; |
| } |
| |
| static void surface_buffer_release(void *data, struct wl_buffer *buffer) |
| { |
| struct dwl_surface *surface = (struct dwl_surface *)data; |
| (void)buffer; |
| |
| size_t i; |
| for (i = 0; i < surface->buffer_count; i++) { |
| if (buffer == surface->buffers[i]) { |
| surface->buffer_use_bit_mask &= ~(1 << i); |
| break; |
| } |
| } |
| } |
| |
| static const struct wl_buffer_listener surface_buffer_listener = { |
| .release = surface_buffer_release}; |
| |
| static struct dwl_surface *dwl_context_get_surface(struct dwl_context *self, |
| uint32_t surface_id) |
| { |
| size_t i; |
| for (i = 0; i < MAX_BUFFER_COUNT; i++) { |
| if (self->surfaces[i] && |
| self->surfaces[i]->surface_id == surface_id) { |
| return self->surfaces[i]; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static bool dwl_context_add_surface(struct dwl_context *self, |
| struct dwl_surface *surface) |
| { |
| size_t i; |
| for (i = 0; i < MAX_BUFFER_COUNT; i++) { |
| if (!self->surfaces[i]) { |
| self->surfaces[i] = surface; |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| static void dwl_context_remove_surface(struct dwl_context *self, |
| uint32_t surface_id) |
| { |
| size_t i; |
| for (i = 0; i < MAX_BUFFER_COUNT; i++) { |
| if (self->surfaces[i] && |
| self->surfaces[i]->surface_id == surface_id) { |
| self->surfaces[i] = NULL; |
| } |
| } |
| } |
| |
| struct dwl_surface *dwl_context_surface_new(struct dwl_context *self, |
| uint32_t parent_id, |
| uint32_t surface_id, |
| int shm_fd, size_t shm_size, |
| size_t buffer_size, uint32_t width, |
| uint32_t height, uint32_t stride, |
| uint32_t flags) |
| { |
| if (buffer_size == 0) |
| return NULL; |
| |
| size_t buffer_count = shm_size / buffer_size; |
| if (buffer_count == 0) |
| return NULL; |
| if (buffer_count > MAX_BUFFER_COUNT) |
| return NULL; |
| |
| struct dwl_surface *disp_surface = |
| calloc(1, sizeof(struct dwl_surface) + |
| sizeof(struct wl_buffer *) * buffer_count); |
| if (!disp_surface) |
| return NULL; |
| |
| disp_surface->context = self; |
| disp_surface->width = width; |
| disp_surface->height = height; |
| disp_surface->scale = DEFAULT_SCALE; |
| disp_surface->buffer_count = buffer_count; |
| |
| struct wl_shm_pool *shm_pool = |
| wl_shm_create_pool(self->ifaces.shm, shm_fd, shm_size); |
| if (!shm_pool) { |
| self->error_callback("failed to make shm pool"); |
| goto fail; |
| } |
| |
| size_t i; |
| uint32_t format = (flags & DWL_SURFACE_FLAG_HAS_ALPHA)? |
| WL_SHM_FORMAT_ARGB8888:WL_SHM_FORMAT_XRGB8888; |
| |
| for (i = 0; i < buffer_count; i++) { |
| struct wl_buffer *buffer = wl_shm_pool_create_buffer( |
| shm_pool, buffer_size * i, width, height, stride, format); |
| if (!buffer) { |
| self->error_callback("failed to create buffer"); |
| goto fail; |
| } |
| disp_surface->buffers[i] = buffer; |
| } |
| |
| for (i = 0; i < buffer_count; i++) |
| wl_buffer_add_listener(disp_surface->buffers[i], |
| &surface_buffer_listener, disp_surface); |
| |
| disp_surface->wl_surface = |
| wl_compositor_create_surface(self->ifaces.compositor); |
| if (!disp_surface->wl_surface) { |
| self->error_callback("failed to make surface"); |
| goto fail; |
| } |
| |
| wl_surface_add_listener(disp_surface->wl_surface, &surface_listener, |
| disp_surface); |
| |
| struct wl_region *region = wl_compositor_create_region(self->ifaces.compositor); |
| if (!region) { |
| self->error_callback("failed to make region"); |
| goto fail; |
| } |
| |
| bool receive_input = (flags & DWL_SURFACE_FLAG_RECEIVE_INPUT); |
| if (receive_input) { |
| wl_region_add(region, 0, 0, width, height); |
| } else { |
| // We have to add an empty region because NULL doesn't work |
| wl_region_add(region, 0, 0, 0, 0); |
| } |
| wl_surface_set_input_region(disp_surface->wl_surface, region); |
| wl_surface_set_opaque_region(disp_surface->wl_surface, region); |
| wl_region_destroy(region); |
| |
| if (!parent_id) { |
| disp_surface->xdg_surface = xdg_wm_base_get_xdg_surface( |
| self->ifaces.xdg_wm_base, disp_surface->wl_surface); |
| if (!disp_surface->xdg_surface) { |
| self->error_callback("failed to make xdg shell surface"); |
| goto fail; |
| } |
| |
| disp_surface->xdg_toplevel = |
| xdg_surface_get_toplevel(disp_surface->xdg_surface); |
| if (!disp_surface->xdg_toplevel) { |
| self->error_callback("failed to make toplevel xdg shell surface"); |
| goto fail; |
| } |
| xdg_toplevel_set_title(disp_surface->xdg_toplevel, "crosvm"); |
| xdg_toplevel_add_listener(disp_surface->xdg_toplevel, |
| &toplevel_listener, disp_surface); |
| |
| xdg_surface_add_listener(disp_surface->xdg_surface, |
| &xdg_surface_listener, |
| NULL); |
| if (self->ifaces.aura) { |
| disp_surface->aura = zaura_shell_get_aura_surface( |
| self->ifaces.aura, disp_surface->wl_surface); |
| if (!disp_surface->aura) { |
| self->error_callback("failed to make aura surface"); |
| goto fail; |
| } |
| zaura_surface_set_frame( |
| disp_surface->aura, |
| ZAURA_SURFACE_FRAME_TYPE_NORMAL); |
| } |
| |
| // signal that the surface is ready to be configured |
| wl_surface_commit(disp_surface->wl_surface); |
| |
| // wait for the surface to be configured |
| wl_display_roundtrip(self->display); |
| } else { |
| struct dwl_surface *parent_surface = |
| dwl_context_get_surface(self, parent_id); |
| |
| if (!parent_surface) { |
| self->error_callback("failed to find parent_surface"); |
| goto fail; |
| } |
| |
| disp_surface->subsurface = wl_subcompositor_get_subsurface( |
| self->ifaces.subcompositor, disp_surface->wl_surface, |
| parent_surface->wl_surface); |
| if (!disp_surface->subsurface) { |
| self->error_callback("failed to make subsurface"); |
| goto fail; |
| } |
| wl_subsurface_set_desync(disp_surface->subsurface); |
| } |
| |
| if (self->ifaces.viewporter) { |
| disp_surface->viewport = wp_viewporter_get_viewport( |
| self->ifaces.viewporter, disp_surface->wl_surface); |
| if (!disp_surface->viewport) { |
| self->error_callback("failed to make surface viewport"); |
| goto fail; |
| } |
| } |
| |
| if (self->ifaces.virtio_gpu_metadata) { |
| disp_surface->virtio_gpu_surface_metadata = |
| wp_virtio_gpu_metadata_v1_get_surface_metadata( |
| self->ifaces.virtio_gpu_metadata, disp_surface->wl_surface); |
| if (!disp_surface->virtio_gpu_surface_metadata) { |
| self->error_callback("failed to make surface virtio surface metadata"); |
| goto fail; |
| } |
| } |
| |
| wl_surface_attach(disp_surface->wl_surface, disp_surface->buffers[0], 0, |
| 0); |
| wl_surface_damage(disp_surface->wl_surface, 0, 0, width, height); |
| wl_shm_pool_destroy(shm_pool); |
| |
| // Needed to get outputs before iterating them. |
| wl_display_roundtrip(self->display); |
| |
| // Assuming that this surface will enter the internal output initially, |
| // trigger a surface enter for that output before doing the first |
| // surface commit. THis is to avoid unpleasant artifacts when the |
| // surface first appears. |
| struct output *output; |
| outputs_for_each(self, i, output) |
| { |
| if (output->internal) { |
| surface_enter(disp_surface, disp_surface->wl_surface, |
| output->output); |
| } |
| } |
| |
| wl_surface_commit(disp_surface->wl_surface); |
| wl_display_flush(self->display); |
| |
| disp_surface->surface_id = surface_id; |
| if (!dwl_context_add_surface(self, disp_surface)) { |
| self->error_callback("failed to add surface to context"); |
| goto fail; |
| } |
| |
| return disp_surface; |
| fail: |
| if (disp_surface->virtio_gpu_surface_metadata) |
| wp_virtio_gpu_surface_metadata_v1_destroy( |
| disp_surface->virtio_gpu_surface_metadata); |
| if (disp_surface->viewport) |
| wp_viewport_destroy(disp_surface->viewport); |
| if (disp_surface->subsurface) |
| wl_subsurface_destroy(disp_surface->subsurface); |
| if (disp_surface->xdg_toplevel) |
| xdg_toplevel_destroy(disp_surface->xdg_toplevel); |
| if (disp_surface->xdg_surface) |
| xdg_surface_destroy(disp_surface->xdg_surface); |
| if (disp_surface->aura) |
| zaura_surface_destroy(disp_surface->aura); |
| if (disp_surface->wl_surface) |
| wl_surface_destroy(disp_surface->wl_surface); |
| for (i = 0; i < buffer_count; i++) |
| if (disp_surface->buffers[i]) |
| wl_buffer_destroy(disp_surface->buffers[i]); |
| if (shm_pool) |
| wl_shm_pool_destroy(shm_pool); |
| free(disp_surface); |
| return NULL; |
| } |
| |
| void dwl_surface_destroy(struct dwl_surface **self) |
| { |
| size_t i; |
| |
| dwl_context_remove_surface((*self)->context, (*self)->surface_id); |
| if ((*self)->virtio_gpu_surface_metadata) |
| wp_virtio_gpu_surface_metadata_v1_destroy( |
| (*self)->virtio_gpu_surface_metadata); |
| if ((*self)->viewport) |
| wp_viewport_destroy((*self)->viewport); |
| if ((*self)->subsurface) |
| wl_subsurface_destroy((*self)->subsurface); |
| if ((*self)->xdg_toplevel) |
| xdg_toplevel_destroy((*self)->xdg_toplevel); |
| if ((*self)->xdg_surface) |
| xdg_surface_destroy((*self)->xdg_surface); |
| if ((*self)->aura) |
| zaura_surface_destroy((*self)->aura); |
| if ((*self)->wl_surface) |
| wl_surface_destroy((*self)->wl_surface); |
| for (i = 0; i < (*self)->buffer_count; i++) |
| wl_buffer_destroy((*self)->buffers[i]); |
| wl_display_flush((*self)->context->display); |
| free(*self); |
| *self = NULL; |
| } |
| |
| void dwl_surface_commit(struct dwl_surface *self) |
| { |
| // It is possible that we are committing frames faster than the |
| // compositor can put them on the screen. This may result in dropped |
| // frames, but this is acceptable considering there is no good way to |
| // apply back pressure to the guest gpu driver right now. The intention |
| // of this module is to help bootstrap gpu support, so it does not have |
| // to have artifact free rendering. |
| wl_surface_commit(self->wl_surface); |
| wl_display_flush(self->context->display); |
| } |
| |
| bool dwl_surface_buffer_in_use(struct dwl_surface *self, size_t buffer_index) |
| { |
| return (self->buffer_use_bit_mask & (1 << buffer_index)) != 0; |
| } |
| |
| void dwl_surface_flip(struct dwl_surface *self, size_t buffer_index) |
| { |
| if (buffer_index >= self->buffer_count) |
| return; |
| wl_surface_attach(self->wl_surface, self->buffers[buffer_index], 0, 0); |
| wl_surface_damage(self->wl_surface, 0, 0, self->width, self->height); |
| dwl_surface_commit(self); |
| self->buffer_use_bit_mask |= 1 << buffer_index; |
| } |
| |
| void dwl_surface_flip_to(struct dwl_surface *self, uint32_t import_id) |
| { |
| // Surface and dmabuf have to exist in same context. |
| struct dwl_dmabuf *dmabuf = dwl_context_get_dmabuf(self->context, |
| import_id); |
| if (!dmabuf) |
| return; |
| |
| if (self->width != dmabuf->width || self->height != dmabuf->height) |
| return; |
| wl_surface_attach(self->wl_surface, dmabuf->buffer, 0, 0); |
| wl_surface_damage(self->wl_surface, 0, 0, self->width, self->height); |
| dwl_surface_commit(self); |
| dmabuf->in_use = true; |
| } |
| |
| bool dwl_surface_close_requested(const struct dwl_surface *self) |
| { |
| return self->close_requested; |
| } |
| |
| void dwl_surface_set_position(struct dwl_surface *self, uint32_t x, uint32_t y) |
| { |
| if (self->subsurface) { |
| wl_subsurface_set_position(self->subsurface, x / self->scale, |
| y / self->scale); |
| wl_surface_commit(self->wl_surface); |
| wl_display_flush(self->context->display); |
| } |
| } |
| |
| const void* dwl_surface_descriptor(const struct dwl_surface *self) |
| { |
| return self->wl_surface; |
| } |
| |
| bool dwl_context_pending_events(const struct dwl_context *self) |
| { |
| if (self->event_write_pos == self->event_read_pos) |
| return false; |
| |
| return true; |
| } |
| |
| void dwl_context_next_event(struct dwl_context *self, struct dwl_event *event) |
| { |
| memcpy(event, self->event_cbuf + self->event_read_pos, |
| sizeof(struct dwl_event)); |
| |
| if (++self->event_read_pos == EVENT_BUF_SIZE) |
| self->event_read_pos = 0; |
| } |
| |
| void dwl_surface_set_scanout_id(struct dwl_surface *self, uint32_t scanout_id) |
| { |
| if (self->virtio_gpu_surface_metadata) { |
| wp_virtio_gpu_surface_metadata_v1_set_scanout_id( |
| self->virtio_gpu_surface_metadata, scanout_id); |
| } |
| } |