X11: Attempt to go fullscreen the way SDL 1.2 did it.
authorRyan C. Gordon <icculus@icculus.org>
Thu, 27 Sep 2012 03:36:13 -0400
changeset 6467 ec5a04e921d4
parent 6466 ebe165c00fab
child 6468 6af2a8db95d0
X11: Attempt to go fullscreen the way SDL 1.2 did it. Ideally this code is never used, but as a legacy fallback, it could be useful.
src/video/x11/SDL_x11sym.h
src/video/x11/SDL_x11window.c
src/video/x11/SDL_x11window.h
--- a/src/video/x11/SDL_x11sym.h	Thu Sep 27 00:53:37 2012 -0400
+++ b/src/video/x11/SDL_x11sym.h	Thu Sep 27 03:36:13 2012 -0400
@@ -30,6 +30,7 @@
 SDL_X11_SYM(int,XChangePointerControl,(Display* a,Bool b,Bool c,int d,int e,int f),(a,b,c,d,e,f),return)
 SDL_X11_SYM(int,XChangeProperty,(Display* a,Window b,Atom c,Atom d,int e,int f,_Xconst unsigned char* g,int h),(a,b,c,d,e,f,g,h),return)
 SDL_X11_SYM(Bool,XCheckIfEvent,(Display* a,XEvent *b,Bool (*c)(Display*,XEvent*,XPointer),XPointer d),(a,b,c,d),return)
+SDL_X11_SYM(int,XClearWindow,(Display* a,Window b),(a,b),return)
 SDL_X11_SYM(int,XCloseDisplay,(Display* a),(a),return)
 SDL_X11_SYM(int,XConvertSelection,(Display* a,Atom b,Atom c,Atom d,Window e,Time f),(a,b,c,d,e,f),return)
 SDL_X11_SYM(Pixmap,XCreateBitmapFromData,(Display *dpy,Drawable d,_Xconst char *data,unsigned int width,unsigned int height),(dpy,d,data,width,height),return)
@@ -69,6 +70,7 @@
 SDL_X11_SYM(Status,XIconifyWindow,(Display* a,Window b,int c),(a,b,c),return)
 SDL_X11_SYM(KeyCode,XKeysymToKeycode,(Display* a,KeySym b),(a,b),return)
 SDL_X11_SYM(char*,XKeysymToString,(KeySym a),(a),return)
+SDL_X11_SYM(int,XInstallColormap,(Display* a,Colormap b),(a,b),return)
 SDL_X11_SYM(Atom,XInternAtom,(Display* a,_Xconst char* b,Bool c),(a,b,c),return)
 SDL_X11_SYM(XPixmapFormatValues*,XListPixmapFormats,(Display* a,int* b),(a,b),return)
 SDL_X11_SYM(KeySym,XLookupKeysym,(XKeyEvent* a,int b),(a,b),return)
@@ -85,6 +87,7 @@
 SDL_X11_SYM(int,XQueryKeymap,(Display* a,char *b),(a,b),return)
 SDL_X11_SYM(Bool,XQueryPointer,(Display* a,Window b,Window* c,Window* d,int* e,int* f,int* g,int* h,unsigned int* i),(a,b,c,d,e,f,g,h,i),return)
 SDL_X11_SYM(int,XRaiseWindow,(Display* a,Window b),(a,b),return)
+SDL_X11_SYM(int,XReparentWindow,(Display* a,Window b,Window c,int d,int e),(a,b,c,d,e),return)
 SDL_X11_SYM(int,XResetScreenSaver,(Display* a),(a),return)
 SDL_X11_SYM(int,XResizeWindow,(Display* a,Window b,unsigned int c,unsigned int d),(a,b,c,d),return)
 SDL_X11_SYM(int,XSelectInput,(Display* a,Window b,long c),(a,b,c),return)
@@ -95,6 +98,7 @@
 SDL_X11_SYM(int,XSetSelectionOwner,(Display* a,Atom b,Window c,Time d),(a,b,c,d),return)
 SDL_X11_SYM(int,XSetTransientForHint,(Display* a,Window b,Window c),(a,b,c),return)
 SDL_X11_SYM(void,XSetTextProperty,(Display* a,Window b,XTextProperty* c,Atom d),(a,b,c,d),)
+SDL_X11_SYM(int,XSetWindowBackground,(Display* a,Window b,unsigned long c),(a,b,c),return)
 SDL_X11_SYM(void,XSetWMProperties,(Display* a,Window b,XTextProperty* c,XTextProperty* d,char** e,int f,XSizeHints* g,XWMHints* h,XClassHint* i),(a,b,c,d,e,f,g,h,i),)
 SDL_X11_SYM(void,XSetWMNormalHints,(Display* a,Window b,XSizeHints* c),(a,b,c),)
 SDL_X11_SYM(Status,XSetWMProtocols,(Display* a,Window b,Atom* c,int d),(a,b,c,d),return)
@@ -256,6 +260,7 @@
 SDL_X11_SYM(Bool,XF86VidModeQueryExtension,(Display *a,int *b,int *c),(a,b,c),return)
 SDL_X11_SYM(Bool,XF86VidModeQueryVersion,(Display *a,int *b,int *c),(a,b,c),return)
 SDL_X11_SYM(Bool,XF86VidModeSwitchToMode,(Display *a,int b,XF86VidModeModeInfo *c),(a,b,c),return)
+SDL_X11_SYM(Bool,XF86VidModeLockModeSwitch,(Display *a,int b,int c),(a,b,c),return)
 #endif
 
 /* *INDENT-ON* */
--- a/src/video/x11/SDL_x11window.c	Thu Sep 27 00:53:37 2012 -0400
+++ b/src/video/x11/SDL_x11window.c	Thu Sep 27 03:36:13 2012 -0400
@@ -38,22 +38,38 @@
 
 #include "SDL_timer.h"
 #include "SDL_syswm.h"
+#include "SDL_assert.h"
 
 #define _NET_WM_STATE_REMOVE    0l
 #define _NET_WM_STATE_ADD       1l
 #define _NET_WM_STATE_TOGGLE    2l
 
-static SDL_bool
-X11_IsWindowOldFullscreen(_THIS, SDL_Window * window)
+static Bool isMapNotify(Display *dpy, XEvent *ev, XPointer win)
+{
+    return ev->type == MapNotify && ev->xmap.window == *((Window*)win);
+}
+static Bool isUnmapNotify(Display *dpy, XEvent *ev, XPointer win)
+{
+    return ev->type == UnmapNotify && ev->xunmap.window == *((Window*)win);
+}
+static Bool isConfigureNotify(Display *dpy, XEvent *ev, XPointer win)
 {
-    SDL_VideoData *videodata = (SDL_VideoData *) _this->driverdata;
+    return ev->type == ConfigureNotify && ev->xunmap.window == *((Window*)win);
+}
+static Bool isFocusOut(Display *dpy, XEvent *ev, XPointer win)
+{
+    return ev->type == FocusOut && ev->xunmap.window == *((Window*)win);
+}
+static Bool isFocusIn(Display *dpy, XEvent *ev, XPointer win)
+{
+    return ev->type == FocusIn && ev->xunmap.window == *((Window*)win);
+}
 
-    /* ICCCM2.0-compliant window managers can handle fullscreen windows */
-    if ((window->flags & SDL_WINDOW_FULLSCREEN) && !videodata->net_wm) {
-        return SDL_TRUE;
-    } else {
-        return SDL_FALSE;
-    }
+static SDL_bool
+X11_IsWindowLegacyFullscreen(_THIS, SDL_Window * window)
+{
+    SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
+    return (data->fswindow != 0);
 }
 
 static SDL_bool
@@ -724,19 +740,6 @@
     XFlush(display);
 }
 
-static Bool isMapNotify(Display *dpy, XEvent *ev, XPointer win)
-{
-    return ev->type == MapNotify && ev->xmap.window == *((Window*)win);
-}
-static Bool isUnmapNotify(Display *dpy, XEvent *ev, XPointer win)
-{
-    return ev->type == UnmapNotify && ev->xunmap.window == *((Window*)win);
-}
-static Bool isConfigureNotify(Display *dpy, XEvent *ev, XPointer win)
-{
-    return ev->type == ConfigureNotify && ev->xunmap.window == *((Window*)win);
-}
-
 void
 X11_SetWindowBordered(_THIS, SDL_Window * window, SDL_bool bordered)
 {
@@ -907,8 +910,9 @@
     return ret;
 }
 
-void
-X11_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDisplay * _display, SDL_bool fullscreen)
+/* This asks the Window Manager to handle fullscreen for us. Most don't do it right, though. */
+static void
+X11_SetWindowFullscreenViaWM(_THIS, SDL_Window * window, SDL_VideoDisplay * _display, SDL_bool fullscreen)
 {
     SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
     SDL_DisplayData *displaydata = (SDL_DisplayData *) _display->driverdata;
@@ -977,6 +981,189 @@
     XFlush(display);
 }
 
+static __inline__ int
+maxint(const int a, const int b)
+{
+    return (a > b ? a : b);
+}
+
+
+/* This handles fullscreen itself, outside the Window Manager. */
+static void
+X11_BeginWindowFullscreenLegacy(_THIS, SDL_Window * window, SDL_VideoDisplay * _display)
+{
+    SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
+    SDL_DisplayData *displaydata = (SDL_DisplayData *) _display->driverdata;
+    Visual *visual = data->visual;
+    Display *display = data->videodata->display;
+    const int screen = displaydata->screen;
+    Window root = RootWindow(display, screen);
+    const int def_vis = (visual == DefaultVisual(display, screen));
+    const int w = maxint(window->w, _display->current_mode.w);
+    const int h = maxint(window->h, _display->current_mode.h);
+    unsigned long xattrmask = 0;
+    XSetWindowAttributes xattr;
+    XEvent ev;
+    int x = 0;
+    int y = 0;
+
+    if ( data->fswindow ) {
+        return;  /* already fullscreen, I hope. */
+    }
+
+    /* Ungrab the input so that we can move the mouse around */
+    XUngrabPointer(display, CurrentTime);
+
+    #if SDL_VIDEO_DRIVER_X11_XINERAMA
+    /* !!! FIXME: there was some Xinerama code in 1.2 here to set x,y to the origin of a specific screen. */
+    #endif
+
+    SDL_zero(xattr);
+    xattr.override_redirect = True;
+    xattrmask |= CWOverrideRedirect;
+    xattr.background_pixel = def_vis ? BlackPixel(display, screen) : 0;
+    xattrmask |= CWBackPixel;
+    xattr.border_pixel = 0;
+    xattrmask |= CWBorderPixel;
+    xattr.colormap = data->colormap;
+    xattrmask |= CWColormap;
+
+    data->fswindow = XCreateWindow(display, root, x, y, w, h, 0,
+                                   displaydata->depth, InputOutput,
+                                   visual, xattrmask, &xattr);
+
+    XSelectInput(display, data->fswindow, StructureNotifyMask);
+
+    XSetWindowBackground(display, data->fswindow, 0);
+    XClearWindow(display, data->fswindow);
+
+    XMapRaised(display, data->fswindow);
+
+    /* Wait to be mapped, filter Unmap event out if it arrives. */
+    XIfEvent(display, &ev, &isMapNotify, (XPointer)&data->fswindow);
+    XCheckIfEvent(display, &ev, &isUnmapNotify, (XPointer)&data->fswindow);
+
+#if SDL_VIDEO_DRIVER_X11_XVIDMODE
+    if ( displaydata->use_vidmode ) {
+        XF86VidModeLockModeSwitch(display, screen, True);
+    }
+#endif
+
+    XInstallColormap(display, data->colormap);
+
+    SetWindowBordered(display, displaydata->screen, data->xwindow, SDL_FALSE);
+    XFlush(display);
+    //XIfEvent(display, &ev, &isConfigureNotify, (XPointer)&data->xwindow);
+
+    /* Center actual window within our cover-the-screen window. */
+    x += (w - window->w) / 2;
+    y += (h - window->h) / 2;
+    XReparentWindow(display, data->xwindow, data->fswindow, x, y);
+    XRaiseWindow(display, data->xwindow);
+
+    /* Make sure the fswindow is in view by warping mouse to the corner */
+    XWarpPointer(display, None, root, 0, 0, 0, 0, 0, 0);
+    XFlush(display);
+
+    /* Center mouse in the window. */
+    x += (window->w / 2);
+    y += (window->h / 2);
+    XWarpPointer(display, None, root, 0, 0, 0, 0, x, y);
+
+    /* Wait to be mapped, filter Unmap event out if it arrives. */
+    XIfEvent(display, &ev, &isMapNotify, (XPointer)&data->xwindow);
+
+    /* Wait to be visible, or XSetInputFocus() triggers an X error. */
+    while (SDL_TRUE) {
+        XWindowAttributes attr;
+        XSync(display, False);
+        XGetWindowAttributes(display, data->xwindow, &attr);
+        if (attr.map_state == IsViewable)
+            break;
+    }
+
+    XSetInputFocus(display, data->xwindow, RevertToParent, CurrentTime);
+    window->flags |= SDL_WINDOW_INPUT_FOCUS;
+    SDL_SetKeyboardFocus(data->window);
+
+    X11_SetWindowGrab(_this, window);
+
+    XSync(display, False);
+}
+
+static void
+X11_EndWindowFullscreenLegacy(_THIS, SDL_Window * window, SDL_VideoDisplay * _display)
+{
+    SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
+    SDL_DisplayData *displaydata = (SDL_DisplayData *) _display->driverdata;
+    Display *display = data->videodata->display;
+    const int screen = displaydata->screen;
+    Window root = RootWindow(display, screen);
+    XEvent ev;
+
+    if (!data->fswindow)
+        return;  /* already not fullscreen, I hope. */
+
+    XReparentWindow(display, data->xwindow, root, window->x, window->y);
+
+#if SDL_VIDEO_DRIVER_X11_VIDMODE
+    if ( displaydata->use_vidmode ) {
+        XF86VidModeLockModeSwitch(display, screen, False);
+    }
+#endif
+
+    XUnmapWindow(display, data->fswindow);
+    /* Wait to be unmapped. */
+    XIfEvent(display, &ev, &isUnmapNotify, (XPointer)&data->fswindow);
+    XDestroyWindow(display, data->fswindow);
+    data->fswindow = 0;
+
+    /* catch these events so we know the window is back in business. */
+    XIfEvent(display, &ev, &isUnmapNotify, (XPointer)&data->xwindow);
+    XIfEvent(display, &ev, &isMapNotify, (XPointer)&data->xwindow);
+
+    XSync(display, True);   /* Flush spurious mode change events */
+
+    X11_SetWindowGrab(_this, window);
+
+    SetWindowBordered(display, screen, data->xwindow,
+                      (window->flags & SDL_WINDOW_BORDERLESS) == 0);
+
+    XFlush(display);
+}
+
+
+void
+X11_SetWindowFullscreen(_THIS, SDL_Window * window, SDL_VideoDisplay * _display, SDL_bool fullscreen)
+{
+    /* !!! FIXME: SDL_Hint? */
+    SDL_bool legacy = SDL_FALSE;
+    const char *env = SDL_getenv("SDL_VIDEO_X11_LEGACY_FULLSCREEN");
+    if (env) {
+        legacy = SDL_atoi(env);
+    } else {
+        SDL_DisplayData *displaydata = (SDL_DisplayData *) _display->driverdata;
+        if ( displaydata->use_vidmode ) {
+            legacy = SDL_TRUE;  /* the new stuff only works with XRandR. */
+        } else {
+            /* !!! FIXME: look at the window manager name, and blacklist certain ones? */
+            /* http://stackoverflow.com/questions/758648/find-the-name-of-the-x-window-manager */
+            legacy = SDL_FALSE;  /* try the new way. */
+        }
+    }
+
+    if (legacy) {
+        if (fullscreen) {
+            X11_BeginWindowFullscreenLegacy(_this, window, _display);
+        } else {
+            X11_EndWindowFullscreenLegacy(_this, window, _display);
+        }
+    } else {
+        X11_SetWindowFullscreenViaWM(_this, window, _display, fullscreen);
+    }
+}
+
+
 int
 X11_SetWindowGammaRamp(_THIS, SDL_Window * window, const Uint16 * ramp)
 {
@@ -1054,7 +1241,7 @@
     SDL_bool oldstyle_fullscreen;
 
     /* ICCCM2.0-compliant window managers can handle fullscreen windows */
-    oldstyle_fullscreen = X11_IsWindowOldFullscreen(_this, window);
+    oldstyle_fullscreen = X11_IsWindowLegacyFullscreen(_this, window);
 
     if (((window->flags & SDL_WINDOW_INPUT_GRABBED) || oldstyle_fullscreen)
         && (window->flags & SDL_WINDOW_INPUT_FOCUS)) {
--- a/src/video/x11/SDL_x11window.h	Thu Sep 27 00:53:37 2012 -0400
+++ b/src/video/x11/SDL_x11window.h	Thu Sep 27 03:36:13 2012 -0400
@@ -27,6 +27,7 @@
 {
     SDL_Window *window;
     Window xwindow;
+    Window fswindow;  /* used if we can't have the WM handle fullscreen. */
     Visual *visual;
     Colormap colormap;
 #ifndef NO_SHARED_MEMORY