/* * This file is part of mpv video player. * Copyright © 2008 Kristian Høgsberg * Copyright © 2012-2013 Collabora, Ltd. * Copyright © 2013 Alexander Preisinger * * mpv is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * mpv is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mpv. If not, see . */ #include #include #include #include #include #include #include #include #include #include #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; state = state == WL_POINTER_BUTTON_STATE_PRESSED ? MP_KEY_STATE_DOWN : MP_KEY_STATE_UP; button = button == BTN_LEFT ? MP_MOUSE_BTN0 : button == BTN_MIDDLE ? MP_MOUSE_BTN1 : MP_MOUSE_BTN2; mp_input_put_key(wl->vo->input_ctx, button | state); if (!mp_input_test_dragging(wl->vo->input_ctx, wl->window.mouse_x, wl->window.mouse_y) && (button == MP_MOUSE_BTN0) && (state == MP_KEY_STATE_DOWN)) 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 ratio in fullscreen mode because the compositor // shows the desktop in the border regions if the video does not have the same // aspect ratio 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_input(wl); destroy_display(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]); }