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.
This commit is contained in:
David Ludwig 2016-05-14 23:29:49 -04:00
parent e964d00f64
commit 441359bd5a
4 changed files with 98 additions and 25 deletions

View File

@ -159,7 +159,9 @@ following, at a high-level:
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 @@ To change these settings:
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 @@ To include `SDL_winrt_main_NonXAML.cpp`:
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 @@ section.
/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 not shown.

After

Width:  |  Height:  |  Size: 326 B

View File

@ -0,0 +1,3 @@
#include "winres.h"
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
5000 CURSOR "SDL2-WinRTResource_BlankCursor.cur"

View File

@ -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 @@ WINRT_ShowCursor(SDL_Cursor * cursor)
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;
}