src/stdlib/SDL_getenv.c
author Sam Lantinga <slouken@libsdl.org>
Sun, 10 Dec 2017 09:17:33 -0800
changeset 11758 c70cf178aacb
parent 11678 1f10a52295e3
permissions -rw-r--r--
Workaround for bug 3931 - spurious SDL_MOUSEMOTION events with SDL_HINT_MOUSE_RELATIVE_MODE_WARP 1 since Windows 10 Fall Creators update

Elisée Maurer

The attached minimal program sets the SDL_HINT_MOUSE_RELATIVE_MODE_WARP to 1, enables relative mouse mode then logs all SDL_MOUSEMOTION xrel values as they happen.

When moving the mouse exclusively to the right:

* On a Windows 10 installation before Fall Creators update (for instance, Version 10.0.15063 Build 15063), only positive values are reported, as expected
* On a Windows 10 installation after Fall Creators update (for instance, Version 10.0.16299 Update 16299), a mix of positive and negative values are reported.

3 different people have reproduced this bug and have confirmed it started to happen after the Fall Creators update was installed. It happens with SDL 2.0.7 as well as latest default branch as of today.

It seems like some obscure (maybe unintended) Windows behavior change? Haven't been able to pin it down more yet.

(To force-upgrade a Windows installation to the Fall Creators update, you can use the update assistant at https://www.microsoft.com/en-us/software-download/windows10)

Eric Wasylishen

Broken GetCursorPos / SetCursorPos based games on Win 10 fall creators are not limited to SDL.. I just tested winquake.exe (original 1997 exe) and it now has "jumps" in the mouse input if you try to look around in a circle. It uses GetCursorPos/SetCursorPos by default. Switching WinQuake to use directinput (-dinput flag) seems to get rid of the jumps.

Daniel Gibson

A friend tested on Win10 1607 (which is before the Fall Creators Update) and the the bug doesn't occur there, so the regression that SetCursorPos() doesn't reliably generate mouse events was indeed introduced with that update.
I even reproduced it in a minimal WinAPI-only application (https://gist.github.com/DanielGibson/b5b033c67b9137f0280af9fc53352c68), the weird thing is that if you don't do anything each "frame" (i.e. the mainloop only polls the events and does nothing else), there are a lot of mouse events with the coordinates you passed to SetCursorPos(), but when sleeping for 10ms in each iteration of the mainloop, those events basically don't happen anymore. Which is bad, because in games the each iteration of the mainloop usually takes 16ms..

I have a patch now that I find acceptable.
It checks for the windows version with RtlGetVersion() (https://msdn.microsoft.com/en-us/library/windows/hardware/ff561910.aspx) and only if it's >= Win10 build 16299, enables the workaround.
All code is in video/windows/SDL_windowsevents.c
and the workaround is, that for each WM_MOUSEMOVE event, "if(isWin10FCUorNewer && mouseID != SDL_TOUCH_MOUSEID && mouse->relative_mode_warp)", an addition mouse move event is generated with the coordinates of the center of the screen
(SDL_SendMouseMotion(data->window, mouseID, 0, center_x, center_y);) - which is exactly what would happen if windows generated those reliably itself.
This will cause SDL_PrivateSendMouseMotion() to set mouse->last_x = center_x; and mouse->last_y = center_y; so the next mouse relative mouse event will be calculated correctly.

If Microsoft ever fixes this bug, the IsWin10FCUorNewer() function would have to
be adjusted to also check for a maximum version, so the workaround is then disabled again.
     1 /*
     2   Simple DirectMedia Layer
     3   Copyright (C) 1997-2017 Sam Lantinga <slouken@libsdl.org>
     4 
     5   This software is provided 'as-is', without any express or implied
     6   warranty.  In no event will the authors be held liable for any damages
     7   arising from the use of this software.
     8 
     9   Permission is granted to anyone to use this software for any purpose,
    10   including commercial applications, and to alter it and redistribute it
    11   freely, subject to the following restrictions:
    12 
    13   1. The origin of this software must not be misrepresented; you must not
    14      claim that you wrote the original software. If you use this software
    15      in a product, an acknowledgment in the product documentation would be
    16      appreciated but is not required.
    17   2. Altered source versions must be plainly marked as such, and must not be
    18      misrepresented as being the original software.
    19   3. This notice may not be removed or altered from any source distribution.
    20 */
    21 
    22 #if defined(__clang_analyzer__) && !defined(SDL_DISABLE_ANALYZE_MACROS)
    23 #define SDL_DISABLE_ANALYZE_MACROS 1
    24 #endif
    25 
    26 #include "../SDL_internal.h"
    27 
    28 #if defined(__WIN32__)
    29 #include "../core/windows/SDL_windows.h"
    30 #endif
    31 
    32 #if defined(__ANDROID__)
    33 #include "../core/android/SDL_android.h"
    34 #endif
    35 
    36 #include "SDL_stdinc.h"
    37 
    38 #if defined(__WIN32__) && (!defined(HAVE_SETENV) || !defined(HAVE_GETENV))
    39 /* Note this isn't thread-safe! */
    40 static char *SDL_envmem = NULL; /* Ugh, memory leak */
    41 static size_t SDL_envmemlen = 0;
    42 #endif
    43 
    44 /* Put a variable into the environment */
    45 /* Note: Name may not contain a '=' character. (Reference: http://www.unix.com/man-page/Linux/3/setenv/) */
    46 #if defined(HAVE_SETENV)
    47 int
    48 SDL_setenv(const char *name, const char *value, int overwrite)
    49 {
    50     /* Input validation */
    51     if (!name || SDL_strlen(name) == 0 || SDL_strchr(name, '=') != NULL || !value) {
    52         return (-1);
    53     }
    54     
    55     return setenv(name, value, overwrite);
    56 }
    57 #elif defined(__WIN32__)
    58 int
    59 SDL_setenv(const char *name, const char *value, int overwrite)
    60 {
    61     /* Input validation */
    62     if (!name || SDL_strlen(name) == 0 || SDL_strchr(name, '=') != NULL || !value) {
    63         return (-1);
    64     }
    65     
    66     if (!overwrite) {
    67         if (GetEnvironmentVariableA(name, NULL, 0) > 0) {
    68             return 0;  /* asked not to overwrite existing value. */
    69         }
    70     }
    71     if (!SetEnvironmentVariableA(name, *value ? value : NULL)) {
    72         return -1;
    73     }
    74     return 0;
    75 }
    76 /* We have a real environment table, but no real setenv? Fake it w/ putenv. */
    77 #elif (defined(HAVE_GETENV) && defined(HAVE_PUTENV) && !defined(HAVE_SETENV))
    78 int
    79 SDL_setenv(const char *name, const char *value, int overwrite)
    80 {
    81     size_t len;
    82     char *new_variable;
    83 
    84     /* Input validation */
    85     if (!name || SDL_strlen(name) == 0 || SDL_strchr(name, '=') != NULL || !value) {
    86         return (-1);
    87     }
    88     
    89     if (getenv(name) != NULL) {
    90         if (overwrite) {
    91             unsetenv(name);
    92         } else {
    93             return 0;  /* leave the existing one there. */
    94         }
    95     }
    96 
    97     /* This leaks. Sorry. Get a better OS so we don't have to do this. */
    98     len = SDL_strlen(name) + SDL_strlen(value) + 2;
    99     new_variable = (char *) SDL_malloc(len);
   100     if (!new_variable) {
   101         return (-1);
   102     }
   103 
   104     SDL_snprintf(new_variable, len, "%s=%s", name, value);
   105     return putenv(new_variable);
   106 }
   107 #else /* roll our own */
   108 static char **SDL_env = (char **) 0;
   109 int
   110 SDL_setenv(const char *name, const char *value, int overwrite)
   111 {
   112     int added;
   113     int len, i;
   114     char **new_env;
   115     char *new_variable;
   116 
   117     /* Input validation */
   118     if (!name || SDL_strlen(name) == 0 || SDL_strchr(name, '=') != NULL || !value) {
   119         return (-1);
   120     }
   121 
   122     /* See if it already exists */
   123     if (!overwrite && SDL_getenv(name)) {
   124         return 0;
   125     }
   126 
   127     /* Allocate memory for the variable */
   128     len = SDL_strlen(name) + SDL_strlen(value) + 2;
   129     new_variable = (char *) SDL_malloc(len);
   130     if (!new_variable) {
   131         return (-1);
   132     }
   133 
   134     SDL_snprintf(new_variable, len, "%s=%s", name, value);
   135     value = new_variable + SDL_strlen(name) + 1;
   136     name = new_variable;
   137 
   138     /* Actually put it into the environment */
   139     added = 0;
   140     i = 0;
   141     if (SDL_env) {
   142         /* Check to see if it's already there... */
   143         len = (value - name);
   144         for (; SDL_env[i]; ++i) {
   145             if (SDL_strncmp(SDL_env[i], name, len) == 0) {
   146                 break;
   147             }
   148         }
   149         /* If we found it, just replace the entry */
   150         if (SDL_env[i]) {
   151             SDL_free(SDL_env[i]);
   152             SDL_env[i] = new_variable;
   153             added = 1;
   154         }
   155     }
   156 
   157     /* Didn't find it in the environment, expand and add */
   158     if (!added) {
   159         new_env = SDL_realloc(SDL_env, (i + 2) * sizeof(char *));
   160         if (new_env) {
   161             SDL_env = new_env;
   162             SDL_env[i++] = new_variable;
   163             SDL_env[i++] = (char *) 0;
   164             added = 1;
   165         } else {
   166             SDL_free(new_variable);
   167         }
   168     }
   169     return (added ? 0 : -1);
   170 }
   171 #endif
   172 
   173 /* Retrieve a variable named "name" from the environment */
   174 #if defined(HAVE_GETENV)
   175 char *
   176 SDL_getenv(const char *name)
   177 {
   178 #if defined(__ANDROID__)
   179     /* Make sure variables from the application manifest are available */
   180     Android_JNI_GetManifestEnvironmentVariables();
   181 #endif
   182 
   183     /* Input validation */
   184     if (!name || !*name) {
   185         return NULL;
   186     }
   187 
   188     return getenv(name);
   189 }
   190 #elif defined(__WIN32__)
   191 char *
   192 SDL_getenv(const char *name)
   193 {
   194     size_t bufferlen;
   195 
   196     /* Input validation */
   197     if (!name || SDL_strlen(name)==0) {
   198         return NULL;
   199     }
   200     
   201     bufferlen =
   202         GetEnvironmentVariableA(name, SDL_envmem, (DWORD) SDL_envmemlen);
   203     if (bufferlen == 0) {
   204         return NULL;
   205     }
   206     if (bufferlen > SDL_envmemlen) {
   207         char *newmem = (char *) SDL_realloc(SDL_envmem, bufferlen);
   208         if (newmem == NULL) {
   209             return NULL;
   210         }
   211         SDL_envmem = newmem;
   212         SDL_envmemlen = bufferlen;
   213         GetEnvironmentVariableA(name, SDL_envmem, (DWORD) SDL_envmemlen);
   214     }
   215     return SDL_envmem;
   216 }
   217 #else
   218 char *
   219 SDL_getenv(const char *name)
   220 {
   221     int len, i;
   222     char *value;
   223 
   224     /* Input validation */
   225     if (!name || SDL_strlen(name)==0) {
   226         return NULL;
   227     }
   228     
   229     value = (char *) 0;
   230     if (SDL_env) {
   231         len = SDL_strlen(name);
   232         for (i = 0; SDL_env[i] && !value; ++i) {
   233             if ((SDL_strncmp(SDL_env[i], name, len) == 0) &&
   234                 (SDL_env[i][len] == '=')) {
   235                 value = &SDL_env[i][len + 1];
   236             }
   237         }
   238     }
   239     return value;
   240 }
   241 #endif
   242 
   243 
   244 #ifdef TEST_MAIN
   245 #include <stdio.h>
   246 
   247 int
   248 main(int argc, char *argv[])
   249 {
   250     char *value;
   251 
   252     printf("Checking for non-existent variable... ");
   253     fflush(stdout);
   254     if (!SDL_getenv("EXISTS")) {
   255         printf("okay\n");
   256     } else {
   257         printf("failed\n");
   258     }
   259     printf("Setting FIRST=VALUE1 in the environment... ");
   260     fflush(stdout);
   261     if (SDL_setenv("FIRST", "VALUE1", 0) == 0) {
   262         printf("okay\n");
   263     } else {
   264         printf("failed\n");
   265     }
   266     printf("Getting FIRST from the environment... ");
   267     fflush(stdout);
   268     value = SDL_getenv("FIRST");
   269     if (value && (SDL_strcmp(value, "VALUE1") == 0)) {
   270         printf("okay\n");
   271     } else {
   272         printf("failed\n");
   273     }
   274     printf("Setting SECOND=VALUE2 in the environment... ");
   275     fflush(stdout);
   276     if (SDL_setenv("SECOND", "VALUE2", 0) == 0) {
   277         printf("okay\n");
   278     } else {
   279         printf("failed\n");
   280     }
   281     printf("Getting SECOND from the environment... ");
   282     fflush(stdout);
   283     value = SDL_getenv("SECOND");
   284     if (value && (SDL_strcmp(value, "VALUE2") == 0)) {
   285         printf("okay\n");
   286     } else {
   287         printf("failed\n");
   288     }
   289     printf("Setting FIRST=NOVALUE in the environment... ");
   290     fflush(stdout);
   291     if (SDL_setenv("FIRST", "NOVALUE", 1) == 0) {
   292         printf("okay\n");
   293     } else {
   294         printf("failed\n");
   295     }
   296     printf("Getting FIRST from the environment... ");
   297     fflush(stdout);
   298     value = SDL_getenv("FIRST");
   299     if (value && (SDL_strcmp(value, "NOVALUE") == 0)) {
   300         printf("okay\n");
   301     } else {
   302         printf("failed\n");
   303     }
   304     printf("Checking for non-existent variable... ");
   305     fflush(stdout);
   306     if (!SDL_getenv("EXISTS")) {
   307         printf("okay\n");
   308     } else {
   309         printf("failed\n");
   310     }
   311     return (0);
   312 }
   313 #endif /* TEST_MAIN */
   314 
   315 /* vi: set ts=4 sw=4 expandtab: */