WinRT: workaround a possible Windows bug, whereby hiding cursors, disables mouse-moved events
authorDavid Ludwig
Sat, 14 May 2016 23:29:49 -0400
changeset 101715b61e12c0a30
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
     1.1 --- a/docs/README-winrt.md	Wed May 11 21:11:12 2016 +0200
     1.2 +++ b/docs/README-winrt.md	Sat May 14 23:29:49 2016 -0400
     1.3 @@ -159,7 +159,9 @@
     1.4     the linker, and will copy SDL's .dll files to your app's final output.
     1.5  4. adjust your app's build settings, at minimum, telling it where to find SDL's
     1.6     header files.
     1.7 -5. add a file that contains a WinRT-appropriate main function.
     1.8 +5. add files that contains a WinRT-appropriate main function, along with some
     1.9 +   data to make sure mouse-cursor-hiding (via SDL_ShowCursor(SDL_DISABLE) calls)
    1.10 +   work properly.
    1.11  6. add SDL-specific app code.
    1.12  7. build and run your app.
    1.13  
    1.14 @@ -267,33 +269,27 @@
    1.15  10. close the dialog, saving settings, by clicking the "OK" button
    1.16  
    1.17  
    1.18 -### 5. Add a WinRT-appropriate main function to the app. ###
    1.19 +### 5. Add a WinRT-appropriate main function, and a blank-cursor image, to the app. ###
    1.20  
    1.21 -C/C++-based WinRT apps do contain a `main` function that the OS will invoke when 
    1.22 -the app starts launching. The parameters of WinRT main functions are different 
    1.23 -than those found on other platforms, Win32 included.  SDL/WinRT provides a 
    1.24 -platform-appropriate main function that will perform these actions, setup key 
    1.25 -portions of the app, then invoke a classic, C/C++-style main function (that take 
    1.26 -in "argc" and "argv" parameters).  The code for this file is contained inside 
    1.27 -SDL's source distribution, under `src/main/winrt/SDL_winrt_main_NonXAML.cpp`.  
    1.28 -You'll need to add this file, or a copy of it, to your app's project, and make 
    1.29 -sure it gets compiled using a Microsoft-specific set of C++ extensions called 
    1.30 -C++/CX.
    1.31 +A few files should be included directly in your app's MSVC project, specifically:
    1.32 +1. a WinRT-appropriate main function (which is different than main() functions on
    1.33 +   other platforms)
    1.34 +2. a Win32-style cursor resource, used by SDL_ShowCursor() to hide the mouse cursor
    1.35 +   (if and when the app needs to do so).  *If this cursor resource is not
    1.36 +   included, mouse-position reporting may fail if and when the cursor is
    1.37 +   hidden, due to possible bugs/design-oddities in Windows itself.*
    1.38  
    1.39 -**NOTE: C++/CX compilation is currently required in at least one file of your 
    1.40 -app's project.  This is to make sure that Visual C++'s linker builds a 'Windows 
    1.41 -Metadata' file (.winmd) for your app.  Not doing so can lead to build errors.**
    1.42 -
    1.43 -To include `SDL_winrt_main_NonXAML.cpp`:
    1.44 +To include these files:
    1.45  
    1.46  1. right-click on your project (again, in Visual C++'s Solution Explorer), 
    1.47     navigate to "Add", then choose "Existing Item...".
    1.48 -2. open `SDL_winrt_main_NonXAML.cpp`, which is found inside SDL's source 
    1.49 -   distribution, under `src/main/winrt/`.  Make sure that the open-file dialog 
    1.50 -   closes, either by double-clicking on the file, or single-clicking on it and 
    1.51 -   then clicking Add.
    1.52 -3. right-click on the file (as listed in your project), then click on 
    1.53 -   "Properties...".
    1.54 +2. navigate to the directory containing SDL's source code, then into its
    1.55 +   subdirectory, 'src/main/winrt/'.  Select, then add, the following files:
    1.56 +   - `SDL_winrt_main_NonXAML.cpp`
    1.57 +   - `SDL2-WinRTResources.rc`
    1.58 +   - `SDL2-WinRTResource_BlankCursor.cur`
    1.59 +3. right-click on the file `SDL_winrt_main_NonXAML.cpp` (as listed in your
    1.60 +   project), then click on "Properties...".
    1.61  4. in the drop-down box next to "Configuration", choose, "All Configurations"
    1.62  5. in the drop-down box next to "Platform", choose, "All Platforms"
    1.63  6. in the left-hand list, click on "C/C++"
    1.64 @@ -301,6 +297,11 @@
    1.65  8. click the OK button.  This will close the dialog.
    1.66  
    1.67  
    1.68 +**NOTE: C++/CX compilation is currently required in at least one file of your 
    1.69 +app's project.  This is to make sure that Visual C++'s linker builds a 'Windows 
    1.70 +Metadata' file (.winmd) for your app.  Not doing so can lead to build errors.**
    1.71 +
    1.72 +
    1.73  ### 6. Add app code and assets ###
    1.74  
    1.75  At this point, you can add in SDL-specific source code.  Be sure to include a 
    1.76 @@ -465,3 +466,13 @@
    1.77  
    1.78      /nodefaultlib:vccorlibd /nodefaultlib:msvcrtd vccorlibd.lib msvcrtd.lib
    1.79  
    1.80 +
    1.81 +#### Mouse-motion events fail to get sent, or SDL_GetMouseState() fails to return updated values
    1.82 +
    1.83 +This may be caused by a bug in Windows itself, whereby hiding the mouse
    1.84 +cursor can cause mouse-position reporting to fail.
    1.85 +
    1.86 +SDL provides a workaround for this, but it requires that an app links to a
    1.87 +set of Win32-style cursor image-resource files.  A copy of suitable resource
    1.88 +files can be found in `src/main/winrt/`.  Adding them to an app's Visual C++
    1.89 +project file should be sufficient to get the app to use them.
     2.1 Binary file src/main/winrt/SDL2-WinRTResource_BlankCursor.cur has changed
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/src/main/winrt/SDL2-WinRTResources.rc	Sat May 14 23:29:49 2016 -0400
     3.3 @@ -0,0 +1,3 @@
     3.4 +#include "winres.h"
     3.5 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
     3.6 +5000 CURSOR "SDL2-WinRTResource_BlankCursor.cur"
     4.1 --- a/src/video/winrt/SDL_winrtmouse.cpp	Wed May 11 21:11:12 2016 +0200
     4.2 +++ b/src/video/winrt/SDL_winrtmouse.cpp	Sat May 14 23:29:49 2016 -0400
     4.3 @@ -26,6 +26,7 @@
     4.4   * Windows includes:
     4.5   */
     4.6  #include <Windows.h>
     4.7 +#include <windows.ui.core.h>
     4.8  using namespace Windows::UI::Core;
     4.9  using Windows::UI::Core::CoreCursor;
    4.10  
    4.11 @@ -116,11 +117,69 @@
    4.12          return 0;
    4.13      }
    4.14  
    4.15 +    CoreWindow ^ coreWindow = CoreWindow::GetForCurrentThread();
    4.16      if (cursor) {
    4.17          CoreCursor ^* theCursor = (CoreCursor ^*) cursor->driverdata;
    4.18 -        CoreWindow::GetForCurrentThread()->PointerCursor = *theCursor;
    4.19 +        coreWindow->PointerCursor = *theCursor;
    4.20      } else {
    4.21 -        CoreWindow::GetForCurrentThread()->PointerCursor = nullptr;
    4.22 +        // HACK ALERT: TL;DR - Hiding the cursor in WinRT/UWP apps is weird, and
    4.23 +        //   a Win32-style cursor resource file must be directly included in apps,
    4.24 +        //   otherwise hiding the cursor will cause mouse-motion data to never be
    4.25 +        //   received.
    4.26 +        //
    4.27 +        // Here's the lengthy explanation:
    4.28 +        //
    4.29 +        // There are two ways to hide a cursor in WinRT/UWP apps.
    4.30 +        // Both involve setting the WinRT CoreWindow's (which is somewhat analogous
    4.31 +        // to a Win32 HWND) 'PointerCursor' property.
    4.32 +        //
    4.33 +        // The first way to hide a cursor sets PointerCursor to nullptr.  This
    4.34 +        // is, arguably, the easiest to implement for an app.  It does have an
    4.35 +        // unfortunate side-effect: it'll prevent mouse-motion events from being
    4.36 +        // sent to the app (via CoreWindow).
    4.37 +        //
    4.38 +        // The second way to hide a cursor sets PointerCursor to a transparent
    4.39 +        // cursor.  This allows mouse-motion events to be sent to the app, but is
    4.40 +        // more difficult to set up, as:
    4.41 +        //   1. WinRT/UWP, while providing a few stock cursors, does not provide
    4.42 +        //      a completely transparent cursor.
    4.43 +        //   2. WinRT/UWP allows apps to provide custom-built cursors, but *ONLY*
    4.44 +        //      if they are linked directly inside the app, via Win32-style
    4.45 +        //      cursor resource files.  APIs to create cursors at runtime are
    4.46 +        //      not provided to apps, and attempting to link-to or use Win32
    4.47 +        //      cursor-creation APIs could cause an app to fail Windows Store
    4.48 +        //      certification.
    4.49 +        //
    4.50 +        // SDL can use either means of hiding the cursor.  It provides a Win32-style
    4.51 +        // set of cursor resource files in its source distribution, inside
    4.52 +        // src/main/winrt/.  If those files are linked to an SDL-for-WinRT/UWP app
    4.53 +        // (by including them in a MSVC project, for example), SDL will attempt to
    4.54 +        // use those, if and when the cursor is hidden via SDL APIs.  If those
    4.55 +        // files are not linked in, SDL will attempt to hide the cursor via the
    4.56 +        // 'set PointerCursor to nullptr' means (which, if you recall, causes
    4.57 +        // mouse-motion data to NOT be sent to the app!).
    4.58 +        //
    4.59 +        // Tech notes:
    4.60 +        //  - SDL's blank cursor resource uses a resource ID of 5000.
    4.61 +        //  - SDL's cursor resources consist of the following two files:
    4.62 +        //     - src/main/winrt/SDL2-WinRTResource_BlankCursor.cur -- cursor pixel data
    4.63 +        //     - src/main/winrt/SDL2-WinRTResources.rc             -- declares the cursor resource, and its ID (of 5000)
    4.64 +        //
    4.65 +
    4.66 +        const unsigned int win32CursorResourceID = 5000;  
    4.67 +        CoreCursor ^ blankCursor = ref new CoreCursor(CoreCursorType::Custom, win32CursorResourceID);
    4.68 +
    4.69 +        // Set 'PointerCursor' to 'blankCursor' in a way that shouldn't throw
    4.70 +        // an exception if the app hasn't loaded that resource.
    4.71 +        ABI::Windows::UI::Core::ICoreCursor * iblankCursor = reinterpret_cast<ABI::Windows::UI::Core::ICoreCursor *>(blankCursor);
    4.72 +        ABI::Windows::UI::Core::ICoreWindow * icoreWindow = reinterpret_cast<ABI::Windows::UI::Core::ICoreWindow *>(coreWindow);
    4.73 +        HRESULT hr = icoreWindow->put_PointerCursor(iblankCursor);
    4.74 +        if (FAILED(hr)) {
    4.75 +            // The app doesn't contain the cursor resource, or some other error
    4.76 +            // occurred.  Just use the other, but mouse-motion-preventing, means of
    4.77 +            // hiding the cursor.
    4.78 +            coreWindow->PointerCursor = nullptr;
    4.79 +        }
    4.80      }
    4.81      return 0;
    4.82  }