mirror of
https://github.com/mpv-player/mpv
synced 2024-12-11 09:25:56 +00:00
4d75514321
Before this commit, all VOs had to toggle the option flag themselves, now command.c does it. I can't really comprehend why it required every VO to do this manually. Maybe it was for rejecting the property/option change if the VO didn't support a specific capability. But then it could have checked the VOCTRL result. In any case, I don't care, and successfully changing the property without doing anything (With some VOs) is fine too. Many things work this way now, and it's simpler overall. This change will be useful for cleaning up VO option handling.
1164 lines
36 KiB
C
1164 lines
36 KiB
C
/*
|
|
* This file is part of mpv video player.
|
|
* Copyright © 2008 Kristian Høgsberg
|
|
* Copyright © 2012-2013 Collabora, Ltd.
|
|
* Copyright © 2013 Alexander Preisinger <alexander.preisinger@gmail.com>
|
|
*
|
|
* mpv is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* mpv is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
#include <inttypes.h>
|
|
#include <limits.h>
|
|
#include <assert.h>
|
|
#include <poll.h>
|
|
#include <unistd.h>
|
|
|
|
#include <sys/mman.h>
|
|
#include <linux/input.h>
|
|
|
|
#include "config.h"
|
|
#include "misc/bstr.h"
|
|
#include "options/options.h"
|
|
#include "common/msg.h"
|
|
#include "mpv_talloc.h"
|
|
|
|
#include "wayland_common.h"
|
|
|
|
#include "vo.h"
|
|
#include "win_state.h"
|
|
#include "osdep/io.h"
|
|
#include "osdep/timer.h"
|
|
|
|
#include "input/input.h"
|
|
#include "input/event.h"
|
|
#include "input/keycodes.h"
|
|
|
|
static int lookupkey(int key);
|
|
|
|
static void hide_cursor(struct vo_wayland_state * wl);
|
|
static void show_cursor(struct vo_wayland_state * wl);
|
|
static void window_move(struct vo_wayland_state * wl, uint32_t serial);
|
|
static void window_set_title(struct vo_wayland_state * wl, const char *title);
|
|
static void schedule_resize(struct vo_wayland_state *wl,
|
|
uint32_t edges,
|
|
int32_t width,
|
|
int32_t height);
|
|
|
|
static void vo_wayland_fullscreen(struct vo *vo);
|
|
|
|
static const struct wl_callback_listener frame_listener;
|
|
|
|
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},
|
|
|
|
// cursor keys
|
|
{XKB_KEY_Left, MP_KEY_LEFT}, {XKB_KEY_Right, MP_KEY_RIGHT},
|
|
{XKB_KEY_Up, MP_KEY_UP}, {XKB_KEY_Down, MP_KEY_DOWN},
|
|
|
|
// navigation block
|
|
{XKB_KEY_Insert, MP_KEY_INSERT}, {XKB_KEY_Delete, MP_KEY_DELETE},
|
|
{XKB_KEY_Home, MP_KEY_HOME}, {XKB_KEY_End, MP_KEY_END},
|
|
{XKB_KEY_Page_Up, MP_KEY_PAGE_UP}, {XKB_KEY_Page_Down, MP_KEY_PAGE_DOWN},
|
|
|
|
// F-keys
|
|
{XKB_KEY_F1, MP_KEY_F+1}, {XKB_KEY_F2, MP_KEY_F+2},
|
|
{XKB_KEY_F3, MP_KEY_F+3}, {XKB_KEY_F4, MP_KEY_F+4},
|
|
{XKB_KEY_F5, MP_KEY_F+5}, {XKB_KEY_F6, MP_KEY_F+6},
|
|
{XKB_KEY_F7, MP_KEY_F+7}, {XKB_KEY_F8, MP_KEY_F+8},
|
|
{XKB_KEY_F9, MP_KEY_F+9}, {XKB_KEY_F10, MP_KEY_F+10},
|
|
{XKB_KEY_F11, MP_KEY_F+11}, {XKB_KEY_F12, MP_KEY_F+12},
|
|
|
|
// numpad independent of numlock
|
|
{XKB_KEY_KP_Subtract, '-'}, {XKB_KEY_KP_Add, '+'},
|
|
{XKB_KEY_KP_Multiply, '*'}, {XKB_KEY_KP_Divide, '/'},
|
|
{XKB_KEY_KP_Enter, MP_KEY_KPENTER},
|
|
|
|
// numpad with numlock
|
|
{XKB_KEY_KP_0, MP_KEY_KP0}, {XKB_KEY_KP_1, MP_KEY_KP1},
|
|
{XKB_KEY_KP_2, MP_KEY_KP2}, {XKB_KEY_KP_3, MP_KEY_KP3},
|
|
{XKB_KEY_KP_4, MP_KEY_KP4}, {XKB_KEY_KP_5, MP_KEY_KP5},
|
|
{XKB_KEY_KP_6, MP_KEY_KP6}, {XKB_KEY_KP_7, MP_KEY_KP7},
|
|
{XKB_KEY_KP_8, MP_KEY_KP8}, {XKB_KEY_KP_9, MP_KEY_KP9},
|
|
{XKB_KEY_KP_Decimal, MP_KEY_KPDEC}, {XKB_KEY_KP_Separator, MP_KEY_KPDEC},
|
|
|
|
// numpad without numlock
|
|
{XKB_KEY_KP_Insert, MP_KEY_KPINS}, {XKB_KEY_KP_End, MP_KEY_KP1},
|
|
{XKB_KEY_KP_Down, MP_KEY_KP2}, {XKB_KEY_KP_Page_Down, MP_KEY_KP3},
|
|
{XKB_KEY_KP_Left, MP_KEY_KP4}, {XKB_KEY_KP_Begin, MP_KEY_KP5},
|
|
{XKB_KEY_KP_Right, MP_KEY_KP6}, {XKB_KEY_KP_Home, MP_KEY_KP7},
|
|
{XKB_KEY_KP_Up, MP_KEY_KP8}, {XKB_KEY_KP_Page_Up, MP_KEY_KP9},
|
|
{XKB_KEY_KP_Delete, MP_KEY_KPDEL},
|
|
|
|
// "Multimedia keyboard" 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}
|
|
};
|
|
|
|
|
|
/** Wayland listeners **/
|
|
|
|
static void ssurface_handle_ping(void *data,
|
|
struct wl_shell_surface *shell_surface,
|
|
uint32_t serial)
|
|
{
|
|
wl_shell_surface_pong(shell_surface, serial);
|
|
}
|
|
|
|
static void ssurface_handle_configure(void *data,
|
|
struct wl_shell_surface *shell_surface,
|
|
uint32_t edges,
|
|
int32_t width,
|
|
int32_t height)
|
|
{
|
|
struct vo_wayland_state *wl = data;
|
|
float win_aspect = wl->window.aspect;
|
|
if (!width || !height)
|
|
return;
|
|
if (!wl->window.is_fullscreen)
|
|
width = win_aspect * height;
|
|
schedule_resize(wl, edges, width, height);
|
|
}
|
|
|
|
static void ssurface_handle_popup_done(void *data,
|
|
struct wl_shell_surface *shell_surface)
|
|
{
|
|
}
|
|
|
|
static const struct wl_shell_surface_listener shell_surface_listener = {
|
|
ssurface_handle_ping,
|
|
ssurface_handle_configure,
|
|
ssurface_handle_popup_done
|
|
};
|
|
|
|
static void output_handle_geometry(void *data,
|
|
struct wl_output *wl_output,
|
|
int32_t x,
|
|
int32_t y,
|
|
int32_t physical_width,
|
|
int32_t physical_height,
|
|
int32_t subpixel,
|
|
const char *make,
|
|
const char *model,
|
|
int32_t transform)
|
|
{
|
|
struct vo_wayland_output *output = data;
|
|
output->make = make;
|
|
output->model = model;
|
|
}
|
|
|
|
static void output_handle_mode(void *data,
|
|
struct wl_output *wl_output,
|
|
uint32_t flags,
|
|
int32_t width,
|
|
int32_t height,
|
|
int32_t refresh)
|
|
{
|
|
struct vo_wayland_output *output = data;
|
|
|
|
// only save current mode
|
|
if (!output || !(flags & WL_OUTPUT_MODE_CURRENT))
|
|
return;
|
|
|
|
output->width = width;
|
|
output->height = height;
|
|
output->flags = flags;
|
|
output->refresh_rate = refresh;
|
|
}
|
|
|
|
static void output_handle_done(void* data, struct wl_output *wl_output)
|
|
{
|
|
}
|
|
|
|
static void output_handle_scale(void* data, struct wl_output *wl_output,
|
|
int32_t factor)
|
|
{
|
|
struct vo_wayland_output *output = data;
|
|
output->scale = factor;
|
|
}
|
|
|
|
static const struct wl_output_listener output_listener = {
|
|
output_handle_geometry,
|
|
output_handle_mode,
|
|
output_handle_done,
|
|
output_handle_scale
|
|
};
|
|
|
|
|
|
/* SURFACE LISTENER */
|
|
|
|
static void surface_handle_enter(void *data,
|
|
struct wl_surface *wl_surface,
|
|
struct wl_output *output)
|
|
{
|
|
struct vo_wayland_state *wl = data;
|
|
wl->display.current_output = NULL;
|
|
|
|
struct vo_wayland_output *o;
|
|
wl_list_for_each(o, &wl->display.output_list, link) {
|
|
if (o->output == output) {
|
|
wl->display.current_output = o;
|
|
break;
|
|
}
|
|
}
|
|
|
|
wl->window.events |= VO_EVENT_WIN_STATE | VO_EVENT_RESIZE;
|
|
}
|
|
|
|
static void surface_handle_leave(void *data,
|
|
struct wl_surface *wl_surface,
|
|
struct wl_output *output)
|
|
{
|
|
// window can be displayed at 2 output, but we only use the most recently
|
|
// entered and discard the previous one even if a part of the window is
|
|
// still visible on the previous entered output.
|
|
// Don't bother with a "leave" logic
|
|
}
|
|
|
|
static const struct wl_surface_listener surface_listener = {
|
|
surface_handle_enter,
|
|
surface_handle_leave
|
|
};
|
|
|
|
/* KEYBOARD LISTENER */
|
|
static void keyboard_handle_keymap(void *data,
|
|
struct wl_keyboard *wl_keyboard,
|
|
uint32_t format,
|
|
int32_t fd,
|
|
uint32_t size)
|
|
{
|
|
struct vo_wayland_state *wl = data;
|
|
char *map_str;
|
|
|
|
if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
|
|
close(fd);
|
|
return;
|
|
}
|
|
|
|
map_str = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
|
|
if (map_str == MAP_FAILED) {
|
|
close(fd);
|
|
return;
|
|
}
|
|
|
|
wl->input.xkb.keymap = xkb_keymap_new_from_string(wl->input.xkb.context,
|
|
map_str,
|
|
XKB_KEYMAP_FORMAT_TEXT_V1,
|
|
0);
|
|
|
|
munmap(map_str, size);
|
|
close(fd);
|
|
|
|
if (!wl->input.xkb.keymap) {
|
|
MP_ERR(wl, "failed to compile keymap\n");
|
|
return;
|
|
}
|
|
|
|
wl->input.xkb.state = xkb_state_new(wl->input.xkb.keymap);
|
|
if (!wl->input.xkb.state) {
|
|
MP_ERR(wl, "failed to create XKB state\n");
|
|
xkb_keymap_unref(wl->input.xkb.keymap);
|
|
wl->input.xkb.keymap = NULL;
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void keyboard_handle_enter(void *data,
|
|
struct wl_keyboard *wl_keyboard,
|
|
uint32_t serial,
|
|
struct wl_surface *surface,
|
|
struct wl_array *keys)
|
|
{
|
|
}
|
|
|
|
static void keyboard_handle_leave(void *data,
|
|
struct wl_keyboard *wl_keyboard,
|
|
uint32_t serial,
|
|
struct wl_surface *surface)
|
|
{
|
|
}
|
|
|
|
static void keyboard_handle_key(void *data,
|
|
struct wl_keyboard *wl_keyboard,
|
|
uint32_t serial,
|
|
uint32_t time,
|
|
uint32_t key,
|
|
uint32_t state)
|
|
{
|
|
struct vo_wayland_state *wl = data;
|
|
|
|
uint32_t code = code = key + 8;
|
|
xkb_keysym_t sym = xkb_state_key_get_one_sym(wl->input.xkb.state, code);
|
|
|
|
int mpmod = state == WL_KEYBOARD_KEY_STATE_PRESSED ? MP_KEY_STATE_DOWN
|
|
: MP_KEY_STATE_UP;
|
|
|
|
static const char *mod_names[] = {XKB_MOD_NAME_SHIFT, XKB_MOD_NAME_CTRL,
|
|
XKB_MOD_NAME_ALT, XKB_MOD_NAME_LOGO, 0};
|
|
static int mods[] = {MP_KEY_MODIFIER_SHIFT, MP_KEY_MODIFIER_CTRL,
|
|
MP_KEY_MODIFIER_ALT, MP_KEY_MODIFIER_META, 0};
|
|
|
|
for (int n = 0; mods[n]; n++) {
|
|
xkb_mod_index_t index =
|
|
xkb_keymap_mod_get_index(wl->input.xkb.keymap, mod_names[n]);
|
|
if (!xkb_state_mod_index_is_consumed(wl->input.xkb.state, code, index)
|
|
&& xkb_state_mod_index_is_active(wl->input.xkb.state, index,
|
|
XKB_STATE_MODS_DEPRESSED))
|
|
mpmod |= mods[n];
|
|
}
|
|
|
|
int mpkey = lookupkey(sym);
|
|
if (mpkey) {
|
|
mp_input_put_key(wl->vo->input_ctx, mpkey | mpmod);
|
|
} else {
|
|
char s[80];
|
|
if (xkb_keysym_to_utf8(sym, s, sizeof(s)) > 0)
|
|
mp_input_put_key_utf8(wl->vo->input_ctx, mpmod, bstr0(s));
|
|
}
|
|
}
|
|
|
|
static void keyboard_handle_modifiers(void *data,
|
|
struct wl_keyboard *wl_keyboard,
|
|
uint32_t serial,
|
|
uint32_t mods_depressed,
|
|
uint32_t mods_latched,
|
|
uint32_t mods_locked,
|
|
uint32_t group)
|
|
{
|
|
struct vo_wayland_state *wl = data;
|
|
|
|
xkb_state_update_mask(wl->input.xkb.state,
|
|
mods_depressed,
|
|
mods_latched,
|
|
mods_locked,
|
|
0, 0, group);
|
|
}
|
|
|
|
static void keyboard_handle_repeat_info(void *data,
|
|
struct wl_keyboard *wl_keyboard,
|
|
int32_t rate,
|
|
int32_t delay)
|
|
{
|
|
struct vo_wayland_state *wl = data;
|
|
if (wl->vo->opts->native_keyrepeat) {
|
|
if (rate < 0 || delay < 0) {
|
|
MP_WARN(wl, "Invalid rate or delay values sent by compositor\n");
|
|
return;
|
|
}
|
|
mp_input_set_repeat_info(wl->vo->input_ctx, rate, delay);
|
|
}
|
|
}
|
|
|
|
static const struct wl_keyboard_listener keyboard_listener = {
|
|
keyboard_handle_keymap,
|
|
keyboard_handle_enter,
|
|
keyboard_handle_leave,
|
|
keyboard_handle_key,
|
|
keyboard_handle_modifiers,
|
|
keyboard_handle_repeat_info
|
|
};
|
|
|
|
/* POINTER LISTENER */
|
|
static void pointer_handle_enter(void *data,
|
|
struct wl_pointer *pointer,
|
|
uint32_t serial,
|
|
struct wl_surface *surface,
|
|
wl_fixed_t sx_w,
|
|
wl_fixed_t sy_w)
|
|
{
|
|
struct vo_wayland_state *wl = data;
|
|
|
|
wl->cursor.serial = serial;
|
|
wl->cursor.pointer = pointer;
|
|
|
|
/* Release the left button on pointer enter again
|
|
* because after moving the shell surface no release event is sent */
|
|
mp_input_put_key(wl->vo->input_ctx, MP_KEY_MOUSE_ENTER);
|
|
mp_input_put_key(wl->vo->input_ctx, MP_MOUSE_BTN0 | MP_KEY_STATE_UP);
|
|
show_cursor(wl);
|
|
}
|
|
|
|
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_w,
|
|
wl_fixed_t sy_w)
|
|
{
|
|
int32_t scale = 1;
|
|
struct vo_wayland_state *wl = data;
|
|
|
|
if (wl->display.current_output)
|
|
scale = wl->display.current_output->scale;
|
|
|
|
wl->cursor.pointer = pointer;
|
|
wl->window.mouse_x = scale*wl_fixed_to_int(sx_w);
|
|
wl->window.mouse_y = scale*wl_fixed_to_int(sy_w);
|
|
|
|
mp_input_set_mouse_pos(wl->vo->input_ctx, wl->window.mouse_x,
|
|
wl->window.mouse_y);
|
|
}
|
|
|
|
static void pointer_handle_button(void *data,
|
|
struct wl_pointer *pointer,
|
|
uint32_t serial,
|
|
uint32_t time,
|
|
uint32_t button,
|
|
uint32_t state)
|
|
{
|
|
struct vo_wayland_state *wl = data;
|
|
|
|
mp_input_put_key(wl->vo->input_ctx, (MP_MOUSE_BTN0 + (button - BTN_LEFT)) |
|
|
((state == WL_POINTER_BUTTON_STATE_PRESSED)
|
|
? MP_KEY_STATE_DOWN : MP_KEY_STATE_UP));
|
|
|
|
if (!mp_input_test_dragging(wl->vo->input_ctx, wl->window.mouse_x, wl->window.mouse_y) &&
|
|
(button == BTN_LEFT) && (state == WL_POINTER_BUTTON_STATE_PRESSED))
|
|
window_move(wl, serial);
|
|
}
|
|
|
|
static void pointer_handle_axis(void *data,
|
|
struct wl_pointer *pointer,
|
|
uint32_t time,
|
|
uint32_t axis,
|
|
wl_fixed_t value)
|
|
{
|
|
struct vo_wayland_state *wl = data;
|
|
|
|
// value is 10.00 on a normal mouse wheel
|
|
// scale it down to 1.00 for multipliying it with the commands
|
|
if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {
|
|
if (value > 0)
|
|
mp_input_put_axis(wl->vo->input_ctx, MP_AXIS_DOWN,
|
|
wl_fixed_to_double(value)*0.1);
|
|
if (value < 0)
|
|
mp_input_put_axis(wl->vo->input_ctx, MP_AXIS_UP,
|
|
wl_fixed_to_double(value)*-0.1);
|
|
}
|
|
else if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) {
|
|
if (value > 0)
|
|
mp_input_put_axis(wl->vo->input_ctx, MP_AXIS_RIGHT,
|
|
wl_fixed_to_double(value)*0.1);
|
|
if (value < 0)
|
|
mp_input_put_axis(wl->vo->input_ctx, MP_AXIS_LEFT,
|
|
wl_fixed_to_double(value)*-0.1);
|
|
}
|
|
}
|
|
|
|
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 seat_handle_capabilities(void *data,
|
|
struct wl_seat *seat,
|
|
enum wl_seat_capability caps)
|
|
{
|
|
struct vo_wayland_state *wl = data;
|
|
|
|
if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !wl->input.keyboard) {
|
|
wl->input.keyboard = wl_seat_get_keyboard(seat);
|
|
wl_keyboard_add_listener(wl->input.keyboard, &keyboard_listener, wl);
|
|
}
|
|
else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && wl->input.keyboard) {
|
|
wl_keyboard_destroy(wl->input.keyboard);
|
|
wl->input.keyboard = NULL;
|
|
}
|
|
if ((caps & WL_SEAT_CAPABILITY_POINTER) && !wl->input.pointer) {
|
|
wl->input.pointer = wl_seat_get_pointer(seat);
|
|
wl_pointer_add_listener(wl->input.pointer, &pointer_listener, wl);
|
|
}
|
|
else if (!(caps & WL_SEAT_CAPABILITY_POINTER) && wl->input.pointer) {
|
|
wl_pointer_destroy(wl->input.pointer);
|
|
wl->input.pointer = NULL;
|
|
}
|
|
}
|
|
|
|
static void seat_handle_name(void *data,
|
|
struct wl_seat *seat,
|
|
const char *name)
|
|
{
|
|
struct vo_wayland_state *wl = data;
|
|
MP_VERBOSE(wl, "Seat \"%s\" connected\n", name);
|
|
}
|
|
|
|
static const struct wl_seat_listener seat_listener = {
|
|
seat_handle_capabilities,
|
|
seat_handle_name,
|
|
};
|
|
|
|
static void registry_handle_global(void *data, struct wl_registry *reg,
|
|
uint32_t id, const char *interface,
|
|
uint32_t version)
|
|
{
|
|
struct vo_wayland_state *wl = data;
|
|
|
|
if (strcmp(interface, "wl_compositor") == 0) {
|
|
|
|
wl->display.compositor = wl_registry_bind(reg, id,
|
|
&wl_compositor_interface,
|
|
MPMIN(3, version));
|
|
}
|
|
|
|
else if (strcmp(interface, "wl_shell") == 0) {
|
|
|
|
wl->display.shell = wl_registry_bind(reg, id, &wl_shell_interface, 1);
|
|
}
|
|
|
|
else if (strcmp(interface, "wl_shm") == 0) {
|
|
|
|
wl->display.shm = wl_registry_bind(reg, id, &wl_shm_interface, 1);
|
|
}
|
|
|
|
else if (strcmp(interface, "wl_output") == 0) {
|
|
|
|
struct vo_wayland_output *output =
|
|
talloc_zero(wl, struct vo_wayland_output);
|
|
|
|
output->id = id;
|
|
output->scale = 1;
|
|
output->output = wl_registry_bind(reg, id, &wl_output_interface,
|
|
MPMIN(2, version));
|
|
|
|
wl_output_add_listener(output->output, &output_listener, output);
|
|
wl_list_insert(&wl->display.output_list, &output->link);
|
|
}
|
|
|
|
else if (strcmp(interface, "wl_seat") == 0) {
|
|
|
|
wl->input.seat = wl_registry_bind(reg, id, &wl_seat_interface, 4);
|
|
wl_seat_add_listener(wl->input.seat, &seat_listener, wl);
|
|
|
|
}
|
|
|
|
else if (strcmp(interface, "wl_subcompositor") == 0) {
|
|
|
|
wl->display.subcomp = wl_registry_bind(reg, id,
|
|
&wl_subcompositor_interface, 1);
|
|
}
|
|
}
|
|
|
|
static void registry_handle_global_remove(void *data,
|
|
struct wl_registry *registry,
|
|
uint32_t id)
|
|
{
|
|
}
|
|
|
|
static const struct wl_registry_listener registry_listener = {
|
|
registry_handle_global,
|
|
registry_handle_global_remove
|
|
};
|
|
|
|
|
|
/*** internal functions ***/
|
|
|
|
static int lookupkey(int key)
|
|
{
|
|
const char *passthrough_keys = " -+*/<>`~!@#$%^&()_{}:;\"\',.?\\|=[]";
|
|
|
|
int mpkey = 0;
|
|
if ((key >= 'a' && key <= 'z') ||
|
|
(key >= 'A' && key <= 'Z') ||
|
|
(key >= '0' && key <= '9') ||
|
|
(key > 0 && key < 256 && strchr(passthrough_keys, key)))
|
|
mpkey = key;
|
|
|
|
if (!mpkey)
|
|
mpkey = lookup_keymap_table(keymap, key);
|
|
|
|
return mpkey;
|
|
}
|
|
|
|
static void hide_cursor (struct vo_wayland_state *wl)
|
|
{
|
|
if (!wl->cursor.pointer)
|
|
return;
|
|
|
|
wl_pointer_set_cursor(wl->cursor.pointer, wl->cursor.serial, NULL, 0, 0);
|
|
}
|
|
|
|
static void show_cursor (struct vo_wayland_state *wl)
|
|
{
|
|
if (!wl->cursor.pointer)
|
|
return;
|
|
|
|
struct wl_cursor_image *image = wl->cursor.default_cursor->images[0];
|
|
struct wl_buffer *buffer = wl_cursor_image_get_buffer(image);
|
|
|
|
wl_pointer_set_cursor(wl->cursor.pointer,
|
|
wl->cursor.serial,
|
|
wl->cursor.surface,
|
|
image->hotspot_x,
|
|
image->hotspot_y);
|
|
|
|
wl_surface_attach(wl->cursor.surface, buffer, 0, 0);
|
|
wl_surface_damage(wl->cursor.surface, 0, 0, image->width, image->height);
|
|
wl_surface_commit(wl->cursor.surface);
|
|
}
|
|
|
|
static void window_move(struct vo_wayland_state *wl, uint32_t serial)
|
|
{
|
|
if (wl->display.shell)
|
|
wl_shell_surface_move(wl->window.shell_surface, wl->input.seat, serial);
|
|
}
|
|
|
|
static void window_set_toplevel(struct vo_wayland_state *wl)
|
|
{
|
|
if (wl->display.shell)
|
|
wl_shell_surface_set_toplevel(wl->window.shell_surface);
|
|
}
|
|
|
|
static void window_set_title(struct vo_wayland_state *wl, const char *title)
|
|
{
|
|
if (wl->display.shell)
|
|
wl_shell_surface_set_title(wl->window.shell_surface, title);
|
|
}
|
|
|
|
static void schedule_resize(struct vo_wayland_state *wl,
|
|
uint32_t edges,
|
|
int32_t width,
|
|
int32_t height)
|
|
{
|
|
int32_t minimum_size = 150;
|
|
int32_t x, y;
|
|
float win_aspect = wl->window.aspect;
|
|
if (win_aspect <= 0)
|
|
win_aspect = 1;
|
|
|
|
MP_DBG(wl, "schedule resize: %dx%d\n", width, height);
|
|
|
|
width = MPMAX(minimum_size, width);
|
|
height = MPMAX(minimum_size, height);
|
|
if (wl->display.current_output) {
|
|
int scale = wl->display.current_output->scale;
|
|
width = MPMIN(width, wl->display.current_output->width /scale);
|
|
height = MPMIN(height, wl->display.current_output->height/scale);
|
|
}
|
|
|
|
// don't keep the aspect ration in fullscreen mode, because the compositor
|
|
// shows the desktop in the border regions if the video has not the same
|
|
// aspect ration as the screen
|
|
/* if only the height is changed we have to calculate the width
|
|
* in any other case we calculate the height */
|
|
switch (edges) {
|
|
case WL_SHELL_SURFACE_RESIZE_TOP:
|
|
case WL_SHELL_SURFACE_RESIZE_BOTTOM:
|
|
width = win_aspect * height;
|
|
break;
|
|
case WL_SHELL_SURFACE_RESIZE_LEFT:
|
|
case WL_SHELL_SURFACE_RESIZE_RIGHT:
|
|
case WL_SHELL_SURFACE_RESIZE_TOP_LEFT: // just a preference
|
|
case WL_SHELL_SURFACE_RESIZE_TOP_RIGHT:
|
|
case WL_SHELL_SURFACE_RESIZE_BOTTOM_LEFT:
|
|
case WL_SHELL_SURFACE_RESIZE_BOTTOM_RIGHT:
|
|
height = (1 / win_aspect) * width;
|
|
break;
|
|
}
|
|
|
|
if (edges & WL_SHELL_SURFACE_RESIZE_LEFT)
|
|
x = wl->window.width - width;
|
|
else
|
|
x = 0;
|
|
|
|
if (edges & WL_SHELL_SURFACE_RESIZE_TOP)
|
|
y = wl->window.height - height;
|
|
else
|
|
y = 0;
|
|
|
|
wl->window.sh_width = width;
|
|
wl->window.sh_height = height;
|
|
wl->window.sh_x = x;
|
|
wl->window.sh_y = y;
|
|
wl->window.events |= VO_EVENT_RESIZE;
|
|
}
|
|
|
|
static void frame_callback(void *data,
|
|
struct wl_callback *callback,
|
|
uint32_t time)
|
|
{
|
|
struct vo_wayland_state *wl = data;
|
|
|
|
if (wl->frame.function)
|
|
wl->frame.function(wl->frame.data, time);
|
|
|
|
if (callback)
|
|
wl_callback_destroy(callback);
|
|
|
|
wl->frame.callback = wl_surface_frame(wl->window.video_surface);
|
|
|
|
if (!wl->frame.callback) {
|
|
MP_ERR(wl, "wl_surface_frame failed\n");
|
|
return;
|
|
}
|
|
|
|
wl_callback_add_listener(wl->frame.callback, &frame_listener, wl);
|
|
wl_surface_commit(wl->window.video_surface);
|
|
}
|
|
|
|
static const struct wl_callback_listener frame_listener = {
|
|
frame_callback
|
|
};
|
|
|
|
static bool create_display(struct vo_wayland_state *wl)
|
|
{
|
|
if (wl->vo->probing && !getenv("XDG_RUNTIME_DIR"))
|
|
return false;
|
|
|
|
wl->display.display = wl_display_connect(NULL);
|
|
|
|
if (!wl->display.display) {
|
|
MP_MSG(wl, wl->vo->probing ? MSGL_V : MSGL_ERR,
|
|
"failed to connect to a wayland server: "
|
|
"check if a wayland compositor is running\n");
|
|
|
|
return false;
|
|
}
|
|
|
|
wl->display.registry = wl_display_get_registry(wl->display.display);
|
|
wl_registry_add_listener(wl->display.registry, ®istry_listener, wl);
|
|
|
|
wl_display_roundtrip(wl->display.display);
|
|
|
|
wl->display.display_fd = wl_display_get_fd(wl->display.display);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void destroy_display(struct vo_wayland_state *wl)
|
|
{
|
|
struct vo_wayland_output *output = NULL;
|
|
struct vo_wayland_output *tmp = NULL;
|
|
|
|
wl_list_for_each_safe(output, tmp, &wl->display.output_list, link) {
|
|
if (output && output->output) {
|
|
wl_output_destroy(output->output);
|
|
output->output = NULL;
|
|
wl_list_remove(&output->link);
|
|
}
|
|
}
|
|
|
|
if (wl->display.shm)
|
|
wl_shm_destroy(wl->display.shm);
|
|
|
|
if (wl->display.shell)
|
|
wl_shell_destroy(wl->display.shell);
|
|
|
|
if (wl->display.subcomp)
|
|
wl_subcompositor_destroy(wl->display.subcomp);
|
|
|
|
if (wl->display.compositor)
|
|
wl_compositor_destroy(wl->display.compositor);
|
|
|
|
if (wl->display.registry)
|
|
wl_registry_destroy(wl->display.registry);
|
|
|
|
if (wl->display.display) {
|
|
wl_display_flush(wl->display.display);
|
|
wl_display_disconnect(wl->display.display);
|
|
}
|
|
}
|
|
|
|
static bool create_window(struct vo_wayland_state *wl)
|
|
{
|
|
wl->window.video_surface =
|
|
wl_compositor_create_surface(wl->display.compositor);
|
|
|
|
wl_surface_add_listener(wl->window.video_surface,
|
|
&surface_listener, wl);
|
|
|
|
if (wl->display.shell) {
|
|
wl->window.shell_surface = wl_shell_get_shell_surface(wl->display.shell,
|
|
wl->window.video_surface);
|
|
|
|
if (!wl->window.shell_surface) {
|
|
MP_ERR(wl, "creating shell surface failed\n");
|
|
return false;
|
|
}
|
|
|
|
wl_shell_surface_add_listener(wl->window.shell_surface,
|
|
&shell_surface_listener, wl);
|
|
|
|
wl_shell_surface_set_toplevel(wl->window.shell_surface);
|
|
wl_shell_surface_set_class(wl->window.shell_surface, "mpv");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void destroy_window(struct vo_wayland_state *wl)
|
|
{
|
|
if (wl->window.shell_surface)
|
|
wl_shell_surface_destroy(wl->window.shell_surface);
|
|
|
|
if (wl->window.video_surface)
|
|
wl_surface_destroy(wl->window.video_surface);
|
|
|
|
if (wl->frame.callback)
|
|
wl_callback_destroy(wl->frame.callback);
|
|
}
|
|
|
|
static bool create_cursor(struct vo_wayland_state *wl)
|
|
{
|
|
if (!wl->display.shm) {
|
|
MP_ERR(wl->vo, "no shm interface available\n");
|
|
return false;
|
|
}
|
|
|
|
wl->cursor.surface =
|
|
wl_compositor_create_surface(wl->display.compositor);
|
|
|
|
if (!wl->cursor.surface)
|
|
return false;
|
|
|
|
wl->cursor.theme = wl_cursor_theme_load(NULL, 32, wl->display.shm);
|
|
wl->cursor.default_cursor = wl_cursor_theme_get_cursor(wl->cursor.theme,
|
|
"left_ptr");
|
|
|
|
return true;
|
|
}
|
|
|
|
static void destroy_cursor(struct vo_wayland_state *wl)
|
|
{
|
|
if (wl->cursor.theme)
|
|
wl_cursor_theme_destroy(wl->cursor.theme);
|
|
|
|
if (wl->cursor.surface)
|
|
wl_surface_destroy(wl->cursor.surface);
|
|
}
|
|
|
|
static bool create_input(struct vo_wayland_state *wl)
|
|
{
|
|
wl->input.xkb.context = xkb_context_new(0);
|
|
|
|
if (!wl->input.xkb.context) {
|
|
MP_ERR(wl, "failed to initialize input: check xkbcommon\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void destroy_input(struct vo_wayland_state *wl)
|
|
{
|
|
if (wl->input.keyboard) {
|
|
wl_keyboard_destroy(wl->input.keyboard);
|
|
xkb_keymap_unref(wl->input.xkb.keymap);
|
|
xkb_state_unref(wl->input.xkb.state);
|
|
}
|
|
|
|
if (wl->input.xkb.context)
|
|
xkb_context_unref(wl->input.xkb.context);
|
|
|
|
if (wl->input.pointer)
|
|
wl_pointer_destroy(wl->input.pointer);
|
|
|
|
if (wl->input.seat)
|
|
wl_seat_destroy(wl->input.seat);
|
|
}
|
|
|
|
/*** mplayer2 interface ***/
|
|
|
|
int vo_wayland_init(struct vo *vo)
|
|
{
|
|
vo->wayland = talloc_zero(NULL, struct vo_wayland_state);
|
|
struct vo_wayland_state *wl = vo->wayland;
|
|
*wl = (struct vo_wayland_state){
|
|
.vo = vo,
|
|
.log = mp_log_new(wl, vo->log, "wayland"),
|
|
.wakeup_pipe = {-1, -1},
|
|
};
|
|
|
|
wl_list_init(&wl->display.output_list);
|
|
|
|
if (!create_input(wl)
|
|
|| !create_display(wl)
|
|
|| !create_window(wl)
|
|
|| !create_cursor(wl))
|
|
{
|
|
vo_wayland_uninit(vo);
|
|
return false;
|
|
}
|
|
|
|
// create_display's roundtrip only adds the interfaces
|
|
// the second roundtrip receives output modes, geometry and more ...
|
|
wl_display_roundtrip(wl->display.display);
|
|
|
|
struct vo_wayland_output *o = NULL;
|
|
wl_list_for_each(o, &wl->display.output_list, link) {
|
|
MP_VERBOSE(wl, "output received:\n"
|
|
"\tvendor: %s\n"
|
|
"\tmodel: %s\n"
|
|
"\tw: %d, h: %d\n"
|
|
"\tscale: %d\n"
|
|
"\tHz: %f\n",
|
|
o->make, o->model,
|
|
o->width, o->height, o->scale,
|
|
o->refresh_rate / 1000.0f);
|
|
}
|
|
|
|
mp_make_wakeup_pipe(wl->wakeup_pipe);
|
|
|
|
return true;
|
|
}
|
|
|
|
void vo_wayland_uninit(struct vo *vo)
|
|
{
|
|
struct vo_wayland_state *wl = vo->wayland;
|
|
destroy_cursor(wl);
|
|
destroy_window(wl);
|
|
destroy_display(wl);
|
|
destroy_input(wl);
|
|
for (int n = 0; n < 2; n++)
|
|
close(wl->wakeup_pipe[n]);
|
|
talloc_free(wl);
|
|
vo->wayland = NULL;
|
|
}
|
|
|
|
static void vo_wayland_ontop(struct vo *vo)
|
|
{
|
|
struct vo_wayland_state *wl = vo->wayland;
|
|
if (!vo->opts->ontop)
|
|
return;
|
|
MP_DBG(wl, "going ontop\n");
|
|
window_set_toplevel(wl);
|
|
schedule_resize(wl, 0, wl->window.width, wl->window.height);
|
|
}
|
|
|
|
static void vo_wayland_fullscreen(struct vo *vo)
|
|
{
|
|
struct vo_wayland_state *wl = vo->wayland;
|
|
if (!wl->display.shell)
|
|
return;
|
|
|
|
struct wl_output *fs_output = wl->display.fs_output;
|
|
|
|
if (vo->opts->fullscreen) {
|
|
MP_DBG(wl, "going fullscreen\n");
|
|
wl->window.is_fullscreen = true;
|
|
wl->window.p_width = wl->window.width;
|
|
wl->window.p_height = wl->window.height;
|
|
if (wl->display.current_output)
|
|
schedule_resize(wl, 0, wl->display.current_output->width,
|
|
wl->display.current_output->height);
|
|
wl_shell_surface_set_fullscreen(wl->window.shell_surface,
|
|
WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT,
|
|
0, fs_output);
|
|
}
|
|
|
|
else {
|
|
MP_DBG(wl, "leaving fullscreen\n");
|
|
wl->window.is_fullscreen = false;
|
|
window_set_toplevel(wl);
|
|
schedule_resize(wl, 0, wl->window.p_width, wl->window.p_height);
|
|
}
|
|
}
|
|
|
|
static void vo_wayland_update_screeninfo(struct vo *vo, struct mp_rect *screenrc)
|
|
{
|
|
struct vo_wayland_state *wl = vo->wayland;
|
|
struct mp_vo_opts *opts = vo->opts;
|
|
|
|
*screenrc = (struct mp_rect){0};
|
|
|
|
int screen_id = 0;
|
|
|
|
struct vo_wayland_output *output;
|
|
struct vo_wayland_output *first_output = NULL;
|
|
struct vo_wayland_output *fsscreen_output = NULL;
|
|
|
|
if (opts->fsscreen_id >= 0) {
|
|
wl_list_for_each_reverse(output, &wl->display.output_list, link) {
|
|
if (!output || !output->width)
|
|
continue;
|
|
|
|
if (opts->fsscreen_id == screen_id)
|
|
fsscreen_output = output;
|
|
|
|
screen_id++;
|
|
}
|
|
}
|
|
|
|
if (fsscreen_output) {
|
|
wl->display.fs_output = fsscreen_output->output;
|
|
screenrc->x1 = fsscreen_output->width;
|
|
screenrc->y1 = fsscreen_output->height;
|
|
}
|
|
else {
|
|
wl->display.fs_output = NULL; /* current output is always 0 */
|
|
|
|
if (first_output) {
|
|
screenrc->x1 = wl->display.current_output->width;
|
|
screenrc->y1 = wl->display.current_output->height;
|
|
}
|
|
}
|
|
|
|
wl->window.fs_width = screenrc->x1;
|
|
wl->window.fs_height = screenrc->y1;
|
|
}
|
|
|
|
int vo_wayland_control(struct vo *vo, int *events, int request, void *arg)
|
|
{
|
|
struct vo_wayland_state *wl = vo->wayland;
|
|
wl_display_dispatch_pending(wl->display.display);
|
|
|
|
switch (request) {
|
|
case VOCTRL_CHECK_EVENTS:
|
|
*events |= wl->window.events;
|
|
wl->window.events = 0;
|
|
return VO_TRUE;
|
|
case VOCTRL_FULLSCREEN:
|
|
vo_wayland_fullscreen(vo);
|
|
return VO_TRUE;
|
|
case VOCTRL_ONTOP:
|
|
vo_wayland_ontop(vo);
|
|
return VO_TRUE;
|
|
case VOCTRL_GET_UNFS_WINDOW_SIZE: {
|
|
int *s = arg, scale = 1;
|
|
if (wl->display.current_output)
|
|
scale = wl->display.current_output->scale;
|
|
s[0] = scale*wl->window.width;
|
|
s[1] = scale*wl->window.height;
|
|
return VO_TRUE;
|
|
}
|
|
case VOCTRL_SET_UNFS_WINDOW_SIZE: {
|
|
int *s = arg;
|
|
if (!wl->window.is_fullscreen)
|
|
schedule_resize(wl, 0, s[0], s[1]);
|
|
return VO_TRUE;
|
|
}
|
|
case VOCTRL_SET_CURSOR_VISIBILITY:
|
|
if (*(bool *)arg) {
|
|
if (!wl->cursor.visible)
|
|
show_cursor(wl);
|
|
}
|
|
else {
|
|
if (wl->cursor.visible)
|
|
hide_cursor(wl);
|
|
}
|
|
wl->cursor.visible = *(bool *)arg;
|
|
return VO_TRUE;
|
|
case VOCTRL_UPDATE_WINDOW_TITLE:
|
|
window_set_title(wl, (char*) arg);
|
|
return VO_TRUE;
|
|
case VOCTRL_GET_DISPLAY_FPS: {
|
|
if (!wl->display.current_output)
|
|
break;
|
|
|
|
// refresh rate is stored in milli-Hertz (mHz)
|
|
double fps = wl->display.current_output->refresh_rate / 1000.0f;
|
|
*(double*) arg = fps;
|
|
return VO_TRUE;
|
|
}
|
|
}
|
|
return VO_NOTIMPL;
|
|
}
|
|
|
|
bool vo_wayland_config(struct vo *vo)
|
|
{
|
|
struct vo_wayland_state *wl = vo->wayland;
|
|
|
|
struct mp_rect screenrc;
|
|
vo_wayland_update_screeninfo(vo, &screenrc);
|
|
|
|
struct vo_win_geometry geo;
|
|
vo_calc_window_geometry(vo, &screenrc, &geo);
|
|
vo_apply_window_geometry(vo, &geo);
|
|
|
|
wl->window.p_width = vo->dwidth;
|
|
wl->window.p_height = vo->dheight;
|
|
wl->window.aspect = vo->dwidth / (float) MPMAX(vo->dheight, 1);
|
|
|
|
wl->window.width = vo->dwidth;
|
|
wl->window.height = vo->dheight;
|
|
vo_wayland_fullscreen(vo);
|
|
|
|
return true;
|
|
}
|
|
|
|
void vo_wayland_request_frame(struct vo *vo, void *data, vo_wayland_frame_cb cb)
|
|
{
|
|
struct vo_wayland_state *wl = vo->wayland;
|
|
wl->frame.data = data;
|
|
wl->frame.function = cb;
|
|
MP_DBG(wl, "restart frame callback\n");
|
|
frame_callback(wl, NULL, 0);
|
|
}
|
|
|
|
void vo_wayland_wakeup(struct vo *vo)
|
|
{
|
|
struct vo_wayland_state *wl = vo->wayland;
|
|
(void)write(wl->wakeup_pipe[1], &(char){0}, 1);
|
|
}
|
|
|
|
void vo_wayland_wait_events(struct vo *vo, int64_t until_time_us)
|
|
{
|
|
struct vo_wayland_state *wl = vo->wayland;
|
|
struct wl_display *dp = wl->display.display;
|
|
|
|
struct pollfd fds[2] = {
|
|
{.fd = wl->display.display_fd, .events = POLLIN },
|
|
{.fd = wl->wakeup_pipe[0], .events = POLLIN },
|
|
};
|
|
|
|
int64_t wait_us = until_time_us - mp_time_us();
|
|
int timeout_ms = MPCLAMP((wait_us + 999) / 1000, 0, 10000);
|
|
|
|
wl_display_dispatch_pending(dp);
|
|
wl_display_flush(dp);
|
|
|
|
poll(fds, 2, timeout_ms);
|
|
|
|
if (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) {
|
|
MP_FATAL(wl, "error occurred on the display fd: "
|
|
"closing file descriptor\n");
|
|
close(wl->display.display_fd);
|
|
mp_input_put_key(vo->input_ctx, MP_KEY_CLOSE_WIN);
|
|
}
|
|
|
|
if (fds[0].revents & POLLIN)
|
|
wl_display_dispatch(dp);
|
|
|
|
if (fds[1].revents & POLLIN)
|
|
mp_flush_wakeup_pipe(wl->wakeup_pipe[0]);
|
|
}
|