0
0
mirror of https://github.com/mpv-player/mpv.git synced 2024-09-20 12:02:23 +02:00
mpv/video/out/wayland_common.c
Dudemanguy a45518cf57 wayland: set current_output on surface leave if applicable
When the mpv surface leaves the output, we no longer mark it as the
current output. However, this implicitly depends on there being a
preceding surface entrance event to a different output. This is not
necessarily the case. Consider moving the window from monitor 1, to
monitor 1-2, and then back to 1 again. mpv gets the entrance event to
monitor 2 and sets that as the current output to work off of. Then when
you move back to only monitor 1, it removes monitor 2 from the current
output. However, monitor 1 is not updated again as the current output
because there is not a new surface entrance event in this case (the
window never left). So the numbers that mpv's core is using are
incorrect and for the wrong monitor. Luckily, we already keep track of
what outputs the mpv surface is currently on no matter how many there
are so it is simply a matter of setting current output again in the
leave event if we have a different output that has the mpv surface.

Ref: https://github.com/swaywm/sway/issues/7932
2024-02-08 22:26:15 +00:00

2730 lines
89 KiB
C

/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <errno.h>
#include <limits.h>
#include <linux/input-event-codes.h>
#include <poll.h>
#include <time.h>
#include <unistd.h>
#include <wayland-cursor.h>
#include <xkbcommon/xkbcommon.h>
#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"
#if HAVE_WAYLAND_PROTOCOLS_1_27
#include "content-type-v1.h"
#include "single-pixel-buffer-v1.h"
#endif
#if HAVE_WAYLAND_PROTOCOLS_1_31
#include "fractional-scale-v1.h"
#endif
#if HAVE_WAYLAND_PROTOCOLS_1_32
#include "cursor-shape-v1.h"
#endif
#if WAYLAND_VERSION_MAJOR > 1 || WAYLAND_VERSION_MINOR >= 21
#define HAVE_WAYLAND_1_21
#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
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, '-'}, {XKB_KEY_KP_Add, '+'},
{XKB_KEY_KP_Multiply, '*'}, {XKB_KEY_KP_Divide, '/'},
{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_KP5},
{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}
};
#define OPT_BASE_STRUCT struct wayland_opts
const struct m_sub_options wayland_conf = {
.opts = (const struct m_option[]) {
{"wayland-configure-bounds", OPT_CHOICE(configure_bounds,
{"auto", -1}, {"no", 0}, {"yes", 1})},
{"wayland-disable-vsync", OPT_BOOL(disable_vsync)},
{"wayland-edge-pixels-pointer", OPT_INT(edge_pixels_pointer),
M_RANGE(0, INT_MAX)},
{"wayland-edge-pixels-touch", OPT_INT(edge_pixels_touch),
M_RANGE(0, INT_MAX)},
{0},
},
.size = sizeof(struct wayland_opts),
.defaults = &(struct wayland_opts) {
.configure_bounds = -1,
.edge_pixels_pointer = 16,
.edge_pixels_touch = 32,
},
};
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;
};
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_state *wl);
static int lookupkey(int key);
static int set_cursor_visibility(struct vo_wayland_state *wl, 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 get_shape_device(struct vo_wayland_state *wl);
static int greatest_common_divisor(int a, int b);
static void guess_focus(struct vo_wayland_state *wl);
static void prepare_resize(struct vo_wayland_state *wl, int width, int height);
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 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 window_move(struct vo_wayland_state *wl, uint32_t serial);
/* 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_state *wl = data;
wl->pointer = pointer;
wl->pointer_id = serial;
set_cursor_visibility(wl, wl->cursor_visible);
mp_input_put_key(wl->vo->input_ctx, MP_KEY_MOUSE_ENTER);
}
static void pointer_handle_leave(void *data, struct wl_pointer *pointer,
uint32_t serial, struct wl_surface *surface)
{
struct vo_wayland_state *wl = data;
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_state *wl = data;
wl->mouse_x = wl_fixed_to_int(sx) * wl->scaling;
wl->mouse_y = wl_fixed_to_int(sy) * wl->scaling;
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_state *wl = data;
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;
}
if (button)
mp_input_put_key(wl->vo->input_ctx, button | state | wl->mpmod);
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))
{
uint32_t edges;
// Implement an edge resize zone if there are no decorations
if (!wl->vo_opts->border && check_for_resize(wl, wl->opts->edge_pixels_pointer, &edges)) {
xdg_toplevel_resize(wl->xdg_toplevel, wl->seat, serial, edges);
} else {
window_move(wl, serial);
}
// Explicitly send an UP event after the client finishes a move/resize
mp_input_put_key(wl->vo->input_ctx, button | MP_KEY_STATE_UP);
}
}
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_state *wl = data;
switch (axis) {
case WL_POINTER_AXIS_VERTICAL_SCROLL:
wl->axis_value_vertical += wl_fixed_to_double(value);
break;
case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
wl->axis_value_horizontal += wl_fixed_to_double(value);
break;
}
}
static void pointer_handle_frame(void *data, struct wl_pointer *wl_pointer)
{
struct vo_wayland_state *wl = data;
double value_vertical, value_horizontal;
if (wl->axis_value120_scroll) {
// Prefer axis_value120 if supported and the axis event is from mouse wheel.
value_vertical = wl->axis_value120_vertical / 120.0;
value_horizontal = wl->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(wl->axis_value_vertical / 10.0, -1, 1);
value_horizontal = MPCLAMP(wl->axis_value_horizontal / 10.0, -1, 1);
}
if (value_vertical > 0)
mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_DOWN | wl->mpmod, +value_vertical);
if (value_vertical < 0)
mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_UP | wl->mpmod, -value_vertical);
if (value_horizontal > 0)
mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_RIGHT | wl->mpmod, +value_horizontal);
if (value_horizontal < 0)
mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_LEFT | wl->mpmod, -value_horizontal);
wl->axis_value120_scroll = false;
wl->axis_value_vertical = 0;
wl->axis_value_horizontal = 0;
wl->axis_value120_vertical = 0;
wl->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)
{
}
#ifdef HAVE_WAYLAND_1_21
static void pointer_handle_axis_value120(void *data, struct wl_pointer *wl_pointer,
uint32_t axis, int32_t value120)
{
struct vo_wayland_state *wl = data;
wl->axis_value120_scroll = true;
switch (axis) {
case WL_POINTER_AXIS_VERTICAL_SCROLL:
wl->axis_value120_vertical += value120;
break;
case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
wl->axis_value120_horizontal += value120;
break;
}
}
#endif
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,
#ifdef HAVE_WAYLAND_1_21
pointer_handle_axis_value120,
#endif
};
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_state *wl = data;
wl->mouse_x = wl_fixed_to_int(x_w) * wl->scaling;
wl->mouse_y = wl_fixed_to_int(y_w) * wl->scaling;
enum xdg_toplevel_resize_edge edge;
if (!mp_input_test_dragging(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y)) {
if (check_for_resize(wl, wl->opts->edge_pixels_touch, &edge)) {
xdg_toplevel_resize(wl->xdg_toplevel, wl->seat, serial, edge);
} else {
xdg_toplevel_move(wl->xdg_toplevel, wl->seat, serial);
}
}
mp_input_set_mouse_pos(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y);
mp_input_put_key(wl->vo->input_ctx, MP_MBTN_LEFT | MP_KEY_STATE_DOWN);
}
static void touch_handle_up(void *data, struct wl_touch *wl_touch,
uint32_t serial, uint32_t time, int32_t id)
{
struct vo_wayland_state *wl = data;
mp_input_put_key(wl->vo->input_ctx, MP_MBTN_LEFT | MP_KEY_STATE_UP);
}
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_state *wl = data;
wl->mouse_x = wl_fixed_to_int(x_w) * wl->scaling;
wl->mouse_y = wl_fixed_to_int(y_w) * wl->scaling;
mp_input_set_mouse_pos(wl->vo->input_ctx, 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)
{
}
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_state *wl = data;
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;
}
wl->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 (!wl->xkb_keymap) {
MP_ERR(wl, "failed to compile keymap\n");
return;
}
wl->xkb_state = xkb_state_new(wl->xkb_keymap);
if (!wl->xkb_state) {
MP_ERR(wl, "failed to create XKB state\n");
xkb_keymap_unref(wl->xkb_keymap);
wl->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_state *wl = data;
wl->has_keyboard_input = true;
guess_focus(wl);
}
static void keyboard_handle_leave(void *data, struct wl_keyboard *wl_keyboard,
uint32_t serial, struct wl_surface *surface)
{
struct vo_wayland_state *wl = data;
wl->has_keyboard_input = false;
wl->keyboard_code = 0;
wl->mpkey = 0;
wl->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_state *wl = data;
wl->keyboard_code = key + 8;
xkb_keysym_t sym = xkb_state_key_get_one_sym(wl->xkb_state, wl->keyboard_code);
int mpkey = lookupkey(sym);
state = state == WL_KEYBOARD_KEY_STATE_PRESSED ? MP_KEY_STATE_DOWN
: MP_KEY_STATE_UP;
if (mpkey) {
mp_input_put_key(wl->vo->input_ctx, mpkey | state | wl->mpmod);
} else {
char s[128];
if (xkb_keysym_to_utf8(sym, s, sizeof(s)) > 0) {
mp_input_put_key_utf8(wl->vo->input_ctx, state | wl->mpmod, bstr0(s));
} else {
// Assume a modifier was pressed and handle it in the mod event instead.
return;
}
}
if (state == MP_KEY_STATE_DOWN)
wl->mpkey = mpkey;
if (mpkey && state == MP_KEY_STATE_UP)
wl->mpkey = 0;
}
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_state *wl = data;
if (wl->xkb_state) {
xkb_state_update_mask(wl->xkb_state, mods_depressed, mods_latched,
mods_locked, 0, 0, group);
wl->mpmod = get_mods(wl);
if (wl->mpkey)
mp_input_put_key(wl->vo->input_ctx, wl->mpkey | MP_KEY_STATE_DOWN | wl->mpmod);
}
}
static void keyboard_handle_repeat_info(void *data, struct wl_keyboard *wl_keyboard,
int32_t rate, int32_t delay)
{
struct vo_wayland_state *wl = data;
if (wl->vo_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_state *wl = data;
if ((caps & WL_SEAT_CAPABILITY_POINTER) && !wl->pointer) {
wl->pointer = wl_seat_get_pointer(seat);
get_shape_device(wl);
wl_pointer_add_listener(wl->pointer, &pointer_listener, wl);
} else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && wl->pointer) {
wl_pointer_destroy(wl->pointer);
wl->pointer = NULL;
}
if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !wl->keyboard) {
wl->keyboard = wl_seat_get_keyboard(seat);
wl_keyboard_add_listener(wl->keyboard, &keyboard_listener, wl);
} else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && wl->keyboard) {
wl_keyboard_destroy(wl->keyboard);
wl->keyboard = NULL;
}
if ((caps & WL_SEAT_CAPABILITY_TOUCH) && !wl->touch) {
wl->touch = wl_seat_get_touch(seat);
wl_touch_set_user_data(wl->touch, wl);
wl_touch_add_listener(wl->touch, &touch_listener, wl);
} else if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && wl->touch) {
wl_touch_destroy(wl->touch);
wl->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_state *wl = data;
int score = mp_event_get_mime_type_score(wl->vo->input_ctx, mime_type);
if (score > wl->dnd_mime_score && wl->vo_opts->drag_and_drop != -2) {
wl->dnd_mime_score = score;
if (wl->dnd_mime_type)
talloc_free(wl->dnd_mime_type);
wl->dnd_mime_type = talloc_strdup(wl, 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_state *wl = data;
if (dnd_action && wl->vo_opts->drag_and_drop != -2) {
if (wl->vo_opts->drag_and_drop >= 0) {
wl->dnd_action = wl->vo_opts->drag_and_drop;
} else {
wl->dnd_action = dnd_action & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY ?
DND_REPLACE : DND_APPEND;
}
MP_VERBOSE(wl, "DND action is %s\n",
wl->dnd_action == DND_REPLACE ? "DND_REPLACE" : "DND_APPEND");
}
}
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_state *wl = data;
if (wl->dnd_offer)
wl_data_offer_destroy(wl->dnd_offer);
wl->dnd_offer = id;
wl_data_offer_add_listener(id, &data_offer_listener, wl);
}
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_state *wl = data;
if (wl->dnd_offer != id) {
MP_FATAL(wl, "DND offer ID mismatch!\n");
return;
}
if (wl->vo_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_state *wl = data;
if (wl->dnd_offer) {
if (wl->dnd_fd != -1)
return;
wl_data_offer_destroy(wl->dnd_offer);
wl->dnd_offer = NULL;
}
if (wl->vo_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_state *wl = data;
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_state *wl = data;
int pipefd[2];
if (pipe2(pipefd, O_CLOEXEC) == -1) {
MP_ERR(wl, "Failed to create dnd pipe!\n");
return;
}
if (wl->vo_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_state *wl = data;
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: %d\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, 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);
spawn_cursor(wl);
set_geometry(wl, false);
prepare_resize(wl, 0, 0);
wl->pending_vo_events |= VO_EVENT_DPI;
}
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;
}
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;
struct vo_wayland_output *o;
wl_list_for_each(o, &wl->output_list, link) {
if (o->output == output) {
wl->current_output = o;
break;
}
}
wl->current_output->has_surface = true;
bool force_resize = false;
if (!wl->fractional_scale_manager && wl_surface_get_version(wl_surface) < 6 &&
wl->scaling != wl->current_output->scale)
{
set_surface_scaling(wl);
spawn_cursor(wl);
force_resize = true;
wl->pending_vo_events |= VO_EVENT_DPI;
}
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, 0, 0);
MP_VERBOSE(wl, "Surface entered output %s %s (0x%x), scale = %f, refresh rate = %f Hz\n",
o->make, o->model, o->id, wl->scaling, o->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;
struct vo_wayland_output *o;
wl_list_for_each(o, &wl->output_list, link) {
if (o->output == output)
o->has_surface = false;
if (o->output != output && o->has_surface)
wl->current_output = o;
}
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;
double old_scale = wl->scaling;
if (wl->fractional_scale_manager)
return;
wl->scaling = scale;
MP_VERBOSE(wl, "Obtained preferred scale, %f, from the compositor.\n",
wl->scaling);
wl->pending_vo_events |= VO_EVENT_DPI;
if (wl->current_output) {
rescale_geometry(wl, old_scale);
set_geometry(wl, false);
prepare_resize(wl, 0, 0);
}
}
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 *vo_opts = wl->vo_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;
}
int old_toplevel_width = wl->toplevel_width;
int old_toplevel_height = wl->toplevel_height;
wl->toplevel_width = width;
wl->toplevel_height = height;
if (!wl->configured) {
/* Save initial window size if the compositor gives us a hint here. */
bool autofit_or_geometry = vo_opts->geometry.wh_valid || vo_opts->autofit.wh_valid ||
vo_opts->autofit_larger.wh_valid || vo_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_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:
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.
*/
vo_opts->window_minimized = false;
m_config_cache_write_opt(wl->vo_opts_cache,
&vo_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 (vo_opts->fullscreen != is_fullscreen) {
wl->state_change = true;
vo_opts->fullscreen = is_fullscreen;
m_config_cache_write_opt(wl->vo_opts_cache, &vo_opts->fullscreen);
}
if (vo_opts->window_maximized != is_maximized) {
wl->state_change = true;
vo_opts->window_maximized = is_maximized;
m_config_cache_write_opt(wl->vo_opts_cache, &vo_opts->window_maximized);
}
wl->tiled = is_tiled;
wl->locked_size = is_fullscreen || is_maximized || is_tiled;
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 (old_toplevel_width == wl->toplevel_width &&
old_toplevel_height == wl->toplevel_height)
return;
if (!wl->locked_size) {
if (vo_opts->keepaspect) {
double scale_factor = (double)width / wl->reduced_width;
width = ceil(wl->reduced_width * scale_factor);
if (vo_opts->keepaspect_window)
height = ceil(wl->reduced_height * scale_factor);
}
wl->window_size.x0 = 0;
wl->window_size.y0 = 0;
wl->window_size.x1 = lround(width * wl->scaling);
wl->window_size.y1 = lround(height * wl->scaling);
}
wl->geometry.x0 = 0;
wl->geometry.y0 = 0;
wl->geometry.x1 = lround(width * wl->scaling);
wl->geometry.y1 = lround(height * wl->scaling);
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, width, height);
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 = width * wl->scaling;
wl->bounded_height = height * wl->scaling;
}
#ifdef XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION
static void handle_wm_capabilities(void *data, struct xdg_toplevel *xdg_toplevel,
struct wl_array *capabilities)
{
}
#endif
static const struct xdg_toplevel_listener xdg_toplevel_listener = {
handle_toplevel_config,
handle_toplevel_close,
handle_configure_bounds,
#ifdef XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION
handle_wm_capabilities,
#endif
};
#if HAVE_WAYLAND_PROTOCOLS_1_31
static void preferred_scale(void *data,
struct wp_fractional_scale_v1 *fractional_scale,
uint32_t scale)
{
struct vo_wayland_state *wl = data;
double old_scale = wl->scaling;
wl->scaling = (double)scale / 120;
MP_VERBOSE(wl, "Obtained preferred scale, %f, from the compositor.\n",
wl->scaling);
wl->pending_vo_events |= VO_EVENT_DPI;
if (wl->current_output) {
rescale_geometry(wl, old_scale);
set_geometry(wl, false);
prepare_resize(wl, 0, 0);
}
}
static const struct wp_fractional_scale_v1_listener fractional_scale_listener = {
preferred_scale,
};
#endif
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 "<unknown>";
}
}
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->vo_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->vo_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->use_present = 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);
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;
void *map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
if (map != MAP_FAILED) {
wl->format_map = map;
wl->format_size = size;
}
}
static void main_device(void *data,
struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1,
struct wl_array *device)
{
}
static void tranche_done(void *data,
struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1)
{
}
static void tranche_target_device(void *data,
struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1,
struct wl_array *device)
{
}
static void tranche_formats(void *data,
struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1,
struct wl_array *indices)
{
}
static void tranche_flags(void *data,
struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1,
uint32_t flags)
{
}
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) && (ver >= 1) && found++) {
wl->subcompositor = wl_registry_bind(reg, id, &wl_subcompositor_interface, 1);
}
if (!strcmp (interface, zwp_linux_dmabuf_v1_interface.name) && (ver >= 4) && found++) {
wl->dmabuf = wl_registry_bind(reg, id, &zwp_linux_dmabuf_v1_interface, 4);
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) && (ver >= 1) && found++) {
wl->viewporter = wl_registry_bind (reg, id, &wp_viewporter_interface, 1);
}
if (!strcmp(interface, wl_data_device_manager_interface.name) && (ver >= 3) && found++) {
wl->dnd_devman = wl_registry_bind(reg, id, &wl_data_device_manager_interface, 3);
}
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");
#ifdef HAVE_WAYLAND_1_21
ver = MPMIN(ver, 8); /* Cap at 8 in case new events are added later. */
#else
ver = MPMIN(ver, 7);
#endif
wl->seat = wl_registry_bind(reg, id, &wl_seat_interface, ver);
wl_seat_add_listener(wl->seat, &seat_listener, wl);
}
if (!strcmp(interface, wl_shm_interface.name) && found++) {
wl->shm = wl_registry_bind(reg, id, &wl_shm_interface, 1);
}
#if HAVE_WAYLAND_PROTOCOLS_1_27
if (!strcmp(interface, wp_content_type_manager_v1_interface.name) && found++) {
wl->content_type_manager = wl_registry_bind(reg, id, &wp_content_type_manager_v1_interface, 1);
}
if (!strcmp(interface, wp_single_pixel_buffer_manager_v1_interface.name) && found++) {
wl->single_pixel_manager = wl_registry_bind(reg, id, &wp_single_pixel_buffer_manager_v1_interface, 1);
}
#endif
#if HAVE_WAYLAND_PROTOCOLS_1_31
if (!strcmp(interface, wp_fractional_scale_manager_v1_interface.name) && found++) {
wl->fractional_scale_manager = wl_registry_bind(reg, id, &wp_fractional_scale_manager_v1_interface, 1);
}
#endif
#if HAVE_WAYLAND_PROTOCOLS_1_32
if (!strcmp(interface, wp_cursor_shape_manager_v1_interface.name) && found++) {
wl->cursor_shape_manager = wl_registry_bind(reg, id, &wp_cursor_shape_manager_v1_interface, 1);
}
#endif
if (!strcmp(interface, wp_presentation_interface.name) && found++) {
wl->presentation = wl_registry_bind(reg, id, &wp_presentation_interface, 1);
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++) {
wl->xdg_decoration_manager = wl_registry_bind(reg, id, &zxdg_decoration_manager_v1_interface, 1);
}
if (!strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) && found++) {
wl->idle_inhibit_manager = wl_registry_bind(reg, id, &zwp_idle_inhibit_manager_v1_interface, 1);
}
if (found > 1)
MP_VERBOSE(wl, "Registered for protocol %s\n", interface);
}
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, *tmp;
wl_list_for_each_safe(output, tmp, &wl->output_list, link) {
if (output->id == id) {
remove_output(output);
return;
}
}
}
static const struct wl_registry_listener registry_listener = {
registry_handle_add,
registry_handle_remove,
};
/* Static functions */
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) {
size_t data_read = 0;
const size_t chunk_size = 1;
bstr file_list = {
.start = talloc_zero_size(NULL, chunk_size),
};
while ((data_read = read(wl->dnd_fd, file_list.start + file_list.len, chunk_size)) > 0) {
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);
}
MP_VERBOSE(wl, "Read %zu bytes from the DND fd\n", file_list.len);
mp_event_drop_mime_data(wl->vo->input_ctx, wl->dnd_mime_type,
file_list, wl->dnd_action);
talloc_free(file_list.start);
if (wl->dnd_mime_type)
talloc_free(wl->dnd_mime_type);
if (wl->dnd_action >= 0 && wl->dnd_offer)
wl_data_offer_finish(wl->dnd_offer);
wl->dnd_action = -1;
wl->dnd_mime_type = NULL;
wl->dnd_mime_score = 0;
}
if (fdp.revents & (POLLIN | POLLERR | POLLHUP)) {
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->vo_opts->fullscreen || wl->vo_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->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 void do_minimize(struct vo_wayland_state *wl)
{
if (!wl->xdg_toplevel)
return;
if (wl->vo_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 int get_mods(struct vo_wayland_state *wl)
{
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(wl->xkb_keymap, mod_names[n]);
if (index != XKB_MOD_INVALID
&& xkb_state_mod_index_is_active(wl->xkb_state, index,
XKB_STATE_MODS_EFFECTIVE))
modifiers |= mods[n];
}
return modifiers;
}
static void get_shape_device(struct vo_wayland_state *wl)
{
#if HAVE_WAYLAND_PROTOCOLS_1_32
if (!wl->cursor_shape_device && wl->cursor_shape_manager) {
wl->cursor_shape_device = wp_cursor_shape_manager_v1_get_pointer(wl->cursor_shape_manager,
wl->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.
if ((!wl->focused && wl->activated && wl->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->vo_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 prepare_resize(struct vo_wayland_state *wl, int width, int height)
{
if (!width)
width = mp_rect_w(wl->geometry) / wl->scaling;
if (!height)
height = mp_rect_h(wl->geometry) / wl->scaling;
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->vo_opts->hidpi_window_scale)
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 set_content_type(struct vo_wayland_state *wl)
{
if (!wl->content_type_manager)
return;
#if HAVE_WAYLAND_PROTOCOLS_1_27
// handle auto;
if (wl->vo_opts->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->vo_opts->content_type);
}
#endif
}
static void set_cursor_shape(struct vo_wayland_state *wl)
{
#if HAVE_WAYLAND_PROTOCOLS_1_32
wp_cursor_shape_device_v1_set_shape(wl->cursor_shape_device, wl->pointer_id,
WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT);
#endif
}
static int set_cursor_visibility(struct vo_wayland_state *wl, bool on)
{
wl->cursor_visible = on;
if (on) {
if (wl->cursor_shape_device) {
set_cursor_shape(wl);
} 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;
int scale = MPMAX(wl->scaling, 1);
wl_pointer_set_cursor(wl->pointer, wl->pointer_id, wl->cursor_surface,
img->hotspot_x / scale, img->hotspot_y / scale);
wl_surface_set_buffer_scale(wl->cursor_surface, 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(wl->pointer, wl->pointer_id, NULL, 0, 0);
}
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_geometry2(vo, &screenrc, wl->scaling, &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, 0, 0);
}
}
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->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->fractional_scale_manager)
return;
double old_scale = wl->scaling;
wl->scaling = wl->current_output->scale;
rescale_geometry(wl, old_scale);
}
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->configure_bounds == -1 && (wl->vo_opts->geometry.wh_valid ||
wl->vo_opts->autofit.wh_valid || wl->vo_opts->autofit_larger.wh_valid ||
wl->vo_opts->autofit_smaller.wh_valid))
{
return;
}
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 int spawn_cursor(struct vo_wayland_state *wl)
{
/* Don't use this if we have cursor-shape. */
if (wl->cursor_shape_device)
return 0;
/* Reuse if size is identical */
if (!wl->pointer || 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, size*wl->scaling, 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)
{
if (!wl->xdg_toplevel)
return;
wl->state_change = true;
bool specific_screen = wl->vo_opts->fsscreen_id >= 0 || wl->vo_opts->fsscreen_name;
if (wl->vo_opts->fullscreen && !specific_screen) {
xdg_toplevel_set_fullscreen(wl->xdg_toplevel, NULL);
} else if (wl->vo_opts->fullscreen && specific_screen) {
struct vo_wayland_output *output = find_output(wl);
xdg_toplevel_set_fullscreen(wl->xdg_toplevel, output->output);
} else {
xdg_toplevel_unset_fullscreen(wl->xdg_toplevel);
}
}
static void toggle_maximized(struct vo_wayland_state *wl)
{
if (!wl->xdg_toplevel)
return;
wl->state_change = true;
if (wl->vo_opts->window_maximized) {
xdg_toplevel_set_maximized(wl->xdg_toplevel);
} else {
xdg_toplevel_unset_maximized(wl->xdg_toplevel);
}
}
static void update_app_id(struct vo_wayland_state *wl)
{
if (!wl->xdg_toplevel)
return;
xdg_toplevel_set_app_id(wl->xdg_toplevel, wl->vo_opts->appid);
}
static int update_window_title(struct vo_wayland_state *wl, const char *title)
{
if (!wl->xdg_toplevel)
return VO_NOTAVAIL;
/* 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 window_move(struct vo_wayland_state *wl, uint32_t serial)
{
if (wl->xdg_toplevel)
xdg_toplevel_move(wl->xdg_toplevel, wl->seat, serial);
}
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);
}
/* 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->vo_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->vo_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->vo_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->vo_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->vo_opts_cache,
&wl->vo_opts->border);
}
}
if (opt == &opts->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->hidpi_window_scale)
set_geometry(wl, true);
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)
{
set_geometry(wl, true);
}
}
return VO_TRUE;
}
case VOCTRL_CONTENT_TYPE: {
#if HAVE_WAYLAND_PROTOCOLS_1_27
wl->current_content_type = *(enum mp_content_type *)arg;
set_content_type(wl);
#endif
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->vo_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->vo_opts->fullscreen && !wl->tiled) {
if (wl->vo_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->vo_opts->window_maximized)
return VO_TRUE;
}
wl->geometry = wl->window_size;
prepare_resize(wl, 0, 0);
}
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)
return VO_NOTAVAIL;
*(double *)arg = wl->scaling;
return VO_TRUE;
}
case VOCTRL_UPDATE_WINDOW_TITLE:
return update_window_title(wl, (const char *)arg);
case VOCTRL_SET_CURSOR_VISIBILITY:
if (!wl->pointer)
return VO_NOTAVAIL;
return set_cursor_visibility(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),
lround(mp_rect_h(wl->geometry) / wl->scaling));
}
bool vo_wayland_init(struct vo *vo)
{
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 = 1,
.wakeup_pipe = {-1, -1},
.display_fd = -1,
.dnd_fd = -1,
.cursor_visible = true,
.vo_opts_cache = m_config_cache_alloc(wl, vo->global, &vo_sub_opts),
};
wl->vo_opts = wl->vo_opts_cache->opts;
bool using_dmabuf_wayland = !strcmp(wl->vo->driver->name, "dmabuf-wayland");
wl_list_init(&wl->output_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, &registry_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_length(&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 HAVE_WAYLAND_PROTOCOLS_1_27
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);
}
#endif
#if HAVE_WAYLAND_PROTOCOLS_1_31
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);
}
#endif
if (wl->dnd_devman && wl->seat) {
wl->dnd_ddev = wl_data_device_manager_get_data_device(wl->dnd_devman, wl->seat);
wl_data_device_add_listener(wl->dnd_ddev, &data_device_listener, wl);
} else if (!wl->dnd_devman) {
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->vo_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->vo_opts->border ?
ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE :
ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE);
} else {
wl->vo_opts->border = false;
m_config_cache_write_opt(wl->vo_opts_cache,
&wl->vo_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->opts = mp_get_config_group(wl, wl->vo->global, &wayland_conf);
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->pending_vo_events |= VO_EVENT_DPI;
}
if (wl->vo_opts->auto_window_resize || !wl->configured)
set_geometry(wl, false);
if (wl->opts->configure_bounds)
set_window_bounds(wl);
if (!wl->configured || !wl->locked_size) {
wl->geometry = wl->window_size;
wl->configured = true;
}
if (wl->vo_opts->cursor_passthrough)
set_input_region(wl, true);
if (wl->vo_opts->fullscreen)
toggle_fullscreen(wl);
if (wl->vo_opts->window_maximized)
toggle_maximized(wl);
if (wl->vo_opts->window_minimized)
do_minimize(wl);
prepare_resize(wl, 0, 0);
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;
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_device)
wp_cursor_shape_device_v1_destroy(wl->cursor_shape_device);
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 HAVE_WAYLAND_PROTOCOLS_1_27
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);
#endif
if (wl->dnd_ddev)
wl_data_device_destroy(wl->dnd_ddev);
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 HAVE_WAYLAND_PROTOCOLS_1_31
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);
#endif
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->keyboard)
wl_keyboard_destroy(wl->keyboard);
if (wl->pointer)
wl_pointer_destroy(wl->pointer);
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->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->seat)
wl_seat_destroy(wl->seat);
if (wl->shm)
wl_shm_destroy(wl->shm);
#if HAVE_WAYLAND_PROTOCOLS_1_27
if (wl->single_pixel_manager)
wp_single_pixel_buffer_manager_v1_destroy(wl->single_pixel_manager);
#endif
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);
if (wl->xkb_keymap)
xkb_keymap_unref(wl->xkb_keymap);
if (wl->xkb_state)
xkb_state_unref(wl->xkb_state);
struct vo_wayland_output *output, *tmp;
wl_list_for_each_safe(output, tmp, &wl->output_list, link)
remove_output(output);
if (wl->display)
wl_display_disconnect(wl->display);
munmap(wl->format_map, wl->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);
}