From 9208663c53828654deff3bb73fe6ce3a68eb527e Mon Sep 17 00:00:00 2001 From: Themaister Date: Fri, 12 Oct 2012 19:05:29 +0200 Subject: [PATCH] Add basic Xinerama support to X11. --- Makefile | 4 +- gfx/context/glx_ctx.c | 66 ++++++++++++++++++++-- gfx/context/x11_common.c | 116 +++++++++++++++++++++++++++++++++++++-- gfx/context/x11_common.h | 18 ++++++ gfx/context/xegl_ctx.c | 66 ++++++++++++++++++++-- input/x11_input.c | 8 ++- qb/config.libs.sh | 5 +- qb/config.params.sh | 1 + 8 files changed, 260 insertions(+), 24 deletions(-) diff --git a/Makefile b/Makefile index 6ea9aab229..69f21d895e 100644 --- a/Makefile +++ b/Makefile @@ -216,8 +216,8 @@ endif ifeq ($(HAVE_X11), 1) OBJ += input/x11_input.o gfx/context/x11_common.o - LIBS += $(X11_LIBS) $(XEXT_LIBS) $(XF86VM_LIBS) - DEFINES += $(X11_CFLAGS) $(XEXT_CFLAGS) $(XF86VM_CFLAGS) + LIBS += $(X11_LIBS) $(XEXT_LIBS) $(XF86VM_LIBS) $(XINERAMA_LIBS) + DEFINES += $(X11_CFLAGS) $(XEXT_CFLAGS) $(XF86VM_CFLAGS) $(XINERAMA_CFLAGS) endif ifeq ($(HAVE_CG), 1) diff --git a/gfx/context/glx_ctx.c b/gfx/context/glx_ctx.c index a8a0dd68a3..8046e8c12a 100644 --- a/gfx/context/glx_ctx.c +++ b/gfx/context/glx_ctx.c @@ -33,6 +33,7 @@ static Window g_win; static Colormap g_cmap; static Atom g_quit_atom; static bool g_has_focus; +static unsigned g_screen; static GLXContext g_ctx; static GLXFBConfig g_fbc; @@ -259,28 +260,63 @@ static bool gfx_ctx_set_video_mode( } } + int x_off = 0; + int y_off = 0; +#ifdef HAVE_XINERAMA + if (fullscreen || g_screen != 0) + { + unsigned new_width = width; + unsigned new_height = height; + if (x11_get_xinerama_coord(g_dpy, g_screen, &x_off, &y_off, &new_width, &new_height)) + RARCH_LOG("[GLX]: Using Xinerama on screen #%u.\n", g_screen); + else + RARCH_LOG("[GLX]: Xinerama is not active on screen.\n"); + + if (fullscreen) + { + width = new_width; + height = new_height; + } + } +#endif + + RARCH_LOG("[GLX]: X = %d, Y = %d, W = %u, H = %u.\n", + x_off, y_off, width, height); + g_win = XCreateWindow(g_dpy, RootWindow(g_dpy, vi->screen), - 0, 0, width ? width : 200, height ? height : 200, 0, + x_off, y_off, width, height, 0, vi->depth, InputOutput, vi->visual, CWBorderPixel | CWColormap | CWEventMask | (true_full ? CWOverrideRedirect : 0), &swa); XSetWindowBackground(g_dpy, g_win, 0); gfx_ctx_update_window_title(true); - x11_hide_mouse(g_dpy, g_win); + + if (fullscreen) + x11_hide_mouse(g_dpy, g_win); if (true_full) { + RARCH_LOG("[GLX]: Using true fullscreen.\n"); XMapRaised(g_dpy, g_win); - XGrabKeyboard(g_dpy, g_win, True, GrabModeAsync, GrabModeAsync, CurrentTime); } else if (fullscreen) // We attempted true fullscreen, but failed. Attempt using windowed fullscreen. { XMapRaised(g_dpy, g_win); - RARCH_WARN("[GLX]: Using windowed fullscreen.\n"); + RARCH_LOG("[GLX]: Using windowed fullscreen.\n"); + // We have to move the window to the screen we want to go fullscreen on first. + // x_off and y_off usually get ignored in XCreateWindow(). + x11_move_window(g_dpy, g_win, x_off, y_off, width, height); x11_windowed_fullscreen(g_dpy, g_win); } else + { XMapWindow(g_dpy, g_win); + // If we want to map the window on a different screen, we'll have to do it by force. + // Otherwise, we should try to let the window manager sort it out. + // x_off and y_off usually get ignored in XCreateWindow(). + if (g_screen) + x11_move_window(g_dpy, g_win, x_off, y_off, width, height); + } XEvent event; XIfEvent(g_dpy, &event, glx_wait_notify, NULL); @@ -326,7 +362,9 @@ static bool gfx_ctx_set_video_mode( driver.display_type = RARCH_DISPLAY_X11; driver.video_display = (uintptr_t)g_dpy; - driver.video_window = (uintptr_t)g_win; + + // Always assume that we have focus in true fullscreen. + driver.video_window = true_full ? (uintptr_t)None : (uintptr_t)g_win; return true; @@ -349,6 +387,22 @@ static void gfx_ctx_destroy(void) if (g_win) { + // Save last used monitor for later. +#ifdef HAVE_XINERAMA + XWindowAttributes target; + Window child; + + int x = 0, y = 0; + XGetWindowAttributes(g_dpy, g_win, &target); + XTranslateCoordinates(g_dpy, g_win, DefaultRootWindow(g_dpy), + target.x, target.y, &x, &y, &child); + + g_screen = x11_get_xinerama_monitor(g_dpy, x, y, + target.width, target.height); + + RARCH_LOG("[GLX]: Saved monitor #%u.\n", g_screen); +#endif + XUnmapWindow(g_dpy, g_win); XDestroyWindow(g_dpy, g_win); g_win = None; @@ -391,7 +445,7 @@ static bool gfx_ctx_has_focus(void) int rev; XGetInputFocus(g_dpy, &win, &rev); - return win == g_win && g_has_focus; + return (win == g_win && g_has_focus) || (g_win == None); } static gfx_ctx_proc_t gfx_ctx_get_proc_address(const char *symbol) diff --git a/gfx/context/x11_common.c b/gfx/context/x11_common.c index 6ff1f8fd73..c4ed2c4028 100644 --- a/gfx/context/x11_common.c +++ b/gfx/context/x11_common.c @@ -46,8 +46,12 @@ void x11_hide_mouse(Display *dpy, Window win) static Atom XA_NET_WM_STATE; static Atom XA_NET_WM_STATE_FULLSCREEN; +static Atom XA_NET_MOVERESIZE_WINDOW; #define XA_INIT(x) XA##x = XInternAtom(dpy, #x, False) #define _NET_WM_STATE_ADD 1 +#define MOVERESIZE_GRAVITY_CENTER 5 +#define MOVERESIZE_X_SHIFT 8 +#define MOVERESIZE_Y_SHIFT 9 void x11_windowed_fullscreen(Display *dpy, Window win) { XA_INIT(_NET_WM_STATE); @@ -55,23 +59,46 @@ void x11_windowed_fullscreen(Display *dpy, Window win) if (!XA_NET_WM_STATE || !XA_NET_WM_STATE_FULLSCREEN) { - RARCH_ERR("[X/EGL]: Cannot set windowed fullscreen.\n"); + RARCH_ERR("[X11]: Cannot set windowed fullscreen.\n"); return; } - XEvent xev; + XEvent xev = {0}; xev.xclient.type = ClientMessage; - xev.xclient.serial = 0; xev.xclient.send_event = True; xev.xclient.message_type = XA_NET_WM_STATE; xev.xclient.window = win; xev.xclient.format = 32; xev.xclient.data.l[0] = _NET_WM_STATE_ADD; xev.xclient.data.l[1] = XA_NET_WM_STATE_FULLSCREEN; - xev.xclient.data.l[2] = 0; - xev.xclient.data.l[3] = 0; - xev.xclient.data.l[4] = 0; + + XSendEvent(dpy, DefaultRootWindow(dpy), False, + SubstructureRedirectMask | SubstructureNotifyMask, + &xev); +} + +// Try to be nice to tiling WMs if possible. +void x11_move_window(Display *dpy, Window win, int x, int y, + unsigned width, unsigned height) +{ + XA_INIT(_NET_MOVERESIZE_WINDOW); + if (!XA_NET_MOVERESIZE_WINDOW) + { + RARCH_ERR("[X11]: Cannot move window.\n"); + return; + } + + XEvent xev = {0}; + + xev.xclient.type = ClientMessage; + xev.xclient.send_event = True; + xev.xclient.message_type = XA_NET_MOVERESIZE_WINDOW; + xev.xclient.window = win; + xev.xclient.format = 32; + xev.xclient.data.l[0] = (1 << MOVERESIZE_X_SHIFT) | (1 << MOVERESIZE_Y_SHIFT); + xev.xclient.data.l[1] = x; + xev.xclient.data.l[2] = y; XSendEvent(dpy, DefaultRootWindow(dpy), False, SubstructureRedirectMask | SubstructureNotifyMask, @@ -141,3 +168,80 @@ void x11_exit_fullscreen(Display *dpy, XF86VidModeModeInfo *desktop_mode) XF86VidModeSetViewPort(dpy, DefaultScreen(dpy), 0, 0); } +#ifdef HAVE_XINERAMA +static XineramaScreenInfo *x11_query_screens(Display *dpy, int *num_screens) +{ + int major, minor; + if (!XineramaQueryExtension(dpy, &major, &minor)) + return false; + + XineramaQueryVersion(dpy, &major, &minor); + RARCH_LOG("[X11]: Xinerama version: %d.%d.\n", major, minor); + + if (!XineramaIsActive(dpy)) + return false; + + return XineramaQueryScreens(dpy, num_screens); +} + +bool x11_get_xinerama_coord(Display *dpy, int screen, + int *x, int *y, unsigned *w, unsigned *h) +{ + bool ret = false; + + int num_screens = 0; + XineramaScreenInfo *info = x11_query_screens(dpy, &num_screens); + RARCH_LOG("[X11]: Xinerama screens: %d.\n", num_screens); + + for (int i = 0; i < num_screens; i++) + { + if (info[i].screen_number == screen) + { + *x = info[i].x_org; + *y = info[i].y_org; + *w = info[i].width; + *h = info[i].height; + ret = true; + break; + } + } + + XFree(info); + return ret; +} + +unsigned x11_get_xinerama_monitor(Display *dpy, int x, int y, + int w, int h) +{ + unsigned monitor = 0; + int largest_area = 0; + + int num_screens = 0; + XineramaScreenInfo *info = x11_query_screens(dpy, &num_screens); + RARCH_LOG("[X11]: Xinerama screens: %d.\n", num_screens); + + for (int i = 0; i < num_screens; i++) + { + int max_lx = max(x, info[i].x_org); + int min_rx = min(x + w, info[i].x_org + info[i].width); + int max_ty = max(y, info[i].y_org); + int min_by = min(y + h, info[i].y_org + info[i].height); + + int len_x = min_rx - max_lx; + int len_y = min_by - max_ty; + if (len_x < 0 || len_y < 0) // The whole window is outside the screen. + continue; + + int area = len_x * len_y; + if (area > largest_area) + { + monitor = i; + largest_area = area; + } + } + + XFree(info); + return monitor; +} +#endif + diff --git a/gfx/context/x11_common.h b/gfx/context/x11_common.h index 2d4bafa1c0..5c5b4e543e 100644 --- a/gfx/context/x11_common.h +++ b/gfx/context/x11_common.h @@ -17,9 +17,18 @@ #ifndef X11_COMMON_H__ #define X11_COMMON_H__ +#ifdef HAVE_CONFIG_H +#include "../../config.h" +#endif + #include #include #include + +#ifdef HAVE_XINERAMA +#include +#endif + #include "../../boolean.h" void x11_hide_mouse(Display *dpy, Window win); @@ -27,6 +36,15 @@ void x11_windowed_fullscreen(Display *dpy, Window win); void x11_suspend_screensaver(Window win); bool x11_enter_fullscreen(Display *dpy, unsigned width, unsigned height, XF86VidModeModeInfo *desktop_mode); void x11_exit_fullscreen(Display *dpy, XF86VidModeModeInfo *desktop_mode); +void x11_move_window(Display *dpy, Window win, int x, int y, unsigned width, unsigned height); + +#ifdef HAVE_XINERAMA +bool x11_get_xinerama_coord(Display *dpy, int screen, + int *x, int *y, unsigned *w, unsigned *h); + +unsigned x11_get_xinerama_monitor(Display *dpy, + int x, int y, int w, int h); +#endif #endif diff --git a/gfx/context/xegl_ctx.c b/gfx/context/xegl_ctx.c index 9593e707c8..9e3e94fd00 100644 --- a/gfx/context/xegl_ctx.c +++ b/gfx/context/xegl_ctx.c @@ -35,6 +35,7 @@ static Window g_win; static Colormap g_cmap; static Atom g_quit_atom; static bool g_has_focus; +static unsigned g_screen; static EGLContext g_egl_ctx; static EGLSurface g_egl_surf; @@ -287,8 +288,31 @@ static bool gfx_ctx_set_video_mode( } } + int x_off = 0; + int y_off = 0; +#ifdef HAVE_XINERAMA + if (fullscreen || g_screen != 0) + { + unsigned new_width = width; + unsigned new_height = height; + if (x11_get_xinerama_coord(g_dpy, g_screen, &x_off, &y_off, &new_width, &new_height)) + RARCH_LOG("[X/EGL]: Using Xinerama on screen #%u.\n", g_screen); + else + RARCH_LOG("[X/EGL]: Xinerama is not active on screen.\n"); + + if (fullscreen) + { + width = new_width; + height = new_height; + } + } +#endif + + RARCH_LOG("[X/EGL]: X = %d, Y = %d, W = %u, H = %u.\n", + x_off, y_off, width, height); + g_win = XCreateWindow(g_dpy, RootWindow(g_dpy, vi->screen), - 0, 0, width ? width : 200, height ? height : 200, 0, + x_off, y_off, width, height, 0, vi->depth, InputOutput, vi->visual, CWBorderPixel | CWColormap | CWEventMask | (true_full ? CWOverrideRedirect : 0), &swa); XSetWindowBackground(g_dpy, g_win, 0); @@ -313,21 +337,33 @@ static bool gfx_ctx_set_video_mode( goto error; gfx_ctx_update_window_title(true); - x11_hide_mouse(g_dpy, g_win); + + if (fullscreen) + x11_hide_mouse(g_dpy, g_win); if (true_full) { + RARCH_LOG("[GLX]: Using true fullscreen.\n"); XMapRaised(g_dpy, g_win); - XGrabKeyboard(g_dpy, g_win, True, GrabModeAsync, GrabModeAsync, CurrentTime); } else if (fullscreen) // We attempted true fullscreen, but failed. Attempt using windowed fullscreen. { XMapRaised(g_dpy, g_win); - RARCH_WARN("[X/EGL]: Using windowed fullscreen.\n"); + RARCH_LOG("[X/EGL]: Using windowed fullscreen.\n"); + // We have to move the window to the screen we want to go fullscreen on first. + // x_off and y_off usually get ignored in XCreateWindow(). + x11_move_window(g_dpy, g_win, x_off, y_off, width, height); x11_windowed_fullscreen(g_dpy, g_win); } else + { XMapWindow(g_dpy, g_win); + // If we want to map the window on a different screen, we'll have to do it by force. + // Otherwise, we should try to let the window manager sort it out. + // x_off and y_off usually get ignored in XCreateWindow(). + if (g_screen) + x11_move_window(g_dpy, g_win, x_off, y_off, width, height); + } XEvent event; XIfEvent(g_dpy, &event, egl_wait_notify, NULL); @@ -345,7 +381,9 @@ static bool gfx_ctx_set_video_mode( driver.display_type = RARCH_DISPLAY_X11; driver.video_display = (uintptr_t)g_dpy; - driver.video_window = (uintptr_t)g_win; + + // Always assume that we have focus in true fullscreen. + driver.video_window = true_full ? (uintptr_t)None : (uintptr_t)g_win; return true; @@ -379,6 +417,22 @@ static void gfx_ctx_destroy(void) if (g_win) { + // Save last used monitor for later. +#ifdef HAVE_XINERAMA + XWindowAttributes target; + Window child; + + int x = 0, y = 0; + XGetWindowAttributes(g_dpy, g_win, &target); + XTranslateCoordinates(g_dpy, g_win, RootWindow(g_dpy, DefaultScreen(g_dpy)), + target.x, target.y, &x, &y, &child); + + g_screen = x11_get_xinerama_monitor(g_dpy, x, y, + target.width, target.height); + + RARCH_LOG("[GLX]: Saved monitor #%u.\n", g_screen); +#endif + XUnmapWindow(g_dpy, g_win); XDestroyWindow(g_dpy, g_win); g_win = None; @@ -421,7 +475,7 @@ static bool gfx_ctx_has_focus(void) int rev; XGetInputFocus(g_dpy, &win, &rev); - return win == g_win && g_has_focus; + return (win == g_win && g_has_focus) || (g_win == None); } static gfx_ctx_proc_t gfx_ctx_get_proc_address(const char *symbol) diff --git a/input/x11_input.c b/input/x11_input.c index 177251f06b..da590a1eae 100644 --- a/input/x11_input.c +++ b/input/x11_input.c @@ -277,8 +277,12 @@ static void x_input_poll_mouse(x11_input_t *x11) x11->mouse_last_x = x11->mouse_x; x11->mouse_last_y = x11->mouse_y; + Window win = x11->win; + if (win == None) + win = RootWindow(x11->display, DefaultScreen(x11->display)); + XQueryPointer(x11->display, - x11->win, + win, &root_win, &child_win, &root_x, &root_y, &win_x, &win_y, @@ -299,7 +303,7 @@ static void x_input_poll(void *data) int rev = 0; XGetInputFocus(x11->display, &win, &rev); - if (win == x11->win) + if (win == x11->win || x11->win == None) XQueryKeymap(x11->display, x11->state); else memset(x11->state, 0, sizeof(x11->state)); diff --git a/qb/config.libs.sh b/qb/config.libs.sh index bf26af03be..9192114b28 100644 --- a/qb/config.libs.sh +++ b/qb/config.libs.sh @@ -165,10 +165,11 @@ fi check_pkgconf FREETYPE freetype2 check_pkgconf X11 x11 -[ "$HAVE_X11" = "no" ] && HAVE_XEXT=no && HAVE_XF86VM=no +[ "$HAVE_X11" = "no" ] && HAVE_XEXT=no && HAVE_XF86VM=no && HAVE_XINERAMA=no check_pkgconf XEXT xext check_pkgconf XF86VM xxf86vm +check_pkgconf XINERAMA xinerama if [ "$HAVE_X11" = 'yes' ] && [ "$HAVE_XEXT" = 'yes' ] && [ "$HAVE_XF86VM" = 'yes' ]; then check_pkgconf XVIDEO xv else @@ -184,6 +185,6 @@ check_pkgconf PYTHON python3 add_define_make OS "$OS" # Creates config.mk and config.h. -VARS="ALSA OSS OSS_BSD OSS_LIB AL RSOUND ROAR JACK COREAUDIO PULSE SDL OPENGL GLES VG EGL KMS GBM DRM DYLIB GETOPT_LONG THREADS CG XML SDL_IMAGE LIBPNG DYNAMIC FFMPEG AVCODEC AVFORMAT AVUTIL CONFIGFILE FREETYPE XVIDEO X11 XEXT XF86VM NETPLAY NETWORK_CMD STDIN_CMD COMMAND SOCKET_LEGACY FBO STRL PYTHON FFMPEG_ALLOC_CONTEXT3 FFMPEG_AVCODEC_OPEN2 FFMPEG_AVIO_OPEN FFMPEG_AVFORMAT_WRITE_HEADER FFMPEG_AVFORMAT_NEW_STREAM FFMPEG_AVCODEC_ENCODE_AUDIO2 FFMPEG_AVCODEC_ENCODE_VIDEO2 SINC FIXED_POINT BSV_MOVIE VIDEOCORE" +VARS="ALSA OSS OSS_BSD OSS_LIB AL RSOUND ROAR JACK COREAUDIO PULSE SDL OPENGL GLES VG EGL KMS GBM DRM DYLIB GETOPT_LONG THREADS CG XML SDL_IMAGE LIBPNG DYNAMIC FFMPEG AVCODEC AVFORMAT AVUTIL CONFIGFILE FREETYPE XVIDEO X11 XEXT XF86VM XINERAMA NETPLAY NETWORK_CMD STDIN_CMD COMMAND SOCKET_LEGACY FBO STRL PYTHON FFMPEG_ALLOC_CONTEXT3 FFMPEG_AVCODEC_OPEN2 FFMPEG_AVIO_OPEN FFMPEG_AVFORMAT_WRITE_HEADER FFMPEG_AVFORMAT_NEW_STREAM FFMPEG_AVCODEC_ENCODE_AUDIO2 FFMPEG_AVCODEC_ENCODE_VIDEO2 SINC FIXED_POINT BSV_MOVIE VIDEOCORE" create_config_make config.mk $VARS create_config_header config.h $VARS diff --git a/qb/config.params.sh b/qb/config.params.sh index e6a4508a83..5e50dd9db8 100644 --- a/qb/config.params.sh +++ b/qb/config.params.sh @@ -9,6 +9,7 @@ HAVE_CONFIGFILE=yes # Disable support for config file HAVE_OPENGL=yes # Disable OpenGL support HAVE_GLES=no # Use GLESv2 instead of desktop GL HAVE_X11=auto # Disable everything X11. +HAVE_XINERAMA=auto # Disable Xinerama support. HAVE_KMS=auto # Enable KMS context support HAVE_EGL=auto # Enable EGL context support HAVE_VG=auto # Enable OpenVG support