/* * Copyright (C) 2023 Mikhail Burakov. This file is part of receiver. * * receiver is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * receiver is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with receiver. If not, see . */ #include "window.h" #include #include #include #include #include #include #include "frame.h" #include "linux-dmabuf-unstable-v1.h" #include "pointer-constraints-unstable-v1.h" #include "relative-pointer-unstable-v1.h" #include "toolbox/utils.h" #include "xdg-shell.h" // TODO(mburakov): This would look like shit until Wayland guys finally fix // https://gitlab.freedesktop.org/wayland/wayland/-/issues/160 struct Window { const struct WindowEventHandlers* event_handlers; void* user; struct wl_display* wl_display; struct wl_surface* wl_surface; struct wl_pointer* wl_pointer; struct wl_keyboard* wl_keyboard; struct xdg_wm_base* xdg_wm_base; struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1; struct zwp_pointer_constraints_v1* zwp_pointer_constraints_v1; struct zwp_relative_pointer_manager_v1* zwp_relative_pointer_manager_v1; struct zwp_relative_pointer_v1* zwp_relative_pointer_v1; struct zwp_locked_pointer_v1* zwp_locked_pointer_v1; struct xdg_surface* xdg_surface; struct xdg_toplevel* xdg_toplevel; struct wl_buffer** wl_buffers; }; static void OnWlRegistryGlobal(void* data, struct wl_registry* wl_registry, uint32_t name, const char* interface, uint32_t version) { struct Window* window = data; if (!strcmp(interface, wl_compositor_interface.name)) { struct wl_compositor* compositor = wl_registry_bind(wl_registry, name, &wl_compositor_interface, version); if (!compositor) { LOG("Failed to bind wl_compositor (%s)", strerror(errno)); return; } window->wl_surface = wl_compositor_create_surface(compositor); if (!window->wl_surface) LOG("Failed to create wl_surface (%s)", strerror(errno)); wl_compositor_destroy(compositor); } else if (!strcmp(interface, wl_seat_interface.name)) { struct wl_seat* wl_seat = wl_registry_bind(wl_registry, name, &wl_seat_interface, version); if (!wl_seat) { LOG("Failed to bind wl_seat (%s)", strerror(errno)); return; } window->wl_pointer = wl_seat_get_pointer(wl_seat); if (!window->wl_pointer) LOG("Failed to get wl_pointer (%s)", strerror(errno)); window->wl_keyboard = wl_seat_get_keyboard(wl_seat); if (!window->wl_keyboard) LOG("Failed to get wl_keyboard (%s)", strerror(errno)); wl_seat_destroy(wl_seat); } else if (!strcmp(interface, xdg_wm_base_interface.name)) { window->xdg_wm_base = wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, version); if (!window->xdg_wm_base) LOG("Failed to bind xdg_wm_base (%s)", strerror(errno)); } else if (!strcmp(interface, zwp_linux_dmabuf_v1_interface.name)) { window->zwp_linux_dmabuf_v1 = wl_registry_bind( wl_registry, name, &zwp_linux_dmabuf_v1_interface, version); if (!window->zwp_linux_dmabuf_v1) LOG("Failed to bind zwp_linux_dmabuf_v1 (%s)", strerror(errno)); } else if (!strcmp(interface, zwp_pointer_constraints_v1_interface.name)) { window->zwp_pointer_constraints_v1 = wl_registry_bind( wl_registry, name, &zwp_pointer_constraints_v1_interface, version); if (!window->zwp_pointer_constraints_v1) LOG("Failed to bind zwp_pointer_constraints_v1 (%s)", strerror(errno)); } else if (!strcmp(interface, zwp_relative_pointer_manager_v1_interface.name)) { window->zwp_relative_pointer_manager_v1 = wl_registry_bind( wl_registry, name, &zwp_relative_pointer_manager_v1_interface, version); if (!window->zwp_relative_pointer_manager_v1) { LOG("Failed to bind zwp_relative_pointer_manager_v1 (%s)", strerror(errno)); } } } static void OnWlRegistryGlobalRemove(void* data, struct wl_registry* registry, uint32_t name) { (void)data; (void)registry; (void)name; } static void OnWlPointerEnter(void* data, struct wl_pointer* wl_pointer, uint32_t serial, struct wl_surface* surface, wl_fixed_t surface_x, wl_fixed_t surface_y) { (void)data; (void)surface; (void)surface_x; (void)surface_y; wl_pointer_set_cursor(wl_pointer, serial, NULL, 0, 0); } static void OnWlPointerLeave(void* data, struct wl_pointer* wl_pointer, uint32_t serial, struct wl_surface* surface) { (void)data; (void)wl_pointer; (void)serial; (void)surface; } static void OnWlPointerMotion(void* data, struct wl_pointer* wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { (void)data; (void)wl_pointer; (void)time; (void)surface_x; (void)surface_y; } static void OnWlPointerButton(void* data, struct wl_pointer* wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { (void)wl_pointer; (void)serial; (void)time; struct Window* window = data; window->event_handlers->OnButton(window->user, button, !!state); } static void OnWlPointerAxis(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 OnWlPointerFrame(void* data, struct wl_pointer* wl_pointer) { (void)data; (void)wl_pointer; } static void OnWlPointerAxisSource(void* data, struct wl_pointer* wl_pointer, uint32_t axis_source) { (void)data; (void)wl_pointer; (void)axis_source; } static void OnWlPointerAxisStop(void* data, struct wl_pointer* wl_pointer, uint32_t time, uint32_t axis) { (void)data; (void)wl_pointer; (void)time; (void)axis; } static void OnWlPointerAxisDiscrete(void* data, struct wl_pointer* wl_pointer, uint32_t axis, int32_t discrete) { (void)data; (void)wl_pointer; (void)axis; (void)discrete; } static void OnWlPointerAxisValue120(void* data, struct wl_pointer* wl_pointer, uint32_t axis, int32_t value120) { (void)wl_pointer; if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) { // mburakov: Current code models regular one-wheeled mouse. return; } struct Window* window = data; // TODO(mburakov): Why minus is needed here? window->event_handlers->OnWheel(window->user, -value120 / 120); } static void OnWlKeyboardKeymap(void* data, struct wl_keyboard* wl_keyboard, uint32_t format, int32_t fd, uint32_t size) { (void)data; (void)wl_keyboard; (void)format; (void)fd; (void)size; } static void OnWlKeyboardEnter(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; (void)keys; struct Window* window = data; window->event_handlers->OnFocus(window->user, true); } static void OnWlKeyboardLeave(void* data, struct wl_keyboard* wl_keyboard, uint32_t serial, struct wl_surface* surface) { (void)data; (void)wl_keyboard; (void)serial; (void)surface; struct Window* window = data; window->event_handlers->OnFocus(window->user, false); } static void OnWlKeyboardKey(void* data, struct wl_keyboard* wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { (void)wl_keyboard; (void)serial; (void)time; struct Window* window = data; window->event_handlers->OnKey(window->user, key, !!state); } static void OnWlKeyboardModifiers(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 OnWlKeyboardRepeatInfo(void* data, struct wl_keyboard* wl_keyboard, int32_t rate, int32_t delay) { (void)data; (void)wl_keyboard; (void)rate; (void)delay; } static void OnXdgWmBasePing(void* data, struct xdg_wm_base* xdg_wm_base, uint32_t serial) { (void)data; xdg_wm_base_pong(xdg_wm_base, serial); } static void OnZwpRelativePointerMotion( void* data, struct zwp_relative_pointer_v1* zwp_relative_pointer_v1, uint32_t utime_hi, uint32_t utime_lo, wl_fixed_t dx, wl_fixed_t dy, wl_fixed_t dx_unaccel, wl_fixed_t dy_unaccel) { (void)zwp_relative_pointer_v1; (void)utime_hi; (void)utime_lo; (void)dx; (void)dy; struct Window* window = data; window->event_handlers->OnMove(window->user, wl_fixed_to_int(dx_unaccel), wl_fixed_to_int(dy_unaccel)); } static void OnXdgSurfaceConfigure(void* data, struct xdg_surface* xdg_surface, uint32_t serial) { (void)data; xdg_surface_ack_configure(xdg_surface, serial); } static void OnXdgToplevelConfigure(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 OnXdgToplevelClose(void* data, struct xdg_toplevel* xdg_toplevel) { (void)xdg_toplevel; struct Window* window = data; window->event_handlers->OnClose(window->user); } static void OnXdgToplevelConfigureBounds(void* data, struct xdg_toplevel* xdg_toplevel, int32_t width, int32_t height) { (void)data; (void)xdg_toplevel; (void)width; (void)height; } static void OnXdgToplevelWmCapabilities(void* data, struct xdg_toplevel* xdg_toplevel, struct wl_array* capabilities) { (void)data; (void)xdg_toplevel; (void)capabilities; } struct Window* WindowCreate(const struct WindowEventHandlers* event_handlers, void* user) { struct Window* window = malloc(sizeof(struct Window)); if (!window) { LOG("Failed to allocate window (%s)", strerror(errno)); return NULL; } *window = (struct Window){ .event_handlers = event_handlers, .user = user, }; window->wl_display = wl_display_connect(NULL); if (!window->wl_display) { LOG("Failed to connect wl_display (%s)", strerror(errno)); goto rollback_window; } struct wl_registry* wl_registry = wl_display_get_registry(window->wl_display); if (!wl_registry) { LOG("Failed to get wl_registry (%s)", strerror(errno)); goto rollback_wl_display; } static const struct wl_registry_listener wl_registry_listener = { .global = OnWlRegistryGlobal, .global_remove = OnWlRegistryGlobalRemove, }; if (wl_registry_add_listener(wl_registry, &wl_registry_listener, window)) { LOG("Failed to add wl_registry listener (%s)", strerror(errno)); goto rollback_wl_registry; } if (wl_display_roundtrip(window->wl_display) == -1) { LOG("Failed to roundtrip wl_display (%s)", strerror(errno)); goto rollback_wl_registry; } if (!window->wl_surface || !window->wl_pointer || !window->wl_keyboard || !window->xdg_wm_base || !window->zwp_linux_dmabuf_v1 || !window->zwp_relative_pointer_manager_v1) { LOG("Some wayland objects are missing"); goto rollback_globals; } static const struct wl_pointer_listener wl_pointer_listener = { .enter = OnWlPointerEnter, .leave = OnWlPointerLeave, .motion = OnWlPointerMotion, .button = OnWlPointerButton, .axis = OnWlPointerAxis, .frame = OnWlPointerFrame, .axis_source = OnWlPointerAxisSource, .axis_stop = OnWlPointerAxisStop, .axis_discrete = OnWlPointerAxisDiscrete, .axis_value120 = OnWlPointerAxisValue120, }; if (wl_pointer_add_listener(window->wl_pointer, &wl_pointer_listener, window)) { LOG("Failed to add wl_pointer listener (%s)", strerror(errno)); goto rollback_globals; } static const struct wl_keyboard_listener wl_keyboard_listener = { .keymap = OnWlKeyboardKeymap, .enter = OnWlKeyboardEnter, .leave = OnWlKeyboardLeave, .key = OnWlKeyboardKey, .modifiers = OnWlKeyboardModifiers, .repeat_info = OnWlKeyboardRepeatInfo, }; if (wl_keyboard_add_listener(window->wl_keyboard, &wl_keyboard_listener, window)) { LOG("Failed to add wl_keyboard listener (%s)", strerror(errno)); goto rollback_globals; } static const struct xdg_wm_base_listener xdg_wm_base_listener = { .ping = OnXdgWmBasePing, }; if (xdg_wm_base_add_listener(window->xdg_wm_base, &xdg_wm_base_listener, NULL)) { LOG("Failed to add xdg_wm_base listener (%s)", strerror(errno)); goto rollback_globals; } window->zwp_locked_pointer_v1 = zwp_pointer_constraints_v1_lock_pointer( window->zwp_pointer_constraints_v1, window->wl_surface, window->wl_pointer, NULL, ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT); if (!window->zwp_locked_pointer_v1) { LOG("Failed to lock wl_pointer (%s)", strerror(errno)); goto rollback_globals; } window->zwp_relative_pointer_v1 = zwp_relative_pointer_manager_v1_get_relative_pointer( window->zwp_relative_pointer_manager_v1, window->wl_pointer); if (!window->zwp_relative_pointer_v1) { LOG("Failed to get zwp_relative_pointer_v1 (%s)", strerror(errno)); goto rollback_zwp_locked_pointer_v1; } static const struct zwp_relative_pointer_v1_listener zwp_relative_pointer_v1_listener = { .relative_motion = OnZwpRelativePointerMotion, }; if (zwp_relative_pointer_v1_add_listener(window->zwp_relative_pointer_v1, &zwp_relative_pointer_v1_listener, window)) { LOG("Failed to add zwp_relative_pointer_v1 listener (%s)", strerror(errno)); goto rollback_zwp_relative_pointer_v1; } window->xdg_surface = xdg_wm_base_get_xdg_surface(window->xdg_wm_base, window->wl_surface); if (!window->xdg_surface) { LOG("Failed to get xdg_surface (%s)", strerror(errno)); goto rollback_zwp_relative_pointer_v1; } static const struct xdg_surface_listener xdg_surface_listener = { .configure = OnXdgSurfaceConfigure, }; if (xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener, NULL)) { LOG("Failed to add xdg_surface listener (%s)", strerror(errno)); goto rollback_xdg_surface; } window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface); if (!window->xdg_toplevel) { LOG("Failed to get xdg_toplevel (%s)", strerror(errno)); goto rollback_xdg_surface; } static const struct xdg_toplevel_listener xdg_toplevel_listener = { .configure = OnXdgToplevelConfigure, .close = OnXdgToplevelClose, .configure_bounds = OnXdgToplevelConfigureBounds, .wm_capabilities = OnXdgToplevelWmCapabilities, }; if (xdg_toplevel_add_listener(window->xdg_toplevel, &xdg_toplevel_listener, window)) { LOG("Failed to add xdg_toplevel listener (%s)", strerror(errno)); goto rollback_xdg_toplevel; } xdg_toplevel_set_fullscreen(window->xdg_toplevel, NULL); wl_surface_commit(window->wl_surface); if (wl_display_roundtrip(window->wl_display) == -1) { LOG("Failed to roundtrip wl_display (%s)", strerror(errno)); goto rollback_xdg_toplevel; } zwp_locked_pointer_v1_set_region(window->zwp_locked_pointer_v1, NULL); wl_registry_destroy(wl_registry); return window; rollback_xdg_toplevel: xdg_toplevel_destroy(window->xdg_toplevel); rollback_xdg_surface: xdg_surface_destroy(window->xdg_surface); rollback_zwp_relative_pointer_v1: zwp_relative_pointer_v1_destroy(window->zwp_relative_pointer_v1); rollback_zwp_locked_pointer_v1: zwp_locked_pointer_v1_destroy(window->zwp_locked_pointer_v1); rollback_globals: if (window->zwp_relative_pointer_manager_v1) { zwp_relative_pointer_manager_v1_destroy( window->zwp_relative_pointer_manager_v1); } if (window->zwp_linux_dmabuf_v1) zwp_linux_dmabuf_v1_destroy(window->zwp_linux_dmabuf_v1); if (window->xdg_wm_base) xdg_wm_base_destroy(window->xdg_wm_base); if (window->wl_keyboard) wl_keyboard_release(window->wl_keyboard); if (window->wl_pointer) wl_pointer_release(window->wl_pointer); if (window->wl_surface) wl_surface_destroy(window->wl_surface); rollback_wl_registry: wl_registry_destroy(wl_registry); rollback_wl_display: wl_display_disconnect(window->wl_display); rollback_window: free(window); return NULL; } int WindowGetEventsFd(const struct Window* window) { int events_fd = wl_display_get_fd(window->wl_display); if (events_fd == -1) LOG("Failed to get wl_display fd (%s)", strerror(errno)); return events_fd; } bool WindowProcessEvents(const struct Window* window) { bool result = wl_display_dispatch(window->wl_display) != -1; if (!result) LOG("Failed to dispatch wl_display (%s)", strerror(errno)); return result; } static void DestroyBuffers(struct Window* window) { if (!window->wl_buffers) return; for (size_t i = 0; window->wl_buffers[i]; i++) wl_buffer_destroy(window->wl_buffers[i]); free(window->wl_buffers); window->wl_buffers = NULL; } static struct wl_buffer* CreateBuffer(struct Window* window, const struct Frame* frame) { struct zwp_linux_buffer_params_v1* zwp_linux_buffer_params_v1 = zwp_linux_dmabuf_v1_create_params(window->zwp_linux_dmabuf_v1); if (!zwp_linux_buffer_params_v1) { LOG("Failed to create zwp_linux_buffer_params_v1 (%s)", strerror(errno)); return NULL; } for (uint32_t i = 0; i < frame->nplanes; i++) { zwp_linux_buffer_params_v1_add( zwp_linux_buffer_params_v1, frame->planes[i].dmabuf_fd, i, frame->planes[i].offset, frame->planes[i].pitch, frame->planes[i].modifier >> 32, frame->planes[i].modifier & UINT32_MAX); } struct wl_buffer* wl_buffer = zwp_linux_buffer_params_v1_create_immed( zwp_linux_buffer_params_v1, (int)frame->width, (int)frame->height, frame->fourcc, 0); zwp_linux_buffer_params_v1_destroy(zwp_linux_buffer_params_v1); if (!wl_buffer) LOG("Failed to create wl_buffer (%s)", strerror(errno)); return wl_buffer; } bool WindowAssignFrames(struct Window* window, size_t nframes, const struct Frame* frames) { DestroyBuffers(window); window->wl_buffers = calloc(nframes + 1, sizeof(struct wl_buffer*)); if (!window->wl_buffers) { LOG("Failed to alloc window buffers (%s)", strerror(errno)); return false; } for (size_t i = 0; i < nframes; i++) { window->wl_buffers[i] = CreateBuffer(window, &frames[i]); if (!window->wl_buffers[i]) { LOG("Failed to create window buffer"); DestroyBuffers(window); return false; } } return true; } bool WindowShowFrame(struct Window* window, size_t index) { wl_surface_attach(window->wl_surface, window->wl_buffers[index], 0, 0); wl_surface_damage(window->wl_surface, 0, 0, INT32_MAX, INT32_MAX); wl_surface_commit(window->wl_surface); bool result = wl_display_roundtrip(window->wl_display) != -1; if (!result) LOG("Failed to roundtrip wl_display (%s)", strerror(errno)); return result; } void WindowDestroy(struct Window* window) { DestroyBuffers(window); xdg_toplevel_destroy(window->xdg_toplevel); xdg_surface_destroy(window->xdg_surface); zwp_relative_pointer_manager_v1_destroy( window->zwp_relative_pointer_manager_v1); zwp_linux_dmabuf_v1_destroy(window->zwp_linux_dmabuf_v1); xdg_wm_base_destroy(window->xdg_wm_base); wl_keyboard_release(window->wl_keyboard); wl_pointer_release(window->wl_pointer); wl_surface_destroy(window->wl_surface); wl_display_disconnect(window->wl_display); free(window); }