diff options
Diffstat (limited to 'video/out/x11_common.c')
-rw-r--r-- | video/out/x11_common.c | 625 |
1 files changed, 450 insertions, 175 deletions
diff --git a/video/out/x11_common.c b/video/out/x11_common.c index 92b3539aca..fa2f2ba38c 100644 --- a/video/out/x11_common.c +++ b/video/out/x11_common.c @@ -29,16 +29,17 @@ #include <X11/Xlib.h> #include <X11/Xutil.h> #include <X11/Xatom.h> +#include <X11/Xresource.h> #include <X11/keysym.h> #include <X11/XKBlib.h> #include <X11/XF86keysym.h> #include <X11/extensions/scrnsaver.h> #include <X11/extensions/dpms.h> -#include <X11/extensions/Xinerama.h> +#include <X11/extensions/shape.h> +#include <X11/extensions/Xpresent.h> #include <X11/extensions/Xrandr.h> -#include "config.h" #include "misc/bstr.h" #include "options/options.h" #include "options/m_config.h" @@ -48,12 +49,14 @@ #include "input/event.h" #include "video/image_loader.h" #include "video/mp_image.h" +#include "present_sync.h" #include "x11_common.h" #include "mpv_talloc.h" #include "vo.h" #include "win_state.h" #include "osdep/io.h" +#include "osdep/poll_wrapper.h" #include "osdep/timer.h" #include "osdep/subprocess.h" @@ -65,6 +68,7 @@ #define vo_wm_STAYS_ON_TOP 4 #define vo_wm_ABOVE 8 #define vo_wm_BELOW 16 +#define vo_wm_STICKY 32 /* EWMH state actions, see http://freedesktop.org/Standards/wm-spec/index.html#id2768769 */ @@ -95,7 +99,9 @@ #define MWM_FUNC_MAXIMIZE (1L << 4) #define MWM_FUNC_CLOSE (1L << 5) -#define MWM_DECOR_ALL (1L << 0) +// Equals to all MWM_DECOR_* OR'd together. +#define MWM_DECOR_ALL 126 +#define MWM_DECOR_TITLE (1L << 3) typedef struct { @@ -107,19 +113,19 @@ typedef struct } MotifWmHints; static const char x11_icon_16[] = -#include "generated/etc/mpv-icon-8bit-16x16.png.inc" +#include "etc/mpv-icon-8bit-16x16.png.inc" ; static const char x11_icon_32[] = -#include "generated/etc/mpv-icon-8bit-32x32.png.inc" +#include "etc/mpv-icon-8bit-32x32.png.inc" ; static const char x11_icon_64[] = -#include "generated/etc/mpv-icon-8bit-64x64.png.inc" +#include "etc/mpv-icon-8bit-64x64.png.inc" ; static const char x11_icon_128[] = -#include "generated/etc/mpv-icon-8bit-128x128.png.inc" +#include "etc/mpv-icon-8bit-128x128.png.inc" ; #define ICON_ENTRY(var) { (char *)var, sizeof(var) } @@ -134,6 +140,7 @@ static const struct bstr x11_icons[] = { static struct mp_log *x11_error_output; static atomic_int x11_error_silence; +static bool rc_overlaps(struct mp_rect rc1, struct mp_rect rc2); static void vo_x11_update_geometry(struct vo *vo); static void vo_x11_fullscreen(struct vo *vo); static void xscreensaver_heartbeat(struct vo_x11_state *x11); @@ -147,6 +154,8 @@ static void vo_x11_move_resize(struct vo *vo, bool move, bool resize, struct mp_rect rc); static void vo_x11_maximize(struct vo *vo); static void vo_x11_minimize(struct vo *vo); +static void vo_x11_set_input_region(struct vo *vo, bool passthrough); +static void vo_x11_sticky(struct vo *vo, bool sticky); #define XA(x11, s) (XInternAtom((x11)->display, # s, False)) #define XAs(x11, s) XInternAtom((x11)->display, s, False) @@ -325,6 +334,7 @@ static int net_wm_support_state_test(struct vo_x11_state *x11, Atom atom) NET_WM_STATE_TEST(ABOVE); NET_WM_STATE_TEST(STAYS_ON_TOP); NET_WM_STATE_TEST(BELOW); + NET_WM_STATE_TEST(STICKY); return 0; } @@ -375,6 +385,19 @@ static int vo_wm_detect(struct vo *vo) return wm; } +static void xpresent_set(struct vo_x11_state *x11) +{ + int present = x11->opts->x11_present; + x11->use_present = x11->present_code && + ((x11->has_mesa && !x11->has_nvidia && present) || + present == 2); + if (x11->use_present) { + MP_VERBOSE(x11, "XPresent enabled.\n"); + } else { + MP_VERBOSE(x11, "XPresent disabled.\n"); + } +} + static void xrandr_read(struct vo_x11_state *x11) { for(int i = 0; i < x11->num_displays; i++) @@ -399,6 +422,32 @@ static void xrandr_read(struct vo_x11_state *x11) return; } + /* Look at the available providers on the current screen and try to determine + * the driver. If amd/intel/radeon, assume this is mesa. If nvidia is found, + * assume nvidia. Because the same screen can have multiple providers (e.g. + * a laptop with switchable graphics), we need to know both of these things. + * In practice, this is used for determining whether or not to use XPresent + * (i.e. needs to be Mesa and not Nvidia). Requires Randr 1.4. */ + XRRProviderResources *pr = XRRGetProviderResources(x11->display, x11->rootwin); + for (int i = 0; i < pr->nproviders; i++) { + XRRProviderInfo *info = XRRGetProviderInfo(x11->display, r, pr->providers[i]); + struct bstr provider_name = bstrdup(x11, bstr0(info->name)); + bstr_lower(provider_name); + int amd = bstr_find0(provider_name, "amd"); + int intel = bstr_find0(provider_name, "intel"); + int modesetting = bstr_find0(provider_name, "modesetting"); + int nouveau = bstr_find0(provider_name, "nouveau"); + int nvidia = bstr_find0(provider_name, "nvidia"); + int radeon = bstr_find0(provider_name, "radeon"); + x11->has_mesa = x11->has_mesa || amd >= 0 || intel >= 0 || + modesetting >= 0 || nouveau >= 0 || radeon >= 0; + x11->has_nvidia = x11->has_nvidia || nvidia >= 0; + XRRFreeProviderInfo(info); + } + if (x11->present_code) + xpresent_set(x11); + XRRFreeProviderResources(pr); + int primary_id = -1; RROutput primary = XRRGetOutputPrimary(x11->display, x11->rootwin); for (int o = 0; o < r->noutput; o++) { @@ -446,6 +495,7 @@ static void xrandr_read(struct vo_x11_state *x11) for (int i = 0; i < x11->num_displays; i++) { struct xrandr_display *d = &(x11->displays[i]); + d->screen = i; if (i == primary_id) { d->atom_id = 0; @@ -461,47 +511,81 @@ static void xrandr_read(struct vo_x11_state *x11) XRRFreeScreenResources(r); } -static void vo_x11_update_screeninfo(struct vo *vo) +static int vo_x11_select_screen(struct vo *vo) { struct vo_x11_state *x11 = vo->x11; struct mp_vo_opts *opts = x11->opts; - bool all_screens = opts->fullscreen && opts->fsscreen_id == -2; - x11->screenrc = (struct mp_rect){.x1 = x11->ws_width, .y1 = x11->ws_height}; - if (opts->screen_id >= -1 && XineramaIsActive(x11->display) && !all_screens) - { - int screen = opts->fullscreen ? opts->fsscreen_id : opts->screen_id; - XineramaScreenInfo *screens; - int num_screens; - + int screen = -2; // all displays + if (!opts->fullscreen || opts->fsscreen_id != -2) { + screen = opts->fullscreen ? opts->fsscreen_id : opts->screen_id; if (opts->fullscreen && opts->fsscreen_id == -1) screen = opts->screen_id; - screens = XineramaQueryScreens(x11->display, &num_screens); - if (screen >= num_screens) - screen = num_screens - 1; + if (screen == -1 && (opts->fsscreen_name || opts->screen_name)) { + char *screen_name = opts->fullscreen ? opts->fsscreen_name : opts->screen_name; + if (screen_name) { + bool screen_found = false; + for (int n = 0; n < x11->num_displays; n++) { + char *display_name = x11->displays[n].name; + if (!strcmp(display_name, screen_name)) { + screen = n; + screen_found = true; + break; + } + } + if (!screen_found) + MP_WARN(x11, "Screen name %s not found!\n", screen_name); + } + } + + if (screen >= x11->num_displays) + screen = x11->num_displays - 1; + } + return screen; +} + +static void vo_x11_update_screeninfo(struct vo *vo) +{ + struct vo_x11_state *x11 = vo->x11; + x11->screenrc = (struct mp_rect){.x1 = x11->ws_width, .y1 = x11->ws_height}; + int screen = vo_x11_select_screen(vo); + if (screen >= -1) { if (screen == -1) { int x = x11->winrc.x0 + RC_W(x11->winrc) / 2; int y = x11->winrc.y0 + RC_H(x11->winrc) / 2; - for (screen = num_screens - 1; screen > 0; screen--) { - int left = screens[screen].x_org; - int right = left + screens[screen].width; - int top = screens[screen].y_org; - int bottom = top + screens[screen].height; + for (screen = x11->num_displays - 1; screen > 0; screen--) { + struct xrandr_display *disp = &x11->displays[screen]; + int left = disp->rc.x0; + int right = disp->rc.x1; + int top = disp->rc.y0; + int bottom = disp->rc.y1; if (left <= x && x <= right && top <= y && y <= bottom) break; } } + if (screen < 0) screen = 0; x11->screenrc = (struct mp_rect){ - .x0 = screens[screen].x_org, - .y0 = screens[screen].y_org, - .x1 = screens[screen].x_org + screens[screen].width, - .y1 = screens[screen].y_org + screens[screen].height, + .x0 = x11->displays[screen].rc.x0, + .y0 = x11->displays[screen].rc.y0, + .x1 = x11->displays[screen].rc.x1, + .y1 = x11->displays[screen].rc.y1, }; + } +} - XFree(screens); +static struct xrandr_display *get_current_display(struct vo *vo) +{ + struct vo_x11_state *x11 = vo->x11; + struct xrandr_display *selected_disp = NULL; + for (int n = 0; n < x11->num_displays; n++) { + struct xrandr_display *disp = &x11->displays[n]; + disp->overlaps = rc_overlaps(disp->rc, x11->winrc); + if (disp->overlaps && (!selected_disp || disp->fps < selected_disp->fps)) + selected_disp = disp; } + return selected_disp; } // Get the monitors for the 4 edges of the rectangle spanning all screens. @@ -509,25 +593,84 @@ static void vo_x11_get_bounding_monitors(struct vo_x11_state *x11, long b[4]) { //top bottom left right b[0] = b[1] = b[2] = b[3] = 0; - int num_screens = 0; - XineramaScreenInfo *screens = XineramaQueryScreens(x11->display, &num_screens); - if (!screens) - return; - for (int n = 0; n < num_screens; n++) { - XineramaScreenInfo *s = &screens[n]; - if (s->y_org < screens[b[0]].y_org) + for (int n = 0; n < x11->num_displays; n++) { + struct xrandr_display *d = &x11->displays[n]; + if (d->rc.y0 < x11->displays[b[0]].rc.y0) b[0] = n; - if (s->y_org + s->height > screens[b[1]].y_org + screens[b[1]].height) + if (d->rc.y1 < x11->displays[b[1]].rc.y1) b[1] = n; - if (s->x_org < screens[b[2]].x_org) + if (d->rc.x0 < x11->displays[b[2]].rc.x0) b[2] = n; - if (s->x_org + s->width > screens[b[3]].x_org + screens[b[3]].width) + if (d->rc.x1 < x11->displays[b[3]].rc.x1) b[3] = n; } - XFree(screens); } -int vo_x11_init(struct vo *vo) +// Get the dpi scale of the x11 screen. Almost no GUI programs use this value +// nowadays, and it has inconsistent behavior for different drivers. +// (it returns the physical display DPI for proprietary NVIDIA driver only, +// but essentially a user-set prefrence value everywhere else) +static void vo_x11_get_x11_screen_dpi_scale(struct vo_x11_state *x11) +{ + int w_mm = DisplayWidthMM(x11->display, x11->screen); + int h_mm = DisplayHeightMM(x11->display, x11->screen); + double dpi_x = x11->ws_width * 25.4 / w_mm; + double dpi_y = x11->ws_height * 25.4 / h_mm; + double base_dpi = 96; + if (isfinite(dpi_x) && isfinite(dpi_y)) { + int s_x = lrint(MPCLAMP(2 * dpi_x / base_dpi, 0, 20)); + int s_y = lrint(MPCLAMP(2 * dpi_y / base_dpi, 0, 20)); + if (s_x == s_y && s_x > 2 && s_x < 20) { + x11->dpi_scale = s_x / 2.0; + MP_VERBOSE(x11, "Using X11 screen DPI scale: %g", x11->dpi_scale); + } + } +} + +// Get the dpi scale from the Xft.dpi resource. In practice, this value is much more +// commonly used by GUI programs for scaling compared to the x11 screen dpi. +// This is always a preference value so it's also more consistent. +static bool vo_x11_get_xft_dpi_scale(struct vo_x11_state *x11) +{ + XrmInitialize(); + char *resman = XResourceManagerString(x11->display); + if (!resman) + return false; + + XrmDatabase db = XrmGetStringDatabase(resman); + if (!db) + return false; + + XrmValue ret; + char *type; + double base_dpi = 96; + bool success = false; + if (XrmGetResource(db, "Xft.dpi", "String", &type, &ret) == True && + ret.addr && !strcmp("String", type)) + { + char *end; + long value = strtol(ret.addr, &end, 10); + if (*ret.addr && *end == '\0') { + int s = lrint(MPCLAMP(2 * value / base_dpi, 0, 20)); + if (s > 2 && s < 20) { + x11->dpi_scale = s / 2.0; + MP_VERBOSE(x11, "Using Xft.dpi scale: %g", x11->dpi_scale); + success = true; + } + } + } + XrmDestroyDatabase(db); + return success; +} + +static void vo_x11_get_dpi_scale(struct vo_x11_state *x11) +{ + if (!vo_x11_get_xft_dpi_scale(x11)) + vo_x11_get_x11_screen_dpi_scale(x11); + x11->pending_vo_events |= VO_EVENT_DPI; +} + +bool vo_x11_init(struct vo *vo) { char *dispName; @@ -550,6 +693,7 @@ int vo_x11_init(struct vo *vo) x11_error_output = x11->log; XSetErrorHandler(x11_errorhandler); + x11->present = mp_present_initialize(x11, x11->opts, VO_MAX_SWAPCHAIN_DEPTH); dispName = XDisplayName(NULL); @@ -590,21 +734,7 @@ int vo_x11_init(struct vo *vo) x11->ws_width, x11->ws_height, dispName, x11->display_is_local ? "local" : "remote"); - int w_mm = DisplayWidthMM(x11->display, x11->screen); - int h_mm = DisplayHeightMM(x11->display, x11->screen); - double dpi_x = x11->ws_width * 25.4 / w_mm; - double dpi_y = x11->ws_height * 25.4 / h_mm; - double base_dpi = 96; - if (isfinite(dpi_x) && isfinite(dpi_y) && x11->opts->hidpi_window_scale) { - int s_x = lrint(MPCLAMP(dpi_x / base_dpi, 0, 10)); - int s_y = lrint(MPCLAMP(dpi_y / base_dpi, 0, 10)); - if (s_x == s_y && s_x > 1 && s_x < 10) { - x11->dpi_scale = s_x; - MP_VERBOSE(x11, "Assuming DPI scale %d for prescaling. This can " - "be disabled with --hidpi-window-scale=no.\n", - x11->dpi_scale); - } - } + vo_x11_get_dpi_scale(x11); x11->wm_type = vo_wm_detect(vo); @@ -615,11 +745,11 @@ int vo_x11_init(struct vo *vo) vo_x11_update_geometry(vo); - return 1; + return true; error: vo_x11_uninit(vo); - return 0; + return false; } static const struct mp_keymap keymap[] = { @@ -643,6 +773,10 @@ static const struct mp_keymap keymap[] = { {XK_F4, MP_KEY_F+4}, {XK_F5, MP_KEY_F+5}, {XK_F6, MP_KEY_F+6}, {XK_F7, MP_KEY_F+7}, {XK_F8, MP_KEY_F+8}, {XK_F9, MP_KEY_F+9}, {XK_F10, MP_KEY_F+10}, {XK_F11, MP_KEY_F+11}, {XK_F12, MP_KEY_F+12}, + {XK_F13, MP_KEY_F+13}, {XK_F14, MP_KEY_F+14}, {XK_F15, MP_KEY_F+15}, + {XK_F16, MP_KEY_F+16}, {XK_F17, MP_KEY_F+17}, {XK_F18, MP_KEY_F+18}, + {XK_F19, MP_KEY_F+19}, {XK_F20, MP_KEY_F+20}, {XK_F21, MP_KEY_F+21}, + {XK_F22, MP_KEY_F+22}, {XK_F23, MP_KEY_F+23}, {XK_F24, MP_KEY_F+24}, // numpad independent of numlock {XK_KP_Subtract, '-'}, {XK_KP_Add, '+'}, {XK_KP_Multiply, '*'}, @@ -656,11 +790,11 @@ static const struct mp_keymap keymap[] = { {XK_KP_Separator, MP_KEY_KPDEC}, // numpad without numlock - {XK_KP_Insert, MP_KEY_KPINS}, {XK_KP_End, MP_KEY_KP1}, - {XK_KP_Down, MP_KEY_KP2}, {XK_KP_Page_Down, MP_KEY_KP3}, - {XK_KP_Left, MP_KEY_KP4}, {XK_KP_Begin, MP_KEY_KP5}, - {XK_KP_Right, MP_KEY_KP6}, {XK_KP_Home, MP_KEY_KP7}, {XK_KP_Up, MP_KEY_KP8}, - {XK_KP_Page_Up, MP_KEY_KP9}, {XK_KP_Delete, MP_KEY_KPDEL}, + {XK_KP_Insert, MP_KEY_KPINS}, {XK_KP_End, MP_KEY_KPEND}, + {XK_KP_Down, MP_KEY_KPDOWN}, {XK_KP_Page_Down, MP_KEY_KPPGDOWN}, + {XK_KP_Left, MP_KEY_KPLEFT}, {XK_KP_Begin, MP_KEY_KP5}, + {XK_KP_Right, MP_KEY_KPRIGHT}, {XK_KP_Home, MP_KEY_KPHOME}, {XK_KP_Up, MP_KEY_KPUP}, + {XK_KP_Page_Up, MP_KEY_KPPGUP}, {XK_KP_Delete, MP_KEY_KPDEL}, {XF86XK_MenuKB, MP_KEY_MENU}, {XF86XK_AudioPlay, MP_KEY_PLAY}, {XF86XK_AudioPause, MP_KEY_PAUSE}, @@ -673,6 +807,9 @@ static const struct mp_keymap keymap[] = { {XF86XK_HomePage, MP_KEY_HOMEPAGE}, {XF86XK_WWW, MP_KEY_WWW}, {XF86XK_Mail, MP_KEY_MAIL}, {XF86XK_Favorites, MP_KEY_FAVORITES}, {XF86XK_Search, MP_KEY_SEARCH}, {XF86XK_Sleep, MP_KEY_SLEEP}, + {XF86XK_Back, MP_KEY_GO_BACK}, {XF86XK_Forward, MP_KEY_GO_FORWARD}, + {XF86XK_Tools, MP_KEY_TOOLS}, + {XF86XK_ZoomIn, MP_KEY_ZOOMIN}, {XF86XK_ZoomOut, MP_KEY_ZOOMOUT}, {0, 0} }; @@ -700,7 +837,7 @@ static int vo_x11_lookupkey(int key) return mpkey; } -static void vo_x11_decoration(struct vo *vo, bool d) +static void vo_x11_decoration(struct vo *vo, bool decorations, bool title_bar) { struct vo_x11_state *x11 = vo->x11; @@ -711,8 +848,9 @@ static void vo_x11_decoration(struct vo *vo, bool d) MotifWmHints mhints = {0}; bool got = x11_get_property_copy(x11, x11->window, motif_hints, motif_hints, 32, &mhints, sizeof(mhints)); - // hints weren't set, and decorations requested -> assume WM displays them - if (!got && d) + // If hints weren't set, and decorations and title bar requested, + // assume WM displays them. + if (!got && decorations && title_bar) return; if (!got) { mhints.flags = MWM_HINTS_FUNCTIONS; @@ -720,7 +858,8 @@ static void vo_x11_decoration(struct vo *vo, bool d) MWM_FUNC_MAXIMIZE | MWM_FUNC_RESIZE; } mhints.flags |= MWM_HINTS_DECORATIONS; - mhints.decorations = d ? MWM_DECOR_ALL : 0; + mhints.decorations = decorations ? MWM_DECOR_ALL : 0; + mhints.decorations &= ~(!title_bar ? MWM_DECOR_TITLE : 0); XChangeProperty(x11->display, x11->window, motif_hints, motif_hints, 32, PropModeReplace, (unsigned char *) &mhints, 5); } @@ -759,10 +898,8 @@ void vo_x11_uninit(struct vo *vo) set_screensaver(x11, true); - if (x11->window != None && x11->window != x11->rootwin) { - XUnmapWindow(x11->display, x11->window); + if (x11->window != None && x11->window != x11->rootwin) XDestroyWindow(x11->display, x11->window); - } if (x11->xic) XDestroyIC(x11->xic); if (x11->colormap != None) @@ -909,15 +1046,20 @@ static void vo_x11_dnd_handle_selection(struct vo *vo, XSelectionEvent *se) if (se->selection == XA(x11, XdndSelection) && se->property == XAs(x11, DND_PROPERTY) && - se->target == x11->dnd_requested_format) + se->target == x11->dnd_requested_format && + x11->opts->drag_and_drop != -2) { int nitems; void *prop = x11_get_property(x11, x11->window, XAs(x11, DND_PROPERTY), x11->dnd_requested_format, 8, &nitems); if (prop) { - enum mp_dnd_action action = - x11->dnd_requested_action == XA(x11, XdndActionCopy) ? - DND_REPLACE : DND_APPEND; + enum mp_dnd_action action; + if (x11->opts->drag_and_drop >= 0) { + action = x11->opts->drag_and_drop; + } else { + action = x11->dnd_requested_action == XA(x11, XdndActionCopy) ? + DND_REPLACE : DND_APPEND; + } char *mime_type = x11_dnd_mime_type(x11, x11->dnd_requested_format); MP_VERBOSE(x11, "Dropping type: %s (%s)\n", @@ -1020,6 +1162,7 @@ static void vo_x11_check_net_wm_state_change(struct vo *vo) } opts->window_minimized = is_minimized; + x11->hidden = is_minimized; m_config_cache_write_opt(x11->opts_cache, &opts->window_minimized); opts->window_maximized = is_maximized; m_config_cache_write_opt(x11->opts_cache, &opts->window_maximized); @@ -1071,7 +1214,28 @@ static void release_all_keys(struct vo *vo) if (x11->no_autorepeat) mp_input_put_key(x11->input_ctx, MP_INPUT_RELEASE_ALL); - x11->win_drag_button1_down = false; +} + +static void vo_x11_begin_dragging(struct vo *vo) +{ + struct vo_x11_state *x11 = vo->x11; + XEvent Event = x11->last_button_event; + if (Event.type == ButtonPress && !x11->fs && + !mp_input_test_dragging(x11->input_ctx, Event.xmotion.x, + Event.xmotion.y)) + { + mp_input_put_key(x11->input_ctx, MP_INPUT_RELEASE_ALL); + XUngrabPointer(x11->display, CurrentTime); + + long params[5] = { + Event.xmotion.x_root, Event.xmotion.y_root, + 8, // _NET_WM_MOVERESIZE_MOVE + Event.xbutton.button, + 1, // source indication: normal + }; + x11_send_ewmh_msg(x11, "_NET_WM_MOVERESIZE", params); + x11->last_button_event = (XEvent){0}; + } } void vo_x11_check_events(struct vo *vo) @@ -1084,6 +1248,8 @@ void vo_x11_check_events(struct vo *vo) while (XPending(display)) { XNextEvent(display, &Event); + if (XFilterEvent(&Event, x11->window)) + continue; MP_TRACE(x11, "XEvent: %d\n", Event.type); switch (Event.type) { case Expose: @@ -1140,30 +1306,12 @@ void vo_x11_check_events(struct vo *vo) release_all_keys(vo); break; case MotionNotify: - if (x11->win_drag_button1_down && !x11->fs && - !mp_input_test_dragging(x11->input_ctx, Event.xmotion.x, - Event.xmotion.y)) - { - mp_input_put_key(x11->input_ctx, MP_INPUT_RELEASE_ALL); - XUngrabPointer(x11->display, CurrentTime); - - long params[5] = { - Event.xmotion.x_root, Event.xmotion.y_root, - 8, // _NET_WM_MOVERESIZE_MOVE - 1, // button 1 - 1, // source indication: normal - }; - x11_send_ewmh_msg(x11, "_NET_WM_MOVERESIZE", params); - } else { - mp_input_set_mouse_pos(x11->input_ctx, Event.xmotion.x, - Event.xmotion.y); - } - x11->win_drag_button1_down = false; + mp_input_set_mouse_pos(x11->input_ctx, Event.xmotion.x, + Event.xmotion.y); break; case LeaveNotify: if (Event.xcrossing.mode != NotifyNormal) break; - x11->win_drag_button1_down = false; mp_input_put_key(x11->input_ctx, MP_KEY_MOUSE_LEAVE); break; case EnterNotify: @@ -1174,27 +1322,25 @@ void vo_x11_check_events(struct vo *vo) case ButtonPress: if (Event.xbutton.button - 1 >= MP_KEY_MOUSE_BTN_COUNT) break; - if (Event.xbutton.button == 1) - x11->win_drag_button1_down = true; mp_input_put_key(x11->input_ctx, (MP_MBTN_BASE + Event.xbutton.button - 1) | get_mods(Event.xbutton.state) | MP_KEY_STATE_DOWN); long msg[4] = {XEMBED_REQUEST_FOCUS}; vo_x11_xembed_send_message(x11, msg); + x11->last_button_event = Event; break; case ButtonRelease: if (Event.xbutton.button - 1 >= MP_KEY_MOUSE_BTN_COUNT) break; - if (Event.xbutton.button == 1) - x11->win_drag_button1_down = false; mp_input_put_key(x11->input_ctx, (MP_MBTN_BASE + Event.xbutton.button - 1) | get_mods(Event.xbutton.state) | MP_KEY_STATE_UP); + x11->last_button_event = Event; break; case MapNotify: x11->window_hidden = false; x11->pseudo_mapped = true; - x11->current_icc_screen = -1; + x11->current_screen = -1; vo_x11_update_geometry(vo); break; case DestroyNotify: @@ -1228,6 +1374,22 @@ void vo_x11_check_events(struct vo *vo) x11->pending_vo_events |= VO_EVENT_ICC_PROFILE_CHANGED; } break; + case GenericEvent: { + XGenericEventCookie *cookie = (XGenericEventCookie *)&Event.xcookie; + if (cookie->extension == x11->present_code && x11->use_present) + { + XGetEventData(x11->display, cookie); + if (cookie->evtype == PresentCompleteNotify) { + XPresentCompleteNotifyEvent *present_event; + present_event = (XPresentCompleteNotifyEvent *)cookie->data; + present_sync_update_values(x11->present, + present_event->ust * 1000, + present_event->msc); + } + } + XFreeEventData(x11->display, cookie); + break; + } default: if (Event.type == x11->ShmCompletionEvent) { if (x11->ShmCompletionWaitCount > 0) @@ -1235,6 +1397,7 @@ void vo_x11_check_events(struct vo *vo) } if (Event.type == x11->xrandr_event) { xrandr_read(x11); + vo_x11_get_dpi_scale(x11); vo_x11_update_geometry(vo); } break; @@ -1252,15 +1415,19 @@ static void vo_x11_sizehint(struct vo *vo, struct mp_rect rc, bool override_pos) if (!x11->window || x11->parent) return; + bool screen = opts->screen_id >= 0 || (opts->screen_name && + opts->screen_name[0]); + bool fsscreen = opts->fsscreen_id >= 0 || (opts->fsscreen_name && + opts->fsscreen_name[0]); bool force_pos = opts->geometry.xy_valid || // explicitly forced by user opts->force_window_position || // resize -> reset position - opts->screen_id >= 0 || // force onto screen area + screen || fsscreen || // force onto screen area + opts->screen_name || // also force onto screen area x11->parent || // force to fill parent override_pos; // for fullscreen and such XSizeHints *hint = XAllocSizeHints(); - if (!hint) - return; // OOM + MP_HANDLE_OOM(hint); hint->flags |= PSize | (force_pos ? PPosition : 0); hint->x = rc.x0; @@ -1326,9 +1493,7 @@ static void vo_x11_set_property_string(struct vo *vo, Atom name, const char *t) // can do this correctly. vo_x11_set_property_utf8(vo, name, t); } - - if (prop.value) - XFree(prop.value); + XFree(prop.value); } static void vo_x11_update_window_title(struct vo *vo) @@ -1340,8 +1505,13 @@ static void vo_x11_update_window_title(struct vo *vo) vo_x11_set_property_string(vo, XA_WM_NAME, x11->window_title); vo_x11_set_property_string(vo, XA_WM_ICON_NAME, x11->window_title); - vo_x11_set_property_utf8(vo, XA(x11, _NET_WM_NAME), x11->window_title); - vo_x11_set_property_utf8(vo, XA(x11, _NET_WM_ICON_NAME), x11->window_title); + + /* _NET_WM_NAME and _NET_WM_ICON_NAME must be sanitized to UTF-8. */ + void *tmp = talloc_new(NULL); + struct bstr b_title = bstr_sanitize_utf8_latin1(tmp, bstr0(x11->window_title)); + vo_x11_set_property_utf8(vo, XA(x11, _NET_WM_NAME), bstrto0(tmp, b_title)); + vo_x11_set_property_utf8(vo, XA(x11, _NET_WM_ICON_NAME), bstrto0(tmp, b_title)); + talloc_free(tmp); } static void vo_x11_xembed_update(struct vo_x11_state *x11, int flags) @@ -1449,13 +1619,21 @@ static void vo_x11_create_window(struct vo *vo, XVisualInfo *vis, Atom protos[1] = {XA(x11, WM_DELETE_WINDOW)}; XSetWMProtocols(x11->display, x11->window, protos, 1); + if (!XPresentQueryExtension(x11->display, &x11->present_code, NULL, NULL)) { + MP_VERBOSE(x11, "The XPresent extension is not supported.\n"); + } else { + MP_VERBOSE(x11, "The XPresent extension was found.\n"); + XPresentSelectInput(x11->display, x11->window, PresentCompleteNotifyMask); + } + xpresent_set(x11); + x11->mouse_cursor_set = false; x11->mouse_cursor_visible = true; vo_update_cursor(vo); if (x11->xim) { x11->xic = XCreateIC(x11->xim, - XNInputStyle, XIMPreeditNone | XIMStatusNone, + XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, x11->window, XNFocusWindow, x11->window, NULL); @@ -1464,10 +1642,11 @@ static void vo_x11_create_window(struct vo *vo, XVisualInfo *vis, if (!x11->parent) { vo_x11_update_composition_hint(vo); vo_x11_set_wm_icon(x11); - vo_x11_update_window_title(vo); vo_x11_dnd_init_window(vo); vo_x11_set_property_utf8(vo, XA(x11, _GTK_THEME_VARIANT), "dark"); } + if (!x11->parent || x11->opts->x11_wid_title) + vo_x11_update_window_title(vo); vo_x11_xembed_update(x11, 0); } @@ -1476,7 +1655,7 @@ static void vo_x11_map_window(struct vo *vo, struct mp_rect rc) struct vo_x11_state *x11 = vo->x11; vo_x11_move_resize(vo, true, true, rc); - vo_x11_decoration(vo, x11->opts->border); + vo_x11_decoration(vo, x11->opts->border, x11->opts->title_bar); if (x11->opts->fullscreen && (x11->wm_type & vo_wm_FULLSCREEN)) { Atom state = XA(x11, _NET_WM_STATE_FULLSCREEN); @@ -1500,9 +1679,18 @@ static void vo_x11_map_window(struct vo *vo, struct mp_rect rc) x11_send_ewmh_msg(x11, "_NET_WM_FULLSCREEN_MONITORS", params); } - if (x11->opts->all_workspaces || x11->opts->geometry.ws > 0) { - long v = x11->opts->all_workspaces - ? 0xFFFFFFFF : x11->opts->geometry.ws - 1; + if (x11->opts->all_workspaces) { + if (x11->wm_type & vo_wm_STICKY) { + Atom state = XA(x11, _NET_WM_STATE_STICKY); + XChangeProperty(x11->display, x11->window, XA(x11, _NET_WM_STATE), XA_ATOM, + 32, PropModeReplace, (unsigned char *)&state, 1); + } else { + long v = 0xFFFFFFFF; + XChangeProperty(x11->display, x11->window, XA(x11, _NET_WM_DESKTOP), + XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&v, 1); + } + } else if (x11->opts->geometry.ws > 0) { + long v = x11->opts->geometry.ws - 1; XChangeProperty(x11->display, x11->window, XA(x11, _NET_WM_DESKTOP), XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&v, 1); } @@ -1519,6 +1707,9 @@ static void vo_x11_map_window(struct vo *vo, struct mp_rect rc) vo_x11_selectinput_witherr(vo, x11->display, x11->window, events); XMapWindow(x11->display, x11->window); + if (x11->opts->cursor_passthrough) + vo_x11_set_input_region(vo, true); + if (x11->opts->window_maximized) // don't override WM default on "no" vo_x11_maximize(vo); if (x11->opts->window_minimized) // don't override WM default on "no" @@ -1530,12 +1721,12 @@ static void vo_x11_map_window(struct vo *vo, struct mp_rect rc) vo_x11_xembed_update(x11, XEMBED_MAPPED); } -static void vo_x11_highlevel_resize(struct vo *vo, struct mp_rect rc) +static void vo_x11_highlevel_resize(struct vo *vo, struct mp_rect rc, bool force) { struct vo_x11_state *x11 = vo->x11; struct mp_vo_opts *opts = x11->opts; - bool reset_pos = opts->force_window_position; + bool reset_pos = opts->force_window_position || force; if (reset_pos) { x11->nofsrc = rc; } else { @@ -1618,17 +1809,25 @@ void vo_x11_config_vo_window(struct vo *vo) rc = (struct mp_rect){0, 0, RC_W(x11->winrc), RC_H(x11->winrc)}; } - bool reset_size = x11->old_dw != RC_W(rc) || x11->old_dh != RC_H(rc); + bool reset_size = (x11->old_dw != RC_W(rc) || x11->old_dh != RC_H(rc)) && + (opts->auto_window_resize || x11->geometry_change); + reset_size |= (x11->old_x != rc.x0 || x11->old_y != rc.y0) && + (x11->geometry_change); + x11->old_dw = RC_W(rc); x11->old_dh = RC_H(rc); + x11->old_x = rc.x0; + x11->old_y = rc.y0; if (x11->window_hidden) { x11->nofsrc = rc; vo_x11_map_window(vo, rc); } else if (reset_size) { - vo_x11_highlevel_resize(vo, rc); + vo_x11_highlevel_resize(vo, rc, x11->geometry_change); } + x11->geometry_change = false; + if (opts->ontop) vo_x11_setlayer(vo, opts->ontop); @@ -1640,6 +1839,23 @@ void vo_x11_config_vo_window(struct vo *vo) x11->pending_vo_events &= ~VO_EVENT_RESIZE; // implicitly done by the VO } +static void vo_x11_sticky(struct vo *vo, bool sticky) +{ + struct vo_x11_state *x11 = vo->x11; + if (x11->wm_type & vo_wm_STICKY) { + x11_set_ewmh_state(x11, "_NET_WM_STATE_STICKY", sticky); + } else { + long params[5] = {0xFFFFFFFF, 1}; + if (!sticky) { + x11_get_property_copy(x11, x11->rootwin, + XA(x11, _NET_CURRENT_DESKTOP), + XA_CARDINAL, 32, ¶ms[0], + sizeof(params[0])); + } + x11_send_ewmh_msg(x11, "_NET_WM_DESKTOP", params); + } +} + static void vo_x11_setlayer(struct vo *vo, bool ontop) { struct vo_x11_state *x11 = vo->x11; @@ -1679,23 +1895,6 @@ static bool rc_overlaps(struct mp_rect rc1, struct mp_rect rc2) return mp_rect_intersection(&rc1, &rc2); // changes the first argument } -// which screen's ICC profile we're going to use -static int get_icc_screen(struct vo *vo) -{ - struct vo_x11_state *x11 = vo->x11; - int cx = x11->winrc.x0 + (x11->winrc.x1 - x11->winrc.x0)/2, - cy = x11->winrc.y0 + (x11->winrc.y1 - x11->winrc.y0)/2; - int screen = x11->current_icc_screen; // xinerama screen number - for (int n = 0; n < x11->num_displays; n++) { - struct xrandr_display *disp = &x11->displays[n]; - if (mp_rect_contains(&disp->rc, cx, cy)) { - screen = n; - break; - } - } - return screen; -} - // update x11->winrc with current boundaries of vo->x11->window static void vo_x11_update_geometry(struct vo *vo) { @@ -1715,25 +1914,25 @@ static void vo_x11_update_geometry(struct vo *vo) &x, &y, &dummy_win); x11->winrc = (struct mp_rect){x, y, x + w, y + h}; } - double fps = 1000.0; - for (int n = 0; n < x11->num_displays; n++) { - struct xrandr_display *disp = &x11->displays[n]; - disp->overlaps = rc_overlaps(disp->rc, x11->winrc); - if (disp->overlaps) - fps = MPMIN(fps, disp->fps); + struct xrandr_display *disp = get_current_display(vo); + // Try to fallback to something reasonable if we have no disp yet + if (!disp) { + int screen = vo_x11_select_screen(vo); + if (screen > -1) { + disp = &x11->displays[screen]; + } else if (x11->current_screen > - 1) { + disp = &x11->displays[x11->current_screen]; + } } - double fallback = x11->num_displays > 0 ? x11->displays[0].fps : 0; - fps = fps < 1000.0 ? fps : fallback; + double fps = disp ? disp->fps : 0; if (fps != x11->current_display_fps) MP_VERBOSE(x11, "Current display FPS: %f\n", fps); x11->current_display_fps = fps; - // might have changed displays - x11->pending_vo_events |= VO_EVENT_WIN_STATE; - int icc_screen = get_icc_screen(vo); - if (x11->current_icc_screen != icc_screen) { - x11->current_icc_screen = icc_screen; + if (disp && x11->current_screen != disp->screen) { + x11->current_screen = disp-&g |