/* * This file is part of mpv video player. * * mpv is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * mpv 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mpv. If not, see . */ #include #include #include #include #include #include #include #include #include "common/msg.h" #include "input/input.h" #include "input/keycodes.h" #include "options/m_config.h" #include "osdep/io.h" #include "osdep/poll_wrapper.h" #include "osdep/timer.h" #include "present_sync.h" #include "wayland_common.h" #include "win_state.h" // Generated from wayland-protocols #include "idle-inhibit-unstable-v1.h" #include "linux-dmabuf-unstable-v1.h" #include "presentation-time.h" #include "xdg-decoration-unstable-v1.h" #include "xdg-shell.h" #include "viewporter.h" #include "content-type-v1.h" #include "single-pixel-buffer-v1.h" #include "fractional-scale-v1.h" #if HAVE_DRM #include #include #include #endif #if HAVE_WAYLAND_PROTOCOLS_1_32 #include "cursor-shape-v1.h" #endif #if WAYLAND_VERSION_MAJOR > 1 || WAYLAND_VERSION_MINOR >= 22 #define HAVE_WAYLAND_1_22 #endif #ifndef CLOCK_MONOTONIC_RAW #define CLOCK_MONOTONIC_RAW 4 #endif #ifndef XDG_TOPLEVEL_STATE_SUSPENDED #define XDG_TOPLEVEL_STATE_SUSPENDED 9 #endif // From the fractional scale protocol #define WAYLAND_SCALE_FACTOR 120.0 enum resizing_constraint { MP_WIDTH_CONSTRAINT = 1, MP_HEIGHT_CONSTRAINT = 2, }; static const struct mp_keymap keymap[] = { /* Special keys */ {XKB_KEY_Pause, MP_KEY_PAUSE}, {XKB_KEY_Escape, MP_KEY_ESC}, {XKB_KEY_BackSpace, MP_KEY_BS}, {XKB_KEY_Tab, MP_KEY_TAB}, {XKB_KEY_Return, MP_KEY_ENTER}, {XKB_KEY_Menu, MP_KEY_MENU}, {XKB_KEY_Print, MP_KEY_PRINT}, {XKB_KEY_ISO_Left_Tab, MP_KEY_TAB}, /* Cursor keys */ {XKB_KEY_Left, MP_KEY_LEFT}, {XKB_KEY_Right, MP_KEY_RIGHT}, {XKB_KEY_Up, MP_KEY_UP}, {XKB_KEY_Down, MP_KEY_DOWN}, /* Navigation keys */ {XKB_KEY_Insert, MP_KEY_INSERT}, {XKB_KEY_Delete, MP_KEY_DELETE}, {XKB_KEY_Home, MP_KEY_HOME}, {XKB_KEY_End, MP_KEY_END}, {XKB_KEY_Page_Up, MP_KEY_PAGE_UP}, {XKB_KEY_Page_Down, MP_KEY_PAGE_DOWN}, /* F-keys */ {XKB_KEY_F1, MP_KEY_F + 1}, {XKB_KEY_F2, MP_KEY_F + 2}, {XKB_KEY_F3, MP_KEY_F + 3}, {XKB_KEY_F4, MP_KEY_F + 4}, {XKB_KEY_F5, MP_KEY_F + 5}, {XKB_KEY_F6, MP_KEY_F + 6}, {XKB_KEY_F7, MP_KEY_F + 7}, {XKB_KEY_F8, MP_KEY_F + 8}, {XKB_KEY_F9, MP_KEY_F + 9}, {XKB_KEY_F10, MP_KEY_F +10}, {XKB_KEY_F11, MP_KEY_F +11}, {XKB_KEY_F12, MP_KEY_F +12}, {XKB_KEY_F13, MP_KEY_F +13}, {XKB_KEY_F14, MP_KEY_F +14}, {XKB_KEY_F15, MP_KEY_F +15}, {XKB_KEY_F16, MP_KEY_F +16}, {XKB_KEY_F17, MP_KEY_F +17}, {XKB_KEY_F18, MP_KEY_F +18}, {XKB_KEY_F19, MP_KEY_F +19}, {XKB_KEY_F20, MP_KEY_F +20}, {XKB_KEY_F21, MP_KEY_F +21}, {XKB_KEY_F22, MP_KEY_F +22}, {XKB_KEY_F23, MP_KEY_F +23}, {XKB_KEY_F24, MP_KEY_F +24}, /* Numpad independent of numlock */ {XKB_KEY_KP_Subtract, MP_KEY_KPSUBTRACT}, {XKB_KEY_KP_Add, MP_KEY_KPADD}, {XKB_KEY_KP_Multiply, MP_KEY_KPMULTIPLY}, {XKB_KEY_KP_Divide, MP_KEY_KPDIVIDE}, {XKB_KEY_KP_Enter, MP_KEY_KPENTER}, /* Numpad with numlock */ {XKB_KEY_KP_0, MP_KEY_KP0}, {XKB_KEY_KP_1, MP_KEY_KP1}, {XKB_KEY_KP_2, MP_KEY_KP2}, {XKB_KEY_KP_3, MP_KEY_KP3}, {XKB_KEY_KP_4, MP_KEY_KP4}, {XKB_KEY_KP_5, MP_KEY_KP5}, {XKB_KEY_KP_6, MP_KEY_KP6}, {XKB_KEY_KP_7, MP_KEY_KP7}, {XKB_KEY_KP_8, MP_KEY_KP8}, {XKB_KEY_KP_9, MP_KEY_KP9}, {XKB_KEY_KP_Decimal, MP_KEY_KPDEC}, {XKB_KEY_KP_Separator, MP_KEY_KPDEC}, /* Numpad without numlock */ {XKB_KEY_KP_Insert, MP_KEY_KPINS}, {XKB_KEY_KP_End, MP_KEY_KPEND}, {XKB_KEY_KP_Down, MP_KEY_KPDOWN}, {XKB_KEY_KP_Page_Down, MP_KEY_KPPGDOWN}, {XKB_KEY_KP_Left, MP_KEY_KPLEFT}, {XKB_KEY_KP_Begin, MP_KEY_KPBEGIN}, {XKB_KEY_KP_Right, MP_KEY_KPRIGHT}, {XKB_KEY_KP_Home, MP_KEY_KPHOME}, {XKB_KEY_KP_Up, MP_KEY_KPUP}, {XKB_KEY_KP_Page_Up, MP_KEY_KPPGUP}, {XKB_KEY_KP_Delete, MP_KEY_KPDEL}, /* Multimedia keys */ {XKB_KEY_XF86MenuKB, MP_KEY_MENU}, {XKB_KEY_XF86AudioPlay, MP_KEY_PLAY}, {XKB_KEY_XF86AudioPause, MP_KEY_PAUSE}, {XKB_KEY_XF86AudioStop, MP_KEY_STOP}, {XKB_KEY_XF86AudioPrev, MP_KEY_PREV}, {XKB_KEY_XF86AudioNext, MP_KEY_NEXT}, {XKB_KEY_XF86AudioRewind, MP_KEY_REWIND}, {XKB_KEY_XF86AudioForward, MP_KEY_FORWARD}, {XKB_KEY_XF86AudioMute, MP_KEY_MUTE}, {XKB_KEY_XF86AudioLowerVolume, MP_KEY_VOLUME_DOWN}, {XKB_KEY_XF86AudioRaiseVolume, MP_KEY_VOLUME_UP}, {XKB_KEY_XF86HomePage, MP_KEY_HOMEPAGE}, {XKB_KEY_XF86WWW, MP_KEY_WWW}, {XKB_KEY_XF86Mail, MP_KEY_MAIL}, {XKB_KEY_XF86Favorites, MP_KEY_FAVORITES}, {XKB_KEY_XF86Search, MP_KEY_SEARCH}, {XKB_KEY_XF86Sleep, MP_KEY_SLEEP}, {XKB_KEY_XF86Back, MP_KEY_GO_BACK}, {XKB_KEY_XF86Forward, MP_KEY_GO_FORWARD}, {XKB_KEY_XF86Tools, MP_KEY_TOOLS}, {XKB_KEY_XF86ZoomIn, MP_KEY_ZOOMIN}, {XKB_KEY_XF86ZoomOut, MP_KEY_ZOOMOUT}, {0, 0} }; struct compositor_format { uint32_t format; uint32_t padding; uint64_t modifier; }; struct vo_wayland_feedback_pool { struct wp_presentation_feedback **fback; struct vo_wayland_state *wl; int len; }; struct vo_wayland_output { struct vo_wayland_state *wl; struct wl_output *output; struct mp_rect geometry; bool has_surface; uint32_t id; uint32_t flags; int phys_width; int phys_height; int scale; double refresh_rate; char *make; char *model; char *name; struct wl_list link; }; struct vo_wayland_seat { struct vo_wayland_state *wl; struct wl_seat *seat; uint32_t id; struct wl_keyboard *keyboard; struct wl_pointer *pointer; struct wl_touch *touch; struct wl_data_device *dnd_ddev; /* TODO: unvoid this if required wayland protocols is bumped to 1.32+ */ void *cursor_shape_device; uint32_t pointer_enter_serial; uint32_t pointer_button_serial; struct xkb_keymap *xkb_keymap; struct xkb_state *xkb_state; uint32_t keyboard_code; int mpkey; int mpmod; double axis_value_vertical; int32_t axis_value120_vertical; double axis_value_horizontal; int32_t axis_value120_horizontal; bool axis_value120_scroll; bool has_keyboard_input; struct wl_list link; bool keyboard_entering; uint32_t *keyboard_entering_keys; int num_keyboard_entering_keys; }; struct vo_wayland_tranche { struct drm_format *compositor_formats; int num_compositor_formats; uint32_t *planar_formats; int num_planar_formats; dev_t device_id; struct wl_list link; }; static bool single_output_spanned(struct vo_wayland_state *wl); static int check_for_resize(struct vo_wayland_state *wl, int edge_pixels, enum xdg_toplevel_resize_edge *edge); static int get_mods(struct vo_wayland_seat *seat); static int greatest_common_divisor(int a, int b); static int handle_round(int scale, int n); static int set_cursor_visibility(struct vo_wayland_seat *s, bool on); static int spawn_cursor(struct vo_wayland_state *wl); static void add_feedback(struct vo_wayland_feedback_pool *fback_pool, struct wp_presentation_feedback *fback); static void apply_keepaspect(struct vo_wayland_state *wl, int *width, int *height); static void get_planar_drm_formats(struct vo_wayland_state *wl); static void get_shape_device(struct vo_wayland_state *wl, struct vo_wayland_seat *s); static void guess_focus(struct vo_wayland_state *wl); static void handle_key_input(struct vo_wayland_seat *s, uint32_t key, uint32_t state, bool no_emit); static void prepare_resize(struct vo_wayland_state *wl); static void remove_feedback(struct vo_wayland_feedback_pool *fback_pool, struct wp_presentation_feedback *fback); static void remove_output(struct vo_wayland_output *out); static void remove_seat(struct vo_wayland_seat *seat); static void request_decoration_mode(struct vo_wayland_state *wl, uint32_t mode); static void rescale_geometry(struct vo_wayland_state *wl, double old_scale); static void set_geometry(struct vo_wayland_state *wl, bool resize); static void set_surface_scaling(struct vo_wayland_state *wl); static void update_output_scaling(struct vo_wayland_state *wl); static void update_output_geometry(struct vo_wayland_state *wl, struct mp_rect old_geometry, struct mp_rect old_output_geometry); /* Wayland listener boilerplate */ static void pointer_handle_enter(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t sx, wl_fixed_t sy) { struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; s->pointer_enter_serial = serial; set_cursor_visibility(s, wl->cursor_visible); mp_input_put_key(wl->vo->input_ctx, MP_KEY_MOUSE_ENTER); wl->mouse_x = handle_round(wl->scaling, wl_fixed_to_int(sx)); wl->mouse_y = handle_round(wl->scaling, wl_fixed_to_int(sy)); if (!wl->toplevel_configured) mp_input_set_mouse_pos(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y); wl->toplevel_configured = false; } static void pointer_handle_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) { struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; mp_input_put_key(wl->vo->input_ctx, MP_KEY_MOUSE_LEAVE); } static void pointer_handle_motion(void *data, struct wl_pointer *pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy) { struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; wl->mouse_x = handle_round(wl->scaling, wl_fixed_to_int(sx)); wl->mouse_y = handle_round(wl->scaling, wl_fixed_to_int(sy)); if (!wl->toplevel_configured) mp_input_set_mouse_pos(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y); wl->toplevel_configured = false; } static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; state = state == WL_POINTER_BUTTON_STATE_PRESSED ? MP_KEY_STATE_DOWN : MP_KEY_STATE_UP; if (button >= BTN_MOUSE && button < BTN_JOYSTICK) { switch (button) { case BTN_LEFT: button = MP_MBTN_LEFT; break; case BTN_MIDDLE: button = MP_MBTN_MID; break; case BTN_RIGHT: button = MP_MBTN_RIGHT; break; case BTN_SIDE: button = MP_MBTN_BACK; break; case BTN_EXTRA: button = MP_MBTN_FORWARD; break; default: button += MP_MBTN9 - BTN_FORWARD; break; } } else { button = 0; } enum xdg_toplevel_resize_edge edges; if (!mp_input_test_dragging(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y) && !wl->locked_size && (button == MP_MBTN_LEFT) && (state == MP_KEY_STATE_DOWN) && !wl->opts->border && check_for_resize(wl, wl->opts->wl_edge_pixels_pointer, &edges)) { // Implement an edge resize zone if there are no decorations xdg_toplevel_resize(wl->xdg_toplevel, s->seat, serial, edges); return; } else if (state == MP_KEY_STATE_DOWN) { // Save the serial and seat for voctrl-initialized dragging requests. s->pointer_button_serial = serial; wl->last_button_seat = s; } else { wl->last_button_seat = NULL; } if (button) mp_input_put_key(wl->vo->input_ctx, button | state | s->mpmod); } static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { struct vo_wayland_seat *s = data; switch (axis) { case WL_POINTER_AXIS_VERTICAL_SCROLL: s->axis_value_vertical += wl_fixed_to_double(value); break; case WL_POINTER_AXIS_HORIZONTAL_SCROLL: s->axis_value_horizontal += wl_fixed_to_double(value); break; } } static void pointer_handle_frame(void *data, struct wl_pointer *wl_pointer) { struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; double value_vertical, value_horizontal; if (s->axis_value120_scroll) { // Prefer axis_value120 if supported and the axis event is from mouse wheel. value_vertical = s->axis_value120_vertical / 120.0; value_horizontal = s->axis_value120_horizontal / 120.0; } else { // The axis value is specified in logical coordinates, but the exact value emitted // by one mouse wheel click is unspecified. In practice, most compositors use either // 10 (GNOME, Weston) or 15 (wlroots, same as libinput) as the value. // Divide the value by 10 and clamp it between -1 and 1 so that mouse wheel clicks // work as intended on all compositors while still allowing high resolution trackpads. value_vertical = MPCLAMP(s->axis_value_vertical / 10.0, -1, 1); value_horizontal = MPCLAMP(s->axis_value_horizontal / 10.0, -1, 1); } if (value_vertical > 0) mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_DOWN | s->mpmod, +value_vertical); if (value_vertical < 0) mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_UP | s->mpmod, -value_vertical); if (value_horizontal > 0) mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_RIGHT | s->mpmod, +value_horizontal); if (value_horizontal < 0) mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_LEFT | s->mpmod, -value_horizontal); s->axis_value120_scroll = false; s->axis_value_vertical = 0; s->axis_value_horizontal = 0; s->axis_value120_vertical = 0; s->axis_value120_horizontal = 0; } static void pointer_handle_axis_source(void *data, struct wl_pointer *wl_pointer, uint32_t axis_source) { } static void pointer_handle_axis_stop(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis) { } static void pointer_handle_axis_discrete(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete) { } static void pointer_handle_axis_value120(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t value120) { struct vo_wayland_seat *s = data; s->axis_value120_scroll = true; switch (axis) { case WL_POINTER_AXIS_VERTICAL_SCROLL: s->axis_value120_vertical += value120; break; case WL_POINTER_AXIS_HORIZONTAL_SCROLL: s->axis_value120_horizontal += value120; break; } } static const struct wl_pointer_listener pointer_listener = { pointer_handle_enter, pointer_handle_leave, pointer_handle_motion, pointer_handle_button, pointer_handle_axis, pointer_handle_frame, pointer_handle_axis_source, pointer_handle_axis_stop, pointer_handle_axis_discrete, pointer_handle_axis_value120, }; static void touch_handle_down(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time, struct wl_surface *surface, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) { struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; // Note: the position should still be saved here for VO dragging handling. wl->mouse_x = handle_round(wl->scaling, wl_fixed_to_int(x_w)); wl->mouse_y = handle_round(wl->scaling, wl_fixed_to_int(y_w)); enum xdg_toplevel_resize_edge edge; if (!mp_input_test_dragging(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y) && !wl->locked_size && check_for_resize(wl, wl->opts->wl_edge_pixels_touch, &edge)) { xdg_toplevel_resize(wl->xdg_toplevel, s->seat, serial, edge); return; } else { // Save the serial and seat for voctrl-initialized dragging requests. s->pointer_button_serial = serial; wl->last_button_seat = s; } mp_input_add_touch_point(wl->vo->input_ctx, id, wl->mouse_x, wl->mouse_y); } static void touch_handle_up(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time, int32_t id) { struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; mp_input_remove_touch_point(wl->vo->input_ctx, id); wl->last_button_seat = NULL; } static void touch_handle_motion(void *data, struct wl_touch *wl_touch, uint32_t time, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w) { struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; wl->mouse_x = handle_round(wl->scaling, wl_fixed_to_int(x_w)); wl->mouse_y = handle_round(wl->scaling, wl_fixed_to_int(y_w)); mp_input_update_touch_point(wl->vo->input_ctx, id, wl->mouse_x, wl->mouse_y); } static void touch_handle_frame(void *data, struct wl_touch *wl_touch) { } static void touch_handle_cancel(void *data, struct wl_touch *wl_touch) { struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; mp_input_put_key(wl->vo->input_ctx, MP_TOUCH_RELEASE_ALL); } static void touch_handle_shape(void *data, struct wl_touch *wl_touch, int32_t id, wl_fixed_t major, wl_fixed_t minor) { } static void touch_handle_orientation(void *data, struct wl_touch *wl_touch, int32_t id, wl_fixed_t orientation) { } static const struct wl_touch_listener touch_listener = { touch_handle_down, touch_handle_up, touch_handle_motion, touch_handle_frame, touch_handle_cancel, touch_handle_shape, touch_handle_orientation, }; static void keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size) { struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; char *map_str; if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { close(fd); return; } map_str = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); if (map_str == MAP_FAILED) { close(fd); return; } if (s->xkb_keymap) xkb_keymap_unref(s->xkb_keymap); s->xkb_keymap = xkb_keymap_new_from_buffer(wl->xkb_context, map_str, strnlen(map_str, size), XKB_KEYMAP_FORMAT_TEXT_V1, 0); munmap(map_str, size); close(fd); if (!s->xkb_keymap) { MP_ERR(wl, "failed to compile keymap\n"); return; } if (s->xkb_state) xkb_state_unref(s->xkb_state); s->xkb_state = xkb_state_new(s->xkb_keymap); if (!s->xkb_state) { MP_ERR(wl, "failed to create XKB state\n"); xkb_keymap_unref(s->xkb_keymap); s->xkb_keymap = NULL; return; } } static void keyboard_handle_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; s->has_keyboard_input = true; s->keyboard_entering = true; guess_focus(wl); uint32_t *key; wl_array_for_each(key, keys) MP_TARRAY_APPEND(s, s->keyboard_entering_keys, s->num_keyboard_entering_keys, *key); } static void keyboard_handle_leave(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface) { struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; s->has_keyboard_input = false; s->keyboard_code = 0; s->mpkey = 0; s->mpmod = 0; mp_input_put_key(wl->vo->input_ctx, MP_INPUT_RELEASE_ALL); guess_focus(wl); } static void keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { struct vo_wayland_seat *s = data; handle_key_input(s, key, state, false); } static void keyboard_handle_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) { struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; if (s->xkb_state) { xkb_state_update_mask(s->xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); s->mpmod = get_mods(s); } // Handle keys pressed during the enter event. if (s->keyboard_entering) { s->keyboard_entering = false; // Only handle entering keys if only one key is pressed since // Wayland doesn't guarantee that these keys are in order. if (s->num_keyboard_entering_keys == 1) for (int n = 0; n < s->num_keyboard_entering_keys; n++) handle_key_input(s, s->keyboard_entering_keys[n], WL_KEYBOARD_KEY_STATE_PRESSED, true); s->num_keyboard_entering_keys = 0; } else if (s->xkb_state && s->mpkey) { mp_input_put_key(wl->vo->input_ctx, s->mpkey | MP_KEY_STATE_DOWN | s->mpmod); } } static void keyboard_handle_repeat_info(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay) { struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; if (wl->opts->native_keyrepeat) mp_input_set_repeat_info(wl->vo->input_ctx, rate, delay); } static const struct wl_keyboard_listener keyboard_listener = { keyboard_handle_keymap, keyboard_handle_enter, keyboard_handle_leave, keyboard_handle_key, keyboard_handle_modifiers, keyboard_handle_repeat_info, }; static void seat_handle_caps(void *data, struct wl_seat *seat, enum wl_seat_capability caps) { struct vo_wayland_seat *s = data; if ((caps & WL_SEAT_CAPABILITY_POINTER) && !s->pointer) { s->pointer = wl_seat_get_pointer(seat); get_shape_device(s->wl, s); wl_pointer_add_listener(s->pointer, &pointer_listener, s); } else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && s->pointer) { wl_pointer_destroy(s->pointer); s->pointer = NULL; } if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !s->keyboard) { s->keyboard = wl_seat_get_keyboard(seat); wl_keyboard_add_listener(s->keyboard, &keyboard_listener, s); } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && s->keyboard) { wl_keyboard_destroy(s->keyboard); s->keyboard = NULL; } if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !s->touch) { s->touch = wl_seat_get_touch(seat); wl_touch_set_user_data(s->touch, s); wl_touch_add_listener(s->touch, &touch_listener, s); } else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && s->touch) { wl_touch_destroy(s->touch); s->touch = NULL; } } static void seat_handle_name(void *data, struct wl_seat *seat, const char *name) { } static const struct wl_seat_listener seat_listener = { seat_handle_caps, seat_handle_name, }; static void data_offer_handle_offer(void *data, struct wl_data_offer *offer, const char *mime_type) { struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; int score = mp_event_get_mime_type_score(wl->vo->input_ctx, mime_type); if (score > wl->dnd_mime_score && wl->opts->drag_and_drop != -2) { wl->dnd_mime_score = score; talloc_replace(wl, wl->dnd_mime_type, mime_type); MP_VERBOSE(wl, "Given DND offer with mime type %s\n", wl->dnd_mime_type); } } static void data_offer_source_actions(void *data, struct wl_data_offer *offer, uint32_t source_actions) { } static void data_offer_action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action) { struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; if (dnd_action && wl->opts->drag_and_drop != -2) { if (wl->opts->drag_and_drop >= 0) { wl->dnd_action = wl->opts->drag_and_drop; } else { wl->dnd_action = dnd_action & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY ? DND_REPLACE : DND_APPEND; } static const char * const dnd_action_names[] = { [DND_REPLACE] = "DND_REPLACE", [DND_APPEND] = "DND_APPEND", [DND_INSERT_NEXT] = "DND_INSERT_NEXT", }; MP_VERBOSE(wl, "DND action is %s\n", dnd_action_names[wl->dnd_action]); } } static const struct wl_data_offer_listener data_offer_listener = { data_offer_handle_offer, data_offer_source_actions, data_offer_action, }; static void data_device_handle_data_offer(void *data, struct wl_data_device *wl_ddev, struct wl_data_offer *id) { struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; if (wl->dnd_offer) wl_data_offer_destroy(wl->dnd_offer); wl->dnd_offer = id; wl_data_offer_add_listener(id, &data_offer_listener, s); } static void data_device_handle_enter(void *data, struct wl_data_device *wl_ddev, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *id) { struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; if (wl->dnd_offer != id) { MP_FATAL(wl, "DND offer ID mismatch!\n"); return; } if (wl->opts->drag_and_drop != -2) { wl_data_offer_set_actions(id, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY | WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY); wl_data_offer_accept(id, serial, wl->dnd_mime_type); MP_VERBOSE(wl, "Accepting DND offer with mime type %s\n", wl->dnd_mime_type); } } static void data_device_handle_leave(void *data, struct wl_data_device *wl_ddev) { struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; if (wl->dnd_offer) { if (wl->dnd_fd != -1) return; wl_data_offer_destroy(wl->dnd_offer); wl->dnd_offer = NULL; } if (wl->opts->drag_and_drop != -2) { MP_VERBOSE(wl, "Releasing DND offer with mime type %s\n", wl->dnd_mime_type); if (wl->dnd_mime_type) TA_FREEP(&wl->dnd_mime_type); wl->dnd_mime_score = 0; } } static void data_device_handle_motion(void *data, struct wl_data_device *wl_ddev, uint32_t time, wl_fixed_t x, wl_fixed_t y) { struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; wl_data_offer_accept(wl->dnd_offer, time, wl->dnd_mime_type); } static void data_device_handle_drop(void *data, struct wl_data_device *wl_ddev) { struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; int pipefd[2]; if (pipe2(pipefd, O_CLOEXEC) == -1) { MP_ERR(wl, "Failed to create dnd pipe!\n"); return; } if (wl->opts->drag_and_drop != -2) { MP_VERBOSE(wl, "Receiving DND offer with mime %s\n", wl->dnd_mime_type); wl_data_offer_receive(wl->dnd_offer, wl->dnd_mime_type, pipefd[1]); } close(pipefd[1]); wl->dnd_fd = pipefd[0]; } static void data_device_handle_selection(void *data, struct wl_data_device *wl_ddev, struct wl_data_offer *id) { struct vo_wayland_seat *s = data; struct vo_wayland_state *wl = s->wl; if (wl->dnd_offer) { wl_data_offer_destroy(wl->dnd_offer); wl->dnd_offer = NULL; MP_VERBOSE(wl, "Received a new DND offer. Releasing the previous offer.\n"); } } static const struct wl_data_device_listener data_device_listener = { data_device_handle_data_offer, data_device_handle_enter, data_device_handle_leave, data_device_handle_motion, data_device_handle_drop, data_device_handle_selection, }; static void output_handle_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, int32_t phys_width, int32_t phys_height, int32_t subpixel, const char *make, const char *model, int32_t transform) { struct vo_wayland_output *output = data; output->make = talloc_strdup(output->wl, make); output->model = talloc_strdup(output->wl, model); output->geometry.x0 = x; output->geometry.y0 = y; output->phys_width = phys_width; output->phys_height = phys_height; } static void output_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { struct vo_wayland_output *output = data; /* Only save current mode */ if (!(flags & WL_OUTPUT_MODE_CURRENT)) return; output->geometry.x1 = width; output->geometry.y1 = height; output->flags = flags; output->refresh_rate = (double)refresh * 0.001; } static void output_handle_done(void *data, struct wl_output *wl_output) { struct vo_wayland_output *o = data; struct vo_wayland_state *wl = o->wl; o->geometry.x1 += o->geometry.x0; o->geometry.y1 += o->geometry.y0; MP_VERBOSE(o->wl, "Registered output %s %s (0x%x):\n" "\tx: %dpx, y: %dpx\n" "\tw: %dpx (%dmm), h: %dpx (%dmm)\n" "\tscale: %f\n" "\tHz: %f\n", o->make, o->model, o->id, o->geometry.x0, o->geometry.y0, mp_rect_w(o->geometry), o->phys_width, mp_rect_h(o->geometry), o->phys_height, o->scale / WAYLAND_SCALE_FACTOR, o->refresh_rate); /* If we satisfy this conditional, something about the current * output must have changed (resolution, scale, etc). All window * geometry and scaling should be recalculated. */ if (wl->current_output && wl->current_output->output == wl_output) { set_surface_scaling(wl); set_geometry(wl, false); prepare_resize(wl); } wl->pending_vo_events |= VO_EVENT_WIN_STATE; } static void output_handle_scale(void *data, struct wl_output *wl_output, int32_t factor) { struct vo_wayland_output *output = data; if (!factor) { MP_ERR(output->wl, "Invalid output scale given by the compositor!\n"); return; } output->scale = factor * WAYLAND_SCALE_FACTOR; } static void output_handle_name(void *data, struct wl_output *wl_output, const char *name) { struct vo_wayland_output *output = data; output->name = talloc_strdup(output->wl, name); } static void output_handle_description(void *data, struct wl_output *wl_output, const char *description) { } static const struct wl_output_listener output_listener = { output_handle_geometry, output_handle_mode, output_handle_done, output_handle_scale, output_handle_name, output_handle_description, }; static void surface_handle_enter(void *data, struct wl_surface *wl_surface, struct wl_output *output) { struct vo_wayland_state *wl = data; if (!wl->current_output) return; struct mp_rect old_output_geometry = wl->current_output->geometry; struct mp_rect old_geometry = wl->geometry; wl->current_output = NULL; int outputs = 0; struct vo_wayland_output *o; wl_list_for_each(o, &wl->output_list, link) { if (o->output == output) { wl->current_output = o; wl->current_output->has_surface = true; } if (o->has_surface) ++outputs; } if (outputs == 1) update_output_geometry(wl, old_geometry, old_output_geometry); MP_VERBOSE(wl, "Surface entered output %s %s (0x%x), scale = %f, refresh rate = %f Hz\n", wl->current_output->make, wl->current_output->model, wl->current_output->id, wl->scaling_factor, wl->current_output->refresh_rate); wl->pending_vo_events |= VO_EVENT_WIN_STATE; } static void surface_handle_leave(void *data, struct wl_surface *wl_surface, struct wl_output *output) { struct vo_wayland_state *wl = data; if (!wl->current_output) return; struct mp_rect old_output_geometry = wl->current_output->geometry; struct mp_rect old_geometry = wl->geometry; int outputs = 0; struct vo_wayland_output *o; wl_list_for_each(o, &wl->output_list, link) { if (o->output == output) o->has_surface = false; if (o->has_surface) ++outputs; if (o->output != output && o->has_surface) wl->current_output = o; } if (outputs == 1) update_output_geometry(wl, old_geometry, old_output_geometry); wl->pending_vo_events |= VO_EVENT_WIN_STATE; } #ifdef HAVE_WAYLAND_1_22 static void surface_handle_preferred_buffer_scale(void *data, struct wl_surface *wl_surface, int32_t scale) { struct vo_wayland_state *wl = data; if (wl->fractional_scale_manager || wl->scaling == scale * WAYLAND_SCALE_FACTOR) return; wl->pending_scaling = scale * WAYLAND_SCALE_FACTOR; wl->scale_configured = true; MP_VERBOSE(wl, "Obtained preferred scale, %f, from the compositor.\n", wl->scaling / WAYLAND_SCALE_FACTOR); wl->pending_vo_events |= VO_EVENT_DPI; wl->need_rescale = true; // Update scaling now. if (single_output_spanned(wl)) update_output_scaling(wl); if (!wl->current_output) { wl->scaling = wl->pending_scaling; wl->scaling_factor = scale; } } static void surface_handle_preferred_buffer_transform(void *data, struct wl_surface *wl_surface, uint32_t transform) { } #endif static const struct wl_surface_listener surface_listener = { surface_handle_enter, surface_handle_leave, #ifdef HAVE_WAYLAND_1_22 surface_handle_preferred_buffer_scale, surface_handle_preferred_buffer_transform, #endif }; static void xdg_wm_base_ping(void *data, struct xdg_wm_base *wm_base, uint32_t serial) { xdg_wm_base_pong(wm_base, serial); } static const struct xdg_wm_base_listener xdg_wm_base_listener = { xdg_wm_base_ping, }; static void handle_surface_config(void *data, struct xdg_surface *surface, uint32_t serial) { xdg_surface_ack_configure(surface, serial); } static const struct xdg_surface_listener xdg_surface_listener = { handle_surface_config, }; static void handle_toplevel_config(void *data, struct xdg_toplevel *toplevel, int32_t width, int32_t height, struct wl_array *states) { struct vo_wayland_state *wl = data; struct mp_vo_opts *opts = wl->opts; struct mp_rect old_geometry = wl->geometry; if (width < 0 || height < 0) { MP_WARN(wl, "Compositor sent negative width/height values. Treating them as zero.\n"); width = height = 0; } if (!wl->geometry_configured) { /* Save initial window size if the compositor gives us a hint here. */ bool autofit_or_geometry = opts->geometry.wh_valid || opts->autofit.wh_valid || opts->autofit_larger.wh_valid || opts->autofit_smaller.wh_valid; if (width && height && !autofit_or_geometry) { wl->initial_size_hint = true; wl->window_size = (struct mp_rect){0, 0, width, height}; wl->geometry = wl->window_size; } return; } bool is_maximized = false; bool is_fullscreen = false; bool is_activated = false; bool is_resizing = false; bool is_suspended = false; bool is_tiled = false; enum xdg_toplevel_state *state; wl_array_for_each(state, states) { switch (*state) { case XDG_TOPLEVEL_STATE_FULLSCREEN: is_fullscreen = true; break; case XDG_TOPLEVEL_STATE_RESIZING: is_resizing = true; break; case XDG_TOPLEVEL_STATE_ACTIVATED: is_activated = true; /* * If we get an ACTIVATED state, we know it cannot be * minimized, but it may not have been minimized * previously, so we can't detect the exact state. */ opts->window_minimized = false; m_config_cache_write_opt(wl->opts_cache, &opts->window_minimized); break; case XDG_TOPLEVEL_STATE_TILED_TOP: case XDG_TOPLEVEL_STATE_TILED_LEFT: case XDG_TOPLEVEL_STATE_TILED_RIGHT: case XDG_TOPLEVEL_STATE_TILED_BOTTOM: is_tiled = true; break; case XDG_TOPLEVEL_STATE_MAXIMIZED: is_maximized = true; break; case XDG_TOPLEVEL_STATE_SUSPENDED: is_suspended = true; break; } } if (wl->hidden != is_suspended) wl->hidden = is_suspended; if (wl->resizing != is_resizing) { wl->resizing = is_resizing; wl->resizing_constraint = 0; } if (opts->fullscreen != is_fullscreen) { wl->state_change = wl->reconfigured; opts->fullscreen = is_fullscreen; m_config_cache_write_opt(wl->opts_cache, &opts->fullscreen); } if (opts->window_maximized != is_maximized) { wl->state_change = wl->reconfigured; opts->window_maximized = is_maximized; m_config_cache_write_opt(wl->opts_cache, &opts->window_maximized); } if (!is_tiled && wl->tiled) wl->state_change = wl->reconfigured; wl->tiled = is_tiled; wl->locked_size = is_fullscreen || is_maximized || is_tiled; wl->reconfigured = false; if (wl->requested_decoration) request_decoration_mode(wl, wl->requested_decoration); if (wl->activated != is_activated) { wl->activated = is_activated; guess_focus(wl); /* Just force a redraw to be on the safe side. */ if (wl->activated) { wl->hidden = false; wl->pending_vo_events |= VO_EVENT_EXPOSE; } } if (wl->state_change) { if (!wl->locked_size) { wl->geometry = wl->window_size; wl->state_change = false; goto resize; } } /* Reuse old size if either of these are 0. */ if (width == 0 || height == 0) { if (!wl->locked_size) { wl->geometry = wl->window_size; } goto resize; } if (!wl->locked_size) { apply_keepaspect(wl, &width, &height); wl->window_size.x0 = 0; wl->window_size.y0 = 0; wl->window_size.x1 = handle_round(wl->scaling, width); wl->window_size.y1 = handle_round(wl->scaling, height); } wl->geometry.x0 = 0; wl->geometry.y0 = 0; wl->geometry.x1 = handle_round(wl->scaling, width); wl->geometry.y1 = handle_round(wl->scaling, height); if (mp_rect_equals(&old_geometry, &wl->geometry)) return; resize: MP_VERBOSE(wl, "Resizing due to xdg from %ix%i to %ix%i\n", mp_rect_w(old_geometry), mp_rect_h(old_geometry), mp_rect_w(wl->geometry), mp_rect_h(wl->geometry)); prepare_resize(wl); wl->toplevel_configured = true; } static void handle_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel) { struct vo_wayland_state *wl = data; mp_input_put_key(wl->vo->input_ctx, MP_KEY_CLOSE_WIN); } static void handle_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel, int32_t width, int32_t height) { struct vo_wayland_state *wl = data; wl->bounded_width = handle_round(wl->scaling, width); wl->bounded_height = handle_round(wl->scaling, height); } static void handle_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel, struct wl_array *capabilities) { } static const struct xdg_toplevel_listener xdg_toplevel_listener = { handle_toplevel_config, handle_toplevel_close, handle_configure_bounds, handle_wm_capabilities, }; static void preferred_scale(void *data, struct wp_fractional_scale_v1 *fractional_scale, uint32_t scale) { struct vo_wayland_state *wl = data; if (wl->scaling == scale) return; wl->pending_scaling = scale; wl->scale_configured = true; MP_VERBOSE(wl, "Obtained preferred scale, %f, from the compositor.\n", wl->pending_scaling / WAYLAND_SCALE_FACTOR); wl->need_rescale = true; // Update scaling now. if (single_output_spanned(wl)) update_output_scaling(wl); if (!wl->current_output) { wl->scaling = wl->pending_scaling; wl->scaling_factor = wl->scaling / WAYLAND_SCALE_FACTOR; } } static const struct wp_fractional_scale_v1_listener fractional_scale_listener = { preferred_scale, }; static const char *zxdg_decoration_mode_to_str(const uint32_t mode) { switch (mode) { case ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE: return "server-side"; case ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE: return "client-side"; default: return ""; } } static void configure_decorations(void *data, struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration, uint32_t mode) { struct vo_wayland_state *wl = data; struct mp_vo_opts *opts = wl->opts; if (wl->requested_decoration && mode != wl->requested_decoration) { MP_DBG(wl, "Requested %s decorations but compositor responded with %s. " "It is likely that compositor wants us to stay in a given mode.\n", zxdg_decoration_mode_to_str(wl->requested_decoration), zxdg_decoration_mode_to_str(mode)); } wl->requested_decoration = 0; if (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE) { MP_VERBOSE(wl, "Enabling server decorations\n"); } else { MP_VERBOSE(wl, "Disabling server decorations\n"); } opts->border = mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE; m_config_cache_write_opt(wl->opts_cache, &opts->border); } static const struct zxdg_toplevel_decoration_v1_listener decoration_listener = { configure_decorations, }; static void pres_set_clockid(void *data, struct wp_presentation *pres, uint32_t clockid) { struct vo_wayland_state *wl = data; if (clockid == CLOCK_MONOTONIC || clockid == CLOCK_MONOTONIC_RAW) wl->present_clock = true; } static const struct wp_presentation_listener pres_listener = { pres_set_clockid, }; static void feedback_sync_output(void *data, struct wp_presentation_feedback *fback, struct wl_output *output) { } static void feedback_presented(void *data, struct wp_presentation_feedback *fback, uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec, uint32_t refresh_nsec, uint32_t seq_hi, uint32_t seq_lo, uint32_t flags) { struct vo_wayland_feedback_pool *fback_pool = data; struct vo_wayland_state *wl = fback_pool->wl; if (fback) remove_feedback(fback_pool, fback); wl->refresh_interval = (int64_t)refresh_nsec; // Very similar to oml_sync_control, in this case we assume that every // time the compositor receives feedback, a buffer swap has been already // been performed. // // Notes: // - tv_sec_lo + tv_sec_hi is the equivalent of oml's ust // - seq_lo + seq_hi is the equivalent of oml's msc // - these values are updated every time the compositor receives feedback. int64_t sec = (uint64_t) tv_sec_lo + ((uint64_t) tv_sec_hi << 32); int64_t ust = MP_TIME_S_TO_NS(sec) + (uint64_t) tv_nsec; int64_t msc = (uint64_t) seq_lo + ((uint64_t) seq_hi << 32); present_sync_update_values(wl->present, ust, msc); } static void feedback_discarded(void *data, struct wp_presentation_feedback *fback) { struct vo_wayland_feedback_pool *fback_pool = data; if (fback) remove_feedback(fback_pool, fback); } static const struct wp_presentation_feedback_listener feedback_listener = { feedback_sync_output, feedback_presented, feedback_discarded, }; static const struct wl_callback_listener frame_listener; static void frame_callback(void *data, struct wl_callback *callback, uint32_t time) { struct vo_wayland_state *wl = data; if (callback) wl_callback_destroy(callback); wl->frame_callback = wl_surface_frame(wl->callback_surface); wl_callback_add_listener(wl->frame_callback, &frame_listener, wl); wl->use_present = wl->present_clock && wl->opts->wl_present; if (wl->use_present) { struct wp_presentation_feedback *fback = wp_presentation_feedback(wl->presentation, wl->callback_surface); add_feedback(wl->fback_pool, fback); wp_presentation_feedback_add_listener(fback, &feedback_listener, wl->fback_pool); } wl->frame_wait = false; wl->hidden = false; } static const struct wl_callback_listener frame_listener = { frame_callback, }; static void done(void *data, struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1) { } static void format_table(void *data, struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, int32_t fd, uint32_t size) { struct vo_wayland_state *wl = data; if (wl->compositor_format_size) { munmap(wl->compositor_format_map, wl->compositor_format_size); wl->compositor_format_map = NULL; wl->compositor_format_size = 0; } void *map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); if (map != MAP_FAILED) { wl->compositor_format_map = map; wl->compositor_format_size = size; } } static void main_device(void *data, struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, struct wl_array *device) { struct vo_wayland_state *wl = data; // Remove any old devices and tranches if we get this again. struct vo_wayland_tranche *tranche, *tranche_tmp; wl_list_for_each_safe(tranche, tranche_tmp, &wl->tranche_list, link) { wl_list_remove(&tranche->link); talloc_free(tranche); } } static void tranche_done(void *data, struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1) { struct vo_wayland_state *wl = data; wl->current_tranche = NULL; } static void tranche_target_device(void *data, struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, struct wl_array *device) { struct vo_wayland_state *wl = data; struct vo_wayland_tranche *tranche = talloc_zero(wl, struct vo_wayland_tranche); dev_t *id; wl_array_for_each(id, device) { memcpy(&tranche->device_id, id, sizeof(dev_t)); break; } static_assert(sizeof(tranche->device_id) == sizeof(dev_t), ""); wl->current_tranche = tranche; get_planar_drm_formats(wl); wl_list_insert(&wl->tranche_list, &tranche->link); } static void tranche_formats(void *data, struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, struct wl_array *indices) { struct vo_wayland_state *wl = data; // Should never happen. if (!wl->compositor_format_map) { MP_WARN(wl, "Compositor did not send a format and modifier table!\n"); return; } struct vo_wayland_tranche *tranche = wl->current_tranche; if (!tranche) return; const struct compositor_format *formats = wl->compositor_format_map; uint16_t *index; MP_DBG(wl, "Querying available drm format and modifier pairs from tranche on device '%lu'\n", tranche->device_id); wl_array_for_each(index, indices) { MP_TARRAY_APPEND(tranche, tranche->compositor_formats, tranche->num_compositor_formats, (struct drm_format) { formats[*index].format, formats[*index].modifier, }); MP_DBG(wl, "Compositor supports drm format: '%s(%016" PRIx64 ")'\n", mp_tag_str(formats[*index].format), formats[*index].modifier); } } static void tranche_flags(void *data, struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1, uint32_t flags) { struct vo_wayland_state *wl = data; if (flags & ZWP_LINUX_DMABUF_FEEDBACK_V1_TRANCHE_FLAGS_SCANOUT) MP_DBG(wl, "Tranche has direct scanout.\n"); } static const struct zwp_linux_dmabuf_feedback_v1_listener dmabuf_feedback_listener = { done, format_table, main_device, tranche_done, tranche_target_device, tranche_formats, tranche_flags, }; static void registry_handle_add(void *data, struct wl_registry *reg, uint32_t id, const char *interface, uint32_t ver) { int found = 1; struct vo_wayland_state *wl = data; if (!strcmp(interface, wl_compositor_interface.name) && (ver >= 4) && found++) { #ifdef HAVE_WAYLAND_1_22 ver = MPMIN(ver, 6); /* Cap at 6 in case new events are added later. */ #else ver = 4; #endif wl->compositor = wl_registry_bind(reg, id, &wl_compositor_interface, ver); wl->surface = wl_compositor_create_surface(wl->compositor); wl->video_surface = wl_compositor_create_surface(wl->compositor); wl->osd_surface = wl_compositor_create_surface(wl->compositor); /* never accept input events on anything besides the main surface */ struct wl_region *region = wl_compositor_create_region(wl->compositor); wl_surface_set_input_region(wl->osd_surface, region); wl_surface_set_input_region(wl->video_surface, region); wl_region_destroy(region); wl->cursor_surface = wl_compositor_create_surface(wl->compositor); wl_surface_add_listener(wl->surface, &surface_listener, wl); } if (!strcmp(interface, wl_subcompositor_interface.name) && found++) { ver = 1; wl->subcompositor = wl_registry_bind(reg, id, &wl_subcompositor_interface, ver); } if (!strcmp (interface, zwp_linux_dmabuf_v1_interface.name) && (ver >= 4) && found++) { ver = 4; wl->dmabuf = wl_registry_bind(reg, id, &zwp_linux_dmabuf_v1_interface, ver); wl->dmabuf_feedback = zwp_linux_dmabuf_v1_get_default_feedback(wl->dmabuf); zwp_linux_dmabuf_feedback_v1_add_listener(wl->dmabuf_feedback, &dmabuf_feedback_listener, wl); } if (!strcmp (interface, wp_viewporter_interface.name) && found++) { ver = 1; wl->viewporter = wl_registry_bind (reg, id, &wp_viewporter_interface, ver); } if (!strcmp(interface, wl_data_device_manager_interface.name) && (ver >= 3) && found++) { ver = 3; wl->dnd_devman = wl_registry_bind(reg, id, &wl_data_device_manager_interface, ver); } if (!strcmp(interface, wl_output_interface.name) && (ver >= 2) && found++) { struct vo_wayland_output *output = talloc_zero(wl, struct vo_wayland_output); output->wl = wl; output->id = id; output->scale = 1; output->name = ""; ver = MPMIN(ver, 4); /* Cap at 4 in case new events are added later. */ output->output = wl_registry_bind(reg, id, &wl_output_interface, ver); wl_output_add_listener(output->output, &output_listener, output); wl_list_insert(&wl->output_list, &output->link); } if (!strcmp(interface, wl_seat_interface.name) && found++) { if (ver < 5) MP_WARN(wl, "Scrolling won't work because the compositor doesn't " "support version 5 of wl_seat protocol!\n"); ver = MPMIN(ver, 8); /* Cap at 8 in case new events are added later. */ struct vo_wayland_seat *seat = talloc_zero(wl, struct vo_wayland_seat); seat->wl = wl; seat->id = id; seat->seat = wl_registry_bind(reg, id, &wl_seat_interface, ver); wl_seat_add_listener(seat->seat, &seat_listener, seat); wl_list_insert(&wl->seat_list, &seat->link); } if (!strcmp(interface, wl_shm_interface.name) && found++) { ver = 1; wl->shm = wl_registry_bind(reg, id, &wl_shm_interface, ver); } if (!strcmp(interface, wp_content_type_manager_v1_interface.name) && found++) { ver = 1; wl->content_type_manager = wl_registry_bind(reg, id, &wp_content_type_manager_v1_interface, ver); } if (!strcmp(interface, wp_single_pixel_buffer_manager_v1_interface.name) && found++) { ver = 1; wl->single_pixel_manager = wl_registry_bind(reg, id, &wp_single_pixel_buffer_manager_v1_interface, ver); } if (!strcmp(interface, wp_fractional_scale_manager_v1_interface.name) && found++) { ver = 1; wl->fractional_scale_manager = wl_registry_bind(reg, id, &wp_fractional_scale_manager_v1_interface, ver); } #if HAVE_WAYLAND_PROTOCOLS_1_32 if (!strcmp(interface, wp_cursor_shape_manager_v1_interface.name) && found++) { ver = 1; wl->cursor_shape_manager = wl_registry_bind(reg, id, &wp_cursor_shape_manager_v1_interface, ver); } #endif if (!strcmp(interface, wp_presentation_interface.name) && found++) { ver = MPMIN(ver, 2); wl->presentation = wl_registry_bind(reg, id, &wp_presentation_interface, ver); wp_presentation_add_listener(wl->presentation, &pres_listener, wl); } if (!strcmp(interface, xdg_wm_base_interface.name) && found++) { ver = MPMIN(ver, 6); /* Cap at 6 in case new events are added later. */ wl->wm_base = wl_registry_bind(reg, id, &xdg_wm_base_interface, ver); xdg_wm_base_add_listener(wl->wm_base, &xdg_wm_base_listener, wl); } if (!strcmp(interface, zxdg_decoration_manager_v1_interface.name) && found++) { ver = 1; wl->xdg_decoration_manager = wl_registry_bind(reg, id, &zxdg_decoration_manager_v1_interface, ver); } if (!strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) && found++) { ver = 1; wl->idle_inhibit_manager = wl_registry_bind(reg, id, &zwp_idle_inhibit_manager_v1_interface, ver); } if (found > 1) MP_VERBOSE(wl, "Registered interface %s at version %d\n", interface, ver); } static void registry_handle_remove(void *data, struct wl_registry *reg, uint32_t id) { struct vo_wayland_state *wl = data; struct vo_wayland_output *output, *output_tmp; wl_list_for_each_safe(output, output_tmp, &wl->output_list, link) { if (output->id == id) { remove_output(output); return; } } struct vo_wayland_seat *seat, *seat_tmp; wl_list_for_each_safe(seat, seat_tmp, &wl->seat_list, link) { if (seat->id == id) { remove_seat(seat); return; } } } static const struct wl_registry_listener registry_listener = { registry_handle_add, registry_handle_remove, }; /* Static functions */ static void apply_keepaspect(struct vo_wayland_state *wl, int *width, int *height) { if (!wl->opts->keepaspect) return; int phys_width = handle_round(wl->scaling, *width); int phys_height = handle_round(wl->scaling, *height); // Ensure that the size actually changes before we start trying to actually // calculate anything so the wrong constraint for the rezie isn't choosen. if (wl->resizing && !wl->resizing_constraint && phys_width == mp_rect_w(wl->geometry) && phys_height == mp_rect_h(wl->geometry)) return; // We are doing a continuous resize (e.g. dragging with mouse), constrain the // aspect ratio against the height if the change is only in the height // coordinate. if (wl->resizing && !wl->resizing_constraint && phys_width == mp_rect_w(wl->geometry) && phys_height != mp_rect_h(wl->geometry)) { wl->resizing_constraint = MP_HEIGHT_CONSTRAINT; } else if (!wl->resizing_constraint) { wl->resizing_constraint = MP_WIDTH_CONSTRAINT; } if (wl->resizing_constraint == MP_HEIGHT_CONSTRAINT) { MPSWAP(int, *width, *height); MPSWAP(int, wl->reduced_width, wl->reduced_height); } double scale_factor = (double)*width / wl->reduced_width; *width = ceil(wl->reduced_width * scale_factor); if (wl->opts->keepaspect_window) *height = ceil(wl->reduced_height * scale_factor); if (wl->resizing_constraint == MP_HEIGHT_CONSTRAINT) { MPSWAP(int, *width, *height); MPSWAP(int, wl->reduced_width, wl->reduced_height); } } static void free_dnd_data(struct vo_wayland_state *wl) { // caller should close wl->dnd_fd if appropriate wl->dnd_action = -1; TA_FREEP(&wl->dnd_mime_type); wl->dnd_mime_score = 0; } static void check_dnd_fd(struct vo_wayland_state *wl) { if (wl->dnd_fd == -1) return; struct pollfd fdp = { wl->dnd_fd, POLLIN | POLLHUP, 0 }; if (poll(&fdp, 1, 0) <= 0) return; if (fdp.revents & POLLIN) { ssize_t data_read = 0; const size_t chunk_size = 256; bstr file_list = { .start = talloc_zero_size(NULL, chunk_size), }; while (1) { data_read = read(wl->dnd_fd, file_list.start + file_list.len, chunk_size); if (data_read == -1 && errno == EINTR) continue; else if (data_read <= 0) break; file_list.len += data_read; file_list.start = talloc_realloc_size(NULL, file_list.start, file_list.len + chunk_size); memset(file_list.start + file_list.len, 0, chunk_size); } if (data_read == -1) { MP_VERBOSE(wl, "DND aborted (read error)\n"); } else { MP_VERBOSE(wl, "Read %zu bytes from the DND fd\n", file_list.len); if (wl->dnd_offer) wl_data_offer_finish(wl->dnd_offer); assert(wl->dnd_action >= 0); mp_event_drop_mime_data(wl->vo->input_ctx, wl->dnd_mime_type, file_list, wl->dnd_action); } talloc_free(file_list.start); free_dnd_data(wl); } if (fdp.revents & (POLLIN | POLLERR | POLLHUP)) { if (wl->dnd_action >= 0) MP_VERBOSE(wl, "DND aborted (hang up or error)\n"); free_dnd_data(wl); close(wl->dnd_fd); wl->dnd_fd = -1; } } static int check_for_resize(struct vo_wayland_state *wl, int edge_pixels, enum xdg_toplevel_resize_edge *edge) { if (wl->opts->fullscreen || wl->opts->window_maximized) return 0; int pos[2] = { wl->mouse_x, wl->mouse_y }; int left_edge = pos[0] < edge_pixels; int top_edge = pos[1] < edge_pixels; int right_edge = pos[0] > (mp_rect_w(wl->geometry) - edge_pixels); int bottom_edge = pos[1] > (mp_rect_h(wl->geometry) - edge_pixels); if (left_edge) { *edge = XDG_TOPLEVEL_RESIZE_EDGE_LEFT; if (top_edge) *edge = XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT; else if (bottom_edge) *edge = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT; } else if (right_edge) { *edge = XDG_TOPLEVEL_RESIZE_EDGE_RIGHT; if (top_edge) *edge = XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT; else if (bottom_edge) *edge = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT; } else if (top_edge) { *edge = XDG_TOPLEVEL_RESIZE_EDGE_TOP; } else if (bottom_edge) { *edge = XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM; } else { *edge = 0; return 0; } return 1; } static bool create_input(struct vo_wayland_state *wl) { wl->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); if (!wl->xkb_context) { MP_ERR(wl, "failed to initialize input: check xkbcommon\n"); return 1; } return 0; } static int create_viewports(struct vo_wayland_state *wl) { wl->viewport = wp_viewporter_get_viewport(wl->viewporter, wl->surface); wl->cursor_viewport = wp_viewporter_get_viewport(wl->viewporter, wl->cursor_surface); wl->osd_viewport = wp_viewporter_get_viewport(wl->viewporter, wl->osd_surface); wl->video_viewport = wp_viewporter_get_viewport(wl->viewporter, wl->video_surface); if (!wl->viewport || !wl->osd_viewport || !wl->video_viewport) { MP_ERR(wl, "failed to create viewport interfaces!\n"); return 1; } return 0; } static int create_xdg_surface(struct vo_wayland_state *wl) { wl->xdg_surface = xdg_wm_base_get_xdg_surface(wl->wm_base, wl->surface); xdg_surface_add_listener(wl->xdg_surface, &xdg_surface_listener, wl); wl->xdg_toplevel = xdg_surface_get_toplevel(wl->xdg_surface); xdg_toplevel_add_listener(wl->xdg_toplevel, &xdg_toplevel_listener, wl); if (!wl->xdg_surface || !wl->xdg_toplevel) { MP_ERR(wl, "failed to create xdg_surface and xdg_toplevel!\n"); return 1; } return 0; } static void add_feedback(struct vo_wayland_feedback_pool *fback_pool, struct wp_presentation_feedback *fback) { for (int i = 0; i < fback_pool->len; ++i) { if (!fback_pool->fback[i]) { fback_pool->fback[i] = fback; break; } else if (i == fback_pool->len - 1) { // Shouldn't happen in practice. wp_presentation_feedback_destroy(fback_pool->fback[i]); fback_pool->fback[i] = fback; } } } static bool devices_are_equal(dev_t a, dev_t b) { bool ret = false; #if HAVE_DRM drmDevice *deviceA, *deviceB; if (!drmGetDeviceFromDevId(a, 0, &deviceA) && !drmGetDeviceFromDevId(b, 0, &deviceB)) ret = drmDevicesEqual(deviceA, deviceB); drmFreeDevice(&deviceA); drmFreeDevice(&deviceB); #endif return ret; } static void do_minimize(struct vo_wayland_state *wl) { if (wl->opts->window_minimized) xdg_toplevel_set_minimized(wl->xdg_toplevel); } static char **get_displays_spanned(struct vo_wayland_state *wl) { char **names = NULL; int displays_spanned = 0; struct vo_wayland_output *output; wl_list_for_each(output, &wl->output_list, link) { if (output->has_surface) { char *name = output->name ? output->name : output->model; MP_TARRAY_APPEND(NULL, names, displays_spanned, talloc_strdup(NULL, name)); } } MP_TARRAY_APPEND(NULL, names, displays_spanned, NULL); return names; } static void get_planar_drm_formats(struct vo_wayland_state *wl) { #if HAVE_DRM struct vo_wayland_tranche *tranche; wl_list_for_each(tranche, &wl->tranche_list, link) { // If there is a device equality, just copy the pointer over. if (devices_are_equal(tranche->device_id, wl->current_tranche->device_id)) { wl->current_tranche->planar_formats = tranche->planar_formats; wl->current_tranche->num_planar_formats = tranche->num_planar_formats; return; } } tranche = wl->current_tranche; drmDevice *device = NULL; drmModePlaneRes *res = NULL; drmModePlane *plane = NULL; if (drmGetDeviceFromDevId(tranche->device_id, 0, &device) != 0) { MP_VERBOSE(wl, "Unable to get drm device from device id: %s\n", mp_strerror(errno)); goto done; } // Pick the first path we get and hope for the best. char *path = NULL; for (int i = 0; i < device->available_nodes; ++i) { if (device->nodes[0]) { path = device->nodes[0]; break; } } if (!path || !path[0]) { MP_VERBOSE(wl, "Unable to find a valid drm device node.\n"); goto done; } int fd = open(path, O_RDWR | O_CLOEXEC); if (fd < 0) { MP_VERBOSE(wl, "Unable to open DRM node path '%s': %s\n", path, mp_strerror(errno)); goto done; } // Need to set this in order to access plane information. if (drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1)) { MP_VERBOSE(wl, "Unable to set DRM atomic cap: %s\n", mp_strerror(errno)); goto done; } res = drmModeGetPlaneResources(fd); if (!res) { MP_VERBOSE(wl, "Unable to get DRM plane resources: %s\n", mp_strerror(errno)); goto done; } if (!res->count_planes) { MP_VERBOSE(wl, "No DRM planes were found.\n"); goto done; } // Only check the formats on the first primary plane we find as a crude guess. int index = -1; for (int i = 0; i < res->count_planes; ++i) { drmModeObjectProperties *props = NULL; props = drmModeObjectGetProperties(fd, res->planes[i], DRM_MODE_OBJECT_PLANE); if (!props) { MP_VERBOSE(wl, "Unable to get DRM plane properties: %s\n", mp_strerror(errno)); continue; } for (int j = 0; j < props->count_props; ++j) { drmModePropertyRes *prop = drmModeGetProperty(fd, props->props[j]); if (!prop) { MP_VERBOSE(wl, "Unable to get DRM plane property: %s\n", mp_strerror(errno)); continue; } if (strcmp(prop->name, "type") == 0) { for (int k = 0; k < prop->count_values; ++k) { if (prop->values[k] == DRM_PLANE_TYPE_PRIMARY) index = i; } } drmModeFreeProperty(prop); if (index > -1) break; } drmModeFreeObjectProperties(props); if (index > -1) break; } if (index == -1) { MP_VERBOSE(wl, "Unable to get DRM plane: %s\n", mp_strerror(errno)); goto done; } plane = drmModeGetPlane(fd, res->planes[index]); tranche->num_planar_formats = plane->count_formats; tranche->planar_formats = talloc_zero_array(tranche, int, tranche->num_planar_formats); for (int i = 0; i < tranche->num_planar_formats; ++i) { MP_DBG(wl, "DRM primary plane supports drm format: %s\n", mp_tag_str(plane->formats[i])); tranche->planar_formats[i] = plane->formats[i]; } done: drmModeFreePlane(plane); drmModeFreePlaneResources(res); drmFreeDevice(&device); #endif } static int get_mods(struct vo_wayland_seat *s) { static char* const mod_names[] = { XKB_MOD_NAME_SHIFT, XKB_MOD_NAME_CTRL, XKB_MOD_NAME_ALT, XKB_MOD_NAME_LOGO, }; static const int mods[] = { MP_KEY_MODIFIER_SHIFT, MP_KEY_MODIFIER_CTRL, MP_KEY_MODIFIER_ALT, MP_KEY_MODIFIER_META, }; int modifiers = 0; for (int n = 0; n < MP_ARRAY_SIZE(mods); n++) { xkb_mod_index_t index = xkb_keymap_mod_get_index(s->xkb_keymap, mod_names[n]); if (index != XKB_MOD_INVALID && xkb_state_mod_index_is_active(s->xkb_state, index, XKB_STATE_MODS_EFFECTIVE)) modifiers |= mods[n]; } return modifiers; } static void get_shape_device(struct vo_wayland_state *wl, struct vo_wayland_seat *s) { #if HAVE_WAYLAND_PROTOCOLS_1_32 if (!s->cursor_shape_device && wl->cursor_shape_manager) { s->cursor_shape_device = wp_cursor_shape_manager_v1_get_pointer(wl->cursor_shape_manager, s->pointer); } #endif } static int greatest_common_divisor(int a, int b) { int rem = a % b; if (rem == 0) return b; return greatest_common_divisor(b, rem); } static void guess_focus(struct vo_wayland_state *wl) { // We can't actually know if the window is focused or not in wayland, // so just guess it with some common sense. Obviously won't work if // the user has no keyboard. We flag has_keyboard_input if // at least one seat has it. bool has_keyboard_input = false; struct vo_wayland_seat *seat; wl_list_for_each(seat, &wl->seat_list, link) { if (seat->has_keyboard_input) { has_keyboard_input = true; } } if ((!wl->focused && wl->activated && has_keyboard_input) || (wl->focused && !wl->activated)) { wl->focused = !wl->focused; wl->pending_vo_events |= VO_EVENT_FOCUS; } } static struct vo_wayland_output *find_output(struct vo_wayland_state *wl) { int index = 0; struct mp_vo_opts *opts = wl->opts; int screen_id = opts->fullscreen ? opts->fsscreen_id : opts->screen_id; char *screen_name = opts->fullscreen ? opts->fsscreen_name : opts->screen_name; struct vo_wayland_output *output = NULL; struct vo_wayland_output *fallback_output = NULL; wl_list_for_each(output, &wl->output_list, link) { if (index == 0) fallback_output = output; if (screen_id == -1 && !screen_name) return output; if (screen_id == -1 && screen_name && !strcmp(screen_name, output->name)) return output; if (screen_id == -1 && screen_name && !strcmp(screen_name, output->model)) return output; if (screen_id == index++) return output; } if (!fallback_output) { MP_ERR(wl, "No screens could be found!\n"); return NULL; } else if (screen_id >= 0) { MP_WARN(wl, "Screen index %i not found/unavailable! Falling back to screen 0!\n", screen_id); } else if (screen_name && screen_name[0]) { MP_WARN(wl, "Screen name %s not found/unavailable! Falling back to screen 0!\n", screen_name); } return fallback_output; } static int lookupkey(int key) { const char *passthrough_keys = " -+*/<>`~!@#$%^&()_{}:;\"\',.?\\|=[]"; int mpkey = 0; if ((key >= 'a' && key <= 'z') || (key >= 'A' && key <= 'Z') || (key >= '0' && key <= '9') || (key > 0 && key < 256 && strchr(passthrough_keys, key))) mpkey = key; if (!mpkey) mpkey = lookup_keymap_table(keymap, key); return mpkey; } static void handle_key_input(struct vo_wayland_seat *s, uint32_t key, uint32_t state, bool no_emit) { struct vo_wayland_state *wl = s->wl; switch (state) { case WL_KEYBOARD_KEY_STATE_RELEASED: state = MP_KEY_STATE_UP; break; case WL_KEYBOARD_KEY_STATE_PRESSED: state = MP_KEY_STATE_DOWN; break; default: return; } if (no_emit) state = state | MP_KEY_STATE_SET_ONLY; s->keyboard_code = key + 8; xkb_keysym_t sym = xkb_state_key_get_one_sym(s->xkb_state, s->keyboard_code); int mpkey = lookupkey(sym); if (mpkey) { mp_input_put_key(wl->vo->input_ctx, mpkey | state | s->mpmod); } else { char str[128]; if (xkb_keysym_to_utf8(sym, str, sizeof(str)) > 0) { mp_input_put_key_utf8(wl->vo->input_ctx, state | s->mpmod, bstr0(str)); } else { // Assume a modifier was pressed and handle it in the mod event instead. // If a modifier is released before a regular key, also release that // key to not activate it again by accident. if (state & MP_KEY_STATE_UP) { s->mpkey = 0; mp_input_put_key(wl->vo->input_ctx, MP_INPUT_RELEASE_ALL); } return; } } if (state & MP_KEY_STATE_DOWN) s->mpkey = mpkey; if (mpkey && (state & MP_KEY_STATE_UP)) s->mpkey = 0; } // Avoid possible floating point errors. static int handle_round(int scale, int n) { return (scale * n + WAYLAND_SCALE_FACTOR / 2) / WAYLAND_SCALE_FACTOR; } static void prepare_resize(struct vo_wayland_state *wl) { int32_t width = mp_rect_w(wl->geometry) / wl->scaling_factor; int32_t height = mp_rect_h(wl->geometry) / wl->scaling_factor; xdg_surface_set_window_geometry(wl->xdg_surface, 0, 0, width, height); wl->pending_vo_events |= VO_EVENT_RESIZE; } static void request_decoration_mode(struct vo_wayland_state *wl, uint32_t mode) { wl->requested_decoration = mode; zxdg_toplevel_decoration_v1_set_mode(wl->xdg_toplevel_decoration, mode); } static void rescale_geometry(struct vo_wayland_state *wl, double old_scale) { if (!wl->opts->hidpi_window_scale && !wl->locked_size) return; double factor = old_scale / wl->scaling; wl->window_size.x1 /= factor; wl->window_size.y1 /= factor; wl->geometry.x1 /= factor; wl->geometry.y1 /= factor; } static void clean_feedback_pool(struct vo_wayland_feedback_pool *fback_pool) { for (int i = 0; i < fback_pool->len; ++i) { if (fback_pool->fback[i]) { wp_presentation_feedback_destroy(fback_pool->fback[i]); fback_pool->fback[i] = NULL; } } } static void remove_feedback(struct vo_wayland_feedback_pool *fback_pool, struct wp_presentation_feedback *fback) { for (int i = 0; i < fback_pool->len; ++i) { if (fback_pool->fback[i] == fback) { wp_presentation_feedback_destroy(fback); fback_pool->fback[i] = NULL; break; } } } static void remove_output(struct vo_wayland_output *out) { if (!out) return; MP_VERBOSE(out->wl, "Deregistering output %s %s (0x%x)\n", out->make, out->model, out->id); wl_list_remove(&out->link); wl_output_destroy(out->output); talloc_free(out->make); talloc_free(out->model); talloc_free(out); return; } static void remove_seat(struct vo_wayland_seat *seat) { if (!seat) return; MP_VERBOSE(seat->wl, "Deregistering seat 0x%x\n", seat->id); wl_list_remove(&seat->link); if (seat == seat->wl->last_button_seat) seat->wl->last_button_seat = NULL; if (seat->keyboard) wl_keyboard_destroy(seat->keyboard); if (seat->pointer) wl_pointer_destroy(seat->pointer); if (seat->touch) wl_touch_destroy(seat->touch); if (seat->dnd_ddev) wl_data_device_destroy(seat->dnd_ddev); #if HAVE_WAYLAND_PROTOCOLS_1_32 if (seat->cursor_shape_device) wp_cursor_shape_device_v1_destroy(seat->cursor_shape_device); #endif if (seat->xkb_keymap) xkb_keymap_unref(seat->xkb_keymap); if (seat->xkb_state) xkb_state_unref(seat->xkb_state); wl_seat_destroy(seat->seat); talloc_free(seat); return; } static void set_content_type(struct vo_wayland_state *wl) { if (!wl->content_type_manager) return; // handle auto; if (wl->opts->wl_content_type == -1) { wp_content_type_v1_set_content_type(wl->content_type, wl->current_content_type); } else { wp_content_type_v1_set_content_type(wl->content_type, wl->opts->wl_content_type); } } static void set_cursor_shape(struct vo_wayland_seat *s) { #if HAVE_WAYLAND_PROTOCOLS_1_32 wp_cursor_shape_device_v1_set_shape(s->cursor_shape_device, s->pointer_enter_serial, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT); #endif } static int set_cursor_visibility(struct vo_wayland_seat *s, bool on) { if (!s) return VO_FALSE; struct vo_wayland_state *wl = s->wl; wl->cursor_visible = on; if (on) { if (s->cursor_shape_device) { set_cursor_shape(s); } else { if (spawn_cursor(wl)) return VO_FALSE; struct wl_cursor_image *img = wl->default_cursor->images[0]; struct wl_buffer *buffer = wl_cursor_image_get_buffer(img); if (!buffer) return VO_FALSE; double scale = MPMAX(wl->scaling_factor, 1); wl_pointer_set_cursor(s->pointer, s->pointer_enter_serial, wl->cursor_surface, img->hotspot_x / scale, img->hotspot_y / scale); wp_viewport_set_destination(wl->cursor_viewport, lround(img->width / scale), lround(img->height / scale)); wl_surface_attach(wl->cursor_surface, buffer, 0, 0); wl_surface_damage_buffer(wl->cursor_surface, 0, 0, img->width, img->height); } wl_surface_commit(wl->cursor_surface); } else { wl_pointer_set_cursor(s->pointer, s->pointer_enter_serial, NULL, 0, 0); } return VO_TRUE; } static int set_cursor_visibility_all_seats(struct vo_wayland_state *wl, bool on) { bool unavailable = true; bool failed = false; struct vo_wayland_seat *seat; wl_list_for_each(seat, &wl->seat_list, link) { if (seat->pointer) { unavailable = false; if (set_cursor_visibility(seat, on) == VO_FALSE) failed = true; } } if (unavailable) return VO_NOTAVAIL; if (failed) return VO_FALSE; return VO_TRUE; } static void set_geometry(struct vo_wayland_state *wl, bool resize) { struct vo *vo = wl->vo; if (!wl->current_output) return; struct vo_win_geometry geo; struct mp_rect screenrc = wl->current_output->geometry; vo_calc_window_geometry(vo, &screenrc, &screenrc, wl->scaling_factor, false, &geo); vo_apply_window_geometry(vo, &geo); int gcd = greatest_common_divisor(vo->dwidth, vo->dheight); wl->reduced_width = vo->dwidth / gcd; wl->reduced_height = vo->dheight / gcd; if (!wl->initial_size_hint) wl->window_size = (struct mp_rect){0, 0, vo->dwidth, vo->dheight}; wl->initial_size_hint = false; if (resize) { if (!wl->locked_size) wl->geometry = wl->window_size; prepare_resize(wl); } } static void set_input_region(struct vo_wayland_state *wl, bool passthrough) { if (passthrough) { struct wl_region *region = wl_compositor_create_region(wl->compositor); wl_surface_set_input_region(wl->surface, region); wl_region_destroy(region); } else { wl_surface_set_input_region(wl->surface, NULL); } } static int set_screensaver_inhibitor(struct vo_wayland_state *wl, int state) { if (!wl->idle_inhibit_manager) return VO_NOTIMPL; if (state == (!!wl->idle_inhibitor)) return VO_TRUE; if (state) { MP_VERBOSE(wl, "Enabling idle inhibitor\n"); struct zwp_idle_inhibit_manager_v1 *mgr = wl->idle_inhibit_manager; wl->idle_inhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor(mgr, wl->callback_surface); } else { MP_VERBOSE(wl, "Disabling the idle inhibitor\n"); zwp_idle_inhibitor_v1_destroy(wl->idle_inhibitor); wl->idle_inhibitor = NULL; } return VO_TRUE; } static void set_surface_scaling(struct vo_wayland_state *wl) { if (wl->scale_configured && (wl->fractional_scale_manager || wl_surface_get_version(wl->surface) >= 6)) { return; } double old_scale = wl->scaling; wl->scaling = wl->current_output->scale; wl->scaling_factor = wl->scaling / WAYLAND_SCALE_FACTOR; rescale_geometry(wl, old_scale); wl->pending_vo_events |= VO_EVENT_DPI; } static void set_window_bounds(struct vo_wayland_state *wl) { // If the user has set geometry/autofit and the option is auto, // don't use these. if (wl->opts->wl_configure_bounds == -1 && (wl->opts->geometry.wh_valid || wl->opts->autofit.wh_valid || wl->opts->autofit_larger.wh_valid || wl->opts->autofit_smaller.wh_valid)) { return; } apply_keepaspect(wl, &wl->bounded_width, &wl->bounded_height); if (wl->bounded_width && wl->bounded_width < wl->window_size.x1) wl->window_size.x1 = wl->bounded_width; if (wl->bounded_height && wl->bounded_height < wl->window_size.y1) wl->window_size.y1 = wl->bounded_height; } static bool single_output_spanned(struct vo_wayland_state *wl) { int outputs = 0; struct vo_wayland_output *output; wl_list_for_each(output, &wl->output_list, link) { if (output->has_surface) ++outputs; if (outputs > 1) return false; } return wl->current_output && outputs == 1; } static int spawn_cursor(struct vo_wayland_state *wl) { if (wl->allocated_cursor_scale == wl->scaling) { return 0; } else if (wl->cursor_theme) { wl_cursor_theme_destroy(wl->cursor_theme); } const char *xcursor_theme = getenv("XCURSOR_THEME"); const char *size_str = getenv("XCURSOR_SIZE"); int size = 24; if (size_str != NULL) { errno = 0; char *end; long size_long = strtol(size_str, &end, 10); if (!*end && !errno && size_long > 0 && size_long <= INT_MAX) size = (int)size_long; } wl->cursor_theme = wl_cursor_theme_load(xcursor_theme, handle_round(wl->scaling, size), wl->shm); if (!wl->cursor_theme) { MP_ERR(wl, "Unable to load cursor theme!\n"); return 1; } wl->default_cursor = wl_cursor_theme_get_cursor(wl->cursor_theme, "default"); if (!wl->default_cursor) wl->default_cursor = wl_cursor_theme_get_cursor(wl->cursor_theme, "left_ptr"); if (!wl->default_cursor) { MP_ERR(wl, "Unable to get default and left_ptr XCursor from theme!\n"); return 1; } wl->allocated_cursor_scale = wl->scaling; return 0; } static void toggle_fullscreen(struct vo_wayland_state *wl) { bool specific_screen = wl->opts->fsscreen_id >= 0 || wl->opts->fsscreen_name; if (wl->opts->fullscreen && !specific_screen) { xdg_toplevel_set_fullscreen(wl->xdg_toplevel, NULL); } else if (wl->opts->fullscreen && specific_screen) { struct vo_wayland_output *output = find_output(wl); xdg_toplevel_set_fullscreen(wl->xdg_toplevel, output->output); } else { wl->state_change = wl->reconfigured; xdg_toplevel_unset_fullscreen(wl->xdg_toplevel); } } static void toggle_maximized(struct vo_wayland_state *wl) { if (wl->opts->window_maximized) { xdg_toplevel_set_maximized(wl->xdg_toplevel); } else { wl->state_change = wl->reconfigured; xdg_toplevel_unset_maximized(wl->xdg_toplevel); } } static void update_app_id(struct vo_wayland_state *wl) { xdg_toplevel_set_app_id(wl->xdg_toplevel, wl->opts->appid); } static void update_output_scaling(struct vo_wayland_state *wl) { double old_scale = wl->scaling; wl->scaling = wl->pending_scaling; wl->scaling_factor = wl->scaling / WAYLAND_SCALE_FACTOR; rescale_geometry(wl, old_scale); set_geometry(wl, false); prepare_resize(wl); wl->need_rescale = false; wl->pending_vo_events |= VO_EVENT_DPI; } static void update_output_geometry(struct vo_wayland_state *wl, struct mp_rect old_geometry, struct mp_rect old_output_geometry) { if (wl->need_rescale) { update_output_scaling(wl); return; } bool force_resize = false; bool use_output_scale = wl_surface_get_version(wl->surface) < 6 && !wl->fractional_scale_manager && wl->scaling != wl->current_output->scale; if (use_output_scale) { set_surface_scaling(wl); force_resize = true; } if (!mp_rect_equals(&old_output_geometry, &wl->current_output->geometry)) { set_geometry(wl, false); force_resize = true; } if (!mp_rect_equals(&old_geometry, &wl->geometry) || force_resize) prepare_resize(wl); } static int update_window_title(struct vo_wayland_state *wl, const char *title) { /* The xdg-shell protocol requires that the title is UTF-8. */ void *tmp = talloc_new(NULL); struct bstr b_title = bstr_sanitize_utf8_latin1(tmp, bstr0(title)); xdg_toplevel_set_title(wl->xdg_toplevel, bstrto0(tmp, b_title)); talloc_free(tmp); return VO_TRUE; } static void wayland_dispatch_events(struct vo_wayland_state *wl, int nfds, int64_t timeout_ns) { if (wl->display_fd == -1) return; struct pollfd fds[2] = { {.fd = wl->display_fd, .events = POLLIN }, {.fd = wl->wakeup_pipe[0], .events = POLLIN }, }; while (wl_display_prepare_read(wl->display) != 0) wl_display_dispatch_pending(wl->display); wl_display_flush(wl->display); mp_poll(fds, nfds, timeout_ns); if (fds[0].revents & POLLIN) { wl_display_read_events(wl->display); } else { wl_display_cancel_read(wl->display); } if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) { MP_FATAL(wl, "Error occurred on the display fd\n"); wl->display_fd = -1; mp_input_put_key(wl->vo->input_ctx, MP_KEY_CLOSE_WIN); } if (fds[1].revents & POLLIN) mp_flush_wakeup_pipe(wl->wakeup_pipe[0]); wl_display_dispatch_pending(wl->display); } static void begin_dragging(struct vo_wayland_state *wl) { struct vo_wayland_seat *s = wl->last_button_seat; if (!mp_input_test_dragging(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y) && !wl->locked_size && s) { xdg_toplevel_move(wl->xdg_toplevel, s->seat, s->pointer_button_serial); wl->last_button_seat = NULL; mp_input_put_key(wl->vo->input_ctx, MP_INPUT_RELEASE_ALL); } } /* Non-static */ int vo_wayland_allocate_memfd(struct vo *vo, size_t size) { #if !HAVE_MEMFD_CREATE return VO_ERROR; #else int fd = memfd_create("mpv", MFD_CLOEXEC | MFD_ALLOW_SEALING); if (fd < 0) { MP_ERR(vo, "Failed to allocate memfd: %s\n", mp_strerror(errno)); return VO_ERROR; } fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL); if (posix_fallocate(fd, 0, size) == 0) return fd; close(fd); MP_ERR(vo, "Failed to allocate memfd: %s\n", mp_strerror(errno)); return VO_ERROR; #endif } bool vo_wayland_check_visible(struct vo *vo) { struct vo_wayland_state *wl = vo->wl; bool render = !wl->hidden || wl->opts->force_render; wl->frame_wait = true; return render; } int vo_wayland_control(struct vo *vo, int *events, int request, void *arg) { struct vo_wayland_state *wl = vo->wl; struct mp_vo_opts *opts = wl->opts; wl_display_dispatch_pending(wl->display); switch (request) { case VOCTRL_CHECK_EVENTS: { check_dnd_fd(wl); *events |= wl->pending_vo_events; if (*events & VO_EVENT_RESIZE) { *events |= VO_EVENT_EXPOSE; wl->frame_wait = false; wl->timeout_count = 0; wl->hidden = false; } wl->pending_vo_events = 0; return VO_TRUE; } case VOCTRL_VO_OPTS_CHANGED: { void *opt; while (m_config_cache_get_next_changed(wl->opts_cache, &opt)) { if (opt == &opts->appid) update_app_id(wl); if (opt == &opts->border) { // This is stupid but the value of border shouldn't be written // unless we get a configure event. Change it back to its old // value and let configure_decorations handle it after the request. if (wl->xdg_toplevel_decoration) { int requested_border_mode = opts->border; opts->border = !opts->border; m_config_cache_write_opt(wl->opts_cache, &opts->border); request_decoration_mode( wl, requested_border_mode ? ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE : ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE); } else { opts->border = false; m_config_cache_write_opt(wl->opts_cache, &wl->opts->border); } } if (opt == &opts->wl_content_type) set_content_type(wl); if (opt == &opts->cursor_passthrough) set_input_region(wl, opts->cursor_passthrough); if (opt == &opts->fullscreen) toggle_fullscreen(wl); if (opt == &opts->window_maximized) toggle_maximized(wl); if (opt == &opts->window_minimized) do_minimize(wl); if (opt == &opts->geometry || opt == &opts->autofit || opt == &opts->autofit_smaller || opt == &opts->autofit_larger) { wl->state_change = true; set_geometry(wl, true); } } return VO_TRUE; } case VOCTRL_CONTENT_TYPE: { wl->current_content_type = *(enum mp_content_type *)arg; set_content_type(wl); return VO_TRUE; } case VOCTRL_GET_FOCUSED: { *(bool *)arg = wl->focused; return VO_TRUE; } case VOCTRL_GET_DISPLAY_NAMES: { *(char ***)arg = get_displays_spanned(wl); return VO_TRUE; } case VOCTRL_GET_UNFS_WINDOW_SIZE: { int *s = arg; if (wl->opts->window_maximized || wl->tiled) { s[0] = mp_rect_w(wl->geometry); s[1] = mp_rect_h(wl->geometry); } else { s[0] = mp_rect_w(wl->window_size); s[1] = mp_rect_h(wl->window_size); } return VO_TRUE; } case VOCTRL_SET_UNFS_WINDOW_SIZE: { int *s = arg; wl->window_size.x0 = 0; wl->window_size.y0 = 0; wl->window_size.x1 = s[0]; wl->window_size.y1 = s[1]; if (!wl->opts->fullscreen && !wl->tiled) { wl->state_change = true; if (wl->opts->window_maximized) { xdg_toplevel_unset_maximized(wl->xdg_toplevel); wl_display_dispatch_pending(wl->display); /* Make sure the compositor let us unmaximize */ if (wl->opts->window_maximized) return VO_TRUE; } wl->geometry = wl->window_size; prepare_resize(wl); } return VO_TRUE; } case VOCTRL_GET_DISPLAY_FPS: { struct vo_wayland_output *out; if (wl->current_output) { out = wl->current_output; } else { out = find_output(wl); } if (!out) return VO_NOTAVAIL; *(double *)arg = out->refresh_rate; return VO_TRUE; } case VOCTRL_GET_DISPLAY_RES: { struct vo_wayland_output *out; if (wl->current_output) { out = wl->current_output; } else { out = find_output(wl); } if (!out) return VO_NOTAVAIL; ((int *)arg)[0] = out->geometry.x1; ((int *)arg)[1] = out->geometry.y1; return VO_TRUE; } case VOCTRL_GET_HIDPI_SCALE: { if (!wl->scaling_factor) return VO_NOTAVAIL; *(double *)arg = wl->scaling_factor; return VO_TRUE; } case VOCTRL_BEGIN_DRAGGING: begin_dragging(wl); return VO_TRUE; case VOCTRL_UPDATE_WINDOW_TITLE: return update_window_title(wl, (const char *)arg); case VOCTRL_SET_CURSOR_VISIBILITY: return set_cursor_visibility_all_seats(wl, *(bool *)arg); case VOCTRL_KILL_SCREENSAVER: return set_screensaver_inhibitor(wl, true); case VOCTRL_RESTORE_SCREENSAVER: return set_screensaver_inhibitor(wl, false); } return VO_NOTIMPL; } void vo_wayland_handle_scale(struct vo_wayland_state *wl) { wp_viewport_set_destination(wl->viewport, lround(mp_rect_w(wl->geometry) / wl->scaling_factor), lround(mp_rect_h(wl->geometry) / wl->scaling_factor)); } bool vo_wayland_valid_format(struct vo_wayland_state *wl, uint32_t drm_format, uint64_t modifier) { #if HAVE_DRM // Tranches are grouped by preference and the first tranche is at the end of // the list. It doesn't really matter for us since we search everything // anyways, but might as well start from the most preferred tranche. struct vo_wayland_tranche *tranche; wl_list_for_each_reverse(tranche, &wl->tranche_list, link) { bool supported_compositor_format = false; struct drm_format *formats = tranche->compositor_formats; for (int i = 0; i < tranche->num_compositor_formats; ++i) { if (formats[i].format != drm_format) continue; if (modifier == formats[i].modifier && modifier != DRM_FORMAT_MOD_INVALID) return true; supported_compositor_format = true; } if (supported_compositor_format && tranche->planar_formats) { for (int i = 0; i < tranche->num_planar_formats; i++) { if (drm_format == tranche->planar_formats[i]) return true; } } } #endif return false; } bool vo_wayland_init(struct vo *vo) { if (!getenv("WAYLAND_DISPLAY")) goto err; vo->wl = talloc_zero(NULL, struct vo_wayland_state); struct vo_wayland_state *wl = vo->wl; *wl = (struct vo_wayland_state) { .display = wl_display_connect(NULL), .vo = vo, .log = mp_log_new(wl, vo->log, "wayland"), .bounded_width = 0, .bounded_height = 0, .refresh_interval = 0, .scaling = WAYLAND_SCALE_FACTOR, .wakeup_pipe = {-1, -1}, .display_fd = -1, .dnd_fd = -1, .cursor_visible = true, .opts_cache = m_config_cache_alloc(wl, vo->global, &vo_sub_opts), }; wl->opts = wl->opts_cache->opts; bool using_dmabuf_wayland = !strcmp(wl->vo->driver->name, "dmabuf-wayland"); wl_list_init(&wl->output_list); wl_list_init(&wl->seat_list); wl_list_init(&wl->tranche_list); if (!wl->display) goto err; if (create_input(wl)) goto err; wl->registry = wl_display_get_registry(wl->display); wl_registry_add_listener(wl->registry, ®istry_listener, wl); /* Do a roundtrip to run the registry */ wl_display_roundtrip(wl->display); if (!wl->surface) { MP_FATAL(wl, "Compositor doesn't support %s (ver. 4)\n", wl_compositor_interface.name); goto err; } if (!wl->wm_base) { MP_FATAL(wl, "Compositor doesn't support the required %s protocol!\n", xdg_wm_base_interface.name); goto err; } if (wl_list_empty(&wl->output_list)) { MP_FATAL(wl, "No outputs found or compositor doesn't support %s (ver. 2)\n", wl_output_interface.name); goto err; } if (!wl->viewporter) { MP_FATAL(wl, "Compositor doesn't support the required %s protocol!\n", wp_viewporter_interface.name); goto err; } /* Can't be initialized during registry due to multi-protocol dependence */ if (create_viewports(wl)) goto err; if (create_xdg_surface(wl)) goto err; if (wl->subcompositor) { wl->osd_subsurface = wl_subcompositor_get_subsurface(wl->subcompositor, wl->osd_surface, wl->video_surface); wl->video_subsurface = wl_subcompositor_get_subsurface(wl->subcompositor, wl->video_surface, wl->surface); } if (wl->content_type_manager) { wl->content_type = wp_content_type_manager_v1_get_surface_content_type(wl->content_type_manager, wl->surface); } else { MP_VERBOSE(wl, "Compositor doesn't support the %s protocol!\n", wp_content_type_manager_v1_interface.name); } if (!wl->single_pixel_manager) { MP_VERBOSE(wl, "Compositor doesn't support the %s protocol!\n", wp_single_pixel_buffer_manager_v1_interface.name); } if (wl->fractional_scale_manager) { wl->fractional_scale = wp_fractional_scale_manager_v1_get_fractional_scale(wl->fractional_scale_manager, wl->surface); wp_fractional_scale_v1_add_listener(wl->fractional_scale, &fractional_scale_listener, wl); } else { MP_VERBOSE(wl, "Compositor doesn't support the %s protocol!\n", wp_fractional_scale_manager_v1_interface.name); } #if HAVE_WAYLAND_PROTOCOLS_1_32 if (!wl->cursor_shape_manager) { MP_VERBOSE(wl, "Compositor doesn't support the %s protocol!\n", wp_cursor_shape_manager_v1_interface.name); } #endif if (wl->dnd_devman) { struct vo_wayland_seat *seat; wl_list_for_each(seat, &wl->seat_list, link) { seat->dnd_ddev = wl_data_device_manager_get_data_device(wl->dnd_devman, seat->seat); wl_data_device_add_listener(seat->dnd_ddev, &data_device_listener, seat); } } else { MP_VERBOSE(wl, "Compositor doesn't support the %s (ver. 3) protocol!\n", wl_data_device_manager_interface.name); } if (wl->presentation) { wl->fback_pool = talloc_zero(wl, struct vo_wayland_feedback_pool); wl->fback_pool->wl = wl; wl->fback_pool->len = VO_MAX_SWAPCHAIN_DEPTH; wl->fback_pool->fback = talloc_zero_array(wl->fback_pool, struct wp_presentation_feedback *, wl->fback_pool->len); wl->present = mp_present_initialize(wl, wl->opts, VO_MAX_SWAPCHAIN_DEPTH); } else { MP_VERBOSE(wl, "Compositor doesn't support the %s protocol!\n", wp_presentation_interface.name); } if (wl->xdg_decoration_manager) { wl->xdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(wl->xdg_decoration_manager, wl->xdg_toplevel); zxdg_toplevel_decoration_v1_add_listener(wl->xdg_toplevel_decoration, &decoration_listener, wl); request_decoration_mode( wl, wl->opts->border ? ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE : ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE); } else { wl->opts->border = false; m_config_cache_write_opt(wl->opts_cache, &wl->opts->border); MP_VERBOSE(wl, "Compositor doesn't support the %s protocol!\n", zxdg_decoration_manager_v1_interface.name); } if (!wl->idle_inhibit_manager) { MP_VERBOSE(wl, "Compositor doesn't support the %s protocol!\n", zwp_idle_inhibit_manager_v1_interface.name); } wl->display_fd = wl_display_get_fd(wl->display); update_app_id(wl); mp_make_wakeup_pipe(wl->wakeup_pipe); wl->callback_surface = using_dmabuf_wayland ? wl->video_surface : wl->surface; wl->frame_callback = wl_surface_frame(wl->callback_surface); wl_callback_add_listener(wl->frame_callback, &frame_listener, wl); wl_surface_commit(wl->surface); /* Do another roundtrip to ensure all of the above is initialized * before mpv does anything else. */ wl_display_roundtrip(wl->display); return true; err: vo_wayland_uninit(vo); return false; } bool vo_wayland_reconfig(struct vo *vo) { struct vo_wayland_state *wl = vo->wl; MP_VERBOSE(wl, "Reconfiguring!\n"); if (!wl->current_output) { wl->current_output = find_output(wl); if (!wl->current_output) return false; set_surface_scaling(wl); wl->scale_configured = true; wl->pending_vo_events |= VO_EVENT_DPI; } if (wl->opts->auto_window_resize || !wl->geometry_configured) set_geometry(wl, false); if (wl->geometry_configured && wl->opts->auto_window_resize) wl->reconfigured = true; if (wl->opts->wl_configure_bounds) set_window_bounds(wl); if (wl->opts->cursor_passthrough) set_input_region(wl, true); if (!wl->geometry_configured || !wl->locked_size) wl->geometry = wl->window_size; if (!wl->geometry_configured) { if (wl->opts->fullscreen) toggle_fullscreen(wl); if (wl->opts->window_maximized) toggle_maximized(wl); if (wl->opts->window_minimized) do_minimize(wl); wl->geometry_configured = true; } prepare_resize(wl); return true; } void vo_wayland_set_opaque_region(struct vo_wayland_state *wl, bool alpha) { const int32_t width = mp_rect_w(wl->geometry); const int32_t height = mp_rect_h(wl->geometry); if (!alpha) { struct wl_region *region = wl_compositor_create_region(wl->compositor); wl_region_add(region, 0, 0, width, height); wl_surface_set_opaque_region(wl->surface, region); wl_region_destroy(region); } else { wl_surface_set_opaque_region(wl->surface, NULL); } } void vo_wayland_uninit(struct vo *vo) { struct vo_wayland_state *wl = vo->wl; if (!wl) return; if (wl->dnd_fd != -1) close(wl->dnd_fd); free_dnd_data(wl); mp_input_put_key(wl->vo->input_ctx, MP_INPUT_RELEASE_ALL); if (wl->compositor) wl_compositor_destroy(wl->compositor); if (wl->subcompositor) wl_subcompositor_destroy(wl->subcompositor); #if HAVE_WAYLAND_PROTOCOLS_1_32 if (wl->cursor_shape_manager) wp_cursor_shape_manager_v1_destroy(wl->cursor_shape_manager); #endif if (wl->cursor_surface) wl_surface_destroy(wl->cursor_surface); if (wl->cursor_theme) wl_cursor_theme_destroy(wl->cursor_theme); if (wl->content_type) wp_content_type_v1_destroy(wl->content_type); if (wl->content_type_manager) wp_content_type_manager_v1_destroy(wl->content_type_manager); if (wl->dnd_devman) wl_data_device_manager_destroy(wl->dnd_devman); if (wl->dnd_offer) wl_data_offer_destroy(wl->dnd_offer); if (wl->fback_pool) clean_feedback_pool(wl->fback_pool); if (wl->fractional_scale) wp_fractional_scale_v1_destroy(wl->fractional_scale); if (wl->fractional_scale_manager) wp_fractional_scale_manager_v1_destroy(wl->fractional_scale_manager); if (wl->frame_callback) wl_callback_destroy(wl->frame_callback); if (wl->idle_inhibitor) zwp_idle_inhibitor_v1_destroy(wl->idle_inhibitor); if (wl->idle_inhibit_manager) zwp_idle_inhibit_manager_v1_destroy(wl->idle_inhibit_manager); if (wl->presentation) wp_presentation_destroy(wl->presentation); if (wl->registry) wl_registry_destroy(wl->registry); if (wl->viewporter) wp_viewporter_destroy(wl->viewporter); if (wl->viewport) wp_viewport_destroy(wl->viewport); if (wl->cursor_viewport) wp_viewport_destroy(wl->cursor_viewport); if (wl->osd_viewport) wp_viewport_destroy(wl->osd_viewport); if (wl->video_viewport) wp_viewport_destroy(wl->video_viewport); if (wl->dmabuf) zwp_linux_dmabuf_v1_destroy(wl->dmabuf); if (wl->dmabuf_feedback) zwp_linux_dmabuf_feedback_v1_destroy(wl->dmabuf_feedback); if (wl->shm) wl_shm_destroy(wl->shm); if (wl->single_pixel_manager) wp_single_pixel_buffer_manager_v1_destroy(wl->single_pixel_manager); if (wl->surface) wl_surface_destroy(wl->surface); if (wl->osd_surface) wl_surface_destroy(wl->osd_surface); if (wl->osd_subsurface) wl_subsurface_destroy(wl->osd_subsurface); if (wl->video_surface) wl_surface_destroy(wl->video_surface); if (wl->video_subsurface) wl_subsurface_destroy(wl->video_subsurface); if (wl->wm_base) xdg_wm_base_destroy(wl->wm_base); if (wl->xdg_decoration_manager) zxdg_decoration_manager_v1_destroy(wl->xdg_decoration_manager); if (wl->xdg_toplevel) xdg_toplevel_destroy(wl->xdg_toplevel); if (wl->xdg_toplevel_decoration) zxdg_toplevel_decoration_v1_destroy(wl->xdg_toplevel_decoration); if (wl->xdg_surface) xdg_surface_destroy(wl->xdg_surface); if (wl->xkb_context) xkb_context_unref(wl->xkb_context); struct vo_wayland_output *output, *output_tmp; wl_list_for_each_safe(output, output_tmp, &wl->output_list, link) remove_output(output); struct vo_wayland_seat *seat, *seat_tmp; wl_list_for_each_safe(seat, seat_tmp, &wl->seat_list, link) remove_seat(seat); if (wl->display) wl_display_disconnect(wl->display); munmap(wl->compositor_format_map, wl->compositor_format_size); for (int n = 0; n < 2; n++) close(wl->wakeup_pipe[n]); talloc_free(wl); vo->wl = NULL; } void vo_wayland_wait_frame(struct vo_wayland_state *wl) { int64_t vblank_time = 0; /* We need some vblank interval to use for the timeout in * this function. The order of preference of values to use is: * 1. vsync duration from presentation time * 2. refresh interval reported by presentation time * 3. refresh rate of the output reported by the compositor * 4. make up crap if vblank_time is still <= 0 (better than nothing) */ if (wl->use_present && wl->present->head) vblank_time = wl->present->head->vsync_duration; if (vblank_time <= 0 && wl->refresh_interval > 0) vblank_time = wl->refresh_interval; if (vblank_time <= 0 && wl->current_output->refresh_rate > 0) vblank_time = 1e9 / wl->current_output->refresh_rate; // Ideally you should never reach this point. if (vblank_time <= 0) vblank_time = 1e9 / 60; // Completely arbitrary amount of additional time to wait. vblank_time += 0.05 * vblank_time; int64_t finish_time = mp_time_ns() + vblank_time; while (wl->frame_wait && finish_time > mp_time_ns()) { int64_t poll_time = finish_time - mp_time_ns(); if (poll_time < 0) { poll_time = 0; } wayland_dispatch_events(wl, 1, poll_time); } /* If the compositor does not have presentation time, we cannot be sure * that this wait is accurate. Do a hacky block with wl_display_roundtrip. */ if (!wl->use_present && !wl_display_get_error(wl->display)) wl_display_roundtrip(wl->display); /* Only use this heuristic if the compositor doesn't support the suspended state. */ if (wl->frame_wait && xdg_toplevel_get_version(wl->xdg_toplevel) < 6) { // Only consider consecutive missed callbacks. if (wl->timeout_count > 1) { wl->hidden = true; return; } else { wl->timeout_count += 1; return; } } wl->timeout_count = 0; } void vo_wayland_wait_events(struct vo *vo, int64_t until_time_ns) { struct vo_wayland_state *wl = vo->wl; int64_t wait_ns = until_time_ns - mp_time_ns(); int64_t timeout_ns = MPCLAMP(wait_ns, 0, MP_TIME_S_TO_NS(10)); wayland_dispatch_events(wl, 2, timeout_ns); } void vo_wayland_wakeup(struct vo *vo) { struct vo_wayland_state *wl = vo->wl; (void)write(wl->wakeup_pipe[1], &(char){0}, 1); }