Browse Source

Merge branch 'gg-development' into SQLiteConsoleRefactor

Marc Chapman 6 years ago
parent
commit
1036097eca

+ 41 - 3
.travis.yml

@@ -1,4 +1,42 @@
 language: cpp
-compiler:
-  - clang
-  - gcc
+
+dist: xenial
+
+matrix:
+  include:
+    - os: osx
+      compiler: clang
+      env: if CXXFLAGS="-fgnu-inline-asm -fasm-blocks"
+    - os: linux
+      compiler: gcc
+    - os: linux
+      compiler: clang
+
+addons:
+  apt:
+    packages:
+      - build-essential
+      - nasm
+      - libogg-dev
+      - libxft-dev
+      - libx11-dev
+      - libxxf86vm-dev
+      - libopenal-dev
+      - libfreetype6-dev
+      - libxcursor-dev
+      - libxinerama-dev
+      - libxi-dev
+      - libxrandr-dev
+      - libxss-dev
+      - libglu1-mesa-dev
+      - libgtk-3-dev
+
+script:
+  - mkdir -p My\ Projects/TestProject/buildFiles/travis/
+  - cd My\ Projects/TestProject/buildFiles/travis/
+  - cmake ../../../.. -DTORQUE_APP_NAME=TestProject -DCMAKE_BUILD_TYPE=Debug
+  - make 2>/dev/null # Do the actual build, but ignore all the warnings
+  - make # build again. This time all output is printed but the warnings that happened earlier do not happen again
+  - make install
+  - cd ../../game/
+  - ls

+ 55 - 25
Engine/lib/nativeFileDialogs/README.md

@@ -1,6 +1,6 @@
 # Native File Dialog #
 
-A tiny, neat C library that portably invokes native file open and save dialogs.  Write dialog code once and have it pop up native dialogs on all supported platforms.  Avoid linking large dependencies like wxWidgets and qt.
+A tiny, neat C library that portably invokes native file open, folder select and save dialogs.  Write dialog code once and have it pop up native dialogs on all supported platforms.  Avoid linking large dependencies like wxWidgets and qt.
 
 Features:
 
@@ -11,11 +11,12 @@ Features:
  - Paid support available.
  - Multiple file selection support.
  - 64-bit and 32-bit friendly.
- - GCC, Clang and Visual Studio supported.
- - No third party dependencies.
+ - GCC, Clang, Xcode, Mingw and Visual Studio supported.
+ - No third party dependencies for building or linking.
  - Support for Vista's modern `IFileDialog` on Windows.
  - Support for non-deprecated Cocoa APIs on OS X.
- - GTK+3 dialog on Linux.
+ - GTK3 dialog on Linux.
+ - Optional Zenity support on Linux to avoid linking GTK.
  - Tested, works alongside [http://www.libsdl.org](SDL2) on all platforms, for the game developers out there.
 
 # Example Usage #
@@ -50,47 +51,74 @@ See [NFD.h](src/include/nfd.h) for more options.
 
 # Screenshots #
 
-![Windows 8 rendering an IFileOpenDialog](screens/open_win8.png?raw=true)
+![Windows 8 rendering an IFileOpenDialog](screens/open_win.png?raw=true)
 ![GTK3 on Linux](screens/open_gtk3.png?raw=true)
 ![Cocoa on Yosemite](screens/open_cocoa.png?raw=true)
 
+## Changelog ##
+
+release | what's new                  | date
+--------|-----------------------------|---------
+1.0.0   | initial                     | oct 2014
+1.1.0   | premake5; scons deprecated  | aug 2016
+1.1.1   | mingw support, build fixes  | aug 2016
+1.1.2   | test_pickfolder() added     | aug 2016
+1.1.3   | zenity linux backend added  | nov 2017
+1.1.3   | fix char type in decls      | nov 2017
+1.1.4   | fix win32 memleaks          | dec 2018
+1.1.4   | improve win32 errorhandling | dec 2018
+1.1.4   | macos fix focus bug         | dec 2018
+   
 
 ## Building ##
 
-NFD uses [SCons](http://www.scons.org) for cross-platform builds.  After installing SCons, build it with:
+NFD uses [Premake5](https://premake.github.io/download.html) generated Makefiles and IDE project files.  The generated project files are checked in under `build/` so you don't have to download and use Premake in most cases.
 
-    cd src
-    scons debug=[0,1]
+If you need to run Premake5 directly, further [build documentation](docs/build.md) is available.
 
-Alternatively, you can avoid Scons by just including NFD files to your existing project:
+Previously, NFD used SCons to build.  It still works, but is now deprecated; updates to it are discouraged.  Opt to use the native build system where possible.
 
- 1. Add all header files in `src/` and `src/include` to your project.
- 2. Add `src/include` to your include search path or copy it into your existing search path.
- 3. Add `src/nfd_common.c` to your project.
- 4. Add `src/nfd_<platform>` to your project, where `<platform>` is the NFD backend for the platform you are fixing to build.
- 5. On Visual Studio, define `_CRT_SECURE_NO_WARNINGS` to avoid warnings.
+`nfd.a` will be built for release builds, and `nfd_d.a` will be built for debug builds.
+
+### Makefiles ###
+
+The makefile offers five options, with `release_x64` as the default.
+
+    make config=release_x86
+    make config=release_x64
+    make config=debug_x86
+    make config=debug_x64
 
 ### Compiling Your Programs ###
 
  1. Add `src/include` to your include search path.
- 2. Add `nfd.lib` to the list of list of static libraries to link against.
- 3. Add `src/` to the library search path.
+ 2. Add `nfd.lib` or `nfd_d.lib` to the list of list of static libraries to link against (for release or debug, respectively).
+ 3. Add `build/<debug|release>/<arch>` to the library search path.
+
+#### Linux GTK ####
+`apt-get libgtk-3-dev` installs the gtk dependency for library compilation.
 
-On Linux, you must compile and link against GTK+.  Recommend use of `pkg-config --cflags --libs gtk+-3.0`.
+On Linux, you have the option of compiling and linking against GTK.  If you use it, the recommended way to compile is to include the arguments of `pkg-config --cflags --libs gtk+-3.0`.
 
-On Mac OS X, add `AppKit` to the list of frameworks.
+#### Linux Zenity ####
 
+Alternatively, you can use the Zenity backend by running the Makefile in `build/gmake_linux_zenity`.  Zenity runs the dialog in its own address space, but requires the user to have Zenity correctly installed and configured on their system.
+
+#### MacOS ####
+On Mac OS, add `AppKit` to the list of frameworks.
+
+#### Windows ####
 On Windows, ensure you are building against `comctl32.lib`.
 
 ## Usage ##
 
 See `NFD.h` for API calls.  See `tests/*.c` for example code.
 
-See `tests/SConstruct` for a working build script that compiles on all platforms.
+After compiling, `build/bin` contains compiled test programs.
 
 ## File Filter Syntax ##
 
-There is a form of file filtering in every file dialog, but no consistent means of supporting it.  NFD provides support for filtering files by groups of extensions, providing its own descriptions (where applicable) for the extensions.
+There is a form of file filtering in every file dialog API, but no consistent means of supporting it.  NFD provides support for filtering files by groups of extensions, providing its own descriptions (where applicable) for the extensions.
 
 A wildcard filter is always added to every dialog.
 
@@ -113,16 +141,14 @@ See [test_opendialogmultiple.c](test/test_opendialogmultiple.c).
 
 # Known Limitations #
 
-I accept quality code patches, or will resolve these and other matters through support.
+I accept quality code patches, or will resolve these and other matters through support.  See [submitting pull requests](docs/submitting_pull_requests.md) for details.
 
  - No support for Windows XP's legacy dialogs such as `GetOpenFileName`.
- - No support for file filter names -- ex: "Image Files" (*.png, *.jpg).  Nameless filters are supported, though.
- - No support for selecting folders instead of files.
- - On Linux, GTK+ cannot be uninitialized to save memory.  Launching a file dialog costs memory.  I am open to accepting an alternative `nfd_zenity.c` implementation which uses Zenity and pipes.
+ - No support for file filter names -- ex: "Image Files" (*.png, *.jpg).  Nameless filters are supported, however.
 
 # Copyright and Credit #
 
-Copyright &copy; 2014 [Frogtoss Games](http://www.frogtoss.com), Inc.
+Copyright &copy; 2014-2017 [Frogtoss Games](http://www.frogtoss.com), Inc.
 File [LICENSE](LICENSE) covers all files in this repo.
 
 Native File Dialog by Michael Labbe
@@ -130,6 +156,10 @@ Native File Dialog by Michael Labbe
 
 Tomasz Konojacki for [microutf8](http://puszcza.gnu.org.ua/software/microutf8/)
 
+[Denis Kolodin](https://github.com/DenisKolodin) for mingw support.
+
+[Tom Mason](https://github.com/wheybags) for Zenity support.
+
 ## Support ##
 
 Directed support for this work is available from the original author under a paid agreement.

+ 12 - 4
Engine/lib/nativeFileDialogs/nfd_cocoa.m

@@ -117,7 +117,7 @@ static nfdresult_t AllocPathSet( NSArray *urls, nfdpathset_t *pathset )
 /* public */
 
 
-nfdresult_t NFD_OpenDialog( const char *filterList,
+nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList,
                             const nfdchar_t *defaultPath,
                             nfdchar_t **outPath )
 {
@@ -146,6 +146,7 @@ nfdresult_t NFD_OpenDialog( const char *filterList,
         if ( !*outPath )
         {
             [pool release];
+            [keyWindow makeKeyAndOrderFront:nil];            
             return NFD_ERROR;
         }
         memcpy( *outPath, utf8Path, len+1 ); /* copy null term */
@@ -163,6 +164,7 @@ nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
                                     nfdpathset_t *outPaths )
 {
     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+    NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
     
     NSOpenPanel *dialog = [NSOpenPanel openPanel];
     [dialog setAllowsMultipleSelection:YES];
@@ -181,12 +183,14 @@ nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
         if ( [urls count] == 0 )
         {
             [pool release];
+            [keyWindow makeKeyAndOrderFront:nil];            
             return NFD_CANCEL;
         }
 
         if ( AllocPathSet( urls, outPaths ) == NFD_ERROR )
         {
             [pool release];
+            [keyWindow makeKeyAndOrderFront:nil];            
             return NFD_ERROR;
         }
 
@@ -194,6 +198,7 @@ nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
     }
     [pool release];
 
+    [keyWindow makeKeyAndOrderFront:nil];    
     return nfdResult;
 }
 
@@ -203,7 +208,8 @@ nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
                             nfdchar_t **outPath )
 {
     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-
+    NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
+    
     NSSavePanel *dialog = [NSSavePanel savePanel];
     [dialog setExtensionHidden:NO];
     
@@ -225,6 +231,7 @@ nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
         if ( !*outPath )
         {
             [pool release];
+            [keyWindow makeKeyAndOrderFront:nil];            
             return NFD_ERROR;
         }
         memcpy( *outPath, utf8Path, byteLen );
@@ -232,7 +239,7 @@ nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
     }
 
     [pool release];
-
+    [keyWindow makeKeyAndOrderFront:nil];
     return nfdResult;
 }
 
@@ -241,7 +248,7 @@ nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath,
 {
     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
 
-    NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];    
+    NSWindow *keyWindow = [[NSApplication sharedApplication] keyWindow];
     NSOpenPanel *dialog = [NSOpenPanel openPanel];
     [dialog setAllowsMultipleSelection:NO];
     [dialog setCanChooseDirectories:YES];
@@ -264,6 +271,7 @@ nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath,
         if ( !*outPath )
         {
             [pool release];
+            [keyWindow makeKeyAndOrderFront:nil];            
             return NFD_ERROR;
         }
         memcpy( *outPath, utf8Path, len+1 ); /* copy null term */

+ 1 - 1
Engine/lib/nativeFileDialogs/nfd_gtk.c

@@ -165,7 +165,7 @@ static void WaitForCleanup(void)
                                  
 /* public */
 
-nfdresult_t NFD_OpenDialog( const char *filterList,
+nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList,
                             const nfdchar_t *defaultPath,
                             nfdchar_t **outPath )
 {    

+ 65 - 38
Engine/lib/nativeFileDialogs/nfd_win.cpp

@@ -4,6 +4,10 @@
   http://www.frogtoss.com/labs
  */
 
+#define _CRTDBG_MAP_ALLOC  
+#include <stdlib.h>  
+#include <crtdbg.h>  
+
 /* only locally define UNICODE in this compilation unit */
 #ifndef UNICODE
 #define UNICODE
@@ -19,7 +23,7 @@
 #include <stdio.h>
 #include <assert.h>
 #include <windows.h>
-#include <ShObjIdl.h>
+#include <shobjidl.h>
 #include "nfd_common.h"
 
 
@@ -47,9 +51,9 @@ static void CopyWCharToNFDChar( const wchar_t *inStr, nfdchar_t **outStr )
 /* includes NULL terminator byte in return */
 static size_t GetUTF8ByteCountForWChar( const wchar_t *str )
 {
-    int bytesNeeded = WideCharToMultiByte( CP_UTF8, 0,
-                                           str, -1,
-                                           NULL, 0, NULL, NULL );
+    size_t bytesNeeded = WideCharToMultiByte( CP_UTF8, 0,
+                                              str, -1,
+                                              NULL, 0, NULL, NULL );
     assert( bytesNeeded );
     return bytesNeeded+1;
 }
@@ -148,12 +152,12 @@ static nfdresult_t AddFiltersToDialog( ::IFileDialog *fileOpenDialog, const char
     }
 
     /* filterCount plus 1 because we hardcode the *.* wildcard after the while loop */
-    COMDLG_FILTERSPEC *specList = (COMDLG_FILTERSPEC*)NFDi_Malloc( sizeof(COMDLG_FILTERSPEC) * (filterCount + 1) );
+    COMDLG_FILTERSPEC *specList = (COMDLG_FILTERSPEC*)NFDi_Malloc( sizeof(COMDLG_FILTERSPEC) * ((size_t)filterCount + 1) );
     if ( !specList )
     {
         return NFD_ERROR;
     }
-    for (size_t i = 0; i < filterCount+1; ++i )
+    for (UINT i = 0; i < filterCount+1; ++i )
     {
         specList[i].pszName = NULL;
         specList[i].pszSpec = NULL;
@@ -181,9 +185,8 @@ static nfdresult_t AddFiltersToDialog( ::IFileDialog *fileOpenDialog, const char
         if ( *p_filterList == ';' || *p_filterList == '\0' )
         {
             /* end of filter -- add it to specList */
-
-            // Empty filter name -- Windows describes them by extension.            
-            CopyNFDCharToWChar(specbuf, (wchar_t**)&specList[specIdx].pszName);
+                                
+            CopyNFDCharToWChar( specbuf, (wchar_t**)&specList[specIdx].pszName );
             CopyNFDCharToWChar( specbuf, (wchar_t**)&specList[specIdx].pszSpec );
                         
             memset( specbuf, 0, sizeof(char)*NFD_MAX_STRLEN );
@@ -270,6 +273,8 @@ static nfdresult_t AllocPathSet( IShellItemArray *shellItems, nfdpathset_t *path
 
         // Calculate length of name with UTF-8 encoding
         bufSize += GetUTF8ByteCountForWChar( name );
+        
+        CoTaskMemFree(name);
     }
 
     assert(bufSize);
@@ -304,6 +309,7 @@ static nfdresult_t AllocPathSet( IShellItemArray *shellItems, nfdpathset_t *path
         shellItem->GetDisplayName(SIGDN_FILESYSPATH, &name);
 
         int bytesWritten = CopyWCharToExistingNFDCharBuffer(name, p_buf);
+        CoTaskMemFree(name);
 
         ptrdiff_t index = p_buf - pathSet->buf;
         assert( index >= 0 );
@@ -353,29 +359,30 @@ static nfdresult_t SetDefaultPath( IFileDialog *dialog, const char *defaultPath
 /* public */
 
 
-nfdresult_t NFD_OpenDialog( const char *filterList,
+nfdresult_t NFD_OpenDialog( const nfdchar_t *filterList,
                             const nfdchar_t *defaultPath,
                             nfdchar_t **outPath )
 {
     nfdresult_t nfdResult = NFD_ERROR;
     
     // Init COM library.
-    HRESULT result = ::CoInitializeEx(NULL,
-                                      ::COINIT_APARTMENTTHREADED |
-                                      ::COINIT_DISABLE_OLE1DDE );
+    HRESULT coResult = ::CoInitializeEx(NULL,
+                                        ::COINIT_APARTMENTTHREADED |
+                                        ::COINIT_DISABLE_OLE1DDE );
 
     ::IFileOpenDialog *fileOpenDialog(NULL);
 
-    if ( !SUCCEEDED(result))
+    if ( !SUCCEEDED(coResult))
     {
+        fileOpenDialog = NULL;
         NFDi_SetError("Could not initialize COM.");
         goto end;
     }
 
     // Create dialog
-    result = ::CoCreateInstance(::CLSID_FileOpenDialog, NULL,
-                                CLSCTX_ALL, ::IID_IFileOpenDialog,
-                                reinterpret_cast<void**>(&fileOpenDialog) );
+    HRESULT result = ::CoCreateInstance(::CLSID_FileOpenDialog, NULL,
+                                        CLSCTX_ALL, ::IID_IFileOpenDialog,
+                                        reinterpret_cast<void**>(&fileOpenDialog) );
                                 
     if ( !SUCCEEDED(result) )
     {
@@ -412,6 +419,7 @@ nfdresult_t NFD_OpenDialog( const char *filterList,
         if ( !SUCCEEDED(result) )
         {
             NFDi_SetError("Could not get file path for selected.");
+            shellItem->Release();
             goto end;
         }
 
@@ -420,6 +428,7 @@ nfdresult_t NFD_OpenDialog( const char *filterList,
         if ( !*outPath )
         {
             /* error is malloc-based, error message would be redundant */
+            shellItem->Release();
             goto end;
         }
 
@@ -436,8 +445,12 @@ nfdresult_t NFD_OpenDialog( const char *filterList,
         nfdResult = NFD_ERROR;
     }
 
- end:
-    ::CoUninitialize();
+end:
+    if (fileOpenDialog)
+        fileOpenDialog->Release();
+
+    if (SUCCEEDED(coResult))
+        ::CoUninitialize();
     
     return nfdResult;
 }
@@ -449,10 +462,10 @@ nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
     nfdresult_t nfdResult = NFD_ERROR;
     
     // Init COM library.
-    HRESULT result = ::CoInitializeEx(NULL,
-                                      ::COINIT_APARTMENTTHREADED |
-                                      ::COINIT_DISABLE_OLE1DDE );
-    if ( !SUCCEEDED(result))
+    HRESULT coResult = ::CoInitializeEx(NULL,
+                                        ::COINIT_APARTMENTTHREADED |
+                                        ::COINIT_DISABLE_OLE1DDE );
+    if ( !SUCCEEDED(coResult))
     {
         NFDi_SetError("Could not initialize COM.");
         return NFD_ERROR;
@@ -461,12 +474,13 @@ nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
     ::IFileOpenDialog *fileOpenDialog(NULL);
 
     // Create dialog
-    result = ::CoCreateInstance(::CLSID_FileOpenDialog, NULL,
-                                CLSCTX_ALL, ::IID_IFileOpenDialog,
-                                reinterpret_cast<void**>(&fileOpenDialog) );
+    HRESULT result = ::CoCreateInstance(::CLSID_FileOpenDialog, NULL,
+                                        CLSCTX_ALL, ::IID_IFileOpenDialog,
+                                        reinterpret_cast<void**>(&fileOpenDialog) );
                                 
     if ( !SUCCEEDED(result) )
     {
+        fileOpenDialog = NULL;
         NFDi_SetError("Could not create dialog.");
         goto end;
     }
@@ -512,6 +526,7 @@ nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
         
         if ( AllocPathSet( shellItems, outPaths ) == NFD_ERROR )
         {
+            shellItems->Release();
             goto end;
         }
 
@@ -528,8 +543,12 @@ nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
         nfdResult = NFD_ERROR;
     }
 
- end:
-    ::CoUninitialize();
+end:
+    if ( fileOpenDialog )
+        fileOpenDialog->Release();
+
+    if (SUCCEEDED(coResult))
+        ::CoUninitialize();
     
     return nfdResult;
 }
@@ -541,10 +560,10 @@ nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
     nfdresult_t nfdResult = NFD_ERROR;
     
     // Init COM library.
-    HRESULT result = ::CoInitializeEx(NULL,
-                                      ::COINIT_APARTMENTTHREADED |
-                                      ::COINIT_DISABLE_OLE1DDE );
-    if ( !SUCCEEDED(result))
+    HRESULT coResult = ::CoInitializeEx(NULL,
+                                        ::COINIT_APARTMENTTHREADED |
+                                        ::COINIT_DISABLE_OLE1DDE );
+    if ( !SUCCEEDED(coResult))
     {
         NFDi_SetError("Could not initialize COM.");
         return NFD_ERROR;
@@ -553,12 +572,13 @@ nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
     ::IFileSaveDialog *fileSaveDialog(NULL);
 
     // Create dialog
-    result = ::CoCreateInstance(::CLSID_FileSaveDialog, NULL,
-                                CLSCTX_ALL, ::IID_IFileSaveDialog,
-                                reinterpret_cast<void**>(&fileSaveDialog) );
+    HRESULT result = ::CoCreateInstance(::CLSID_FileSaveDialog, NULL,
+                                        CLSCTX_ALL, ::IID_IFileSaveDialog,
+                                        reinterpret_cast<void**>(&fileSaveDialog) );
 
     if ( !SUCCEEDED(result) )
     {
+        fileSaveDialog = NULL;
         NFDi_SetError("Could not create dialog.");
         goto end;
     }
@@ -591,6 +611,7 @@ nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
         result = shellItem->GetDisplayName(::SIGDN_FILESYSPATH, &filePath);
         if ( !SUCCEEDED(result) )
         {
+            shellItem->Release();
             NFDi_SetError("Could not get file path for selected.");
             goto end;
         }
@@ -600,6 +621,7 @@ nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
         if ( !*outPath )
         {
             /* error is malloc-based, error message would be redundant */
+            shellItem->Release();
             goto end;
         }
 
@@ -616,8 +638,12 @@ nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
         nfdResult = NFD_ERROR;
     }
     
- end:
-    ::CoUninitialize();
+end:
+    if ( fileSaveDialog )
+        fileSaveDialog->Release();
+
+    if (SUCCEEDED(coResult))
+        ::CoUninitialize();
         
     return nfdResult;
 }
@@ -729,7 +755,8 @@ nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath,
     ComPtr<IShellItem> pShellItem;
     if (!SUCCEEDED(pFileDialog->GetResult(&pShellItem)))
     {
-        return NFD_OKAY;
+        NFDi_SetError("Could not get shell item from dialog.");
+        return NFD_ERROR;
     }
 
     // Finally get the path

+ 311 - 0
Engine/lib/nativeFileDialogs/nfd_zenity.c

@@ -0,0 +1,311 @@
+/*
+  Native File Dialog
+
+  http://www.frogtoss.com/labs
+*/
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include "nfd.h"
+#include "nfd_common.h"
+
+#define SIMPLE_EXEC_IMPLEMENTATION
+#include "simple_exec.h"
+
+
+const char NO_ZENITY_MSG[] = "zenity not installed";
+
+
+static void AddTypeToFilterName( const char *typebuf, char *filterName, size_t bufsize )
+{
+    size_t len = strlen(filterName);
+    if( len > 0 )
+        strncat( filterName, " *.", bufsize - len - 1 );
+    else
+        strncat( filterName, "--file-filter=*.", bufsize - len - 1 );
+    
+    len = strlen(filterName);
+    strncat( filterName, typebuf, bufsize - len - 1 );
+}
+
+static void AddFiltersToCommandArgs(char** commandArgs, int commandArgsLen, const char *filterList )
+{
+    char typebuf[NFD_MAX_STRLEN] = {0};
+    const char *p_filterList = filterList;
+    char *p_typebuf = typebuf;
+    char filterName[NFD_MAX_STRLEN] = {0};
+    int i;
+    
+    if ( !filterList || strlen(filterList) == 0 )
+        return;
+
+    while ( 1 )
+    {
+        
+        if ( NFDi_IsFilterSegmentChar(*p_filterList) )
+        {
+            char typebufWildcard[NFD_MAX_STRLEN];
+            /* add another type to the filter */
+            assert( strlen(typebuf) > 0 );
+            assert( strlen(typebuf) < NFD_MAX_STRLEN-1 );
+            
+            snprintf( typebufWildcard, NFD_MAX_STRLEN, "*.%s", typebuf );
+
+            AddTypeToFilterName( typebuf, filterName, NFD_MAX_STRLEN );
+            
+            p_typebuf = typebuf;
+            memset( typebuf, 0, sizeof(char) * NFD_MAX_STRLEN );
+        }
+        
+        if ( *p_filterList == ';' || *p_filterList == '\0' )
+        {
+            /* end of filter -- add it to the dialog */
+
+            for(i = 0; commandArgs[i] != NULL && i < commandArgsLen; i++);
+
+            commandArgs[i] = strdup(filterName);
+            
+            filterName[0] = '\0';
+
+            if ( *p_filterList == '\0' )
+                break;
+        }
+
+        if ( !NFDi_IsFilterSegmentChar( *p_filterList ) )
+        {
+            *p_typebuf = *p_filterList;
+            p_typebuf++;
+        }
+
+        p_filterList++;
+    }
+    
+    /* always append a wildcard option to the end*/
+    
+    for(i = 0; commandArgs[i] != NULL && i < commandArgsLen; i++);
+
+    commandArgs[i] = strdup("--file-filter=*.*");
+}
+
+static nfdresult_t ZenityCommon(char** command, int commandLen, const char* defaultPath, const char* filterList, char** stdOut)
+{
+    if(defaultPath != NULL)
+    {
+        char* prefix = "--filename";
+        int len = strlen(prefix) + strlen(defaultPath) + 1;
+
+        char* tmp = (char*) calloc(len, 1);
+        strcat(tmp, prefix);
+        strcat(tmp, defaultPath);
+
+        int i;
+        for(i = 0; command[i] != NULL && i < commandLen; i++);
+
+        command[i] = tmp;
+    }
+
+    AddFiltersToCommandArgs(command, commandLen, filterList);
+
+    int byteCount = 0;
+    int exitCode = 0;
+    int processInvokeError = runCommandArray(stdOut, &byteCount, &exitCode, 0, command);
+
+    for(int i = 0; command[i] != NULL && i < commandLen; i++)
+        free(command[i]);
+
+    nfdresult_t result = NFD_OKAY;
+
+    if(processInvokeError == COMMAND_NOT_FOUND)
+    {
+        NFDi_SetError(NO_ZENITY_MSG);
+        result = NFD_ERROR;
+    }
+    else
+    {
+        if(exitCode == 1)
+            result = NFD_CANCEL;
+    }
+
+    return result;
+}
+ 
+
+static nfdresult_t AllocPathSet(char* zenityList, nfdpathset_t *pathSet )
+{
+    size_t bufSize = 0;
+    nfdchar_t *p_buf;
+    size_t count = 0;
+    
+    assert(zenityList);
+    assert(pathSet);
+    
+    size_t len = strlen(zenityList) + 1;
+    pathSet->buf = NFDi_Malloc(len);
+
+    int numEntries = 1;
+
+    for(size_t i = 0; i < len; i++)
+    {
+        char ch = zenityList[i];
+
+        if(ch == '|')
+        {
+            numEntries++;
+            ch = '\0';
+        }
+
+        pathSet->buf[i] = ch;
+    }
+
+    pathSet->count = numEntries;
+    assert( pathSet->count > 0 );
+
+    pathSet->indices = NFDi_Malloc( sizeof(size_t)*pathSet->count );
+
+    int entry = 0;
+    pathSet->indices[0] = 0;
+    for(size_t i = 0; i < len; i++)
+    {
+        char ch = zenityList[i];
+
+        if(ch == '|')
+        {
+            entry++;
+            pathSet->indices[entry] = i + 1;
+        }
+    }
+    
+    return NFD_OKAY;
+}
+                                 
+/* public */
+
+nfdresult_t NFD_OpenDialog( const char *filterList,
+                            const nfdchar_t *defaultPath,
+                            nfdchar_t **outPath )
+{    
+    int commandLen = 100;
+    char* command[commandLen];
+    memset(command, 0, commandLen * sizeof(char*));
+
+    command[0] = strdup("zenity");
+    command[1] = strdup("--file-selection");
+    command[2] = strdup("--title=Open File");
+
+    char* stdOut = NULL;
+    nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, filterList, &stdOut);
+            
+    if(stdOut != NULL)
+    {
+        size_t len = strlen(stdOut);
+        *outPath = NFDi_Malloc(len);
+        memcpy(*outPath, stdOut, len);
+        (*outPath)[len-1] = '\0'; // trim out the final \n with a null terminator
+        free(stdOut);
+    }
+    else
+    {
+        *outPath = NULL;
+    }
+
+    return result;
+}
+
+
+nfdresult_t NFD_OpenDialogMultiple( const nfdchar_t *filterList,
+                                    const nfdchar_t *defaultPath,
+                                    nfdpathset_t *outPaths )
+{
+    int commandLen = 100;
+    char* command[commandLen];
+    memset(command, 0, commandLen * sizeof(char*));
+
+    command[0] = strdup("zenity");
+    command[1] = strdup("--file-selection");
+    command[2] = strdup("--title=Open Files");
+    command[3] = strdup("--multiple");
+
+    char* stdOut = NULL;
+    nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, filterList, &stdOut);
+            
+    if(stdOut != NULL)
+    {
+        size_t len = strlen(stdOut);
+        stdOut[len-1] = '\0'; // remove trailing newline
+
+        if ( AllocPathSet( stdOut, outPaths ) == NFD_ERROR )
+            result = NFD_ERROR;
+
+        free(stdOut);
+    }
+    else
+    {
+        result = NFD_ERROR;
+    }
+
+    return result;
+}
+
+nfdresult_t NFD_SaveDialog( const nfdchar_t *filterList,
+                            const nfdchar_t *defaultPath,
+                            nfdchar_t **outPath )
+{
+    int commandLen = 100;
+    char* command[commandLen];
+    memset(command, 0, commandLen * sizeof(char*));
+
+    command[0] = strdup("zenity");
+    command[1] = strdup("--file-selection");
+    command[2] = strdup("--title=Save File");
+    command[3] = strdup("--save");
+
+    char* stdOut = NULL;
+    nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, filterList, &stdOut);
+            
+    if(stdOut != NULL)
+    {
+        size_t len = strlen(stdOut);
+        *outPath = NFDi_Malloc(len);
+        memcpy(*outPath, stdOut, len);
+        (*outPath)[len-1] = '\0'; // trim out the final \n with a null terminator
+        free(stdOut);
+    }
+    else
+    {
+        *outPath = NULL;
+    }
+
+    return result;
+}
+
+nfdresult_t NFD_PickFolder(const nfdchar_t *defaultPath,
+    nfdchar_t **outPath)
+{
+    int commandLen = 100;
+    char* command[commandLen];
+    memset(command, 0, commandLen * sizeof(char*));
+
+    command[0] = strdup("zenity");
+    command[1] = strdup("--file-selection");
+    command[2] = strdup("--directory");
+    command[3] = strdup("--title=Select folder");
+
+    char* stdOut = NULL;
+    nfdresult_t result = ZenityCommon(command, commandLen, defaultPath, "", &stdOut);
+            
+    if(stdOut != NULL)
+    {
+        size_t len = strlen(stdOut);
+        *outPath = NFDi_Malloc(len);
+        memcpy(*outPath, stdOut, len);
+        (*outPath)[len-1] = '\0'; // trim out the final \n with a null terminator
+        free(stdOut);
+    }
+    else
+    {
+        *outPath = NULL;
+    }
+
+    return result;
+}

+ 214 - 0
Engine/lib/nativeFileDialogs/simple_exec.h

@@ -0,0 +1,214 @@
+// copied from: https://github.com/wheybags/simple_exec/blob/5a74c507c4ce1b2bb166177ead4cca7cfa23cb35/simple_exec.h
+
+// simple_exec.h, single header library to run external programs + retrieve their status code and output (unix only for now)
+//
+// do this:
+// #define SIMPLE_EXEC_IMPLEMENTATION
+//   before you include this file in *one* C or C++ file to create the implementation.
+// i.e. it should look like this:
+// #define SIMPLE_EXEC_IMPLEMENTATION
+// #include "simple_exec.h"
+
+#ifndef SIMPLE_EXEC_H
+#define SIMPLE_EXEC_H
+
+int runCommand(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* command, ...);
+int runCommandArray(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* const* allArgs);
+
+#endif // SIMPLE_EXEC_H
+
+#ifdef SIMPLE_EXEC_IMPLEMENTATION
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <assert.h>
+#include <sys/wait.h>
+#include <stdarg.h>
+#include <fcntl.h>
+
+#define release_assert(x) do { int __release_assert_tmp__ = (x); assert(__release_assert_tmp__); } while(0)
+
+enum PIPE_FILE_DESCRIPTORS
+{
+  READ_FD  = 0,
+  WRITE_FD = 1
+};
+
+enum RUN_COMMAND_ERROR
+{
+    COMMAND_RAN_OK = 0,
+    COMMAND_NOT_FOUND = 1
+};
+
+int runCommandArray(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* const* allArgs)
+{
+    // adapted from: https://stackoverflow.com/a/479103
+
+    int bufferSize = 256;
+    char buffer[bufferSize + 1];
+
+    int dataReadFromChildDefaultSize = bufferSize * 5;
+    int dataReadFromChildSize = dataReadFromChildDefaultSize;
+    int dataReadFromChildUsed = 0;
+    char* dataReadFromChild = (char*)malloc(dataReadFromChildSize);
+
+
+    int parentToChild[2];
+    release_assert(pipe(parentToChild) == 0);
+
+    int childToParent[2];
+    release_assert(pipe(childToParent) == 0);
+
+    int errPipe[2];
+    release_assert(pipe(errPipe) == 0);
+
+    pid_t pid;
+    switch( pid = fork() )
+    {
+        case -1:
+        {
+            release_assert(0 && "Fork failed");
+        }
+
+        case 0: // child
+        {
+            release_assert(dup2(parentToChild[READ_FD ], STDIN_FILENO ) != -1);
+            release_assert(dup2(childToParent[WRITE_FD], STDOUT_FILENO) != -1);
+            
+            if(includeStdErr)
+            {
+                release_assert(dup2(childToParent[WRITE_FD], STDERR_FILENO) != -1);
+            }
+            else
+            {
+                int devNull = open("/dev/null", O_WRONLY);
+                release_assert(dup2(devNull, STDERR_FILENO) != -1);
+            }
+
+            // unused
+            release_assert(close(parentToChild[WRITE_FD]) == 0);
+            release_assert(close(childToParent[READ_FD ]) == 0);
+            release_assert(close(errPipe[READ_FD]) == 0);
+            
+            const char* command = allArgs[0];
+            execvp(command, allArgs);
+
+            char err = 1;
+            write(errPipe[WRITE_FD], &err, 1);
+            
+            close(errPipe[WRITE_FD]);
+            close(parentToChild[READ_FD]);
+            close(childToParent[WRITE_FD]);
+
+            exit(0);
+        }
+
+
+        default: // parent
+        {
+            // unused
+            release_assert(close(parentToChild[READ_FD]) == 0);
+            release_assert(close(childToParent[WRITE_FD]) == 0);
+            release_assert(close(errPipe[WRITE_FD]) == 0);
+
+            while(1)
+            {
+                ssize_t bytesRead = 0;
+                switch(bytesRead = read(childToParent[READ_FD], buffer, bufferSize))
+                {
+                    case 0: // End-of-File, or non-blocking read.
+                    {
+                        int status = 0;
+                        release_assert(waitpid(pid, &status, 0) == pid);
+
+                        // done with these now
+                        release_assert(close(parentToChild[WRITE_FD]) == 0);
+                        release_assert(close(childToParent[READ_FD]) == 0);
+
+                        char errChar = 0;
+                        read(errPipe[READ_FD], &errChar, 1);
+                        close(errPipe[READ_FD]);
+
+                        if(errChar)
+                        {
+                            free(dataReadFromChild); 
+                            return COMMAND_NOT_FOUND;
+                        }
+                        
+                        // free any un-needed memory with realloc + add a null terminator for convenience
+                        dataReadFromChild = (char*)realloc(dataReadFromChild, dataReadFromChildUsed + 1);
+                        dataReadFromChild[dataReadFromChildUsed] = '\0';
+                        
+                        if(stdOut != NULL)
+                            *stdOut = dataReadFromChild;
+                        else
+                            free(dataReadFromChild);
+
+                        if(stdOutByteCount != NULL)
+                            *stdOutByteCount = dataReadFromChildUsed;
+                        if(returnCode != NULL)
+                            *returnCode = WEXITSTATUS(status);
+
+                        return COMMAND_RAN_OK;
+                    }
+                    case -1:
+                    {
+                        release_assert(0 && "read() failed");
+                    }
+
+                    default:
+                    {
+                        if(dataReadFromChildUsed + bytesRead + 1 >= dataReadFromChildSize)
+                        {
+                            dataReadFromChildSize += dataReadFromChildDefaultSize;
+                            dataReadFromChild = (char*)realloc(dataReadFromChild, dataReadFromChildSize);
+                        }
+
+                        memcpy(dataReadFromChild + dataReadFromChildUsed, buffer, bytesRead);
+                        dataReadFromChildUsed += bytesRead;
+                        break;
+                    }
+                }
+            }
+        }
+    }
+}
+
+int runCommand(char** stdOut, int* stdOutByteCount, int* returnCode, int includeStdErr, char* command, ...)
+{
+    va_list vl;
+    va_start(vl, command);
+      
+    char* currArg = NULL;
+      
+    int allArgsInitialSize = 16;
+    int allArgsSize = allArgsInitialSize;
+    char** allArgs = (char**)malloc(sizeof(char*) * allArgsSize);
+    allArgs[0] = command;
+        
+    int i = 1;
+    do
+    {
+        currArg = va_arg(vl, char*);
+        allArgs[i] = currArg;
+
+        i++;
+
+        if(i >= allArgsSize)
+        {
+            allArgsSize += allArgsInitialSize;
+            allArgs = (char**)realloc(allArgs, sizeof(char*) * allArgsSize);
+        }
+
+    } while(currArg != NULL);
+
+    va_end(vl);
+
+    int retval = runCommandArray(stdOut, stdOutByteCount, returnCode, includeStdErr, allArgs);
+    free(allArgs);
+    return retval;
+}
+
+#endif //SIMPLE_EXEC_IMPLEMENTATION

+ 7 - 1
Engine/source/afx/afxRenderHighlightMgr.cpp

@@ -151,6 +151,12 @@ void afxRenderHighlightMgr::render( SceneRenderState *state )
             matrixSet.setProjection(*passRI->projection);
             mat->setTransforms(matrixSet, state);
 
+            // Setup HW skinning transforms if applicable
+            if (mat->usesHardwareSkinning())
+            {
+               mat->setNodeTransforms(passRI->mNodeTransforms, passRI->mNodeTransformCount);
+            }
+
             mat->setSceneInfo(state, sgData);
             mat->setBuffers(passRI->vertBuff, passRI->primBuff);
 
@@ -173,4 +179,4 @@ void afxRenderHighlightMgr::render( SceneRenderState *state )
 
    // Make sure the effect is gonna render.
    getSelectionEffect()->setSkip( false );
-}
+}

+ 79 - 23
Engine/source/gui/utility/guiInputCtrl.cpp

@@ -58,9 +58,25 @@ ConsoleDocClass( GuiInputCtrl,
 
 //------------------------------------------------------------------------------
 
+GuiInputCtrl::GuiInputCtrl()
+   : mSendAxisEvents(false),
+   mSendBreakEvents(false),
+   mSendModifierEvents(false)
+{
+}
+
+//------------------------------------------------------------------------------
+
 void GuiInputCtrl::initPersistFields()
 {
-   
+   addGroup("GuiInputCtrl");
+   addField("sendAxisEvents", TypeBool, Offset(mSendAxisEvents, GuiInputCtrl),
+      "If true, onAxisEvent callbacks will be sent for SI_AXIS Move events (Default false).");
+   addField("sendBreakEvents", TypeBool, Offset(mSendBreakEvents, GuiInputCtrl),
+      "If true, break events for all devices will generate callbacks (Default false).");
+   addField("sendModifierEvents", TypeBool, Offset(mSendModifierEvents, GuiInputCtrl),
+      "If true, Make events will be sent for modifier keys (Default false).");
+   endGroup("GuiInputCtrl");
 
    Parent::initPersistFields();
 }
@@ -110,6 +126,8 @@ static bool isModifierKey( U16 keyCode )
       case KEY_RALT:
       case KEY_LSHIFT:
       case KEY_RSHIFT:
+      case KEY_MAC_LOPT:
+      case KEY_MAC_ROPT:
          return( true );
    }
 
@@ -117,33 +135,49 @@ static bool isModifierKey( U16 keyCode )
 }
 
 IMPLEMENT_CALLBACK( GuiInputCtrl, onInputEvent, void, (const char* device, const char* action, bool state ),
-														  ( device, action, state),
-	"@brief Callback that occurs when an input is triggered on this control\n\n"
-	"@param device The device type triggering the input, such as keyboard, mouse, etc\n"
-	"@param action The actual event occuring, such as a key or button\n"
-	"@param state True if the action is being pressed, false if it is being release\n\n"
-);
+   ( device, action, state),
+   "@brief Callback that occurs when an input is triggered on this control\n\n"
+   "@param device The device type triggering the input, such as keyboard, mouse, etc\n"
+   "@param action The actual event occuring, such as a key or button\n"
+   "@param state True if the action is being pressed, false if it is being release\n\n");
+
+IMPLEMENT_CALLBACK(GuiInputCtrl, onAxisEvent, void, (const char* device, const char* action, F32 axisValue),
+   (device, action, axisValue),
+   "@brief Callback that occurs when an axis event is triggered on this control\n\n"
+   "@param device The device type triggering the input, such as mouse, joystick, gamepad, etc\n"
+   "@param action The ActionMap code for the axis\n"
+   "@param axisValue The current value of the axis\n\n");
 
 //------------------------------------------------------------------------------
 bool GuiInputCtrl::onInputEvent( const InputEventInfo &event )
 {
-   // TODO - add POV support...
+   char deviceString[32];
    if ( event.action == SI_MAKE )
    {
       if ( event.objType == SI_BUTTON
         || event.objType == SI_POV
-        || ( ( event.objType == SI_KEY ) && !isModifierKey( event.objInst ) ) )
+        || event.objType == SI_KEY )
       {
-         char deviceString[32];
          if ( !ActionMap::getDeviceName( event.deviceType, event.deviceInst, deviceString ) )
-            return( false );
-
-         const char* actionString = ActionMap::buildActionString( &event );
-
-		 //Con::executef( this, "onInputEvent", deviceString, actionString, "1" );
-		 onInputEvent_callback(deviceString, actionString, 1);
-
-         return( true );
+            return false;
+
+         if ((event.objType == SI_KEY) && isModifierKey(event.objInst))
+         {
+            if (!mSendModifierEvents)
+               return false;
+
+            char keyString[32];
+            if (!ActionMap::getKeyString(event.objInst, keyString))
+               return false;
+
+            onInputEvent_callback(deviceString, keyString, 1);
+         }
+         else
+         {
+            const char* actionString = ActionMap::buildActionString(&event);
+            onInputEvent_callback(deviceString, actionString, 1);
+         }
+         return true;
       }
    }
    else if ( event.action == SI_BREAK )
@@ -152,14 +186,36 @@ bool GuiInputCtrl::onInputEvent( const InputEventInfo &event )
       {
          char keyString[32];
          if ( !ActionMap::getKeyString( event.objInst, keyString ) )
-            return( false );
+            return false;
 
-         //Con::executef( this, "onInputEvent", "keyboard", keyString, "0" );
-		 onInputEvent_callback("keyboard", keyString, 0);
+         onInputEvent_callback("keyboard", keyString, 0);
+         return true;
+      }
+      else if (mSendBreakEvents)
+      {
+         if (!ActionMap::getDeviceName(event.deviceType, event.deviceInst, deviceString))
+            return false;
 
-         return( true );
+         const char* actionString = ActionMap::buildActionString(&event);
+
+         onInputEvent_callback(deviceString, actionString, 0);
+         return true;
       }
    }
+   else if (mSendAxisEvents && ((event.objType == SI_AXIS) || (event.objType == SI_INT) || (event.objType == SI_FLOAT)))
+   {
+      F32 fValue = event.fValue;
+      if (event.objType == SI_INT)
+         fValue = (F32)event.iValue;
 
-   return( false );
+      if (!ActionMap::getDeviceName(event.deviceType, event.deviceInst, deviceString))
+         return false;
+
+      const char* actionString = ActionMap::buildActionString(&event);
+
+      onAxisEvent_callback(deviceString, actionString, fValue);
+      return (event.deviceType != MouseDeviceType);   // Don't consume mouse move events
+   }
+
+   return false;
 }

+ 21 - 13
Engine/source/gui/utility/guiInputCtrl.h

@@ -32,23 +32,31 @@
 /// to script.  This is useful for implementing custom keyboard handling code.
 class GuiInputCtrl : public GuiMouseEventCtrl
 {
-   public:
+protected:
+   bool mSendAxisEvents;
+   bool mSendBreakEvents;
+   bool mSendModifierEvents;
 
-      typedef GuiMouseEventCtrl Parent;
-   
-      // GuiControl.
-      virtual bool onWake();
-      virtual void onSleep();
+public:
 
-      virtual bool onInputEvent( const InputEventInfo &event );
-      
-      static void initPersistFields();
+   typedef GuiMouseEventCtrl Parent;
 
-      DECLARE_CONOBJECT(GuiInputCtrl);
-      DECLARE_CATEGORY( "Gui Other Script" );
-      DECLARE_DESCRIPTION( "A control that locks the mouse and reports all keyboard input events to script." );
+   GuiInputCtrl();
 
-	  DECLARE_CALLBACK( void, onInputEvent, ( const char* device, const char* action, bool state ));
+   // GuiControl.
+   virtual bool onWake();
+   virtual void onSleep();
+
+   virtual bool onInputEvent( const InputEventInfo &event );
+
+   static void initPersistFields();
+
+   DECLARE_CONOBJECT(GuiInputCtrl);
+   DECLARE_CATEGORY( "Gui Other Script" );
+   DECLARE_DESCRIPTION( "A control that locks the mouse and reports all input events to script." );
+
+   DECLARE_CALLBACK( void, onInputEvent, ( const char* device, const char* action, bool state ));
+   DECLARE_CALLBACK(void, onAxisEvent, (const char* device, const char* action, F32 axisValue));
 };
 
 #endif // _GUI_INPUTCTRL_H

+ 11 - 2
Engine/source/platform/input/event.h

@@ -249,6 +249,14 @@ enum InputObjectInstancesEnum
    SI_DPOV2          = 0x215,
    SI_LPOV2          = 0x216,
    SI_RPOV2          = 0x217,
+   SI_POVMASK        = 0x218,
+   SI_POVMASK2       = 0x219,
+
+   /// Trackball event codes.
+   SI_XBALL          = 0x21A,
+   SI_YBALL          = 0x21B,
+   SI_XBALL2         = 0x21C,
+   SI_YBALL2         = 0x21D,
 
    XI_CONNECT        = 0x300,
    XI_THUMBLX        = 0x301,
@@ -262,7 +270,7 @@ enum InputObjectInstancesEnum
    XI_DPAD_DOWN      = 0x308,
    XI_DPAD_LEFT      = 0x309,
    XI_DPAD_RIGHT     = 0x310,*/
-   
+
    XI_START          = 0x311,
    XI_BACK           = 0x312,
    XI_LEFT_THUMB     = 0x313,
@@ -273,7 +281,8 @@ enum InputObjectInstancesEnum
    XI_A              = 0x317,
    XI_B              = 0x318,
    XI_X              = 0x319,
-   XI_Y              = 0x320,
+   XI_Y              = 0x31A,
+   XI_GUIDE          = 0x31B,
 
    INPUT_DEVICE_PLUGIN_CODES_START = 0x400,
 };

+ 23 - 28
Engine/source/platformSDL/sdlInput.cpp

@@ -27,15 +27,11 @@
 
 #include "sdlInput.h"
 #include "platform/platformInput.h"
+#include "sdlInputManager.h"
 #include "SDL.h"
 
-#ifdef LOG_INPUT
-#include <time.h>
-#include <stdarg.h>
-#endif
-
 // Static class variables:
-InputManager*  Input::smManager;
+InputManager*  Input::smManager = NULL;
 bool           Input::smActive;
 U8             Input::smModifierKeys;
 bool           Input::smLastKeyboardActivated;
@@ -43,10 +39,6 @@ bool           Input::smLastMouseActivated;
 bool           Input::smLastJoystickActivated;
 InputEvent     Input::smInputEvent;
 
-#ifdef LOG_INPUT
-static HANDLE gInputLog;
-#endif
-
 static void fillAsciiTable() {}
 
 //------------------------------------------------------------------------------
@@ -91,24 +83,17 @@ void Input::init()
    fillAsciiTable();
    Con::printf( "" );
 
+   smManager = new SDLInputManager;
+   if (smManager)
+   {
+      SDLInputManager::init();
+   }
+
    // Set ourselves to participate in per-frame processing.
    Process::notify(Input::process, PROCESS_INPUT_ORDER);
 
 }
 
-//------------------------------------------------------------------------------
-DefineEngineFunction(isJoystickDetected, bool, (),, "")
-{
-   return(SDL_NumJoysticks() > 0);
-}
-
-//------------------------------------------------------------------------------
-DefineEngineFunction(getJoystickAxes, const char*, (const char* instance), , "")
-{
-   // TODO SDL
-   return("");
-}
-
 //------------------------------------------------------------------------------
 U16 Input::getKeyCode( U16 asciiCode )
 {
@@ -118,7 +103,7 @@ U16 Input::getKeyCode( U16 asciiCode )
     char c[2];
     c[0]= asciiCode;
     c[1] = NULL;
-    return KeyMapSDL::getTorqueScanCodeFromSDL( SDL_GetScancodeFromName( c ) );
+    return KeyMapSDL::getTorqueScanCodeFromSDL( SDL_GetScancodeFromKey( SDL_GetKeyFromName(c) ) );
 }
 
 //------------------------------------------------------------------------------
@@ -159,6 +144,13 @@ void Input::destroy()
 
    SDL_QuitSubSystem( SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER );
 
+   if (smManager)
+   {
+      if (smManager->isEnabled())
+         smManager->disable();
+      delete smManager;
+      smManager = NULL;
+   }
 }
 
 //------------------------------------------------------------------------------
@@ -186,8 +178,8 @@ void Input::activate()
    //ImmReleaseContext( getWin32WindowHandle(), winState.imeHandle );
 #endif
 
-   if ( !Con::getBoolVariable( "$enableDirectInput" ) )
-      return;
+   if (smManager && !smManager->isEnabled())
+      smManager->enable();
 
    if ( smManager && smManager->isEnabled() && !smActive )
    {
@@ -199,7 +191,10 @@ void Input::activate()
 //------------------------------------------------------------------------------
 void Input::deactivate()
 {
-   if ( smManager && smManager->isEnabled() && smActive )
+   if (smManager && smManager->isEnabled())
+      smManager->disable();
+
+   if (smActive)
    {
       smActive = false;
       Con::printf( "Input deactivated." );
@@ -435,4 +430,4 @@ U32 KeyMapSDL::getSDLScanCodeFromTorque(U32 torque)
       buildScanCodeArray();
 
    return T3D_SDL[torque];
-}
+}

+ 1381 - 0
Engine/source/platformSDL/sdlInputManager.cpp

@@ -0,0 +1,1381 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#include "console/console.h"
+#include "console/consoleTypes.h"
+#include "console/engineAPI.h"
+#include "sim/actionMap.h"
+
+#include "sdlInputManager.h"
+
+typedef SDL_JoystickType SDLJoystickType;
+DefineEnumType(SDLJoystickType);
+ImplementEnumType(SDLJoystickType,
+   "The type of device connected.\n\n"
+   "@ingroup Input")
+{ SDL_JOYSTICK_TYPE_UNKNOWN, "Unknown"},
+{ SDL_JOYSTICK_TYPE_GAMECONTROLLER, "Game Controller" },
+{ SDL_JOYSTICK_TYPE_WHEEL, "Wheel" },
+{ SDL_JOYSTICK_TYPE_ARCADE_STICK, "Arcade Stick" },
+{ SDL_JOYSTICK_TYPE_FLIGHT_STICK, "Flight Stick" },
+{ SDL_JOYSTICK_TYPE_DANCE_PAD, "Dance Pad" },
+{ SDL_JOYSTICK_TYPE_GUITAR, "Guitar" },
+{ SDL_JOYSTICK_TYPE_DRUM_KIT, "Drum Kit" },
+{ SDL_JOYSTICK_TYPE_ARCADE_PAD, "Arcade Pad" },
+{ SDL_JOYSTICK_TYPE_THROTTLE, "Throttle" },
+EndImplementEnumType;
+
+typedef SDL_JoystickPowerLevel SDLPowerEnum;
+DefineEnumType(SDLPowerEnum);
+ImplementEnumType(SDLPowerEnum,
+   "An enumeration of battery levels of a joystick.\n\n"
+   "@ingroup Input")
+{ SDL_JOYSTICK_POWER_UNKNOWN, "Unknown" },
+{ SDL_JOYSTICK_POWER_EMPTY, "Empty" },
+{ SDL_JOYSTICK_POWER_LOW, "Low" },
+{ SDL_JOYSTICK_POWER_MEDIUM, "Medium" },
+{ SDL_JOYSTICK_POWER_FULL, "Full" },
+{ SDL_JOYSTICK_POWER_WIRED, "Wired" },
+{ SDL_JOYSTICK_POWER_MAX, "Max" },
+EndImplementEnumType;
+
+IMPLEMENT_STATIC_CLASS(SDLInputManager, ,
+   "@brief Static class exposing the SDL_Joystick and SDL_GameController APIs to Torque Script.\n"
+   "SDLInputManager provides access to the functions of these APIs through static class "
+   "functions.These functions are not required to bind or process events.By setting "
+   "pref::Input::JoystickEnabled or pref::Input::sdlControllerEnabled to true, all connected "
+   "devices will automatically be opened.All of the joystick and controller events defined "
+   "in event.h can then be bound. For complete API documentation see the Joystick and Game "
+   "Controller section of https ://wiki.libsdl.org/APIByCategory#Input_Events.\n\n"
+
+   "@tsexample\n"
+   "// Get the name and device type for all connected devices\n"
+   "%sdlDevices = SDLInputManager::numJoysticks();\n"
+   "for (%i = 0; %i < %sdlDevices; %i++)\n"
+   "{\n"
+   "   %deviceName = SDLInputManager::JoystickNameForIndex(%i);\n"
+   "   %deviceType = SDLInputManager::GetDeviceType(%i);\n"
+   "}\n"
+   "\n"
+   "// List all installed controller mappings\n"
+   "%numMappings = SDLInputManager::GameControllerNumMappings();\n"
+   "for (%i = 0; %i < %numMappings; %i++)\n"
+   "   echo(SDLInputManager::GameControllerMappingForIndex(%i));\n"
+   "@endtsexample\n\n");
+
+IMPLEMENT_GLOBAL_CALLBACK(onSDLDeviceConnected, void, (S32 sdlIndex, const char* deviceName, const char* deviceType),
+(sdlIndex, deviceName, deviceType),
+"Callback that occurs when an input device is connected to the system.\n\n"
+"@param sdlIndex The index that will be used by sdl to refer to the device.\n"
+"@param deviceName The name that the device reports. This will be the return "
+"value of SDL_JoystickNameForIndex or SDL_GameControllerNameForIndex depending on the device type.\n"
+"@param deviceType The type of device connected. See SDLInputManager::getDeviceType() "
+"for possible string values.\n\n");
+
+IMPLEMENT_GLOBAL_CALLBACK(onSDLDeviceDisconnected, void, (S32 sdlIndex), (sdlIndex),
+"Callback that occurs when an input device is disconnected from the system.\n\n"
+"@param sdlIndex The index of the device that was removed.\n");
+
+//------------------------------------------------------------------------------
+// Static class variables:
+bool SDLInputManager::smJoystickEnabled = true;
+bool SDLInputManager::smJoystickSplitAxesLR = true;
+bool SDLInputManager::smControllerEnabled = true;
+bool SDLInputManager::smPOVButtonEvents = true;
+bool SDLInputManager::smPOVMaskEvents = false;
+
+// Map SDL controller Axis to torque input event
+// Commented text from map_StringForControllerAxis[] in SDL_gamecontroller.c
+S32 SDLInputManager::map_EventForControllerAxis[] = {
+   SI_XAXIS, //"leftx",
+   SI_YAXIS, //"lefty",
+   SI_RXAXIS, //"rightx",
+   SI_RYAXIS, //"righty",
+   SI_ZAXIS, //"lefttrigger",
+   SI_RZAXIS, //"righttrigger",
+   -1 // NULL
+};
+
+// Map SDL controller button ID to torque input event
+// Commented text from map_StringForControllerButton[] in SDL_gamecontroller.c
+S32 SDLInputManager::map_EventForControllerButton[] = {
+   XI_A, //"a",
+   XI_B, //"b",
+   XI_X, //"x",
+   XI_Y, //"y",
+   XI_BACK, //"back",
+   XI_GUIDE, //"guide",
+   XI_START, //"start",
+   XI_LEFT_THUMB, //"leftstick",
+   XI_RIGHT_THUMB, //"rightstick",
+   XI_LEFT_SHOULDER, //"leftshoulder",
+   XI_RIGHT_SHOULDER, //"rightshoulder",
+   SI_UPOV, //"dpup",
+   SI_DPOV, //"dpdown",
+   SI_LPOV, //"dpleft",
+   SI_RPOV, //"dpright",
+   -1 // NULL
+};
+
+//------------------------------------------------------------------------------
+void SDLInputManager::joystickState::reset()
+{
+   sdlInstID = -1;
+   inputDevice = NULL;
+   numAxes = 0;
+   lastHatState[0] = 0;
+   lastHatState[1] = 0;
+}
+
+//------------------------------------------------------------------------------
+SDLInputManager::SDLInputManager()
+{
+   mEnabled = true;
+   mJoystickActive = true;
+
+   for (S32 i = 0; i < MaxJoysticks; ++i)
+   {
+      mJoysticks[i].reset();
+      mJoysticks[i].torqueInstID = i;
+   }
+
+   for (S32 i = 0; i < MaxControllers; ++i)
+   {
+      mControllers[i].sdlInstID = -1;
+      mControllers[i].torqueInstID = i;
+      mControllers[i].inputDevice = NULL;
+   }
+}
+
+//------------------------------------------------------------------------------
+void SDLInputManager::init()
+{
+   Con::addVariable( "pref::Input::JoystickEnabled",  TypeBool, &smJoystickEnabled, 
+      "If true, Joystick devices will be automatically opened.\n\n"
+	   "@ingroup Input");
+   Con::addVariable("pref::Input::JoystickSplitAxesLR", TypeBool, &smJoystickSplitAxesLR,
+      "Split axis inputs on 4 axis joysticks. This has no effect on any other device.\n\n"
+      "4 Axis joysticks use IDs 0-3 which get mapped to xaxis, yaxis, zaxis and rxaxis. "
+      "When true, this will increment IDs 2 and 3 so the inputs map to xaxis, yaxis, rxaxis and ryaxis.\n"
+      "@ingroup Input");
+   Con::addVariable("pref::Input::sdlControllerEnabled", TypeBool, &smControllerEnabled,
+      "If true, any Joystick device that SDL recognizes as a Game Controller will be automatically opened as a game controller.\n\n"
+      "@ingroup Input");
+   Con::addVariable("pref::Input::JoystickPOVButtons", TypeBool, &smPOVButtonEvents,
+      "If true, the pov hat will be treated as 4 buttons and make/break events will be generated for "
+      "upov, dpov, lpov and rpov.\n"
+      "@ingroup Input");
+   Con::addVariable("pref::Input::JoystickPOVMask", TypeBool, &smPOVMaskEvents,
+      "If true, the pov hat will be treated as a single input with a 4 bit mask value. The povmask "
+      "event will be generated with the current mask every time the mask value changes.\n"
+      "@ingroup Input");
+
+   // POV Hat mask bits
+   Con::setIntVariable("$SDLMask::HatUp", SDL_HAT_UP);
+   Con::setIntVariable("$SDLMask::HatRight", SDL_HAT_RIGHT);
+   Con::setIntVariable("$SDLMask::HatDown", SDL_HAT_DOWN);
+   Con::setIntVariable("$SDLMask::HatLeft", SDL_HAT_LEFT);
+}
+
+//------------------------------------------------------------------------------
+bool SDLInputManager::enable()
+{
+   disable();
+
+   if (smControllerEnabled || smJoystickEnabled)
+   {
+      for (S32 i = 0; i < SDL_NumJoysticks(); ++i)
+      {
+         if (smControllerEnabled && SDL_IsGameController(i))
+            openController(i, 0);
+         else if (smJoystickEnabled)
+            openJoystick(i, 0);
+      }
+   }
+   mEnabled = true;
+   return true;
+}
+
+//------------------------------------------------------------------------------
+void SDLInputManager::disable()
+{
+   // Close any open devices
+   for (S32 i = 0; i < MaxControllers; ++i)
+      closeControllerByIndex(i);
+   for (S32 i = 0; i < MaxJoysticks; ++i)
+      closeJoystickByIndex(i);
+
+   mEnabled = false;
+}
+
+//------------------------------------------------------------------------------
+void SDLInputManager::process()
+{
+}
+
+//------------------------------------------------------------------------------
+void SDLInputManager::processEvent(SDL_Event &evt)
+{
+   switch (evt.type)
+   {
+   case SDL_JOYAXISMOTION:
+   {
+      joystickState* torqueMapping;
+      if (mJoystickMap.isEmpty() || !mJoystickMap.find(evt.jaxis.which, torqueMapping))
+         break;
+      // SDL axis value inputs are in (range: -32768 to 32767)
+      // Torque axis values are -1.0 to 1.0
+      F32 value = ((F32)evt.jaxis.value) / (F32) (evt.jaxis.value > 0 ? SDL_JOYSTICK_AXIS_MAX : -SDL_JOYSTICK_AXIS_MIN);
+      S32 mapAxis = SI_XAXIS + evt.jaxis.axis;
+      if (evt.jaxis.axis > 1 && torqueMapping->numAxes == 4 && smJoystickSplitAxesLR)
+         mapAxis += 1; // On a 4 axis, we'll shift the second two so we use LX LY RX RY instead of LX LY LZ RX
+      buildInputEvent(JoystickDeviceType, torqueMapping->torqueInstID, SI_AXIS, mapAxis, SI_MOVE, value);
+      break;
+   }
+
+   case SDL_JOYBALLMOTION:
+   {
+      joystickState* torqueMapping;
+      if (mJoystickMap.isEmpty() || !mJoystickMap.find(evt.jball.which, torqueMapping) || evt.jball.ball >= MaxBalls)
+         break;
+      if (evt.jball.xrel != 0)
+         buildInputEvent(JoystickDeviceType, torqueMapping->torqueInstID, SI_INT, evt.jball.ball ? SI_XBALL2 : SI_XBALL, SI_MOVE, (S32) evt.jball.xrel);
+      if (evt.jball.yrel != 0)
+         buildInputEvent(JoystickDeviceType, torqueMapping->torqueInstID, SI_INT, evt.jball.ball ? SI_YBALL2 : SI_YBALL, SI_MOVE, (S32) evt.jball.yrel);
+      break;
+   }
+
+   case SDL_JOYHATMOTION:
+   {
+      joystickState* torqueMapping;
+      if (mJoystickMap.isEmpty() || !mJoystickMap.find(evt.jball.which, torqueMapping) || evt.jhat.hat >= MaxHats)
+         break;
+      if (torqueMapping->lastHatState[evt.jhat.hat] != evt.jhat.value)
+      {
+         buildHatEvents(JoystickDeviceType, torqueMapping->torqueInstID, torqueMapping->lastHatState[evt.jhat.hat], evt.jhat.value, evt.jhat.hat);
+         torqueMapping->lastHatState[evt.jhat.hat] = evt.jhat.value;
+      }
+      break;
+   }
+
+   case SDL_JOYBUTTONDOWN:
+   case SDL_JOYBUTTONUP:
+   {
+      joystickState* torqueMapping;
+      if (mJoystickMap.isEmpty() || !mJoystickMap.find(evt.jbutton.which, torqueMapping))
+         break;
+      buildInputEvent(JoystickDeviceType, torqueMapping->torqueInstID, SI_BUTTON, KEY_BUTTON0 + evt.jbutton.button,
+         evt.cbutton.state == SDL_PRESSED ? SI_MAKE : SI_BREAK, evt.cbutton.state == SDL_PRESSED ? 1.0f : 0.0f);
+      break;
+   }
+
+   case SDL_JOYDEVICEADDED:
+   {
+      deviceConnectedCallback(evt.jdevice.which);
+      if (smControllerEnabled && SDL_IsGameController(evt.jdevice.which))
+         break;   // This device will be added as a controller
+
+      if (smJoystickEnabled)
+         openJoystick(evt.jdevice.which, 0);
+      break;
+   }
+
+   case SDL_JOYDEVICEREMOVED:
+   {
+      onSDLDeviceDisconnected_callback(evt.jdevice.which);
+      closeJoystick(evt.jdevice.which);
+   }
+
+   case SDL_CONTROLLERAXISMOTION:
+   {
+      controllerState* torqueMapping;
+      if (mControllerMap.isEmpty() || !mControllerMap.find(evt.caxis.which, torqueMapping))
+         break;
+      // SDL axis value inputs are in (range: -32768 to 32767)
+      // Torque axis values are -1.0 to 1.0
+      F32 value = ((F32)evt.caxis.value) / (F32) (evt.caxis.value > 0 ? SDL_JOYSTICK_AXIS_MAX : -SDL_JOYSTICK_AXIS_MIN);
+      buildInputEvent(GamepadDeviceType, torqueMapping->torqueInstID, SI_AXIS, map_EventForControllerAxis[evt.caxis.axis], SI_MOVE, value);
+      break;
+   }
+
+   case SDL_CONTROLLERBUTTONDOWN:
+   case SDL_CONTROLLERBUTTONUP:
+   {
+      controllerState* torqueMapping;
+      if (mControllerMap.isEmpty() || !mControllerMap.find(evt.cbutton.which, torqueMapping))
+         break;
+      buildInputEvent(GamepadDeviceType, torqueMapping->torqueInstID, SI_BUTTON, map_EventForControllerButton[evt.cbutton.button],
+         evt.cbutton.state == SDL_PRESSED ? SI_MAKE : SI_BREAK, evt.cbutton.state == SDL_PRESSED ? 1.0f : 0.0f);
+      break;
+   }
+
+   case SDL_CONTROLLERDEVICEADDED:
+   {
+      if (smControllerEnabled)
+         openController(evt.cdevice.which, 0);
+      break;
+   }
+
+   case SDL_CONTROLLERDEVICEREMOVED:
+   {
+      closeController(evt.cdevice.which);
+      break;
+   }
+
+   case SDL_CONTROLLERDEVICEREMAPPED:
+      break;
+
+   default:
+#ifdef TORQUE_DEBUG
+      Con::warnf("Unhandled SDL input event: 0x%04x", evt.type);
+#endif
+      break;
+   }
+}
+
+//------------------------------------------------------------------------------
+void SDLInputManager::buildInputEvent(U32 deviceType, U32 deviceInst, InputEventType objType, InputObjectInstances objInst, InputActionType action, S32 iValue)
+{
+   InputEventInfo newEvent;
+
+   newEvent.deviceType = deviceType;
+   newEvent.deviceInst = deviceInst;
+   newEvent.objType = objType;
+   newEvent.objInst = objInst;
+   newEvent.action = action;
+   newEvent.iValue = iValue;
+
+   newEvent.postToSignal(Input::smInputEvent);
+}
+
+//------------------------------------------------------------------------------
+void SDLInputManager::buildInputEvent(U32 deviceType, U32 deviceInst, InputEventType objType, InputObjectInstances objInst, InputActionType action, F32 fValue)
+{
+   InputEventInfo newEvent;
+
+   newEvent.deviceType = deviceType;
+   newEvent.deviceInst = deviceInst;
+   newEvent.objType = objType;
+   newEvent.objInst = objInst;
+   newEvent.action = action;
+   newEvent.fValue = fValue;
+
+   newEvent.postToSignal(Input::smInputEvent);
+}
+
+//------------------------------------------------------------------------------
+void SDLInputManager::buildHatEvents(U32 deviceType, U32 deviceInst, U8 lastState, U8 currentState, S32 hatIndex)
+{
+   if (smPOVButtonEvents)
+   {
+      if ((lastState & SDL_HAT_UP) != (currentState & SDL_HAT_UP))
+      {
+         buildInputEvent(deviceType, deviceInst, SI_POV, hatIndex ? SI_UPOV2 : SI_UPOV,
+            (currentState & SDL_HAT_UP) ? SI_MAKE : SI_BREAK, (currentState & SDL_HAT_UP) ? 1.0f : 0.0f);
+      }
+
+      if ((lastState & SDL_HAT_DOWN) != (currentState & SDL_HAT_DOWN))
+      {
+         buildInputEvent(deviceType, deviceInst, SI_POV, hatIndex ? SI_DPOV2 : SI_DPOV,
+            (currentState & SDL_HAT_DOWN) ? SI_MAKE : SI_BREAK, (currentState & SDL_HAT_DOWN) ? 1.0f : 0.0f);
+      }
+
+      if ((lastState & SDL_HAT_LEFT) != (currentState & SDL_HAT_LEFT))
+      {
+         buildInputEvent(deviceType, deviceInst, SI_POV, hatIndex ? SI_LPOV2 : SI_LPOV,
+            (currentState & SDL_HAT_LEFT) ? SI_MAKE : SI_BREAK, (currentState & SDL_HAT_LEFT) ? 1.0f : 0.0f);
+      }
+
+      if ((lastState & SDL_HAT_RIGHT) != (currentState & SDL_HAT_RIGHT))
+      {
+         buildInputEvent(deviceType, deviceInst, SI_POV, hatIndex ? SI_RPOV2 : SI_RPOV,
+            (currentState & SDL_HAT_RIGHT) ? SI_MAKE : SI_BREAK, (currentState & SDL_HAT_RIGHT) ? 1.0f : 0.0f);
+      }
+   }
+
+   if (smPOVMaskEvents)
+   {
+      buildInputEvent(deviceType, deviceInst, SI_INT, hatIndex ? SI_POVMASK2 : SI_POVMASK, SI_VALUE, (S32) currentState);
+   }
+}
+
+//------------------------------------------------------------------------------
+S32 SDLInputManager::openController(S32 sdlIndex, S32 requestedTID)
+{
+   if ((sdlIndex < 0) || (sdlIndex >= SDL_NumJoysticks()) || (requestedTID < 0) || (requestedTID >= MaxControllers))
+      return -1;
+
+   if (SDL_IsGameController(sdlIndex))
+   {
+      SDL_GameController *inputDevice = SDL_GameControllerOpen(sdlIndex);
+      if (inputDevice)
+      {
+         SDL_JoystickID sdlId = SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(inputDevice));
+
+         // See if the device is already open as a joystick
+         for (S32 i = 0; i < MaxJoysticks; ++i)
+         {
+            if (mJoysticks[i].sdlInstID == sdlId)
+            {
+               if (!closeJoystickByIndex(i))
+               {
+                  SDL_GameControllerClose(inputDevice);
+                  return -1;
+               }
+            }
+         }
+
+         controllerState* torqueMapping = NULL;
+         if (mControllerMap.find(sdlId, torqueMapping))
+         {
+            if (torqueMapping->torqueInstID == (U32) requestedTID)
+            {
+               SDL_GameControllerClose(inputDevice);
+               return requestedTID;  // Already open at the requested ID
+            }
+            closeControllerByIndex(torqueMapping->torqueInstID);
+         }
+
+         S32 gamepadSlot = -1;
+         if (!mControllers[requestedTID].inputDevice)
+            gamepadSlot = requestedTID;
+         else
+         {
+            // Find the first available gamepad device slot
+            for (S32 i = 0; i < MaxControllers; ++i)
+            {
+               if (!mControllers[i].inputDevice)
+               {
+                  gamepadSlot = i;
+                  break;
+               }
+            }
+         }
+
+         if (gamepadSlot == -1)
+         {
+            Con::errorf("Unable to open Game Controller %s. Too many devices present.", SDL_GameControllerName(inputDevice));
+            SDL_GameControllerClose(inputDevice);
+            return -1;
+         }
+
+         mControllers[gamepadSlot].inputDevice = inputDevice;
+         mControllers[gamepadSlot].sdlInstID = sdlId;
+         mControllerMap.insertUnique(sdlId, &mControllers[gamepadSlot]);
+
+         return gamepadSlot;
+      }
+   }
+   return -1;
+}
+
+//------------------------------------------------------------------------------
+void SDLInputManager::closeController(SDL_JoystickID sdlId)
+{
+   controllerState* torqueMapping = NULL;
+   if (mControllerMap.find(sdlId, torqueMapping))
+      closeControllerByIndex(torqueMapping->torqueInstID);
+}
+
+//------------------------------------------------------------------------------
+bool SDLInputManager::closeControllerByIndex(S32 index)
+{
+   if (index < 0 || index >= MaxControllers)
+      return false;
+
+   if (mControllers[index].inputDevice && mControllers[index].sdlInstID != -1)
+   {
+      SDL_GameControllerClose(mControllers[index].inputDevice);
+      mControllerMap.erase(mControllers[index].sdlInstID);
+      mControllers[index].sdlInstID = -1;
+      mControllers[index].inputDevice = NULL;
+      return true;
+   }
+
+   return false;
+}
+
+//------------------------------------------------------------------------------
+S32 SDLInputManager::openJoystick(S32 sdlIndex, S32 requestedTID)
+{
+   if ((sdlIndex < 0) || (sdlIndex >= SDL_NumJoysticks()) || (requestedTID < 0) || (requestedTID >= MaxJoysticks))
+      return -1;
+
+   SDL_Joystick *inputDevice = SDL_JoystickOpen(sdlIndex);
+   if (inputDevice)
+   {
+      SDL_JoystickID sdlId = SDL_JoystickInstanceID(inputDevice);
+
+      // See if the device is already open as a controller
+      for (S32 i = 0; i < MaxControllers; ++i)
+      {
+         if (mControllers[i].sdlInstID == sdlId)
+         {
+            if (!closeControllerByIndex(i))
+            {
+               SDL_JoystickClose(inputDevice);
+               return -1;
+            }
+         }
+      }
+
+      joystickState* torqueMapping = NULL;
+      if (mJoystickMap.find(sdlId, torqueMapping))
+      {
+         if (torqueMapping->torqueInstID == (U32) requestedTID)
+         {
+            SDL_JoystickClose(inputDevice);
+            return requestedTID;  // Already open at the requested ID
+         }
+         closeJoystickByIndex(torqueMapping->torqueInstID);
+      }
+
+      S32 joystickSlot = -1;
+      if (!mJoysticks[requestedTID].inputDevice)
+         joystickSlot = requestedTID;
+      else
+      {
+         // Find the first available joystick device slot
+         for (S32 i = 0; i < MaxJoysticks; ++i)
+         {
+            if (!mJoysticks[i].inputDevice)
+            {
+               joystickSlot = i;
+               break;
+            }
+         }
+      }
+
+      if (joystickSlot == -1)
+      {
+         Con::errorf("Unable to open Joystick %s. Too many devices present.", SDL_JoystickName(inputDevice));
+         SDL_JoystickClose(inputDevice);
+         return -1;
+      }
+
+      mJoysticks[joystickSlot].inputDevice = inputDevice;
+      mJoysticks[joystickSlot].sdlInstID = sdlId;
+      mJoysticks[joystickSlot].numAxes = SDL_JoystickNumAxes(inputDevice);
+      mJoystickMap.insertUnique(sdlId, &mJoysticks[joystickSlot]);
+
+      return joystickSlot;
+   }
+   return -1;
+}
+
+//------------------------------------------------------------------------------
+void SDLInputManager::closeJoystick(SDL_JoystickID sdlId)
+{
+   joystickState* torqueMapping = NULL;
+   if (mJoystickMap.find(sdlId, torqueMapping))
+      closeJoystickByIndex(torqueMapping->torqueInstID);
+}
+
+//------------------------------------------------------------------------------
+bool SDLInputManager::closeJoystickByIndex(S32 index)
+{
+   if (index < 0 || index >= MaxJoysticks)
+      return false;
+
+   if (mJoysticks[index].inputDevice && mJoysticks[index].sdlInstID != -1)
+   {
+      SDL_JoystickClose(mJoysticks[index].inputDevice);
+      mJoystickMap.erase(mJoysticks[index].sdlInstID);
+      mJoysticks[index].reset();
+      return true;
+   }
+
+   return false;
+}
+
+//------------------------------------------------------------------------------
+void SDLInputManager::closeDevice(S32 sdlIndex)
+{
+   if (sdlIndex < 0 || sdlIndex >= SDL_NumJoysticks())
+      return;
+
+   SDL_JoystickID sdlId = -1;
+   SDL_Joystick *inputDevice = SDL_JoystickOpen(sdlIndex);
+   if (inputDevice)
+   {
+      sdlId = SDL_JoystickInstanceID(inputDevice);
+      SDL_JoystickClose(inputDevice);
+   }
+
+   if (sdlId < 0)
+      return;
+
+   for (S32 i = 0; i < MaxControllers; ++i)
+   {
+      if (mControllers[i].sdlInstID == sdlId)
+      {
+         closeControllerByIndex(i);
+         return;
+      }
+   }
+
+   for (S32 i = 0; i < MaxJoysticks; ++i)
+   {
+      if (mJoysticks[i].sdlInstID == sdlId)
+      {
+         closeJoystickByIndex(i);
+         return;
+      }
+   }
+}
+
+//------------------------------------------------------------------------------
+void SDLInputManager::deviceConnectedCallback(S32 index)
+{
+   // This will generate the script callback:
+   // onSDLDeviceConnected(%sdlIndex, %isController, %deviceName)
+   bool isController = SDL_IsGameController(index);
+   const char *deviceName = isController ? SDL_GameControllerNameForIndex(index) : SDL_JoystickNameForIndex(index);
+   SDL_JoystickType deviceType = SDL_JoystickGetDeviceType(index);
+   onSDLDeviceConnected_callback(index, deviceName, castConsoleTypeToString(deviceType));
+}
+
+//------------------------------------------------------------------------------
+// Console interface
+
+//------------------------------------------------------------------------------
+// Get the N'th SDL device state -1=doesn't exist, 0=closed, 1=open joystick, 2=open controller
+S32 SDLInputManager::getJoystickOpenState(S32 sdlIndex)
+{
+   if (sdlIndex < 0 || sdlIndex >= SDL_NumJoysticks())
+      return -1;
+
+   S32 currentState = 0;
+   // We need to open the joystick to get the sdl instanceID
+   // This will increase the refcount on the joystick if it was already open.
+   SDL_Joystick *inputDevice = SDL_JoystickOpen(sdlIndex);
+   if (inputDevice)
+   {
+      SDL_JoystickID sdlId = SDL_JoystickInstanceID(inputDevice);
+      controllerState* controllerMapping = NULL;
+      joystickState* joystickMapping = NULL;
+      if (!mControllerMap.isEmpty() && mControllerMap.find(sdlId, controllerMapping))
+         currentState = 2;
+      else if (!mJoystickMap.isEmpty() && mJoystickMap.find(sdlId, joystickMapping))
+         currentState = 1;
+
+      // Close the joystick to return the refcount to the previouse state
+      SDL_JoystickClose(inputDevice);
+   }
+
+   return currentState;
+}
+
+//------------------------------------------------------------------------------
+// Fills in the torque device instance string from an sdl joystick index number
+void SDLInputManager::getJoystickTorqueInst(S32 sdlIndex, char* instBuffer)
+{
+   if (sdlIndex < 0 || sdlIndex >= SDL_NumJoysticks())
+      return;
+
+   SDL_Joystick *inputDevice = SDL_JoystickOpen(sdlIndex);
+   if (inputDevice)
+   {
+      SDL_JoystickID sdlId = SDL_JoystickInstanceID(inputDevice);
+      controllerState* controllerMapping = NULL;
+      joystickState* joystickMapping = NULL;
+      if (!mControllerMap.isEmpty() && mControllerMap.find(sdlId, controllerMapping))
+         ActionMap::getDeviceName(GamepadDeviceType, controllerMapping->torqueInstID, instBuffer);
+      else if (!mJoystickMap.isEmpty() && mJoystickMap.find(sdlId, joystickMapping))
+         ActionMap::getDeviceName(JoystickDeviceType, joystickMapping->torqueInstID, instBuffer);
+
+      SDL_JoystickClose(inputDevice);
+   }
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, numJoysticks, S32, (), ,
+   "@brief Returns the number of currently connected joystick devices.\n\n"
+   "Game Controllers are a sub-set of joysticks and are included in the joystick count. "
+   "See https://wiki.libsdl.org/SDL_NumJoysticks for more details.\n"
+   "@ingroup Input")
+{
+   return SDL_NumJoysticks();
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, getDeviceOpenState, S32, ( S32 sdlIndex ), ( 0 ),
+   "@brief Used to determine the current state of the N'th item in the SDL device list.\n\n"
+   "@param sdlIndex The SDL index for this device.\n"
+   "@return values:\n"
+   "-1 if the device does not exist (invalid sdlIndex passed)\n"
+   "0 The device is closed\n"
+   "1 The device is open as a Joystick\n"
+   "2 The device is open as a Game Controller\n"
+   "@ingroup Input")
+{
+   SDLInputManager* mgr = dynamic_cast<SDLInputManager*>(Input::getManager());
+   if (mgr && mgr->isEnabled())
+      return mgr->getJoystickOpenState(sdlIndex);
+   return -1;
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, openAsJoystick, S32, ( S32 sdlIndex, S32 torqueInstId ), ( 0, 0 ),
+   "@brief Used to open the device as a Joystick.\n\n"
+   "If the device is currently open as a Game Controller, it will be closed and opened as "
+   "a Joystick. If it is currently opened as a Joystick with a different T3D instance ID, "
+   "it will be changed to the requested ID if that ID is available.\n"
+   "@param sdlIndex The SDL index for this device.\n"
+   "@param  torqueInstId Is the requested T3D device instance ID. If there is already an open Joystick with "
+   "the requested ID, The first available ID will be assigned.\n"
+   "@return The T3D device instance ID assigned, or -1 if the device could not be opened.")
+{
+   SDLInputManager* mgr = dynamic_cast<SDLInputManager*>(Input::getManager());
+   if (mgr && mgr->isEnabled())
+      return mgr->openJoystick(sdlIndex, torqueInstId);
+   return -1;
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, openAsController, S32, (S32 sdlIndex, S32 torqueInstId), (0, 0),
+   "@brief Used to open the device as a Game Controller.\n\n"
+   "If the device is currently open as a Joystick, it will be closed and opened as "
+   "a Game Controller. If it is currently opened as a Game Controller with a different "
+   "T3D instance ID, it will be changed to the requested ID if that ID is available.\n"
+   "@param sdlIndex The SDL index for this device.\n"
+   "@param  torqueInstId Is the requested T3D device instance ID. If there is already an "
+   "open Game Controller with the requested ID, The first available ID will be assigned.\n"
+   "@return The T3D device instance ID assigned, or -1 if the device could not be opened.")
+{
+   SDLInputManager* mgr = dynamic_cast<SDLInputManager*>(Input::getManager());
+   if (mgr && mgr->isEnabled())
+      return mgr->openController(sdlIndex, torqueInstId);
+   return -1;
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, closeDevice, void, (S32 sdlIndex), (0),
+   "@brief Used to close the N'th item in the SDL device list.\n\n"
+   "This will close a Joystick or Game Controller.\n"
+   "@param sdlIndex The SDL index for this device.\n")
+{
+   SDLInputManager* mgr = dynamic_cast<SDLInputManager*>(Input::getManager());
+   if (mgr && mgr->isEnabled())
+      mgr->closeDevice(sdlIndex);
+   return;
+}
+
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, getTorqueInstFromDevice, const char *, (S32 sdlIndex), (0),
+   "@brief Gets the T3D instance identifier for an open SDL joystick.\n\n"
+   "@param sdlIndex The SDL index for this device.\n"
+   "@return Returns the T3D instance ID used for mapping this device or Null if it does not exist.\n"
+   "@ingroup Input")
+{
+   SDLInputManager* mgr = dynamic_cast<SDLInputManager*>(Input::getManager());
+   if (mgr && mgr->isEnabled())
+   {
+      char* deviceInst = Con::getReturnBuffer(32);
+      deviceInst[0] = '\0';
+      mgr->getJoystickTorqueInst(sdlIndex, deviceInst);
+      return deviceInst;
+   }
+   return NULL;
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, JoystickNameForIndex, const char *, (S32 sdlIndex), (0),
+   "@brief Exposes SDL_JoystickNameForIndex() to script.\n\n"
+   "@param sdlIndex The SDL index for this device.\n"
+   "@return Returns the name of the selected joystick or Null if it does not exist.\n"
+   "@see https://wiki.libsdl.org/SDL_JoystickNameForIndex \n"
+   "@ingroup Input")
+{
+   if (sdlIndex >= 0 && sdlIndex < SDL_NumJoysticks())
+      return SDL_JoystickNameForIndex(sdlIndex);
+   return NULL;
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, ControllerNameForIndex, const char *, (S32 sdlIndex), (0),
+   "@brief Exposes SDL_GameControllerNameForIndex() to script.\n\n"
+   "@param sdlIndex The SDL index for this device.\n"
+   "@return Returns the implementation dependent name for the game controller, "
+   "or NULL if there is no name or the index is invalid.\n"
+   "@see https://wiki.libsdl.org/SDL_GameControllerNameForIndex \n"
+   "@ingroup Input")
+{
+   if (sdlIndex >= 0 && sdlIndex < SDL_NumJoysticks() || !SDL_IsGameController(sdlIndex))
+      return SDL_GameControllerNameForIndex(sdlIndex);
+   return NULL;
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, JoystickGetGUID, const char *, (S32 sdlIndex), (0),
+   "@brief Exposes SDL_JoystickGetDeviceGUID() to script.\n\n"
+   "@param sdlIndex The SDL index for this device.\n"
+   "@return GUID for the indexed device or Null if it does not exist.\n"
+   "@see https://wiki.libsdl.org/SDL_JoystickGetDeviceGUID \n"
+   "@ingroup Input")
+{
+   if (sdlIndex < 0 || sdlIndex >= SDL_NumJoysticks())
+      return NULL;
+      
+   SDL_JoystickGUID guidVal = SDL_JoystickGetDeviceGUID(sdlIndex);
+   char *guidStr = Con::getReturnBuffer(64);
+   SDL_JoystickGetGUIDString(guidVal, guidStr, 64);
+
+   return guidStr;
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, GetVendor, S32, (S32 sdlIndex), (0),
+   "Gets the USB vendor ID of a joystick device, if available.\n\n"
+   "@param sdlIndex The SDL index for this device.\n"
+   "@return The USB vendor ID. If the vendor ID isn't available this function returns 0.\n"
+   "@see https://wiki.libsdl.org/SDL_JoystickGetDeviceVendor \n"
+   "@see https://wiki.libsdl.org/SDL_JoystickGetVendor \n"
+   "@see https://wiki.libsdl.org/SDL_GameControllerGetVendor \n"
+   "@ingroup Input")
+{
+   if (sdlIndex < 0 || sdlIndex >= SDL_NumJoysticks())
+      return 0;
+
+   return (S32) SDL_JoystickGetDeviceVendor(sdlIndex);
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, GetProduct, S32, (S32 sdlIndex), (0),
+   "Gets the USB product ID of a joystick device, if available.\n\n"
+   "@param sdlIndex The SDL index for this device.\n"
+   "@return The USB product ID. If the product ID isn't available this function returns 0.\n"
+   "@see https://wiki.libsdl.org/SDL_JoystickGetDeviceProduct \n"
+   "@see https://wiki.libsdl.org/SDL_JoystickGetProduct \n"
+   "@see https://wiki.libsdl.org/SDL_GameControllerGetProduct \n"
+   "@ingroup Input")
+{
+   if (sdlIndex < 0 || sdlIndex >= SDL_NumJoysticks())
+      return 0;
+
+   return (S32)SDL_JoystickGetDeviceProduct(sdlIndex);
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, GetProductVersion, S32, (S32 sdlIndex), (0),
+   "Gets the product version of a joystick device, if available.\n\n"
+   "@param sdlIndex The SDL index for this device.\n"
+   "@return The product version. If the product version isn't available this function returns 0.\n"
+   "@see https://wiki.libsdl.org/SDL_JoystickGetDeviceProductVersion \n"
+   "@see https://wiki.libsdl.org/SDL_JoystickGetProductVersion \n"
+   "@see https://wiki.libsdl.org/SDL_GameControllerGetProductVersion \n"
+   "@ingroup Input")
+{
+   if (sdlIndex < 0 || sdlIndex >= SDL_NumJoysticks())
+      return 0;
+
+   return (S32)SDL_JoystickGetDeviceProductVersion(sdlIndex);
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, GetDeviceType, SDLJoystickType, (S32 sdlIndex), (0),
+   "@brief Exposes SDL_JoystickGetDeviceType() to script.\n\n"
+   "@param sdlIndex The SDL index for this device.\n"
+   "@return The type of device connected. Possible return strings are: \"Unknown\", "
+   "\"Game Controller\", \"Wheel\", \"Arcade Stick\", \"Flight Stick\", \"Dance Pad\", "
+   "\"Guitar\", \"Drum Kit\", \"Arcade Pad\" and \"Throttle\"\n"
+   "@see https://wiki.libsdl.org/SDL_JoystickGetDeviceType \n"
+   "@ingroup Input")
+{
+   if (sdlIndex < 0 || sdlIndex >= SDL_NumJoysticks())
+      return SDL_JOYSTICK_TYPE_UNKNOWN;
+
+   return SDL_JoystickGetDeviceType(sdlIndex);
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, JoystickNumAxes, S32, (S32 sdlIndex), (0),
+   "@brief Exposes SDL_JoystickNumAxes() to script.\n\n"
+   "@param sdlIndex The SDL index for this device.\n"
+   "@return Returns the number of axis controls/number of axes on success or zero on failure.\n"
+   "@see https://wiki.libsdl.org/SDL_JoystickNumAxes \n"
+   "@ingroup Input")
+{
+   if (sdlIndex < 0 || sdlIndex >= SDL_NumJoysticks())
+      return 0;
+
+   S32 numAxes = 0;
+   SDL_Joystick *inputDevice = SDL_JoystickOpen(sdlIndex);
+   if (inputDevice)
+   {
+      numAxes = SDL_JoystickNumAxes(inputDevice);
+      if (numAxes < 0)
+      {
+         Con::errorf("SDL Joystick error: %s", SDL_GetError());
+         numAxes = 0;
+      }
+
+      SDL_JoystickClose(inputDevice);
+   }
+
+   return numAxes;
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, JoystickNumBalls, S32, (S32 sdlIndex), (0),
+   "@brief Exposes SDL_JoystickNumBalls() to script.\n\n"
+   "@param sdlIndex The SDL index for this device.\n"
+   "@return Returns the number of trackballs on success or zero on failure.\n"
+   "@see https://wiki.libsdl.org/SDL_JoystickNumBalls \n"
+   "@ingroup Input")
+{
+   if (sdlIndex < 0 || sdlIndex >= SDL_NumJoysticks())
+      return 0;
+
+   S32 numBalls = 0;
+   SDL_Joystick *inputDevice = SDL_JoystickOpen(sdlIndex);
+   if (inputDevice)
+   {
+      numBalls = SDL_JoystickNumBalls(inputDevice);
+      if (numBalls < 0)
+      {
+         Con::errorf("SDL Joystick error: %s", SDL_GetError());
+         numBalls = 0;
+      }
+
+      SDL_JoystickClose(inputDevice);
+   }
+
+   return numBalls;
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, JoystickNumButtons, S32, (S32 sdlIndex), (0),
+   "@brief Exposes SDL_JoystickNumButtons() to script.\n\n"
+   "@param sdlIndex The SDL index for this device.\n"
+   "@return Returns the number of buttons on success or zero on failure.\n"
+   "@see https://wiki.libsdl.org/SDL_JoystickNumButtons \n"
+   "@ingroup Input")
+{
+   if (sdlIndex < 0 || sdlIndex >= SDL_NumJoysticks())
+      return 0;
+
+   S32 numButtons = 0;
+   SDL_Joystick *inputDevice = SDL_JoystickOpen(sdlIndex);
+   if (inputDevice)
+   {
+      numButtons = SDL_JoystickNumButtons(inputDevice);
+      if (numButtons < 0)
+      {
+         Con::errorf("SDL Joystick error: %s", SDL_GetError());
+         numButtons = 0;
+      }
+
+      SDL_JoystickClose(inputDevice);
+   }
+
+   return numButtons;
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, JoystickNumHats, S32, (S32 sdlIndex), (0),
+   "@brief Exposes SDL_JoystickNumHats() to script.\n\n"
+   "@param sdlIndex The SDL index for this device.\n"
+   "@return Returns the number of POV hats on success or zero on failure.\n"
+   "@see https://wiki.libsdl.org/SDL_JoystickNumHats \n"
+   "@ingroup Input")
+{
+   if (sdlIndex < 0 || sdlIndex >= SDL_NumJoysticks())
+      return 0;
+
+   S32 numHats = 0;
+   SDL_Joystick *inputDevice = SDL_JoystickOpen(sdlIndex);
+   if (inputDevice)
+   {
+      numHats = SDL_JoystickNumHats(inputDevice);
+      if (numHats < 0)
+      {
+         Con::errorf("SDL Joystick error: %s", SDL_GetError());
+         numHats = 0;
+      }
+
+      SDL_JoystickClose(inputDevice);
+   }
+
+   return numHats;
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, IsGameController, bool, (S32 sdlIndex), (0),
+   "@brief Exposes SDL_IsGameController() to script.\n\n"
+   "@param sdlIndex The SDL index for this device.\n"
+   "@return Returns true if the given joystick is supported by the game controller "
+   "interface, false if it isn't or it's an invalid index.\n"
+   "@see https://wiki.libsdl.org/SDL_IsGameController \n"
+   "@ingroup Input")
+{
+   if (sdlIndex < 0 || sdlIndex >= SDL_NumJoysticks() || !SDL_IsGameController(sdlIndex))
+      return false;
+
+   return true;
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, JoystickIsHaptic, bool, (S32 sdlIndex), (0),
+   "@brief Exposes SDL_JoystickIsHaptic() to script.\n\n"
+   "@param sdlIndex The SDL index for this device.\n"
+   "@return Returns true if the joystick is haptic.\n"
+   "@see https://wiki.libsdl.org/SDL_JoystickIsHaptic \n"
+   "@ingroup Input")
+{
+   if (sdlIndex < 0 || sdlIndex >= SDL_NumJoysticks())
+      return false;
+
+   bool isHaptic = false;
+   SDL_Joystick *inputDevice = SDL_JoystickOpen(sdlIndex);
+   if (inputDevice)
+   {
+      isHaptic = (SDL_JoystickIsHaptic(inputDevice) == SDL_TRUE);
+      SDL_JoystickClose(inputDevice);
+   }
+
+   return isHaptic;
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, JoystickPowerLevel, SDLPowerEnum, (S32 sdlIndex), (0),
+   "@brief Exposes SDL_JoystickCurrentPowerLevel() to script.\n\n"
+   "@param sdlIndex The SDL index for this device.\n"
+   "@return Returns the current battery level or \"Wired\" if it's a connected device.\n"
+   "@see https://wiki.libsdl.org/SDL_JoystickCurrentPowerLevel \n"
+   "@ingroup Input")
+{
+   SDL_JoystickPowerLevel powerLevel = SDL_JOYSTICK_POWER_UNKNOWN;
+   if (sdlIndex >= 0 && sdlIndex < SDL_NumJoysticks())
+   {
+      SDL_Joystick *inputDevice = SDL_JoystickOpen(sdlIndex);
+      if (inputDevice)
+      {
+         powerLevel = SDL_JoystickCurrentPowerLevel(inputDevice);
+         SDL_JoystickClose(inputDevice);
+      }
+   }
+   return powerLevel;
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, JoystickGetSpecs, String, (S32 sdlIndex), (0),
+   "@brief A convenience function to reurn all of the data for a Joystick/Game Controller "
+   " packed as fields in a tab separated string.\n\n"
+   "There is overhead involved in querying joystick data, especially if the device is not open. "
+   "If more than one field is required, it is more efficient to call JoystickGetSpecs() and "
+   "parse the data out of the return string than to call the console method for each.\n"
+   "@param sdlIndex The SDL index for this device.\n"
+   "@return A tab separated string that can be parsed from script with getField()/getFields().\n\n"
+   "Field 0: Number of Axes\n"
+   "      1: Number of Buttons\n"
+   "      2: Number of POV Hats\n"
+   "      3: Number of Trackballs\n"
+   "      4: SDL_IsGameController() (Boolean)\n"
+   "      5: SDL_JoystickIsHaptic() (Boolean)\n"
+   "      6: Power Level (String)\n"
+   "      7: Device Type (String)\n"
+   "@ingroup Input")
+{
+   String specStr;
+   if (sdlIndex < 0 || sdlIndex >= SDL_NumJoysticks())
+      return specStr;
+
+   bool isController = SDL_IsGameController(sdlIndex);
+   SDL_Joystick *inputDevice = SDL_JoystickOpen(sdlIndex);
+   if (inputDevice)
+   {
+      SDL_JoystickPowerLevel powerLevel = SDL_JoystickCurrentPowerLevel(inputDevice);
+      SDL_JoystickType deviceType = SDL_JoystickGetDeviceType(sdlIndex);
+
+      specStr = String::ToString("%d\t%d\t%d\t%d\t%d\t%d\t%s\t%s\t",
+         SDL_JoystickNumAxes(inputDevice), SDL_JoystickNumButtons(inputDevice),
+         SDL_JoystickNumHats(inputDevice), SDL_JoystickNumBalls(inputDevice),
+         isController ? 1 : 0, (SDL_JoystickIsHaptic(inputDevice) == SDL_TRUE) ? 1 : 0,
+         castConsoleTypeToString(powerLevel), castConsoleTypeToString(deviceType));
+      SDL_JoystickClose(inputDevice);
+   }
+
+   return specStr;
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, JoystickGetAxes, String, (S32 sdlIndex), (0),
+   "@brief Gets the current value for all joystick axes.\n\n"
+   "@param sdlIndex The SDL index for this device.\n"
+   "@return A tab separated string that can be parsed from script with getField()/getFields(). "
+   "Each axis is one field, so a 4 axis device will have 4 fields.\n\n"
+   "@ingroup Input")
+{
+   String axesStr;
+   if (sdlIndex < 0 || sdlIndex >= SDL_NumJoysticks())
+      return axesStr;
+
+   SDL_Joystick *inputDevice = SDL_JoystickOpen(sdlIndex);
+   if (inputDevice)
+   {
+      S32 numAxes = SDL_JoystickNumAxes(inputDevice);
+      for (S32 i = 0; i < numAxes; i++)
+      {
+         F32 axisVal = (F32) SDL_JoystickGetAxis(inputDevice, i);
+         F32 value = axisVal / (F32)(axisVal > 0.0f ? SDL_JOYSTICK_AXIS_MAX : -SDL_JOYSTICK_AXIS_MIN);
+         axesStr += String::ToString("%0.3f\t", value);
+      }
+      SDL_JoystickClose(inputDevice);
+   }
+
+   return axesStr;
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, JoystickGetButtons, String, (S32 sdlIndex), (0),
+   "@brief Gets the current value for all joystick buttons.\n\n"
+   "@param sdlIndex The SDL index for this device.\n"
+   "@return A tab separated string that can be parsed from script with getField()/getFields(). "
+   "Each button is one field. 0 - SDL_JoystickNumButtons() fields.\n\n"
+   "@ingroup Input")
+{
+   String buttonStr;
+   if (sdlIndex < 0 || sdlIndex >= SDL_NumJoysticks())
+      return buttonStr;
+
+   SDL_Joystick *inputDevice = SDL_JoystickOpen(sdlIndex);
+   if (inputDevice)
+   {
+      S32 numbuttons = SDL_JoystickNumButtons(inputDevice);
+      for (S32 i = 0; i < numbuttons; i++)
+      {
+         buttonStr += String::ToString("%d\t", (S32) SDL_JoystickGetButton(inputDevice, i));
+      }
+      SDL_JoystickClose(inputDevice);
+   }
+
+   return buttonStr;
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, JoystickGetHats, String, (S32 sdlIndex), (0),
+   "@brief Gets the current value for all POV hats.\n\n"
+   "@param sdlIndex The SDL index for this device.\n"
+   "@return A tab separated string that can be parsed from script with getField()/getFields(). "
+   "Each hat is one field. 0 - SDL_JoystickNumHats() fields. The value is a 4 bit bitmask. "
+   "If no bits are set, the hat is centered. Bit 0 is up, 1 is right, 2 is down and 3 is left.\n\n"
+   "@ingroup Input")
+{
+   String hatStr;
+   if (sdlIndex < 0 || sdlIndex >= SDL_NumJoysticks())
+      return hatStr;
+
+   SDL_Joystick *inputDevice = SDL_JoystickOpen(sdlIndex);
+   if (inputDevice)
+   {
+      S32 numHats = SDL_JoystickNumHats(inputDevice);
+      for (S32 i = 0; i < numHats; i++)
+      {
+         hatStr += String::ToString("%d\t", (S32)SDL_JoystickGetHat(inputDevice, i));
+      }
+      SDL_JoystickClose(inputDevice);
+   }
+
+   return hatStr;
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, ControllerGetAxes, String, (S32 sdlIndex), (0),
+   "@brief Gets the current value for all controller axes.\n\n"
+   "@param sdlIndex The SDL index for this device.\n"
+   "@return A tab separated string that can be parsed from script with getField()/getFields(). "
+   "Game controllers always have 6 axes in the following order: 0-LX, 1-LY, 2-RX, 3-RY, 4-LT, 5-RT.\n\n"
+   "@ingroup Input")
+{
+   String axesStr;
+   if (sdlIndex < 0 || sdlIndex >= SDL_NumJoysticks())
+      return axesStr;
+
+   bool isController = SDL_IsGameController(sdlIndex);
+   if (!isController)
+      return axesStr;
+
+   SDL_GameController *inputDevice = SDL_GameControllerOpen(sdlIndex);
+   if (inputDevice)
+   {
+      for (S32 i = SDL_CONTROLLER_AXIS_LEFTX; i < SDL_CONTROLLER_AXIS_MAX; i++)
+      {
+         F32 axisVal = (F32)SDL_GameControllerGetAxis(inputDevice, (SDL_GameControllerAxis) i);
+         F32 value = axisVal / (F32)(axisVal > 0.0f ? SDL_JOYSTICK_AXIS_MAX : -SDL_JOYSTICK_AXIS_MIN);
+         axesStr += String::ToString("%0.3f\t", value);
+      }
+      SDL_GameControllerClose(inputDevice);
+   }
+
+   return axesStr;
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, ControllerGetButtons, String, (S32 sdlIndex), (0),
+   "@brief Gets the current value for all controller buttons.\n\n"
+   "@param sdlIndex The SDL index for this device.\n"
+   "@return A tab separated string that can be parsed from script with getField()/getFields(). "
+   "Game controllers always have 15 buttons in the following order: 0-A, 1-B, 2-X, 3-Y, 4-Back, "
+   "5-Guide, 6-Start, 7-Left Stick, 8-Right Stick, 9-Left Shoulder, 10-Right Shoulder, "
+   "11-DPad Up, 12-DPad Down, 13-DPad Left, 14-DPad Right.\n\n"
+   "@ingroup Input")
+{
+   String buttonStr;
+   if (sdlIndex < 0 || sdlIndex >= SDL_NumJoysticks())
+      return buttonStr;
+
+   bool isController = SDL_IsGameController(sdlIndex);
+   if (!isController)
+      return buttonStr;
+
+   SDL_GameController *inputDevice = SDL_GameControllerOpen(sdlIndex);
+   if (inputDevice)
+   {
+      for (S32 i = SDL_CONTROLLER_BUTTON_A; i < SDL_CONTROLLER_BUTTON_MAX; i++)
+      {
+         buttonStr += String::ToString("%d\t", (S32)SDL_GameControllerGetButton(inputDevice, (SDL_GameControllerButton) i));
+      }
+      SDL_GameControllerClose(inputDevice);
+   }
+
+   return buttonStr;
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, GameControllerMapping, String, (S32 sdlIndex), (0),
+   "@brief Exposes SDL_GameControllerMapping() to script.\n\n"
+   "@param sdlIndex The SDL index for this device.\n"
+   "@return Returns a string that has the controller's mapping or NULL if no mapping "
+   "is available or it does not exist.\n"
+   "@see https://wiki.libsdl.org/SDL_JoystickNameForIndex \n"
+   "@ingroup Input")
+{
+   String mapping;
+   if (sdlIndex < 0 || sdlIndex >= SDL_NumJoysticks())
+      return mapping;
+
+   SDL_GameController *inputDevice = SDL_GameControllerOpen(sdlIndex);
+   if (inputDevice)
+   {
+      char* sdlStr = SDL_GameControllerMapping(inputDevice);
+      if (sdlStr)
+      {
+         mapping = sdlStr;
+         SDL_free(sdlStr);
+      }
+      else
+         Con::errorf("SDL Joystick error: %s", SDL_GetError());
+
+      SDL_GameControllerClose(inputDevice);
+   }
+
+   return mapping;
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, GameControllerMappingForGUID, String, (const char* guidStr), ,
+   "@brief Exposes SDL_GameControllerMappingForGUID() to script.\n\n"
+   "@param guidStr The GUID for which a mapping is desired.\n"
+   "@return Returns a mapping string or NULL on error.\n"
+   "@see https://wiki.libsdl.org/SDL_GameControllerMappingForGUID \n"
+   "@ingroup Input")
+{
+   String mapping;
+   SDL_JoystickGUID guid = SDL_JoystickGetGUIDFromString(guidStr);
+
+   char* sdlStr = SDL_GameControllerMappingForGUID(guid);
+   if (sdlStr)
+   {
+      mapping = sdlStr;
+      SDL_free(sdlStr);
+   }
+
+   return mapping;
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, GameControllerAddMapping, S32, (const char* mappingString), ,
+   "@brief Exposes SDL_GameControllerAddMapping() to script.\n\n"
+   "Use this function to add support for controllers that SDL is unaware of or "
+   "to cause an existing controller to have a different binding.\n"
+   "@param mappingString The new mapping string to apply. Full details on the format of this "
+   "string are available at the linked SDL wiki page.\n"
+   "@return Returns 1 if a new mapping is added, 0 if an existing mapping is updated, -1 on error.\n"
+   "@see https://wiki.libsdl.org/SDL_GameControllerAddMapping \n"
+   "@ingroup Input")
+{
+   S32 retVal = SDL_GameControllerAddMapping(mappingString);
+   if (retVal == -1)
+      Con::errorf("SDL Joystick error: %s", SDL_GetError());
+
+   return retVal;
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, GameControllerAddMappingsFromFile, S32, (const char* fileName), ,
+   "@brief Exposes SDL_GameControllerAddMappingsFromFile() to script.\n\n"
+   "Use this function to load a set of Game Controller mappings from a file, filtered by the "
+   "current SDL_GetPlatform(). A community sourced database of controllers is available at "
+   "https://raw.githubusercontent.com/gabomdq/SDL_GameControllerDB/master/gamecontrollerdb.txt \n"
+   "@param fileName The file to load mappings from.\n"
+   "@return Returns the number of mappings added or -1 on error.\n"
+   "@see https://wiki.libsdl.org/SDL_GameControllerAddMappingsFromFile \n"
+   "@ingroup Input")
+{
+   char torquePath[1024];
+   Con::expandScriptFilename(torquePath, sizeof(torquePath), fileName);
+   S32 retVal = SDL_GameControllerAddMappingsFromFile(torquePath);
+   if (retVal == -1)
+      Con::errorf("SDL Joystick error: %s", SDL_GetError());
+
+   return retVal;
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, GameControllerNumMappings, S32, (), ,
+   "Get the number of mappings installed. Used with GameControllerMappingForIndex "
+   "to iterate through all installed mappings.\n\n"
+   "@ingroup Input")
+{
+   return SDL_GameControllerNumMappings();
+}
+
+//------------------------------------------------------------------------------
+DefineEngineStaticMethod(SDLInputManager, GameControllerMappingForIndex, String, (S32 mappingIndex), ,
+   "Get the mapping at a particular index.\n\n"
+   "@param mappingIndex The index for which a mapping is desired.\n"
+   "@return Returns a mapping string or NULL if the index is out of range.\n"
+   "@ingroup Input")
+{
+   String mapping;
+   char* sdlStr = SDL_GameControllerMappingForIndex(mappingIndex);
+
+   if (sdlStr)
+   {
+      mapping = sdlStr;
+      SDL_free(sdlStr);
+   }
+
+   return mapping;
+}

+ 114 - 0
Engine/source/platformSDL/sdlInputManager.h

@@ -0,0 +1,114 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2012 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#ifndef _SDLINPUTMANAGER_H_
+#define _SDLINPUTMANAGER_H_
+
+#ifndef _PLATFORMINPUT_H_
+#include "platform/platformInput.h"
+#endif
+#include "SDL.h"
+
+//------------------------------------------------------------------------------
+class SDLInputManager : public InputManager
+{
+   enum Constants {
+      MaxJoysticks = 4,          // Up to 4 simultaneous joysticks
+      MaxControllers = 4,        // Up to 4 simultaneous controllers
+      MaxHats = 2,               // Maximum 2 hats per device
+      MaxBalls = 2,              // Maximum 2 trackballs per device
+      MaxControllerAxes = 7,     // From map_StringForControllerAxis[] in SDL_gamecontroller.c
+      MaxControllerButtons = 16  // From map_StringForControllerButton[] in SDL_gamecontroller.c
+   };
+
+   struct controllerState
+   {
+      S32 sdlInstID;    // SDL device instance id
+      U32 torqueInstID; // Torque device instance id
+      SDL_GameController *inputDevice;
+   };
+
+   struct joystickState
+   {
+      S32 sdlInstID;    // SDL device instance id
+      U32 torqueInstID; // Torque device instance id
+      SDL_Joystick *inputDevice;
+      U32 numAxes;
+      U8 lastHatState[MaxHats];
+
+      void reset();
+   };
+
+private:
+   typedef InputManager Parent;
+
+   static S32 map_EventForControllerAxis[MaxControllerAxes];
+   static S32 map_EventForControllerButton[MaxControllerButtons];
+
+   static bool smJoystickEnabled;
+   static bool smJoystickSplitAxesLR;
+   static bool smControllerEnabled;
+   static bool smPOVButtonEvents;
+   static bool smPOVMaskEvents;
+
+   joystickState mJoysticks[MaxJoysticks];
+   controllerState mControllers[MaxControllers];
+
+   // Used to look up a torque instance based on a device inst
+   HashTable<S32, joystickState*> mJoystickMap;
+   HashTable<S32, controllerState*> mControllerMap;
+
+   bool mJoystickActive;
+
+   void deviceConnectedCallback(S32 index);
+   bool closeControllerByIndex(S32 index);
+   void closeController(SDL_JoystickID sdlId);
+   bool closeJoystickByIndex(S32 index);
+   void closeJoystick(SDL_JoystickID sdlId);
+
+   void buildInputEvent(U32 deviceType, U32 deviceInst, InputEventType objType, InputObjectInstances objInst, InputActionType action, S32 iValue);
+   void buildInputEvent(U32 deviceType, U32 deviceInst, InputEventType objType, InputObjectInstances objInst, InputActionType action, F32 fValue);
+   void buildHatEvents(U32 deviceType, U32 deviceInst, U8 lastState, U8 currentState, S32 hatIndex);
+
+public:
+   DECLARE_STATIC_CLASS(SDLInputManager);
+
+public:
+   SDLInputManager();
+
+   bool enable();
+   void disable();
+   void process();
+
+   void processEvent(SDL_Event &evt);
+
+   static void init();
+
+   // Console interface:
+   S32 openJoystick(S32 sdlIndex, S32 requestedTID);
+   S32 openController(S32 sdlIndex, S32 requestedTID);
+   void closeDevice(S32 sdlIndex);
+   S32 getJoystickOpenState(S32 sdlIndex);
+   void getJoystickTorqueInst(S32 sdlIndex, char* instBuffer);
+};
+
+#endif  // _SDLINPUTMANAGER_H_

+ 14 - 5
Engine/source/windowManager/sdl/sdlWindowMgr.cpp

@@ -21,6 +21,7 @@
 //-----------------------------------------------------------------------------
 
 #include "windowManager/sdl/sdlWindowMgr.h"
+#include "platformSDL/sdlInputManager.h"
 #include "gfx/gfxDevice.h"
 #include "core/util/journal/process.h"
 #include "core/strings/unicode.h"
@@ -269,6 +270,13 @@ void PlatformWindowManagerSDL::_process()
    SDL_Event evt;
    while( SDL_PollEvent(&evt) )
    {      
+      if (evt.type >= SDL_JOYAXISMOTION && evt.type <= SDL_CONTROLLERDEVICEREMAPPED)
+      {
+         SDLInputManager* mgr = static_cast<SDLInputManager*>(Input::getManager());
+         if (mgr)
+            mgr->processEvent(evt);
+         continue;
+      }
       switch(evt.type)
       {
           case SDL_QUIT:
@@ -356,15 +364,16 @@ void PlatformWindowManagerSDL::_process()
 
          case(SDL_DROPCOMPLETE):
          {
-            if (!Con::isFunction("onDropEnd"))
-               break;
-
-            Con::executef("onDropEnd");
+            if (Con::isFunction("onDropEnd"))
+               Con::executef("onDropEnd");
+            break;
          }
 
          default:
          {
-            //Con::printf("Event: %d", evt.type);
+#ifdef TORQUE_DEBUG
+            Con::warnf("Unhandled SDL input event: 0x%04x", evt.type);
+#endif
          }
       }
    }

+ 9 - 2
Tools/CMake/torque3d.cmake

@@ -204,6 +204,9 @@ mark_as_advanced(TORQUE_DEBUG_GFX_MODE)
 #option(DEBUG_SPEW "more debug" OFF)
 set(TORQUE_NO_DSO_GENERATION ON)
 
+option(TORQUE_USE_ZENITY "use the Zenity backend for NFD" OFF)
+mark_as_advanced(TORQUE_USE_ZENITY)
+
 if(WIN32)
     # warning C4800: 'XXX' : forcing value to bool 'true' or 'false' (performance warning)
     set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -wd4800")
@@ -460,7 +463,11 @@ if(TORQUE_SDL)
        # Add other flags to the compiler
        add_definitions(${GTK3_CFLAGS_OTHER})
 
-       set(BLACKLIST "nfd_win.cpp" "nfd_cocoa.m"  )
+       if(TORQUE_USE_ZENITY)
+          set(BLACKLIST "nfd_win.cpp" "nfd_cocoa.m" "nfd_gtk.c" )
+       else()
+          set(BLACKLIST "nfd_win.cpp" "nfd_cocoa.m" "simple_exec.h" "nfd_zenity.c")
+       endif()
        addLib(nativeFileDialogs)
 
        set(BLACKLIST ""  )
@@ -470,7 +477,7 @@ if(TORQUE_SDL)
       addLib(nativeFileDialogs)
       set(BLACKLIST ""  )
  	else()
- 	   set(BLACKLIST "nfd_gtk.c" "nfd_cocoa.m" )
+ 	   set(BLACKLIST "nfd_gtk.c" "nfd_cocoa.m" "simple_exec.h" "nfd_zenity.c")
  	   addLib(nativeFileDialogs)
      set(BLACKLIST ""  )
  	   addLib(comctl32)