WinRT: workaround a possible Windows bug, whereby hiding cursors, disables mouse-moved events
authorDavid Ludwig <dludwig@pobox.com>
Sat, 14 May 2016 23:29:49 -0400
changeset 10171 5b61e12c0a30
parent 10170 ccdb4d0934e9
child 10172 b4316ed0c72c
WinRT: workaround a possible Windows bug, whereby hiding cursors, disables mouse-moved events This workaround, unfortunately, requires that apps directly link to a set of Win32-style cursor resource files (that contain a transparent cursor image). Copies of suitable resource files are in src/core/winrt/, and should be included directly in an app's MSVC project. A rough explanation of this workaround/hack, and why it's needed (and seemingly can't be done through programmatic means), is in this change's code.
docs/README-winrt.md
src/main/winrt/SDL2-WinRTResource_BlankCursor.cur
src/main/winrt/SDL2-WinRTResources.rc
src/video/winrt/SDL_winrtmouse.cpp
--- a/docs/README-winrt.md	Wed May 11 21:11:12 2016 +0200
+++ b/docs/README-winrt.md	Sat May 14 23:29:49 2016 -0400
@@ -159,7 +159,9 @@
    the linker, and will copy SDL's .dll files to your app's final output.
 4. adjust your app's build settings, at minimum, telling it where to find SDL's
    header files.
-5. add a file that contains a WinRT-appropriate main function.
+5. add files that contains a WinRT-appropriate main function, along with some
+   data to make sure mouse-cursor-hiding (via SDL_ShowCursor(SDL_DISABLE) calls)
+   work properly.
 6. add SDL-specific app code.
 7. build and run your app.
 
@@ -267,33 +269,27 @@
 10. close the dialog, saving settings, by clicking the "OK" button
 
 
-### 5. Add a WinRT-appropriate main function to the app. ###
+### 5. Add a WinRT-appropriate main function, and a blank-cursor image, to the app. ###
 
-C/C++-based WinRT apps do contain a `main` function that the OS will invoke when 
-the app starts launching. The parameters of WinRT main functions are different 
-than those found on other platforms, Win32 included.  SDL/WinRT provides a 
-platform-appropriate main function that will perform these actions, setup key 
-portions of the app, then invoke a classic, C/C++-style main function (that take 
-in "argc" and "argv" parameters).  The code for this file is contained inside 
-SDL's source distribution, under `src/main/winrt/SDL_winrt_main_NonXAML.cpp`.  
-You'll need to add this file, or a copy of it, to your app's project, and make 
-sure it gets compiled using a Microsoft-specific set of C++ extensions called 
-C++/CX.
+A few files should be included directly in your app's MSVC project, specifically:
+1. a WinRT-appropriate main function (which is different than main() functions on
+   other platforms)
+2. a Win32-style cursor resource, used by SDL_ShowCursor() to hide the mouse cursor
+   (if and when the app needs to do so).  *If this cursor resource is not
+   included, mouse-position reporting may fail if and when the cursor is
+   hidden, due to possible bugs/design-oddities in Windows itself.*
 
-**NOTE: C++/CX compilation is currently required in at least one file of your 
-app's project.  This is to make sure that Visual C++'s linker builds a 'Windows 
-Metadata' file (.winmd) for your app.  Not doing so can lead to build errors.**
-
-To include `SDL_winrt_main_NonXAML.cpp`:
+To include these files:
 
 1. right-click on your project (again, in Visual C++'s Solution Explorer), 
    navigate to "Add", then choose "Existing Item...".
-2. open `SDL_winrt_main_NonXAML.cpp`, which is found inside SDL's source 
-   distribution, under `src/main/winrt/`.  Make sure that the open-file dialog 
-   closes, either by double-clicking on the file, or single-clicking on it and 
-   then clicking Add.
-3. right-click on the file (as listed in your project), then click on 
-   "Properties...".
+2. navigate to the directory containing SDL's source code, then into its
+   subdirectory, 'src/main/winrt/'.  Select, then add, the following files:
+   - `SDL_winrt_main_NonXAML.cpp`
+   - `SDL2-WinRTResources.rc`
+   - `SDL2-WinRTResource_BlankCursor.cur`
+3. right-click on the file `SDL_winrt_main_NonXAML.cpp` (as listed in your
+   project), then click on "Properties...".
 4. in the drop-down box next to "Configuration", choose, "All Configurations"
 5. in the drop-down box next to "Platform", choose, "All Platforms"
 6. in the left-hand list, click on "C/C++"
@@ -301,6 +297,11 @@
 8. click the OK button.  This will close the dialog.
 
 
+**NOTE: C++/CX compilation is currently required in at least one file of your 
+app's project.  This is to make sure that Visual C++'s linker builds a 'Windows 
+Metadata' file (.winmd) for your app.  Not doing so can lead to build errors.**
+
+
 ### 6. Add app code and assets ###
 
 At this point, you can add in SDL-specific source code.  Be sure to include a 
@@ -465,3 +466,13 @@
 
     /nodefaultlib:vccorlibd /nodefaultlib:msvcrtd vccorlibd.lib msvcrtd.lib
 
+
+#### Mouse-motion events fail to get sent, or SDL_GetMouseState() fails to return updated values
+
+This may be caused by a bug in Windows itself, whereby hiding the mouse
+cursor can cause mouse-position reporting to fail.
+
+SDL provides a workaround for this, but it requires that an app links to a
+set of Win32-style cursor image-resource files.  A copy of suitable resource
+files can be found in `src/main/winrt/`.  Adding them to an app's Visual C++
+project file should be sufficient to get the app to use them.
Binary file src/main/winrt/SDL2-WinRTResource_BlankCursor.cur has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/main/winrt/SDL2-WinRTResources.rc	Sat May 14 23:29:49 2016 -0400
@@ -0,0 +1,3 @@
+#include "winres.h"
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+5000 CURSOR "SDL2-WinRTResource_BlankCursor.cur"
--- a/src/video/winrt/SDL_winrtmouse.cpp	Wed May 11 21:11:12 2016 +0200
+++ b/src/video/winrt/SDL_winrtmouse.cpp	Sat May 14 23:29:49 2016 -0400
@@ -26,6 +26,7 @@
  * Windows includes:
  */
 #include <Windows.h>
+#include <windows.ui.core.h>
 using namespace Windows::UI::Core;
 using Windows::UI::Core::CoreCursor;
 
@@ -116,11 +117,69 @@
         return 0;
     }
 
+    CoreWindow ^ coreWindow = CoreWindow::GetForCurrentThread();
     if (cursor) {
         CoreCursor ^* theCursor = (CoreCursor ^*) cursor->driverdata;
-        CoreWindow::GetForCurrentThread()->PointerCursor = *theCursor;
+        coreWindow->PointerCursor = *theCursor;
     } else {
-        CoreWindow::GetForCurrentThread()->PointerCursor = nullptr;
+        // HACK ALERT: TL;DR - Hiding the cursor in WinRT/UWP apps is weird, and
+        //   a Win32-style cursor resource file must be directly included in apps,
+        //   otherwise hiding the cursor will cause mouse-motion data to never be
+        //   received.
+        //
+        // Here's the lengthy explanation:
+        //
+        // There are two ways to hide a cursor in WinRT/UWP apps.
+        // Both involve setting the WinRT CoreWindow's (which is somewhat analogous
+        // to a Win32 HWND) 'PointerCursor' property.
+        //
+        // The first way to hide a cursor sets PointerCursor to nullptr.  This
+        // is, arguably, the easiest to implement for an app.  It does have an
+        // unfortunate side-effect: it'll prevent mouse-motion events from being
+        // sent to the app (via CoreWindow).
+        //
+        // The second way to hide a cursor sets PointerCursor to a transparent
+        // cursor.  This allows mouse-motion events to be sent to the app, but is
+        // more difficult to set up, as:
+        //   1. WinRT/UWP, while providing a few stock cursors, does not provide
+        //      a completely transparent cursor.
+        //   2. WinRT/UWP allows apps to provide custom-built cursors, but *ONLY*
+        //      if they are linked directly inside the app, via Win32-style
+        //      cursor resource files.  APIs to create cursors at runtime are
+        //      not provided to apps, and attempting to link-to or use Win32
+        //      cursor-creation APIs could cause an app to fail Windows Store
+        //      certification.
+        //
+        // SDL can use either means of hiding the cursor.  It provides a Win32-style
+        // set of cursor resource files in its source distribution, inside
+        // src/main/winrt/.  If those files are linked to an SDL-for-WinRT/UWP app
+        // (by including them in a MSVC project, for example), SDL will attempt to
+        // use those, if and when the cursor is hidden via SDL APIs.  If those
+        // files are not linked in, SDL will attempt to hide the cursor via the
+        // 'set PointerCursor to nullptr' means (which, if you recall, causes
+        // mouse-motion data to NOT be sent to the app!).
+        //
+        // Tech notes:
+        //  - SDL's blank cursor resource uses a resource ID of 5000.
+        //  - SDL's cursor resources consist of the following two files:
+        //     - src/main/winrt/SDL2-WinRTResource_BlankCursor.cur -- cursor pixel data
+        //     - src/main/winrt/SDL2-WinRTResources.rc             -- declares the cursor resource, and its ID (of 5000)
+        //
+
+        const unsigned int win32CursorResourceID = 5000;  
+        CoreCursor ^ blankCursor = ref new CoreCursor(CoreCursorType::Custom, win32CursorResourceID);
+
+        // Set 'PointerCursor' to 'blankCursor' in a way that shouldn't throw
+        // an exception if the app hasn't loaded that resource.
+        ABI::Windows::UI::Core::ICoreCursor * iblankCursor = reinterpret_cast<ABI::Windows::UI::Core::ICoreCursor *>(blankCursor);
+        ABI::Windows::UI::Core::ICoreWindow * icoreWindow = reinterpret_cast<ABI::Windows::UI::Core::ICoreWindow *>(coreWindow);
+        HRESULT hr = icoreWindow->put_PointerCursor(iblankCursor);
+        if (FAILED(hr)) {
+            // The app doesn't contain the cursor resource, or some other error
+            // occurred.  Just use the other, but mouse-motion-preventing, means of
+            // hiding the cursor.
+            coreWindow->PointerCursor = nullptr;
+        }
     }
     return 0;
 }