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"
|
2022-06-10 16:49:28 +00:00
|
|
|
#include "present_sync.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"
|
2022-05-30 17:58:57 +00:00
|
|
|
#include "generated/wayland/linux-dmabuf-unstable-v1.h"
|
2021-06-26 21:12:03 +00:00
|
|
|
#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"
|
2022-05-18 14:35:53 +00:00
|
|
|
#include "generated/wayland/viewporter.h"
|
2013-02-28 18:55:02 +00:00
|
|
|
|
wayland: add support for content-type protocol
The content-type protocol allows mpv to send compositor a hint about the
type of content being displayed on its surface so it could potentially
make some sort of optimization. Fundamentally, this is pretty simple but
since this requires a very new wayland-protocols version (1.27), we have
to mess with the build to add a new define and add a bunch of if's in
here. The protocol itself exposes 4 different types of content: none,
photo, video, and game.
To do that, let's add a new option (wayland-content-type) that lets
users control what hint to send to the compossitor. Since the previous
commit adds a VOCTRL that notifies us about the content being displayed,
we can also add an auto value to this option. As you'd expect, the
compositor hint would be set to photo if mpv's core detects an image,
video for other things, and it is set to none for the special case of
forcing a window when there is not a video track. For completion's sake,
game is also allowed as a value for this option, but in practice there
shouldn't be a reason to use that.
2022-11-15 21:51:45 +00:00
|
|
|
#if HAVE_WAYLAND_PROTOCOLS_1_27
|
|
|
|
#include "generated/wayland/content-type-v1.h"
|
2022-11-16 01:52:56 +00:00
|
|
|
#include "generated/wayland/single-pixel-buffer-v1.h"
|
wayland: add support for content-type protocol
The content-type protocol allows mpv to send compositor a hint about the
type of content being displayed on its surface so it could potentially
make some sort of optimization. Fundamentally, this is pretty simple but
since this requires a very new wayland-protocols version (1.27), we have
to mess with the build to add a new define and add a bunch of if's in
here. The protocol itself exposes 4 different types of content: none,
photo, video, and game.
To do that, let's add a new option (wayland-content-type) that lets
users control what hint to send to the compossitor. Since the previous
commit adds a VOCTRL that notifies us about the content being displayed,
we can also add an auto value to this option. As you'd expect, the
compositor hint would be set to photo if mpv's core detects an image,
video for other things, and it is set to none for the special case of
forcing a window when there is not a video track. For completion's sake,
game is also allowed as a value for this option, but in practice there
shouldn't be a reason to use that.
2022-11-15 21:51:45 +00:00
|
|
|
#endif
|
|
|
|
|
2023-01-23 20:16:43 +00:00
|
|
|
#if HAVE_WAYLAND_PROTOCOLS_1_31
|
|
|
|
#include "generated/wayland/fractional-scale-v1.h"
|
|
|
|
#endif
|
|
|
|
|
2023-02-08 14:00:08 +00:00
|
|
|
#if WAYLAND_VERSION_MAJOR > 1 || WAYLAND_VERSION_MINOR >= 22
|
|
|
|
#define HAVE_WAYLAND_1_22
|
|
|
|
#endif
|
|
|
|
|
2022-10-04 16:05:00 +00:00
|
|
|
#ifndef CLOCK_MONOTONIC_RAW
|
|
|
|
#define CLOCK_MONOTONIC_RAW 4
|
|
|
|
#endif
|
|
|
|
|
2022-05-18 14:35:53 +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},
|
2022-09-02 10:53:18 +00:00
|
|
|
{XKB_KEY_F13, MP_KEY_F +13}, {XKB_KEY_F14, MP_KEY_F +14},
|
|
|
|
{XKB_KEY_F15, MP_KEY_F +15}, {XKB_KEY_F16, MP_KEY_F +16},
|
|
|
|
{XKB_KEY_F17, MP_KEY_F +17}, {XKB_KEY_F18, MP_KEY_F +18},
|
|
|
|
{XKB_KEY_F19, MP_KEY_F +19}, {XKB_KEY_F20, MP_KEY_F +20},
|
|
|
|
{XKB_KEY_F21, MP_KEY_F +21}, {XKB_KEY_F22, MP_KEY_F +22},
|
|
|
|
{XKB_KEY_F23, MP_KEY_F +23}, {XKB_KEY_F24, MP_KEY_F +24},
|
2021-06-26 21:12:03 +00:00
|
|
|
|
|
|
|
/* 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[]) {
|
2023-01-26 20:21:58 +00:00
|
|
|
{"wayland-configure-bounds", OPT_CHOICE(configure_bounds,
|
|
|
|
{"auto", -1}, {"no", 0}, {"yes", 1})},
|
2023-02-20 03:32:50 +00:00
|
|
|
{"wayland-disable-vsync", OPT_BOOL(disable_vsync)},
|
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-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) {
|
2023-01-26 20:21:58 +00:00
|
|
|
.configure_bounds = -1,
|
2023-03-07 17:24:53 +00:00
|
|
|
.edge_pixels_pointer = 16,
|
2021-08-08 03:05:05 +00:00
|
|
|
.edge_pixels_touch = 32,
|
2019-10-14 17:16:42 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2023-01-14 01:28:46 +00:00
|
|
|
struct vo_wayland_feedback_pool {
|
|
|
|
struct wp_presentation_feedback **fback;
|
|
|
|
struct vo_wayland_state *wl;
|
|
|
|
int len;
|
|
|
|
};
|
|
|
|
|
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;
|
2022-04-08 15:19:39 +00:00
|
|
|
char *name;
|
2021-06-26 21:12:03 +00:00
|
|
|
struct wl_list link;
|
2017-10-01 20:16:49 +00:00
|
|
|
};
|
|
|
|
|
2023-03-07 17:16:45 +00:00
|
|
|
static int check_for_resize(struct vo_wayland_state *wl, int edge_pixels,
|
|
|
|
enum xdg_toplevel_resize_edge *edge);
|
2021-06-26 21:12:03 +00:00
|
|
|
static int get_mods(struct vo_wayland_state *wl);
|
|
|
|
static int lookupkey(int key);
|
|
|
|
static int set_cursor_visibility(struct vo_wayland_state *wl, bool on);
|
|
|
|
static int spawn_cursor(struct vo_wayland_state *wl);
|
|
|
|
|
2023-01-14 01:28:46 +00:00
|
|
|
static void add_feedback(struct vo_wayland_feedback_pool *fback_pool,
|
|
|
|
struct wp_presentation_feedback *fback);
|
2021-06-26 21:12:03 +00:00
|
|
|
static void greatest_common_divisor(struct vo_wayland_state *wl, int a, int b);
|
2023-05-17 21:13:04 +00:00
|
|
|
static void guess_focus(struct vo_wayland_state *wl);
|
2023-01-14 01:28:46 +00:00
|
|
|
static void remove_feedback(struct vo_wayland_feedback_pool *fback_pool,
|
|
|
|
struct wp_presentation_feedback *fback);
|
2021-06-26 21:12:03 +00:00
|
|
|
static void remove_output(struct vo_wayland_output *out);
|
2021-08-11 02:17:42 +00:00
|
|
|
static void request_decoration_mode(struct vo_wayland_state *wl, uint32_t mode);
|
2023-01-23 20:16:43 +00:00
|
|
|
static void rescale_geometry(struct vo_wayland_state *wl, double old_scale);
|
2023-01-26 18:53:41 +00:00
|
|
|
static void set_geometry(struct vo_wayland_state *wl, bool resize);
|
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 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;
|
|
|
|
|
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;
|
|
|
|
|
2022-01-27 23:21:27 +00:00
|
|
|
if (button >= BTN_MOUSE && button < BTN_JOYSTICK) {
|
|
|
|
switch (button) {
|
|
|
|
case BTN_LEFT:
|
|
|
|
button = MP_MBTN_LEFT;
|
|
|
|
break;
|
|
|
|
case BTN_MIDDLE:
|
|
|
|
button = MP_MBTN_MID;
|
|
|
|
break;
|
|
|
|
case BTN_RIGHT:
|
|
|
|
button = MP_MBTN_RIGHT;
|
|
|
|
break;
|
|
|
|
case BTN_SIDE:
|
|
|
|
button = MP_MBTN_BACK;
|
|
|
|
break;
|
|
|
|
case BTN_EXTRA:
|
|
|
|
button = MP_MBTN_FORWARD;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
button += MP_MBTN9 - BTN_FORWARD;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
2019-07-11 14:02:33 +00:00
|
|
|
button = 0;
|
|
|
|
}
|
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) &&
|
2023-03-07 17:16:45 +00:00
|
|
|
(button == MP_MBTN_LEFT) && (state == MP_KEY_STATE_DOWN))
|
|
|
|
{
|
2019-11-25 23:52:53 +00:00
|
|
|
uint32_t edges;
|
|
|
|
// Implement an edge resize zone if there are no decorations
|
2023-03-07 17:16:45 +00:00
|
|
|
if (!wl->vo_opts->border && check_for_resize(wl, wl->opts->edge_pixels_pointer, &edges)) {
|
2019-11-25 23:52:53 +00:00
|
|
|
xdg_toplevel_resize(wl->xdg_toplevel, wl->seat, serial, edges);
|
2023-03-07 17:16:45 +00:00
|
|
|
} else {
|
2019-11-25 23:52:53 +00:00
|
|
|
window_move(wl, serial);
|
2023-03-07 17:16:45 +00:00
|
|
|
}
|
2023-03-27 20:42:17 +00:00
|
|
|
// Explicitly send an UP event after the client finishes a move/resize
|
2020-04-23 19:49:18 +00:00
|
|
|
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
|
|
|
|
2021-11-20 15:13:06 +00:00
|
|
|
int mpmod = get_mods(wl);
|
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)
|
2021-11-20 15:13:06 +00:00
|
|
|
mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_DOWN | mpmod, +val);
|
2017-10-01 20:16:49 +00:00
|
|
|
if (value < 0)
|
2021-11-20 15:13:06 +00:00
|
|
|
mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_UP | mpmod, -val);
|
2017-10-01 20:16:49 +00:00
|
|
|
break;
|
|
|
|
case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
|
|
|
|
if (value > 0)
|
2021-11-20 15:13:06 +00:00
|
|
|
mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_RIGHT | mpmod, +val);
|
2017-10-01 20:16:49 +00:00
|
|
|
if (value < 0)
|
2021-11-20 15:13:06 +00:00
|
|
|
mp_input_put_wheel(wl->vo->input_ctx, MP_WHEEL_LEFT | mpmod, -val);
|
2017-10-01 20:16:49 +00:00
|
|
|
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;
|
|
|
|
wl->mouse_x = wl_fixed_to_int(x_w) * wl->scaling;
|
|
|
|
wl->mouse_y = wl_fixed_to_int(y_w) * wl->scaling;
|
|
|
|
|
2021-08-08 03:05:05 +00:00
|
|
|
enum xdg_toplevel_resize_edge edge;
|
2022-12-01 16:16:49 +00:00
|
|
|
if (!mp_input_test_dragging(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y)) {
|
2023-03-07 17:16:45 +00:00
|
|
|
if (check_for_resize(wl, wl->opts->edge_pixels_touch, &edge)) {
|
2022-12-01 16:16:49 +00:00
|
|
|
xdg_toplevel_resize(wl->xdg_toplevel, wl->seat, serial, edge);
|
|
|
|
} else {
|
|
|
|
xdg_toplevel_move(wl->xdg_toplevel, wl->seat, serial);
|
|
|
|
}
|
2021-08-08 03:05:05 +00:00
|
|
|
}
|
2022-12-01 16:16:49 +00:00
|
|
|
|
|
|
|
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);
|
2017-10-01 20:16:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void touch_handle_up(void *data, struct wl_touch *wl_touch,
|
|
|
|
uint32_t serial, uint32_t time, int32_t id)
|
|
|
|
{
|
|
|
|
struct vo_wayland_state *wl = data;
|
|
|
|
mp_input_put_key(wl->vo->input_ctx, MP_MBTN_LEFT | MP_KEY_STATE_UP);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void touch_handle_motion(void *data, struct wl_touch *wl_touch,
|
|
|
|
uint32_t time, int32_t id, wl_fixed_t x_w, wl_fixed_t y_w)
|
|
|
|
{
|
|
|
|
struct vo_wayland_state *wl = data;
|
|
|
|
|
|
|
|
wl->mouse_x = wl_fixed_to_int(x_w) * wl->scaling;
|
|
|
|
wl->mouse_y = wl_fixed_to_int(y_w) * wl->scaling;
|
|
|
|
|
|
|
|
mp_input_set_mouse_pos(wl->vo->input_ctx, wl->mouse_x, wl->mouse_y);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void touch_handle_frame(void *data, struct wl_touch *wl_touch)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static void touch_handle_cancel(void *data, struct wl_touch *wl_touch)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static 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
|
|
|
|
2022-01-07 22:26:18 +00:00
|
|
|
map_str = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
2021-06-26 21:12:03 +00:00
|
|
|
if (map_str == MAP_FAILED) {
|
|
|
|
close(fd);
|
|
|
|
return;
|
|
|
|
}
|
2013-07-18 15:35:28 +00:00
|
|
|
|
2022-01-07 22:28:02 +00:00
|
|
|
wl->xkb_keymap = xkb_keymap_new_from_buffer(wl->xkb_context, map_str,
|
|
|
|
strnlen(map_str, size),
|
2021-06-26 21:12:03 +00:00
|
|
|
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;
|
2023-05-17 21:13:04 +00:00
|
|
|
guess_focus(wl);
|
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;
|
2023-05-17 21:13:04 +00:00
|
|
|
guess_focus(wl);
|
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;
|
|
|
|
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
|
|
|
|
2021-08-15 14:32:11 +00:00
|
|
|
if (wl->xkb_state) {
|
|
|
|
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);
|
2023-06-12 23:09:21 +00:00
|
|
|
if (score > wl->dnd_mime_score && wl->vo_opts->drag_and_drop != -2) {
|
2017-10-01 20:16:49 +00:00
|
|
|
wl->dnd_mime_score = score;
|
2023-06-12 23:09:21 +00:00
|
|
|
if (wl->dnd_mime_type)
|
|
|
|
talloc_free(wl->dnd_mime_type);
|
2017-10-01 20:16:49 +00:00
|
|
|
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;
|
2023-06-12 23:09:21 +00:00
|
|
|
if (dnd_action && wl->vo_opts->drag_and_drop != -2) {
|
2023-05-20 21:51:12 +00:00
|
|
|
if (wl->vo_opts->drag_and_drop >= 0) {
|
|
|
|
wl->dnd_action = wl->vo_opts->drag_and_drop;
|
|
|
|
} else {
|
|
|
|
wl->dnd_action = dnd_action & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY ?
|
|
|
|
DND_REPLACE : DND_APPEND;
|
|
|
|
}
|
2023-05-20 21:11:13 +00:00
|
|
|
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
|
|
|
|
2023-06-12 23:09:21 +00:00
|
|
|
if (wl->vo_opts->drag_and_drop != -2) {
|
|
|
|
wl_data_offer_set_actions(id, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY |
|
|
|
|
WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE,
|
|
|
|
WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY);
|
|
|
|
wl_data_offer_accept(id, serial, wl->dnd_mime_type);
|
|
|
|
MP_VERBOSE(wl, "Accepting DND offer with mime type %s\n", wl->dnd_mime_type);
|
|
|
|
}
|
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_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;
|
|
|
|
}
|
|
|
|
|
2023-06-12 23:09:21 +00:00
|
|
|
if (wl->vo_opts->drag_and_drop != -2) {
|
|
|
|
MP_VERBOSE(wl, "Releasing DND offer with mime type %s\n", wl->dnd_mime_type);
|
|
|
|
if (wl->dnd_mime_type)
|
|
|
|
TA_FREEP(&wl->dnd_mime_type);
|
|
|
|
wl->dnd_mime_score = 0;
|
|
|
|
}
|
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;
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2023-06-12 23:09:21 +00:00
|
|
|
if (wl->vo_opts->drag_and_drop != -2) {
|
|
|
|
MP_VERBOSE(wl, "Receiving DND offer with mime %s\n", wl->dnd_mime_type);
|
|
|
|
wl_data_offer_receive(wl->dnd_offer, wl->dnd_mime_type, pipefd[1]);
|
|
|
|
}
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2017-10-01 20:16:49 +00:00
|
|
|
close(pipefd[1]);
|
|
|
|
wl->dnd_fd = pipefd[0];
|
|
|
|
}
|
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)
|
|
|
|
{
|
wayland: partially fix drag and drop handling
Drag and drop in wayland is weird and it seems everyone does this
slightly differently (fun). In the past, there was a crash that
occured (fixed by 700f4ef5fad353800fa866b059663bc1dd58d3b7) which
involved using the wl_data_offer_finish in an incorrect way that
triggered a protocol error (always fatal). The fix involved moving the
function call to data_device_handle_drop which seemingly works, but it
has an unfortunate side effect. It appears like GTK applications (or at
least firefox) close the pipe after this function is called which makes
it impossible for mpv to read data from the fd (well you could force it
open again in theory but let's not do that). Who knows if that was the
case when that commit was made (probably not because I'd think I would
have noticed; could just be a dummy though), but obviously having broken
dnd for a major application isn't so fun (this works with QT and
chromium anyway).
Ideally one would just simply check the pipe in data_device_handle_drop,
but this doesn't work because it doesn't seem the compositor actually
sends mpv the data by then. There's not actually a defined event when
you're supposed to be able to read data from this pipe, so we wait for
the usual event checking loop later for this. In that case,
wl_data_offer_finish needs to go back into check_dnd_fd, but we have to
be careful when calling it otherwise we'd just commit protocol errors
like before. We check to make sure we even have a valid wl->dnd_offer
before trying to indicate that it is finished and additionally make sure
there is a valid dnd_action (after checking the fd, it's always set back
to -1).
This doesn't fix everything though. Specifically, sway with
focus_follows_mouse (the default) and GTK as the data source still
doesn't work. The reason is that when you do a drag and drop in sway
with that option on, a new wl_data_device.data_offer event is sent out
instantly after the drop event. This happens before any data is sent
across the fd and before mpv even has a chance to check it. What GTK
does, when getting this new data_offer event, is close the pipe
(POLLHUP). This means mpv can't read it when we reach the event loop and
thus no data is ever read and broken drag and drop. From the client
side, this isn't really fixable since the wayland protocol doesn't have
a clear indication of when clients are supposed to read from the fd and
both the compositor and data source are doing things totally out of our
control. So we'll consider this weird case, "not our bug" at least. The
rest should work.
2022-02-03 16:48:56 +00:00
|
|
|
struct vo_wayland_state *wl = data;
|
|
|
|
|
|
|
|
if (wl->dnd_offer) {
|
|
|
|
wl_data_offer_destroy(wl->dnd_offer);
|
|
|
|
wl->dnd_offer = NULL;
|
2022-07-23 16:48:28 +00:00
|
|
|
MP_VERBOSE(wl, "Received a new DND offer. Releasing the previous offer.\n");
|
wayland: partially fix drag and drop handling
Drag and drop in wayland is weird and it seems everyone does this
slightly differently (fun). In the past, there was a crash that
occured (fixed by 700f4ef5fad353800fa866b059663bc1dd58d3b7) which
involved using the wl_data_offer_finish in an incorrect way that
triggered a protocol error (always fatal). The fix involved moving the
function call to data_device_handle_drop which seemingly works, but it
has an unfortunate side effect. It appears like GTK applications (or at
least firefox) close the pipe after this function is called which makes
it impossible for mpv to read data from the fd (well you could force it
open again in theory but let's not do that). Who knows if that was the
case when that commit was made (probably not because I'd think I would
have noticed; could just be a dummy though), but obviously having broken
dnd for a major application isn't so fun (this works with QT and
chromium anyway).
Ideally one would just simply check the pipe in data_device_handle_drop,
but this doesn't work because it doesn't seem the compositor actually
sends mpv the data by then. There's not actually a defined event when
you're supposed to be able to read data from this pipe, so we wait for
the usual event checking loop later for this. In that case,
wl_data_offer_finish needs to go back into check_dnd_fd, but we have to
be careful when calling it otherwise we'd just commit protocol errors
like before. We check to make sure we even have a valid wl->dnd_offer
before trying to indicate that it is finished and additionally make sure
there is a valid dnd_action (after checking the fd, it's always set back
to -1).
This doesn't fix everything though. Specifically, sway with
focus_follows_mouse (the default) and GTK as the data source still
doesn't work. The reason is that when you do a drag and drop in sway
with that option on, a new wl_data_device.data_offer event is sent out
instantly after the drop event. This happens before any data is sent
across the fd and before mpv even has a chance to check it. What GTK
does, when getting this new data_offer event, is close the pipe
(POLLHUP). This means mpv can't read it when we reach the event loop and
thus no data is ever read and broken drag and drop. From the client
side, this isn't really fixable since the wayland protocol doesn't have
a clear indication of when clients are supposed to read from the fd and
both the compositor and data source are doing things totally out of our
control. So we'll consider this weird case, "not our bug" at least. The
rest should work.
2022-02-03 16:48:56 +00:00
|
|
|
}
|
|
|
|
|
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 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
|
|
|
|
2022-04-08 15:19:39 +00:00
|
|
|
static void output_handle_done(void *data, struct wl_output *wl_output)
|
2021-06-26 21:12:03 +00:00
|
|
|
{
|
|
|
|
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);
|
2021-06-26 21:12:03 +00:00
|
|
|
spawn_cursor(wl);
|
2023-01-26 18:53:41 +00:00
|
|
|
set_geometry(wl, false);
|
2021-06-26 21:12:03 +00:00
|
|
|
wl->pending_vo_events |= VO_EVENT_DPI;
|
|
|
|
wl->pending_vo_events |= VO_EVENT_RESIZE;
|
|
|
|
}
|
|
|
|
|
|
|
|
wl->pending_vo_events |= VO_EVENT_WIN_STATE;
|
|
|
|
}
|
|
|
|
|
2022-04-08 15:19:39 +00:00
|
|
|
static void output_handle_scale(void *data, struct wl_output *wl_output,
|
2021-06-26 21:12:03 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-04-08 15:19:39 +00:00
|
|
|
static void output_handle_name(void *data, struct wl_output *wl_output,
|
|
|
|
const char *name)
|
|
|
|
{
|
|
|
|
struct vo_wayland_output *output = data;
|
|
|
|
output->name = talloc_strdup(output->wl, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void output_handle_description(void *data, struct wl_output *wl_output,
|
|
|
|
const char *description)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
static const struct wl_output_listener output_listener = {
|
|
|
|
output_handle_geometry,
|
|
|
|
output_handle_mode,
|
|
|
|
output_handle_done,
|
|
|
|
output_handle_scale,
|
2022-04-08 15:19:39 +00:00
|
|
|
output_handle_name,
|
|
|
|
output_handle_description,
|
2021-06-26 21:12:03 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static void surface_handle_enter(void *data, struct wl_surface *wl_surface,
|
|
|
|
struct wl_output *output)
|
|
|
|
{
|
|
|
|
struct vo_wayland_state *wl = data;
|
2021-11-20 16:48:09 +00:00
|
|
|
if (!wl->current_output)
|
|
|
|
return;
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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;
|
|
|
|
|
2023-02-08 14:00:08 +00:00
|
|
|
if (!wl->fractional_scale_manager && wl_surface_get_version(wl_surface) < 6 &&
|
|
|
|
wl->scaling != wl->current_output->scale)
|
|
|
|
{
|
2021-06-28 16:03:51 +00:00
|
|
|
set_surface_scaling(wl);
|
|
|
|
spawn_cursor(wl);
|
2022-01-28 00:34:17 +00:00
|
|
|
force_resize = true;
|
2021-06-28 16:03:51 +00:00
|
|
|
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)) {
|
2023-01-26 18:53:41 +00:00
|
|
|
set_geometry(wl, false);
|
2021-06-26 21:12:03 +00:00
|
|
|
force_resize = true;
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
2023-01-23 20:16:43 +00:00
|
|
|
MP_VERBOSE(wl, "Surface entered output %s %s (0x%x), scale = %f, refresh rate = %f Hz\n",
|
2022-11-18 22:36:21 +00:00
|
|
|
o->make, o->model, o->id, wl->scaling, o->refresh_rate);
|
2017-10-09 01:08:36 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-08 14:00:08 +00:00
|
|
|
#ifdef HAVE_WAYLAND_1_22
|
|
|
|
static void surface_handle_preferred_buffer_scale(void *data,
|
|
|
|
struct wl_surface *wl_surface,
|
|
|
|
int32_t scale)
|
|
|
|
{
|
|
|
|
struct vo_wayland_state *wl = data;
|
|
|
|
double old_scale = wl->scaling;
|
|
|
|
|
|
|
|
if (wl->fractional_scale_manager)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// dmabuf_wayland is always wl->scaling = 1
|
|
|
|
wl->scaling = !wl->using_dmabuf_wayland ? scale : 1;
|
|
|
|
MP_VERBOSE(wl, "Obtained preferred scale, %f, from the compositor.\n",
|
|
|
|
wl->scaling);
|
|
|
|
wl->pending_vo_events |= VO_EVENT_DPI;
|
|
|
|
if (wl->current_output) {
|
|
|
|
rescale_geometry(wl, old_scale);
|
|
|
|
set_geometry(wl, false);
|
|
|
|
wl->pending_vo_events |= VO_EVENT_RESIZE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void surface_handle_preferred_buffer_transform(void *data,
|
|
|
|
struct wl_surface *wl_surface,
|
|
|
|
uint32_t transform)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2017-10-09 01:08:36 +00:00
|
|
|
static const struct wl_surface_listener surface_listener = {
|
|
|
|
surface_handle_enter,
|
|
|
|
surface_handle_leave,
|
2023-02-08 14:00:08 +00:00
|
|
|
#ifdef HAVE_WAYLAND_1_22
|
|
|
|
surface_handle_preferred_buffer_scale,
|
|
|
|
surface_handle_preferred_buffer_transform,
|
|
|
|
#endif
|
2017-10-09 01:08:36 +00:00
|
|
|
};
|
|
|
|
|
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-08-06 16:47:16 +00:00
|
|
|
int old_toplevel_width = wl->toplevel_width;
|
|
|
|
int old_toplevel_height = wl->toplevel_height;
|
|
|
|
wl->toplevel_width = width;
|
|
|
|
wl->toplevel_height = height;
|
|
|
|
|
2022-01-25 17:48:43 +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)
|
2021-04-25 16:40:49 +00:00
|
|
|
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
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2021-08-11 02:17:42 +00:00
|
|
|
if (wl->requested_decoration)
|
|
|
|
request_decoration_mode(wl, wl->requested_decoration);
|
|
|
|
|
2020-09-07 16:22:25 +00:00
|
|
|
if (wl->activated != is_activated) {
|
|
|
|
wl->activated = is_activated;
|
2023-05-17 21:13:04 +00:00
|
|
|
guess_focus(wl);
|
wayland: cleanup handle_toplevel_config
The source of many geometry woes. There's some loosely related toplevel
things that should be cleaned up/fixed. First of all,
VO_EVENT_LIVE_RESIZING is actually completely useless. It might have
been useful sometime in the past, but there's no point. It doesn't
"speed up" resizing in any way and appears to be originally for cocoa.
Just remove it.
Way back in the day, toplevel_width/height was added as a workaround for
when we got uncoorperative (i.e. wrong) width/height coordinates from
the compositor in this event. Basically it could happen due to numerous
reasons but a lack of atomic commits was part of the reason and also
mpv's geometry handling then was a lot rougher. We *shouldn't* need this
workaround anymore. The width/height values are only used exactly when
we need them. If mpv sets geometry on its own, it should still be the
right dimensions.
Related to the above, mpv never actually propertly handled the case
where width or height was equal to 0. According to the xdg-shell spec,
"If the width or height arguments are zero, it means the client should
decided its own window dimension." An example of a compositor doing this
is weston. It's, unsurprisingly, broken. Getting out of fullscreen or a
maximized state does not restore the old window size like it should. The
right way to handle this is to just return near the end of the function
if we have a 0 for either argument and before any geometry is set
(wl->geometry's width or height can never be zero). Luckily, state
changes are already being detected so they just trigger the goto when
needed.
Finally, e2c24ad mistakenly removed the VO_EVENT_EXPOSE. There are edge
cases where this is needed and it's safer to just force a redraw here
when the window gets activated again. Just force wl->hidden to false
first and then trigger the expose.
2021-08-02 21:49:48 +00:00
|
|
|
/* Just force a redraw to be on the safe side. */
|
|
|
|
if (wl->activated) {
|
|
|
|
wl->hidden = false;
|
|
|
|
wl->pending_vo_events |= VO_EVENT_EXPOSE;
|
|
|
|
}
|
2020-09-07 16:22:25 +00:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2022-01-25 17:56:37 +00:00
|
|
|
/* Reuse old size if either of these are 0. */
|
|
|
|
if (width == 0 || height == 0) {
|
|
|
|
if (!is_fullscreen && !is_maximized) {
|
|
|
|
wl->geometry = wl->window_size;
|
|
|
|
}
|
|
|
|
goto resize;
|
|
|
|
}
|
|
|
|
|
2021-08-06 16:47:16 +00:00
|
|
|
if (old_toplevel_width == wl->toplevel_width &&
|
|
|
|
old_toplevel_height == wl->toplevel_height)
|
2020-02-06 02:47:10 +00:00
|
|
|
return;
|
|
|
|
|
2020-08-18 17:55:01 +00:00
|
|
|
if (!is_fullscreen && !is_maximized) {
|
2021-08-09 01:55:26 +00:00
|
|
|
if (vo_opts->keepaspect) {
|
|
|
|
double scale_factor = (double)width / wl->reduced_width;
|
|
|
|
width = ceil(wl->reduced_width * scale_factor);
|
|
|
|
if (vo_opts->keepaspect_window)
|
|
|
|
height = ceil(wl->reduced_height * scale_factor);
|
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;
|
2023-01-22 23:06:15 +00:00
|
|
|
wl->window_size.x1 = round(width * wl->scaling);
|
|
|
|
wl->window_size.y1 = round(height * wl->scaling);
|
2017-10-01 20:16:49 +00:00
|
|
|
}
|
2020-08-18 17:55:01 +00:00
|
|
|
wl->geometry.x0 = 0;
|
|
|
|
wl->geometry.y0 = 0;
|
2023-01-22 23:06:15 +00:00
|
|
|
wl->geometry.x1 = round(width * wl->scaling);
|
|
|
|
wl->geometry.y1 = round(height * wl->scaling);
|
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",
|
2023-01-22 23:06:15 +00:00
|
|
|
mp_rect_w(old_geometry), mp_rect_h(old_geometry),
|
|
|
|
mp_rect_w(wl->geometry), mp_rect_h(wl->geometry));
|
2017-10-01 20:16:49 +00:00
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2022-05-31 13:36:15 +00:00
|
|
|
static void handle_configure_bounds(void *data, struct xdg_toplevel *xdg_toplevel,
|
|
|
|
int32_t width, int32_t height)
|
|
|
|
{
|
|
|
|
struct vo_wayland_state *wl = data;
|
2023-04-11 14:17:00 +00:00
|
|
|
wl->bounded_width = width * wl->scaling;
|
|
|
|
wl->bounded_height = height * wl->scaling;
|
2022-05-31 13:36:15 +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,
|
2022-05-31 13:36:15 +00:00
|
|
|
handle_configure_bounds,
|
2017-10-01 20:16:49 +00:00
|
|
|
};
|
|
|
|
|
2023-01-23 20:16:43 +00:00
|
|
|
#if HAVE_WAYLAND_PROTOCOLS_1_31
|
|
|
|
static void preferred_scale(void *data,
|
|
|
|
struct wp_fractional_scale_v1 *fractional_scale,
|
|
|
|
uint32_t scale)
|
|
|
|
{
|
|
|
|
struct vo_wayland_state *wl = data;
|
|
|
|
double old_scale = wl->scaling;
|
|
|
|
|
|
|
|
// dmabuf_wayland is always wl->scaling = 1
|
2023-02-28 09:39:50 +00:00
|
|
|
wl->scaling = !wl->using_dmabuf_wayland ? (double)scale / 120 : 1;
|
2023-01-23 20:16:43 +00:00
|
|
|
MP_VERBOSE(wl, "Obtained preferred scale, %f, from the compositor.\n",
|
|
|
|
wl->scaling);
|
|
|
|
wl->pending_vo_events |= VO_EVENT_DPI;
|
|
|
|
if (wl->current_output) {
|
|
|
|
rescale_geometry(wl, old_scale);
|
2023-01-26 18:53:41 +00:00
|
|
|
set_geometry(wl, false);
|
2023-01-23 20:16:43 +00:00
|
|
|
wl->pending_vo_events |= VO_EVENT_RESIZE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct wp_fractional_scale_v1_listener fractional_scale_listener = {
|
|
|
|
preferred_scale,
|
|
|
|
};
|
|
|
|
#endif
|
|
|
|
|
2022-10-22 16:00:25 +00:00
|
|
|
static const char *zxdg_decoration_mode_to_str(const uint32_t mode)
|
|
|
|
{
|
|
|
|
switch (mode) {
|
|
|
|
case ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE:
|
|
|
|
return "server-side";
|
|
|
|
case ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE:
|
|
|
|
return "client-side";
|
|
|
|
default:
|
|
|
|
return "<unknown>";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-09 16:40:46 +00:00
|
|
|
static void configure_decorations(void *data,
|
|
|
|
struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration,
|
|
|
|
uint32_t mode)
|
|
|
|
{
|
|
|
|
struct vo_wayland_state *wl = data;
|
2021-08-11 02:17:42 +00:00
|
|
|
struct mp_vo_opts *opts = wl->vo_opts;
|
|
|
|
|
2022-10-26 20:37:48 +00:00
|
|
|
if (wl->requested_decoration && mode != wl->requested_decoration) {
|
|
|
|
MP_DBG(wl,
|
|
|
|
"Requested %s decorations but compositor responded with %s. "
|
|
|
|
"It is likely that compositor wants us to stay in a given mode.\n",
|
|
|
|
zxdg_decoration_mode_to_str(wl->requested_decoration),
|
|
|
|
zxdg_decoration_mode_to_str(mode));
|
2022-10-22 16:00:25 +00:00
|
|
|
}
|
2021-08-11 02:17:42 +00:00
|
|
|
|
2022-10-26 20:37:48 +00:00
|
|
|
wl->requested_decoration = 0;
|
|
|
|
|
2021-08-11 02:17:42 +00:00
|
|
|
if (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE) {
|
|
|
|
MP_VERBOSE(wl, "Enabling server decorations\n");
|
|
|
|
} else {
|
|
|
|
MP_VERBOSE(wl, "Disabling server decorations\n");
|
|
|
|
}
|
2022-10-22 09:50:18 +00:00
|
|
|
opts->border = mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE;
|
2021-08-11 02:17:42 +00:00
|
|
|
m_config_cache_write_opt(wl->vo_opts_cache, &opts->border);
|
2021-08-09 16:40:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static const struct zxdg_toplevel_decoration_v1_listener decoration_listener = {
|
|
|
|
configure_decorations,
|
|
|
|
};
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
static void pres_set_clockid(void *data, struct wp_presentation *pres,
|
2022-10-04 16:05:00 +00:00
|
|
|
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
|
|
|
|
2022-10-04 16:05:00 +00:00
|
|
|
if (clockid == CLOCK_MONOTONIC || clockid == CLOCK_MONOTONIC_RAW)
|
|
|
|
wl->use_present = true;
|
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
|
|
|
{
|
2023-01-14 01:28:46 +00:00
|
|
|
struct vo_wayland_feedback_pool *fback_pool = data;
|
|
|
|
struct vo_wayland_state *wl = fback_pool->wl;
|
2018-11-10 18:45:58 +00:00
|
|
|
|
2023-01-14 01:28:46 +00:00
|
|
|
if (fback)
|
|
|
|
remove_feedback(fback_pool, fback);
|
2021-06-26 21:12:03 +00:00
|
|
|
|
|
|
|
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
|
2023-03-27 20:42:17 +00:00
|
|
|
// - these values are updated every time the compositor receives feedback.
|
2021-06-26 21:12:03 +00:00
|
|
|
|
|
|
|
int64_t sec = (uint64_t) tv_sec_lo + ((uint64_t) tv_sec_hi << 32);
|
2022-06-10 16:49:28 +00:00
|
|
|
int64_t ust = sec * 1000000LL + (uint64_t) tv_nsec / 1000;
|
|
|
|
int64_t msc = (uint64_t) seq_lo + ((uint64_t) seq_hi << 32);
|
|
|
|
present_update_sync_values(wl->present, ust, msc);
|
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
|
|
|
{
|
2023-01-14 01:28:46 +00:00
|
|
|
struct vo_wayland_feedback_pool *fback_pool = data;
|
|
|
|
if (fback)
|
|
|
|
remove_feedback(fback_pool, fback);
|
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
|
|
|
|
2023-02-17 10:13:30 +00:00
|
|
|
wl->frame_callback = wl_surface_frame(wl->callback_surface);
|
2021-06-26 21:12:03 +00:00
|
|
|
wl_callback_add_listener(wl->frame_callback, &frame_listener, wl);
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2023-01-01 03:06:38 +00:00
|
|
|
if (wl->use_present) {
|
2023-02-17 10:13:30 +00:00
|
|
|
struct wp_presentation_feedback *fback = wp_presentation_feedback(wl->presentation, wl->callback_surface);
|
2023-01-14 01:28:46 +00:00
|
|
|
add_feedback(wl->fback_pool, fback);
|
|
|
|
wp_presentation_feedback_add_listener(fback, &feedback_listener, wl->fback_pool);
|
2021-06-26 21:12:03 +00:00
|
|
|
}
|
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,
|
|
|
|
};
|
|
|
|
|
2022-07-07 10:03:21 +00:00
|
|
|
static void done(void *data,
|
|
|
|
struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1)
|
2022-05-30 17:58:57 +00:00
|
|
|
{
|
2022-05-18 14:35:53 +00:00
|
|
|
}
|
|
|
|
|
2022-07-07 10:03:21 +00:00
|
|
|
static void format_table(void *data,
|
|
|
|
struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1,
|
|
|
|
int32_t fd,
|
|
|
|
uint32_t size)
|
|
|
|
{
|
|
|
|
struct vo_wayland_state *wl = data;
|
|
|
|
|
|
|
|
void *map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
|
|
|
close(fd);
|
|
|
|
|
|
|
|
if (map != MAP_FAILED) {
|
|
|
|
wl->format_map = map;
|
|
|
|
wl->format_size = size;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void main_device(void *data,
|
|
|
|
struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1,
|
|
|
|
struct wl_array *device)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tranche_done(void *data,
|
|
|
|
struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tranche_target_device(void *data,
|
|
|
|
struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1,
|
|
|
|
struct wl_array *device)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tranche_formats(void *data,
|
|
|
|
struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1,
|
|
|
|
struct wl_array *indices)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static void tranche_flags(void *data,
|
|
|
|
struct zwp_linux_dmabuf_feedback_v1 *zwp_linux_dmabuf_feedback_v1,
|
|
|
|
uint32_t flags)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct zwp_linux_dmabuf_feedback_v1_listener dmabuf_feedback_listener = {
|
|
|
|
done,
|
|
|
|
format_table,
|
|
|
|
main_device,
|
|
|
|
tranche_done,
|
|
|
|
tranche_target_device,
|
|
|
|
tranche_formats,
|
|
|
|
tranche_flags,
|
2022-05-18 14:35:53 +00:00
|
|
|
};
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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;
|
|
|
|
|
2022-04-27 19:03:34 +00:00
|
|
|
if (!strcmp(interface, wl_compositor_interface.name) && (ver >= 4) && found++) {
|
2023-02-08 14:00:08 +00:00
|
|
|
#ifdef HAVE_WAYLAND_1_22
|
|
|
|
ver = MPMIN(ver, 6); /* Cap at 6 in case new events are added later. */
|
|
|
|
#else
|
|
|
|
ver = 4;
|
|
|
|
#endif
|
|
|
|
wl->compositor = wl_registry_bind(reg, id, &wl_compositor_interface, ver);
|
2021-06-26 21:12:03 +00:00
|
|
|
wl->surface = wl_compositor_create_surface(wl->compositor);
|
2022-05-18 14:35:53 +00:00
|
|
|
wl->video_surface = wl_compositor_create_surface(wl->compositor);
|
|
|
|
/* never accept input events on the video surface */
|
2022-05-30 17:58:57 +00:00
|
|
|
struct wl_region *region = wl_compositor_create_region(wl->compositor);
|
|
|
|
wl_surface_set_input_region(wl->video_surface, region);
|
|
|
|
wl_region_destroy(region);
|
2021-06-26 21:12:03 +00:00
|
|
|
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
|
|
|
|
2022-05-18 14:35:53 +00:00
|
|
|
if (!strcmp(interface, wl_subcompositor_interface.name) && (ver >= 1) && found++) {
|
|
|
|
wl->subcompositor = wl_registry_bind(reg, id, &wl_subcompositor_interface, 1);
|
|
|
|
}
|
|
|
|
|
2022-07-07 10:03:21 +00:00
|
|
|
if (!strcmp (interface, zwp_linux_dmabuf_v1_interface.name) && (ver >= 4) && found++) {
|
|
|
|
wl->dmabuf = wl_registry_bind(reg, id, &zwp_linux_dmabuf_v1_interface, 4);
|
|
|
|
wl->dmabuf_feedback = zwp_linux_dmabuf_v1_get_default_feedback(wl->dmabuf);
|
|
|
|
zwp_linux_dmabuf_feedback_v1_add_listener(wl->dmabuf_feedback, &dmabuf_feedback_listener, wl);
|
2022-05-18 14:35:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!strcmp (interface, wp_viewporter_interface.name) && (ver >= 1) && found++) {
|
|
|
|
wl->viewporter = wl_registry_bind (reg, id, &wp_viewporter_interface, 1);
|
|
|
|
}
|
|
|
|
|
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;
|
2022-04-08 15:19:39 +00:00
|
|
|
output->name = "";
|
2019-12-30 21:22:02 +00:00
|
|
|
|
2022-04-08 15:19:39 +00:00
|
|
|
ver = MPMIN(ver, 4); /* Cap at 4 in case new events are added later. */
|
|
|
|
output->output = wl_registry_bind(reg, id, &wl_output_interface, ver);
|
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
|
|
|
|
wayland: add support for content-type protocol
The content-type protocol allows mpv to send compositor a hint about the
type of content being displayed on its surface so it could potentially
make some sort of optimization. Fundamentally, this is pretty simple but
since this requires a very new wayland-protocols version (1.27), we have
to mess with the build to add a new define and add a bunch of if's in
here. The protocol itself exposes 4 different types of content: none,
photo, video, and game.
To do that, let's add a new option (wayland-content-type) that lets
users control what hint to send to the compossitor. Since the previous
commit adds a VOCTRL that notifies us about the content being displayed,
we can also add an auto value to this option. As you'd expect, the
compositor hint would be set to photo if mpv's core detects an image,
video for other things, and it is set to none for the special case of
forcing a window when there is not a video track. For completion's sake,
game is also allowed as a value for this option, but in practice there
shouldn't be a reason to use that.
2022-11-15 21:51:45 +00:00
|
|
|
#if HAVE_WAYLAND_PROTOCOLS_1_27
|
|
|
|
if (!strcmp(interface, wp_content_type_manager_v1_interface.name) && found++) {
|
|
|
|
wl->content_type_manager = wl_registry_bind(reg, id, &wp_content_type_manager_v1_interface, 1);
|
|
|
|
}
|
2022-11-16 01:52:56 +00:00
|
|
|
|
|
|
|
if (!strcmp(interface, wp_single_pixel_buffer_manager_v1_interface.name) && found++) {
|
|
|
|
wl->single_pixel_manager = wl_registry_bind(reg, id, &wp_single_pixel_buffer_manager_v1_interface, 1);
|
|
|
|
}
|
wayland: add support for content-type protocol
The content-type protocol allows mpv to send compositor a hint about the
type of content being displayed on its surface so it could potentially
make some sort of optimization. Fundamentally, this is pretty simple but
since this requires a very new wayland-protocols version (1.27), we have
to mess with the build to add a new define and add a bunch of if's in
here. The protocol itself exposes 4 different types of content: none,
photo, video, and game.
To do that, let's add a new option (wayland-content-type) that lets
users control what hint to send to the compossitor. Since the previous
commit adds a VOCTRL that notifies us about the content being displayed,
we can also add an auto value to this option. As you'd expect, the
compositor hint would be set to photo if mpv's core detects an image,
video for other things, and it is set to none for the special case of
forcing a window when there is not a video track. For completion's sake,
game is also allowed as a value for this option, but in practice there
shouldn't be a reason to use that.
2022-11-15 21:51:45 +00:00
|
|
|
#endif
|
|
|
|
|
2023-01-23 20:16:43 +00:00
|
|
|
#if HAVE_WAYLAND_PROTOCOLS_1_31
|
|
|
|
if (!strcmp(interface, wp_fractional_scale_manager_v1_interface.name) && found++) {
|
|
|
|
wl->fractional_scale_manager = wl_registry_bind(reg, id, &wp_fractional_scale_manager_v1_interface, 1);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
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++) {
|
2022-05-31 13:36:15 +00:00
|
|
|
ver = MPMIN(ver, 4); /* Cap at 4 in case new events are added later. */
|
2021-06-26 21:12:03 +00:00
|
|
|
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
|
|
|
|
2023-06-27 05:50:14 +00:00
|
|
|
struct pollfd fdp = { wl->dnd_fd, POLLIN | POLLHUP, 0 };
|
2021-06-26 21:12:03 +00:00
|
|
|
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:
|
wayland: partially fix drag and drop handling
Drag and drop in wayland is weird and it seems everyone does this
slightly differently (fun). In the past, there was a crash that
occured (fixed by 700f4ef5fad353800fa866b059663bc1dd58d3b7) which
involved using the wl_data_offer_finish in an incorrect way that
triggered a protocol error (always fatal). The fix involved moving the
function call to data_device_handle_drop which seemingly works, but it
has an unfortunate side effect. It appears like GTK applications (or at
least firefox) close the pipe after this function is called which makes
it impossible for mpv to read data from the fd (well you could force it
open again in theory but let's not do that). Who knows if that was the
case when that commit was made (probably not because I'd think I would
have noticed; could just be a dummy though), but obviously having broken
dnd for a major application isn't so fun (this works with QT and
chromium anyway).
Ideally one would just simply check the pipe in data_device_handle_drop,
but this doesn't work because it doesn't seem the compositor actually
sends mpv the data by then. There's not actually a defined event when
you're supposed to be able to read data from this pipe, so we wait for
the usual event checking loop later for this. In that case,
wl_data_offer_finish needs to go back into check_dnd_fd, but we have to
be careful when calling it otherwise we'd just commit protocol errors
like before. We check to make sure we even have a valid wl->dnd_offer
before trying to indicate that it is finished and additionally make sure
there is a valid dnd_action (after checking the fd, it's always set back
to -1).
This doesn't fix everything though. Specifically, sway with
focus_follows_mouse (the default) and GTK as the data source still
doesn't work. The reason is that when you do a drag and drop in sway
with that option on, a new wl_data_device.data_offer event is sent out
instantly after the drop event. This happens before any data is sent
across the fd and before mpv even has a chance to check it. What GTK
does, when getting this new data_offer event, is close the pipe
(POLLHUP). This means mpv can't read it when we reach the event loop and
thus no data is ever read and broken drag and drop. From the client
side, this isn't really fixable since the wayland protocol doesn't have
a clear indication of when clients are supposed to read from the fd and
both the compositor and data source are doing things totally out of our
control. So we'll consider this weird case, "not our bug" at least. The
rest should work.
2022-02-03 16:48:56 +00:00
|
|
|
if (wl->dnd_mime_type)
|
|
|
|
talloc_free(wl->dnd_mime_type);
|
|
|
|
|
|
|
|
if (wl->dnd_action >= 0 && wl->dnd_offer)
|
|
|
|
wl_data_offer_finish(wl->dnd_offer);
|
|
|
|
|
|
|
|
wl->dnd_action = -1;
|
2021-06-26 21:12:03 +00:00
|
|
|
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
|
|
|
|
2023-03-07 17:16:45 +00:00
|
|
|
static int check_for_resize(struct vo_wayland_state *wl, int edge_pixels,
|
|
|
|
enum xdg_toplevel_resize_edge *edge)
|
2021-06-26 21:12:03 +00:00
|
|
|
{
|
2021-08-08 03:05:05 +00:00
|
|
|
if (wl->vo_opts->fullscreen || wl->vo_opts->window_maximized)
|
2021-06-26 21:12:03 +00:00
|
|
|
return 0;
|
2013-02-28 18:55:02 +00:00
|
|
|
|
2023-03-07 17:16:45 +00:00
|
|
|
int pos[2] = { wl->mouse_x, wl->mouse_y };
|
2021-06-26 21:12:03 +00:00
|
|
|
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
|
|
|
|
2023-01-23 20:16:43 +00:00
|
|
|
static int create_viewports(struct vo_wayland_state *wl)
|
|
|
|
{
|
|
|
|
if (wl->viewporter) {
|
|
|
|
wl->viewport = wp_viewporter_get_viewport(wl->viewporter, wl->surface);
|
|
|
|
wl->video_viewport = wp_viewporter_get_viewport(wl->viewporter, wl->video_surface);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wl->viewporter && (!wl->viewport || !wl->video_viewport)) {
|
|
|
|
MP_ERR(wl, "failed to create viewport interfaces!\n");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2023-01-23 20:16:43 +00:00
|
|
|
if (!wl->xdg_surface || !wl->xdg_toplevel) {
|
2023-03-27 20:42:17 +00:00
|
|
|
MP_ERR(wl, "failed to create xdg_surface and xdg_toplevel!\n");
|
2021-06-26 21:12:03 +00:00
|
|
|
return 1;
|
2023-01-23 20:16:43 +00:00
|
|
|
}
|
2021-06-26 21:12:03 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2019-05-13 13:47:13 +00:00
|
|
|
|
2023-01-14 01:28:46 +00:00
|
|
|
static void add_feedback(struct vo_wayland_feedback_pool *fback_pool,
|
|
|
|
struct wp_presentation_feedback *fback)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < fback_pool->len; ++i) {
|
|
|
|
if (!fback_pool->fback[i]) {
|
|
|
|
fback_pool->fback[i] = fback;
|
|
|
|
break;
|
|
|
|
} else if (i == fback_pool->len - 1) {
|
|
|
|
// Shouldn't happen in practice.
|
|
|
|
wp_presentation_feedback_destroy(fback_pool->fback[i]);
|
|
|
|
fback_pool->fback[i] = fback;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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) {
|
2022-05-13 18:48:46 +00:00
|
|
|
if (output->has_surface) {
|
|
|
|
char *name = output->name ? output->name : output->model;
|
2021-06-26 21:12:03 +00:00
|
|
|
MP_TARRAY_APPEND(NULL, names, displays_spanned,
|
2022-05-13 18:48:46 +00:00
|
|
|
talloc_strdup(NULL, name));
|
|
|
|
}
|
2021-06-26 21:12:03 +00:00
|
|
|
}
|
|
|
|
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
|
|
|
|
2022-05-12 21:26:49 +00:00
|
|
|
static void guess_focus(struct vo_wayland_state *wl)
|
|
|
|
{
|
2023-05-17 21:13:04 +00:00
|
|
|
// We can't actually know if the window is focused or not in wayland,
|
|
|
|
// so just guess it with some common sense. Obviously won't work if
|
|
|
|
// the user has no keyboard.
|
|
|
|
if ((!wl->focused && wl->activated && wl->has_keyboard_input) ||
|
|
|
|
(wl->focused && !wl->activated))
|
|
|
|
{
|
|
|
|
wl->focused = !wl->focused;
|
|
|
|
wl->pending_vo_events |= VO_EVENT_FOCUS;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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;
|
2022-04-08 15:19:39 +00:00
|
|
|
if (screen_id == -1 && screen_name && !strcmp(screen_name, output->name))
|
|
|
|
return output;
|
2021-06-26 21:12:03 +00:00
|
|
|
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 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;
|
|
|
|
}
|
|
|
|
|
2021-08-11 02:17:42 +00:00
|
|
|
static void request_decoration_mode(struct vo_wayland_state *wl, uint32_t mode)
|
|
|
|
{
|
|
|
|
wl->requested_decoration = mode;
|
|
|
|
zxdg_toplevel_decoration_v1_set_mode(wl->xdg_toplevel_decoration, mode);
|
|
|
|
}
|
|
|
|
|
2023-01-23 20:16:43 +00:00
|
|
|
static void rescale_geometry(struct vo_wayland_state *wl, double old_scale)
|
|
|
|
{
|
|
|
|
double factor = old_scale / wl->scaling;
|
|
|
|
wl->window_size.x1 /= factor;
|
|
|
|
wl->window_size.y1 /= factor;
|
|
|
|
wl->geometry.x1 /= factor;
|
|
|
|
wl->geometry.y1 /= factor;
|
|
|
|
}
|
|
|
|
|
2023-01-14 01:28:46 +00:00
|
|
|
static void clean_feedback_pool(struct vo_wayland_feedback_pool *fback_pool)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < fback_pool->len; ++i) {
|
|
|
|
if (fback_pool->fback[i]) {
|
|
|
|
wp_presentation_feedback_destroy(fback_pool->fback[i]);
|
|
|
|
fback_pool->fback[i] = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void remove_feedback(struct vo_wayland_feedback_pool *fback_pool,
|
|
|
|
struct wp_presentation_feedback *fback)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < fback_pool->len; ++i) {
|
|
|
|
if (fback_pool->fback[i] == fback) {
|
|
|
|
wp_presentation_feedback_destroy(fback);
|
|
|
|
fback_pool->fback[i] = NULL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
wayland: add support for content-type protocol
The content-type protocol allows mpv to send compositor a hint about the
type of content being displayed on its surface so it could potentially
make some sort of optimization. Fundamentally, this is pretty simple but
since this requires a very new wayland-protocols version (1.27), we have
to mess with the build to add a new define and add a bunch of if's in
here. The protocol itself exposes 4 different types of content: none,
photo, video, and game.
To do that, let's add a new option (wayland-content-type) that lets
users control what hint to send to the compossitor. Since the previous
commit adds a VOCTRL that notifies us about the content being displayed,
we can also add an auto value to this option. As you'd expect, the
compositor hint would be set to photo if mpv's core detects an image,
video for other things, and it is set to none for the special case of
forcing a window when there is not a video track. For completion's sake,
game is also allowed as a value for this option, but in practice there
shouldn't be a reason to use that.
2022-11-15 21:51:45 +00:00
|
|
|
static void set_content_type(struct vo_wayland_state *wl)
|
|
|
|
{
|
|
|
|
if (!wl->content_type_manager)
|
|
|
|
return;
|
|
|
|
#if HAVE_WAYLAND_PROTOCOLS_1_27
|
|
|
|
// handle auto;
|
|
|
|
if (wl->vo_opts->content_type == -1) {
|
|
|
|
wp_content_type_v1_set_content_type(wl->content_type, wl->current_content_type);
|
|
|
|
} else {
|
|
|
|
wp_content_type_v1_set_content_type(wl->content_type, wl->vo_opts->content_type);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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);
|
2022-04-27 19:03:34 +00:00
|
|
|
wl_surface_damage_buffer(wl->cursor_surface, 0, 0, img->width, img->height);
|
2021-06-26 21:12:03 +00:00
|
|
|
wl_surface_commit(wl->cursor_surface);
|
|
|
|
} else {
|
|
|
|
wl_pointer_set_cursor(wl->pointer, wl->pointer_id, NULL, 0, 0);
|
|
|
|
}
|
|
|
|
return VO_TRUE;
|
|
|
|
}
|
|
|
|
|
2023-01-26 18:53:41 +00:00
|
|
|
static void set_geometry(struct vo_wayland_state *wl, bool resize)
|
2021-06-26 21:12:03 +00:00
|
|
|
{
|
|
|
|
struct vo *vo = wl->vo;
|
2023-01-26 18:53:41 +00:00
|
|
|
if (!wl->current_output)
|
|
|
|
return;
|
2021-06-26 21:12:03 +00:00
|
|
|
|
|
|
|
struct vo_win_geometry geo;
|
|
|
|
struct mp_rect screenrc = wl->current_output->geometry;
|
2023-01-22 23:06:15 +00:00
|
|
|
vo_calc_window_geometry2(vo, &screenrc, wl->scaling, &geo);
|
2021-06-26 21:12:03 +00:00
|
|
|
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;
|
|
|
|
|
2023-01-22 23:06:15 +00:00
|
|
|
wl->window_size = (struct mp_rect){0, 0, vo->dwidth, vo->dheight};
|
2023-01-26 18:53:41 +00:00
|
|
|
|
|
|
|
if (resize) {
|
|
|
|
if (!wl->vo_opts->fullscreen && !wl->vo_opts->window_maximized)
|
|
|
|
wl->geometry = wl->window_size;
|
|
|
|
wl->pending_vo_events |= VO_EVENT_RESIZE;
|
|
|
|
}
|
2021-06-26 21:12:03 +00:00
|
|
|
}
|
|
|
|
|
2022-05-12 21:26:49 +00:00
|
|
|
static void set_input_region(struct vo_wayland_state *wl, bool passthrough)
|
|
|
|
{
|
|
|
|
if (passthrough) {
|
|
|
|
struct wl_region *region = wl_compositor_create_region(wl->compositor);
|
|
|
|
wl_surface_set_input_region(wl->surface, region);
|
|
|
|
wl_region_destroy(region);
|
|
|
|
} else {
|
|
|
|
wl_surface_set_input_region(wl->surface, NULL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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)
|
|
|
|
{
|
2023-01-23 20:16:43 +00:00
|
|
|
if (wl->fractional_scale_manager)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// dmabuf_wayland is always wl->scaling = 1
|
|
|
|
double old_scale = wl->scaling;
|
2023-02-28 09:39:50 +00:00
|
|
|
wl->scaling = !wl->using_dmabuf_wayland ? wl->current_output->scale : 1;
|
2021-06-28 16:03:51 +00:00
|
|
|
|
2023-01-23 20:16:43 +00:00
|
|
|
rescale_geometry(wl, old_scale);
|
2021-11-08 15:44:22 +00:00
|
|
|
wl_surface_set_buffer_scale(wl->surface, wl->scaling);
|
2021-06-28 16:03:51 +00:00
|
|
|
}
|
|
|
|
|
2022-05-31 13:36:15 +00:00
|
|
|
static void set_window_bounds(struct vo_wayland_state *wl)
|
|
|
|
{
|
2023-01-26 20:21:58 +00:00
|
|
|
// If the user has set geometry/autofit and the option is auto,
|
|
|
|
// don't use these.
|
|
|
|
if (wl->opts->configure_bounds == -1 && (wl->vo_opts->geometry.wh_valid ||
|
|
|
|
wl->vo_opts->autofit.wh_valid || wl->vo_opts->autofit_larger.wh_valid ||
|
|
|
|
wl->vo_opts->autofit_smaller.wh_valid))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-05-31 13:36:15 +00:00
|
|
|
if (wl->bounded_width && wl->bounded_width < wl->window_size.x1)
|
|
|
|
wl->window_size.x1 = wl->bounded_width;
|
|
|
|
if (wl->bounded_height && wl->bounded_height < wl->window_size.y1)
|
|
|
|
wl->window_size.y1 = wl->bounded_height;
|
|
|
|
}
|
|
|
|
|
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);
|
|
|
|
|
2021-08-27 23:04:14 +00:00
|
|
|
const char *xcursor_theme = getenv("XCURSOR_THEME");
|
2021-06-26 21:12:03 +00:00
|
|
|
const char *size_str = getenv("XCURSOR_SIZE");
|
2021-08-27 23:11:50 +00:00
|
|
|
int size = 24;
|
2021-06-26 21:12:03 +00:00
|
|
|
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-08-27 23:04:14 +00:00
|
|
|
wl->cursor_theme = wl_cursor_theme_load(xcursor_theme, size*wl->scaling, wl->shm);
|
2021-06-26 21:12:03 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
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;
|
2022-01-19 01:45:27 +00:00
|
|
|
/* The xdg-shell protocol requires that the title is UTF-8. */
|
|
|
|
void *tmp = talloc_new(NULL);
|
|
|
|
struct bstr b_title = bstr_sanitize_utf8_latin1(tmp, bstr0(title));
|
2022-11-09 14:03:18 +00:00
|
|
|
xdg_toplevel_set_title(wl->xdg_toplevel, bstrto0(tmp, b_title));
|
2022-01-19 01:45:27 +00:00
|
|
|
talloc_free(tmp);
|
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
|
|
|
|
2023-01-01 02:51:13 +00:00
|
|
|
static void wayland_dispatch_events(struct vo_wayland_state *wl, int nfds, int timeout)
|
2021-06-26 21:12:03 +00:00
|
|
|
{
|
2023-01-01 17:16:31 +00:00
|
|
|
if (wl->display_fd == -1)
|
|
|
|
return;
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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-11-25 00:40:31 +00:00
|
|
|
if (fds[0].revents & POLLIN) {
|
|
|
|
wl_display_read_events(wl->display);
|
|
|
|
} else {
|
|
|
|
wl_display_cancel_read(wl->display);
|
|
|
|
}
|
|
|
|
|
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");
|
|
|
|
close(wl->display_fd);
|
|
|
|
wl->display_fd = -1;
|
|
|
|
mp_input_put_key(wl->vo->input_ctx, MP_KEY_CLOSE_WIN);
|
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]);
|
2021-11-25 00:40:31 +00:00
|
|
|
|
|
|
|
wl_display_dispatch_pending(wl->display);
|
2013-02-28 18:55:02 +00:00
|
|
|
}
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
/* Non-static */
|
2022-05-30 17:19:23 +00:00
|
|
|
int vo_wayland_allocate_memfd(struct vo *vo, size_t size)
|
|
|
|
{
|
|
|
|
#if !HAVE_MEMFD_CREATE
|
|
|
|
return VO_ERROR;
|
|
|
|
#else
|
|
|
|
int fd = memfd_create("mpv", MFD_CLOEXEC | MFD_ALLOW_SEALING);
|
|
|
|
if (fd < 0) {
|
|
|
|
MP_ERR(vo, "Failed to allocate memfd: %s\n", mp_strerror(errno));
|
|
|
|
return VO_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
|
|
|
|
|
|
|
|
if (posix_fallocate(fd, 0, size) == 0)
|
|
|
|
return fd;
|
|
|
|
|
|
|
|
close(fd);
|
|
|
|
MP_ERR(vo, "Failed to allocate memfd: %s\n", mp_strerror(errno));
|
|
|
|
|
|
|
|
return VO_ERROR;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2022-04-07 04:01:21 +00:00
|
|
|
bool vo_wayland_check_visible(struct vo *vo)
|
|
|
|
{
|
|
|
|
struct vo_wayland_state *wl = vo->wl;
|
2022-11-11 19:57:53 +00:00
|
|
|
bool render = !wl->hidden || wl->vo_opts->force_render;
|
2022-04-07 04:01:21 +00:00
|
|
|
wl->frame_wait = true;
|
|
|
|
return render;
|
|
|
|
}
|
|
|
|
|
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;
|
2022-02-02 21:36:05 +00:00
|
|
|
if (*events & VO_EVENT_RESIZE) {
|
|
|
|
*events |= VO_EVENT_EXPOSE;
|
|
|
|
wl->frame_wait = false;
|
|
|
|
wl->timeout_count = 0;
|
|
|
|
wl->hidden = false;
|
|
|
|
}
|
2017-10-09 01:08:36 +00:00
|
|
|
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);
|
2021-08-11 02:17:42 +00:00
|
|
|
if (opt == &opts->border)
|
|
|
|
{
|
|
|
|
// This is stupid but the value of border shouldn't be written
|
|
|
|
// unless we get a configure event. Change it back to its old
|
|
|
|
// value and let configure_decorations handle it after the request.
|
|
|
|
if (wl->xdg_toplevel_decoration) {
|
2022-10-22 16:40:37 +00:00
|
|
|
int requested_border_mode = opts->border;
|
2021-08-11 02:17:42 +00:00
|
|
|
opts->border = !opts->border;
|
|
|
|
m_config_cache_write_opt(wl->vo_opts_cache,
|
|
|
|
&opts->border);
|
2022-10-22 09:50:18 +00:00
|
|
|
request_decoration_mode(
|
2022-10-22 16:40:37 +00:00
|
|
|
wl, requested_border_mode ?
|
2022-10-22 09:50:18 +00:00
|
|
|
ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE :
|
|
|
|
ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE);
|
2021-08-11 02:17:42 +00:00
|
|
|
} else {
|
|
|
|
opts->border = false;
|
|
|
|
m_config_cache_write_opt(wl->vo_opts_cache,
|
|
|
|
&wl->vo_opts->border);
|
|
|
|
}
|
|
|
|
}
|
wayland: add support for content-type protocol
The content-type protocol allows mpv to send compositor a hint about the
type of content being displayed on its surface so it could potentially
make some sort of optimization. Fundamentally, this is pretty simple but
since this requires a very new wayland-protocols version (1.27), we have
to mess with the build to add a new define and add a bunch of if's in
here. The protocol itself exposes 4 different types of content: none,
photo, video, and game.
To do that, let's add a new option (wayland-content-type) that lets
users control what hint to send to the compossitor. Since the previous
commit adds a VOCTRL that notifies us about the content being displayed,
we can also add an auto value to this option. As you'd expect, the
compositor hint would be set to photo if mpv's core detects an image,
video for other things, and it is set to none for the special case of
forcing a window when there is not a video track. For completion's sake,
game is also allowed as a value for this option, but in practice there
shouldn't be a reason to use that.
2022-11-15 21:51:45 +00:00
|
|
|
if (opt == &opts->content_type)
|
|
|
|
set_content_type(wl);
|
2022-05-12 21:26:49 +00:00
|
|
|
if (opt == &opts->cursor_passthrough)
|
|
|
|
set_input_region(wl, opts->cursor_passthrough);
|
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)
|
2023-01-26 19:17:39 +00:00
|
|
|
set_geometry(wl, 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)
|
|
|
|
{
|
2023-01-26 18:53:41 +00:00
|
|
|
set_geometry(wl, true);
|
2020-12-13 21:03:34 +00:00
|
|
|
}
|
2019-11-30 02:00:49 +00:00
|
|
|
}
|
|
|
|
return VO_TRUE;
|
|
|
|
}
|
wayland: add support for content-type protocol
The content-type protocol allows mpv to send compositor a hint about the
type of content being displayed on its surface so it could potentially
make some sort of optimization. Fundamentally, this is pretty simple but
since this requires a very new wayland-protocols version (1.27), we have
to mess with the build to add a new define and add a bunch of if's in
here. The protocol itself exposes 4 different types of content: none,
photo, video, and game.
To do that, let's add a new option (wayland-content-type) that lets
users control what hint to send to the compossitor. Since the previous
commit adds a VOCTRL that notifies us about the content being displayed,
we can also add an auto value to this option. As you'd expect, the
compositor hint would be set to photo if mpv's core detects an image,
video for other things, and it is set to none for the special case of
forcing a window when there is not a video track. For completion's sake,
game is also allowed as a value for this option, but in practice there
shouldn't be a reason to use that.
2022-11-15 21:51:45 +00:00
|
|
|
case VOCTRL_CONTENT_TYPE: {
|
|
|
|
#if HAVE_WAYLAND_PROTOCOLS_1_27
|
2023-02-25 04:53:42 +00:00
|
|
|
wl->current_content_type = *(enum mp_content_type *)arg;
|
wayland: add support for content-type protocol
The content-type protocol allows mpv to send compositor a hint about the
type of content being displayed on its surface so it could potentially
make some sort of optimization. Fundamentally, this is pretty simple but
since this requires a very new wayland-protocols version (1.27), we have
to mess with the build to add a new define and add a bunch of if's in
here. The protocol itself exposes 4 different types of content: none,
photo, video, and game.
To do that, let's add a new option (wayland-content-type) that lets
users control what hint to send to the compossitor. Since the previous
commit adds a VOCTRL that notifies us about the content being displayed,
we can also add an auto value to this option. As you'd expect, the
compositor hint would be set to photo if mpv's core detects an image,
video for other things, and it is set to none for the special case of
forcing a window when there is not a video track. For completion's sake,
game is also allowed as a value for this option, but in practice there
shouldn't be a reason to use that.
2022-11-15 21:51:45 +00:00
|
|
|
set_content_type(wl);
|
|
|
|
#endif
|
|
|
|
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;
|
2021-09-13 18:45:17 +00:00
|
|
|
if (wl->vo_opts->window_maximized) {
|
2023-01-22 23:06:15 +00:00
|
|
|
s[0] = mp_rect_w(wl->geometry);
|
|
|
|
s[1] = mp_rect_h(wl->geometry);
|
2021-09-13 18:45:17 +00:00
|
|
|
} else {
|
2023-01-22 23:06:15 +00:00
|
|
|
s[0] = mp_rect_w(wl->window_size);
|
|
|
|
s[1] = mp_rect_h(wl->window_size);
|
2021-09-13 18:45:17 +00:00
|
|
|
}
|
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;
|
2023-01-22 23:06:15 +00:00
|
|
|
wl->window_size.x1 = s[0];
|
|
|
|
wl->window_size.y1 = s[1];
|
2021-08-07 23:07:09 +00:00
|
|
|
if (!wl->vo_opts->fullscreen) {
|
|
|
|
if (wl->vo_opts->window_maximized) {
|
|
|
|
xdg_toplevel_unset_maximized(wl->xdg_toplevel);
|
|
|
|
wl_display_dispatch_pending(wl->display);
|
|
|
|
/* Make sure the compositor let us unmaximize */
|
|
|
|
if (wl->vo_opts->window_maximized)
|
|
|
|
return VO_TRUE;
|
|
|
|
}
|
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;
|
|
|
|
}
|
|
|
|
|
2023-01-23 20:16:43 +00:00
|
|
|
void vo_wayland_handle_fractional_scale(struct vo_wayland_state *wl)
|
|
|
|
{
|
|
|
|
if (wl->fractional_scale_manager && wl->viewport)
|
|
|
|
wp_viewport_set_destination(wl->viewport,
|
|
|
|
round(mp_rect_w(wl->geometry) / wl->scaling),
|
|
|
|
round(mp_rect_h(wl->geometry) / wl->scaling));
|
|
|
|
}
|
|
|
|
|
2023-01-08 19:16:47 +00:00
|
|
|
bool vo_wayland_init(struct vo *vo)
|
2021-06-26 21:12:03 +00:00
|
|
|
{
|
|
|
|
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"),
|
2022-05-31 13:36:15 +00:00
|
|
|
.bounded_width = 0,
|
|
|
|
.bounded_height = 0,
|
2022-06-10 16:49:28 +00:00
|
|
|
.refresh_interval = 0,
|
2021-06-26 21:12:03 +00:00
|
|
|
.scaling = 1,
|
|
|
|
.wakeup_pipe = {-1, -1},
|
2023-01-01 17:16:31 +00:00
|
|
|
.display_fd = -1,
|
2021-06-26 21:12:03 +00:00
|
|
|
.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;
|
2023-02-28 09:39:50 +00:00
|
|
|
wl->using_dmabuf_wayland = !strcmp(wl->vo->driver->name, "dmabuf-wayland");
|
2021-06-26 21:12:03 +00:00
|
|
|
|
|
|
|
wl_list_init(&wl->output_list);
|
|
|
|
|
|
|
|
if (!wl->display)
|
2023-01-08 19:31:03 +00:00
|
|
|
goto err;
|
2021-06-26 21:12:03 +00:00
|
|
|
|
|
|
|
if (create_input(wl))
|
2023-01-08 19:31:03 +00:00
|
|
|
goto err;
|
2021-06-26 21:12:03 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2022-11-15 01:06:14 +00:00
|
|
|
if (!wl->surface) {
|
|
|
|
MP_FATAL(wl, "Compositor doesn't support %s (ver. 4)\n",
|
|
|
|
wl_compositor_interface.name);
|
2023-01-08 19:31:03 +00:00
|
|
|
goto err;
|
2022-11-15 01:06:14 +00:00
|
|
|
}
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
if (!wl->wm_base) {
|
|
|
|
MP_FATAL(wl, "Compositor doesn't support the required %s protocol!\n",
|
|
|
|
xdg_wm_base_interface.name);
|
2023-01-08 19:31:03 +00:00
|
|
|
goto err;
|
2021-06-26 21:12:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2023-01-08 19:31:03 +00:00
|
|
|
goto err;
|
2021-06-26 21:12:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Can't be initialized during registry due to multi-protocol dependence */
|
2023-01-23 20:16:43 +00:00
|
|
|
if (create_viewports(wl))
|
|
|
|
goto err;
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
if (create_xdg_surface(wl))
|
2023-01-08 19:31:03 +00:00
|
|
|
goto err;
|
2021-06-26 21:12:03 +00:00
|
|
|
|
2022-05-28 20:13:23 +00:00
|
|
|
if (wl->subcompositor) {
|
|
|
|
wl->video_subsurface = wl_subcompositor_get_subsurface(wl->subcompositor, wl->video_surface, wl->surface);
|
|
|
|
wl_subsurface_set_desync(wl->video_subsurface);
|
|
|
|
}
|
|
|
|
|
wayland: add support for content-type protocol
The content-type protocol allows mpv to send compositor a hint about the
type of content being displayed on its surface so it could potentially
make some sort of optimization. Fundamentally, this is pretty simple but
since this requires a very new wayland-protocols version (1.27), we have
to mess with the build to add a new define and add a bunch of if's in
here. The protocol itself exposes 4 different types of content: none,
photo, video, and game.
To do that, let's add a new option (wayland-content-type) that lets
users control what hint to send to the compossitor. Since the previous
commit adds a VOCTRL that notifies us about the content being displayed,
we can also add an auto value to this option. As you'd expect, the
compositor hint would be set to photo if mpv's core detects an image,
video for other things, and it is set to none for the special case of
forcing a window when there is not a video track. For completion's sake,
game is also allowed as a value for this option, but in practice there
shouldn't be a reason to use that.
2022-11-15 21:51:45 +00:00
|
|
|
#if HAVE_WAYLAND_PROTOCOLS_1_27
|
|
|
|
if (wl->content_type_manager) {
|
|
|
|
wl->content_type = wp_content_type_manager_v1_get_surface_content_type(wl->content_type_manager, wl->surface);
|
|
|
|
} else {
|
|
|
|
MP_VERBOSE(wl, "Compositor doesn't support the %s protocol!\n",
|
|
|
|
wp_content_type_manager_v1_interface.name);
|
|
|
|
}
|
2022-11-16 01:52:56 +00:00
|
|
|
|
|
|
|
if (!wl->single_pixel_manager) {
|
|
|
|
MP_VERBOSE(wl, "Compositor doesn't support the %s protocol!\n",
|
|
|
|
wp_single_pixel_buffer_manager_v1_interface.name);
|
|
|
|
}
|
wayland: add support for content-type protocol
The content-type protocol allows mpv to send compositor a hint about the
type of content being displayed on its surface so it could potentially
make some sort of optimization. Fundamentally, this is pretty simple but
since this requires a very new wayland-protocols version (1.27), we have
to mess with the build to add a new define and add a bunch of if's in
here. The protocol itself exposes 4 different types of content: none,
photo, video, and game.
To do that, let's add a new option (wayland-content-type) that lets
users control what hint to send to the compossitor. Since the previous
commit adds a VOCTRL that notifies us about the content being displayed,
we can also add an auto value to this option. As you'd expect, the
compositor hint would be set to photo if mpv's core detects an image,
video for other things, and it is set to none for the special case of
forcing a window when there is not a video track. For completion's sake,
game is also allowed as a value for this option, but in practice there
shouldn't be a reason to use that.
2022-11-15 21:51:45 +00:00
|
|
|
#endif
|
|
|
|
|
2023-01-23 20:16:43 +00:00
|
|
|
#if HAVE_WAYLAND_PROTOCOLS_1_31
|
|
|
|
if (wl->fractional_scale_manager) {
|
|
|
|
wl->fractional_scale = wp_fractional_scale_manager_v1_get_fractional_scale(wl->fractional_scale_manager, wl->surface);
|
|
|
|
wp_fractional_scale_v1_add_listener(wl->fractional_scale, &fractional_scale_listener, wl);
|
|
|
|
} else {
|
|
|
|
MP_VERBOSE(wl, "Compositor doesn't support the %s protocol!\n",
|
|
|
|
wp_fractional_scale_manager_v1_interface.name);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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) {
|
2023-01-14 01:28:46 +00:00
|
|
|
wl->fback_pool = talloc_zero(wl, struct vo_wayland_feedback_pool);
|
|
|
|
wl->fback_pool->wl = wl;
|
|
|
|
wl->fback_pool->len = 8; // max swapchain depth allowed
|
|
|
|
wl->fback_pool->fback = talloc_zero_array(wl->fback_pool, struct wp_presentation_feedback *,
|
|
|
|
wl->fback_pool->len);
|
2022-06-10 16:49:28 +00:00
|
|
|
wl->present = talloc_zero(wl, struct mp_present);
|
2021-06-26 21:12:03 +00:00
|
|
|
} 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);
|
2021-08-09 16:40:46 +00:00
|
|
|
zxdg_toplevel_decoration_v1_add_listener(wl->xdg_toplevel_decoration, &decoration_listener, wl);
|
2022-10-22 09:50:18 +00:00
|
|
|
request_decoration_mode(
|
|
|
|
wl, wl->vo_opts->border ?
|
|
|
|
ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE :
|
|
|
|
ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE);
|
2021-06-26 21:12:03 +00:00
|
|
|
} 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);
|
|
|
|
}
|
|
|
|
|
2023-01-16 17:29:44 +00:00
|
|
|
if (!wl->idle_inhibit_manager) {
|
2021-06-26 21:12:03 +00:00
|
|
|
MP_VERBOSE(wl, "Compositor doesn't support the %s protocol!\n",
|
|
|
|
zwp_idle_inhibit_manager_v1_interface.name);
|
|
|
|
|
2023-01-16 17:29:44 +00:00
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
wl->opts = mp_get_config_group(wl, wl->vo->global, &wayland_conf);
|
|
|
|
wl->display_fd = wl_display_get_fd(wl->display);
|
2022-06-07 15:23:25 +00:00
|
|
|
|
2022-06-11 15:09:00 +00:00
|
|
|
update_app_id(wl);
|
|
|
|
mp_make_wakeup_pipe(wl->wakeup_pipe);
|
|
|
|
|
2023-02-17 10:13:30 +00:00
|
|
|
wl->callback_surface = wl->using_dmabuf_wayland ? wl->video_surface : wl->surface;
|
|
|
|
wl->frame_callback = wl_surface_frame(wl->callback_surface);
|
2021-06-26 21:12:03 +00:00
|
|
|
wl_callback_add_listener(wl->frame_callback, &frame_listener, wl);
|
2022-06-07 15:23:25 +00:00
|
|
|
wl_surface_commit(wl->surface);
|
2021-06-26 21:12:03 +00:00
|
|
|
|
2022-10-27 02:44:23 +00:00
|
|
|
/* Do another roundtrip to ensure all of the above is initialized
|
|
|
|
* before mpv does anything else. */
|
|
|
|
wl_display_roundtrip(wl->display);
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
return true;
|
2023-01-08 19:31:03 +00:00
|
|
|
|
|
|
|
err:
|
|
|
|
vo_wayland_uninit(vo);
|
|
|
|
return false;
|
2015-08-19 19:41:26 +00:00
|
|
|
}
|
|
|
|
|
2023-01-08 20:02:13 +00:00
|
|
|
bool 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;
|
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;
|
2022-06-07 15:23:25 +00:00
|
|
|
set_surface_scaling(wl);
|
|
|
|
wl->pending_vo_events |= VO_EVENT_DPI;
|
2021-06-26 21:12:03 +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
|
|
|
|
2021-10-20 02:47:23 +00:00
|
|
|
if (wl->vo_opts->auto_window_resize || mp_rect_w(wl->geometry) == 0 ||
|
|
|
|
mp_rect_h(wl->geometry) == 0)
|
|
|
|
{
|
|
|
|
set_geometry(wl, false);
|
|
|
|
}
|
2021-06-26 21:12:03 +00:00
|
|
|
|
2022-05-31 13:36:15 +00:00
|
|
|
if (wl->opts->configure_bounds)
|
|
|
|
set_window_bounds(wl);
|
|
|
|
|
2022-06-07 15:23:25 +00:00
|
|
|
if ((!wl->vo_opts->fullscreen && !wl->vo_opts->window_maximized) ||
|
|
|
|
mp_rect_w(wl->geometry) == 0 || mp_rect_h(wl->geometry) == 0)
|
|
|
|
{
|
2021-06-26 21:12:03 +00:00
|
|
|
wl->geometry = wl->window_size;
|
2022-06-07 15:23:25 +00:00
|
|
|
}
|
2021-06-26 21:12:03 +00:00
|
|
|
|
2022-05-12 21:26:49 +00:00
|
|
|
if (wl->vo_opts->cursor_passthrough)
|
|
|
|
set_input_region(wl, true);
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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);
|
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2023-02-27 11:12:09 +00:00
|
|
|
void vo_wayland_set_opaque_region(struct vo_wayland_state *wl, bool alpha)
|
2020-10-05 15:28:37 +00:00
|
|
|
{
|
2023-01-22 23:06:15 +00:00
|
|
|
const int32_t width = mp_rect_w(wl->geometry);
|
|
|
|
const int32_t height = mp_rect_h(wl->geometry);
|
2020-10-05 15:28:37 +00:00
|
|
|
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_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);
|
|
|
|
|
2022-05-18 14:35:53 +00:00
|
|
|
if (wl->subcompositor)
|
|
|
|
wl_subcompositor_destroy(wl->subcompositor);
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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);
|
|
|
|
|
wayland: add support for content-type protocol
The content-type protocol allows mpv to send compositor a hint about the
type of content being displayed on its surface so it could potentially
make some sort of optimization. Fundamentally, this is pretty simple but
since this requires a very new wayland-protocols version (1.27), we have
to mess with the build to add a new define and add a bunch of if's in
here. The protocol itself exposes 4 different types of content: none,
photo, video, and game.
To do that, let's add a new option (wayland-content-type) that lets
users control what hint to send to the compossitor. Since the previous
commit adds a VOCTRL that notifies us about the content being displayed,
we can also add an auto value to this option. As you'd expect, the
compositor hint would be set to photo if mpv's core detects an image,
video for other things, and it is set to none for the special case of
forcing a window when there is not a video track. For completion's sake,
game is also allowed as a value for this option, but in practice there
shouldn't be a reason to use that.
2022-11-15 21:51:45 +00:00
|
|
|
#if HAVE_WAYLAND_PROTOCOLS_1_27
|
|
|
|
if (wl->content_type)
|
|
|
|
wp_content_type_v1_destroy(wl->content_type);
|
|
|
|
|
|
|
|
if (wl->content_type_manager)
|
|
|
|
wp_content_type_manager_v1_destroy(wl->content_type_manager);
|
|
|
|
#endif
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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);
|
|
|
|
|
2023-01-14 01:28:46 +00:00
|
|
|
if (wl->fback_pool)
|
|
|
|
clean_feedback_pool(wl->fback_pool);
|
2023-01-14 04:03:00 +00:00
|
|
|
|
2023-01-23 20:16:43 +00:00
|
|
|
#if HAVE_WAYLAND_PROTOCOLS_1_31
|
|
|
|
if (wl->fractional_scale)
|
|
|
|
wp_fractional_scale_v1_destroy(wl->fractional_scale);
|
|
|
|
|
|
|
|
if (wl->fractional_scale_manager)
|
|
|
|
wp_fractional_scale_manager_v1_destroy(wl->fractional_scale_manager);
|
|
|
|
#endif
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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);
|
|
|
|
|
2022-05-18 14:35:53 +00:00
|
|
|
if (wl->viewporter)
|
2022-10-29 20:51:02 +00:00
|
|
|
wp_viewporter_destroy(wl->viewporter);
|
2022-05-18 14:35:53 +00:00
|
|
|
|
|
|
|
if (wl->viewport)
|
2022-10-29 20:51:02 +00:00
|
|
|
wp_viewport_destroy(wl->viewport);
|
2022-05-18 14:35:53 +00:00
|
|
|
|
|
|
|
if (wl->video_viewport)
|
2022-10-29 20:51:02 +00:00
|
|
|
wp_viewport_destroy(wl->video_viewport);
|
2022-05-18 14:35:53 +00:00
|
|
|
|
|
|
|
if (wl->dmabuf)
|
2022-10-29 20:51:02 +00:00
|
|
|
zwp_linux_dmabuf_v1_destroy(wl->dmabuf);
|
|
|
|
|
|
|
|
if (wl->dmabuf_feedback)
|
|
|
|
zwp_linux_dmabuf_feedback_v1_destroy(wl->dmabuf_feedback);
|
2022-05-18 14:35:53 +00:00
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
if (wl->seat)
|
|
|
|
wl_seat_destroy(wl->seat);
|
|
|
|
|
|
|
|
if (wl->shm)
|
|
|
|
wl_shm_destroy(wl->shm);
|
|
|
|
|
2022-11-16 01:52:56 +00:00
|
|
|
#if HAVE_WAYLAND_PROTOCOLS_1_27
|
|
|
|
if (wl->single_pixel_manager)
|
|
|
|
wp_single_pixel_buffer_manager_v1_destroy(wl->single_pixel_manager);
|
|
|
|
#endif
|
|
|
|
|
2023-01-14 04:03:00 +00:00
|
|
|
if (wl->surface)
|
|
|
|
wl_surface_destroy(wl->surface);
|
|
|
|
|
2022-05-18 14:35:53 +00:00
|
|
|
if (wl->video_surface)
|
|
|
|
wl_surface_destroy(wl->video_surface);
|
|
|
|
|
|
|
|
if (wl->video_subsurface)
|
|
|
|
wl_subsurface_destroy(wl->video_subsurface);
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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);
|
|
|
|
|
2023-01-14 04:03:00 +00:00
|
|
|
if (wl->xdg_toplevel)
|
|
|
|
xdg_toplevel_destroy(wl->xdg_toplevel);
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
if (wl->xdg_toplevel_decoration)
|
|
|
|
zxdg_toplevel_decoration_v1_destroy(wl->xdg_toplevel_decoration);
|
|
|
|
|
2023-01-14 04:03:00 +00:00
|
|
|
if (wl->xdg_surface)
|
|
|
|
xdg_surface_destroy(wl->xdg_surface);
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-07-07 10:03:21 +00:00
|
|
|
munmap(wl->format_map, wl->format_size);
|
|
|
|
|
2021-06-26 21:12:03 +00:00
|
|
|
struct vo_wayland_output *output, *tmp;
|
|
|
|
wl_list_for_each_safe(output, tmp, &wl->output_list, link)
|
|
|
|
remove_output(output);
|
|
|
|
|
|
|
|
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
|
2023-03-27 20:42:17 +00:00
|
|
|
* 2. refresh interval reported by presentation time
|
2021-04-16 19:55:15 +00:00
|
|
|
* 3. refresh rate of the output reported by the compositor
|
|
|
|
* 4. make up crap if vblank_time is still <= 0 (better than nothing) */
|
|
|
|
|
2022-10-04 16:05:00 +00:00
|
|
|
if (wl->use_present)
|
2022-06-10 16:49:28 +00:00
|
|
|
vblank_time = wl->present->vsync_duration;
|
2021-04-16 19:55:15 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2021-11-25 05:37:43 +00:00
|
|
|
// Completely arbitrary amount of additional time to wait.
|
|
|
|
vblank_time += 0.05 * vblank_time;
|
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;
|
|
|
|
}
|
2023-01-01 02:51:13 +00:00
|
|
|
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. */
|
2022-10-04 16:05:00 +00:00
|
|
|
if (!wl->use_present && !wl_display_get_error(wl->display))
|
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_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;
|
|
|
|
|
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
|
|
|
|
2023-01-01 02:51:13 +00:00
|
|
|
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);
|
|
|
|
}
|