2013-02-28 18:55:02 +00:00
|
|
|
/*
|
2013-09-09 16:37:33 +00:00
|
|
|
* This file is part of mpv video player.
|
2013-02-28 18:55:02 +00:00
|
|
|
*
|
Relicense some non-MPlayer source files to LGPL 2.1 or later
This covers source files which were added in mplayer2 and mpv times
only, and where all code is covered by LGPL relicensing agreements.
There are probably more files to which this applies, but I'm being
conservative here.
A file named ao_sdl.c exists in MPlayer too, but the mpv one is a
complete rewrite, and was added some time after the original ao_sdl.c
was removed. The same applies to vo_sdl.c, for which the SDL2 API is
radically different in addition (MPlayer supports SDL 1.2 only).
common.c contains only code written by me. But common.h is a strange
case: although it originally was named mp_common.h and exists in MPlayer
too, by now it contains only definitions written by uau and me. The
exceptions are the CONTROL_ defines - thus not changing the license of
common.h yet.
codec_tags.c contained once large tables generated from MPlayer's
codecs.conf, but all of these tables were removed.
From demux_playlist.c I'm removing a code fragment from someone who was
not asked; this probably could be done later (see commit 15dccc37).
misc.c is a bit complicated to reason about (it was split off mplayer.c
and thus contains random functions out of this file), but actually all
functions have been added post-MPlayer. Except get_relative_time(),
which was written by uau, but looks similar to 3 different versions of
something similar in each of the Unix/win32/OSX timer source files. I'm
not sure what that means in regards to copyright, so I've just moved it
into another still-GPL source file for now.
screenshot.c once had some minor parts of MPlayer's vf_screenshot.c, but
they're all gone.
2016-01-19 17:36:06 +00:00
|
|
|
* 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.
|
2013-02-28 18:55:02 +00:00
|
|
|
*
|
2013-09-09 16:37:33 +00:00
|
|
|
* mpv is distributed in the hope that it will be useful,
|
2013-02-28 18:55:02 +00:00
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
Relicense some non-MPlayer source files to LGPL 2.1 or later
This covers source files which were added in mplayer2 and mpv times
only, and where all code is covered by LGPL relicensing agreements.
There are probably more files to which this applies, but I'm being
conservative here.
A file named ao_sdl.c exists in MPlayer too, but the mpv one is a
complete rewrite, and was added some time after the original ao_sdl.c
was removed. The same applies to vo_sdl.c, for which the SDL2 API is
radically different in addition (MPlayer supports SDL 1.2 only).
common.c contains only code written by me. But common.h is a strange
case: although it originally was named mp_common.h and exists in MPlayer
too, by now it contains only definitions written by uau and me. The
exceptions are the CONTROL_ defines - thus not changing the license of
common.h yet.
codec_tags.c contained once large tables generated from MPlayer's
codecs.conf, but all of these tables were removed.
From demux_playlist.c I'm removing a code fragment from someone who was
not asked; this probably could be done later (see commit 15dccc37).
misc.c is a bit complicated to reason about (it was split off mplayer.c
and thus contains random functions out of this file), but actually all
functions have been added post-MPlayer. Except get_relative_time(),
which was written by uau, but looks similar to 3 different versions of
something similar in each of the Unix/win32/OSX timer source files. I'm
not sure what that means in regards to copyright, so I've just moved it
into another still-GPL source file for now.
screenshot.c once had some minor parts of MPlayer's vf_screenshot.c, but
they're all gone.
2016-01-19 17:36:06 +00:00
|
|
|
* GNU Lesser General Public License for more details.
|
2013-02-28 18:55:02 +00:00
|
|
|
*
|
Relicense some non-MPlayer source files to LGPL 2.1 or later
This covers source files which were added in mplayer2 and mpv times
only, and where all code is covered by LGPL relicensing agreements.
There are probably more files to which this applies, but I'm being
conservative here.
A file named ao_sdl.c exists in MPlayer too, but the mpv one is a
complete rewrite, and was added some time after the original ao_sdl.c
was removed. The same applies to vo_sdl.c, for which the SDL2 API is
radically different in addition (MPlayer supports SDL 1.2 only).
common.c contains only code written by me. But common.h is a strange
case: although it originally was named mp_common.h and exists in MPlayer
too, by now it contains only definitions written by uau and me. The
exceptions are the CONTROL_ defines - thus not changing the license of
common.h yet.
codec_tags.c contained once large tables generated from MPlayer's
codecs.conf, but all of these tables were removed.
From demux_playlist.c I'm removing a code fragment from someone who was
not asked; this probably could be done later (see commit 15dccc37).
misc.c is a bit complicated to reason about (it was split off mplayer.c
and thus contains random functions out of this file), but actually all
functions have been added post-MPlayer. Except get_relative_time(),
which was written by uau, but looks similar to 3 different versions of
something similar in each of the Unix/win32/OSX timer source files. I'm
not sure what that means in regards to copyright, so I've just moved it
into another still-GPL source file for now.
screenshot.c once had some minor parts of MPlayer's vf_screenshot.c, but
they're all gone.
2016-01-19 17:36:06 +00:00
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
2013-02-28 18:55:02 +00:00
|
|
|
*/
|
|
|
|
|
2018-10-10 17:59:04 +00:00
|
|
|
#include <errno.h>
|
|
|
|
#include <limits.h>
|
2020-10-25 12:53:34 +00:00
|
|
|
#include <linux/input-event-codes.h>
|
2021-06-26 21:12:03 +00:00
|
|
|
#include <poll.h>
|
2019-10-10 19:14:40 +00:00
|
|
|
#include <time.h>
|
2021-06-26 21:12:03 +00:00
|
|
|
#include <unistd.h>
|
|
|
|
#include <wayland-cursor.h>
|
|
|
|
#include <xkbcommon/xkbcommon.h>
|
|
|
|
|
2013-12-17 01:39:45 +00:00
|
|
|
#include "common/msg.h"
|
2017-10-01 20:16:49 +00:00
|
|
|
#include "input/input.h"
|
|
|
|
#include "input/keycodes.h"
|
2021-06-26 21:12:03 +00:00
|
|
|
#include "options/m_config.h"
|
2016-07-21 11:25:30 +00:00
|
|
|
#include "osdep/io.h"
|
2013-02-28 18:55:02 +00:00
|
|
|
#include "osdep/timer.h"
|
2017-10-01 20:16:49 +00:00
|
|
|
#include "wayland_common.h"
|
2021-06-26 21:12:03 +00:00
|
|
|
#include "win_state.h"
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
// Generated from wayland-protocols
|
|
|
|
#include "generated/wayland/idle-inhibit-unstable-v1.h"
|
|
|
|
#include "generated/wayland/presentation-time.h"
|
|
|
|
#include "generated/wayland/xdg-decoration-unstable-v1.h"
|
2019-06-21 22:03:31 +00:00
|
|
|
#include "generated/wayland/xdg-shell.h"
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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},
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
/* 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},
|
2018-11-07 12:53:46 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
/* 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},
|
|
|
|
|
|
|
|
/* 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_KP1},
|
|
|
|
{XKB_KEY_KP_Down, MP_KEY_KP2}, {XKB_KEY_KP_Page_Down, MP_KEY_KP3},
|
|
|
|
{XKB_KEY_KP_Left, MP_KEY_KP4}, {XKB_KEY_KP_Begin, MP_KEY_KP5},
|
|
|
|
{XKB_KEY_KP_Right, MP_KEY_KP6}, {XKB_KEY_KP_Home, MP_KEY_KP7},
|
|
|
|
{XKB_KEY_KP_Up, MP_KEY_KP8}, {XKB_KEY_KP_Page_Up, MP_KEY_KP9},
|
|
|
|
{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},
|
|
|
|
|
|
|
|
{0, 0}
|
|
|
|
};
|
2019-10-10 19:14:40 +00:00
|
|
|
|
2019-10-14 17:16:42 +00:00
|
|
|
#define OPT_BASE_STRUCT struct wayland_opts
|
|
|
|
const struct m_sub_options wayland_conf = {
|
|
|
|
.opts = (const struct m_option[]) {
|
options: change option macros and all option declarations
Change all OPT_* macros such that they don't define the entire m_option
initializer, and instead expand only to a part of it, which sets certain
fields. This requires changing almost every option declaration, because
they all use these macros. A declaration now always starts with
{"name", ...
followed by designated initializers only (possibly wrapped in macros).
The OPT_* macros now initialize the .offset and .type fields only,
sometimes also .priv and others.
I think this change makes the option macros less tricky. The old code
had to stuff everything into macro arguments (and attempted to allow
setting arbitrary fields by letting the user pass designated
initializers in the vararg parts). Some of this was made messy due to
C99 and C11 not allowing 0-sized varargs with ',' removal. It's also
possible that this change is pointless, other than cosmetic preferences.
Not too happy about some things. For example, the OPT_CHOICE()
indentation I applied looks a bit ugly.
Much of this change was done with regex search&replace, but some places
required manual editing. In particular, code in "obscure" areas (which I
didn't include in compilation) might be broken now.
In wayland_common.c the author of some option declarations confused the
flags parameter with the default value (though the default value was
also properly set below). I fixed this with this change.
2020-03-14 20:28:01 +00:00
|
|
|
{"wayland-disable-vsync", OPT_FLAG(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)},
|
2019-10-14 17:16:42 +00:00
|
|
|
{0},
|
|
|
|
},
|
|
|
|
.size = sizeof(struct wayland_opts),
|
|
|
|
.defaults = &(struct wayland_opts) {
|
|
|
|
.disable_vsync = false,
|
2019-11-26 23:36:35 +00:00
|
|
|
.edge_pixels_pointer = 10,
|
|
|
|
.edge_pixels_touch = 64,
|
2019-10-14 17:16:42 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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;
|
|
|
|
struct wl_list link;
|
2017-10-01 20:16:49 +00:00
|
|
|
};
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
struct vo_wayland_sync {
|
|
|
|
int64_t ust;
|
|
|
|
int64_t msc;
|
|
|
|
int64_t sbc;
|
|
|
|
bool filled;
|
|
|
|
};
|
2020-11-07 19:47:26 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
static int check_for_resize(struct vo_wayland_state *wl, wl_fixed_t x_w, wl_fixed_t y_w,
|
|
|
|
int edge_pixels, enum xdg_toplevel_resize_edge *edge);
|
|
|
|
static int get_mods(struct vo_wayland_state *wl);
|
|
|
|
static int last_available_sync(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 greatest_common_divisor(struct vo_wayland_state *wl, int a, int b);
|
|
|
|
static void queue_new_sync(struct vo_wayland_state *wl);
|
|
|
|
static void remove_output(struct vo_wayland_output *out);
|
|
|
|
static void set_geometry(struct vo_wayland_state *wl);
|
2021-06-28 16:03:51 +00:00
|
|
|
static void set_surface_scaling(struct vo_wayland_state *wl);
|
2021-06-26 21:12:03 +00:00
|
|
|
static void sync_shift(struct vo_wayland_state *wl);
|
|
|
|
static void window_move(struct vo_wayland_state *wl, uint32_t serial);
|
|
|
|
|
|
|
|
/* Wayland listener boilerplate */
|
2017-10-01 20:16:49 +00:00
|
|
|
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;
|
|
|
|
|
2019-02-04 10:27:49 +00:00
|
|
|
set_cursor_visibility(wl, wl->cursor_visible);
|
2017-10-01 20:16:49 +00:00
|
|
|
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;
|
2019-11-25 23:52:53 +00:00
|
|
|
wl->mouse_unscaled_x = sx;
|
|
|
|
wl->mouse_unscaled_y = sy;
|
2017-10-01 20:16:49 +00:00
|
|
|
|
2020-08-18 17:55:01 +00:00
|
|
|
if (!wl->toplevel_configured)
|
2020-08-02 21:45:06 +00:00
|
|
|
mp_input_set_mouse_pos(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y);
|
2020-08-18 17:55:01 +00:00
|
|
|
wl->toplevel_configured = false;
|
2017-10-01 20:16:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2020-11-07 19:47:26 +00:00
|
|
|
int mpmod = 0;
|
2017-10-01 20:16:49 +00:00
|
|
|
|
|
|
|
state = state == WL_POINTER_BUTTON_STATE_PRESSED ? MP_KEY_STATE_DOWN
|
|
|
|
: MP_KEY_STATE_UP;
|
|
|
|
|
2019-07-11 14:02:33 +00:00
|
|
|
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 = 0;
|
|
|
|
break;
|
|
|
|
}
|
2017-10-01 20:16:49 +00:00
|
|
|
|
2020-11-07 19:47:26 +00:00
|
|
|
if (wl->keyboard)
|
|
|
|
mpmod = get_mods(wl);
|
|
|
|
|
|
|
|
if (button)
|
|
|
|
mp_input_put_key(wl->vo->input_ctx, button | state | mpmod);
|
2017-10-01 20:16:49 +00:00
|
|
|
|
|
|
|
if (!mp_input_test_dragging(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y) &&
|
2021-03-02 22:38:54 +00:00
|
|
|
(!wl->vo_opts->fullscreen) && (!wl->vo_opts->window_maximized) &&
|
2019-11-25 23:52:53 +00:00
|
|
|
(button == MP_MBTN_LEFT) && (state == MP_KEY_STATE_DOWN)) {
|
|
|
|
uint32_t edges;
|
|
|
|
// Implement an edge resize zone if there are no decorations
|
|
|
|
if (!wl->xdg_toplevel_decoration &&
|
|
|
|
check_for_resize(wl, wl->mouse_unscaled_x, wl->mouse_unscaled_y,
|
2019-11-26 23:36:35 +00:00
|
|
|
wl->opts->edge_pixels_pointer, &edges))
|
2019-11-25 23:52:53 +00:00
|
|
|
xdg_toplevel_resize(wl->xdg_toplevel, wl->seat, serial, edges);
|
|
|
|
else
|
|
|
|
window_move(wl, serial);
|
2020-04-23 19:49:18 +00:00
|
|
|
// Explictly send an UP event after the client finishes a move/resize
|
|
|
|
mp_input_put_key(wl->vo->input_ctx, button | MP_KEY_STATE_UP);
|
2019-11-25 23:52:53 +00:00
|
|
|
}
|
2017-10-01 20:16:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2020-03-18 18:14:50 +00:00
|
|
|
|
|
|
|
double val = wl_fixed_to_double(value) < 0 ? -1 : 1;
|
2017-10-01 20:16:49 +00:00
|
|
|
switch (axis) {
|
|
|
|
case WL_POINTER_AXIS_VERTICAL_SCROLL:
|
|
|
|
if (value > 0)
|
|
|
|
mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_DOWN, +val);
|
|
|
|
if (value < 0)
|
|
|
|
mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_UP, -val);
|
|
|
|
break;
|
|
|
|
case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
|
|
|
|
if (value > 0)
|
|
|
|
mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_RIGHT, +val);
|
|
|
|
if (value < 0)
|
|
|
|
mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_LEFT, -val);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct wl_pointer_listener pointer_listener = {
|
|
|
|
pointer_handle_enter,
|
|
|
|
pointer_handle_leave,
|
|
|
|
pointer_handle_motion,
|
|
|
|
pointer_handle_button,
|
|
|
|
pointer_handle_axis,
|
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
2018-04-23 14:10:40 +00:00
|
|
|
enum xdg_toplevel_resize_edge edge;
|
2019-11-26 23:36:35 +00:00
|
|
|
if (check_for_resize(wl, x_w, y_w, wl->opts->edge_pixels_touch, &edge)) {
|
2017-10-01 20:16:49 +00:00
|
|
|
wl->touch_entries = 0;
|
2018-04-23 14:10:40 +00:00
|
|
|
xdg_toplevel_resize(wl->xdg_toplevel, wl->seat, serial, edge);
|
2017-10-01 20:16:49 +00:00
|
|
|
return;
|
|
|
|
} else if (wl->touch_entries) {
|
|
|
|
wl->touch_entries = 0;
|
2018-04-23 14:10:40 +00:00
|
|
|
xdg_toplevel_move(wl->xdg_toplevel, wl->seat, serial);
|
2017-10-01 20:16:49 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
wl->touch_entries = 1;
|
|
|
|
|
|
|
|
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);
|
|
|
|
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;
|
|
|
|
|
|
|
|
wl->touch_entries = 0;
|
|
|
|
|
|
|
|
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 const struct wl_touch_listener touch_listener = {
|
|
|
|
touch_handle_down,
|
|
|
|
touch_handle_up,
|
|
|
|
touch_handle_motion,
|
|
|
|
touch_handle_frame,
|
|
|
|
touch_handle_cancel,
|
|
|
|
};
|
2015-03-22 00:47:27 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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;
|
2013-07-18 15:35:28 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
|
|
|
|
close(fd);
|
|
|
|
return;
|
|
|
|
}
|
2013-07-18 15:35:28 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
map_str = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
|
|
|
|
if (map_str == MAP_FAILED) {
|
|
|
|
close(fd);
|
|
|
|
return;
|
|
|
|
}
|
2013-07-18 15:35:28 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
wl->xkb_keymap = xkb_keymap_new_from_string(wl->xkb_context, map_str,
|
|
|
|
XKB_KEYMAP_FORMAT_TEXT_V1, 0);
|
2013-07-18 15:35:28 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
munmap(map_str, size);
|
|
|
|
close(fd);
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
if (!wl->xkb_keymap) {
|
2013-09-12 14:29:13 +00:00
|
|
|
MP_ERR(wl, "failed to compile keymap\n");
|
2013-02-28 18:55:02 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
wl->xkb_state = xkb_state_new(wl->xkb_keymap);
|
|
|
|
if (!wl->xkb_state) {
|
2013-09-12 14:29:13 +00:00
|
|
|
MP_ERR(wl, "failed to create XKB state\n");
|
2017-10-01 20:16:49 +00:00
|
|
|
xkb_keymap_unref(wl->xkb_keymap);
|
|
|
|
wl->xkb_keymap = NULL;
|
2013-02-28 18:55:02 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
static void keyboard_handle_enter(void *data, struct wl_keyboard *wl_keyboard,
|
|
|
|
uint32_t serial, struct wl_surface *surface,
|
2013-02-28 18:55:02 +00:00
|
|
|
struct wl_array *keys)
|
|
|
|
{
|
2020-09-07 16:22:25 +00:00
|
|
|
struct vo_wayland_state *wl = data;
|
|
|
|
wl->has_keyboard_input = true;
|
2013-02-28 18:55:02 +00:00
|
|
|
}
|
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
static void keyboard_handle_leave(void *data, struct wl_keyboard *wl_keyboard,
|
|
|
|
uint32_t serial, struct wl_surface *surface)
|
2013-02-28 18:55:02 +00:00
|
|
|
{
|
2020-09-07 16:22:25 +00:00
|
|
|
struct vo_wayland_state *wl = data;
|
|
|
|
wl->has_keyboard_input = false;
|
2013-02-28 18:55:02 +00:00
|
|
|
}
|
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
static void keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard,
|
|
|
|
uint32_t serial, uint32_t time, uint32_t key,
|
2013-02-28 18:55:02 +00:00
|
|
|
uint32_t state)
|
|
|
|
{
|
|
|
|
struct vo_wayland_state *wl = data;
|
|
|
|
|
2020-11-07 19:47:26 +00:00
|
|
|
wl->keyboard_code = key + 8;
|
|
|
|
xkb_keysym_t sym = xkb_state_key_get_one_sym(wl->xkb_state, wl->keyboard_code);
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2020-11-07 19:47:26 +00:00
|
|
|
state = state == WL_KEYBOARD_KEY_STATE_PRESSED ? MP_KEY_STATE_DOWN
|
|
|
|
: MP_KEY_STATE_UP;
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2020-11-07 19:47:26 +00:00
|
|
|
int mpmod = get_mods(wl);
|
2015-01-12 15:41:00 +00:00
|
|
|
int mpkey = lookupkey(sym);
|
|
|
|
if (mpkey) {
|
2020-11-07 19:47:26 +00:00
|
|
|
mp_input_put_key(wl->vo->input_ctx, mpkey | state | mpmod);
|
2015-01-12 15:41:00 +00:00
|
|
|
} else {
|
2017-10-01 20:16:49 +00:00
|
|
|
char s[128];
|
2015-01-12 15:41:00 +00:00
|
|
|
if (xkb_keysym_to_utf8(sym, s, sizeof(s)) > 0)
|
2020-11-07 19:47:26 +00:00
|
|
|
mp_input_put_key_utf8(wl->vo->input_ctx, state | mpmod, bstr0(s));
|
2013-02-28 18:55:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
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,
|
2013-02-28 18:55:02 +00:00
|
|
|
uint32_t group)
|
|
|
|
{
|
2013-07-18 15:35:28 +00:00
|
|
|
struct vo_wayland_state *wl = data;
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
xkb_state_update_mask(wl->xkb_state, mods_depressed, mods_latched,
|
|
|
|
mods_locked, 0, 0, group);
|
2013-02-28 18:55:02 +00:00
|
|
|
}
|
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
static void keyboard_handle_repeat_info(void *data, struct wl_keyboard *wl_keyboard,
|
|
|
|
int32_t rate, int32_t delay)
|
2014-09-19 15:38:24 +00:00
|
|
|
{
|
|
|
|
struct vo_wayland_state *wl = data;
|
2019-11-30 02:00:49 +00:00
|
|
|
if (wl->vo_opts->native_keyrepeat)
|
2014-09-19 15:38:24 +00:00
|
|
|
mp_input_set_repeat_info(wl->vo->input_ctx, rate, delay);
|
|
|
|
}
|
|
|
|
|
2014-01-08 15:15:26 +00:00
|
|
|
static const struct wl_keyboard_listener keyboard_listener = {
|
2013-02-28 18:55:02 +00:00
|
|
|
keyboard_handle_keymap,
|
|
|
|
keyboard_handle_enter,
|
|
|
|
keyboard_handle_leave,
|
|
|
|
keyboard_handle_key,
|
2014-09-19 15:38:24 +00:00
|
|
|
keyboard_handle_modifiers,
|
2017-10-01 20:16:49 +00:00
|
|
|
keyboard_handle_repeat_info,
|
2013-02-28 18:55:02 +00:00
|
|
|
};
|
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
static void seat_handle_caps(void *data, struct wl_seat *seat,
|
|
|
|
enum wl_seat_capability caps)
|
2013-02-28 18:55:02 +00:00
|
|
|
{
|
|
|
|
struct vo_wayland_state *wl = data;
|
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
if ((caps & WL_SEAT_CAPABILITY_POINTER) && !wl->pointer) {
|
|
|
|
wl->pointer = wl_seat_get_pointer(seat);
|
|
|
|
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;
|
|
|
|
}
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
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;
|
|
|
|
}
|
2013-02-28 18:55:02 +00:00
|
|
|
}
|
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
static const struct wl_seat_listener seat_listener = {
|
|
|
|
seat_handle_caps,
|
|
|
|
};
|
|
|
|
|
|
|
|
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->dnd_mime_score = score;
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2017-03-17 06:25:32 +00:00
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
static void data_offer_source_actions(void *data, struct wl_data_offer *offer, uint32_t source_actions)
|
|
|
|
{
|
2013-02-28 18:55:02 +00:00
|
|
|
}
|
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
static void data_offer_action(void *data, struct wl_data_offer *wl_data_offer, uint32_t dnd_action)
|
2013-02-28 18:55:02 +00:00
|
|
|
{
|
|
|
|
struct vo_wayland_state *wl = data;
|
2017-10-01 20:16:49 +00:00
|
|
|
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");
|
2013-02-28 18:55:02 +00:00
|
|
|
}
|
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
static const struct wl_data_offer_listener data_offer_listener = {
|
|
|
|
data_offer_handle_offer,
|
|
|
|
data_offer_source_actions,
|
|
|
|
data_offer_action,
|
2013-02-28 18:55:02 +00:00
|
|
|
};
|
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
static void data_device_handle_data_offer(void *data, struct wl_data_device *wl_ddev,
|
|
|
|
struct wl_data_offer *id)
|
2013-02-28 18:55:02 +00:00
|
|
|
{
|
|
|
|
struct vo_wayland_state *wl = data;
|
2017-10-01 20:16:49 +00:00
|
|
|
if (wl->dnd_offer)
|
|
|
|
wl_data_offer_destroy(wl->dnd_offer);
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
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;
|
2013-02-28 18:55:02 +00:00
|
|
|
}
|
2017-10-01 20:16:49 +00:00
|
|
|
|
|
|
|
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);
|
2013-02-28 18:55:02 +00:00
|
|
|
}
|
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
static void data_device_handle_leave(void *data, struct wl_data_device *wl_ddev)
|
2014-09-19 15:38:24 +00:00
|
|
|
{
|
|
|
|
struct vo_wayland_state *wl = data;
|
2017-10-01 20:16:49 +00:00
|
|
|
|
|
|
|
if (wl->dnd_offer) {
|
|
|
|
if (wl->dnd_fd != -1)
|
|
|
|
return;
|
|
|
|
wl_data_offer_destroy(wl->dnd_offer);
|
|
|
|
wl->dnd_offer = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
MP_VERBOSE(wl, "Releasing DND offer with mime type %s\n", wl->dnd_mime_type);
|
|
|
|
|
|
|
|
talloc_free(wl->dnd_mime_type);
|
|
|
|
wl->dnd_mime_type = NULL;
|
|
|
|
wl->dnd_mime_score = 0;
|
2014-09-19 15:38:24 +00:00
|
|
|
}
|
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
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;
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
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)
|
2013-02-28 18:55:02 +00:00
|
|
|
{
|
|
|
|
struct vo_wayland_state *wl = data;
|
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
int pipefd[2];
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
if (pipe2(pipefd, O_CLOEXEC) == -1) {
|
|
|
|
MP_ERR(wl, "Failed to create dnd pipe!\n");
|
|
|
|
return;
|
2013-02-28 18:55:02 +00:00
|
|
|
}
|
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
MP_VERBOSE(wl, "Receiving DND offer with mime %s\n", wl->dnd_mime_type);
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
wl_data_offer_receive(wl->dnd_offer, wl->dnd_mime_type, pipefd[1]);
|
|
|
|
close(pipefd[1]);
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
wl->dnd_fd = pipefd[0];
|
2020-07-29 23:13:59 +00:00
|
|
|
wl_data_offer_finish(wl->dnd_offer);
|
2017-10-01 20:16:49 +00:00
|
|
|
}
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
static void data_device_handle_selection(void *data, struct wl_data_device *wl_ddev,
|
|
|
|
struct wl_data_offer *id)
|
|
|
|
{
|
|
|
|
}
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
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,
|
|
|
|
};
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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)
|
wayland: handle multiple outputs more correctly
There's a bit of a catch-22 in the wayland backend. mpv needs to know
several things about the wl_output the surface is on (geometry, scale,
etc.) for lots of its options. You still have to render something
somewhere before you can know what wl_output the surface is actually on.
So this means that when initializing the player, it is entirely possible
to calculate initial parameters using the wrong wl_output. The surface
listener is what will eventually correct this and pick the correct
output. However not everything was technically working correctly in a
multi-output setup.
The first rule here is to rework find_output so that it returns a
vo_wayland_output instead of internally setting wl->current_output. The
reason is simply because the output found here is not guaranteed to be
the output the surface is actually on. Note that for initialization of
the player, we must set the output returned from this function as the
wl->current_output even if it is not technically correct. The surface
listener will fix it later.
vo_wayland_reconfig has to confusingly serve two roles. It must ensure
some wayland-related things are configured as well as setup things for
mpv's vo. The various functions are shuffled around and some things are
removed here which has subtle implications. For instance, there's no
reason to always set the buffer scale. It only needs to be done once
(when the wl->current_output is being created). A roundtrip needs to be
done once after a wl_surface_commit to ensure there are no configuration
errors.
surface_handle_enter is now handles two different things: scaling as
well as mpv's autofit/geometry options. When a surface enters a new
output, the new scaling value is applied to all of the geometry-related
structs (previously, this wasn't done). This ensures, in a multi-monitor
case with mixed scale values, the surface is rescaled correctly to the
actual output it is on if the initial selection of wl->current_output is
incorrect.
Additionally, autofit/geometry values are recalculated if they exist.
This means that dragging a surface across different outputs will autofit
correctly to the new output and not always be "stuck" on the old one.
A very astute observer may notice that set_buffer_scale isn't set when
the surface enters a new output. The API doesn't really indicate this,
but a WAYLAND_DEBUG log reveals that the compositor (well at least
sway/wlroots anyway) magically sets this for you. That's quite fortunate
because setting in the surface handler caused all sorts of problems.
2020-12-06 23:34:36 +00:00
|
|
|
{
|
2021-06-26 21:12:03 +00:00
|
|
|
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;
|
wayland: handle multiple outputs more correctly
There's a bit of a catch-22 in the wayland backend. mpv needs to know
several things about the wl_output the surface is on (geometry, scale,
etc.) for lots of its options. You still have to render something
somewhere before you can know what wl_output the surface is actually on.
So this means that when initializing the player, it is entirely possible
to calculate initial parameters using the wrong wl_output. The surface
listener is what will eventually correct this and pick the correct
output. However not everything was technically working correctly in a
multi-output setup.
The first rule here is to rework find_output so that it returns a
vo_wayland_output instead of internally setting wl->current_output. The
reason is simply because the output found here is not guaranteed to be
the output the surface is actually on. Note that for initialization of
the player, we must set the output returned from this function as the
wl->current_output even if it is not technically correct. The surface
listener will fix it later.
vo_wayland_reconfig has to confusingly serve two roles. It must ensure
some wayland-related things are configured as well as setup things for
mpv's vo. The various functions are shuffled around and some things are
removed here which has subtle implications. For instance, there's no
reason to always set the buffer scale. It only needs to be done once
(when the wl->current_output is being created). A roundtrip needs to be
done once after a wl_surface_commit to ensure there are no configuration
errors.
surface_handle_enter is now handles two different things: scaling as
well as mpv's autofit/geometry options. When a surface enters a new
output, the new scaling value is applied to all of the geometry-related
structs (previously, this wasn't done). This ensures, in a multi-monitor
case with mixed scale values, the surface is rescaled correctly to the
actual output it is on if the initial selection of wl->current_output is
incorrect.
Additionally, autofit/geometry values are recalculated if they exist.
This means that dragging a surface across different outputs will autofit
correctly to the new output and not always be "stuck" on the old one.
A very astute observer may notice that set_buffer_scale isn't set when
the surface enters a new output. The API doesn't really indicate this,
but a WAYLAND_DEBUG log reveals that the compositor (well at least
sway/wlroots anyway) magically sets this for you. That's quite fortunate
because setting in the surface handler caused all sorts of problems.
2020-12-06 23:34:36 +00:00
|
|
|
}
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
static void output_handle_mode(void *data, struct wl_output *wl_output,
|
|
|
|
uint32_t flags, int32_t width,
|
|
|
|
int32_t height, int32_t refresh)
|
2017-10-09 01:08:36 +00:00
|
|
|
{
|
2021-06-26 21:12:03 +00:00
|
|
|
struct vo_wayland_output *output = data;
|
2017-10-09 01:08:36 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
/* Only save current mode */
|
|
|
|
if (!(flags & WL_OUTPUT_MODE_CURRENT))
|
|
|
|
return;
|
2017-10-09 01:08:36 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
output->geometry.x1 = width;
|
|
|
|
output->geometry.y1 = height;
|
|
|
|
output->flags = flags;
|
|
|
|
output->refresh_rate = (double)refresh * 0.001;
|
|
|
|
}
|
2020-12-13 21:03:34 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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;
|
wayland: handle multiple outputs more correctly
There's a bit of a catch-22 in the wayland backend. mpv needs to know
several things about the wl_output the surface is on (geometry, scale,
etc.) for lots of its options. You still have to render something
somewhere before you can know what wl_output the surface is actually on.
So this means that when initializing the player, it is entirely possible
to calculate initial parameters using the wrong wl_output. The surface
listener is what will eventually correct this and pick the correct
output. However not everything was technically working correctly in a
multi-output setup.
The first rule here is to rework find_output so that it returns a
vo_wayland_output instead of internally setting wl->current_output. The
reason is simply because the output found here is not guaranteed to be
the output the surface is actually on. Note that for initialization of
the player, we must set the output returned from this function as the
wl->current_output even if it is not technically correct. The surface
listener will fix it later.
vo_wayland_reconfig has to confusingly serve two roles. It must ensure
some wayland-related things are configured as well as setup things for
mpv's vo. The various functions are shuffled around and some things are
removed here which has subtle implications. For instance, there's no
reason to always set the buffer scale. It only needs to be done once
(when the wl->current_output is being created). A roundtrip needs to be
done once after a wl_surface_commit to ensure there are no configuration
errors.
surface_handle_enter is now handles two different things: scaling as
well as mpv's autofit/geometry options. When a surface enters a new
output, the new scaling value is applied to all of the geometry-related
structs (previously, this wasn't done). This ensures, in a multi-monitor
case with mixed scale values, the surface is rescaled correctly to the
actual output it is on if the initial selection of wl->current_output is
incorrect.
Additionally, autofit/geometry values are recalculated if they exist.
This means that dragging a surface across different outputs will autofit
correctly to the new output and not always be "stuck" on the old one.
A very astute observer may notice that set_buffer_scale isn't set when
the surface enters a new output. The API doesn't really indicate this,
but a WAYLAND_DEBUG log reveals that the compositor (well at least
sway/wlroots anyway) magically sets this for you. That's quite fortunate
because setting in the surface handler caused all sorts of problems.
2020-12-06 23:34:36 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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) {
|
2021-06-28 16:03:51 +00:00
|
|
|
set_surface_scaling(wl);
|
|
|
|
if (!wl->vo_opts->fullscreen && !wl->vo_opts->window_maximized) {
|
|
|
|
wl_surface_set_buffer_scale(wl->surface, wl->scaling);
|
|
|
|
} else {
|
|
|
|
wl->scale_change = true;
|
|
|
|
}
|
2021-06-26 21:12:03 +00:00
|
|
|
spawn_cursor(wl);
|
|
|
|
set_geometry(wl);
|
|
|
|
wl->window_size = wl->vdparams;
|
|
|
|
if (!wl->vo_opts->fullscreen && !wl->vo_opts->window_maximized)
|
|
|
|
wl->geometry = wl->window_size;
|
|
|
|
wl->pending_vo_events |= VO_EVENT_DPI;
|
|
|
|
wl->pending_vo_events |= VO_EVENT_RESIZE;
|
|
|
|
}
|
|
|
|
|
|
|
|
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 const struct wl_output_listener output_listener = {
|
|
|
|
output_handle_geometry,
|
|
|
|
output_handle_mode,
|
|
|
|
output_handle_done,
|
|
|
|
output_handle_scale,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void surface_handle_enter(void *data, struct wl_surface *wl_surface,
|
|
|
|
struct wl_output *output)
|
|
|
|
{
|
|
|
|
struct vo_wayland_state *wl = data;
|
|
|
|
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;
|
|
|
|
|
2021-06-28 16:03:51 +00:00
|
|
|
if (wl->scaling != wl->current_output->scale) {
|
|
|
|
set_surface_scaling(wl);
|
|
|
|
if (!wl->vo_opts->fullscreen && !wl->vo_opts->window_maximized) {
|
|
|
|
wl->scale_change = true;
|
|
|
|
} else {
|
|
|
|
wl_surface_set_buffer_scale(wl->surface, wl->scaling);
|
|
|
|
}
|
|
|
|
spawn_cursor(wl);
|
|
|
|
wl->pending_vo_events |= VO_EVENT_DPI;
|
|
|
|
}
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
if (!mp_rect_equals(&old_output_geometry, &wl->current_output->geometry)) {
|
|
|
|
set_geometry(wl);
|
|
|
|
wl->window_size = wl->vdparams;
|
|
|
|
force_resize = true;
|
|
|
|
}
|
|
|
|
|
2020-12-13 21:03:34 +00:00
|
|
|
if (!wl->vo_opts->fullscreen && !wl->vo_opts->window_maximized)
|
wayland: handle multiple outputs more correctly
There's a bit of a catch-22 in the wayland backend. mpv needs to know
several things about the wl_output the surface is on (geometry, scale,
etc.) for lots of its options. You still have to render something
somewhere before you can know what wl_output the surface is actually on.
So this means that when initializing the player, it is entirely possible
to calculate initial parameters using the wrong wl_output. The surface
listener is what will eventually correct this and pick the correct
output. However not everything was technically working correctly in a
multi-output setup.
The first rule here is to rework find_output so that it returns a
vo_wayland_output instead of internally setting wl->current_output. The
reason is simply because the output found here is not guaranteed to be
the output the surface is actually on. Note that for initialization of
the player, we must set the output returned from this function as the
wl->current_output even if it is not technically correct. The surface
listener will fix it later.
vo_wayland_reconfig has to confusingly serve two roles. It must ensure
some wayland-related things are configured as well as setup things for
mpv's vo. The various functions are shuffled around and some things are
removed here which has subtle implications. For instance, there's no
reason to always set the buffer scale. It only needs to be done once
(when the wl->current_output is being created). A roundtrip needs to be
done once after a wl_surface_commit to ensure there are no configuration
errors.
surface_handle_enter is now handles two different things: scaling as
well as mpv's autofit/geometry options. When a surface enters a new
output, the new scaling value is applied to all of the geometry-related
structs (previously, this wasn't done). This ensures, in a multi-monitor
case with mixed scale values, the surface is rescaled correctly to the
actual output it is on if the initial selection of wl->current_output is
incorrect.
Additionally, autofit/geometry values are recalculated if they exist.
This means that dragging a surface across different outputs will autofit
correctly to the new output and not always be "stuck" on the old one.
A very astute observer may notice that set_buffer_scale isn't set when
the surface enters a new output. The API doesn't really indicate this,
but a WAYLAND_DEBUG log reveals that the compositor (well at least
sway/wlroots anyway) magically sets this for you. That's quite fortunate
because setting in the surface handler caused all sorts of problems.
2020-12-06 23:34:36 +00:00
|
|
|
wl->geometry = wl->window_size;
|
2020-12-13 21:03:34 +00:00
|
|
|
|
|
|
|
if (!mp_rect_equals(&old_geometry, &wl->geometry) || force_resize)
|
2017-10-09 01:08:36 +00:00
|
|
|
wl->pending_vo_events |= VO_EVENT_RESIZE;
|
|
|
|
|
|
|
|
MP_VERBOSE(wl, "Surface entered output %s %s (0x%x), scale = %i\n", o->make,
|
|
|
|
o->model, o->id, wl->scaling);
|
|
|
|
|
|
|
|
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;
|
|
|
|
wl->pending_vo_events |= VO_EVENT_WIN_STATE;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct wl_surface_listener surface_listener = {
|
|
|
|
surface_handle_enter,
|
|
|
|
surface_handle_leave,
|
|
|
|
};
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
static void xdg_wm_base_ping(void *data, struct xdg_wm_base *wm_base, uint32_t serial)
|
2013-02-28 18:55:02 +00:00
|
|
|
{
|
2021-06-26 21:12:03 +00:00
|
|
|
xdg_wm_base_pong(wm_base, serial);
|
2013-02-28 18:55:02 +00:00
|
|
|
}
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
static const struct xdg_wm_base_listener xdg_wm_base_listener = {
|
|
|
|
xdg_wm_base_ping,
|
2017-10-01 20:16:49 +00:00
|
|
|
};
|
|
|
|
|
2018-04-23 14:10:40 +00:00
|
|
|
static void handle_surface_config(void *data, struct xdg_surface *surface,
|
2017-10-01 20:16:49 +00:00
|
|
|
uint32_t serial)
|
2014-08-06 10:41:33 +00:00
|
|
|
{
|
2018-04-23 14:10:40 +00:00
|
|
|
xdg_surface_ack_configure(surface, serial);
|
2014-08-06 10:41:33 +00:00
|
|
|
}
|
|
|
|
|
2018-04-23 14:10:40 +00:00
|
|
|
static const struct xdg_surface_listener xdg_surface_listener = {
|
2017-10-01 20:16:49 +00:00
|
|
|
handle_surface_config,
|
|
|
|
};
|
|
|
|
|
2018-04-23 14:10:40 +00:00
|
|
|
static void handle_toplevel_config(void *data, struct xdg_toplevel *toplevel,
|
2017-10-01 20:16:49 +00:00
|
|
|
int32_t width, int32_t height, struct wl_array *states)
|
2014-08-06 10:41:33 +00:00
|
|
|
{
|
2017-10-01 20:16:49 +00:00
|
|
|
struct vo_wayland_state *wl = data;
|
2019-11-30 02:00:49 +00:00
|
|
|
struct mp_vo_opts *vo_opts = wl->vo_opts;
|
2017-10-01 20:16:49 +00:00
|
|
|
struct mp_rect old_geometry = wl->geometry;
|
|
|
|
|
2021-04-25 16:40:49 +00:00
|
|
|
/* Don't do anything here if we haven't finished setting geometry. */
|
|
|
|
if (mp_rect_w(wl->geometry) == 0 || mp_rect_h(wl->geometry) == 0)
|
|
|
|
return;
|
|
|
|
|
2020-08-18 17:55:01 +00:00
|
|
|
bool is_maximized = false;
|
|
|
|
bool is_fullscreen = false;
|
2020-09-07 16:22:25 +00:00
|
|
|
bool is_activated = false;
|
2018-04-23 14:10:40 +00:00
|
|
|
enum xdg_toplevel_state *state;
|
2017-10-09 01:08:36 +00:00
|
|
|
wl_array_for_each(state, states) {
|
|
|
|
switch (*state) {
|
2018-04-23 14:10:40 +00:00
|
|
|
case XDG_TOPLEVEL_STATE_FULLSCREEN:
|
2020-08-18 17:55:01 +00:00
|
|
|
is_fullscreen = true;
|
2017-10-09 01:08:36 +00:00
|
|
|
break;
|
2018-04-23 14:10:40 +00:00
|
|
|
case XDG_TOPLEVEL_STATE_RESIZING:
|
2017-10-09 01:08:36 +00:00
|
|
|
wl->pending_vo_events |= VO_EVENT_LIVE_RESIZING;
|
|
|
|
break;
|
2018-04-23 14:10:40 +00:00
|
|
|
case XDG_TOPLEVEL_STATE_ACTIVATED:
|
2020-09-07 16:22:25 +00:00
|
|
|
is_activated = true;
|
2019-11-30 02:00:49 +00:00
|
|
|
/*
|
|
|
|
* If we get an ACTIVATED state, we know it cannot be
|
2020-02-06 02:47:10 +00:00
|
|
|
* minimized, but it may not have been minimized
|
2019-11-30 02:00:49 +00:00
|
|
|
* 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);
|
2017-10-09 01:08:36 +00:00
|
|
|
break;
|
2018-05-15 19:20:16 +00:00
|
|
|
case XDG_TOPLEVEL_STATE_TILED_TOP:
|
|
|
|
case XDG_TOPLEVEL_STATE_TILED_LEFT:
|
|
|
|
case XDG_TOPLEVEL_STATE_TILED_RIGHT:
|
|
|
|
case XDG_TOPLEVEL_STATE_TILED_BOTTOM:
|
|
|
|
case XDG_TOPLEVEL_STATE_MAXIMIZED:
|
2020-08-18 17:55:01 +00:00
|
|
|
is_maximized = true;
|
2018-05-15 19:20:16 +00:00
|
|
|
break;
|
2017-10-01 20:16:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-18 17:55:01 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2020-09-07 16:22:25 +00:00
|
|
|
if (wl->activated != is_activated) {
|
|
|
|
wl->activated = is_activated;
|
|
|
|
if ((!wl->focused && wl->activated && wl->has_keyboard_input) ||
|
|
|
|
(wl->focused && !wl->activated))
|
|
|
|
{
|
|
|
|
wl->focused = !wl->focused;
|
|
|
|
wl->pending_vo_events |= VO_EVENT_FOCUS;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-18 17:55:01 +00:00
|
|
|
if (!(wl->pending_vo_events & VO_EVENT_LIVE_RESIZING))
|
|
|
|
vo_query_and_reset_events(wl->vo, VO_EVENT_LIVE_RESIZING);
|
2019-11-30 02:00:49 +00:00
|
|
|
|
2020-02-06 02:47:10 +00:00
|
|
|
int old_toplevel_width = wl->toplevel_width;
|
|
|
|
int old_toplevel_height = wl->toplevel_height;
|
|
|
|
wl->toplevel_width = width;
|
|
|
|
wl->toplevel_height = height;
|
|
|
|
|
2021-06-28 16:03:51 +00:00
|
|
|
if (wl->scale_change) {
|
|
|
|
wl_surface_set_buffer_scale(wl->surface, wl->scaling);
|
|
|
|
wl->scale_change = false;
|
|
|
|
}
|
|
|
|
|
2020-08-18 17:55:01 +00:00
|
|
|
if (wl->state_change) {
|
|
|
|
if (!is_fullscreen && !is_maximized) {
|
|
|
|
wl->geometry = wl->window_size;
|
|
|
|
wl->state_change = false;
|
|
|
|
goto resize;
|
|
|
|
}
|
|
|
|
}
|
2017-10-05 14:34:48 +00:00
|
|
|
|
2020-02-06 02:47:10 +00:00
|
|
|
if (old_toplevel_width == wl->toplevel_width && old_toplevel_height == wl->toplevel_height)
|
|
|
|
return;
|
|
|
|
|
2020-08-18 17:55:01 +00:00
|
|
|
if (!is_fullscreen && !is_maximized) {
|
|
|
|
if (vo_opts->keepaspect && vo_opts->keepaspect_window) {
|
|
|
|
if (abs(wl->toplevel_width - old_toplevel_width) > abs(wl->toplevel_height - old_toplevel_height)) {
|
|
|
|
double scale_factor = (double)width / wl->reduced_width;
|
|
|
|
width = wl->reduced_width * scale_factor;
|
|
|
|
} else {
|
|
|
|
double scale_factor = (double)height / wl->reduced_height;
|
|
|
|
height = wl->reduced_height * scale_factor;
|
2017-10-01 20:16:49 +00:00
|
|
|
}
|
|
|
|
}
|
2020-08-18 17:55:01 +00:00
|
|
|
wl->window_size.x0 = 0;
|
|
|
|
wl->window_size.y0 = 0;
|
|
|
|
wl->window_size.x1 = width;
|
|
|
|
wl->window_size.y1 = height;
|
2017-10-01 20:16:49 +00:00
|
|
|
}
|
2020-08-18 17:55:01 +00:00
|
|
|
wl->geometry.x0 = 0;
|
|
|
|
wl->geometry.y0 = 0;
|
|
|
|
wl->geometry.x1 = width;
|
|
|
|
wl->geometry.y1 = height;
|
2017-10-01 20:16:49 +00:00
|
|
|
|
2017-10-09 01:08:36 +00:00
|
|
|
if (mp_rect_equals(&old_geometry, &wl->geometry))
|
|
|
|
return;
|
|
|
|
|
2020-08-18 17:55:01 +00:00
|
|
|
resize:
|
2017-10-01 20:16:49 +00:00
|
|
|
MP_VERBOSE(wl, "Resizing due to xdg from %ix%i to %ix%i\n",
|
|
|
|
mp_rect_w(old_geometry)*wl->scaling, mp_rect_h(old_geometry)*wl->scaling,
|
|
|
|
mp_rect_w(wl->geometry)*wl->scaling, mp_rect_h(wl->geometry)*wl->scaling);
|
|
|
|
|
|
|
|
wl->pending_vo_events |= VO_EVENT_RESIZE;
|
2020-08-18 17:55:01 +00:00
|
|
|
wl->toplevel_configured = true;
|
2014-08-06 10:41:33 +00:00
|
|
|
}
|
|
|
|
|
2018-04-23 14:10:40 +00:00
|
|
|
static void handle_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel)
|
2014-08-06 10:41:33 +00:00
|
|
|
{
|
2017-10-05 15:43:44 +00:00
|
|
|
struct vo_wayland_state *wl = data;
|
|
|
|
mp_input_put_key(wl->vo->input_ctx, MP_KEY_CLOSE_WIN);
|
2014-08-06 10:41:33 +00:00
|
|
|
}
|
|
|
|
|
2018-04-23 14:10:40 +00:00
|
|
|
static const struct xdg_toplevel_listener xdg_toplevel_listener = {
|
2017-10-01 20:16:49 +00:00
|
|
|
handle_toplevel_config,
|
|
|
|
handle_toplevel_close,
|
|
|
|
};
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
static void pres_set_clockid(void *data, struct wp_presentation *pres,
|
|
|
|
uint32_t clockid)
|
2013-05-07 18:51:09 +00:00
|
|
|
{
|
2021-06-26 21:12:03 +00:00
|
|
|
struct vo_wayland_state *wl = data;
|
2013-08-23 09:29:09 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
if (clockid == CLOCK_MONOTONIC)
|
|
|
|
wl->presentation = pres;
|
2013-05-07 18:51:09 +00:00
|
|
|
}
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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)
|
2020-08-12 14:51:51 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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)
|
2013-02-28 18:55:02 +00:00
|
|
|
{
|
2021-06-26 21:12:03 +00:00
|
|
|
struct vo_wayland_state *wl = data;
|
|
|
|
sync_shift(wl);
|
2018-11-10 18:45:58 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
if (fback)
|
|
|
|
wp_presentation_feedback_destroy(fback);
|
|
|
|
|
|
|
|
wl->refresh_interval = (int64_t)refresh_nsec / 1000;
|
|
|
|
|
|
|
|
// 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 everytime the compositor receives feedback.
|
|
|
|
|
|
|
|
int index = last_available_sync(wl);
|
|
|
|
if (index < 0) {
|
|
|
|
queue_new_sync(wl);
|
|
|
|
index = 0;
|
2017-10-09 01:08:36 +00:00
|
|
|
}
|
2021-06-26 21:12:03 +00:00
|
|
|
int64_t sec = (uint64_t) tv_sec_lo + ((uint64_t) tv_sec_hi << 32);
|
|
|
|
wl->sync[index].ust = sec * 1000000LL + (uint64_t) tv_nsec / 1000;
|
|
|
|
wl->sync[index].msc = (uint64_t) seq_lo + ((uint64_t) seq_hi << 32);
|
|
|
|
wl->sync[index].filled = true;
|
2017-10-01 20:16:49 +00:00
|
|
|
}
|
2014-01-07 00:06:28 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
static void feedback_discarded(void *data, struct wp_presentation_feedback *fback)
|
2017-10-01 20:16:49 +00:00
|
|
|
{
|
2021-06-26 21:12:03 +00:00
|
|
|
}
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
static const struct wp_presentation_feedback_listener feedback_listener = {
|
|
|
|
feedback_sync_output,
|
|
|
|
feedback_presented,
|
|
|
|
feedback_discarded,
|
|
|
|
};
|
2013-09-10 16:33:43 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
static const struct wl_callback_listener frame_listener;
|
2017-10-12 22:14:05 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
static void frame_callback(void *data, struct wl_callback *callback, uint32_t time)
|
|
|
|
{
|
|
|
|
struct vo_wayland_state *wl = data;
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
if (callback)
|
|
|
|
wl_callback_destroy(callback);
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
wl->frame_callback = wl_surface_frame(wl->surface);
|
|
|
|
wl_callback_add_listener(wl->frame_callback, &frame_listener, wl);
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
if (wl->presentation) {
|
|
|
|
wl->feedback = wp_presentation_feedback(wl->presentation, wl->surface);
|
|
|
|
wp_presentation_feedback_add_listener(wl->feedback, &feedback_listener, wl);
|
|
|
|
}
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
wl->frame_wait = false;
|
2021-07-26 19:26:51 +00:00
|
|
|
wl->hidden = false;
|
2021-06-26 21:12:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static const struct wl_callback_listener frame_listener = {
|
|
|
|
frame_callback,
|
|
|
|
};
|
|
|
|
|
|
|
|
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 >= 3) && found++) {
|
|
|
|
wl->compositor = wl_registry_bind(reg, id, &wl_compositor_interface, 3);
|
|
|
|
wl->surface = wl_compositor_create_surface(wl->compositor);
|
|
|
|
wl->cursor_surface = wl_compositor_create_surface(wl->compositor);
|
|
|
|
wl_surface_add_listener(wl->surface, &surface_listener, wl);
|
2017-10-05 14:34:48 +00:00
|
|
|
}
|
2017-10-01 20:16:49 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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);
|
2017-10-11 18:47:03 +00:00
|
|
|
}
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
if (!strcmp(interface, wl_output_interface.name) && (ver >= 2) && found++) {
|
|
|
|
struct vo_wayland_output *output = talloc_zero(wl, struct vo_wayland_output);
|
2014-01-08 19:58:32 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
output->wl = wl;
|
|
|
|
output->id = id;
|
|
|
|
output->scale = 1;
|
|
|
|
output->output = wl_registry_bind(reg, id, &wl_output_interface, 2);
|
2019-12-30 21:22:02 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
wl_output_add_listener(output->output, &output_listener, output);
|
|
|
|
wl_list_insert(&wl->output_list, &output->link);
|
2017-10-01 20:16:49 +00:00
|
|
|
}
|
2014-06-15 12:46:27 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
if (!strcmp(interface, wl_seat_interface.name) && found++) {
|
|
|
|
wl->seat = wl_registry_bind(reg, id, &wl_seat_interface, 1);
|
|
|
|
wl_seat_add_listener(wl->seat, &seat_listener, wl);
|
2019-10-10 19:14:40 +00:00
|
|
|
}
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
if (!strcmp(interface, wl_shm_interface.name) && found++) {
|
|
|
|
wl->shm = wl_registry_bind(reg, id, &wl_shm_interface, 1);
|
2017-10-01 20:16:49 +00:00
|
|
|
}
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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);
|
|
|
|
}
|
2017-10-09 01:08:36 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
if (!strcmp(interface, xdg_wm_base_interface.name) && found++) {
|
|
|
|
ver = MPMIN(ver, 2); /* We can use either 1 or 2 */
|
|
|
|
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);
|
|
|
|
}
|
2020-12-11 19:14:50 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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);
|
|
|
|
}
|
2014-01-08 19:58:32 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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);
|
2013-02-28 18:55:02 +00:00
|
|
|
}
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
static void registry_handle_remove(void *data, struct wl_registry *reg, uint32_t id)
|
2013-02-28 18:55:02 +00:00
|
|
|
{
|
2021-06-26 21:12:03 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-01-28 12:07:00 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
static const struct wl_registry_listener registry_listener = {
|
|
|
|
registry_handle_add,
|
|
|
|
registry_handle_remove,
|
|
|
|
};
|
2017-10-11 18:47:03 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
/* Static functions */
|
|
|
|
static void check_dnd_fd(struct vo_wayland_state *wl)
|
|
|
|
{
|
|
|
|
if (wl->dnd_fd == -1)
|
|
|
|
return;
|
2019-05-13 13:47:13 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
struct pollfd fdp = { wl->dnd_fd, POLLIN | POLLERR | POLLHUP, 0 };
|
|
|
|
if (poll(&fdp, 1, 0) <= 0)
|
|
|
|
return;
|
2014-08-18 20:00:39 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
if (fdp.revents & POLLIN) {
|
|
|
|
ptrdiff_t offset = 0;
|
|
|
|
size_t data_read = 0;
|
|
|
|
const size_t chunk_size = 1;
|
|
|
|
uint8_t *buffer = ta_zalloc_size(wl, chunk_size);
|
|
|
|
if (!buffer)
|
|
|
|
goto end;
|
2014-08-06 10:41:33 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
while ((data_read = read(wl->dnd_fd, buffer + offset, chunk_size)) > 0) {
|
|
|
|
offset += data_read;
|
|
|
|
buffer = ta_realloc_size(wl, buffer, offset + chunk_size);
|
|
|
|
memset(buffer + offset, 0, chunk_size);
|
|
|
|
if (!buffer)
|
|
|
|
goto end;
|
|
|
|
}
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
MP_VERBOSE(wl, "Read %td bytes from the DND fd\n", offset);
|
2019-05-13 13:47:13 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
struct bstr file_list = bstr0(buffer);
|
|
|
|
mp_event_drop_mime_data(wl->vo->input_ctx, wl->dnd_mime_type,
|
|
|
|
file_list, wl->dnd_action);
|
|
|
|
talloc_free(buffer);
|
|
|
|
end:
|
|
|
|
talloc_free(wl->dnd_mime_type);
|
|
|
|
wl->dnd_mime_type = NULL;
|
|
|
|
wl->dnd_mime_score = 0;
|
|
|
|
}
|
2019-05-13 13:47:13 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
if (fdp.revents & (POLLIN | POLLERR | POLLHUP)) {
|
|
|
|
close(wl->dnd_fd);
|
|
|
|
wl->dnd_fd = -1;
|
|
|
|
}
|
|
|
|
}
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
static int check_for_resize(struct vo_wayland_state *wl, wl_fixed_t x_w, wl_fixed_t y_w,
|
|
|
|
int edge_pixels, enum xdg_toplevel_resize_edge *edge)
|
|
|
|
{
|
|
|
|
if (wl->touch_entries || wl->vo_opts->fullscreen || wl->vo_opts->window_maximized)
|
|
|
|
return 0;
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
int pos[2] = { wl_fixed_to_double(x_w), wl_fixed_to_double(y_w) };
|
|
|
|
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);
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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;
|
|
|
|
}
|
2014-01-08 19:58:32 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
return 1;
|
|
|
|
}
|
2019-05-13 13:47:13 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
static bool create_input(struct vo_wayland_state *wl)
|
|
|
|
{
|
|
|
|
wl->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
|
2015-03-22 00:47:27 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
if (!wl->xkb_context) {
|
|
|
|
MP_ERR(wl, "failed to initialize input: check xkbcommon\n");
|
|
|
|
return 1;
|
|
|
|
}
|
2019-08-07 14:26:24 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2018-11-07 12:53:46 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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);
|
2018-11-07 12:53:46 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
wl->xdg_toplevel = xdg_surface_get_toplevel(wl->xdg_surface);
|
|
|
|
xdg_toplevel_add_listener(wl->xdg_toplevel, &xdg_toplevel_listener, wl);
|
2019-05-13 13:47:13 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
if (!wl->xdg_surface || !wl->xdg_toplevel)
|
|
|
|
return 1;
|
|
|
|
return 0;
|
|
|
|
}
|
2019-05-13 13:47:13 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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);
|
|
|
|
}
|
2019-05-13 13:47:13 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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)
|
|
|
|
MP_TARRAY_APPEND(NULL, names, displays_spanned,
|
|
|
|
talloc_strdup(NULL, output->model));
|
|
|
|
}
|
|
|
|
MP_TARRAY_APPEND(NULL, names, displays_spanned, NULL);
|
|
|
|
return names;
|
|
|
|
}
|
2014-08-06 10:41:33 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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,
|
|
|
|
};
|
2014-08-13 20:32:22 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
static const int mods[] = {
|
|
|
|
MP_KEY_MODIFIER_SHIFT,
|
|
|
|
MP_KEY_MODIFIER_CTRL,
|
|
|
|
MP_KEY_MODIFIER_ALT,
|
|
|
|
MP_KEY_MODIFIER_META,
|
|
|
|
};
|
2019-10-10 19:14:40 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
int modifiers = 0;
|
2020-08-14 23:22:58 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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 (!xkb_state_mod_index_is_consumed(wl->xkb_state, wl->keyboard_code, index)
|
|
|
|
&& xkb_state_mod_index_is_active(wl->xkb_state, index,
|
|
|
|
XKB_STATE_MODS_DEPRESSED))
|
|
|
|
modifiers |= mods[n];
|
|
|
|
}
|
|
|
|
return modifiers;
|
|
|
|
}
|
2019-05-13 13:47:13 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
static void greatest_common_divisor(struct vo_wayland_state *wl, int a, int b) {
|
|
|
|
// euclidean algorithm
|
|
|
|
int larger;
|
|
|
|
int smaller;
|
|
|
|
if (a > b) {
|
|
|
|
larger = a;
|
|
|
|
smaller = b;
|
|
|
|
} else {
|
|
|
|
larger = b;
|
|
|
|
smaller = a;
|
|
|
|
}
|
|
|
|
int remainder = larger - smaller * floor(larger/smaller);
|
|
|
|
if (remainder == 0) {
|
|
|
|
wl->gcd = smaller;
|
|
|
|
} else {
|
|
|
|
greatest_common_divisor(wl, smaller, remainder);
|
|
|
|
}
|
|
|
|
}
|
2019-05-13 13:47:13 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
static struct vo_wayland_output *find_output(struct vo_wayland_state *wl)
|
|
|
|
{
|
|
|
|
int index = 0;
|
|
|
|
int screen_id = wl->vo_opts->fsscreen_id;
|
|
|
|
char *screen_name = wl->vo_opts->fsscreen_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->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 (wl->vo_opts->fsscreen_id >= 0) {
|
|
|
|
MP_WARN(wl, "Screen index %i not found/unavailable! Falling back to screen 0!\n", screen_id);
|
|
|
|
} else if (wl->vo_opts->fsscreen_name) {
|
|
|
|
MP_WARN(wl, "Screen name %s not found/unavailable! Falling back to screen 0!\n", screen_name);
|
|
|
|
}
|
|
|
|
return fallback_output;
|
|
|
|
}
|
2019-05-13 13:47:13 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
static int last_available_sync(struct vo_wayland_state *wl)
|
|
|
|
{
|
|
|
|
for (int i = wl->sync_size - 1; i > -1; --i) {
|
|
|
|
if (!wl->sync[i].filled)
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
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 queue_new_sync(struct vo_wayland_state *wl)
|
|
|
|
{
|
|
|
|
wl->sync_size += 1;
|
|
|
|
wl->sync = talloc_realloc(wl, wl->sync, struct vo_wayland_sync, wl->sync_size);
|
|
|
|
sync_shift(wl);
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
talloc_free(out->make);
|
|
|
|
talloc_free(out->model);
|
|
|
|
talloc_free(out);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void set_border_decorations(struct vo_wayland_state *wl, int state)
|
|
|
|
{
|
|
|
|
if (!wl->xdg_toplevel_decoration) {
|
|
|
|
wl->vo_opts->border = false;
|
|
|
|
m_config_cache_write_opt(wl->vo_opts_cache,
|
|
|
|
&wl->vo_opts->border);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
enum zxdg_toplevel_decoration_v1_mode mode;
|
|
|
|
if (state) {
|
|
|
|
MP_VERBOSE(wl, "Enabling server decorations\n");
|
|
|
|
mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE;
|
|
|
|
} else {
|
|
|
|
MP_VERBOSE(wl, "Disabling server decorations\n");
|
|
|
|
mode = ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE;
|
|
|
|
}
|
|
|
|
zxdg_toplevel_decoration_v1_set_mode(wl->xdg_toplevel_decoration, mode);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int set_cursor_visibility(struct vo_wayland_state *wl, bool on)
|
|
|
|
{
|
|
|
|
wl->cursor_visible = on;
|
|
|
|
if (on) {
|
|
|
|
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;
|
|
|
|
wl_pointer_set_cursor(wl->pointer, wl->pointer_id, wl->cursor_surface,
|
|
|
|
img->hotspot_x/wl->scaling, img->hotspot_y/wl->scaling);
|
|
|
|
wl_surface_set_buffer_scale(wl->cursor_surface, wl->scaling);
|
|
|
|
wl_surface_attach(wl->cursor_surface, buffer, 0, 0);
|
|
|
|
wl_surface_damage(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)
|
|
|
|
{
|
|
|
|
struct vo *vo = wl->vo;
|
|
|
|
|
|
|
|
struct vo_win_geometry geo;
|
|
|
|
struct mp_rect screenrc = wl->current_output->geometry;
|
|
|
|
vo_calc_window_geometry(vo, &screenrc, &geo);
|
|
|
|
vo_apply_window_geometry(vo, &geo);
|
|
|
|
|
|
|
|
greatest_common_divisor(wl, vo->dwidth, vo->dheight);
|
|
|
|
wl->reduced_width = vo->dwidth / wl->gcd;
|
|
|
|
wl->reduced_height = vo->dheight / wl->gcd;
|
|
|
|
|
|
|
|
wl->vdparams.x0 = 0;
|
|
|
|
wl->vdparams.y0 = 0;
|
|
|
|
wl->vdparams.x1 = vo->dwidth / wl->scaling;
|
|
|
|
wl->vdparams.y1 = vo->dheight / wl->scaling;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-06-28 16:03:51 +00:00
|
|
|
static void set_surface_scaling(struct vo_wayland_state *wl)
|
|
|
|
{
|
|
|
|
int old_scale = wl->scaling;
|
|
|
|
if (wl->vo_opts->hidpi_window_scale) {
|
|
|
|
wl->scaling = wl->current_output->scale;
|
|
|
|
} else {
|
|
|
|
wl->scaling = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
double factor = (double)old_scale / wl->scaling;
|
|
|
|
wl->vdparams.x1 *= factor;
|
|
|
|
wl->vdparams.y1 *= factor;
|
|
|
|
wl->window_size.x1 *= factor;
|
|
|
|
wl->window_size.y1 *= factor;
|
|
|
|
}
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
static int spawn_cursor(struct vo_wayland_state *wl)
|
|
|
|
{
|
|
|
|
/* 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 *size_str = getenv("XCURSOR_SIZE");
|
|
|
|
int size = 32;
|
|
|
|
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;
|
|
|
|
}
|
2019-05-13 13:47:13 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
wl->cursor_theme = wl_cursor_theme_load(NULL, size*wl->scaling, wl->shm);
|
|
|
|
if (!wl->cursor_theme) {
|
|
|
|
MP_ERR(wl, "Unable to load cursor theme!\n");
|
|
|
|
return 1;
|
2017-10-01 20:16:49 +00:00
|
|
|
}
|
2013-08-19 12:07:17 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
wl->default_cursor = wl_cursor_theme_get_cursor(wl->cursor_theme, "left_ptr");
|
|
|
|
if (!wl->default_cursor) {
|
|
|
|
MP_ERR(wl, "Unable to load cursor theme!\n");
|
|
|
|
return 1;
|
|
|
|
}
|
2017-10-09 01:08:36 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
wl->allocated_cursor_scale = wl->scaling;
|
2017-10-09 01:08:36 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
return 0;
|
2013-07-18 15:35:28 +00:00
|
|
|
}
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
static void sync_shift(struct vo_wayland_state *wl)
|
2013-07-18 15:35:28 +00:00
|
|
|
{
|
2021-06-26 21:12:03 +00:00
|
|
|
for (int i = wl->sync_size - 1; i > 0; --i) {
|
|
|
|
wl->sync[i] = wl->sync[i-1];
|
2020-03-06 18:15:21 +00:00
|
|
|
}
|
2021-06-26 21:12:03 +00:00
|
|
|
struct vo_wayland_sync sync = {0, 0, 0, 0};
|
|
|
|
wl->sync[0] = sync;
|
2013-02-28 18:55:02 +00:00
|
|
|
}
|
|
|
|
|
2020-08-18 17:55:01 +00:00
|
|
|
static void toggle_fullscreen(struct vo_wayland_state *wl)
|
|
|
|
{
|
|
|
|
if (!wl->xdg_toplevel)
|
|
|
|
return;
|
|
|
|
wl->state_change = true;
|
2020-11-23 20:56:24 +00:00
|
|
|
bool specific_screen = wl->vo_opts->fsscreen_id >= 0 || wl->vo_opts->fsscreen_name;
|
|
|
|
if (wl->vo_opts->fullscreen && !specific_screen) {
|
2020-08-18 17:55:01 +00:00
|
|
|
xdg_toplevel_set_fullscreen(wl->xdg_toplevel, NULL);
|
2020-11-23 20:56:24 +00:00
|
|
|
} else if (wl->vo_opts->fullscreen && specific_screen) {
|
wayland: handle multiple outputs more correctly
There's a bit of a catch-22 in the wayland backend. mpv needs to know
several things about the wl_output the surface is on (geometry, scale,
etc.) for lots of its options. You still have to render something
somewhere before you can know what wl_output the surface is actually on.
So this means that when initializing the player, it is entirely possible
to calculate initial parameters using the wrong wl_output. The surface
listener is what will eventually correct this and pick the correct
output. However not everything was technically working correctly in a
multi-output setup.
The first rule here is to rework find_output so that it returns a
vo_wayland_output instead of internally setting wl->current_output. The
reason is simply because the output found here is not guaranteed to be
the output the surface is actually on. Note that for initialization of
the player, we must set the output returned from this function as the
wl->current_output even if it is not technically correct. The surface
listener will fix it later.
vo_wayland_reconfig has to confusingly serve two roles. It must ensure
some wayland-related things are configured as well as setup things for
mpv's vo. The various functions are shuffled around and some things are
removed here which has subtle implications. For instance, there's no
reason to always set the buffer scale. It only needs to be done once
(when the wl->current_output is being created). A roundtrip needs to be
done once after a wl_surface_commit to ensure there are no configuration
errors.
surface_handle_enter is now handles two different things: scaling as
well as mpv's autofit/geometry options. When a surface enters a new
output, the new scaling value is applied to all of the geometry-related
structs (previously, this wasn't done). This ensures, in a multi-monitor
case with mixed scale values, the surface is rescaled correctly to the
actual output it is on if the initial selection of wl->current_output is
incorrect.
Additionally, autofit/geometry values are recalculated if they exist.
This means that dragging a surface across different outputs will autofit
correctly to the new output and not always be "stuck" on the old one.
A very astute observer may notice that set_buffer_scale isn't set when
the surface enters a new output. The API doesn't really indicate this,
but a WAYLAND_DEBUG log reveals that the compositor (well at least
sway/wlroots anyway) magically sets this for you. That's quite fortunate
because setting in the surface handler caused all sorts of problems.
2020-12-06 23:34:36 +00:00
|
|
|
struct vo_wayland_output *output = find_output(wl);
|
|
|
|
xdg_toplevel_set_fullscreen(wl->xdg_toplevel, output->output);
|
2020-08-18 17:55:01 +00:00
|
|
|
} 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
static void update_app_id(struct vo_wayland_state *wl)
|
2020-08-18 17:55:01 +00:00
|
|
|
{
|
|
|
|
if (!wl->xdg_toplevel)
|
|
|
|
return;
|
2021-06-26 21:12:03 +00:00
|
|
|
xdg_toplevel_set_app_id(wl->xdg_toplevel, wl->vo_opts->appid);
|
2017-10-01 20:16:49 +00:00
|
|
|
}
|
2017-09-16 04:24:57 +00:00
|
|
|
|
2020-08-12 14:41:56 +00:00
|
|
|
static int update_window_title(struct vo_wayland_state *wl, const char *title)
|
2013-02-28 18:55:02 +00:00
|
|
|
{
|
2017-10-09 01:08:36 +00:00
|
|
|
if (!wl->xdg_toplevel)
|
|
|
|
return VO_NOTAVAIL;
|
2018-04-23 14:10:40 +00:00
|
|
|
xdg_toplevel_set_title(wl->xdg_toplevel, title);
|
2017-10-09 01:08:36 +00:00
|
|
|
return VO_TRUE;
|
2013-02-28 18:55:02 +00:00
|
|
|
}
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
static void window_move(struct vo_wayland_state *wl, uint32_t serial)
|
2013-02-28 18:55:02 +00:00
|
|
|
{
|
2021-06-26 21:12:03 +00:00
|
|
|
if (wl->xdg_toplevel)
|
|
|
|
xdg_toplevel_move(wl->xdg_toplevel, wl->seat, serial);
|
|
|
|
}
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
static void vo_wayland_dispatch_events(struct vo_wayland_state *wl, int nfds, int timeout)
|
|
|
|
{
|
|
|
|
struct pollfd fds[2] = {
|
|
|
|
{.fd = wl->display_fd, .events = POLLIN },
|
|
|
|
{.fd = wl->wakeup_pipe[0], .events = POLLIN },
|
|
|
|
};
|
2017-10-01 20:16:49 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
while (wl_display_prepare_read(wl->display) != 0)
|
|
|
|
wl_display_dispatch_pending(wl->display);
|
|
|
|
wl_display_flush(wl->display);
|
2017-10-01 20:16:49 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
poll(fds, nfds, timeout);
|
2017-10-01 20:16:49 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
|
|
|
|
MP_FATAL(wl, "Error occurred on the display fd, closing\n");
|
|
|
|
wl_display_cancel_read(wl->display);
|
|
|
|
close(wl->display_fd);
|
|
|
|
wl->display_fd = -1;
|
|
|
|
mp_input_put_key(wl->vo->input_ctx, MP_KEY_CLOSE_WIN);
|
|
|
|
} else {
|
|
|
|
wl_display_read_events(wl->display);
|
2013-02-28 18:55:02 +00:00
|
|
|
}
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
if (fds[0].revents & POLLIN)
|
|
|
|
wl_display_dispatch_pending(wl->display);
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
if (fds[1].revents & POLLIN)
|
|
|
|
mp_flush_wakeup_pipe(wl->wakeup_pipe[0]);
|
2013-02-28 18:55:02 +00:00
|
|
|
}
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
/* Non-static */
|
2016-07-21 12:28:58 +00:00
|
|
|
int vo_wayland_control(struct vo *vo, int *events, int request, void *arg)
|
2013-05-15 16:17:18 +00:00
|
|
|
{
|
2017-10-01 20:16:49 +00:00
|
|
|
struct vo_wayland_state *wl = vo->wl;
|
2019-11-30 02:00:49 +00:00
|
|
|
struct mp_vo_opts *opts = wl->vo_opts;
|
2017-10-01 20:16:49 +00:00
|
|
|
wl_display_dispatch_pending(wl->display);
|
2013-05-19 11:04:59 +00:00
|
|
|
|
2013-05-15 16:17:18 +00:00
|
|
|
switch (request) {
|
2017-10-09 01:08:36 +00:00
|
|
|
case VOCTRL_CHECK_EVENTS: {
|
|
|
|
check_dnd_fd(wl);
|
|
|
|
*events |= wl->pending_vo_events;
|
|
|
|
wl->pending_vo_events = 0;
|
|
|
|
return VO_TRUE;
|
|
|
|
}
|
2019-11-30 02:00:49 +00:00
|
|
|
case VOCTRL_VO_OPTS_CHANGED: {
|
|
|
|
void *opt;
|
|
|
|
while (m_config_cache_get_next_changed(wl->vo_opts_cache, &opt)) {
|
2021-06-28 16:03:51 +00:00
|
|
|
if (opt == &opts->appid)
|
|
|
|
update_app_id(wl);
|
|
|
|
if (opt == &opts->border)
|
|
|
|
set_border_decorations(wl, opts->border);
|
2019-11-30 02:00:49 +00:00
|
|
|
if (opt == &opts->fullscreen)
|
2020-01-04 21:51:09 +00:00
|
|
|
toggle_fullscreen(wl);
|
2021-06-28 16:03:51 +00:00
|
|
|
if (opt == &opts->hidpi_window_scale)
|
|
|
|
{
|
|
|
|
set_surface_scaling(wl);
|
|
|
|
if (!wl->vo_opts->fullscreen && !wl->vo_opts->window_maximized) {
|
|
|
|
wl_surface_set_buffer_scale(wl->surface, wl->scaling);
|
|
|
|
} else {
|
|
|
|
wl->scale_change = true;
|
|
|
|
}
|
|
|
|
}
|
2019-11-30 02:00:49 +00:00
|
|
|
if (opt == &opts->window_maximized)
|
2020-01-04 21:51:09 +00:00
|
|
|
toggle_maximized(wl);
|
2021-06-28 16:03:51 +00:00
|
|
|
if (opt == &opts->window_minimized)
|
|
|
|
do_minimize(wl);
|
2020-12-13 21:03:34 +00:00
|
|
|
if (opt == &opts->geometry || opt == &opts->autofit ||
|
|
|
|
opt == &opts->autofit_smaller || opt == &opts->autofit_larger)
|
|
|
|
{
|
|
|
|
set_geometry(wl);
|
|
|
|
wl->window_size = wl->vdparams;
|
|
|
|
if (!wl->vo_opts->fullscreen && !wl->vo_opts->window_maximized)
|
|
|
|
wl->geometry = wl->window_size;
|
|
|
|
wl->pending_vo_events |= VO_EVENT_RESIZE;
|
|
|
|
}
|
2019-11-30 02:00:49 +00:00
|
|
|
}
|
|
|
|
return VO_TRUE;
|
|
|
|
}
|
2020-09-07 16:22:25 +00:00
|
|
|
case VOCTRL_GET_FOCUSED: {
|
|
|
|
*(bool *)arg = wl->focused;
|
|
|
|
return VO_TRUE;
|
|
|
|
}
|
2017-10-09 01:08:36 +00:00
|
|
|
case VOCTRL_GET_DISPLAY_NAMES: {
|
|
|
|
*(char ***)arg = get_displays_spanned(wl);
|
|
|
|
return VO_TRUE;
|
|
|
|
}
|
|
|
|
case VOCTRL_GET_UNFS_WINDOW_SIZE: {
|
|
|
|
int *s = arg;
|
2020-08-18 17:55:01 +00:00
|
|
|
s[0] = mp_rect_w(wl->geometry) * wl->scaling;
|
|
|
|
s[1] = mp_rect_h(wl->geometry) * wl->scaling;
|
2017-10-09 01:08:36 +00:00
|
|
|
return VO_TRUE;
|
|
|
|
}
|
|
|
|
case VOCTRL_SET_UNFS_WINDOW_SIZE: {
|
|
|
|
int *s = arg;
|
2020-08-18 17:55:01 +00:00
|
|
|
wl->window_size.x0 = 0;
|
|
|
|
wl->window_size.y0 = 0;
|
|
|
|
wl->window_size.x1 = s[0] / wl->scaling;
|
|
|
|
wl->window_size.y1 = s[1] / wl->scaling;
|
2019-12-02 00:55:52 +00:00
|
|
|
if (!wl->vo_opts->fullscreen && !wl->vo_opts->window_maximized) {
|
2020-08-18 17:55:01 +00:00
|
|
|
wl->geometry = wl->window_size;
|
2017-10-09 01:08:36 +00:00
|
|
|
wl->pending_vo_events |= VO_EVENT_RESIZE;
|
2017-10-01 20:16:49 +00:00
|
|
|
}
|
2017-10-09 01:08:36 +00:00
|
|
|
return VO_TRUE;
|
|
|
|
}
|
|
|
|
case VOCTRL_GET_DISPLAY_FPS: {
|
|
|
|
if (!wl->current_output)
|
|
|
|
return VO_NOTAVAIL;
|
|
|
|
*(double *)arg = wl->current_output->refresh_rate;
|
|
|
|
return VO_TRUE;
|
|
|
|
}
|
2021-04-12 17:51:41 +00:00
|
|
|
case VOCTRL_GET_DISPLAY_RES: {
|
|
|
|
if (!wl->current_output)
|
|
|
|
return VO_NOTAVAIL;
|
|
|
|
((int *)arg)[0] = wl->current_output->geometry.x1;
|
|
|
|
((int *)arg)[1] = wl->current_output->geometry.y1;
|
|
|
|
return VO_TRUE;
|
|
|
|
}
|
2021-06-26 21:12:03 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
int 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"),
|
|
|
|
.scaling = 1,
|
|
|
|
.wakeup_pipe = {-1, -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;
|
|
|
|
|
|
|
|
wl_list_init(&wl->output_list);
|
|
|
|
|
|
|
|
if (!wl->display)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (create_input(wl))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
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->wm_base) {
|
|
|
|
MP_FATAL(wl, "Compositor doesn't support the required %s protocol!\n",
|
|
|
|
xdg_wm_base_interface.name);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Can't be initialized during registry due to multi-protocol dependence */
|
|
|
|
if (create_xdg_surface(wl))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
const char *xdg_current_desktop = getenv("XDG_CURRENT_DESKTOP");
|
|
|
|
if (xdg_current_desktop != NULL && strstr(xdg_current_desktop, "GNOME"))
|
|
|
|
MP_WARN(wl, "GNOME's wayland compositor lacks support for the idle inhibit protocol. This means the screen can blank during playback.\n");
|
|
|
|
|
|
|
|
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);
|
2021-04-12 18:53:10 +00:00
|
|
|
}
|
2021-06-26 21:12:03 +00:00
|
|
|
|
|
|
|
if (wl->presentation) {
|
|
|
|
wl->sync = talloc_zero_array(wl, struct vo_wayland_sync, 1);
|
|
|
|
struct vo_wayland_sync sync = {0, 0, 0, 0};
|
|
|
|
wl->sync[0] = sync;
|
|
|
|
wl->sync_size += 1;
|
|
|
|
} else {
|
|
|
|
MP_VERBOSE(wl, "Compositor doesn't support the %s protocol!\n",
|
|
|
|
wp_presentation_interface.name);
|
2013-05-15 16:17:18 +00:00
|
|
|
}
|
2013-05-16 14:43:34 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
if (wl->xdg_decoration_manager) {
|
|
|
|
wl->xdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(wl->xdg_decoration_manager, wl->xdg_toplevel);
|
|
|
|
set_border_decorations(wl, wl->vo_opts->border);
|
|
|
|
} 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);
|
|
|
|
wl->frame_callback = wl_surface_frame(wl->surface);
|
|
|
|
wl_callback_add_listener(wl->frame_callback, &frame_listener, wl);
|
|
|
|
|
|
|
|
update_app_id(wl);
|
|
|
|
mp_make_wakeup_pipe(wl->wakeup_pipe);
|
|
|
|
|
|
|
|
return true;
|
2015-08-19 19:41:26 +00:00
|
|
|
}
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
int vo_wayland_reconfig(struct vo *vo)
|
wayland: refactor dispatching events
This was originally just a bugfix for a race condition, but the scope
expanded a bit. Currently, the wayland code does a prepare_read ->
dispatch_pending -> display_flush -> read_events -> dispatch_pending
routine that's basically straight from the wayland client API
documentation. This essentially just queues up all the wayland events
mpv has and dispatches them to the compositor. We do this for blocking
purposes on every frame we render. A very similar thing is done for
wait_events from the VO.
This code can pretty easily be unified and split off into a separate
function, vo_wayland_dispatch_events. vo_wayland_wait_frame will call
this function in a while loop (which will break either on timeout or if
we receive frame callback from the compositor). wait_events needs to
just check this in case we get some state change on the wakeup_pipe
(i.e. waking up from the paused state).
As for the actual bugfix part of this, it's a slight regression from
c26d833. The toplevel config event always forced a redraw when a surface
became activated again. This is for something like displaying cover art
on a music file. If the window was originally out of view and then later
brought back into focus, no picture would be rendered (i.e. the window
is just black). That's because something like cover art is just 1 frame
and the VO stops doing any other additional rendering. If you miss that
1 frame, nothing would show up ever again. The fix in this case is to
always just force a redraw when the mpv window comes back into view.
Well with the aforementioned commit, we stopped doing
wl_display_roundtrip calls on every frame. That means we no longer do
roundtrip blocking calls. We just be sure to queue up all of the events
we have and then dispatch them. Because wayland is fundamentally an
asynchronous protocol, there's no guarantee what order these events
would be processed in. This meant that on occasion, a
vo_wayland_wait_frame call (this could occur multiple times depending on
the exact situation) would occur before the compositor would send back
frame callback. That would result in the aforementioned bug of having
just a black window. The fix, in this case, is to just do a
vo_wayland_wait_frame call directly before we force the VO to do a
redraw. Note that merely dispatching events isn't enough because we
specifically need to wait for the compositor to give us frame callback
before doing a new render.
P.S. fix a typo too.
2021-05-27 20:22:21 +00:00
|
|
|
{
|
2021-06-26 21:12:03 +00:00
|
|
|
struct vo_wayland_state *wl = vo->wl;
|
|
|
|
bool configure = false;
|
wayland: refactor dispatching events
This was originally just a bugfix for a race condition, but the scope
expanded a bit. Currently, the wayland code does a prepare_read ->
dispatch_pending -> display_flush -> read_events -> dispatch_pending
routine that's basically straight from the wayland client API
documentation. This essentially just queues up all the wayland events
mpv has and dispatches them to the compositor. We do this for blocking
purposes on every frame we render. A very similar thing is done for
wait_events from the VO.
This code can pretty easily be unified and split off into a separate
function, vo_wayland_dispatch_events. vo_wayland_wait_frame will call
this function in a while loop (which will break either on timeout or if
we receive frame callback from the compositor). wait_events needs to
just check this in case we get some state change on the wakeup_pipe
(i.e. waking up from the paused state).
As for the actual bugfix part of this, it's a slight regression from
c26d833. The toplevel config event always forced a redraw when a surface
became activated again. This is for something like displaying cover art
on a music file. If the window was originally out of view and then later
brought back into focus, no picture would be rendered (i.e. the window
is just black). That's because something like cover art is just 1 frame
and the VO stops doing any other additional rendering. If you miss that
1 frame, nothing would show up ever again. The fix in this case is to
always just force a redraw when the mpv window comes back into view.
Well with the aforementioned commit, we stopped doing
wl_display_roundtrip calls on every frame. That means we no longer do
roundtrip blocking calls. We just be sure to queue up all of the events
we have and then dispatch them. Because wayland is fundamentally an
asynchronous protocol, there's no guarantee what order these events
would be processed in. This meant that on occasion, a
vo_wayland_wait_frame call (this could occur multiple times depending on
the exact situation) would occur before the compositor would send back
frame callback. That would result in the aforementioned bug of having
just a black window. The fix, in this case, is to just do a
vo_wayland_wait_frame call directly before we force the VO to do a
redraw. Note that merely dispatching events isn't enough because we
specifically need to wait for the compositor to give us frame callback
before doing a new render.
P.S. fix a typo too.
2021-05-27 20:22:21 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
MP_VERBOSE(wl, "Reconfiguring!\n");
|
wayland: refactor dispatching events
This was originally just a bugfix for a race condition, but the scope
expanded a bit. Currently, the wayland code does a prepare_read ->
dispatch_pending -> display_flush -> read_events -> dispatch_pending
routine that's basically straight from the wayland client API
documentation. This essentially just queues up all the wayland events
mpv has and dispatches them to the compositor. We do this for blocking
purposes on every frame we render. A very similar thing is done for
wait_events from the VO.
This code can pretty easily be unified and split off into a separate
function, vo_wayland_dispatch_events. vo_wayland_wait_frame will call
this function in a while loop (which will break either on timeout or if
we receive frame callback from the compositor). wait_events needs to
just check this in case we get some state change on the wakeup_pipe
(i.e. waking up from the paused state).
As for the actual bugfix part of this, it's a slight regression from
c26d833. The toplevel config event always forced a redraw when a surface
became activated again. This is for something like displaying cover art
on a music file. If the window was originally out of view and then later
brought back into focus, no picture would be rendered (i.e. the window
is just black). That's because something like cover art is just 1 frame
and the VO stops doing any other additional rendering. If you miss that
1 frame, nothing would show up ever again. The fix in this case is to
always just force a redraw when the mpv window comes back into view.
Well with the aforementioned commit, we stopped doing
wl_display_roundtrip calls on every frame. That means we no longer do
roundtrip blocking calls. We just be sure to queue up all of the events
we have and then dispatch them. Because wayland is fundamentally an
asynchronous protocol, there's no guarantee what order these events
would be processed in. This meant that on occasion, a
vo_wayland_wait_frame call (this could occur multiple times depending on
the exact situation) would occur before the compositor would send back
frame callback. That would result in the aforementioned bug of having
just a black window. The fix, in this case, is to just do a
vo_wayland_wait_frame call directly before we force the VO to do a
redraw. Note that merely dispatching events isn't enough because we
specifically need to wait for the compositor to give us frame callback
before doing a new render.
P.S. fix a typo too.
2021-05-27 20:22:21 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
if (!wl->current_output) {
|
|
|
|
wl->current_output = find_output(wl);
|
|
|
|
if (!wl->current_output)
|
|
|
|
return false;
|
2021-06-28 16:03:51 +00:00
|
|
|
set_surface_scaling(wl);
|
2021-06-26 21:12:03 +00:00
|
|
|
wl_surface_set_buffer_scale(wl->surface, wl->scaling);
|
|
|
|
wl_surface_commit(wl->surface);
|
|
|
|
configure = true;
|
|
|
|
}
|
wayland: refactor dispatching events
This was originally just a bugfix for a race condition, but the scope
expanded a bit. Currently, the wayland code does a prepare_read ->
dispatch_pending -> display_flush -> read_events -> dispatch_pending
routine that's basically straight from the wayland client API
documentation. This essentially just queues up all the wayland events
mpv has and dispatches them to the compositor. We do this for blocking
purposes on every frame we render. A very similar thing is done for
wait_events from the VO.
This code can pretty easily be unified and split off into a separate
function, vo_wayland_dispatch_events. vo_wayland_wait_frame will call
this function in a while loop (which will break either on timeout or if
we receive frame callback from the compositor). wait_events needs to
just check this in case we get some state change on the wakeup_pipe
(i.e. waking up from the paused state).
As for the actual bugfix part of this, it's a slight regression from
c26d833. The toplevel config event always forced a redraw when a surface
became activated again. This is for something like displaying cover art
on a music file. If the window was originally out of view and then later
brought back into focus, no picture would be rendered (i.e. the window
is just black). That's because something like cover art is just 1 frame
and the VO stops doing any other additional rendering. If you miss that
1 frame, nothing would show up ever again. The fix in this case is to
always just force a redraw when the mpv window comes back into view.
Well with the aforementioned commit, we stopped doing
wl_display_roundtrip calls on every frame. That means we no longer do
roundtrip blocking calls. We just be sure to queue up all of the events
we have and then dispatch them. Because wayland is fundamentally an
asynchronous protocol, there's no guarantee what order these events
would be processed in. This meant that on occasion, a
vo_wayland_wait_frame call (this could occur multiple times depending on
the exact situation) would occur before the compositor would send back
frame callback. That would result in the aforementioned bug of having
just a black window. The fix, in this case, is to just do a
vo_wayland_wait_frame call directly before we force the VO to do a
redraw. Note that merely dispatching events isn't enough because we
specifically need to wait for the compositor to give us frame callback
before doing a new render.
P.S. fix a typo too.
2021-05-27 20:22:21 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
set_geometry(wl);
|
|
|
|
|
|
|
|
if (wl->vo_opts->keepaspect && wl->vo_opts->keepaspect_window)
|
|
|
|
wl->window_size = wl->vdparams;
|
|
|
|
|
|
|
|
if (!wl->vo_opts->fullscreen && !wl->vo_opts->window_maximized)
|
|
|
|
wl->geometry = wl->window_size;
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
if (configure) {
|
|
|
|
wl->window_size = wl->vdparams;
|
|
|
|
wl->geometry = wl->window_size;
|
|
|
|
wl_display_roundtrip(wl->display);
|
|
|
|
wl->pending_vo_events |= VO_EVENT_DPI;
|
wayland: refactor dispatching events
This was originally just a bugfix for a race condition, but the scope
expanded a bit. Currently, the wayland code does a prepare_read ->
dispatch_pending -> display_flush -> read_events -> dispatch_pending
routine that's basically straight from the wayland client API
documentation. This essentially just queues up all the wayland events
mpv has and dispatches them to the compositor. We do this for blocking
purposes on every frame we render. A very similar thing is done for
wait_events from the VO.
This code can pretty easily be unified and split off into a separate
function, vo_wayland_dispatch_events. vo_wayland_wait_frame will call
this function in a while loop (which will break either on timeout or if
we receive frame callback from the compositor). wait_events needs to
just check this in case we get some state change on the wakeup_pipe
(i.e. waking up from the paused state).
As for the actual bugfix part of this, it's a slight regression from
c26d833. The toplevel config event always forced a redraw when a surface
became activated again. This is for something like displaying cover art
on a music file. If the window was originally out of view and then later
brought back into focus, no picture would be rendered (i.e. the window
is just black). That's because something like cover art is just 1 frame
and the VO stops doing any other additional rendering. If you miss that
1 frame, nothing would show up ever again. The fix in this case is to
always just force a redraw when the mpv window comes back into view.
Well with the aforementioned commit, we stopped doing
wl_display_roundtrip calls on every frame. That means we no longer do
roundtrip blocking calls. We just be sure to queue up all of the events
we have and then dispatch them. Because wayland is fundamentally an
asynchronous protocol, there's no guarantee what order these events
would be processed in. This meant that on occasion, a
vo_wayland_wait_frame call (this could occur multiple times depending on
the exact situation) would occur before the compositor would send back
frame callback. That would result in the aforementioned bug of having
just a black window. The fix, in this case, is to just do a
vo_wayland_wait_frame call directly before we force the VO to do a
redraw. Note that merely dispatching events isn't enough because we
specifically need to wait for the compositor to give us frame callback
before doing a new render.
P.S. fix a typo too.
2021-05-27 20:22:21 +00:00
|
|
|
}
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
wl->pending_vo_events |= VO_EVENT_RESIZE;
|
wayland: refactor dispatching events
This was originally just a bugfix for a race condition, but the scope
expanded a bit. Currently, the wayland code does a prepare_read ->
dispatch_pending -> display_flush -> read_events -> dispatch_pending
routine that's basically straight from the wayland client API
documentation. This essentially just queues up all the wayland events
mpv has and dispatches them to the compositor. We do this for blocking
purposes on every frame we render. A very similar thing is done for
wait_events from the VO.
This code can pretty easily be unified and split off into a separate
function, vo_wayland_dispatch_events. vo_wayland_wait_frame will call
this function in a while loop (which will break either on timeout or if
we receive frame callback from the compositor). wait_events needs to
just check this in case we get some state change on the wakeup_pipe
(i.e. waking up from the paused state).
As for the actual bugfix part of this, it's a slight regression from
c26d833. The toplevel config event always forced a redraw when a surface
became activated again. This is for something like displaying cover art
on a music file. If the window was originally out of view and then later
brought back into focus, no picture would be rendered (i.e. the window
is just black). That's because something like cover art is just 1 frame
and the VO stops doing any other additional rendering. If you miss that
1 frame, nothing would show up ever again. The fix in this case is to
always just force a redraw when the mpv window comes back into view.
Well with the aforementioned commit, we stopped doing
wl_display_roundtrip calls on every frame. That means we no longer do
roundtrip blocking calls. We just be sure to queue up all of the events
we have and then dispatch them. Because wayland is fundamentally an
asynchronous protocol, there's no guarantee what order these events
would be processed in. This meant that on occasion, a
vo_wayland_wait_frame call (this could occur multiple times depending on
the exact situation) would occur before the compositor would send back
frame callback. That would result in the aforementioned bug of having
just a black window. The fix, in this case, is to just do a
vo_wayland_wait_frame call directly before we force the VO to do a
redraw. Note that merely dispatching events isn't enough because we
specifically need to wait for the compositor to give us frame callback
before doing a new render.
P.S. fix a typo too.
2021-05-27 20:22:21 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
return true;
|
wayland: refactor dispatching events
This was originally just a bugfix for a race condition, but the scope
expanded a bit. Currently, the wayland code does a prepare_read ->
dispatch_pending -> display_flush -> read_events -> dispatch_pending
routine that's basically straight from the wayland client API
documentation. This essentially just queues up all the wayland events
mpv has and dispatches them to the compositor. We do this for blocking
purposes on every frame we render. A very similar thing is done for
wait_events from the VO.
This code can pretty easily be unified and split off into a separate
function, vo_wayland_dispatch_events. vo_wayland_wait_frame will call
this function in a while loop (which will break either on timeout or if
we receive frame callback from the compositor). wait_events needs to
just check this in case we get some state change on the wakeup_pipe
(i.e. waking up from the paused state).
As for the actual bugfix part of this, it's a slight regression from
c26d833. The toplevel config event always forced a redraw when a surface
became activated again. This is for something like displaying cover art
on a music file. If the window was originally out of view and then later
brought back into focus, no picture would be rendered (i.e. the window
is just black). That's because something like cover art is just 1 frame
and the VO stops doing any other additional rendering. If you miss that
1 frame, nothing would show up ever again. The fix in this case is to
always just force a redraw when the mpv window comes back into view.
Well with the aforementioned commit, we stopped doing
wl_display_roundtrip calls on every frame. That means we no longer do
roundtrip blocking calls. We just be sure to queue up all of the events
we have and then dispatch them. Because wayland is fundamentally an
asynchronous protocol, there's no guarantee what order these events
would be processed in. This meant that on occasion, a
vo_wayland_wait_frame call (this could occur multiple times depending on
the exact situation) would occur before the compositor would send back
frame callback. That would result in the aforementioned bug of having
just a black window. The fix, in this case, is to just do a
vo_wayland_wait_frame call directly before we force the VO to do a
redraw. Note that merely dispatching events isn't enough because we
specifically need to wait for the compositor to give us frame callback
before doing a new render.
P.S. fix a typo too.
2021-05-27 20:22:21 +00:00
|
|
|
}
|
|
|
|
|
2020-10-05 15:28:37 +00:00
|
|
|
void vo_wayland_set_opaque_region(struct vo_wayland_state *wl, int alpha)
|
|
|
|
{
|
|
|
|
const int32_t width = wl->scaling * mp_rect_w(wl->geometry);
|
|
|
|
const int32_t height = wl->scaling * 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
void vo_wayland_sync_swap(struct vo_wayland_state *wl)
|
2019-10-10 19:14:40 +00:00
|
|
|
{
|
|
|
|
int index = wl->sync_size - 1;
|
|
|
|
|
2020-08-16 21:29:00 +00:00
|
|
|
// If these are the same, presentation feedback has not been received.
|
2020-10-19 16:00:15 +00:00
|
|
|
// This can happen if a frame takes too long and misses vblank.
|
|
|
|
// Additionally, a compositor may return an ust value of 0. In either case,
|
|
|
|
// Don't attempt to use these statistics and wait until the next presentation
|
wayland: only render if we have frame callback
Back in the olden days, mpv's wayland backend was driven by the frame
callback. This had several issues and was removed in favor of the
current approach which allowed some advanced features (like
display-resample and presentation time) to actually work properly.
However as a consequence, it meant that mpv always rendered, even if the
surface was hidden. Wayland people consider this "wasteful" (and well
they aren't wrong). This commit aims to avoid wasteful rendering by
doing some additional checks in the swapchain. There's three main parts
to this.
1. Wayland EGL now uses an external swapchain (like the drm context).
Before we start a new frame, we check to see if we are waiting on a
callback from the compositor. If there is no wait, then go ahead and
proceed to render the frame, swap buffers, and then initiate
vo_wayland_wait_frame to poll (with a timeout) for the next potential
callback. If we are still waiting on callback from the compositor when
starting a new frame, then we simple skip rendering it entirely until
the surface comes back into view.
2. Wayland on vulkan has essentially the same approach although the
details are a little different. The ra_vk_ctx does not have support for
an external swapchain and although such a mechanism could theoretically
be added, it doesn't make much sense with libplacebo. Instead,
start_frame was added as a param and used to check for callback.
3. For wlshm, it's simply a matter of adding frame callback to it,
leveraging vo_wayland_wait_frame, and using the frame callback value to
whether or not to draw the image.
2020-09-18 17:29:53 +00:00
|
|
|
// event arrives.
|
2020-10-19 16:00:15 +00:00
|
|
|
if (!wl->sync[index].ust || wl->sync[index].ust == wl->last_ust) {
|
2020-08-22 03:22:10 +00:00
|
|
|
wl->last_skipped_vsyncs = -1;
|
|
|
|
wl->vsync_duration = -1;
|
|
|
|
wl->last_queue_display_time = -1;
|
|
|
|
return;
|
2019-10-10 19:14:40 +00:00
|
|
|
}
|
2020-08-22 03:22:10 +00:00
|
|
|
|
|
|
|
wl->last_skipped_vsyncs = 0;
|
2019-10-10 19:14:40 +00:00
|
|
|
|
|
|
|
int64_t ust_passed = wl->sync[index].ust ? wl->sync[index].ust - wl->last_ust: 0;
|
|
|
|
wl->last_ust = wl->sync[index].ust;
|
|
|
|
int64_t msc_passed = wl->sync[index].msc ? wl->sync[index].msc - wl->last_msc: 0;
|
|
|
|
wl->last_msc = wl->sync[index].msc;
|
|
|
|
|
|
|
|
if (msc_passed && ust_passed)
|
|
|
|
wl->vsync_duration = ust_passed / msc_passed;
|
|
|
|
|
wayland: only render if we have frame callback
Back in the olden days, mpv's wayland backend was driven by the frame
callback. This had several issues and was removed in favor of the
current approach which allowed some advanced features (like
display-resample and presentation time) to actually work properly.
However as a consequence, it meant that mpv always rendered, even if the
surface was hidden. Wayland people consider this "wasteful" (and well
they aren't wrong). This commit aims to avoid wasteful rendering by
doing some additional checks in the swapchain. There's three main parts
to this.
1. Wayland EGL now uses an external swapchain (like the drm context).
Before we start a new frame, we check to see if we are waiting on a
callback from the compositor. If there is no wait, then go ahead and
proceed to render the frame, swap buffers, and then initiate
vo_wayland_wait_frame to poll (with a timeout) for the next potential
callback. If we are still waiting on callback from the compositor when
starting a new frame, then we simple skip rendering it entirely until
the surface comes back into view.
2. Wayland on vulkan has essentially the same approach although the
details are a little different. The ra_vk_ctx does not have support for
an external swapchain and although such a mechanism could theoretically
be added, it doesn't make much sense with libplacebo. Instead,
start_frame was added as a param and used to check for callback.
3. For wlshm, it's simply a matter of adding frame callback to it,
leveraging vo_wayland_wait_frame, and using the frame callback value to
whether or not to draw the image.
2020-09-18 17:29:53 +00:00
|
|
|
struct timespec ts;
|
|
|
|
if (clock_gettime(CLOCK_MONOTONIC, &ts)) {
|
|
|
|
return;
|
2019-10-10 19:14:40 +00:00
|
|
|
}
|
|
|
|
|
wayland: only render if we have frame callback
Back in the olden days, mpv's wayland backend was driven by the frame
callback. This had several issues and was removed in favor of the
current approach which allowed some advanced features (like
display-resample and presentation time) to actually work properly.
However as a consequence, it meant that mpv always rendered, even if the
surface was hidden. Wayland people consider this "wasteful" (and well
they aren't wrong). This commit aims to avoid wasteful rendering by
doing some additional checks in the swapchain. There's three main parts
to this.
1. Wayland EGL now uses an external swapchain (like the drm context).
Before we start a new frame, we check to see if we are waiting on a
callback from the compositor. If there is no wait, then go ahead and
proceed to render the frame, swap buffers, and then initiate
vo_wayland_wait_frame to poll (with a timeout) for the next potential
callback. If we are still waiting on callback from the compositor when
starting a new frame, then we simple skip rendering it entirely until
the surface comes back into view.
2. Wayland on vulkan has essentially the same approach although the
details are a little different. The ra_vk_ctx does not have support for
an external swapchain and although such a mechanism could theoretically
be added, it doesn't make much sense with libplacebo. Instead,
start_frame was added as a param and used to check for callback.
3. For wlshm, it's simply a matter of adding frame callback to it,
leveraging vo_wayland_wait_frame, and using the frame callback value to
whether or not to draw the image.
2020-09-18 17:29:53 +00:00
|
|
|
uint64_t now_monotonic = ts.tv_sec * 1000000LL + ts.tv_nsec / 1000;
|
|
|
|
uint64_t ust_mp_time = mp_time_us() - (now_monotonic - wl->sync[index].ust);
|
2019-10-10 19:14:40 +00:00
|
|
|
|
wayland: only render if we have frame callback
Back in the olden days, mpv's wayland backend was driven by the frame
callback. This had several issues and was removed in favor of the
current approach which allowed some advanced features (like
display-resample and presentation time) to actually work properly.
However as a consequence, it meant that mpv always rendered, even if the
surface was hidden. Wayland people consider this "wasteful" (and well
they aren't wrong). This commit aims to avoid wasteful rendering by
doing some additional checks in the swapchain. There's three main parts
to this.
1. Wayland EGL now uses an external swapchain (like the drm context).
Before we start a new frame, we check to see if we are waiting on a
callback from the compositor. If there is no wait, then go ahead and
proceed to render the frame, swap buffers, and then initiate
vo_wayland_wait_frame to poll (with a timeout) for the next potential
callback. If we are still waiting on callback from the compositor when
starting a new frame, then we simple skip rendering it entirely until
the surface comes back into view.
2. Wayland on vulkan has essentially the same approach although the
details are a little different. The ra_vk_ctx does not have support for
an external swapchain and although such a mechanism could theoretically
be added, it doesn't make much sense with libplacebo. Instead,
start_frame was added as a param and used to check for callback.
3. For wlshm, it's simply a matter of adding frame callback to it,
leveraging vo_wayland_wait_frame, and using the frame callback value to
whether or not to draw the image.
2020-09-18 17:29:53 +00:00
|
|
|
wl->last_queue_display_time = ust_mp_time + wl->vsync_duration;
|
2019-10-10 19:14:40 +00:00
|
|
|
}
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
void vo_wayland_uninit(struct vo *vo)
|
2015-08-19 19:41:26 +00:00
|
|
|
{
|
2017-10-01 20:16:49 +00:00
|
|
|
struct vo_wayland_state *wl = vo->wl;
|
2021-06-26 21:12:03 +00:00
|
|
|
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->current_output && wl->current_output->output)
|
|
|
|
wl_output_destroy(wl->current_output->output);
|
|
|
|
|
|
|
|
if (wl->cursor_surface)
|
|
|
|
wl_surface_destroy(wl->cursor_surface);
|
|
|
|
|
|
|
|
if (wl->cursor_theme)
|
|
|
|
wl_cursor_theme_destroy(wl->cursor_theme);
|
|
|
|
|
|
|
|
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->feedback)
|
|
|
|
wp_presentation_feedback_destroy(wl->feedback);
|
|
|
|
|
|
|
|
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->seat)
|
|
|
|
wl_seat_destroy(wl->seat);
|
|
|
|
|
|
|
|
if (wl->shm)
|
|
|
|
wl_shm_destroy(wl->shm);
|
|
|
|
|
|
|
|
if (wl->surface)
|
|
|
|
wl_surface_destroy(wl->surface);
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
if (wl->display) {
|
|
|
|
close(wl_display_get_fd(wl->display));
|
|
|
|
wl_display_disconnect(wl->display);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct vo_wayland_output *output, *tmp;
|
|
|
|
wl_list_for_each_safe(output, tmp, &wl->output_list, link)
|
|
|
|
remove_output(output);
|
|
|
|
|
|
|
|
talloc_free(wl->dnd_mime_type);
|
|
|
|
|
|
|
|
for (int n = 0; n < 2; n++)
|
|
|
|
close(wl->wakeup_pipe[n]);
|
|
|
|
talloc_free(wl);
|
|
|
|
vo->wl = NULL;
|
2016-07-21 11:25:30 +00:00
|
|
|
}
|
2015-08-19 19:41:26 +00:00
|
|
|
|
wayland: shuffle around the render loop again
Take two. f4e89dd went wrong by moving vo_wayland_wait_frame before
start_frame was called. Whether or not this matters depends on the
compositor, but some weird things can happen. Basically, it's a
scheduling issue. vo_wayland_wait_frame queues all events and sends them
to the server to process (with no blocking if presentation time is
available). If mpv changes state while rendering (and this function is
called before every frame is drawn), then that event also gets
dispatched and sent to the compositor. This, in some cases, can cause
some funny behavior because the next frame gets attached to the surface
while the old buffer is getting released. It's safer to call this
function after the swap already happens and well before mpv calls its
next draw. There's no weird scheduling of events, and the compositor log
is more normal.
The second part of this is to fix some stuttering issues. This is mostly
just conjecture, but probably what was happening was this thing called
"composition". The easiest way to see this is to play a video on the
default audio sync mode (probably easiest to see on a typical 23.976
video). Have that in a window and float it over firefox (floating
windows are bloat on a tiling wm anyway). Then in firefox, do some short
bursts of smooth scrolling (likely uses egl). Some stutter in video
rendering could be observed, particularly in panning shots.
Compositors are supposed to prevent tearing so what likely was happening
was that the compositor was simply holding the buffer a wee bit longer
to make sure it happened in sync with the smooth scrolling. Because the
mpv code waits precisely on presentation time, the loop would timeout on
occasion instead of receiving the frame callback. This would then lead
to a skipped frame when rendering and thus causing stuttering.
The fix is simple: just only count consecutive timeouts as not receiving
frame callback. If a compositor holds the mpv buffer slightly longer to
avoid tearing, then we will definitely receive frame callback on the
next round of the render loop. This logic also appears to be sound for
plasma (funfact: Plasma always returns frame callback even when the
window is hidden. Not sure what's up with that, but luckily it doesn't
matter to us.), so get rid of the goofy 1/vblank_time thing and just
keep it a simple > 1 check.
2021-05-23 19:36:19 +00:00
|
|
|
void vo_wayland_wait_frame(struct vo_wayland_state *wl)
|
2019-10-07 20:58:36 +00:00
|
|
|
{
|
2021-04-16 19:55:15 +00:00
|
|
|
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 inteval 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->presentation)
|
|
|
|
vblank_time = wl->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 = 1e6 / wl->current_output->refresh_rate;
|
|
|
|
|
|
|
|
// Ideally you should never reach this point.
|
|
|
|
if (vblank_time <= 0)
|
|
|
|
vblank_time = 1e6 / 60;
|
|
|
|
|
2020-01-30 17:19:22 +00:00
|
|
|
int64_t finish_time = mp_time_us() + vblank_time;
|
2019-10-07 20:58:36 +00:00
|
|
|
|
2019-10-10 19:14:40 +00:00
|
|
|
while (wl->frame_wait && finish_time > mp_time_us()) {
|
2020-02-07 05:54:57 +00:00
|
|
|
int poll_time = ceil((double)(finish_time - mp_time_us()) / 1000);
|
2019-10-07 20:58:36 +00:00
|
|
|
if (poll_time < 0) {
|
|
|
|
poll_time = 0;
|
|
|
|
}
|
wayland: refactor dispatching events
This was originally just a bugfix for a race condition, but the scope
expanded a bit. Currently, the wayland code does a prepare_read ->
dispatch_pending -> display_flush -> read_events -> dispatch_pending
routine that's basically straight from the wayland client API
documentation. This essentially just queues up all the wayland events
mpv has and dispatches them to the compositor. We do this for blocking
purposes on every frame we render. A very similar thing is done for
wait_events from the VO.
This code can pretty easily be unified and split off into a separate
function, vo_wayland_dispatch_events. vo_wayland_wait_frame will call
this function in a while loop (which will break either on timeout or if
we receive frame callback from the compositor). wait_events needs to
just check this in case we get some state change on the wakeup_pipe
(i.e. waking up from the paused state).
As for the actual bugfix part of this, it's a slight regression from
c26d833. The toplevel config event always forced a redraw when a surface
became activated again. This is for something like displaying cover art
on a music file. If the window was originally out of view and then later
brought back into focus, no picture would be rendered (i.e. the window
is just black). That's because something like cover art is just 1 frame
and the VO stops doing any other additional rendering. If you miss that
1 frame, nothing would show up ever again. The fix in this case is to
always just force a redraw when the mpv window comes back into view.
Well with the aforementioned commit, we stopped doing
wl_display_roundtrip calls on every frame. That means we no longer do
roundtrip blocking calls. We just be sure to queue up all of the events
we have and then dispatch them. Because wayland is fundamentally an
asynchronous protocol, there's no guarantee what order these events
would be processed in. This meant that on occasion, a
vo_wayland_wait_frame call (this could occur multiple times depending on
the exact situation) would occur before the compositor would send back
frame callback. That would result in the aforementioned bug of having
just a black window. The fix, in this case, is to just do a
vo_wayland_wait_frame call directly before we force the VO to do a
redraw. Note that merely dispatching events isn't enough because we
specifically need to wait for the compositor to give us frame callback
before doing a new render.
P.S. fix a typo too.
2021-05-27 20:22:21 +00:00
|
|
|
vo_wayland_dispatch_events(wl, 1, poll_time);
|
2019-10-07 20:58:36 +00:00
|
|
|
}
|
2020-07-30 20:21:57 +00:00
|
|
|
|
wayland: refactor dispatching events
This was originally just a bugfix for a race condition, but the scope
expanded a bit. Currently, the wayland code does a prepare_read ->
dispatch_pending -> display_flush -> read_events -> dispatch_pending
routine that's basically straight from the wayland client API
documentation. This essentially just queues up all the wayland events
mpv has and dispatches them to the compositor. We do this for blocking
purposes on every frame we render. A very similar thing is done for
wait_events from the VO.
This code can pretty easily be unified and split off into a separate
function, vo_wayland_dispatch_events. vo_wayland_wait_frame will call
this function in a while loop (which will break either on timeout or if
we receive frame callback from the compositor). wait_events needs to
just check this in case we get some state change on the wakeup_pipe
(i.e. waking up from the paused state).
As for the actual bugfix part of this, it's a slight regression from
c26d833. The toplevel config event always forced a redraw when a surface
became activated again. This is for something like displaying cover art
on a music file. If the window was originally out of view and then later
brought back into focus, no picture would be rendered (i.e. the window
is just black). That's because something like cover art is just 1 frame
and the VO stops doing any other additional rendering. If you miss that
1 frame, nothing would show up ever again. The fix in this case is to
always just force a redraw when the mpv window comes back into view.
Well with the aforementioned commit, we stopped doing
wl_display_roundtrip calls on every frame. That means we no longer do
roundtrip blocking calls. We just be sure to queue up all of the events
we have and then dispatch them. Because wayland is fundamentally an
asynchronous protocol, there's no guarantee what order these events
would be processed in. This meant that on occasion, a
vo_wayland_wait_frame call (this could occur multiple times depending on
the exact situation) would occur before the compositor would send back
frame callback. That would result in the aforementioned bug of having
just a black window. The fix, in this case, is to just do a
vo_wayland_wait_frame call directly before we force the VO to do a
redraw. Note that merely dispatching events isn't enough because we
specifically need to wait for the compositor to give us frame callback
before doing a new render.
P.S. fix a typo too.
2021-05-27 20:22:21 +00:00
|
|
|
/* If the compositor does not have presentation time, we cannot be sure
|
wayland: shuffle around the render loop again
Take two. f4e89dd went wrong by moving vo_wayland_wait_frame before
start_frame was called. Whether or not this matters depends on the
compositor, but some weird things can happen. Basically, it's a
scheduling issue. vo_wayland_wait_frame queues all events and sends them
to the server to process (with no blocking if presentation time is
available). If mpv changes state while rendering (and this function is
called before every frame is drawn), then that event also gets
dispatched and sent to the compositor. This, in some cases, can cause
some funny behavior because the next frame gets attached to the surface
while the old buffer is getting released. It's safer to call this
function after the swap already happens and well before mpv calls its
next draw. There's no weird scheduling of events, and the compositor log
is more normal.
The second part of this is to fix some stuttering issues. This is mostly
just conjecture, but probably what was happening was this thing called
"composition". The easiest way to see this is to play a video on the
default audio sync mode (probably easiest to see on a typical 23.976
video). Have that in a window and float it over firefox (floating
windows are bloat on a tiling wm anyway). Then in firefox, do some short
bursts of smooth scrolling (likely uses egl). Some stutter in video
rendering could be observed, particularly in panning shots.
Compositors are supposed to prevent tearing so what likely was happening
was that the compositor was simply holding the buffer a wee bit longer
to make sure it happened in sync with the smooth scrolling. Because the
mpv code waits precisely on presentation time, the loop would timeout on
occasion instead of receiving the frame callback. This would then lead
to a skipped frame when rendering and thus causing stuttering.
The fix is simple: just only count consecutive timeouts as not receiving
frame callback. If a compositor holds the mpv buffer slightly longer to
avoid tearing, then we will definitely receive frame callback on the
next round of the render loop. This logic also appears to be sound for
plasma (funfact: Plasma always returns frame callback even when the
window is hidden. Not sure what's up with that, but luckily it doesn't
matter to us.), so get rid of the goofy 1/vblank_time thing and just
keep it a simple > 1 check.
2021-05-23 19:36:19 +00:00
|
|
|
* that this wait is accurate. Do a hacky block with wl_display_roundtrip. */
|
|
|
|
if (!wl->presentation && !wl_display_get_error(wl->display))
|
|
|
|
wl_display_roundtrip(wl->display);
|
|
|
|
|
|
|
|
if (wl->frame_wait) {
|
|
|
|
// Only consider consecutive missed callbacks.
|
|
|
|
if (wl->timeout_count > 1) {
|
2021-06-27 14:55:33 +00:00
|
|
|
wl->hidden = true;
|
wayland: shuffle around the render loop again
Take two. f4e89dd went wrong by moving vo_wayland_wait_frame before
start_frame was called. Whether or not this matters depends on the
compositor, but some weird things can happen. Basically, it's a
scheduling issue. vo_wayland_wait_frame queues all events and sends them
to the server to process (with no blocking if presentation time is
available). If mpv changes state while rendering (and this function is
called before every frame is drawn), then that event also gets
dispatched and sent to the compositor. This, in some cases, can cause
some funny behavior because the next frame gets attached to the surface
while the old buffer is getting released. It's safer to call this
function after the swap already happens and well before mpv calls its
next draw. There's no weird scheduling of events, and the compositor log
is more normal.
The second part of this is to fix some stuttering issues. This is mostly
just conjecture, but probably what was happening was this thing called
"composition". The easiest way to see this is to play a video on the
default audio sync mode (probably easiest to see on a typical 23.976
video). Have that in a window and float it over firefox (floating
windows are bloat on a tiling wm anyway). Then in firefox, do some short
bursts of smooth scrolling (likely uses egl). Some stutter in video
rendering could be observed, particularly in panning shots.
Compositors are supposed to prevent tearing so what likely was happening
was that the compositor was simply holding the buffer a wee bit longer
to make sure it happened in sync with the smooth scrolling. Because the
mpv code waits precisely on presentation time, the loop would timeout on
occasion instead of receiving the frame callback. This would then lead
to a skipped frame when rendering and thus causing stuttering.
The fix is simple: just only count consecutive timeouts as not receiving
frame callback. If a compositor holds the mpv buffer slightly longer to
avoid tearing, then we will definitely receive frame callback on the
next round of the render loop. This logic also appears to be sound for
plasma (funfact: Plasma always returns frame callback even when the
window is hidden. Not sure what's up with that, but luckily it doesn't
matter to us.), so get rid of the goofy 1/vblank_time thing and just
keep it a simple > 1 check.
2021-05-23 19:36:19 +00:00
|
|
|
return;
|
wayland: simplify render loop
This is actually a very nice simplification that should have been
thought of years ago (sue me). In a nutshell, the story with the
wayland code is that the frame callback and swap buffer behavior doesn't
fit very well with mpv's rendering loop. It's been refactored/changed
quite a few times over the years and works well enough but things could
be better. The current iteration works with an external swapchain to
check if we have frame callback before deciding whether or not to
render. This logic was implemented in both egl and vulkan.
This does have its warts however. There's some hidden state detection
logic which works but is kind of ugly. Since wayland doesn't allow
clients to know if they are actually visible (questionable but
whatever), you can just reasonably assume that if a bunch of callbacks
are missed in a row, you're probably not visible. That's fine, but it is
indeed less than ideal since the threshold is basically entirely
arbitrary and mpv does do a few wasteful renders before it decides that
the window is actually hidden.
The biggest urk in the vo_wayland_wait_frame is the use of
wl_display_roundtrip. Wayland developers would probably be offended by
the way mpv abuses that function, but essentially it was a way to have
semi-blocking behavior needed for display-resample to work. Since the
swap interval must be 0 on wayland (otherwise it will block the entire
player's rendering loop), we need some other way to wait on vsync. The
idea here was to dispatch and poll a bunch of wayland events, wait (with
a timeout) until we get frame callback, and then wait for the compositor
to process it. That pretty much perfectly waits on vsync and lets us
keep all the good timings and all that jazz that we want for mpv. The
problem is that wl_display_roundtrip is conceptually a bad function. It
can internally call wl_display_dispatch which in certain instances,
empty event queue, will block forever. Now strictly speaking, this
probably will never, ever happen (once I was able to to trigger it by
hardcoding an error into a compositor), but ideally
vo_wayland_wait_frame should never infinitely block and stall the
player. Unfortunately, removing that function always lead to problems
with timings and unsteady vsync intervals so it survived many refactors.
Until now, of course. In wayland, the ideal is to never do wasteful
rendering (i.e. don't render if the window isn't visible). Instead of
wrestling around with hidden states and possible missed vblanks, let's
rearrange the wayland rendering logic so we only ever draw a frame when
the frame callback is returned to use (within a reasonable timeout to
avoid blocking forever).
This slight rearrangement of the wait allows for several simplifications
to be made. Namely, wl_display_roundtrip stops being needed. Instead, we
can rely entirely on totally nonblocking calls (dispatch_pending, flush,
and so on). We still need to poll the fd here to actually get the frame
callback event from the compositor, but there's no longer any reason to
do extra waiting. As soon as we get the callback, we immediately draw.
This works quite well and has stable vsync (display-resample and audio).
Additionally, all of the logic about hidden states is no longer needed.
If vo_wayland_wait_frame times out, it's okay to assume immediately that
the window is not visible and skip rendering.
Unfortunately, there's one limitation on this new approach. It will only
work correctly if the compositor implements presentation time. That
means a reduced version of the old way still has to be carried around in
vo_wayland_wait_frame. So if the compositor has no presentation time,
then we are forced to use wl_display_roundtrip and juggle some funny
assumptions about whether or not the window is hidden or not. Plasma is
the only real notable compositor without presentation time at this stage
so perhaps this "legacy" mechanism could be removed in the future.
2021-05-17 19:36:59 +00:00
|
|
|
} else {
|
wayland: shuffle around the render loop again
Take two. f4e89dd went wrong by moving vo_wayland_wait_frame before
start_frame was called. Whether or not this matters depends on the
compositor, but some weird things can happen. Basically, it's a
scheduling issue. vo_wayland_wait_frame queues all events and sends them
to the server to process (with no blocking if presentation time is
available). If mpv changes state while rendering (and this function is
called before every frame is drawn), then that event also gets
dispatched and sent to the compositor. This, in some cases, can cause
some funny behavior because the next frame gets attached to the surface
while the old buffer is getting released. It's safer to call this
function after the swap already happens and well before mpv calls its
next draw. There's no weird scheduling of events, and the compositor log
is more normal.
The second part of this is to fix some stuttering issues. This is mostly
just conjecture, but probably what was happening was this thing called
"composition". The easiest way to see this is to play a video on the
default audio sync mode (probably easiest to see on a typical 23.976
video). Have that in a window and float it over firefox (floating
windows are bloat on a tiling wm anyway). Then in firefox, do some short
bursts of smooth scrolling (likely uses egl). Some stutter in video
rendering could be observed, particularly in panning shots.
Compositors are supposed to prevent tearing so what likely was happening
was that the compositor was simply holding the buffer a wee bit longer
to make sure it happened in sync with the smooth scrolling. Because the
mpv code waits precisely on presentation time, the loop would timeout on
occasion instead of receiving the frame callback. This would then lead
to a skipped frame when rendering and thus causing stuttering.
The fix is simple: just only count consecutive timeouts as not receiving
frame callback. If a compositor holds the mpv buffer slightly longer to
avoid tearing, then we will definitely receive frame callback on the
next round of the render loop. This logic also appears to be sound for
plasma (funfact: Plasma always returns frame callback even when the
window is hidden. Not sure what's up with that, but luckily it doesn't
matter to us.), so get rid of the goofy 1/vblank_time thing and just
keep it a simple > 1 check.
2021-05-23 19:36:19 +00:00
|
|
|
wl->timeout_count += 1;
|
|
|
|
return;
|
wayland: simplify render loop
This is actually a very nice simplification that should have been
thought of years ago (sue me). In a nutshell, the story with the
wayland code is that the frame callback and swap buffer behavior doesn't
fit very well with mpv's rendering loop. It's been refactored/changed
quite a few times over the years and works well enough but things could
be better. The current iteration works with an external swapchain to
check if we have frame callback before deciding whether or not to
render. This logic was implemented in both egl and vulkan.
This does have its warts however. There's some hidden state detection
logic which works but is kind of ugly. Since wayland doesn't allow
clients to know if they are actually visible (questionable but
whatever), you can just reasonably assume that if a bunch of callbacks
are missed in a row, you're probably not visible. That's fine, but it is
indeed less than ideal since the threshold is basically entirely
arbitrary and mpv does do a few wasteful renders before it decides that
the window is actually hidden.
The biggest urk in the vo_wayland_wait_frame is the use of
wl_display_roundtrip. Wayland developers would probably be offended by
the way mpv abuses that function, but essentially it was a way to have
semi-blocking behavior needed for display-resample to work. Since the
swap interval must be 0 on wayland (otherwise it will block the entire
player's rendering loop), we need some other way to wait on vsync. The
idea here was to dispatch and poll a bunch of wayland events, wait (with
a timeout) until we get frame callback, and then wait for the compositor
to process it. That pretty much perfectly waits on vsync and lets us
keep all the good timings and all that jazz that we want for mpv. The
problem is that wl_display_roundtrip is conceptually a bad function. It
can internally call wl_display_dispatch which in certain instances,
empty event queue, will block forever. Now strictly speaking, this
probably will never, ever happen (once I was able to to trigger it by
hardcoding an error into a compositor), but ideally
vo_wayland_wait_frame should never infinitely block and stall the
player. Unfortunately, removing that function always lead to problems
with timings and unsteady vsync intervals so it survived many refactors.
Until now, of course. In wayland, the ideal is to never do wasteful
rendering (i.e. don't render if the window isn't visible). Instead of
wrestling around with hidden states and possible missed vblanks, let's
rearrange the wayland rendering logic so we only ever draw a frame when
the frame callback is returned to use (within a reasonable timeout to
avoid blocking forever).
This slight rearrangement of the wait allows for several simplifications
to be made. Namely, wl_display_roundtrip stops being needed. Instead, we
can rely entirely on totally nonblocking calls (dispatch_pending, flush,
and so on). We still need to poll the fd here to actually get the frame
callback event from the compositor, but there's no longer any reason to
do extra waiting. As soon as we get the callback, we immediately draw.
This works quite well and has stable vsync (display-resample and audio).
Additionally, all of the logic about hidden states is no longer needed.
If vo_wayland_wait_frame times out, it's okay to assume immediately that
the window is not visible and skip rendering.
Unfortunately, there's one limitation on this new approach. It will only
work correctly if the compositor implements presentation time. That
means a reduced version of the old way still has to be carried around in
vo_wayland_wait_frame. So if the compositor has no presentation time,
then we are forced to use wl_display_roundtrip and juggle some funny
assumptions about whether or not the window is hidden or not. Plasma is
the only real notable compositor without presentation time at this stage
so perhaps this "legacy" mechanism could be removed in the future.
2021-05-17 19:36:59 +00:00
|
|
|
}
|
2020-10-13 15:36:08 +00:00
|
|
|
}
|
|
|
|
|
wayland: shuffle around the render loop again
Take two. f4e89dd went wrong by moving vo_wayland_wait_frame before
start_frame was called. Whether or not this matters depends on the
compositor, but some weird things can happen. Basically, it's a
scheduling issue. vo_wayland_wait_frame queues all events and sends them
to the server to process (with no blocking if presentation time is
available). If mpv changes state while rendering (and this function is
called before every frame is drawn), then that event also gets
dispatched and sent to the compositor. This, in some cases, can cause
some funny behavior because the next frame gets attached to the surface
while the old buffer is getting released. It's safer to call this
function after the swap already happens and well before mpv calls its
next draw. There's no weird scheduling of events, and the compositor log
is more normal.
The second part of this is to fix some stuttering issues. This is mostly
just conjecture, but probably what was happening was this thing called
"composition". The easiest way to see this is to play a video on the
default audio sync mode (probably easiest to see on a typical 23.976
video). Have that in a window and float it over firefox (floating
windows are bloat on a tiling wm anyway). Then in firefox, do some short
bursts of smooth scrolling (likely uses egl). Some stutter in video
rendering could be observed, particularly in panning shots.
Compositors are supposed to prevent tearing so what likely was happening
was that the compositor was simply holding the buffer a wee bit longer
to make sure it happened in sync with the smooth scrolling. Because the
mpv code waits precisely on presentation time, the loop would timeout on
occasion instead of receiving the frame callback. This would then lead
to a skipped frame when rendering and thus causing stuttering.
The fix is simple: just only count consecutive timeouts as not receiving
frame callback. If a compositor holds the mpv buffer slightly longer to
avoid tearing, then we will definitely receive frame callback on the
next round of the render loop. This logic also appears to be sound for
plasma (funfact: Plasma always returns frame callback even when the
window is hidden. Not sure what's up with that, but luckily it doesn't
matter to us.), so get rid of the goofy 1/vblank_time thing and just
keep it a simple > 1 check.
2021-05-23 19:36:19 +00:00
|
|
|
wl->timeout_count = 0;
|
2019-10-07 20:58:36 +00:00
|
|
|
}
|
|
|
|
|
2016-07-21 11:25:30 +00:00
|
|
|
void vo_wayland_wait_events(struct vo *vo, int64_t until_time_us)
|
|
|
|
{
|
2017-10-01 20:16:49 +00:00
|
|
|
struct vo_wayland_state *wl = vo->wl;
|
|
|
|
|
|
|
|
if (wl->display_fd == -1)
|
|
|
|
return;
|
2015-08-19 19:41:26 +00:00
|
|
|
|
2016-07-21 11:25:30 +00:00
|
|
|
int64_t wait_us = until_time_us - mp_time_us();
|
2016-08-26 18:22:33 +00:00
|
|
|
int timeout_ms = MPCLAMP((wait_us + 999) / 1000, 0, 10000);
|
2015-08-19 19:41:26 +00:00
|
|
|
|
wayland: refactor dispatching events
This was originally just a bugfix for a race condition, but the scope
expanded a bit. Currently, the wayland code does a prepare_read ->
dispatch_pending -> display_flush -> read_events -> dispatch_pending
routine that's basically straight from the wayland client API
documentation. This essentially just queues up all the wayland events
mpv has and dispatches them to the compositor. We do this for blocking
purposes on every frame we render. A very similar thing is done for
wait_events from the VO.
This code can pretty easily be unified and split off into a separate
function, vo_wayland_dispatch_events. vo_wayland_wait_frame will call
this function in a while loop (which will break either on timeout or if
we receive frame callback from the compositor). wait_events needs to
just check this in case we get some state change on the wakeup_pipe
(i.e. waking up from the paused state).
As for the actual bugfix part of this, it's a slight regression from
c26d833. The toplevel config event always forced a redraw when a surface
became activated again. This is for something like displaying cover art
on a music file. If the window was originally out of view and then later
brought back into focus, no picture would be rendered (i.e. the window
is just black). That's because something like cover art is just 1 frame
and the VO stops doing any other additional rendering. If you miss that
1 frame, nothing would show up ever again. The fix in this case is to
always just force a redraw when the mpv window comes back into view.
Well with the aforementioned commit, we stopped doing
wl_display_roundtrip calls on every frame. That means we no longer do
roundtrip blocking calls. We just be sure to queue up all of the events
we have and then dispatch them. Because wayland is fundamentally an
asynchronous protocol, there's no guarantee what order these events
would be processed in. This meant that on occasion, a
vo_wayland_wait_frame call (this could occur multiple times depending on
the exact situation) would occur before the compositor would send back
frame callback. That would result in the aforementioned bug of having
just a black window. The fix, in this case, is to just do a
vo_wayland_wait_frame call directly before we force the VO to do a
redraw. Note that merely dispatching events isn't enough because we
specifically need to wait for the compositor to give us frame callback
before doing a new render.
P.S. fix a typo too.
2021-05-27 20:22:21 +00:00
|
|
|
vo_wayland_dispatch_events(wl, 2, timeout_ms);
|
2015-08-19 19:41:26 +00:00
|
|
|
}
|
2021-06-26 21:12:03 +00:00
|
|
|
|
|
|
|
void vo_wayland_wakeup(struct vo *vo)
|
|
|
|
{
|
|
|
|
struct vo_wayland_state *wl = vo->wl;
|
|
|
|
(void)write(wl->wakeup_pipe[1], &(char){0}, 1);
|
|
|
|
}
|