Show More
Commit Description:
Various UI improvements.
Commit Description:
Various UI improvements.
References:
File last commit:
Show/Diff file:
Action:
FNA/src/FNAPlatform/SDL2_FNAPlatform.cs
3300 lines | 96.6 KiB | text/x-csharp | CSharpLexer
3300 lines | 96.6 KiB | text/x-csharp | CSharpLexer
r0 | #region License | |||
/* FNA - XNA4 Reimplementation for Desktop Platforms | ||||
* Copyright 2009-2020 Ethan Lee and the MonoGame Team | ||||
* | ||||
* Released under the Microsoft Public License. | ||||
* See LICENSE for details. | ||||
*/ | ||||
#endregion | ||||
#region Using Statements | ||||
using System; | ||||
using System.IO; | ||||
using System.Text; | ||||
using System.Collections.Generic; | ||||
using System.Runtime.InteropServices; | ||||
using SDL2; | ||||
using Microsoft.Xna.Framework.Graphics; | ||||
using Microsoft.Xna.Framework.Audio; | ||||
using Microsoft.Xna.Framework.Input; | ||||
using Microsoft.Xna.Framework.Input.Touch; | ||||
#endregion | ||||
namespace Microsoft.Xna.Framework | ||||
{ | ||||
internal static class SDL2_FNAPlatform | ||||
{ | ||||
#region Static Constants | ||||
private static string OSVersion; | ||||
private static readonly bool UseScancodes = Environment.GetEnvironmentVariable( | ||||
"FNA_KEYBOARD_USE_SCANCODES" | ||||
) == "1"; | ||||
private static bool SupportsGlobalMouse; | ||||
private static string ForcedGLDevice; | ||||
private static string ActualGLDevice; | ||||
// For iOS high dpi support | ||||
private static int RetinaWidth; | ||||
private static int RetinaHeight; | ||||
#endregion | ||||
#region Graphics Backend String Constants | ||||
private const string OPENGL = "OpenGLDevice"; | ||||
private const string MODERNGL = "ModernGLDevice"; | ||||
private const string THREADEDGL = "ThreadedGLDevice"; | ||||
private const string METAL = "MetalDevice"; | ||||
private const string VULKAN = "VulkanDevice"; | ||||
#endregion | ||||
#region Game Objects | ||||
/* This is needed for asynchronous window events */ | ||||
private static List<Game> activeGames = new List<Game>(); | ||||
#endregion | ||||
#region Init/Exit Methods | ||||
public static string ProgramInit(LaunchParameters args) | ||||
{ | ||||
// This is how we can weed out cases where fnalibs is missing | ||||
try | ||||
{ | ||||
OSVersion = SDL.SDL_GetPlatform(); | ||||
} | ||||
catch(Exception e) | ||||
{ | ||||
FNALoggerEXT.LogError( | ||||
"SDL2 was not found! Do you have fnalibs?" | ||||
); | ||||
throw e; | ||||
} | ||||
/* SDL2 might complain if an OS that uses SDL_main has not actually | ||||
* used SDL_main by the time you initialize SDL2. | ||||
* The only platform that is affected is Windows, but we can skip | ||||
* their WinMain. This was only added to prevent iOS from exploding. | ||||
* -flibit | ||||
*/ | ||||
SDL.SDL_SetMainReady(); | ||||
/* A number of platforms don't support global mouse, but | ||||
* this really only matters on desktop where the game | ||||
* screen may not be covering the whole display. | ||||
*/ | ||||
if ( OSVersion.Equals("Windows") || | ||||
OSVersion.Equals("Mac OS X") || | ||||
OSVersion.Equals("Linux") || | ||||
OSVersion.Equals("FreeBSD") || | ||||
OSVersion.Equals("OpenBSD") || | ||||
OSVersion.Equals("NetBSD") ) | ||||
{ | ||||
SupportsGlobalMouse = true; | ||||
} | ||||
else | ||||
{ | ||||
SupportsGlobalMouse = false; | ||||
} | ||||
// Also, Windows is an idiot. -flibit | ||||
if ( OSVersion.Equals("Windows") || | ||||
OSVersion.Equals("WinRT") ) | ||||
{ | ||||
// Visual Studio is an idiot. | ||||
if (System.Diagnostics.Debugger.IsAttached) | ||||
{ | ||||
SDL.SDL_SetHint( | ||||
SDL.SDL_HINT_WINDOWS_DISABLE_THREAD_NAMING, | ||||
"1" | ||||
); | ||||
} | ||||
/* Windows has terrible event pumping and doesn't give us | ||||
* WM_PAINT events correctly. So we get to do this! | ||||
* -flibit | ||||
*/ | ||||
SDL.SDL_SetEventFilter( | ||||
win32OnPaint, | ||||
IntPtr.Zero | ||||
); | ||||
} | ||||
/* Mount TitleLocation.Path */ | ||||
string titleLocation = GetBaseDirectory(); | ||||
// If available, load the SDL_GameControllerDB | ||||
string mappingsDB = Path.Combine( | ||||
titleLocation, | ||||
"gamecontrollerdb.txt" | ||||
); | ||||
if (File.Exists(mappingsDB)) | ||||
{ | ||||
SDL.SDL_GameControllerAddMappingsFromFile( | ||||
mappingsDB | ||||
); | ||||
} | ||||
// Built-in SDL2 command line arguments | ||||
string arg; | ||||
if (args.TryGetValue("disablelateswaptear", out arg) && arg == "1") | ||||
{ | ||||
Environment.SetEnvironmentVariable( | ||||
"FNA_OPENGL_DISABLE_LATESWAPTEAR", | ||||
"1" | ||||
); | ||||
} | ||||
if (args.TryGetValue("glprofile", out arg)) | ||||
{ | ||||
if (arg == "es3") | ||||
{ | ||||
Environment.SetEnvironmentVariable( | ||||
"FNA_OPENGL_FORCE_ES3", | ||||
"1" | ||||
); | ||||
} | ||||
else if (arg == "core") | ||||
{ | ||||
Environment.SetEnvironmentVariable( | ||||
"FNA_OPENGL_FORCE_CORE_PROFILE", | ||||
"1" | ||||
); | ||||
} | ||||
else if (arg == "compatibility") | ||||
{ | ||||
Environment.SetEnvironmentVariable( | ||||
"FNA_OPENGL_FORCE_COMPATIBILITY_PROFILE", | ||||
"1" | ||||
); | ||||
} | ||||
} | ||||
if (args.TryGetValue("angle", out arg) && arg == "1") | ||||
{ | ||||
Environment.SetEnvironmentVariable( | ||||
"FNA_OPENGL_FORCE_ES3", | ||||
"1" | ||||
); | ||||
Environment.SetEnvironmentVariable( | ||||
"SDL_OPENGL_ES_DRIVER", | ||||
"1" | ||||
); | ||||
} | ||||
// This _should_ be the first real SDL call we make... | ||||
SDL.SDL_Init( | ||||
SDL.SDL_INIT_VIDEO | | ||||
SDL.SDL_INIT_JOYSTICK | | ||||
SDL.SDL_INIT_GAMECONTROLLER | | ||||
SDL.SDL_INIT_HAPTIC | ||||
); | ||||
// Set any hints to match XNA4 behavior... | ||||
string hint = SDL.SDL_GetHint(SDL.SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS); | ||||
if (String.IsNullOrEmpty(hint)) | ||||
{ | ||||
SDL.SDL_SetHint( | ||||
SDL.SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, | ||||
"1" | ||||
); | ||||
} | ||||
// By default, assume physical layout, since XNA games probably assume XInput | ||||
hint = SDL.SDL_GetHint(SDL.SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS); | ||||
if (String.IsNullOrEmpty(hint)) | ||||
{ | ||||
SDL.SDL_SetHint( | ||||
SDL.SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, | ||||
"0" | ||||
); | ||||
} | ||||
SDL.SDL_SetHint( | ||||
SDL.SDL_HINT_ORIENTATIONS, | ||||
"LandscapeLeft LandscapeRight Portrait" | ||||
); | ||||
// We want to initialize the controllers ASAP! | ||||
SDL.SDL_Event[] evt = new SDL.SDL_Event[1]; | ||||
SDL.SDL_PumpEvents(); | ||||
while (SDL.SDL_PeepEvents( | ||||
evt, | ||||
1, | ||||
SDL.SDL_eventaction.SDL_GETEVENT, | ||||
SDL.SDL_EventType.SDL_CONTROLLERDEVICEADDED, | ||||
SDL.SDL_EventType.SDL_CONTROLLERDEVICEADDED | ||||
) == 1) { | ||||
INTERNAL_AddInstance(evt[0].cdevice.which); | ||||
} | ||||
return titleLocation; | ||||
} | ||||
public static void ProgramExit(object sender, EventArgs e) | ||||
{ | ||||
AudioEngine.ProgramExiting = true; | ||||
if (SoundEffect.FAudioContext.Context != null) | ||||
{ | ||||
SoundEffect.FAudioContext.Context.Dispose(); | ||||
} | ||||
Media.MediaPlayer.DisposeIfNecessary(); | ||||
// This _should_ be the last SDL call we make... | ||||
SDL.SDL_Quit(); | ||||
} | ||||
#endregion | ||||
#region Window Methods | ||||
private static bool PrepareVKAttributes() | ||||
{ | ||||
// Who will write the VulkanDevice.. will it be YOU? | ||||
return false; | ||||
} | ||||
private static bool PrepareMTLAttributes() | ||||
{ | ||||
if ( String.IsNullOrEmpty(ForcedGLDevice) || | ||||
!ForcedGLDevice.Equals(METAL) ) | ||||
{ | ||||
return false; | ||||
} | ||||
#if DEBUG | ||||
// Always enable the validation layer in debug mode | ||||
Environment.SetEnvironmentVariable( | ||||
"METAL_DEVICE_WRAPPER_TYPE", | ||||
"1" | ||||
); | ||||
#endif | ||||
if (OSVersion.Equals("Mac OS X")) | ||||
{ | ||||
// Let's find out if the OS supports Metal... | ||||
try | ||||
{ | ||||
if (MetalDevice.MTLCreateSystemDefaultDevice() != IntPtr.Zero) | ||||
{ | ||||
// We're good to go! | ||||
return true; | ||||
} | ||||
} | ||||
catch | ||||
{ | ||||
// The OS is too old for Metal! | ||||
return false; | ||||
} | ||||
} | ||||
else if (OSVersion.Equals("iOS") || OSVersion.Equals("tvOS")) | ||||
{ | ||||
/* We only support iOS/tvOS 11.0+ so | ||||
* Metal is guaranteed to be supported. | ||||
*/ | ||||
return true; | ||||
} | ||||
// Oh well, to OpenGL we go! | ||||
return false; | ||||
} | ||||
private static bool PrepareGLAttributes() | ||||
{ | ||||
if ( !String.IsNullOrEmpty(ForcedGLDevice) && | ||||
!ForcedGLDevice.Equals(OPENGL) && | ||||
!ForcedGLDevice.Equals(MODERNGL) && | ||||
!ForcedGLDevice.Equals(THREADEDGL) ) | ||||
{ | ||||
return false; | ||||
} | ||||
// GLContext environment variables | ||||
bool forceES3 = Environment.GetEnvironmentVariable( | ||||
"FNA_OPENGL_FORCE_ES3" | ||||
) == "1"; | ||||
bool forceCoreProfile = Environment.GetEnvironmentVariable( | ||||
"FNA_OPENGL_FORCE_CORE_PROFILE" | ||||
) == "1"; | ||||
bool forceCompatProfile = Environment.GetEnvironmentVariable( | ||||
"FNA_OPENGL_FORCE_COMPATIBILITY_PROFILE" | ||||
) == "1"; | ||||
// Some platforms are GLES only | ||||
forceES3 |= ( | ||||
OSVersion.Equals("WinRT") || | ||||
OSVersion.Equals("iOS") || | ||||
OSVersion.Equals("tvOS") || | ||||
OSVersion.Equals("Stadia") || | ||||
OSVersion.Equals("Android") || | ||||
OSVersion.Equals("Emscripten") | ||||
); | ||||
int depthSize = 24; | ||||
int stencilSize = 8; | ||||
DepthFormat windowDepthFormat; | ||||
if (Enum.TryParse( | ||||
Environment.GetEnvironmentVariable("FNA_OPENGL_WINDOW_DEPTHSTENCILFORMAT"), | ||||
true, | ||||
out windowDepthFormat | ||||
)) { | ||||
if (windowDepthFormat == DepthFormat.None) | ||||
{ | ||||
depthSize = 0; | ||||
stencilSize = 0; | ||||
} | ||||
else if (windowDepthFormat == DepthFormat.Depth16) | ||||
{ | ||||
depthSize = 16; | ||||
stencilSize = 0; | ||||
} | ||||
else if (windowDepthFormat == DepthFormat.Depth24) | ||||
{ | ||||
depthSize = 24; | ||||
stencilSize = 0; | ||||
} | ||||
else if (windowDepthFormat == DepthFormat.Depth24Stencil8) | ||||
{ | ||||
depthSize = 24; | ||||
stencilSize = 8; | ||||
} | ||||
} | ||||
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_RED_SIZE, 8); | ||||
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_GREEN_SIZE, 8); | ||||
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_BLUE_SIZE, 8); | ||||
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_ALPHA_SIZE, 8); | ||||
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_DEPTH_SIZE, depthSize); | ||||
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_STENCIL_SIZE, stencilSize); | ||||
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_DOUBLEBUFFER, 1); | ||||
if (forceES3) | ||||
{ | ||||
SDL.SDL_GL_SetAttribute( | ||||
SDL.SDL_GLattr.SDL_GL_RETAINED_BACKING, | ||||
0 | ||||
); | ||||
SDL.SDL_GL_SetAttribute( | ||||
SDL.SDL_GLattr.SDL_GL_ACCELERATED_VISUAL, | ||||
1 | ||||
); | ||||
SDL.SDL_GL_SetAttribute( | ||||
SDL.SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, | ||||
3 | ||||
); | ||||
SDL.SDL_GL_SetAttribute( | ||||
SDL.SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, | ||||
0 | ||||
); | ||||
SDL.SDL_GL_SetAttribute( | ||||
SDL.SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, | ||||
(int) SDL.SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_ES | ||||
); | ||||
} | ||||
else if (forceCoreProfile) | ||||
{ | ||||
SDL.SDL_GL_SetAttribute( | ||||
SDL.SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, | ||||
4 | ||||
); | ||||
SDL.SDL_GL_SetAttribute( | ||||
SDL.SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, | ||||
6 | ||||
); | ||||
SDL.SDL_GL_SetAttribute( | ||||
SDL.SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, | ||||
(int) SDL.SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_CORE | ||||
); | ||||
} | ||||
else if (forceCompatProfile) | ||||
{ | ||||
SDL.SDL_GL_SetAttribute( | ||||
SDL.SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, | ||||
2 | ||||
); | ||||
SDL.SDL_GL_SetAttribute( | ||||
SDL.SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, | ||||
1 | ||||
); | ||||
SDL.SDL_GL_SetAttribute( | ||||
SDL.SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, | ||||
(int) SDL.SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_COMPATIBILITY | ||||
); | ||||
} | ||||
#if DEBUG | ||||
SDL.SDL_GL_SetAttribute( | ||||
SDL.SDL_GLattr.SDL_GL_CONTEXT_FLAGS, | ||||
(int) SDL.SDL_GLcontext.SDL_GL_CONTEXT_DEBUG_FLAG | ||||
); | ||||
#endif | ||||
return true; | ||||
} | ||||
public static GameWindow CreateWindow() | ||||
{ | ||||
// Set and initialize the SDL2 window | ||||
SDL.SDL_WindowFlags initFlags = ( | ||||
SDL.SDL_WindowFlags.SDL_WINDOW_HIDDEN | | ||||
SDL.SDL_WindowFlags.SDL_WINDOW_INPUT_FOCUS | | ||||
SDL.SDL_WindowFlags.SDL_WINDOW_MOUSE_FOCUS | ||||
); | ||||
// Did the user force a particular GLDevice? | ||||
ForcedGLDevice = Environment.GetEnvironmentVariable( | ||||
"FNA_GRAPHICS_FORCE_GLDEVICE" | ||||
); | ||||
bool vulkan = false, metal = false, opengl = false; | ||||
if (vulkan = PrepareVKAttributes()) | ||||
{ | ||||
initFlags |= SDL.SDL_WindowFlags.SDL_WINDOW_VULKAN; | ||||
ActualGLDevice = VULKAN; | ||||
} | ||||
else if (metal = PrepareMTLAttributes()) | ||||
{ | ||||
SDL.SDL_SetHint(SDL.SDL_HINT_VIDEO_EXTERNAL_CONTEXT, "1"); | ||||
// Metal doesn't require a window flag | ||||
ActualGLDevice = METAL; | ||||
} | ||||
else if (opengl = PrepareGLAttributes()) | ||||
{ | ||||
initFlags |= SDL.SDL_WindowFlags.SDL_WINDOW_OPENGL; | ||||
if ( ForcedGLDevice == MODERNGL || | ||||
ForcedGLDevice == THREADEDGL ) | ||||
{ | ||||
ActualGLDevice = ForcedGLDevice; | ||||
} | ||||
else | ||||
{ | ||||
ActualGLDevice = OPENGL; | ||||
} | ||||
} | ||||
if (Environment.GetEnvironmentVariable("FNA_GRAPHICS_ENABLE_HIGHDPI") == "1") | ||||
{ | ||||
initFlags |= SDL.SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI; | ||||
} | ||||
string title = MonoGame.Utilities.AssemblyHelper.GetDefaultWindowTitle(); | ||||
IntPtr window = SDL.SDL_CreateWindow( | ||||
title, | ||||
SDL.SDL_WINDOWPOS_CENTERED, | ||||
SDL.SDL_WINDOWPOS_CENTERED, | ||||
GraphicsDeviceManager.DefaultBackBufferWidth, | ||||
GraphicsDeviceManager.DefaultBackBufferHeight, | ||||
initFlags | ||||
); | ||||
if (window == IntPtr.Zero) | ||||
{ | ||||
/* If this happens, the GL attributes were | ||||
* rejected by the platform. This is EXTREMELY | ||||
* rare (unless you're on Android, of course). | ||||
*/ | ||||
throw new NoSuitableGraphicsDeviceException( | ||||
SDL.SDL_GetError() | ||||
); | ||||
} | ||||
INTERNAL_SetIcon(window, title); | ||||
// Disable the screensaver. | ||||
SDL.SDL_DisableScreenSaver(); | ||||
// We hide the mouse cursor by default. | ||||
OnIsMouseVisibleChanged(false); | ||||
/* When using OpenGL, iOS and tvOS require | ||||
* an active GL context to get the drawable | ||||
* size of the screen. | ||||
* | ||||
* When using Metal, all Apple platforms | ||||
* require a view to get the drawable size. | ||||
*/ | ||||
IntPtr tempContext = IntPtr.Zero; | ||||
if (opengl && (OSVersion.Equals("iOS") || OSVersion.Equals("tvOS"))) | ||||
{ | ||||
tempContext = SDL.SDL_GL_CreateContext(window); | ||||
} | ||||
else if (metal) | ||||
{ | ||||
tempContext = SDL.SDL_Metal_CreateView(window); | ||||
} | ||||
/* If high DPI is not found, unset the HIGHDPI var. | ||||
* This is our way to communicate that it failed... | ||||
* -flibit | ||||
*/ | ||||
int drawX, drawY; | ||||
if (vulkan) | ||||
{ | ||||
SDL.SDL_Vulkan_GetDrawableSize(window, out drawX, out drawY); | ||||
} | ||||
else if (metal) | ||||
{ | ||||
MetalDevice.GetDrawableSizeFromView(tempContext, out drawX, out drawY); | ||||
} | ||||
else if (opengl) | ||||
{ | ||||
SDL.SDL_GL_GetDrawableSize(window, out drawX, out drawY); | ||||
} | ||||
else | ||||
{ | ||||
throw new InvalidOperationException("DirectX? Glide? What?"); | ||||
} | ||||
if ( drawX == GraphicsDeviceManager.DefaultBackBufferWidth && | ||||
drawY == GraphicsDeviceManager.DefaultBackBufferHeight ) | ||||
{ | ||||
Environment.SetEnvironmentVariable("FNA_GRAPHICS_ENABLE_HIGHDPI", "0"); | ||||
} | ||||
else | ||||
{ | ||||
// Store the full retina resolution of the display | ||||
RetinaWidth = drawX; | ||||
RetinaHeight = drawY; | ||||
} | ||||
// We're done with that temporary context. | ||||
if (tempContext != IntPtr.Zero) | ||||
{ | ||||
if (opengl) | ||||
{ | ||||
SDL.SDL_GL_DeleteContext(tempContext); | ||||
} | ||||
else if (metal) | ||||
{ | ||||
SDL.SDL_Metal_DestroyView(tempContext); | ||||
} | ||||
} | ||||
return new FNAWindow( | ||||
window, | ||||
@"\\.\DISPLAY" + ( | ||||
SDL.SDL_GetWindowDisplayIndex(window) + 1 | ||||
).ToString() | ||||
); | ||||
} | ||||
public static void DisposeWindow(GameWindow window) | ||||
{ | ||||
/* Some window managers might try to minimize the window as we're | ||||
* destroying it. This looks pretty stupid and could cause problems, | ||||
* so set this hint right before we destroy everything. | ||||
* -flibit | ||||
*/ | ||||
SDL.SDL_SetHintWithPriority( | ||||
SDL.SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, | ||||
"0", | ||||
SDL.SDL_HintPriority.SDL_HINT_OVERRIDE | ||||
); | ||||
if (Mouse.WindowHandle == window.Handle) | ||||
{ | ||||
Mouse.WindowHandle = IntPtr.Zero; | ||||
} | ||||
if (TouchPanel.WindowHandle == window.Handle) | ||||
{ | ||||
TouchPanel.WindowHandle = IntPtr.Zero; | ||||
} | ||||
SDL.SDL_DestroyWindow(window.Handle); | ||||
} | ||||
public static void ApplyWindowChanges( | ||||
IntPtr window, | ||||
int clientWidth, | ||||
int clientHeight, | ||||
bool wantsFullscreen, | ||||
string screenDeviceName, | ||||
ref string resultDeviceName | ||||
) { | ||||
bool center = false; | ||||
if ( Environment.GetEnvironmentVariable("FNA_GRAPHICS_ENABLE_HIGHDPI") == "1" && | ||||
OSVersion.Equals("Mac OS X") ) | ||||
{ | ||||
/* For high-DPI windows, halve the size! | ||||
* The drawable size is now the primary width/height, so | ||||
* the window needs to accommodate the GL viewport. | ||||
* -flibit | ||||
*/ | ||||
clientWidth /= 2; | ||||
clientHeight /= 2; | ||||
} | ||||
// When windowed, set the size before moving | ||||
if (!wantsFullscreen) | ||||
{ | ||||
bool resize = false; | ||||
if ((SDL.SDL_GetWindowFlags(window) & (uint) SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN) != 0) | ||||
{ | ||||
SDL.SDL_SetWindowFullscreen(window, 0); | ||||
resize = true; | ||||
} | ||||
else | ||||
{ | ||||
int w, h; | ||||
SDL.SDL_GetWindowSize( | ||||
window, | ||||
out w, | ||||
out h | ||||
); | ||||
resize = (clientWidth != w || clientHeight != h); | ||||
} | ||||
if (resize) | ||||
{ | ||||
SDL.SDL_SetWindowSize(window, clientWidth, clientHeight); | ||||
center = true; | ||||
} | ||||
} | ||||
// Get on the right display! | ||||
int displayIndex = 0; | ||||
for (int i = 0; i < GraphicsAdapter.Adapters.Count; i += 1) | ||||
{ | ||||
if (screenDeviceName == GraphicsAdapter.Adapters[i].DeviceName) | ||||
{ | ||||
displayIndex = i; | ||||
break; | ||||
} | ||||
} | ||||
// Just to be sure, become a window first before changing displays | ||||
if (resultDeviceName != screenDeviceName) | ||||
{ | ||||
SDL.SDL_SetWindowFullscreen(window, 0); | ||||
resultDeviceName = screenDeviceName; | ||||
center = true; | ||||
} | ||||
// Window always gets centered on changes, per XNA behavior | ||||
if (center) | ||||
{ | ||||
int pos = SDL.SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex); | ||||
SDL.SDL_SetWindowPosition( | ||||
window, | ||||
pos, | ||||
pos | ||||
); | ||||
} | ||||
// Set fullscreen after we've done all the ugly stuff. | ||||
if (wantsFullscreen) | ||||
{ | ||||
if ((SDL.SDL_GetWindowFlags(window) & (uint) SDL.SDL_WindowFlags.SDL_WINDOW_SHOWN) == 0) | ||||
{ | ||||
/* If we're still hidden, we can't actually go fullscreen yet. | ||||
* But, we can at least set the hidden window size to match | ||||
* what the window/drawable sizes will eventually be later. | ||||
* -flibit | ||||
*/ | ||||
SDL.SDL_DisplayMode mode; | ||||
SDL.SDL_GetCurrentDisplayMode( | ||||
displayIndex, | ||||
out mode | ||||
); | ||||
SDL.SDL_SetWindowSize(window, mode.w, mode.h); | ||||
} | ||||
SDL.SDL_SetWindowFullscreen( | ||||
window, | ||||
(uint) SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP | ||||
); | ||||
} | ||||
} | ||||
public static Rectangle GetWindowBounds(IntPtr window) | ||||
{ | ||||
Rectangle result; | ||||
if ((SDL.SDL_GetWindowFlags(window) & (uint) SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN) != 0) | ||||
{ | ||||
/* FIXME: SDL2 bug! | ||||
* SDL's a little weird about SDL_GetWindowSize. | ||||
* If you call it early enough (for example, | ||||
* Game.Initialize()), it reports outdated ints. | ||||
* So you know what, let's just use this. | ||||
* -flibit | ||||
*/ | ||||
SDL.SDL_DisplayMode mode; | ||||
SDL.SDL_GetCurrentDisplayMode( | ||||
SDL.SDL_GetWindowDisplayIndex( | ||||
window | ||||
), | ||||
out mode | ||||
); | ||||
result.X = 0; | ||||
result.Y = 0; | ||||
result.Width = mode.w; | ||||
result.Height = mode.h; | ||||
} | ||||
else | ||||
{ | ||||
SDL.SDL_GetWindowPosition( | ||||
window, | ||||
out result.X, | ||||
out result.Y | ||||
); | ||||
SDL.SDL_GetWindowSize( | ||||
window, | ||||
out result.Width, | ||||
out result.Height | ||||
); | ||||
} | ||||
return result; | ||||
} | ||||
public static bool GetWindowResizable(IntPtr window) | ||||
{ | ||||
return ((SDL.SDL_GetWindowFlags(window) & (uint) SDL.SDL_WindowFlags.SDL_WINDOW_RESIZABLE) != 0); | ||||
} | ||||
public static void SetWindowResizable(IntPtr window, bool resizable) | ||||
{ | ||||
SDL.SDL_SetWindowResizable( | ||||
window, | ||||
resizable ? | ||||
SDL.SDL_bool.SDL_TRUE : | ||||
SDL.SDL_bool.SDL_FALSE | ||||
); | ||||
} | ||||
public static bool GetWindowBorderless(IntPtr window) | ||||
{ | ||||
return ((SDL.SDL_GetWindowFlags(window) & (uint) SDL.SDL_WindowFlags.SDL_WINDOW_BORDERLESS) != 0); | ||||
} | ||||
public static void SetWindowBorderless(IntPtr window, bool borderless) | ||||
{ | ||||
SDL.SDL_SetWindowBordered( | ||||
window, | ||||
borderless ? | ||||
SDL.SDL_bool.SDL_FALSE : | ||||
SDL.SDL_bool.SDL_TRUE | ||||
); | ||||
} | ||||
public static void SetWindowTitle(IntPtr window, string title) | ||||
{ | ||||
SDL.SDL_SetWindowTitle( | ||||
window, | ||||
title | ||||
); | ||||
} | ||||
private static void INTERNAL_SetIcon(IntPtr window, string title) | ||||
{ | ||||
string fileIn = String.Empty; | ||||
/* If the game's using SDL2_image, provide the option to use a PNG | ||||
* instead of a BMP. Nice for anyone who cares about transparency. | ||||
* -flibit | ||||
*/ | ||||
try | ||||
{ | ||||
fileIn = INTERNAL_GetIconName(title + ".png"); | ||||
if (!String.IsNullOrEmpty(fileIn)) | ||||
{ | ||||
IntPtr icon = SDL_image.IMG_Load(fileIn); | ||||
SDL.SDL_SetWindowIcon(window, icon); | ||||
SDL.SDL_FreeSurface(icon); | ||||
return; | ||||
} | ||||
} | ||||
catch(DllNotFoundException) | ||||
{ | ||||
// Not that big a deal guys. | ||||
} | ||||
fileIn = INTERNAL_GetIconName(title + ".bmp"); | ||||
if (!String.IsNullOrEmpty(fileIn)) | ||||
{ | ||||
IntPtr icon = SDL.SDL_LoadBMP(fileIn); | ||||
SDL.SDL_SetWindowIcon(window, icon); | ||||
SDL.SDL_FreeSurface(icon); | ||||
} | ||||
} | ||||
private static string INTERNAL_GetIconName(string title) | ||||
{ | ||||
string fileIn = Path.Combine(TitleLocation.Path, title); | ||||
if (File.Exists(fileIn)) | ||||
{ | ||||
// If the title and filename work, it just works. Fine. | ||||
return fileIn; | ||||
} | ||||
else | ||||
{ | ||||
// But sometimes the title has invalid characters inside. | ||||
fileIn = Path.Combine( | ||||
TitleLocation.Path, | ||||
INTERNAL_StripBadChars(title) | ||||
); | ||||
if (File.Exists(fileIn)) | ||||
{ | ||||
return fileIn; | ||||
} | ||||
} | ||||
return String.Empty; | ||||
} | ||||
private static string INTERNAL_StripBadChars(string path) | ||||
{ | ||||
/* In addition to the filesystem's invalid charset, we need to | ||||
* blacklist the Windows standard set too, no matter what. | ||||
* -flibit | ||||
*/ | ||||
char[] hardCodeBadChars = new char[] | ||||
{ | ||||
'<', | ||||
'>', | ||||
':', | ||||
'"', | ||||
'/', | ||||
'\\', | ||||
'|', | ||||
'?', | ||||
'*' | ||||
}; | ||||
List<char> badChars = new List<char>(); | ||||
badChars.AddRange(Path.GetInvalidFileNameChars()); | ||||
badChars.AddRange(hardCodeBadChars); | ||||
string stripChars = path; | ||||
foreach (char c in badChars) | ||||
{ | ||||
stripChars = stripChars.Replace(c.ToString(), ""); | ||||
} | ||||
return stripChars; | ||||
} | ||||
public static void SetTextInputRectangle(Rectangle rectangle) | ||||
{ | ||||
SDL.SDL_Rect rect = new SDL.SDL_Rect(); | ||||
rect.x = rectangle.X; | ||||
rect.y = rectangle.Y; | ||||
rect.w = rectangle.Width; | ||||
rect.h = rectangle.Height; | ||||
SDL.SDL_SetTextInputRect(ref rect); | ||||
} | ||||
#endregion | ||||
#region Display Methods | ||||
private static DisplayOrientation INTERNAL_ConvertOrientation(SDL.SDL_DisplayOrientation orientation) | ||||
{ | ||||
switch (orientation) | ||||
{ | ||||
case SDL.SDL_DisplayOrientation.SDL_ORIENTATION_LANDSCAPE: | ||||
return DisplayOrientation.LandscapeLeft; | ||||
case SDL.SDL_DisplayOrientation.SDL_ORIENTATION_LANDSCAPE_FLIPPED: | ||||
return DisplayOrientation.LandscapeRight; | ||||
case SDL.SDL_DisplayOrientation.SDL_ORIENTATION_PORTRAIT: | ||||
return DisplayOrientation.Portrait; | ||||
default: | ||||
throw new NotSupportedException("FNA does not support this device orientation."); | ||||
} | ||||
} | ||||
private static void INTERNAL_HandleOrientationChange( | ||||
DisplayOrientation orientation, | ||||
GraphicsDevice graphicsDevice, | ||||
FNAWindow window | ||||
) { | ||||
// Flip the backbuffer dimensions if needed | ||||
int width = graphicsDevice.PresentationParameters.BackBufferWidth; | ||||
int height = graphicsDevice.PresentationParameters.BackBufferHeight; | ||||
int min = Math.Min(width, height); | ||||
int max = Math.Max(width, height); | ||||
if (orientation == DisplayOrientation.Portrait) | ||||
{ | ||||
graphicsDevice.PresentationParameters.BackBufferWidth = min; | ||||
graphicsDevice.PresentationParameters.BackBufferHeight = max; | ||||
} | ||||
else | ||||
{ | ||||
graphicsDevice.PresentationParameters.BackBufferWidth = max; | ||||
graphicsDevice.PresentationParameters.BackBufferHeight = min; | ||||
} | ||||
// Update the graphics device and window | ||||
graphicsDevice.PresentationParameters.DisplayOrientation = orientation; | ||||
window.CurrentOrientation = orientation; | ||||
graphicsDevice.Reset(); | ||||
window.INTERNAL_OnOrientationChanged(); | ||||
} | ||||
public static bool SupportsOrientationChanges() | ||||
{ | ||||
return OSVersion.Equals("iOS") || OSVersion.Equals("Android"); | ||||
} | ||||
#endregion | ||||
#region Event Loop | ||||
public static void RunLoop(Game game) | ||||
{ | ||||
SDL.SDL_ShowWindow(game.Window.Handle); | ||||
game.IsActive = true; | ||||
Rectangle windowBounds = game.Window.ClientBounds; | ||||
Mouse.INTERNAL_WindowWidth = windowBounds.Width; | ||||
Mouse.INTERNAL_WindowHeight = windowBounds.Height; | ||||
// Which display did we end up on? | ||||
int displayIndex = SDL.SDL_GetWindowDisplayIndex( | ||||
game.Window.Handle | ||||
); | ||||
// Store this for internal event filter work | ||||
activeGames.Add(game); | ||||
// OSX has some fancy fullscreen features, let's use them! | ||||
bool osxUseSpaces; | ||||
if (OSVersion.Equals("Mac OS X")) | ||||
{ | ||||
string hint = SDL.SDL_GetHint(SDL.SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES); | ||||
osxUseSpaces = (String.IsNullOrEmpty(hint) || hint.Equals("1")); | ||||
} | ||||
else | ||||
{ | ||||
osxUseSpaces = false; | ||||
} | ||||
// Perform initial check for a touch device | ||||
TouchPanel.TouchDeviceExists = GetTouchCapabilities().IsConnected; | ||||
// Do we want to read keycodes or scancodes? | ||||
if (UseScancodes) | ||||
{ | ||||
FNALoggerEXT.LogInfo("Using scancodes instead of keycodes!"); | ||||
} | ||||
// Active Key List | ||||
List<Keys> keys = new List<Keys>(); | ||||
/* Setup Text Input Control Character Arrays | ||||
* (Only 7 control keys supported at this time) | ||||
*/ | ||||
char[] textInputCharacters = new char[] | ||||
{ | ||||
(char) 2, // Home | ||||
(char) 3, // End | ||||
(char) 8, // Backspace | ||||
(char) 9, // Tab | ||||
(char) 13, // Enter | ||||
(char) 127, // Delete | ||||
(char) 22 // Ctrl+V (Paste) | ||||
}; | ||||
Dictionary<Keys, int> textInputBindings = new Dictionary<Keys, int>() | ||||
{ | ||||
{ Keys.Home, 0 }, | ||||
{ Keys.End, 1 }, | ||||
{ Keys.Back, 2 }, | ||||
{ Keys.Tab, 3 }, | ||||
{ Keys.Enter, 4 }, | ||||
{ Keys.Delete, 5 } | ||||
// Ctrl+V is special! | ||||
}; | ||||
bool[] textInputControlDown = new bool[textInputCharacters.Length]; | ||||
int[] textInputControlRepeat = new int[textInputCharacters.Length]; | ||||
bool textInputSuppress = false; | ||||
SDL.SDL_Event evt; | ||||
while (game.RunApplication) | ||||
{ | ||||
while (SDL.SDL_PollEvent(out evt) == 1) | ||||
{ | ||||
// Keyboard | ||||
if (evt.type == SDL.SDL_EventType.SDL_KEYDOWN) | ||||
{ | ||||
Keys key = ToXNAKey(ref evt.key.keysym); | ||||
if (!keys.Contains(key)) | ||||
{ | ||||
keys.Add(key); | ||||
int textIndex; | ||||
if (textInputBindings.TryGetValue(key, out textIndex)) | ||||
{ | ||||
textInputControlDown[textIndex] = true; | ||||
textInputControlRepeat[textIndex] = Environment.TickCount + 400; | ||||
TextInputEXT.OnTextInput(textInputCharacters[textIndex]); | ||||
} | ||||
else if (keys.Contains(Keys.LeftControl) && key == Keys.V) | ||||
{ | ||||
textInputControlDown[6] = true; | ||||
textInputControlRepeat[6] = Environment.TickCount + 400; | ||||
TextInputEXT.OnTextInput(textInputCharacters[6]); | ||||
textInputSuppress = true; | ||||
} | ||||
} | ||||
} | ||||
else if (evt.type == SDL.SDL_EventType.SDL_KEYUP) | ||||
{ | ||||
Keys key = ToXNAKey(ref evt.key.keysym); | ||||
if (keys.Remove(key)) | ||||
{ | ||||
int value; | ||||
if (textInputBindings.TryGetValue(key, out value)) | ||||
{ | ||||
textInputControlDown[value] = false; | ||||
} | ||||
else if ((!keys.Contains(Keys.LeftControl) && textInputControlDown[3]) || key == Keys.V) | ||||
{ | ||||
textInputControlDown[6] = false; | ||||
textInputSuppress = false; | ||||
} | ||||
} | ||||
} | ||||
// Mouse Input | ||||
else if (evt.type == SDL.SDL_EventType.SDL_MOUSEBUTTONDOWN) | ||||
{ | ||||
Mouse.INTERNAL_onClicked(evt.button.button - 1); | ||||
} | ||||
else if (evt.type == SDL.SDL_EventType.SDL_MOUSEWHEEL) | ||||
{ | ||||
// 120 units per notch. Because reasons. | ||||
Mouse.INTERNAL_MouseWheel += evt.wheel.y * 120; | ||||
} | ||||
// Touch Input | ||||
else if (evt.type == SDL.SDL_EventType.SDL_FINGERDOWN) | ||||
{ | ||||
// Windows only notices a touch screen once it's touched | ||||
TouchPanel.TouchDeviceExists = true; | ||||
TouchPanel.INTERNAL_onTouchEvent( | ||||
(int) evt.tfinger.fingerId, | ||||
TouchLocationState.Pressed, | ||||
evt.tfinger.x, | ||||
evt.tfinger.y, | ||||
0, | ||||
0 | ||||
); | ||||
} | ||||
else if (evt.type == SDL.SDL_EventType.SDL_FINGERMOTION) | ||||
{ | ||||
TouchPanel.INTERNAL_onTouchEvent( | ||||
(int) evt.tfinger.fingerId, | ||||
TouchLocationState.Moved, | ||||
evt.tfinger.x, | ||||
evt.tfinger.y, | ||||
evt.tfinger.dx, | ||||
evt.tfinger.dy | ||||
); | ||||
} | ||||
else if (evt.type == SDL.SDL_EventType.SDL_FINGERUP) | ||||
{ | ||||
TouchPanel.INTERNAL_onTouchEvent( | ||||
(int) evt.tfinger.fingerId, | ||||
TouchLocationState.Released, | ||||
evt.tfinger.x, | ||||
evt.tfinger.y, | ||||
0, | ||||
0 | ||||
); | ||||
} | ||||
// Various Window Events... | ||||
else if (evt.type == SDL.SDL_EventType.SDL_WINDOWEVENT) | ||||
{ | ||||
// Window Focus | ||||
if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_FOCUS_GAINED) | ||||
{ | ||||
game.IsActive = true; | ||||
if (!osxUseSpaces) | ||||
{ | ||||
// If we alt-tab away, we lose the 'fullscreen desktop' flag on some WMs | ||||
SDL.SDL_SetWindowFullscreen( | ||||
game.Window.Handle, | ||||
game.GraphicsDevice.PresentationParameters.IsFullScreen ? | ||||
(uint) SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP : | ||||
0 | ||||
); | ||||
} | ||||
// Disable the screensaver when we're back. | ||||
SDL.SDL_DisableScreenSaver(); | ||||
} | ||||
else if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_FOCUS_LOST) | ||||
{ | ||||
game.IsActive = false; | ||||
if (!osxUseSpaces) | ||||
{ | ||||
SDL.SDL_SetWindowFullscreen(game.Window.Handle, 0); | ||||
} | ||||
// Give the screensaver back, we're not that important now. | ||||
SDL.SDL_EnableScreenSaver(); | ||||
} | ||||
// Window Resize | ||||
else if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED) | ||||
{ | ||||
// This is called on both API and WM resizes | ||||
Mouse.INTERNAL_WindowWidth = evt.window.data1; | ||||
Mouse.INTERNAL_WindowHeight = evt.window.data2; | ||||
} | ||||
else if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_RESIZED) | ||||
{ | ||||
/* This should be called on user resize only, NOT ApplyChanges! | ||||
* Sadly some window managers are idiots and fire events anyway. | ||||
* Also ignore any other "resizes" (alt-tab, fullscreen, etc.) | ||||
* -flibit | ||||
*/ | ||||
if (GetWindowResizable(game.Window.Handle)) | ||||
{ | ||||
((FNAWindow) game.Window).INTERNAL_ClientSizeChanged(); | ||||
} | ||||
} | ||||
else if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_EXPOSED) | ||||
{ | ||||
// This is typically called when the window is made bigger | ||||
game.RedrawWindow(); | ||||
} | ||||
// Window Move | ||||
else if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_MOVED) | ||||
{ | ||||
/* Apparently if you move the window to a new | ||||
* display, a GraphicsDevice Reset occurs. | ||||
* -flibit | ||||
*/ | ||||
int newIndex = SDL.SDL_GetWindowDisplayIndex( | ||||
game.Window.Handle | ||||
); | ||||
if (newIndex != displayIndex) | ||||
{ | ||||
displayIndex = newIndex; | ||||
game.GraphicsDevice.Reset( | ||||
game.GraphicsDevice.PresentationParameters, | ||||
GraphicsAdapter.Adapters[displayIndex] | ||||
); | ||||
} | ||||
} | ||||
// Mouse Focus | ||||
else if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_ENTER) | ||||
{ | ||||
SDL.SDL_DisableScreenSaver(); | ||||
} | ||||
else if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_LEAVE) | ||||
{ | ||||
SDL.SDL_EnableScreenSaver(); | ||||
} | ||||
} | ||||
// Display Events | ||||
else if (evt.type == SDL.SDL_EventType.SDL_DISPLAYEVENT) | ||||
{ | ||||
// Orientation Change | ||||
if (evt.display.displayEvent == SDL.SDL_DisplayEventID.SDL_DISPLAYEVENT_ORIENTATION) | ||||
{ | ||||
DisplayOrientation orientation = INTERNAL_ConvertOrientation( | ||||
(SDL.SDL_DisplayOrientation) evt.display.data1 | ||||
); | ||||
INTERNAL_HandleOrientationChange( | ||||
orientation, | ||||
game.GraphicsDevice, | ||||
(FNAWindow) game.Window | ||||
); | ||||
} | ||||
} | ||||
// Controller device management | ||||
else if (evt.type == SDL.SDL_EventType.SDL_CONTROLLERDEVICEADDED) | ||||
{ | ||||
INTERNAL_AddInstance(evt.cdevice.which); | ||||
} | ||||
else if (evt.type == SDL.SDL_EventType.SDL_CONTROLLERDEVICEREMOVED) | ||||
{ | ||||
INTERNAL_RemoveInstance(evt.cdevice.which); | ||||
} | ||||
// Text Input | ||||
else if (evt.type == SDL.SDL_EventType.SDL_TEXTINPUT && !textInputSuppress) | ||||
{ | ||||
// Based on the SDL2# LPUtf8StrMarshaler | ||||
unsafe | ||||
{ | ||||
byte* endPtr = evt.text.text; | ||||
if (*endPtr != 0) | ||||
{ | ||||
int bytes = 0; | ||||
while (*endPtr != 0) | ||||
{ | ||||
endPtr++; | ||||
bytes += 1; | ||||
} | ||||
/* UTF8 will never encode more characters | ||||
* than bytes in a string, so bytes is a | ||||
* suitable upper estimate of size needed | ||||
*/ | ||||
char* charsBuffer = stackalloc char[bytes]; | ||||
int chars = Encoding.UTF8.GetChars( | ||||
evt.text.text, | ||||
bytes, | ||||
charsBuffer, | ||||
bytes | ||||
); | ||||
for (int i = 0; i < chars; i += 1) | ||||
{ | ||||
TextInputEXT.OnTextInput(charsBuffer[i]); | ||||
} | ||||
} | ||||
} | ||||
} | ||||
// Quit | ||||
else if (evt.type == SDL.SDL_EventType.SDL_QUIT) | ||||
{ | ||||
game.RunApplication = false; | ||||
break; | ||||
} | ||||
} | ||||
// Text Input Controls Key Handling | ||||
for (int i = 0; i < textInputCharacters.Length; i += 1) | ||||
{ | ||||
if (textInputControlDown[i] && textInputControlRepeat[i] <= Environment.TickCount) | ||||
{ | ||||
TextInputEXT.OnTextInput(textInputCharacters[i]); | ||||
} | ||||
} | ||||
Keyboard.SetKeys(keys); | ||||
game.Tick(); | ||||
} | ||||
// Okay, we don't care about the events anymore | ||||
activeGames.Remove(game); | ||||
// We out. | ||||
game.Exit(); | ||||
} | ||||
#endregion | ||||
#region IGL/IAL Methods | ||||
public static IGLDevice CreateGLDevice( | ||||
PresentationParameters presentationParameters, | ||||
GraphicsAdapter adapter | ||||
) { | ||||
if (string.IsNullOrEmpty(ActualGLDevice)) | ||||
{ | ||||
/* This may be a GraphicsDevice with no Game. | ||||
* in that case, try this var one last time. | ||||
*/ | ||||
ActualGLDevice = Environment.GetEnvironmentVariable( | ||||
"FNA_GRAPHICS_FORCE_GLDEVICE" | ||||
); | ||||
if (string.IsNullOrEmpty(ActualGLDevice)) | ||||
{ | ||||
// No device requested at all? Try to guess. | ||||
SDL.SDL_WindowFlags flags = (SDL.SDL_WindowFlags) SDL.SDL_GetWindowFlags( | ||||
presentationParameters.DeviceWindowHandle | ||||
); | ||||
if ((flags & SDL.SDL_WindowFlags.SDL_WINDOW_VULKAN) == SDL.SDL_WindowFlags.SDL_WINDOW_VULKAN) | ||||
{ | ||||
ActualGLDevice = VULKAN; | ||||
} | ||||
else if ((flags & SDL.SDL_WindowFlags.SDL_WINDOW_OPENGL) == SDL.SDL_WindowFlags.SDL_WINDOW_OPENGL) | ||||
{ | ||||
ActualGLDevice = OPENGL; | ||||
} | ||||
else if ( OSVersion.Equals("Mac OS X") || | ||||
OSVersion.Equals("iOS") || | ||||
OSVersion.Equals("tvOS") ) | ||||
{ | ||||
ActualGLDevice = METAL; | ||||
} | ||||
} | ||||
} | ||||
switch (ActualGLDevice) | ||||
{ | ||||
case VULKAN: break; // Maybe some day! | ||||
case METAL: | ||||
return new MetalDevice(presentationParameters); | ||||
case MODERNGL: | ||||
// FIXME: This is still experimental! -flibit | ||||
return new ModernGLDevice(presentationParameters); | ||||
case THREADEDGL: | ||||
// FIXME: This is still experimental! -flibit | ||||
return new ThreadedGLDevice(presentationParameters); | ||||
case OPENGL: | ||||
return new OpenGLDevice(presentationParameters); | ||||
} | ||||
throw new NotSupportedException( | ||||
"The requested GLDevice is not present!" | ||||
); | ||||
} | ||||
#endregion | ||||
#region Graphics Methods | ||||
public static GraphicsAdapter[] GetGraphicsAdapters() | ||||
{ | ||||
SDL.SDL_DisplayMode filler = new SDL.SDL_DisplayMode(); | ||||
GraphicsAdapter[] adapters = new GraphicsAdapter[SDL.SDL_GetNumVideoDisplays()]; | ||||
for (int i = 0; i < adapters.Length; i += 1) | ||||
{ | ||||
List<DisplayMode> modes = new List<DisplayMode>(); | ||||
int numModes = SDL.SDL_GetNumDisplayModes(i); | ||||
for (int j = numModes - 1; j >= 0; j -= 1) | ||||
{ | ||||
SDL.SDL_GetDisplayMode(i, j, out filler); | ||||
// Check for dupes caused by varying refresh rates. | ||||
bool dupe = false; | ||||
foreach (DisplayMode mode in modes) | ||||
{ | ||||
if (filler.w == mode.Width && filler.h == mode.Height) | ||||
{ | ||||
dupe = true; | ||||
} | ||||
} | ||||
if (!dupe) | ||||
{ | ||||
modes.Add( | ||||
new DisplayMode( | ||||
filler.w, | ||||
filler.h, | ||||
SurfaceFormat.Color // FIXME: Assumption! | ||||
) | ||||
); | ||||
} | ||||
} | ||||
adapters[i] = new GraphicsAdapter( | ||||
new DisplayModeCollection(modes), | ||||
@"\\.\DISPLAY" + (i + 1).ToString(), | ||||
SDL.SDL_GetDisplayName(i) | ||||
); | ||||
} | ||||
return adapters; | ||||
} | ||||
public static DisplayMode GetCurrentDisplayMode(int adapterIndex) | ||||
{ | ||||
SDL.SDL_DisplayMode filler = new SDL.SDL_DisplayMode(); | ||||
SDL.SDL_GetCurrentDisplayMode(adapterIndex, out filler); | ||||
if ( OSVersion.Equals("iOS") && | ||||
Environment.GetEnvironmentVariable("FNA_GRAPHICS_ENABLE_HIGHDPI") == "1" ) | ||||
{ | ||||
// Provide the actual resolution in pixels, not points. | ||||
filler.w = RetinaWidth; | ||||
filler.h = RetinaHeight; | ||||
} | ||||
return new DisplayMode( | ||||
filler.w, | ||||
filler.h, | ||||
SurfaceFormat.Color // FIXME: Assumption! | ||||
); | ||||
} | ||||
#endregion | ||||
#region Mouse Methods | ||||
public static void GetMouseState( | ||||
IntPtr window, | ||||
out int x, | ||||
out int y, | ||||
out ButtonState left, | ||||
out ButtonState middle, | ||||
out ButtonState right, | ||||
out ButtonState x1, | ||||
out ButtonState x2 | ||||
) { | ||||
uint flags; | ||||
if (GetRelativeMouseMode()) | ||||
{ | ||||
flags = SDL.SDL_GetRelativeMouseState(out x, out y); | ||||
} | ||||
else if (SupportsGlobalMouse) | ||||
{ | ||||
flags = SDL.SDL_GetGlobalMouseState(out x, out y); | ||||
int wx = 0, wy = 0; | ||||
SDL.SDL_GetWindowPosition(window, out wx, out wy); | ||||
x -= wx; | ||||
y -= wy; | ||||
} | ||||
else | ||||
{ | ||||
/* This is inaccurate, but what can you do... */ | ||||
flags = SDL.SDL_GetMouseState(out x, out y); | ||||
} | ||||
left = (ButtonState) (flags & SDL.SDL_BUTTON_LMASK); | ||||
middle = (ButtonState) ((flags & SDL.SDL_BUTTON_MMASK) >> 1); | ||||
right = (ButtonState) ((flags & SDL.SDL_BUTTON_RMASK) >> 2); | ||||
x1 = (ButtonState) ((flags & SDL.SDL_BUTTON_X1MASK) >> 3); | ||||
x2 = (ButtonState) ((flags & SDL.SDL_BUTTON_X2MASK) >> 4); | ||||
} | ||||
public static void OnIsMouseVisibleChanged(bool visible) | ||||
{ | ||||
SDL.SDL_ShowCursor(visible ? 1 : 0); | ||||
} | ||||
public static bool GetRelativeMouseMode() | ||||
{ | ||||
return SDL.SDL_GetRelativeMouseMode() == SDL.SDL_bool.SDL_TRUE; | ||||
} | ||||
public static void SetRelativeMouseMode(bool enable) | ||||
{ | ||||
SDL.SDL_SetRelativeMouseMode( | ||||
enable ? | ||||
SDL.SDL_bool.SDL_TRUE : | ||||
SDL.SDL_bool.SDL_FALSE | ||||
); | ||||
} | ||||
#endregion | ||||
#region Storage Methods | ||||
private static string GetBaseDirectory() | ||||
{ | ||||
if (Environment.GetEnvironmentVariable("FNA_SDL2_FORCE_BASE_PATH") != "1") | ||||
{ | ||||
// If your platform uses a CLR, you want to be in this list! | ||||
if ( OSVersion.Equals("Windows") || | ||||
OSVersion.Equals("Mac OS X") || | ||||
OSVersion.Equals("Linux") || | ||||
OSVersion.Equals("FreeBSD") || | ||||
OSVersion.Equals("OpenBSD") || | ||||
OSVersion.Equals("NetBSD") ) | ||||
{ | ||||
return AppDomain.CurrentDomain.BaseDirectory; | ||||
} | ||||
} | ||||
string result = SDL.SDL_GetBasePath(); | ||||
if (string.IsNullOrEmpty(result)) | ||||
{ | ||||
result = AppDomain.CurrentDomain.BaseDirectory; | ||||
} | ||||
if (string.IsNullOrEmpty(result)) | ||||
{ | ||||
/* In the chance that there is no base directory, | ||||
* return the working directory and hope for the best. | ||||
* | ||||
* If we've reached this, the game has either been | ||||
* started from its directory, or a wrapper has set up | ||||
* the working directory to the game dir for us. | ||||
* | ||||
* Note about Android: | ||||
* | ||||
* There is no way from the C# side of things to cleanly | ||||
* obtain where the game is located without looking at an | ||||
* instance of System.Diagnostics.StackTrace or without | ||||
* some interop between the Java and C# side of things. | ||||
* We're assuming that either the environment itself is | ||||
* setting one of the possible base paths to point to the | ||||
* game dir, or that the Java side has called into the C# | ||||
* side to set Environment.CurrentDirectory. | ||||
* | ||||
* In the best case, nothing would be set and the game | ||||
* wouldn't use the title location in the first place, as | ||||
* the assets would be read directly from the .apk / .obb | ||||
* -ade | ||||
*/ | ||||
result = Environment.CurrentDirectory; | ||||
} | ||||
return result; | ||||
} | ||||
public static string GetStorageRoot() | ||||
{ | ||||
// Generate the path of the game's savefolder | ||||
string exeName = Path.GetFileNameWithoutExtension( | ||||
AppDomain.CurrentDomain.FriendlyName | ||||
).Replace(".vshost", ""); | ||||
// Get the OS save folder, append the EXE name | ||||
if (OSVersion.Equals("Windows")) | ||||
{ | ||||
return Path.Combine( | ||||
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), | ||||
"SavedGames", | ||||
exeName | ||||
); | ||||
} | ||||
if (OSVersion.Equals("Mac OS X")) | ||||
{ | ||||
string osConfigDir = Environment.GetEnvironmentVariable("HOME"); | ||||
if (String.IsNullOrEmpty(osConfigDir)) | ||||
{ | ||||
return "."; // Oh well. | ||||
} | ||||
return Path.Combine( | ||||
osConfigDir, | ||||
"Library/Application Support", | ||||
exeName | ||||
); | ||||
} | ||||
if ( OSVersion.Equals("Linux") || | ||||
OSVersion.Equals("FreeBSD") || | ||||
OSVersion.Equals("OpenBSD") || | ||||
OSVersion.Equals("NetBSD") ) | ||||
{ | ||||
// Assuming a non-macOS Unix platform will follow the XDG. Which it should. | ||||
string osConfigDir = Environment.GetEnvironmentVariable("XDG_DATA_HOME"); | ||||
if (String.IsNullOrEmpty(osConfigDir)) | ||||
{ | ||||
osConfigDir = Environment.GetEnvironmentVariable("HOME"); | ||||
if (String.IsNullOrEmpty(osConfigDir)) | ||||
{ | ||||
return "."; // Oh well. | ||||
} | ||||
osConfigDir += "/.local/share"; | ||||
} | ||||
return Path.Combine(osConfigDir, exeName); | ||||
} | ||||
/* There is a minor inaccuracy here: SDL_GetPrefPath | ||||
* creates the directories right away, whereas XNA will | ||||
* only create the directory upon creating a container. | ||||
* So if you create a StorageDevice and hit a property, | ||||
* the game folder is made early! | ||||
* -flibit | ||||
*/ | ||||
return SDL.SDL_GetPrefPath(null, exeName); | ||||
} | ||||
public static DriveInfo GetDriveInfo(string storageRoot) | ||||
{ | ||||
if (OSVersion.Equals("WinRT")) | ||||
{ | ||||
// WinRT DriveInfo is a bunch of crap -flibit | ||||
return null; | ||||
} | ||||
DriveInfo result; | ||||
try | ||||
{ | ||||
result = new DriveInfo(MonoPathRootWorkaround(storageRoot)); | ||||
} | ||||
catch(Exception e) | ||||
{ | ||||
FNALoggerEXT.LogError("Failed to get DriveInfo: " + e.ToString()); | ||||
result = null; | ||||
} | ||||
return result; | ||||
} | ||||
private static string MonoPathRootWorkaround(string storageRoot) | ||||
{ | ||||
if (Environment.OSVersion.Platform == PlatformID.Win32NT) | ||||
{ | ||||
// This is what we should be doing everywhere... | ||||
return Path.GetPathRoot(storageRoot); | ||||
} | ||||
// This is stolen from Mono's Path.cs | ||||
if (storageRoot == null) | ||||
{ | ||||
return null; | ||||
} | ||||
if (storageRoot.Trim().Length == 0) | ||||
{ | ||||
throw new ArgumentException("The specified path is not of a legal form."); | ||||
} | ||||
if (!Path.IsPathRooted(storageRoot)) | ||||
{ | ||||
return string.Empty; | ||||
} | ||||
/* FIXME: Mono bug! | ||||
* | ||||
* For Unix, the Mono Path.GetPathRoot is pretty lazy: | ||||
* https://github.com/mono/mono/blob/master/mcs/class/corlib/System.IO/Path.cs#L443 | ||||
* It should actually be checking the drives and | ||||
* comparing them to the provided path. | ||||
* If a Mono maintainer is reading this, please steal | ||||
* this code so we don't have to hack around Mono! | ||||
* | ||||
* -flibit | ||||
*/ | ||||
int drive = -1, length = 0; | ||||
string[] drives = Environment.GetLogicalDrives(); | ||||
for (int i = 0; i < drives.Length; i += 1) | ||||
{ | ||||
if (string.IsNullOrEmpty(drives[i])) | ||||
{ | ||||
// ... What? | ||||
continue; | ||||
} | ||||
string name = drives[i]; | ||||
if (name[name.Length - 1] != Path.DirectorySeparatorChar) | ||||
{ | ||||
name += Path.DirectorySeparatorChar; | ||||
} | ||||
if ( storageRoot.StartsWith(name) && | ||||
name.Length > length ) | ||||
{ | ||||
drive = i; | ||||
length = name.Length; | ||||
} | ||||
} | ||||
if (drive >= 0) | ||||
{ | ||||
return drives[drive]; | ||||
} | ||||
// Uhhhhh | ||||
return Path.GetPathRoot(storageRoot); | ||||
} | ||||
#endregion | ||||
#region Logging/Messaging Methods | ||||
public static void ShowRuntimeError(string title, string message) | ||||
{ | ||||
SDL.SDL_ShowSimpleMessageBox( | ||||
SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, | ||||
title ?? "", | ||||
message ?? "", | ||||
IntPtr.Zero | ||||
); | ||||
} | ||||
#endregion | ||||
#region Image I/O Methods | ||||
private static IntPtr TextureDataFromStreamInternal( | ||||
Stream stream, | ||||
int reqWidth, | ||||
int reqHeight, | ||||
bool zoom | ||||
) { | ||||
// Load the SDL_Surface* from RWops, get the image data | ||||
IntPtr surface = SDL_image.IMG_Load_RW( | ||||
FakeRWops.Alloc(stream), | ||||
1 | ||||
); | ||||
if (surface == IntPtr.Zero) | ||||
{ | ||||
// File not found, supported, etc. | ||||
FNALoggerEXT.LogError( | ||||
"TextureDataFromStream: " + | ||||
SDL.SDL_GetError() | ||||
); | ||||
return IntPtr.Zero; | ||||
} | ||||
surface = INTERNAL_convertSurfaceFormat(surface); | ||||
// Image scaling, if applicable | ||||
if (reqWidth != -1 && reqHeight != -1) | ||||
{ | ||||
// Get the file surface dimensions now... | ||||
int rw; | ||||
int rh; | ||||
unsafe | ||||
{ | ||||
SDL.SDL_Surface* surPtr = (SDL.SDL_Surface*) surface; | ||||
rw = surPtr->w; | ||||
rh = surPtr->h; | ||||
} | ||||
// Calculate the image scale factor | ||||
bool scaleWidth; | ||||
if (zoom) | ||||
{ | ||||
scaleWidth = rw < rh; | ||||
} | ||||
else | ||||
{ | ||||
scaleWidth = rw > rh; | ||||
} | ||||
float scale; | ||||
if (scaleWidth) | ||||
{ | ||||
scale = reqWidth / (float) rw; | ||||
} | ||||
else | ||||
{ | ||||
scale = reqHeight / (float) rh; | ||||
} | ||||
// Calculate the scaled image size, crop if zoomed | ||||
int resultWidth; | ||||
int resultHeight; | ||||
SDL.SDL_Rect crop = new SDL.SDL_Rect(); | ||||
if (zoom) | ||||
{ | ||||
resultWidth = reqWidth; | ||||
resultHeight = reqHeight; | ||||
if (scaleWidth) | ||||
{ | ||||
crop.x = 0; | ||||
crop.w = rw; | ||||
crop.y = (int) (rh / 2 - (reqHeight / scale) / 2); | ||||
crop.h = (int) (reqHeight / scale); | ||||
} | ||||
else | ||||
{ | ||||
crop.y = 0; | ||||
crop.h = rh; | ||||
crop.x = (int) (rw / 2 - (reqWidth / scale) / 2); | ||||
crop.w = (int) (reqWidth / scale); | ||||
} | ||||
} | ||||
else | ||||
{ | ||||
resultWidth = (int) (rw * scale); | ||||
resultHeight = (int) (rh * scale); | ||||
} | ||||
// Alloc surface, blit! | ||||
IntPtr newSurface = SDL.SDL_CreateRGBSurface( | ||||
0, | ||||
resultWidth, | ||||
resultHeight, | ||||
32, | ||||
0x000000FF, | ||||
0x0000FF00, | ||||
0x00FF0000, | ||||
0xFF000000 | ||||
); | ||||
SDL.SDL_SetSurfaceBlendMode( | ||||
surface, | ||||
SDL.SDL_BlendMode.SDL_BLENDMODE_NONE | ||||
); | ||||
if (zoom) | ||||
{ | ||||
SDL.SDL_BlitScaled( | ||||
surface, | ||||
ref crop, | ||||
newSurface, | ||||
IntPtr.Zero | ||||
); | ||||
} | ||||
else | ||||
{ | ||||
SDL.SDL_BlitScaled( | ||||
surface, | ||||
IntPtr.Zero, | ||||
newSurface, | ||||
IntPtr.Zero | ||||
); | ||||
} | ||||
SDL.SDL_FreeSurface(surface); | ||||
surface = newSurface; | ||||
} | ||||
return surface; | ||||
} | ||||
private static unsafe void TextureDataClearAlpha( | ||||
byte* pixels, | ||||
int len | ||||
) { | ||||
/* Ensure that the alpha pixels are... well, actual alpha. | ||||
* You think this looks stupid, but be assured: Your paint program is | ||||
* almost certainly even stupider. | ||||
* -flibit | ||||
*/ | ||||
for (int i = 0; i < len; i += 4, pixels += 4) | ||||
{ | ||||
if (pixels[3] == 0) | ||||
{ | ||||
pixels[0] = 0; | ||||
pixels[1] = 0; | ||||
pixels[2] = 0; | ||||
} | ||||
} | ||||
} | ||||
public static void TextureDataFromStream( | ||||
Stream stream, | ||||
out int width, | ||||
out int height, | ||||
out byte[] pixels, | ||||
int reqWidth = -1, | ||||
int reqHeight = -1, | ||||
bool zoom = false | ||||
) { | ||||
IntPtr surface = TextureDataFromStreamInternal( | ||||
stream, | ||||
reqWidth, | ||||
reqHeight, | ||||
zoom | ||||
); | ||||
if (surface == IntPtr.Zero) | ||||
{ | ||||
width = 0; | ||||
height = 0; | ||||
pixels = null; | ||||
return; | ||||
} | ||||
// Copy surface data to output managed byte array | ||||
unsafe | ||||
{ | ||||
SDL.SDL_Surface* surPtr = (SDL.SDL_Surface*) surface; | ||||
width = surPtr->w; | ||||
height = surPtr->h; | ||||
pixels = new byte[width * height * 4]; // MUST be SurfaceFormat.Color! | ||||
Marshal.Copy(surPtr->pixels, pixels, 0, pixels.Length); | ||||
fixed (byte* pixPtr = &pixels[0]) | ||||
{ | ||||
TextureDataClearAlpha(pixPtr, pixels.Length); | ||||
} | ||||
} | ||||
SDL.SDL_FreeSurface(surface); | ||||
} | ||||
public static void TextureDataFromStreamPtr( | ||||
Stream stream, | ||||
out int width, | ||||
out int height, | ||||
out IntPtr pixels, | ||||
out int len, | ||||
int reqWidth = -1, | ||||
int reqHeight = -1, | ||||
bool zoom = false | ||||
) { | ||||
IntPtr surface = TextureDataFromStreamInternal( | ||||
stream, | ||||
reqWidth, | ||||
reqHeight, | ||||
zoom | ||||
); | ||||
if (surface == IntPtr.Zero) | ||||
{ | ||||
width = 0; | ||||
height = 0; | ||||
pixels = IntPtr.Zero; | ||||
len = 0; | ||||
return; | ||||
} | ||||
// Copy surface data to output managed byte array | ||||
unsafe | ||||
{ | ||||
SDL.SDL_Surface* surPtr = (SDL.SDL_Surface*) surface; | ||||
width = surPtr->w; | ||||
height = surPtr->h; | ||||
len = width * height * 4; | ||||
pixels = Marshal.AllocHGlobal(len); // MUST be SurfaceFormat.Color! | ||||
SDL.SDL_memcpy(pixels, surPtr->pixels, (IntPtr) len); | ||||
TextureDataClearAlpha((byte*) pixels, len); | ||||
} | ||||
SDL.SDL_FreeSurface(surface); | ||||
} | ||||
public static void SavePNG( | ||||
Stream stream, | ||||
int width, | ||||
int height, | ||||
int imgWidth, | ||||
int imgHeight, | ||||
byte[] data | ||||
) { | ||||
IntPtr surface = INTERNAL_getScaledSurface( | ||||
data, | ||||
imgWidth, | ||||
imgHeight, | ||||
width, | ||||
height | ||||
); | ||||
SDL_image.IMG_SavePNG_RW( | ||||
surface, | ||||
FakeRWops.Alloc(stream), | ||||
1 | ||||
); | ||||
SDL.SDL_FreeSurface(surface); | ||||
} | ||||
public static void SaveJPG( | ||||
Stream stream, | ||||
int width, | ||||
int height, | ||||
int imgWidth, | ||||
int imgHeight, | ||||
byte[] data | ||||
) { | ||||
// FIXME: What does XNA pick for this? -flibit | ||||
const int quality = 100; | ||||
IntPtr surface = INTERNAL_getScaledSurface( | ||||
data, | ||||
imgWidth, | ||||
imgHeight, | ||||
width, | ||||
height | ||||
); | ||||
// FIXME: Hack for Bugzilla #3972 | ||||
IntPtr temp = SDL.SDL_ConvertSurfaceFormat( | ||||
surface, | ||||
SDL.SDL_PIXELFORMAT_RGB24, | ||||
0 | ||||
); | ||||
SDL.SDL_FreeSurface(surface); | ||||
surface = temp; | ||||
SDL_image.IMG_SaveJPG_RW( | ||||
surface, | ||||
FakeRWops.Alloc(stream), | ||||
1, | ||||
quality | ||||
); | ||||
SDL.SDL_FreeSurface(surface); | ||||
} | ||||
public static IntPtr INTERNAL_getScaledSurface( | ||||
byte[] data, | ||||
int srcW, | ||||
int srcH, | ||||
int dstW, | ||||
int dstH | ||||
) { | ||||
// Create an SDL_Surface*, write the pixel data | ||||
IntPtr surface = SDL.SDL_CreateRGBSurface( | ||||
0, | ||||
srcW, | ||||
srcH, | ||||
32, | ||||
0x000000FF, | ||||
0x0000FF00, | ||||
0x00FF0000, | ||||
0xFF000000 | ||||
); | ||||
SDL.SDL_LockSurface(surface); | ||||
unsafe | ||||
{ | ||||
SDL.SDL_Surface* surPtr = (SDL.SDL_Surface*) surface; | ||||
Marshal.Copy( | ||||
data, | ||||
0, | ||||
surPtr->pixels, | ||||
data.Length | ||||
); | ||||
} | ||||
SDL.SDL_UnlockSurface(surface); | ||||
// Blit to a scaled surface of the size we want, if needed. | ||||
if (srcW != dstW || srcH != dstH) | ||||
{ | ||||
IntPtr scaledSurface = SDL.SDL_CreateRGBSurface( | ||||
0, | ||||
dstW, | ||||
dstH, | ||||
32, | ||||
0x000000FF, | ||||
0x0000FF00, | ||||
0x00FF0000, | ||||
0xFF000000 | ||||
); | ||||
SDL.SDL_SetSurfaceBlendMode( | ||||
surface, | ||||
SDL.SDL_BlendMode.SDL_BLENDMODE_NONE | ||||
); | ||||
SDL.SDL_BlitScaled( | ||||
surface, | ||||
IntPtr.Zero, | ||||
scaledSurface, | ||||
IntPtr.Zero | ||||
); | ||||
SDL.SDL_FreeSurface(surface); | ||||
surface = scaledSurface; | ||||
} | ||||
return surface; | ||||
} | ||||
private static unsafe IntPtr INTERNAL_convertSurfaceFormat(IntPtr surface) | ||||
{ | ||||
IntPtr result = surface; | ||||
unsafe | ||||
{ | ||||
SDL.SDL_Surface* surPtr = (SDL.SDL_Surface*) surface; | ||||
SDL.SDL_PixelFormat* pixelFormatPtr = (SDL.SDL_PixelFormat*) surPtr->format; | ||||
// SurfaceFormat.Color is SDL_PIXELFORMAT_ABGR8888 | ||||
if (pixelFormatPtr->format != SDL.SDL_PIXELFORMAT_ABGR8888) | ||||
{ | ||||
// Create a properly formatted copy, free the old surface | ||||
result = SDL.SDL_ConvertSurfaceFormat(surface, SDL.SDL_PIXELFORMAT_ABGR8888, 0); | ||||
SDL.SDL_FreeSurface(surface); | ||||
} | ||||
} | ||||
return result; | ||||
} | ||||
private static class FakeRWops | ||||
{ | ||||
private static readonly Dictionary<IntPtr, Stream> streamMap = | ||||
new Dictionary<IntPtr, Stream>(); | ||||
// Based on PNG_ZBUF_SIZE default | ||||
private static byte[] temp = new byte[8192]; | ||||
private static readonly SDL.SDLRWopsSizeCallback sizeFunc = size; | ||||
private static readonly SDL.SDLRWopsSeekCallback seekFunc = seek; | ||||
private static readonly SDL.SDLRWopsReadCallback readFunc = read; | ||||
private static readonly SDL.SDLRWopsWriteCallback writeFunc = write; | ||||
private static readonly SDL.SDLRWopsCloseCallback closeFunc = close; | ||||
private static readonly IntPtr sizePtr = | ||||
Marshal.GetFunctionPointerForDelegate(sizeFunc); | ||||
private static readonly IntPtr seekPtr = | ||||
Marshal.GetFunctionPointerForDelegate(seekFunc); | ||||
private static readonly IntPtr readPtr = | ||||
Marshal.GetFunctionPointerForDelegate(readFunc); | ||||
private static readonly IntPtr writePtr = | ||||
Marshal.GetFunctionPointerForDelegate(writeFunc); | ||||
private static readonly IntPtr closePtr = | ||||
Marshal.GetFunctionPointerForDelegate(closeFunc); | ||||
public static IntPtr Alloc(Stream stream) | ||||
{ | ||||
IntPtr rwops = SDL.SDL_AllocRW(); | ||||
unsafe | ||||
{ | ||||
SDL.SDL_RWops* p = (SDL.SDL_RWops*) rwops; | ||||
p->size = sizePtr; | ||||
p->seek = seekPtr; | ||||
p->read = readPtr; | ||||
p->write = writePtr; | ||||
p->close = closePtr; | ||||
} | ||||
lock (streamMap) | ||||
{ | ||||
streamMap.Add(rwops, stream); | ||||
} | ||||
return rwops; | ||||
} | ||||
private static byte[] GetTemp(int len) | ||||
{ | ||||
if (len > temp.Length) | ||||
{ | ||||
temp = new byte[len]; | ||||
} | ||||
return temp; | ||||
} | ||||
[ObjCRuntime.MonoPInvokeCallback(typeof(SDL.SDLRWopsSizeCallback))] | ||||
private static long size(IntPtr context) | ||||
{ | ||||
Stream stream; | ||||
lock (streamMap) | ||||
{ | ||||
stream = streamMap[context]; | ||||
} | ||||
return stream.Length; | ||||
} | ||||
[ObjCRuntime.MonoPInvokeCallback(typeof(SDL.SDLRWopsSeekCallback))] | ||||
private static long seek(IntPtr context, long offset, int whence) | ||||
{ | ||||
Stream stream; | ||||
lock (streamMap) | ||||
{ | ||||
stream = streamMap[context]; | ||||
} | ||||
stream.Seek(offset, (SeekOrigin) whence); | ||||
return stream.Position; | ||||
} | ||||
[ObjCRuntime.MonoPInvokeCallback(typeof(SDL.SDLRWopsReadCallback))] | ||||
private static IntPtr read( | ||||
IntPtr context, | ||||
IntPtr ptr, | ||||
IntPtr size, | ||||
IntPtr maxnum | ||||
) { | ||||
Stream stream; | ||||
int len = size.ToInt32() * maxnum.ToInt32(); | ||||
lock (streamMap) | ||||
{ | ||||
stream = streamMap[context]; | ||||
// Other streams may contend for temp! | ||||
len = stream.Read( | ||||
GetTemp(len), | ||||
0, | ||||
len | ||||
); | ||||
Marshal.Copy(temp, 0, ptr, len); | ||||
} | ||||
return (IntPtr) len; | ||||
} | ||||
[ObjCRuntime.MonoPInvokeCallback(typeof(SDL.SDLRWopsWriteCallback))] | ||||
private static IntPtr write( | ||||
IntPtr context, | ||||
IntPtr ptr, | ||||
IntPtr size, | ||||
IntPtr num | ||||
) { | ||||
Stream stream; | ||||
int len = size.ToInt32() * num.ToInt32(); | ||||
lock (streamMap) | ||||
{ | ||||
stream = streamMap[context]; | ||||
// Other streams may contend for temp! | ||||
Marshal.Copy( | ||||
ptr, | ||||
GetTemp(len), | ||||
0, | ||||
len | ||||
); | ||||
stream.Write(temp, 0, len); | ||||
} | ||||
return (IntPtr) len; | ||||
} | ||||
[ObjCRuntime.MonoPInvokeCallback(typeof(SDL.SDLRWopsCloseCallback))] | ||||
public static int close(IntPtr context) | ||||
{ | ||||
lock (streamMap) | ||||
{ | ||||
streamMap.Remove(context); | ||||
} | ||||
SDL.SDL_FreeRW(context); | ||||
return 0; | ||||
} | ||||
} | ||||
#endregion | ||||
#region Microphone Implementation | ||||
/* Microphone is almost never used, so we give this subsystem | ||||
* special treatment and init only when we start calling these | ||||
* functions. | ||||
* -flibit | ||||
*/ | ||||
private static bool micInit = false; | ||||
public static Microphone[] GetMicrophones() | ||||
{ | ||||
// Init subsystem if needed | ||||
if (!micInit) | ||||
{ | ||||
SDL.SDL_InitSubSystem(SDL.SDL_INIT_AUDIO); | ||||
micInit = true; | ||||
} | ||||
// How many devices do we have...? | ||||
int numDev = SDL.SDL_GetNumAudioDevices(1); | ||||
if (numDev < 1) | ||||
{ | ||||
// Blech | ||||
return new Microphone[0]; | ||||
} | ||||
Microphone[] result = new Microphone[numDev + 1]; | ||||
// Default input format | ||||
SDL.SDL_AudioSpec have; | ||||
SDL.SDL_AudioSpec want = new SDL.SDL_AudioSpec(); | ||||
want.freq = Microphone.SAMPLERATE; | ||||
want.format = SDL.AUDIO_S16; | ||||
want.channels = 1; | ||||
want.samples = 4096; /* FIXME: Anything specific? */ | ||||
// First mic is always OS default | ||||
result[0] = new Microphone( | ||||
SDL.SDL_OpenAudioDevice( | ||||
null, | ||||
1, | ||||
ref want, | ||||
out have, | ||||
0 | ||||
), | ||||
"Default Device" | ||||
); | ||||
for (int i = 0; i < numDev; i += 1) | ||||
{ | ||||
string name = SDL.SDL_GetAudioDeviceName(i, 1); | ||||
result[i + 1] = new Microphone( | ||||
SDL.SDL_OpenAudioDevice( | ||||
name, | ||||
1, | ||||
ref want, | ||||
out have, | ||||
0 | ||||
), | ||||
name | ||||
); | ||||
} | ||||
return result; | ||||
} | ||||
public static unsafe int GetMicrophoneSamples( | ||||
uint handle, | ||||
byte[] buffer, | ||||
int offset, | ||||
int count | ||||
) { | ||||
fixed (byte* ptr = &buffer[offset]) | ||||
{ | ||||
return (int) SDL.SDL_DequeueAudio( | ||||
handle, | ||||
(IntPtr) ptr, | ||||
(uint) count | ||||
); | ||||
} | ||||
} | ||||
public static int GetMicrophoneQueuedBytes(uint handle) | ||||
{ | ||||
return (int) SDL.SDL_GetQueuedAudioSize(handle); | ||||
} | ||||
public static void StartMicrophone(uint handle) | ||||
{ | ||||
SDL.SDL_PauseAudioDevice(handle, 0); | ||||
} | ||||
public static void StopMicrophone(uint handle) | ||||
{ | ||||
SDL.SDL_PauseAudioDevice(handle, 1); | ||||
} | ||||
#endregion | ||||
#region GamePad Backend | ||||
// Controller device information | ||||
private static IntPtr[] INTERNAL_devices = new IntPtr[GamePad.GAMEPAD_COUNT]; | ||||
private static Dictionary<int, int> INTERNAL_instanceList = new Dictionary<int, int>(); | ||||
private static string[] INTERNAL_guids = GenStringArray(); | ||||
// Light bar information | ||||
private static string[] INTERNAL_lightBars = GenStringArray(); | ||||
// Cached GamePadStates/Capabilities | ||||
private static GamePadState[] INTERNAL_states = new GamePadState[GamePad.GAMEPAD_COUNT]; | ||||
private static GamePadCapabilities[] INTERNAL_capabilities = new GamePadCapabilities[GamePad.GAMEPAD_COUNT]; | ||||
private static readonly GamePadType[] INTERNAL_gamepadType = new GamePadType[] | ||||
{ | ||||
GamePadType.Unknown, | ||||
GamePadType.GamePad, | ||||
GamePadType.Wheel, | ||||
GamePadType.ArcadeStick, | ||||
GamePadType.FlightStick, | ||||
GamePadType.DancePad, | ||||
GamePadType.Guitar, | ||||
GamePadType.DrumKit, | ||||
GamePadType.BigButtonPad | ||||
}; | ||||
public static GamePadCapabilities GetGamePadCapabilities(int index) | ||||
{ | ||||
if (INTERNAL_devices[index] == IntPtr.Zero) | ||||
{ | ||||
return new GamePadCapabilities(); | ||||
} | ||||
return INTERNAL_capabilities[index]; | ||||
} | ||||
public static GamePadState GetGamePadState(int index, GamePadDeadZone deadZoneMode) | ||||
{ | ||||
IntPtr device = INTERNAL_devices[index]; | ||||
if (device == IntPtr.Zero) | ||||
{ | ||||
return new GamePadState(); | ||||
} | ||||
// The "master" button state is built from this. | ||||
Buttons gc_buttonState = (Buttons) 0; | ||||
// Sticks | ||||
Vector2 stickLeft = new Vector2( | ||||
(float) SDL.SDL_GameControllerGetAxis( | ||||
device, | ||||
SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX | ||||
) / 32767.0f, | ||||
(float) SDL.SDL_GameControllerGetAxis( | ||||
device, | ||||
SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY | ||||
) / -32767.0f | ||||
); | ||||
Vector2 stickRight = new Vector2( | ||||
(float) SDL.SDL_GameControllerGetAxis( | ||||
device, | ||||
SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTX | ||||
) / 32767.0f, | ||||
(float) SDL.SDL_GameControllerGetAxis( | ||||
device, | ||||
SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTY | ||||
) / -32767.0f | ||||
); | ||||
gc_buttonState |= READ_StickToButtons( | ||||
stickLeft, | ||||
Buttons.LeftThumbstickLeft, | ||||
Buttons.LeftThumbstickRight, | ||||
Buttons.LeftThumbstickUp, | ||||
Buttons.LeftThumbstickDown, | ||||
GamePad.LeftDeadZone | ||||
); | ||||
gc_buttonState |= READ_StickToButtons( | ||||
stickRight, | ||||
Buttons.RightThumbstickLeft, | ||||
Buttons.RightThumbstickRight, | ||||
Buttons.RightThumbstickUp, | ||||
Buttons.RightThumbstickDown, | ||||
GamePad.RightDeadZone | ||||
); | ||||
// Triggers | ||||
float triggerLeft = (float) SDL.SDL_GameControllerGetAxis( | ||||
device, | ||||
SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT | ||||
) / 32767.0f; | ||||
float triggerRight = (float) SDL.SDL_GameControllerGetAxis( | ||||
device, | ||||
SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT | ||||
) / 32767.0f; | ||||
if (triggerLeft > GamePad.TriggerThreshold) | ||||
{ | ||||
gc_buttonState |= Buttons.LeftTrigger; | ||||
} | ||||
if (triggerRight > GamePad.TriggerThreshold) | ||||
{ | ||||
gc_buttonState |= Buttons.RightTrigger; | ||||
} | ||||
// Buttons | ||||
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A) != 0) | ||||
{ | ||||
gc_buttonState |= Buttons.A; | ||||
} | ||||
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B) != 0) | ||||
{ | ||||
gc_buttonState |= Buttons.B; | ||||
} | ||||
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X) != 0) | ||||
{ | ||||
gc_buttonState |= Buttons.X; | ||||
} | ||||
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y) != 0) | ||||
{ | ||||
gc_buttonState |= Buttons.Y; | ||||
} | ||||
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK) != 0) | ||||
{ | ||||
gc_buttonState |= Buttons.Back; | ||||
} | ||||
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_GUIDE) != 0) | ||||
{ | ||||
gc_buttonState |= Buttons.BigButton; | ||||
} | ||||
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START) != 0) | ||||
{ | ||||
gc_buttonState |= Buttons.Start; | ||||
} | ||||
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK) != 0) | ||||
{ | ||||
gc_buttonState |= Buttons.LeftStick; | ||||
} | ||||
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSTICK) != 0) | ||||
{ | ||||
gc_buttonState |= Buttons.RightStick; | ||||
} | ||||
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER) != 0) | ||||
{ | ||||
gc_buttonState |= Buttons.LeftShoulder; | ||||
} | ||||
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) != 0) | ||||
{ | ||||
gc_buttonState |= Buttons.RightShoulder; | ||||
} | ||||
// DPad | ||||
ButtonState dpadUp = ButtonState.Released; | ||||
ButtonState dpadDown = ButtonState.Released; | ||||
ButtonState dpadLeft = ButtonState.Released; | ||||
ButtonState dpadRight = ButtonState.Released; | ||||
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP) != 0) | ||||
{ | ||||
gc_buttonState |= Buttons.DPadUp; | ||||
dpadUp = ButtonState.Pressed; | ||||
} | ||||
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN) != 0) | ||||
{ | ||||
gc_buttonState |= Buttons.DPadDown; | ||||
dpadDown = ButtonState.Pressed; | ||||
} | ||||
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT) != 0) | ||||
{ | ||||
gc_buttonState |= Buttons.DPadLeft; | ||||
dpadLeft = ButtonState.Pressed; | ||||
} | ||||
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT) != 0) | ||||
{ | ||||
gc_buttonState |= Buttons.DPadRight; | ||||
dpadRight = ButtonState.Pressed; | ||||
} | ||||
// Build the GamePadState, increment PacketNumber if state changed. | ||||
GamePadState gc_builtState = new GamePadState( | ||||
new GamePadThumbSticks(stickLeft, stickRight, deadZoneMode), | ||||
new GamePadTriggers(triggerLeft, triggerRight, deadZoneMode), | ||||
new GamePadButtons(gc_buttonState), | ||||
new GamePadDPad(dpadUp, dpadDown, dpadLeft, dpadRight) | ||||
); | ||||
gc_builtState.IsConnected = true; | ||||
gc_builtState.PacketNumber = INTERNAL_states[index].PacketNumber; | ||||
if (gc_builtState != INTERNAL_states[index]) | ||||
{ | ||||
gc_builtState.PacketNumber += 1; | ||||
INTERNAL_states[index] = gc_builtState; | ||||
} | ||||
return gc_builtState; | ||||
} | ||||
public static bool SetGamePadVibration(int index, float leftMotor, float rightMotor) | ||||
{ | ||||
IntPtr device = INTERNAL_devices[index]; | ||||
if (device == IntPtr.Zero) | ||||
{ | ||||
return false; | ||||
} | ||||
return SDL.SDL_GameControllerRumble( | ||||
device, | ||||
(ushort) (MathHelper.Clamp(leftMotor, 0.0f, 1.0f) * 0xFFFF), | ||||
(ushort) (MathHelper.Clamp(rightMotor, 0.0f, 1.0f) * 0xFFFF), | ||||
0 | ||||
) == 0; | ||||
} | ||||
public static string GetGamePadGUID(int index) | ||||
{ | ||||
return INTERNAL_guids[index]; | ||||
} | ||||
public static void SetGamePadLightBar(int index, Color color) | ||||
{ | ||||
if (String.IsNullOrEmpty(INTERNAL_lightBars[index])) | ||||
{ | ||||
return; | ||||
} | ||||
string baseDir = INTERNAL_lightBars[index]; | ||||
try | ||||
{ | ||||
File.WriteAllText(baseDir + "red/brightness", color.R.ToString()); | ||||
File.WriteAllText(baseDir + "green/brightness", color.G.ToString()); | ||||
File.WriteAllText(baseDir + "blue/brightness", color.B.ToString()); | ||||
} | ||||
catch | ||||
{ | ||||
// If something went wrong, assume the worst and just remove it. | ||||
INTERNAL_lightBars[index] = String.Empty; | ||||
} | ||||
} | ||||
private static void INTERNAL_AddInstance(int dev) | ||||
{ | ||||
int which = -1; | ||||
for (int i = 0; i < INTERNAL_devices.Length; i += 1) | ||||
{ | ||||
if (INTERNAL_devices[i] == IntPtr.Zero) | ||||
{ | ||||
which = i; | ||||
break; | ||||
} | ||||
} | ||||
if (which == -1) | ||||
{ | ||||
return; // Ignoring more than 4 controllers. | ||||
} | ||||
// Clear the error buffer. We're about to do a LOT of dangerous stuff. | ||||
SDL.SDL_ClearError(); | ||||
// Open the device! | ||||
INTERNAL_devices[which] = SDL.SDL_GameControllerOpen(dev); | ||||
// We use this when dealing with GUID initialization. | ||||
IntPtr thisJoystick = SDL.SDL_GameControllerGetJoystick(INTERNAL_devices[which]); | ||||
// Pair up the instance ID to the player index. | ||||
// FIXME: Remove check after 2.0.4? -flibit | ||||
int thisInstance = SDL.SDL_JoystickInstanceID(thisJoystick); | ||||
if (INTERNAL_instanceList.ContainsKey(thisInstance)) | ||||
{ | ||||
// Duplicate? Usually this is OSX being dumb, but...? | ||||
INTERNAL_devices[which] = IntPtr.Zero; | ||||
return; | ||||
} | ||||
INTERNAL_instanceList.Add(thisInstance, which); | ||||
// Start with a fresh state. | ||||
INTERNAL_states[which] = new GamePadState(); | ||||
INTERNAL_states[which].IsConnected = true; | ||||
// Initialize the haptics for the joystick, if applicable. | ||||
bool hasRumble = SDL.SDL_GameControllerRumble( | ||||
INTERNAL_devices[which], | ||||
0, | ||||
0, | ||||
0 | ||||
) == 0; | ||||
// An SDL_GameController _should_ always be complete... | ||||
GamePadCapabilities caps = new GamePadCapabilities(); | ||||
caps.IsConnected = true; | ||||
caps.GamePadType = INTERNAL_gamepadType[(int) SDL.SDL_JoystickGetType(thisJoystick)]; | ||||
caps.HasAButton = SDL.SDL_GameControllerGetBindForButton( | ||||
INTERNAL_devices[which], | ||||
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A | ||||
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE; | ||||
caps.HasBButton = SDL.SDL_GameControllerGetBindForButton( | ||||
INTERNAL_devices[which], | ||||
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B | ||||
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE; | ||||
caps.HasXButton = SDL.SDL_GameControllerGetBindForButton( | ||||
INTERNAL_devices[which], | ||||
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X | ||||
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE; | ||||
caps.HasYButton = SDL.SDL_GameControllerGetBindForButton( | ||||
INTERNAL_devices[which], | ||||
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y | ||||
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE; | ||||
caps.HasBackButton = SDL.SDL_GameControllerGetBindForButton( | ||||
INTERNAL_devices[which], | ||||
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK | ||||
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE; | ||||
caps.HasBigButton = SDL.SDL_GameControllerGetBindForButton( | ||||
INTERNAL_devices[which], | ||||
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_GUIDE | ||||
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE; | ||||
caps.HasStartButton = SDL.SDL_GameControllerGetBindForButton( | ||||
INTERNAL_devices[which], | ||||
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START | ||||
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE; | ||||
caps.HasLeftStickButton = SDL.SDL_GameControllerGetBindForButton( | ||||
INTERNAL_devices[which], | ||||
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK | ||||
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE; | ||||
caps.HasRightStickButton = SDL.SDL_GameControllerGetBindForButton( | ||||
INTERNAL_devices[which], | ||||
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSTICK | ||||
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE; | ||||
caps.HasLeftShoulderButton = SDL.SDL_GameControllerGetBindForButton( | ||||
INTERNAL_devices[which], | ||||
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER | ||||
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE; | ||||
caps.HasRightShoulderButton = SDL.SDL_GameControllerGetBindForButton( | ||||
INTERNAL_devices[which], | ||||
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER | ||||
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE; | ||||
caps.HasDPadUpButton = SDL.SDL_GameControllerGetBindForButton( | ||||
INTERNAL_devices[which], | ||||
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP | ||||
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE; | ||||
caps.HasDPadDownButton = SDL.SDL_GameControllerGetBindForButton( | ||||
INTERNAL_devices[which], | ||||
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN | ||||
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE; | ||||
caps.HasDPadLeftButton = SDL.SDL_GameControllerGetBindForButton( | ||||
INTERNAL_devices[which], | ||||
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT | ||||
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE; | ||||
caps.HasDPadRightButton = SDL.SDL_GameControllerGetBindForButton( | ||||
INTERNAL_devices[which], | ||||
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT | ||||
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE; | ||||
caps.HasLeftXThumbStick = SDL.SDL_GameControllerGetBindForAxis( | ||||
INTERNAL_devices[which], | ||||
SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX | ||||
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE; | ||||
caps.HasLeftYThumbStick = SDL.SDL_GameControllerGetBindForAxis( | ||||
INTERNAL_devices[which], | ||||
SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY | ||||
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE; | ||||
caps.HasRightXThumbStick = SDL.SDL_GameControllerGetBindForAxis( | ||||
INTERNAL_devices[which], | ||||
SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTX | ||||
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE; | ||||
caps.HasRightYThumbStick = SDL.SDL_GameControllerGetBindForAxis( | ||||
INTERNAL_devices[which], | ||||
SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTY | ||||
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE; | ||||
caps.HasLeftTrigger = SDL.SDL_GameControllerGetBindForAxis( | ||||
INTERNAL_devices[which], | ||||
SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT | ||||
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE; | ||||
caps.HasRightTrigger = SDL.SDL_GameControllerGetBindForAxis( | ||||
INTERNAL_devices[which], | ||||
SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT | ||||
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE; | ||||
caps.HasLeftVibrationMotor = hasRumble; | ||||
caps.HasRightVibrationMotor = hasRumble; | ||||
caps.HasVoiceSupport = false; | ||||
INTERNAL_capabilities[which] = caps; | ||||
/* Store the GUID string for this device | ||||
* FIXME: Replace GetGUIDEXT string with 3 short values -flibit | ||||
*/ | ||||
ushort vendor = SDL.SDL_JoystickGetVendor(thisJoystick); | ||||
ushort product = SDL.SDL_JoystickGetProduct(thisJoystick); | ||||
if (vendor == 0x00 && product == 0x00) | ||||
{ | ||||
INTERNAL_guids[which] = "xinput"; | ||||
} | ||||
else | ||||
{ | ||||
INTERNAL_guids[which] = string.Format( | ||||
"{0:x2}{1:x2}{2:x2}{3:x2}", | ||||
vendor & 0xFF, | ||||
vendor >> 8, | ||||
product & 0xFF, | ||||
product >> 8 | ||||
); | ||||
} | ||||
// Initialize light bar | ||||
if ( OSVersion.Equals("Linux") && | ||||
( INTERNAL_guids[which].Equals("4c05c405") || | ||||
INTERNAL_guids[which].Equals("4c05cc09") ) ) | ||||
{ | ||||
// Get all of the individual PS4 LED instances | ||||
List<string> ledList = new List<string>(); | ||||
string[] dirs = Directory.GetDirectories("/sys/class/leds/"); | ||||
foreach (string dir in dirs) | ||||
{ | ||||
if ( dir.EndsWith("blue") && | ||||
( dir.Contains("054C:05C4") || | ||||
dir.Contains("054C:09CC") ) ) | ||||
{ | ||||
ledList.Add(dir.Substring(0, dir.LastIndexOf(':') + 1)); | ||||
} | ||||
} | ||||
// Find how many of these are already in use | ||||
int numLights = 0; | ||||
for (int i = 0; i < INTERNAL_lightBars.Length; i += 1) | ||||
{ | ||||
if (!String.IsNullOrEmpty(INTERNAL_lightBars[i])) | ||||
{ | ||||
numLights += 1; | ||||
} | ||||
} | ||||
// If all are not already in use, use the first unused light | ||||
if (numLights < ledList.Count) | ||||
{ | ||||
INTERNAL_lightBars[which] = ledList[numLights]; | ||||
} | ||||
} | ||||
// Print controller information to stdout. | ||||
FNALoggerEXT.LogInfo( | ||||
"Controller " + which.ToString() + ": " + | ||||
SDL.SDL_GameControllerName(INTERNAL_devices[which]) | ||||
); | ||||
} | ||||
private static void INTERNAL_RemoveInstance(int dev) | ||||
{ | ||||
int output; | ||||
if (!INTERNAL_instanceList.TryGetValue(dev, out output)) | ||||
{ | ||||
// Odds are, this is controller 5+ getting removed. | ||||
return; | ||||
} | ||||
INTERNAL_instanceList.Remove(dev); | ||||
SDL.SDL_GameControllerClose(INTERNAL_devices[output]); | ||||
INTERNAL_devices[output] = IntPtr.Zero; | ||||
INTERNAL_states[output] = new GamePadState(); | ||||
INTERNAL_guids[output] = String.Empty; | ||||
// A lot of errors can happen here, but honestly, they can be ignored... | ||||
SDL.SDL_ClearError(); | ||||
FNALoggerEXT.LogInfo("Removed device, player: " + output.ToString()); | ||||
} | ||||
// GetState can convert stick values to button values | ||||
private static Buttons READ_StickToButtons(Vector2 stick, Buttons left, Buttons right, Buttons up , Buttons down, float DeadZoneSize) | ||||
{ | ||||
Buttons b = (Buttons) 0; | ||||
if (stick.X > DeadZoneSize) | ||||
{ | ||||
b |= right; | ||||
} | ||||
if (stick.X < -DeadZoneSize) | ||||
{ | ||||
b |= left; | ||||
} | ||||
if (stick.Y > DeadZoneSize) | ||||
{ | ||||
b |= up; | ||||
} | ||||
if (stick.Y < -DeadZoneSize) | ||||
{ | ||||
b |= down; | ||||
} | ||||
return b; | ||||
} | ||||
private static string[] GenStringArray() | ||||
{ | ||||
string[] result = new string[GamePad.GAMEPAD_COUNT]; | ||||
for (int i = 0; i < result.Length; i += 1) | ||||
{ | ||||
result[i] = String.Empty; | ||||
} | ||||
return result; | ||||
} | ||||
#endregion | ||||
#region Touch Methods | ||||
public static TouchPanelCapabilities GetTouchCapabilities() | ||||
{ | ||||
/* Take these reported capabilities with a grain of salt. | ||||
* On Windows, touch devices won't be detected until they | ||||
* are interacted with. Also, MaximumTouchCount is completely | ||||
* bogus. For any touch device, XNA always reports 4. | ||||
* | ||||
* -caleb | ||||
*/ | ||||
bool touchDeviceExists = SDL.SDL_GetNumTouchDevices() > 0; | ||||
return new TouchPanelCapabilities( | ||||
touchDeviceExists, | ||||
touchDeviceExists ? 4 : 0 | ||||
); | ||||
} | ||||
public static unsafe void UpdateTouchPanelState() | ||||
{ | ||||
// Poll the touch device for all active fingers | ||||
long touchDevice = SDL.SDL_GetTouchDevice(0); | ||||
for (int i = 0; i < TouchPanel.MAX_TOUCHES; i += 1) | ||||
{ | ||||
SDL.SDL_Finger* finger = (SDL.SDL_Finger*) SDL.SDL_GetTouchFinger(touchDevice, i); | ||||
if (finger == null) | ||||
{ | ||||
// No finger found at this index | ||||
TouchPanel.SetFinger(i, TouchPanel.NO_FINGER, Vector2.Zero); | ||||
continue; | ||||
} | ||||
// Send the finger data to the TouchPanel | ||||
TouchPanel.SetFinger( | ||||
i, | ||||
(int) finger->id, | ||||
new Vector2( | ||||
(float) Math.Round(finger->x * TouchPanel.DisplayWidth), | ||||
(float) Math.Round(finger->y * TouchPanel.DisplayHeight) | ||||
) | ||||
); | ||||
} | ||||
} | ||||
public static int GetNumTouchFingers() | ||||
{ | ||||
return SDL.SDL_GetNumTouchFingers( | ||||
SDL.SDL_GetTouchDevice(0) | ||||
); | ||||
} | ||||
#endregion | ||||
#region SDL2<->XNA Key Conversion Methods | ||||
/* From: http://blogs.msdn.com/b/shawnhar/archive/2007/07/02/twin-paths-to-garbage-collector-nirvana.aspx | ||||
* "If you use an enum type as a dictionary key, internal dictionary operations will cause boxing. | ||||
* You can avoid this by using integer keys, and casting your enum values to ints before adding | ||||
* them to the dictionary." | ||||
*/ | ||||
private static Dictionary<int, Keys> INTERNAL_keyMap = new Dictionary<int, Keys>() | ||||
{ | ||||
{ (int) SDL.SDL_Keycode.SDLK_a, Keys.A }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_b, Keys.B }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_c, Keys.C }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_d, Keys.D }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_e, Keys.E }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_f, Keys.F }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_g, Keys.G }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_h, Keys.H }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_i, Keys.I }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_j, Keys.J }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_k, Keys.K }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_l, Keys.L }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_m, Keys.M }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_n, Keys.N }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_o, Keys.O }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_p, Keys.P }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_q, Keys.Q }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_r, Keys.R }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_s, Keys.S }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_t, Keys.T }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_u, Keys.U }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_v, Keys.V }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_w, Keys.W }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_x, Keys.X }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_y, Keys.Y }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_z, Keys.Z }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_0, Keys.D0 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_1, Keys.D1 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_2, Keys.D2 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_3, Keys.D3 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_4, Keys.D4 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_5, Keys.D5 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_6, Keys.D6 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_7, Keys.D7 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_8, Keys.D8 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_9, Keys.D9 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_KP_0, Keys.NumPad0 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_KP_1, Keys.NumPad1 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_KP_2, Keys.NumPad2 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_KP_3, Keys.NumPad3 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_KP_4, Keys.NumPad4 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_KP_5, Keys.NumPad5 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_KP_6, Keys.NumPad6 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_KP_7, Keys.NumPad7 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_KP_8, Keys.NumPad8 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_KP_9, Keys.NumPad9 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_KP_CLEAR, Keys.OemClear }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_KP_DECIMAL, Keys.Decimal }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_KP_DIVIDE, Keys.Divide }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_KP_ENTER, Keys.Enter }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_KP_MINUS, Keys.Subtract }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_KP_MULTIPLY, Keys.Multiply }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_KP_PERIOD, Keys.OemPeriod }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_KP_PLUS, Keys.Add }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_F1, Keys.F1 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_F2, Keys.F2 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_F3, Keys.F3 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_F4, Keys.F4 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_F5, Keys.F5 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_F6, Keys.F6 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_F7, Keys.F7 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_F8, Keys.F8 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_F9, Keys.F9 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_F10, Keys.F10 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_F11, Keys.F11 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_F12, Keys.F12 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_F13, Keys.F13 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_F14, Keys.F14 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_F15, Keys.F15 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_F16, Keys.F16 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_F17, Keys.F17 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_F18, Keys.F18 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_F19, Keys.F19 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_F20, Keys.F20 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_F21, Keys.F21 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_F22, Keys.F22 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_F23, Keys.F23 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_F24, Keys.F24 }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_SPACE, Keys.Space }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_UP, Keys.Up }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_DOWN, Keys.Down }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_LEFT, Keys.Left }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_RIGHT, Keys.Right }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_LALT, Keys.LeftAlt }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_RALT, Keys.RightAlt }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_LCTRL, Keys.LeftControl }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_RCTRL, Keys.RightControl }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_LGUI, Keys.LeftWindows }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_RGUI, Keys.RightWindows }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_LSHIFT, Keys.LeftShift }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_RSHIFT, Keys.RightShift }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_APPLICATION, Keys.Apps }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_SLASH, Keys.OemQuestion }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_BACKSLASH, Keys.OemBackslash }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_LEFTBRACKET, Keys.OemOpenBrackets }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_RIGHTBRACKET, Keys.OemCloseBrackets }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_CAPSLOCK, Keys.CapsLock }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_COMMA, Keys.OemComma }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_DELETE, Keys.Delete }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_END, Keys.End }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_BACKSPACE, Keys.Back }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_RETURN, Keys.Enter }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_ESCAPE, Keys.Escape }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_HOME, Keys.Home }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_INSERT, Keys.Insert }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_MINUS, Keys.OemMinus }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_NUMLOCKCLEAR, Keys.NumLock }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_PAGEUP, Keys.PageUp }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_PAGEDOWN, Keys.PageDown }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_PAUSE, Keys.Pause }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_PERIOD, Keys.OemPeriod }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_EQUALS, Keys.OemPlus }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_PRINTSCREEN, Keys.PrintScreen }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_QUOTE, Keys.OemQuotes }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_SCROLLLOCK, Keys.Scroll }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_SEMICOLON, Keys.OemSemicolon }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_SLEEP, Keys.Sleep }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_TAB, Keys.Tab }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_BACKQUOTE, Keys.OemTilde }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_VOLUMEUP, Keys.VolumeUp }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_VOLUMEDOWN, Keys.VolumeDown }, | ||||
{ '²' /* FIXME: AZERTY SDL2? -flibit */, Keys.OemTilde }, | ||||
{ 'é' /* FIXME: BEPO SDL2? -flibit */, Keys.None }, | ||||
{ '|' /* FIXME: Norwegian SDL2? -flibit */, Keys.OemPipe }, | ||||
{ '+' /* FIXME: Norwegian SDL2? -flibit */, Keys.OemPlus }, | ||||
{ 'ø' /* FIXME: Norwegian SDL2? -flibit */, Keys.OemSemicolon }, | ||||
{ 'æ' /* FIXME: Norwegian SDL2? -flibit */, Keys.OemQuotes }, | ||||
{ (int) SDL.SDL_Keycode.SDLK_UNKNOWN, Keys.None } | ||||
}; | ||||
private static Dictionary<int, Keys> INTERNAL_scanMap = new Dictionary<int, Keys>() | ||||
{ | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_A, Keys.A }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_B, Keys.B }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_C, Keys.C }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_D, Keys.D }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_E, Keys.E }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F, Keys.F }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_G, Keys.G }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_H, Keys.H }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_I, Keys.I }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_J, Keys.J }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_K, Keys.K }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_L, Keys.L }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_M, Keys.M }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_N, Keys.N }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_O, Keys.O }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_P, Keys.P }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_Q, Keys.Q }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_R, Keys.R }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_S, Keys.S }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_T, Keys.T }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_U, Keys.U }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_V, Keys.V }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_W, Keys.W }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_X, Keys.X }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_Y, Keys.Y }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_Z, Keys.Z }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_0, Keys.D0 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_1, Keys.D1 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_2, Keys.D2 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_3, Keys.D3 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_4, Keys.D4 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_5, Keys.D5 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_6, Keys.D6 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_7, Keys.D7 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_8, Keys.D8 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_9, Keys.D9 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_0, Keys.NumPad0 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_1, Keys.NumPad1 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_2, Keys.NumPad2 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_3, Keys.NumPad3 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_4, Keys.NumPad4 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_5, Keys.NumPad5 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_6, Keys.NumPad6 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_7, Keys.NumPad7 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_8, Keys.NumPad8 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_9, Keys.NumPad9 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_CLEAR, Keys.OemClear }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_DECIMAL, Keys.Decimal }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_DIVIDE, Keys.Divide }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_ENTER, Keys.Enter }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_MINUS, Keys.Subtract }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_MULTIPLY, Keys.Multiply }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_PERIOD, Keys.OemPeriod }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_PLUS, Keys.Add }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F1, Keys.F1 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F2, Keys.F2 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F3, Keys.F3 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F4, Keys.F4 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F5, Keys.F5 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F6, Keys.F6 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F7, Keys.F7 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F8, Keys.F8 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F9, Keys.F9 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F10, Keys.F10 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F11, Keys.F11 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F12, Keys.F12 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F13, Keys.F13 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F14, Keys.F14 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F15, Keys.F15 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F16, Keys.F16 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F17, Keys.F17 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F18, Keys.F18 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F19, Keys.F19 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F20, Keys.F20 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F21, Keys.F21 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F22, Keys.F22 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F23, Keys.F23 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F24, Keys.F24 }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_SPACE, Keys.Space }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_UP, Keys.Up }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_DOWN, Keys.Down }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_LEFT, Keys.Left }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_RIGHT, Keys.Right }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_LALT, Keys.LeftAlt }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_RALT, Keys.RightAlt }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_LCTRL, Keys.LeftControl }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_RCTRL, Keys.RightControl }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_LGUI, Keys.LeftWindows }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_RGUI, Keys.RightWindows }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_LSHIFT, Keys.LeftShift }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_RSHIFT, Keys.RightShift }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_APPLICATION, Keys.Apps }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_SLASH, Keys.OemQuestion }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_BACKSLASH, Keys.OemBackslash }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_LEFTBRACKET, Keys.OemOpenBrackets }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_RIGHTBRACKET, Keys.OemCloseBrackets }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_CAPSLOCK, Keys.CapsLock }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_COMMA, Keys.OemComma }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_DELETE, Keys.Delete }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_END, Keys.End }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_BACKSPACE, Keys.Back }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_RETURN, Keys.Enter }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_ESCAPE, Keys.Escape }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_HOME, Keys.Home }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_INSERT, Keys.Insert }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_MINUS, Keys.OemMinus }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_NUMLOCKCLEAR, Keys.NumLock }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_PAGEUP, Keys.PageUp }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_PAGEDOWN, Keys.PageDown }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_PAUSE, Keys.Pause }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_PERIOD, Keys.OemPeriod }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_EQUALS, Keys.OemPlus }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_PRINTSCREEN, Keys.PrintScreen }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_APOSTROPHE, Keys.OemQuotes }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_SCROLLLOCK, Keys.Scroll }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_SEMICOLON, Keys.OemSemicolon }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_SLEEP, Keys.Sleep }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_TAB, Keys.Tab }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_GRAVE, Keys.OemTilde }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_VOLUMEUP, Keys.VolumeUp }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_VOLUMEDOWN, Keys.VolumeDown }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_UNKNOWN, Keys.None }, | ||||
/* FIXME: The following scancodes need verification! */ | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_NONUSHASH, Keys.None }, | ||||
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_NONUSBACKSLASH, Keys.None } | ||||
}; | ||||
private static Dictionary<int, SDL.SDL_Scancode> INTERNAL_xnaMap = new Dictionary<int, SDL.SDL_Scancode>() | ||||
{ | ||||
{ (int) Keys.A, SDL.SDL_Scancode.SDL_SCANCODE_A }, | ||||
{ (int) Keys.B, SDL.SDL_Scancode.SDL_SCANCODE_B }, | ||||
{ (int) Keys.C, SDL.SDL_Scancode.SDL_SCANCODE_C }, | ||||
{ (int) Keys.D, SDL.SDL_Scancode.SDL_SCANCODE_D }, | ||||
{ (int) Keys.E, SDL.SDL_Scancode.SDL_SCANCODE_E }, | ||||
{ (int) Keys.F, SDL.SDL_Scancode.SDL_SCANCODE_F }, | ||||
{ (int) Keys.G, SDL.SDL_Scancode.SDL_SCANCODE_G }, | ||||
{ (int) Keys.H, SDL.SDL_Scancode.SDL_SCANCODE_H }, | ||||
{ (int) Keys.I, SDL.SDL_Scancode.SDL_SCANCODE_I }, | ||||
{ (int) Keys.J, SDL.SDL_Scancode.SDL_SCANCODE_J }, | ||||
{ (int) Keys.K, SDL.SDL_Scancode.SDL_SCANCODE_K }, | ||||
{ (int) Keys.L, SDL.SDL_Scancode.SDL_SCANCODE_L }, | ||||
{ (int) Keys.M, SDL.SDL_Scancode.SDL_SCANCODE_M }, | ||||
{ (int) Keys.N, SDL.SDL_Scancode.SDL_SCANCODE_N }, | ||||
{ (int) Keys.O, SDL.SDL_Scancode.SDL_SCANCODE_O }, | ||||
{ (int) Keys.P, SDL.SDL_Scancode.SDL_SCANCODE_P }, | ||||
{ (int) Keys.Q, SDL.SDL_Scancode.SDL_SCANCODE_Q }, | ||||
{ (int) Keys.R, SDL.SDL_Scancode.SDL_SCANCODE_R }, | ||||
{ (int) Keys.S, SDL.SDL_Scancode.SDL_SCANCODE_S }, | ||||
{ (int) Keys.T, SDL.SDL_Scancode.SDL_SCANCODE_T }, | ||||
{ (int) Keys.U, SDL.SDL_Scancode.SDL_SCANCODE_U }, | ||||
{ (int) Keys.V, SDL.SDL_Scancode.SDL_SCANCODE_V }, | ||||
{ (int) Keys.W, SDL.SDL_Scancode.SDL_SCANCODE_W }, | ||||
{ (int) Keys.X, SDL.SDL_Scancode.SDL_SCANCODE_X }, | ||||
{ (int) Keys.Y, SDL.SDL_Scancode.SDL_SCANCODE_Y }, | ||||
{ (int) Keys.Z, SDL.SDL_Scancode.SDL_SCANCODE_Z }, | ||||
{ (int) Keys.D0, SDL.SDL_Scancode.SDL_SCANCODE_0 }, | ||||
{ (int) Keys.D1, SDL.SDL_Scancode.SDL_SCANCODE_1 }, | ||||
{ (int) Keys.D2, SDL.SDL_Scancode.SDL_SCANCODE_2 }, | ||||
{ (int) Keys.D3, SDL.SDL_Scancode.SDL_SCANCODE_3 }, | ||||
{ (int) Keys.D4, SDL.SDL_Scancode.SDL_SCANCODE_4 }, | ||||
{ (int) Keys.D5, SDL.SDL_Scancode.SDL_SCANCODE_5 }, | ||||
{ (int) Keys.D6, SDL.SDL_Scancode.SDL_SCANCODE_6 }, | ||||
{ (int) Keys.D7, SDL.SDL_Scancode.SDL_SCANCODE_7 }, | ||||
{ (int) Keys.D8, SDL.SDL_Scancode.SDL_SCANCODE_8 }, | ||||
{ (int) Keys.D9, SDL.SDL_Scancode.SDL_SCANCODE_9 }, | ||||
{ (int) Keys.NumPad0, SDL.SDL_Scancode.SDL_SCANCODE_KP_0 }, | ||||
{ (int) Keys.NumPad1, SDL.SDL_Scancode.SDL_SCANCODE_KP_1 }, | ||||
{ (int) Keys.NumPad2, SDL.SDL_Scancode.SDL_SCANCODE_KP_2 }, | ||||
{ (int) Keys.NumPad3, SDL.SDL_Scancode.SDL_SCANCODE_KP_3 }, | ||||
{ (int) Keys.NumPad4, SDL.SDL_Scancode.SDL_SCANCODE_KP_4 }, | ||||
{ (int) Keys.NumPad5, SDL.SDL_Scancode.SDL_SCANCODE_KP_5 }, | ||||
{ (int) Keys.NumPad6, SDL.SDL_Scancode.SDL_SCANCODE_KP_6 }, | ||||
{ (int) Keys.NumPad7, SDL.SDL_Scancode.SDL_SCANCODE_KP_7 }, | ||||
{ (int) Keys.NumPad8, SDL.SDL_Scancode.SDL_SCANCODE_KP_8 }, | ||||
{ (int) Keys.NumPad9, SDL.SDL_Scancode.SDL_SCANCODE_KP_9 }, | ||||
{ (int) Keys.OemClear, SDL.SDL_Scancode.SDL_SCANCODE_KP_CLEAR }, | ||||
{ (int) Keys.Decimal, SDL.SDL_Scancode.SDL_SCANCODE_KP_DECIMAL }, | ||||
{ (int) Keys.Divide, SDL.SDL_Scancode.SDL_SCANCODE_KP_DIVIDE }, | ||||
{ (int) Keys.Multiply, SDL.SDL_Scancode.SDL_SCANCODE_KP_MULTIPLY }, | ||||
{ (int) Keys.Subtract, SDL.SDL_Scancode.SDL_SCANCODE_KP_MINUS }, | ||||
{ (int) Keys.Add, SDL.SDL_Scancode.SDL_SCANCODE_KP_PLUS }, | ||||
{ (int) Keys.F1, SDL.SDL_Scancode.SDL_SCANCODE_F1 }, | ||||
{ (int) Keys.F2, SDL.SDL_Scancode.SDL_SCANCODE_F2 }, | ||||
{ (int) Keys.F3, SDL.SDL_Scancode.SDL_SCANCODE_F3 }, | ||||
{ (int) Keys.F4, SDL.SDL_Scancode.SDL_SCANCODE_F4 }, | ||||
{ (int) Keys.F5, SDL.SDL_Scancode.SDL_SCANCODE_F5 }, | ||||
{ (int) Keys.F6, SDL.SDL_Scancode.SDL_SCANCODE_F6 }, | ||||
{ (int) Keys.F7, SDL.SDL_Scancode.SDL_SCANCODE_F7 }, | ||||
{ (int) Keys.F8, SDL.SDL_Scancode.SDL_SCANCODE_F8 }, | ||||
{ (int) Keys.F9, SDL.SDL_Scancode.SDL_SCANCODE_F9 }, | ||||
{ (int) Keys.F10, SDL.SDL_Scancode.SDL_SCANCODE_F10 }, | ||||
{ (int) Keys.F11, SDL.SDL_Scancode.SDL_SCANCODE_F11 }, | ||||
{ (int) Keys.F12, SDL.SDL_Scancode.SDL_SCANCODE_F12 }, | ||||
{ (int) Keys.F13, SDL.SDL_Scancode.SDL_SCANCODE_F13 }, | ||||
{ (int) Keys.F14, SDL.SDL_Scancode.SDL_SCANCODE_F14 }, | ||||
{ (int) Keys.F15, SDL.SDL_Scancode.SDL_SCANCODE_F15 }, | ||||
{ (int) Keys.F16, SDL.SDL_Scancode.SDL_SCANCODE_F16 }, | ||||
{ (int) Keys.F17, SDL.SDL_Scancode.SDL_SCANCODE_F17 }, | ||||
{ (int) Keys.F18, SDL.SDL_Scancode.SDL_SCANCODE_F18 }, | ||||
{ (int) Keys.F19, SDL.SDL_Scancode.SDL_SCANCODE_F19 }, | ||||
{ (int) Keys.F20, SDL.SDL_Scancode.SDL_SCANCODE_F20 }, | ||||
{ (int) Keys.F21, SDL.SDL_Scancode.SDL_SCANCODE_F21 }, | ||||
{ (int) Keys.F22, SDL.SDL_Scancode.SDL_SCANCODE_F22 }, | ||||
{ (int) Keys.F23, SDL.SDL_Scancode.SDL_SCANCODE_F23 }, | ||||
{ (int) Keys.F24, SDL.SDL_Scancode.SDL_SCANCODE_F24 }, | ||||
{ (int) Keys.Space, SDL.SDL_Scancode.SDL_SCANCODE_SPACE }, | ||||
{ (int) Keys.Up, SDL.SDL_Scancode.SDL_SCANCODE_UP }, | ||||
{ (int) Keys.Down, SDL.SDL_Scancode.SDL_SCANCODE_DOWN }, | ||||
{ (int) Keys.Left, SDL.SDL_Scancode.SDL_SCANCODE_LEFT }, | ||||
{ (int) Keys.Right, SDL.SDL_Scancode.SDL_SCANCODE_RIGHT }, | ||||
{ (int) Keys.LeftAlt, SDL.SDL_Scancode.SDL_SCANCODE_LALT }, | ||||
{ (int) Keys.RightAlt, SDL.SDL_Scancode.SDL_SCANCODE_RALT }, | ||||
{ (int) Keys.LeftControl, SDL.SDL_Scancode.SDL_SCANCODE_LCTRL }, | ||||
{ (int) Keys.RightControl, SDL.SDL_Scancode.SDL_SCANCODE_RCTRL }, | ||||
{ (int) Keys.LeftWindows, SDL.SDL_Scancode.SDL_SCANCODE_LGUI }, | ||||
{ (int) Keys.RightWindows, SDL.SDL_Scancode.SDL_SCANCODE_RGUI }, | ||||
{ (int) Keys.LeftShift, SDL.SDL_Scancode.SDL_SCANCODE_LSHIFT }, | ||||
{ (int) Keys.RightShift, SDL.SDL_Scancode.SDL_SCANCODE_RSHIFT }, | ||||
{ (int) Keys.Apps, SDL.SDL_Scancode.SDL_SCANCODE_APPLICATION }, | ||||
{ (int) Keys.OemQuestion, SDL.SDL_Scancode.SDL_SCANCODE_SLASH }, | ||||
{ (int) Keys.OemBackslash, SDL.SDL_Scancode.SDL_SCANCODE_BACKSLASH }, | ||||
{ (int) Keys.OemOpenBrackets, SDL.SDL_Scancode.SDL_SCANCODE_LEFTBRACKET }, | ||||
{ (int) Keys.OemCloseBrackets, SDL.SDL_Scancode.SDL_SCANCODE_RIGHTBRACKET }, | ||||
{ (int) Keys.CapsLock, SDL.SDL_Scancode.SDL_SCANCODE_CAPSLOCK }, | ||||
{ (int) Keys.OemComma, SDL.SDL_Scancode.SDL_SCANCODE_COMMA }, | ||||
{ (int) Keys.Delete, SDL.SDL_Scancode.SDL_SCANCODE_DELETE }, | ||||
{ (int) Keys.End, SDL.SDL_Scancode.SDL_SCANCODE_END }, | ||||
{ (int) Keys.Back, SDL.SDL_Scancode.SDL_SCANCODE_BACKSPACE }, | ||||
{ (int) Keys.Enter, SDL.SDL_Scancode.SDL_SCANCODE_RETURN }, | ||||
{ (int) Keys.Escape, SDL.SDL_Scancode.SDL_SCANCODE_ESCAPE }, | ||||
{ (int) Keys.Home, SDL.SDL_Scancode.SDL_SCANCODE_HOME }, | ||||
{ (int) Keys.Insert, SDL.SDL_Scancode.SDL_SCANCODE_INSERT }, | ||||
{ (int) Keys.OemMinus, SDL.SDL_Scancode.SDL_SCANCODE_MINUS }, | ||||
{ (int) Keys.NumLock, SDL.SDL_Scancode.SDL_SCANCODE_NUMLOCKCLEAR }, | ||||
{ (int) Keys.PageUp, SDL.SDL_Scancode.SDL_SCANCODE_PAGEUP }, | ||||
{ (int) Keys.PageDown, SDL.SDL_Scancode.SDL_SCANCODE_PAGEDOWN }, | ||||
{ (int) Keys.Pause, SDL.SDL_Scancode.SDL_SCANCODE_PAUSE }, | ||||
{ (int) Keys.OemPeriod, SDL.SDL_Scancode.SDL_SCANCODE_PERIOD }, | ||||
{ (int) Keys.OemPlus, SDL.SDL_Scancode.SDL_SCANCODE_EQUALS }, | ||||
{ (int) Keys.PrintScreen, SDL.SDL_Scancode.SDL_SCANCODE_PRINTSCREEN }, | ||||
{ (int) Keys.OemQuotes, SDL.SDL_Scancode.SDL_SCANCODE_APOSTROPHE }, | ||||
{ (int) Keys.Scroll, SDL.SDL_Scancode.SDL_SCANCODE_SCROLLLOCK }, | ||||
{ (int) Keys.OemSemicolon, SDL.SDL_Scancode.SDL_SCANCODE_SEMICOLON }, | ||||
{ (int) Keys.Sleep, SDL.SDL_Scancode.SDL_SCANCODE_SLEEP }, | ||||
{ (int) Keys.Tab, SDL.SDL_Scancode.SDL_SCANCODE_TAB }, | ||||
{ (int) Keys.OemTilde, SDL.SDL_Scancode.SDL_SCANCODE_GRAVE }, | ||||
{ (int) Keys.VolumeUp, SDL.SDL_Scancode.SDL_SCANCODE_VOLUMEUP }, | ||||
{ (int) Keys.VolumeDown, SDL.SDL_Scancode.SDL_SCANCODE_VOLUMEDOWN }, | ||||
{ (int) Keys.None, SDL.SDL_Scancode.SDL_SCANCODE_UNKNOWN } | ||||
}; | ||||
private static Keys ToXNAKey(ref SDL.SDL_Keysym key) | ||||
{ | ||||
Keys retVal; | ||||
if (UseScancodes) | ||||
{ | ||||
if (INTERNAL_scanMap.TryGetValue((int) key.scancode, out retVal)) | ||||
{ | ||||
return retVal; | ||||
} | ||||
} | ||||
else | ||||
{ | ||||
if (INTERNAL_keyMap.TryGetValue((int) key.sym, out retVal)) | ||||
{ | ||||
return retVal; | ||||
} | ||||
} | ||||
FNALoggerEXT.LogWarn( | ||||
"KEY/SCANCODE MISSING FROM SDL2->XNA DICTIONARY: " + | ||||
key.sym.ToString() + " " + | ||||
key.scancode.ToString() | ||||
); | ||||
return Keys.None; | ||||
} | ||||
public static Keys GetKeyFromScancode(Keys scancode) | ||||
{ | ||||
if (UseScancodes) | ||||
{ | ||||
return scancode; | ||||
} | ||||
SDL.SDL_Scancode retVal; | ||||
if (INTERNAL_xnaMap.TryGetValue((int) scancode, out retVal)) | ||||
{ | ||||
Keys result; | ||||
SDL.SDL_Keycode sym = SDL.SDL_GetKeyFromScancode(retVal); | ||||
if (INTERNAL_keyMap.TryGetValue((int) sym, out result)) | ||||
{ | ||||
return result; | ||||
} | ||||
FNALoggerEXT.LogWarn( | ||||
"KEYCODE MISSING FROM SDL2->XNA DICTIONARY: " + | ||||
sym.ToString() | ||||
); | ||||
} | ||||
else | ||||
{ | ||||
FNALoggerEXT.LogWarn( | ||||
"SCANCODE MISSING FROM XNA->SDL2 DICTIONARY: " + | ||||
scancode.ToString() | ||||
); | ||||
} | ||||
return Keys.None; | ||||
} | ||||
#endregion | ||||
#region Private Static Win32 WM_PAINT Interop | ||||
private static SDL.SDL_EventFilter win32OnPaint = Win32OnPaint; | ||||
private static unsafe int Win32OnPaint(IntPtr func, IntPtr evtPtr) | ||||
{ | ||||
SDL.SDL_Event* evt = (SDL.SDL_Event*) evtPtr; | ||||
if ( evt->type == SDL.SDL_EventType.SDL_WINDOWEVENT && | ||||
evt->window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_EXPOSED ) | ||||
{ | ||||
foreach (Game game in activeGames) | ||||
{ | ||||
if ( game.Window != null && | ||||
evt->window.windowID == SDL.SDL_GetWindowID(game.Window.Handle) ) | ||||
{ | ||||
game.RedrawWindow(); | ||||
return 0; | ||||
} | ||||
} | ||||
} | ||||
return 1; | ||||
} | ||||
#endregion | ||||
} | ||||
} | ||||