Merged Ryan's SDL-gui-backend branch.
authorRyan C. Gordon <icculus@icculus.org>
Wed, 25 Jun 2014 17:06:12 -0400
changeset 8953 dc80dc0bd22e
parent 8926 38e89ab78465 (current diff)
parent 8952 4bb098814ec4 (diff)
child 8954 7bcc8257809d
Merged Ryan's SDL-gui-backend branch. Adds three APIs, and implements them on X11, Cocoa, and Windows: - SDL_CaptureMouse() - SDL_GetGlobalMouseState() - SDL_SetWindowHitTest()
.hgignore
include/SDL_mouse.h
src/dynapi/SDL_dynapi_overrides.h
src/dynapi/SDL_dynapi_procs.h
src/events/SDL_mouse.c
src/events/SDL_mouse_c.h
src/test/SDL_test_common.c
src/video/SDL_sysvideo.h
src/video/SDL_video.c
src/video/cocoa/SDL_cocoamouse.m
src/video/cocoa/SDL_cocoawindow.m
src/video/windows/SDL_windowsevents.c
src/video/windows/SDL_windowsmouse.c
src/video/windows/SDL_windowsvideo.c
src/video/windows/SDL_windowswindow.c
src/video/windows/SDL_windowswindow.h
src/video/x11/SDL_x11events.c
src/video/x11/SDL_x11mouse.c
src/video/x11/SDL_x11video.c
src/video/x11/SDL_x11window.c
--- a/.hgignore	Wed Jun 25 02:08:37 2014 -0700
+++ b/.hgignore	Wed Jun 25 17:06:12 2014 -0400
@@ -84,6 +84,7 @@
 test/testgl2
 test/testgles
 test/testhaptic
+test/testhittesting
 test/testiconv
 test/testime
 test/testintersections
--- a/include/SDL_mouse.h	Wed Jun 25 02:08:37 2014 -0700
+++ b/include/SDL_mouse.h	Wed Jun 25 17:06:12 2014 -0400
@@ -78,6 +78,31 @@
 extern DECLSPEC Uint32 SDLCALL SDL_GetMouseState(int *x, int *y);
 
 /**
+ *  \brief Get the current state of the mouse, in relation to the desktop
+ *
+ *  This works just like SDL_GetMouseState(), but the coordinates will be
+ *  reported relative to the top-left of the desktop. This can be useful if
+ *  you need to track the mouse outside of a specific window and
+ *  SDL_CaptureMouse() doesn't fit your needs. For example, it could be
+ *  useful if you need to track the mouse while dragging a window, where
+ *  coordinates relative to a window might not be in sync at all times.
+ *
+ *  \note SDL_GetMouseState() returns the mouse position as SDL understands
+ *        it from the last pump of the event queue. This function, however,
+ *        queries the OS for the current mouse position, and as such, might
+ *        be a slightly less efficient function. Unless you know what you're
+ *        doing and have a good reason to use this function, you probably want
+ *        SDL_GetMouseState() instead.
+ *
+ *  \param x Returns the current X coord, relative to the desktop. Can be NULL.
+ *  \param y Returns the current Y coord, relative to the desktop. Can be NULL.
+ *  \return The current button state as a bitmask, which can be tested using the SDL_BUTTON(X) macros.
+ *
+ *  \sa SDL_GetMouseState
+ */
+extern DECLSPEC Uint32 SDLCALL SDL_GetGlobalMouseState(int *x, int *y);
+
+/**
  *  \brief Retrieve the relative state of the mouse.
  *
  *  The current button state is returned as a button bitmask, which can
@@ -127,6 +152,37 @@
 extern DECLSPEC int SDLCALL SDL_SetRelativeMouseMode(SDL_bool enabled);
 
 /**
+ *  \brief Capture the mouse, to track input outside an SDL window.
+ *
+ *  \param enabled Whether or not to enable capturing
+ *
+ *  Capturing enables your app to obtain mouse events globally, instead of
+ *  just within your window. Not all video targets support this function.
+ *  When capturing is enabled, the current window will get all mouse events,
+ *  but unlike relative mode, no change is made to the cursor and it is
+ *  not restrained to your window.
+ *
+ *  This function may also deny mouse input to other windows--both those in
+ *  your application and others on the system--so you should use this
+ *  function sparingly, and in small bursts. For example, you might want to
+ *  track the mouse while the user is dragging something, until the user
+ *  releases a mouse button. It is not recommended that you capture the mouse
+ *  for long periods of time, such as the entire time your app is running.
+ *
+ *  While captured, mouse events still report coordinates relative to the
+ *  current (foreground) window, but those coordinates may be outside the
+ *  bounds of the window (including negative values). Capturing is only
+ *  allowed for the foreground window. If the window loses focus while
+ *  capturing, the capture will be disabled automatically.
+ *
+ *  While capturing is enabled, the current window will have the
+ *  SDL_WINDOW_MOUSE_CAPTURE flag set.
+ *
+ *  \return 0 on success, or -1 if not supported.
+ */
+extern DECLSPEC int SDLCALL SDL_CaptureMouse(SDL_bool enabled);
+
+/**
  *  \brief Query whether relative mouse mode is enabled.
  *
  *  \sa SDL_SetRelativeMouseMode()
--- a/include/SDL_rect.h	Wed Jun 25 02:08:37 2014 -0700
+++ b/include/SDL_rect.h	Wed Jun 25 17:06:12 2014 -0400
@@ -43,6 +43,7 @@
  *  \brief  The structure that defines a point
  *
  *  \sa SDL_EnclosePoints
+ *  \sa SDL_PointInRect
  */
 typedef struct SDL_Point
 {
@@ -67,6 +68,15 @@
 } SDL_Rect;
 
 /**
+ *  \brief Returns true if point resides inside a rectangle.
+ */
+SDL_FORCE_INLINE SDL_bool SDL_PointInRect(const SDL_Point *p, const SDL_Rect *r)
+{
+    return ( (p->x >= r->x) && (p->x < (r->x + r->w)) &&
+             (p->y >= r->y) && (p->y < (r->y + r->h)) ) ? SDL_TRUE : SDL_FALSE;
+}
+
+/**
  *  \brief Returns true if the rectangle has no area.
  */
 SDL_FORCE_INLINE SDL_bool SDL_RectEmpty(const SDL_Rect *r)
--- a/include/SDL_video.h	Wed Jun 25 02:08:37 2014 -0700
+++ b/include/SDL_video.h	Wed Jun 25 17:06:12 2014 -0400
@@ -108,7 +108,8 @@
     SDL_WINDOW_MOUSE_FOCUS = 0x00000400,        /**< window has mouse focus */
     SDL_WINDOW_FULLSCREEN_DESKTOP = ( SDL_WINDOW_FULLSCREEN | 0x00001000 ),
     SDL_WINDOW_FOREIGN = 0x00000800,            /**< window not created by SDL */
-    SDL_WINDOW_ALLOW_HIGHDPI = 0x00002000       /**< window should be created in high-DPI mode if supported */
+    SDL_WINDOW_ALLOW_HIGHDPI = 0x00002000,      /**< window should be created in high-DPI mode if supported */
+    SDL_WINDOW_MOUSE_CAPTURE = 0x00004000       /**< window has mouse captured (unrelated to INPUT_GRABBED) */
 } SDL_WindowFlags;
 
 /**
@@ -791,6 +792,75 @@
                                                    Uint16 * blue);
 
 /**
+ *  \brief Possible return values from the SDL_HitTest callback.
+ *
+ *  \sa SDL_HitTest
+ */
+typedef enum
+{
+    SDL_HITTEST_NORMAL,  /**< Region is normal. No special properties. */
+    SDL_HITTEST_DRAGGABLE,  /**< Region can drag entire window. */
+    SDL_HITTEST_RESIZE_TOPLEFT,
+    SDL_HITTEST_RESIZE_TOP,
+    SDL_HITTEST_RESIZE_TOPRIGHT,
+    SDL_HITTEST_RESIZE_RIGHT,
+    SDL_HITTEST_RESIZE_BOTTOMRIGHT,
+    SDL_HITTEST_RESIZE_BOTTOM,
+    SDL_HITTEST_RESIZE_BOTTOMLEFT,
+    SDL_HITTEST_RESIZE_LEFT
+} SDL_HitTestResult;
+
+/**
+ *  \brief Callback used for hit-testing.
+ *
+ *  \sa SDL_SetWindowHitTest
+ */
+typedef SDL_HitTestResult (SDLCALL *SDL_HitTest)(SDL_Window *win,
+                                                 const SDL_Point *area,
+                                                 void *data);
+
+/**
+ *  \brief Provide a callback that decides if a window region has special properties.
+ *
+ *  Normally windows are dragged and resized by decorations provided by the
+ *  system window manager (a title bar, borders, etc), but for some apps, it
+ *  makes sense to drag them from somewhere else inside the window itself; for
+ *  example, one might have a borderless window that wants to be draggable
+ *  from any part, or simulate its own title bar, etc.
+ *
+ *  This function lets the app provide a callback that designates pieces of
+ *  a given window as special. This callback is run during event processing
+ *  if we need to tell the OS to treat a region of the window specially; the
+ *  use of this callback is known as "hit testing."
+ *
+ *  Mouse input may not be delivered to your application if it is within
+ *  a special area; the OS will often apply that input to moving the window or
+ *  resizing the window and not deliver it to the application.
+ *
+ *  Specifying NULL for a callback disables hit-testing. Hit-testing is
+ *  disabled by default.
+ *
+ *  Platforms that don't support this functionality will return -1
+ *  unconditionally, even if you're attempting to disable hit-testing.
+ *
+ *  Your callback may fire at any time, and its firing does not indicate any
+ *  specific behavior (for example, on Windows, this certainly might fire
+ *  when the OS is deciding whether to drag your window, but it fires for lots
+ *  of other reasons, too, some unrelated to anything you probably care about
+ *  _and when the mouse isn't actually at the location it is testing_).
+ *  Since this can fire at any time, you should try to keep your callback
+ *  efficient, devoid of allocations, etc.
+ *
+ *  \param window The window to set hit-testing on.
+ *  \param callback The callback to call when doing a hit-test.
+ *  \param callback_data An app-defined void pointer passed to the callback.
+ *  \return 0 on success, -1 on error (including unsupported).
+ */
+extern DECLSPEC int SDLCALL SDL_SetWindowHitTest(SDL_Window * window,
+                                                 SDL_HitTest callback,
+                                                 void *callback_data);
+
+/**
  *  \brief Destroy a window.
  */
 extern DECLSPEC void SDLCALL SDL_DestroyWindow(SDL_Window * window);
--- a/src/dynapi/SDL_dynapi_overrides.h	Wed Jun 25 02:08:37 2014 -0700
+++ b/src/dynapi/SDL_dynapi_overrides.h	Wed Jun 25 17:06:12 2014 -0400
@@ -584,3 +584,6 @@
 #define SDL_sqrtf SDL_sqrtf_REAL
 #define SDL_tan SDL_tan_REAL
 #define SDL_tanf SDL_tanf_REAL
+#define SDL_CaptureMouse SDL_CaptureMouse_REAL
+#define SDL_SetWindowHitTest SDL_SetWindowHitTest_REAL
+#define SDL_GetGlobalMouseState SDL_GetGlobalMouseState_REAL
--- a/src/dynapi/SDL_dynapi_procs.h	Wed Jun 25 02:08:37 2014 -0700
+++ b/src/dynapi/SDL_dynapi_procs.h	Wed Jun 25 17:06:12 2014 -0400
@@ -616,3 +616,6 @@
 SDL_DYNAPI_PROC(float,SDL_sqrtf,(float a),(a),return)
 SDL_DYNAPI_PROC(double,SDL_tan,(double a),(a),return)
 SDL_DYNAPI_PROC(float,SDL_tanf,(float a),(a),return)
+SDL_DYNAPI_PROC(int,SDL_CaptureMouse,(SDL_bool a),(a),return)
+SDL_DYNAPI_PROC(int,SDL_SetWindowHitTest,(SDL_Window *a, SDL_HitTest b, void *c),(a,b,c),return)
+SDL_DYNAPI_PROC(Uint32,SDL_GetGlobalMouseState,(int *a, int *b),(a,b),return)
--- a/src/events/SDL_keyboard.c	Wed Jun 25 02:08:37 2014 -0700
+++ b/src/events/SDL_keyboard.c	Wed Jun 25 17:06:12 2014 -0400
@@ -25,6 +25,7 @@
 #include "SDL_timer.h"
 #include "SDL_events.h"
 #include "SDL_events_c.h"
+#include "SDL_assert.h"
 #include "../video/SDL_sysvideo.h"
 
 
@@ -619,6 +620,16 @@
 
     /* See if the current window has lost focus */
     if (keyboard->focus && keyboard->focus != window) {
+
+        /* new window shouldn't think it has mouse captured. */
+        SDL_assert(!window || !(window->flags & SDL_WINDOW_MOUSE_CAPTURE));
+
+        /* old window must lose an existing mouse capture. */
+        if (keyboard->focus->flags & SDL_WINDOW_MOUSE_CAPTURE) {
+            SDL_CaptureMouse(SDL_FALSE);  /* drop the capture. */
+            SDL_assert(!(keyboard->focus->flags & SDL_WINDOW_MOUSE_CAPTURE));
+        }
+
         SDL_SendWindowEvent(keyboard->focus, SDL_WINDOWEVENT_FOCUS_LOST,
                             0, 0);
 
--- a/src/events/SDL_mouse.c	Wed Jun 25 02:08:37 2014 -0700
+++ b/src/events/SDL_mouse.c	Wed Jun 25 17:06:12 2014 -0400
@@ -140,14 +140,14 @@
 SDL_UpdateMouseFocus(SDL_Window * window, int x, int y, Uint32 buttonstate)
 {
     SDL_Mouse *mouse = SDL_GetMouse();
-    int w, h;
-    SDL_bool inWindow;
+    SDL_bool inWindow = SDL_TRUE;
 
-    SDL_GetWindowSize(window, &w, &h);
-    if (x < 0 || y < 0 || x >= w || y >= h) {
-        inWindow = SDL_FALSE;
-    } else {
-        inWindow = SDL_TRUE;
+    if ((window->flags & SDL_WINDOW_MOUSE_CAPTURE) == 0) {
+        int w, h;
+        SDL_GetWindowSize(window, &w, &h);
+        if (x < 0 || y < 0 || x >= w || y >= h) {
+            inWindow = SDL_FALSE;
+        }
     }
 
 /* Linux doesn't give you mouse events outside your window unless you grab
@@ -246,24 +246,29 @@
         mouse->y += yrel;
     }
 
-    /* !!! FIXME: shouldn't this be (window) instead of (mouse->focus)? */
-    SDL_GetWindowSize(mouse->focus, &x_max, &y_max);
-    --x_max;
-    --y_max;
+    /* make sure that the pointers find themselves inside the windows,
+       unless we have the mouse captured. */
+    if ((window->flags & SDL_WINDOW_MOUSE_CAPTURE) == 0) {
+        int x_max = 0, y_max = 0;
+
+        // !!! FIXME: shouldn't this be (window) instead of (mouse->focus)?
+        SDL_GetWindowSize(mouse->focus, &x_max, &y_max);
+        --x_max;
+        --y_max;
 
-    /* make sure that the pointers find themselves inside the windows */
-    if (mouse->x > x_max) {
-        mouse->x = x_max;
-    }
-    if (mouse->x < 0) {
-        mouse->x = 0;
-    }
+        if (mouse->x > x_max) {
+            mouse->x = x_max;
+        }
+        if (mouse->x < 0) {
+            mouse->x = 0;
+        }
 
-    if (mouse->y > y_max) {
-        mouse->y = y_max;
-    }
-    if (mouse->y < 0) {
-        mouse->y = 0;
+        if (mouse->y > y_max) {
+            mouse->y = y_max;
+        }
+        if (mouse->y < 0) {
+            mouse->y = 0;
+        }
     }
 
     mouse->xdelta += xrel;
@@ -426,6 +431,7 @@
     SDL_Cursor *cursor, *next;
     SDL_Mouse *mouse = SDL_GetMouse();
 
+    SDL_CaptureMouse(SDL_FALSE);
     SDL_SetRelativeMouseMode(SDL_FALSE);
     SDL_ShowCursor(1);
 
@@ -477,16 +483,42 @@
     return mouse->buttonstate;
 }
 
+Uint32
+SDL_GetGlobalMouseState(int *x, int *y)
+{
+    SDL_Mouse *mouse = SDL_GetMouse();
+    int tmpx, tmpy;
+
+    /* make sure these are never NULL for the backend implementations... */
+    if (!x) {
+        x = &tmpx;
+    }
+    if (!y) {
+        y = &tmpy;
+    }
+
+    *x = *y = 0;
+
+    if (!mouse->GetGlobalMouseState) {
+        SDL_assert(0 && "This should really be implemented for every target.");
+        return 0;
+    }
+
+    return mouse->GetGlobalMouseState(x, y);
+}
+
 void
 SDL_WarpMouseInWindow(SDL_Window * window, int x, int y)
 {
     SDL_Mouse *mouse = SDL_GetMouse();
 
-    if (window == NULL)
+    if (window == NULL) {
         window = mouse->focus;
+    }
 
-    if (window == NULL)
+    if (window == NULL) {
         return;
+    }
 
     if (mouse->WarpMouse) {
         mouse->WarpMouse(window, x, y);
--- a/src/events/SDL_mouse_c.h	Wed Jun 25 02:08:37 2014 -0700
+++ b/src/events/SDL_mouse_c.h	Wed Jun 25 17:06:12 2014 -0400
@@ -66,6 +66,12 @@
     /* Set relative mode */
     int (*SetRelativeMouseMode) (SDL_bool enabled);
 
+    /* Set mouse capture */
+    int (*CaptureMouse) (SDL_Window * window);
+
+    /* Get absolute mouse coordinates. (x) and (y) are never NULL and set to zero before call. */
+    Uint32 (*GetGlobalMouseState) (int *x, int *y);
+
     /* Data common to all mice */
     SDL_MouseID mouseID;
     SDL_Window *focus;
--- a/src/test/SDL_test_common.c	Wed Jun 25 02:08:37 2014 -0700
+++ b/src/test/SDL_test_common.c	Wed Jun 25 17:06:12 2014 -0400
@@ -1379,6 +1379,14 @@
                     }
                 }
             }
+            if (withShift) {
+                SDL_Window *current_win = SDL_GetKeyboardFocus();
+                if (current_win) {
+                    const SDL_bool shouldCapture = (SDL_GetWindowFlags(current_win) & SDL_WINDOW_MOUSE_CAPTURE) == 0;
+                    const int rc = SDL_CaptureMouse(shouldCapture);
+                    SDL_Log("%sapturing mouse %s!\n", shouldCapture ? "C" : "Unc", (rc == 0) ? "succeeded" : "failed");
+                }
+            }
             break;
         case SDLK_v:
             if (withControl) {
@@ -1478,6 +1486,19 @@
                 }
             }
             break;
+        case SDLK_a:
+            if (withControl) {
+                /* Ctrl-A reports absolute mouse position. */
+                int x, y;
+                const Uint32 mask = SDL_GetGlobalMouseState(&x, &y);
+                SDL_Log("ABSOLUTE MOUSE: (%d, %d)%s%s%s%s%s\n", x, y,
+                        (mask & SDL_BUTTON_LMASK) ? " [LBUTTON]" : "",
+                        (mask & SDL_BUTTON_MMASK) ? " [MBUTTON]" : "",
+                        (mask & SDL_BUTTON_RMASK) ? " [RBUTTON]" : "",
+                        (mask & SDL_BUTTON_X1MASK) ? " [X2BUTTON]" : "",
+                        (mask & SDL_BUTTON_X2MASK) ? " [X2BUTTON]" : "");
+            }
+            break;
         case SDLK_0:
             if (withControl) {
                 SDL_Window *window = SDL_GetWindowFromID(event->key.windowID);
--- a/src/video/SDL_sysvideo.h	Wed Jun 25 02:08:37 2014 -0700
+++ b/src/video/SDL_sysvideo.h	Wed Jun 25 17:06:12 2014 -0400
@@ -97,6 +97,9 @@
 
     SDL_WindowShaper *shaper;
 
+    SDL_HitTest hit_test;
+    void *hit_test_data;
+
     SDL_WindowUserData *data;
 
     void *driverdata;
@@ -261,6 +264,9 @@
     /* MessageBox */
     int (*ShowMessageBox) (_THIS, const SDL_MessageBoxData *messageboxdata, int *buttonid);
 
+    /* Hit-testing */
+    int (*SetWindowHitTest)(SDL_Window * window, SDL_bool enabled);
+
     /* * * */
     /* Data common to all drivers */
     SDL_bool suspend_screensaver;
--- a/src/video/SDL_video.c	Wed Jun 25 02:08:37 2014 -0700
+++ b/src/video/SDL_video.c	Wed Jun 25 17:06:12 2014 -0400
@@ -1428,6 +1428,11 @@
         SDL_SetWindowIcon(window, icon);
         SDL_FreeSurface(icon);
     }
+
+    if (window->hit_test) {
+        _this->SetWindowHitTest(window, SDL_TRUE);
+    }
+
     SDL_FinishWindowCreation(window, flags);
 
     return 0;
@@ -3292,12 +3297,17 @@
     int retval = -1;
     SDL_bool relative_mode;
     int show_cursor_prev;
+    SDL_bool mouse_captured;
+    SDL_Window *current_window;
 
     if (!messageboxdata) {
         return SDL_InvalidParamError("messageboxdata");
     }
 
+    current_window = SDL_GetKeyboardFocus();
+    mouse_captured = current_window && ((SDL_GetWindowFlags(current_window) & SDL_WINDOW_MOUSE_CAPTURE) != 0);
     relative_mode = SDL_GetRelativeMouseMode();
+    SDL_CaptureMouse(SDL_FALSE);
     SDL_SetRelativeMouseMode(SDL_FALSE);
     show_cursor_prev = SDL_ShowCursor(1);
 
@@ -3349,6 +3359,13 @@
         SDL_SetError("No message system available");
     }
 
+    if (current_window) {
+        SDL_RaiseWindow(current_window);
+        if (mouse_captured) {
+            SDL_CaptureMouse(SDL_TRUE);
+        }
+    }
+
     SDL_ShowCursor(show_cursor_prev);
     SDL_SetRelativeMouseMode(relative_mode);
 
@@ -3391,4 +3408,21 @@
     return SDL_TRUE;
 }
 
+int
+SDL_SetWindowHitTest(SDL_Window * window, SDL_HitTest callback, void *userdata)
+{
+    CHECK_WINDOW_MAGIC(window, -1);
+
+    if (!_this->SetWindowHitTest) {
+        return SDL_Unsupported();
+    } else if (_this->SetWindowHitTest(window, callback != NULL) == -1) {
+        return -1;
+    }
+
+    window->hit_test = callback;
+    window->hit_test_data = userdata;
+
+    return 0;
+}
+
 /* vi: set ts=4 sw=4 expandtab: */
--- a/src/video/cocoa/SDL_cocoamouse.m	Wed Jun 25 02:08:37 2014 -0700
+++ b/src/video/cocoa/SDL_cocoamouse.m	Wed Jun 25 17:06:12 2014 -0400
@@ -307,6 +307,39 @@
     return 0;
 }
 
+static int
+Cocoa_CaptureMouse(SDL_Window *window)
+{
+    /* our Cocoa event code already tracks the mouse outside the window,
+        so all we have to do here is say "okay" and do what we always do. */
+    return 0;
+}
+
+static Uint32
+Cocoa_GetGlobalMouseState(int *x, int *y)
+{
+    const NSUInteger cocoaButtons = [NSEvent pressedMouseButtons];
+    const NSPoint cocoaLocation = [NSEvent mouseLocation];
+    Uint32 retval = 0;
+
+    for (NSScreen *screen in [NSScreen screens]) {
+        NSRect frame = [screen frame];
+        if (NSPointInRect(cocoaLocation, frame)) {
+            *x = (int) cocoaLocation.x;
+            *y = (int) ((frame.origin.y + frame.size.height) - cocoaLocation.y);
+            break;
+        }
+    }
+
+    retval |= (cocoaButtons & (1 << 0)) ? SDL_BUTTON_LMASK : 0;
+    retval |= (cocoaButtons & (1 << 1)) ? SDL_BUTTON_RMASK : 0;
+    retval |= (cocoaButtons & (1 << 2)) ? SDL_BUTTON_MMASK : 0;
+    retval |= (cocoaButtons & (1 << 3)) ? SDL_BUTTON_X1MASK : 0;
+    retval |= (cocoaButtons & (1 << 4)) ? SDL_BUTTON_X2MASK : 0;
+
+    return retval;
+}
+
 void
 Cocoa_InitMouse(_THIS)
 {
@@ -321,6 +354,8 @@
     mouse->WarpMouse = Cocoa_WarpMouse;
     mouse->WarpMouseGlobal = Cocoa_WarpMouseGlobal;
     mouse->SetRelativeMouseMode = Cocoa_SetRelativeMouseMode;
+    mouse->CaptureMouse = Cocoa_CaptureMouse;
+    mouse->GetGlobalMouseState = Cocoa_GetGlobalMouseState;
 
     SDL_SetDefaultCursor(Cocoa_CreateDefaultCursor());
 
--- a/src/video/cocoa/SDL_cocoavideo.m	Wed Jun 25 02:08:37 2014 -0700
+++ b/src/video/cocoa/SDL_cocoavideo.m	Wed Jun 25 17:06:12 2014 -0400
@@ -108,6 +108,7 @@
     device->SetWindowGrab = Cocoa_SetWindowGrab;
     device->DestroyWindow = Cocoa_DestroyWindow;
     device->GetWindowWMInfo = Cocoa_GetWindowWMInfo;
+    device->SetWindowHitTest = Cocoa_SetWindowHitTest;
 
     device->shape_driver.CreateShaper = Cocoa_CreateShaper;
     device->shape_driver.SetWindowShape = Cocoa_SetWindowShape;
--- a/src/video/cocoa/SDL_cocoawindow.h	Wed Jun 25 02:08:37 2014 -0700
+++ b/src/video/cocoa/SDL_cocoawindow.h	Wed Jun 25 17:06:12 2014 -0400
@@ -45,6 +45,7 @@
     PendingWindowOperation pendingWindowOperation;
     BOOL isMoving;
     int pendingWindowWarpX, pendingWindowWarpY;
+    BOOL isDragAreaRunning;
 }
 
 -(void) listen:(SDL_WindowData *) data;
@@ -75,6 +76,9 @@
 -(void) windowDidExitFullScreen:(NSNotification *) aNotification;
 -(NSApplicationPresentationOptions)window:(NSWindow *)window willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions;
 
+/* See if event is in a drag area, toggle on window dragging. */
+-(BOOL) processHitTest:(NSEvent *)theEvent;
+
 /* Window event handling */
 -(void) mouseDown:(NSEvent *) theEvent;
 -(void) rightMouseDown:(NSEvent *) theEvent;
@@ -138,8 +142,8 @@
 extern int Cocoa_GetWindowGammaRamp(_THIS, SDL_Window * window, Uint16 * ramp);
 extern void Cocoa_SetWindowGrab(_THIS, SDL_Window * window, SDL_bool grabbed);
 extern void Cocoa_DestroyWindow(_THIS, SDL_Window * window);
-extern SDL_bool Cocoa_GetWindowWMInfo(_THIS, SDL_Window * window,
-                                      struct SDL_SysWMinfo *info);
+extern SDL_bool Cocoa_GetWindowWMInfo(_THIS, SDL_Window * window, struct SDL_SysWMinfo *info);
+extern int Cocoa_SetWindowHitTest(SDL_Window *window, SDL_bool enabled);
 
 #endif /* _SDL_cocoawindow_h */
 
--- a/src/video/cocoa/SDL_cocoawindow.m	Wed Jun 25 02:08:37 2014 -0700
+++ b/src/video/cocoa/SDL_cocoawindow.m	Wed Jun 25 17:06:12 2014 -0400
@@ -192,6 +192,7 @@
     inFullscreenTransition = NO;
     pendingWindowOperation = PENDING_OPERATION_NONE;
     isMoving = NO;
+    isDragAreaRunning = NO;
 
     center = [NSNotificationCenter defaultCenter];
 
@@ -663,10 +664,48 @@
     /*Cocoa_HandleKeyEvent(SDL_GetVideoDevice(), theEvent);*/
 }
 
+/* We'll respond to selectors by doing nothing so we don't beep.
+ * The escape key gets converted to a "cancel" selector, etc.
+ */
+- (void)doCommandBySelector:(SEL)aSelector
+{
+    /*NSLog(@"doCommandBySelector: %@\n", NSStringFromSelector(aSelector));*/
+}
+
+- (BOOL)processHitTest:(NSEvent *)theEvent
+{
+    SDL_assert(isDragAreaRunning == [_data->nswindow isMovableByWindowBackground]);
+
+    if (_data->window->hit_test) {  /* if no hit-test, skip this. */
+        const NSPoint location = [theEvent locationInWindow];
+        const SDL_Point point = { (int) location.x, _data->window->h - (((int) location.y)-1) };
+        const SDL_HitTestResult rc = _data->window->hit_test(_data->window, &point, _data->window->hit_test_data);
+        if (rc == SDL_HITTEST_DRAGGABLE) {
+            if (!isDragAreaRunning) {
+                isDragAreaRunning = YES;
+                [_data->nswindow setMovableByWindowBackground:YES];
+            }
+            return YES;  /* dragging! */
+        }
+    }
+
+    if (isDragAreaRunning) {
+        isDragAreaRunning = NO;
+        [_data->nswindow setMovableByWindowBackground:NO];
+        return YES;  /* was dragging, drop event. */
+    }
+
+    return NO;  /* not a special area, carry on. */
+}
+
 - (void)mouseDown:(NSEvent *)theEvent
 {
     int button;
 
+    if ([self processHitTest:theEvent]) {
+        return;  /* dragging, drop event. */
+    }
+
     switch ([theEvent buttonNumber]) {
     case 0:
         if (([theEvent modifierFlags] & NSControlKeyMask) &&
@@ -705,6 +744,10 @@
 {
     int button;
 
+    if ([self processHitTest:theEvent]) {
+        return;  /* stopped dragging, drop event. */
+    }
+
     switch ([theEvent buttonNumber]) {
     case 0:
         if (wasCtrlLeft) {
@@ -744,6 +787,10 @@
     NSPoint point;
     int x, y;
 
+    if ([self processHitTest:theEvent]) {
+        return;  /* dragging, drop event. */
+    }
+
     if (mouse->relative_mode) {
         return;
     }
@@ -752,8 +799,8 @@
     x = (int)point.x;
     y = (int)(window->h - point.y);
 
-    if (x < 0 || x >= window->w || y < 0 || y >= window->h) {
-        if (window->flags & SDL_WINDOW_INPUT_GRABBED) {
+    if (window->flags & SDL_WINDOW_INPUT_GRABBED) {
+        if (x < 0 || x >= window->w || y < 0 || y >= window->h) {
             if (x < 0) {
                 x = 0;
             } else if (x >= window->w) {
@@ -890,6 +937,7 @@
 
 /* The default implementation doesn't pass rightMouseDown to responder chain */
 - (void)rightMouseDown:(NSEvent *)theEvent;
+- (BOOL)mouseDownCanMoveWindow;
 @end
 
 @implementation SDLView
@@ -898,6 +946,14 @@
     [[self nextResponder] rightMouseDown:theEvent];
 }
 
+- (BOOL)mouseDownCanMoveWindow
+{
+    /* Always say YES, but this doesn't do anything until we call
+       -[NSWindow setMovableByWindowBackground:YES], which we ninja-toggle
+       during mouse events when we're using a drag area. */
+    return YES;
+}
+
 - (void)resetCursorRects
 {
     [super resetCursorRects];
@@ -995,6 +1051,7 @@
 
     /* All done! */
     [pool release];
+    window->driverdata = data;
     return 0;
 }
 
@@ -1566,6 +1623,12 @@
     return succeeded;
 }
 
+int
+Cocoa_SetWindowHitTest(SDL_Window * window, SDL_bool enabled)
+{
+    return 0;  /* just succeed, the real work is done elsewhere. */
+}
+
 #endif /* SDL_VIDEO_DRIVER_COCOA */
 
 /* vi: set ts=4 sw=4 expandtab: */
--- a/src/video/windows/SDL_windowsevents.c	Wed Jun 25 02:08:37 2014 -0700
+++ b/src/video/windows/SDL_windowsevents.c	Wed Jun 25 17:06:12 2014 -0400
@@ -30,6 +30,7 @@
 #include "../../events/SDL_events_c.h"
 #include "../../events/SDL_touch_c.h"
 #include "../../events/scancodes_windows.h"
+#include "SDL_assert.h"
 
 /* Dropfile support */
 #include <shellapi.h>
@@ -438,33 +439,55 @@
             HRAWINPUT hRawInput = (HRAWINPUT)lParam;
             RAWINPUT inp;
             UINT size = sizeof(inp);
+            const SDL_bool isRelative = mouse->relative_mode || mouse->relative_mode_warp;
+            const SDL_bool isCapture = ((data->window->flags & SDL_WINDOW_MOUSE_CAPTURE) != 0);
 
-            if (!mouse->relative_mode || mouse->relative_mode_warp || mouse->focus != data->window) {
-                break;
+            if (!isRelative || mouse->focus != data->window) {
+                if (!isCapture) {
+                    break;
+                }
             }
 
             GetRawInputData(hRawInput, RID_INPUT, &inp, &size, sizeof(RAWINPUTHEADER));
 
             /* Mouse data */
             if (inp.header.dwType == RIM_TYPEMOUSE) {
-                RAWMOUSE* mouse = &inp.data.mouse;
+                if (isRelative) {
+                    RAWMOUSE* mouse = &inp.data.mouse;
 
-                if ((mouse->usFlags & 0x01) == MOUSE_MOVE_RELATIVE) {
-                    SDL_SendMouseMotion(data->window, 0, 1, (int)mouse->lLastX, (int)mouse->lLastY);
-                } else {
-                    /* synthesize relative moves from the abs position */
-                    static SDL_Point initialMousePoint;
-                    if (initialMousePoint.x == 0 && initialMousePoint.y == 0) {
+                    if ((mouse->usFlags & 0x01) == MOUSE_MOVE_RELATIVE) {
+                        SDL_SendMouseMotion(data->window, 0, 1, (int)mouse->lLastX, (int)mouse->lLastY);
+                    } else {
+                        /* synthesize relative moves from the abs position */
+                        static SDL_Point initialMousePoint;
+                        if (initialMousePoint.x == 0 && initialMousePoint.y == 0) {
+                            initialMousePoint.x = mouse->lLastX;
+                            initialMousePoint.y = mouse->lLastY;
+                        }
+
+                        SDL_SendMouseMotion(data->window, 0, 1, (int)(mouse->lLastX-initialMousePoint.x), (int)(mouse->lLastY-initialMousePoint.y) );
+
                         initialMousePoint.x = mouse->lLastX;
                         initialMousePoint.y = mouse->lLastY;
                     }
-
-                    SDL_SendMouseMotion(data->window, 0, 1, (int)(mouse->lLastX-initialMousePoint.x), (int)(mouse->lLastY-initialMousePoint.y));
-
-                    initialMousePoint.x = mouse->lLastX;
-                    initialMousePoint.y = mouse->lLastY;
+                    WIN_CheckRawMouseButtons( mouse->usButtonFlags, data );
+                } else if (isCapture) {
+                    /* we check for where Windows thinks the system cursor lives in this case, so we don't really lose mouse accel, etc. */
+                    POINT pt;
+                    HWND hwnd = data->hwnd;
+                    GetCursorPos(&pt);
+                    if (WindowFromPoint(pt) != hwnd) {  /* if in the window, WM_MOUSEMOVE, etc, will cover it. */
+                        ScreenToClient(data->hwnd, &pt);
+                        SDL_SendMouseMotion(data->window, 0, 0, (int) pt.x, (int) pt.y);
+                        SDL_SendMouseButton(data->window, 0, GetAsyncKeyState(VK_LBUTTON) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_LEFT);
+                        SDL_SendMouseButton(data->window, 0, GetAsyncKeyState(VK_RBUTTON) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_RIGHT);
+                        SDL_SendMouseButton(data->window, 0, GetAsyncKeyState(VK_MBUTTON) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_MIDDLE);
+                        SDL_SendMouseButton(data->window, 0, GetAsyncKeyState(VK_XBUTTON1) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_X1);
+                        SDL_SendMouseButton(data->window, 0, GetAsyncKeyState(VK_XBUTTON2) & 0x8000 ? SDL_PRESSED : SDL_RELEASED, SDL_BUTTON_X2);
+                    }
+                } else {
+                    SDL_assert(0 && "Shouldn't happen");
                 }
-                WIN_CheckRawMouseButtons(mouse->usButtonFlags, data);
             }
         }
         break;
@@ -509,7 +532,7 @@
 
 #ifdef WM_MOUSELEAVE
     case WM_MOUSELEAVE:
-        if (SDL_GetMouseFocus() == data->window && !SDL_GetMouse()->relative_mode) {
+        if (SDL_GetMouseFocus() == data->window && !SDL_GetMouse()->relative_mode && !(data->window->flags & SDL_WINDOW_MOUSE_CAPTURE)) {
             if (!IsIconic(hwnd)) {
                 POINT cursorPos;
                 GetCursorPos(&cursorPos);
@@ -863,6 +886,32 @@
             return 0;
         }
         break;
+
+    case WM_NCHITTEST:
+        {
+            SDL_Window *window = data->window;
+            if (window->hit_test) {
+                POINT winpoint = { (int) LOWORD(lParam), (int) HIWORD(lParam) };
+                if (ScreenToClient(data->hwnd, &winpoint)) {
+                    const SDL_Point point = { (int) winpoint.x, (int) winpoint.y };
+                    const SDL_HitTestResult rc = window->hit_test(window, &point, window->hit_test_data);
+                    switch (rc) {
+                        case SDL_HITTEST_DRAGGABLE: return HTCAPTION;
+                        case SDL_HITTEST_RESIZE_TOPLEFT: return HTTOPLEFT;
+                        case SDL_HITTEST_RESIZE_TOP: return HTTOP;
+                        case SDL_HITTEST_RESIZE_TOPRIGHT: return HTTOPRIGHT;
+                        case SDL_HITTEST_RESIZE_RIGHT: return HTRIGHT;
+                        case SDL_HITTEST_RESIZE_BOTTOMRIGHT: return HTBOTTOMRIGHT;
+                        case SDL_HITTEST_RESIZE_BOTTOM: return HTBOTTOM;
+                        case SDL_HITTEST_RESIZE_BOTTOMLEFT: return HTBOTTOMLEFT;
+                        case SDL_HITTEST_RESIZE_LEFT: return HTLEFT;
+                    }
+                }
+                /* If we didn't return, this will call DefWindowProc below. */
+            }
+        }
+        break;
+
     }
 
     /* If there's a window proc, assume it's going to handle messages */
--- a/src/video/windows/SDL_windowsmouse.c	Wed Jun 25 02:08:37 2014 -0700
+++ b/src/video/windows/SDL_windowsmouse.c	Wed Jun 25 17:06:12 2014 -0400
@@ -30,6 +30,44 @@
 
 HCURSOR SDL_cursor = NULL;
 
+static int rawInputEnableCount = 0;
+
+static int 
+ToggleRawInput(SDL_bool enabled)
+{
+    RAWINPUTDEVICE rawMouse = { 0x01, 0x02, 0, NULL }; /* Mouse: UsagePage = 1, Usage = 2 */
+
+    if (enabled) {
+        rawInputEnableCount++;
+        if (rawInputEnableCount > 1) {
+            return 0;  /* already done. */
+        }
+    } else {
+        if (rawInputEnableCount == 0) {
+            return 0;  /* already done. */
+        }
+        rawInputEnableCount--;
+        if (rawInputEnableCount > 0) {
+            return 0;  /* not time to disable yet */
+        }
+    }
+
+    if (!enabled) {
+        rawMouse.dwFlags |= RIDEV_REMOVE;
+    }
+
+    /* (Un)register raw input for mice */
+    if (RegisterRawInputDevices(&rawMouse, 1, sizeof(RAWINPUTDEVICE)) == FALSE) {
+
+        /* Only return an error when registering. If we unregister and fail,
+           then it's probably that we unregistered twice. That's OK. */
+        if (enabled) {
+            return SDL_Unsupported();
+        }
+    }
+    return 0;
+}
+
 
 static SDL_Cursor *
 WIN_CreateDefaultCursor()
@@ -211,22 +249,43 @@
 static int
 WIN_SetRelativeMouseMode(SDL_bool enabled)
 {
-    RAWINPUTDEVICE rawMouse = { 0x01, 0x02, 0, NULL }; /* Mouse: UsagePage = 1, Usage = 2 */
+    return ToggleRawInput(enabled);
+}
 
-    if (!enabled) {
-        rawMouse.dwFlags |= RIDEV_REMOVE;
+static int
+WIN_CaptureMouse(SDL_Window *window)
+{
+    if (!window) {
+        SDL_Window *focusWin = SDL_GetKeyboardFocus();
+        if (focusWin) {
+            SDL_WindowData *data = (SDL_WindowData *)focusWin->driverdata;
+            WIN_OnWindowEnter(SDL_GetVideoDevice(), focusWin);  /* make sure WM_MOUSELEAVE messages are (re)enabled. */
+        }
     }
 
-    /* (Un)register raw input for mice */
-    if (RegisterRawInputDevices(&rawMouse, 1, sizeof(RAWINPUTDEVICE)) == FALSE) {
+    /* While we were thinking of SetCapture() when designing this API in SDL,
+       we didn't count on the fact that SetCapture() only tracks while the
+       left mouse button is held down! Instead, we listen for raw mouse input
+       and manually query the mouse when it leaves the window. :/ */
+    return ToggleRawInput(window != NULL);
+}
 
-        /* Only return an error when registering. If we unregister and fail,
-           then it's probably that we unregistered twice. That's OK. */
-        if (enabled) {
-            return SDL_Unsupported();
-        }
-    }
-    return 0;
+static Uint32
+WIN_GetGlobalMouseState(int *x, int *y)
+{
+    Uint32 retval = 0;
+    POINT pt = { 0, 0 };
+    GetCursorPos(&pt);
+    *x = (int) pt.x;
+    *y = (int) pt.y;
+
+    retval |= GetAsyncKeyState(VK_LBUTTON) & 0x8000 ? SDL_BUTTON_LMASK : 0;
+    retval |= GetAsyncKeyState(VK_RBUTTON) & 0x8000 ? SDL_BUTTON_RMASK : 0;
+    retval |= GetAsyncKeyState(VK_MBUTTON) & 0x8000 ? SDL_BUTTON_MMASK : 0;
+    retval |= GetAsyncKeyState(VK_X1BUTTON) & 0x8000 ? SDL_BUTTON_X1MASK : 0;
+    retval |= GetAsyncKeyState(VK_X2BUTTON) & 0x8000 ? SDL_BUTTON_X2MASK : 0;
+
+    return retval;
 }
 
 void
@@ -241,6 +300,8 @@
     mouse->WarpMouse = WIN_WarpMouse;
     mouse->WarpMouseGlobal = WIN_WarpMouseGlobal;
     mouse->SetRelativeMouseMode = WIN_SetRelativeMouseMode;
+    mouse->CaptureMouse = WIN_CaptureMouse;
+    mouse->GetGlobalMouseState = WIN_GetGlobalMouseState;
 
     SDL_SetDefaultCursor(WIN_CreateDefaultCursor());
 
@@ -256,6 +317,11 @@
         mouse->def_cursor = NULL;
         mouse->cur_cursor = NULL;
     }
+
+    if (rawInputEnableCount) {  /* force RAWINPUT off here. */
+        rawInputEnableCount = 1;
+        ToggleRawInput(SDL_FALSE);
+    }
 }
 
 #endif /* SDL_VIDEO_DRIVER_WINDOWS */
--- a/src/video/windows/SDL_windowsvideo.c	Wed Jun 25 02:08:37 2014 -0700
+++ b/src/video/windows/SDL_windowsvideo.c	Wed Jun 25 17:06:12 2014 -0400
@@ -144,6 +144,7 @@
     device->UpdateWindowFramebuffer = WIN_UpdateWindowFramebuffer;
     device->DestroyWindowFramebuffer = WIN_DestroyWindowFramebuffer;
     device->OnWindowEnter = WIN_OnWindowEnter;
+    device->SetWindowHitTest = WIN_SetWindowHitTest;
 
     device->shape_driver.CreateShaper = Win32_CreateShaper;
     device->shape_driver.SetWindowShape = Win32_SetWindowShape;
--- a/src/video/windows/SDL_windowswindow.c	Wed Jun 25 02:08:37 2014 -0700
+++ b/src/video/windows/SDL_windowswindow.c	Wed Jun 25 17:06:12 2014 -0400
@@ -785,6 +785,12 @@
     }
 }
 
+int
+WIN_SetWindowHitTest(SDL_Window *window, SDL_bool enabled)
+{
+    return 0;  /* just succeed, the real work is done elsewhere. */
+}
+
 #endif /* SDL_VIDEO_DRIVER_WINDOWS */
 
 /* vi: set ts=4 sw=4 expandtab: */
--- a/src/video/windows/SDL_windowswindow.h	Wed Jun 25 02:08:37 2014 -0700
+++ b/src/video/windows/SDL_windowswindow.h	Wed Jun 25 17:06:12 2014 -0400
@@ -69,6 +69,7 @@
                                     struct SDL_SysWMinfo *info);
 extern void WIN_OnWindowEnter(_THIS, SDL_Window * window);
 extern void WIN_UpdateClipCursor(SDL_Window *window);
+extern int WIN_SetWindowHitTest(SDL_Window *window, SDL_bool enabled);
 
 #endif /* _SDL_windowswindow_h */
 
--- a/src/video/x11/SDL_x11events.c	Wed Jun 25 02:08:37 2014 -0700
+++ b/src/video/x11/SDL_x11events.c	Wed Jun 25 17:06:12 2014 -0400
@@ -40,6 +40,42 @@
 
 #include <stdio.h>
 
+#ifndef _NET_WM_MOVERESIZE_SIZE_TOPLEFT
+#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT      0
+#endif
+
+#ifndef _NET_WM_MOVERESIZE_SIZE_TOP
+#define _NET_WM_MOVERESIZE_SIZE_TOP          1
+#endif
+
+#ifndef _NET_WM_MOVERESIZE_SIZE_TOPRIGHT
+#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT     2
+#endif
+
+#ifndef _NET_WM_MOVERESIZE_SIZE_RIGHT
+#define _NET_WM_MOVERESIZE_SIZE_RIGHT        3
+#endif
+
+#ifndef _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT
+#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT  4
+#endif
+
+#ifndef _NET_WM_MOVERESIZE_SIZE_BOTTOM
+#define _NET_WM_MOVERESIZE_SIZE_BOTTOM       5
+#endif
+
+#ifndef _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT
+#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT   6
+#endif
+
+#ifndef _NET_WM_MOVERESIZE_SIZE_LEFT
+#define _NET_WM_MOVERESIZE_SIZE_LEFT         7
+#endif
+
+#ifndef _NET_WM_MOVERESIZE_MOVE
+#define _NET_WM_MOVERESIZE_MOVE              8
+#endif
+
 typedef struct {
     unsigned char *data;
     int format, count;
@@ -348,6 +384,124 @@
 }
 
 static void
+InitiateWindowMove(_THIS, const SDL_WindowData *data, const SDL_Point *point)
+{
+    SDL_VideoData *viddata = (SDL_VideoData *) _this->driverdata;
+    SDL_Window* window = data->window;
+    Display *display = viddata->display;
+
+    /* !!! FIXME: we need to regrab this if necessary when the drag is done. */
+    X11_XUngrabPointer(display, 0L);
+    X11_XFlush(display);
+
+    XEvent evt;
+    evt.xclient.type = ClientMessage;
+    evt.xclient.window = data->xwindow;
+    evt.xclient.message_type = X11_XInternAtom(display, "_NET_WM_MOVERESIZE", True);
+    evt.xclient.format = 32;
+    evt.xclient.data.l[0] = window->x + point->x;
+    evt.xclient.data.l[1] = window->y + point->y;
+    evt.xclient.data.l[2] = _NET_WM_MOVERESIZE_MOVE;
+    evt.xclient.data.l[3] = Button1;
+    evt.xclient.data.l[4] = 0;
+    X11_XSendEvent(display, DefaultRootWindow(display), False, SubstructureRedirectMask | SubstructureNotifyMask, &evt);
+
+    X11_XSync(display, 0);
+}
+
+static void
+InitiateWindowResize(_THIS, const SDL_WindowData *data, const SDL_Point *point, int direction)
+{
+    SDL_VideoData *viddata = (SDL_VideoData *) _this->driverdata;
+    SDL_Window* window = data->window;
+    Display *display = viddata->display;
+
+    if (direction < _NET_WM_MOVERESIZE_SIZE_TOPLEFT || direction > _NET_WM_MOVERESIZE_SIZE_LEFT)
+        return;
+
+    /* !!! FIXME: we need to regrab this if necessary when the drag is done. */
+    X11_XUngrabPointer(display, 0L);
+    X11_XFlush(display);
+
+    XEvent evt;
+    evt.xclient.type = ClientMessage;
+    evt.xclient.window = data->xwindow;
+    evt.xclient.message_type = X11_XInternAtom(display, "_NET_WM_MOVERESIZE", True);
+    evt.xclient.format = 32;
+    evt.xclient.data.l[0] = window->x + point->x;
+    evt.xclient.data.l[1] = window->y + point->y;
+    evt.xclient.data.l[2] = direction;
+    evt.xclient.data.l[3] = Button1;
+    evt.xclient.data.l[4] = 0;
+    X11_XSendEvent(display, DefaultRootWindow(display), False, SubstructureRedirectMask | SubstructureNotifyMask, &evt);
+
+    X11_XSync(display, 0);
+}
+
+static SDL_bool
+ProcessHitTest(_THIS, const SDL_WindowData *data, const XEvent *xev)
+{
+    SDL_Window *window = data->window;
+    SDL_bool ret = SDL_FALSE;
+
+    if (window->hit_test) {
+        const SDL_Point point = { xev->xbutton.x, xev->xbutton.y };
+        const SDL_HitTestResult rc = window->hit_test(window, &point, window->hit_test_data);
+        switch (rc) {
+            case SDL_HITTEST_DRAGGABLE: {
+                    InitiateWindowMove(_this, data, &point);
+                    ret = SDL_TRUE;
+                }
+                break;
+            case SDL_HITTEST_RESIZE_TOPLEFT: {
+                    InitiateWindowResize(_this, data, &point, _NET_WM_MOVERESIZE_SIZE_TOPLEFT);
+                    ret = SDL_TRUE;
+                }
+                break;
+            case SDL_HITTEST_RESIZE_TOP: {
+                    InitiateWindowResize(_this, data, &point, _NET_WM_MOVERESIZE_SIZE_TOP);
+                    ret = SDL_TRUE;
+                }
+                break;
+            case SDL_HITTEST_RESIZE_TOPRIGHT: {
+                    InitiateWindowResize(_this, data, &point, _NET_WM_MOVERESIZE_SIZE_TOPRIGHT);
+                    ret = SDL_TRUE;
+                }
+                break;
+            case SDL_HITTEST_RESIZE_RIGHT: {
+                    InitiateWindowResize(_this, data, &point, _NET_WM_MOVERESIZE_SIZE_RIGHT);
+                    ret = SDL_TRUE;
+                }
+                break;
+            case SDL_HITTEST_RESIZE_BOTTOMRIGHT: {
+                    InitiateWindowResize(_this, data, &point, _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT);
+                    ret = SDL_TRUE;
+                }
+                break;
+            case SDL_HITTEST_RESIZE_BOTTOM: {
+                    InitiateWindowResize(_this, data, &point, _NET_WM_MOVERESIZE_SIZE_BOTTOM);
+                    ret = SDL_TRUE;
+                }
+                break;
+            case SDL_HITTEST_RESIZE_BOTTOMLEFT: {
+                    InitiateWindowResize(_this, data, &point, _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT);
+                    ret = SDL_TRUE;
+                }
+                break;
+            case SDL_HITTEST_RESIZE_LEFT: {
+                    InitiateWindowResize(_this, data, &point, _NET_WM_MOVERESIZE_SIZE_LEFT);
+                    ret = SDL_TRUE;
+                }
+                break;
+            default:
+                break;
+        }
+    }
+
+    return ret;
+}
+
+static void
 X11_DispatchEvent(_THIS)
 {
     SDL_VideoData *videodata = (SDL_VideoData *) _this->driverdata;
@@ -820,6 +974,11 @@
             if (X11_IsWheelEvent(display,&xevent,&ticks)) {
                 SDL_SendMouseWheel(data->window, 0, 0, ticks);
             } else {
+                if(xevent.xbutton.button == Button1) {
+                    if (ProcessHitTest(_this, data, &xevent)) {
+                        break;  /* don't pass this event on to app. */
+                    }
+                }
                 SDL_SendMouseButton(data->window, 0, SDL_PRESSED, xevent.xbutton.button);
             }
         }
--- a/src/video/x11/SDL_x11mouse.c	Wed Jun 25 02:08:37 2014 -0700
+++ b/src/video/x11/SDL_x11mouse.c	Wed Jun 25 17:06:12 2014 -0400
@@ -339,6 +339,62 @@
     return -1;
 }
 
+static int
+X11_CaptureMouse(SDL_Window *window)
+{
+    Display *display = GetDisplay();
+
+    if (window) {
+        SDL_WindowData *data = (SDL_WindowData *) window->driverdata;
+        const unsigned int mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask;
+        const int rc = X11_XGrabPointer(display, data->xwindow, False,
+                                        mask, GrabModeAsync, GrabModeAsync,
+                                        None, None, CurrentTime);
+        if (rc != GrabSuccess) {
+            return SDL_SetError("X server refused mouse capture");
+        }
+    } else {
+        X11_XUngrabPointer(display, CurrentTime);
+    }
+
+    X11_XSync(display, False);
+
+    return 0;
+}
+
+static Uint32
+X11_GetGlobalMouseState(int *x, int *y)
+{
+    Display *display = GetDisplay();
+    const int num_screens = SDL_GetNumVideoDisplays();
+    int i;
+
+    /* !!! FIXME: should we XSync() here first? */
+
+    for (i = 0; i < num_screens; i++) {
+        SDL_DisplayData *data = (SDL_DisplayData *) SDL_GetDisplayDriverData(i);
+        if (data != NULL) {
+            Window root, child;
+            int rootx, rooty, winx, winy;
+            unsigned int mask;
+            if (X11_XQueryPointer(display, RootWindow(display, data->screen), &root, &child, &rootx, &rooty, &winx, &winy, &mask)) {
+                Uint32 retval = 0;
+                retval |= (mask & Button1Mask) ? SDL_BUTTON_LMASK : 0;
+                retval |= (mask & Button2Mask) ? SDL_BUTTON_MMASK : 0;
+                retval |= (mask & Button3Mask) ? SDL_BUTTON_RMASK : 0;
+                *x = data->x + rootx;
+                *y = data->y + rooty;
+                return retval;
+            }
+        }
+    }
+
+    SDL_assert(0 && "The pointer wasn't on any X11 screen?!");
+
+    return 0;
+}
+
+
 void
 X11_InitMouse(_THIS)
 {
@@ -351,6 +407,8 @@
     mouse->WarpMouse = X11_WarpMouse;
     mouse->WarpMouseGlobal = X11_WarpMouseGlobal;
     mouse->SetRelativeMouseMode = X11_SetRelativeMouseMode;
+    mouse->CaptureMouse = X11_CaptureMouse;
+    mouse->GetGlobalMouseState = X11_GetGlobalMouseState;
 
     SDL_SetDefaultCursor(X11_CreateDefaultCursor());
 }
--- a/src/video/x11/SDL_x11video.c	Wed Jun 25 02:08:37 2014 -0700
+++ b/src/video/x11/SDL_x11video.c	Wed Jun 25 17:06:12 2014 -0400
@@ -243,6 +243,7 @@
     device->UpdateWindowFramebuffer = X11_UpdateWindowFramebuffer;
     device->DestroyWindowFramebuffer = X11_DestroyWindowFramebuffer;
     device->GetWindowWMInfo = X11_GetWindowWMInfo;
+    device->SetWindowHitTest = X11_SetWindowHitTest;
 
     device->shape_driver.CreateShaper = X11_CreateShaper;
     device->shape_driver.SetWindowShape = X11_SetWindowShape;
--- a/src/video/x11/SDL_x11window.c	Wed Jun 25 02:08:37 2014 -0700
+++ b/src/video/x11/SDL_x11window.c	Wed Jun 25 17:06:12 2014 -0400
@@ -1445,6 +1445,12 @@
     }
 }
 
+int
+X11_SetWindowHitTest(SDL_Window *window, SDL_bool enabled)
+{
+    return 0;  /* just succeed, the real work is done elsewhere. */
+}
+
 #endif /* SDL_VIDEO_DRIVER_X11 */
 
 /* vi: set ts=4 sw=4 expandtab: */
--- a/src/video/x11/SDL_x11window.h	Wed Jun 25 02:08:37 2014 -0700
+++ b/src/video/x11/SDL_x11window.h	Wed Jun 25 17:06:12 2014 -0400
@@ -93,6 +93,7 @@
 extern void X11_DestroyWindow(_THIS, SDL_Window * window);
 extern SDL_bool X11_GetWindowWMInfo(_THIS, SDL_Window * window,
                                     struct SDL_SysWMinfo *info);
+extern int X11_SetWindowHitTest(SDL_Window *window, SDL_bool enabled);
 
 #endif /* _SDL_x11window_h */
 
--- a/test/Makefile.in	Wed Jun 25 02:08:37 2014 -0700
+++ b/test/Makefile.in	Wed Jun 25 17:06:12 2014 -0400
@@ -23,6 +23,7 @@
 	testgles$(EXE) \
 	testgles2$(EXE) \
 	testhaptic$(EXE) \
+	testhittesting$(EXE) \
 	testrumble$(EXE) \
 	testhotplug$(EXE) \
 	testthread$(EXE) \
@@ -108,6 +109,9 @@
 testrelative$(EXE): $(srcdir)/testrelative.c
 	$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
 
+testhittesting$(EXE): $(srcdir)/testhittesting.c
+	$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
+
 testdraw2$(EXE): $(srcdir)/testdraw2.c
 	$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/testhittesting.c	Wed Jun 25 17:06:12 2014 -0400
@@ -0,0 +1,134 @@
+#include <stdio.h>
+#include "SDL.h"
+
+/* !!! FIXME: rewrite this to be wired in to test framework. */
+
+#define RESIZE_BORDER 20
+
+const SDL_Rect drag_areas[] = {
+    { 20, 20, 100, 100 },
+    { 200, 70, 100, 100 },
+    { 400, 90, 100, 100 }
+};
+
+static const SDL_Rect *areas = drag_areas;
+static int numareas = SDL_arraysize(drag_areas);
+
+static SDL_HitTestResult
+hitTest(SDL_Window *window, const SDL_Point *pt, void *data)
+{
+    int i;
+    int w, h;
+
+    for (i = 0; i < numareas; i++) {
+        if (SDL_PointInRect(pt, &areas[i])) {
+            SDL_Log("HIT-TEST: DRAGGABLE\n");
+            return SDL_HITTEST_DRAGGABLE;
+        }
+    }
+
+    SDL_GetWindowSize(window, &w, &h);
+
+    #define REPORT_RESIZE_HIT(name) { \
+        SDL_Log("HIT-TEST: RESIZE_" #name "\n"); \
+        return SDL_HITTEST_RESIZE_##name; \
+    }
+
+    if (pt->x < RESIZE_BORDER && pt->y < RESIZE_BORDER) {
+        REPORT_RESIZE_HIT(TOPLEFT);
+    } else if (pt->x > RESIZE_BORDER && pt->x < w - RESIZE_BORDER && pt->y < RESIZE_BORDER) {
+        REPORT_RESIZE_HIT(TOP);
+    } else if (pt->x > w - RESIZE_BORDER && pt->y < RESIZE_BORDER) {
+        REPORT_RESIZE_HIT(TOPRIGHT);
+    } else if (pt->x > w - RESIZE_BORDER && pt->y > RESIZE_BORDER && pt->y < h - RESIZE_BORDER) {
+        REPORT_RESIZE_HIT(RIGHT);
+    } else if (pt->x > w - RESIZE_BORDER && pt->y > h - RESIZE_BORDER) {
+        REPORT_RESIZE_HIT(BOTTOMRIGHT);
+    } else if (pt->x < w - RESIZE_BORDER && pt->x > RESIZE_BORDER && pt->y > h - RESIZE_BORDER) {
+        REPORT_RESIZE_HIT(BOTTOM);
+    } else if (pt->x < RESIZE_BORDER && pt->y > h - RESIZE_BORDER) {
+        REPORT_RESIZE_HIT(BOTTOMLEFT);
+    } else if (pt->x < RESIZE_BORDER && pt->y < h - RESIZE_BORDER && pt->y > RESIZE_BORDER) {
+        REPORT_RESIZE_HIT(LEFT);
+    }
+
+    SDL_Log("HIT-TEST: NORMAL\n");
+    return SDL_HITTEST_NORMAL;
+}
+
+
+int main(int argc, char **argv)
+{
+    int done = 0;
+    SDL_Window *window;
+    SDL_Renderer *renderer;
+
+    /* !!! FIXME: check for errors. */
+    SDL_Init(SDL_INIT_VIDEO);
+    window = SDL_CreateWindow("Drag the red boxes", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_BORDERLESS | SDL_WINDOW_RESIZABLE);
+    renderer = SDL_CreateRenderer(window, -1, 0);
+
+    if (SDL_SetWindowHitTest(window, hitTest, NULL) == -1) {
+        SDL_Log("Enabling hit-testing failed!\n");
+        SDL_Quit();
+        return 1;
+    }
+
+    while (!done)
+    {
+        SDL_Event e;
+        int nothing_to_do = 1;
+
+        SDL_SetRenderDrawColor(renderer, 0, 0, 127, 255);
+        SDL_RenderClear(renderer);
+        SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
+        SDL_RenderFillRects(renderer, areas, SDL_arraysize(drag_areas));
+        SDL_RenderPresent(renderer);
+
+        while (SDL_PollEvent(&e)) {
+            nothing_to_do = 0;
+
+            switch (e.type)
+            {
+                case SDL_MOUSEBUTTONDOWN:
+                    SDL_Log("button down!\n");
+                    break;
+
+                case SDL_MOUSEBUTTONUP:
+                    SDL_Log("button up!\n");
+                    break;
+
+                case SDL_WINDOWEVENT:
+                    if (e.window.event == SDL_WINDOWEVENT_MOVED) {
+                        SDL_Log("Window event moved to (%d, %d)!\n", (int) e.window.data1, (int) e.window.data2);
+                    }
+                    break;
+
+                case SDL_KEYDOWN:
+                    if (e.key.keysym.sym == SDLK_ESCAPE) {
+                        done = 1;
+                    } else if (e.key.keysym.sym == SDLK_x) {
+                        if (!areas) {
+                            areas = drag_areas;
+                            numareas = SDL_arraysize(drag_areas);
+                        } else {
+                            areas = NULL;
+                            numareas = 0;
+                        }
+                    }
+                    break;
+
+                case SDL_QUIT:
+                    done = 1;
+                    break;
+            }
+        }
+
+        if (nothing_to_do) {
+            SDL_Delay(50);
+        }
+    }
+
+    SDL_Quit();
+    return 0;
+}