minetest/irr/src/CIrrDeviceSDL.cpp
grorp 9a1501ae89
CIrrDeviceSDL: Fix numpad key events not having correct KeyInput.Char (#14780)
Allows you to change viewing range using numpad +/- again. This fix also works with the current unreleased version of SDL 3.

The keycodes for numpad keys (both SDL keycodes and Irrlicht keycodes) are not the same as the keycodes for the equivalent non-numpad keys and don't correspond to chars, so I mapped them to chars manually.

Since I think the resolution of https://github.com/minetest/minetest/issues/13770 was "just disable numlock", I made sure to only do this for the numpad number keys if numlock is enabled.
2024-06-27 14:44:44 +02:00

1492 lines
46 KiB
C++

// Copyright (C) 2002-2012 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_
#include "CIrrDeviceSDL.h"
#include "IEventReceiver.h"
#include "IGUIElement.h"
#include "IGUIEnvironment.h"
#include "IImageLoader.h"
#include "IFileSystem.h"
#include "os.h"
#include "CTimer.h"
#include "irrString.h"
#include "Keycodes.h"
#include "COSOperator.h"
#include <cstdio>
#include <cstdlib>
#include "SIrrCreationParameters.h"
#include <SDL_video.h>
#ifdef _IRR_EMSCRIPTEN_PLATFORM_
#include <emscripten.h>
#endif
#include "CSDLManager.h"
static int SDLDeviceInstances = 0;
namespace irr
{
namespace video
{
#ifdef _IRR_COMPILE_WITH_OPENGL_
IVideoDriver *createOpenGLDriver(const SIrrlichtCreationParameters &params, io::IFileSystem *io, IContextManager *contextManager);
#else
static IVideoDriver *createOpenGLDriver(const SIrrlichtCreationParameters &params, io::IFileSystem *io, IContextManager *contextManager)
{
os::Printer::log("No OpenGL support compiled in.", ELL_ERROR);
return nullptr;
}
#endif
#ifdef ENABLE_OPENGL3
IVideoDriver *createOpenGL3Driver(const SIrrlichtCreationParameters &params, io::IFileSystem *io, IContextManager *contextManager);
#else
static IVideoDriver *createOpenGL3Driver(const SIrrlichtCreationParameters &params, io::IFileSystem *io, IContextManager *contextManager)
{
os::Printer::log("No OpenGL 3 support compiled in.", ELL_ERROR);
return nullptr;
}
#endif
#ifdef _IRR_COMPILE_WITH_OGLES2_
IVideoDriver *createOGLES2Driver(const SIrrlichtCreationParameters &params, io::IFileSystem *io, IContextManager *contextManager);
#else
static IVideoDriver *createOGLES2Driver(const SIrrlichtCreationParameters &params, io::IFileSystem *io, IContextManager *contextManager)
{
os::Printer::log("No OpenGL ES 2 support compiled in.", ELL_ERROR);
return nullptr;
}
#endif
#ifdef _IRR_COMPILE_WITH_WEBGL1_
IVideoDriver *createWebGL1Driver(const SIrrlichtCreationParameters &params, io::IFileSystem *io, IContextManager *contextManager);
#else
static IVideoDriver *createWebGL1Driver(const SIrrlichtCreationParameters &params, io::IFileSystem *io, IContextManager *contextManager)
{
os::Printer::log("No WebGL 1 support compiled in.", ELL_ERROR);
return nullptr;
}
#endif
} // end namespace video
} // end namespace irr
namespace irr
{
#ifdef _IRR_EMSCRIPTEN_PLATFORM_
EM_BOOL CIrrDeviceSDL::MouseUpDownCallback(int eventType, const EmscriptenMouseEvent *event, void *userData)
{
// We need this callback so far only because otherwise "emscripten_request_pointerlock" calls will
// fail as their request are infinitely deferred.
// Not exactly certain why, maybe SDL does catch those mouse-events otherwise and not pass them on.
return EM_FALSE;
}
EM_BOOL CIrrDeviceSDL::MouseEnterCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData)
{
CIrrDeviceSDL *This = static_cast<CIrrDeviceSDL *>(userData);
SEvent irrevent;
irrevent.EventType = irr::EET_MOUSE_INPUT_EVENT;
irrevent.MouseInput.Event = irr::EMIE_MOUSE_ENTER_CANVAS;
This->MouseX = irrevent.MouseInput.X = mouseEvent->canvasX;
This->MouseY = irrevent.MouseInput.Y = mouseEvent->canvasY;
This->MouseXRel = mouseEvent->movementX; // should be 0 I guess? Or can it enter while pointer is locked()?
This->MouseYRel = mouseEvent->movementY;
irrevent.MouseInput.ButtonStates = This->MouseButtonStates; // TODO: not correct, but couldn't figure out the bitset of mouseEvent->buttons yet.
irrevent.MouseInput.Shift = mouseEvent->shiftKey;
irrevent.MouseInput.Control = mouseEvent->ctrlKey;
This->postEventFromUser(irrevent);
return EM_FALSE;
}
EM_BOOL CIrrDeviceSDL::MouseLeaveCallback(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData)
{
CIrrDeviceSDL *This = static_cast<CIrrDeviceSDL *>(userData);
SEvent irrevent;
irrevent.EventType = irr::EET_MOUSE_INPUT_EVENT;
irrevent.MouseInput.Event = irr::EMIE_MOUSE_LEAVE_CANVAS;
This->MouseX = irrevent.MouseInput.X = mouseEvent->canvasX;
This->MouseY = irrevent.MouseInput.Y = mouseEvent->canvasY;
This->MouseXRel = mouseEvent->movementX; // should be 0 I guess? Or can it enter while pointer is locked()?
This->MouseYRel = mouseEvent->movementY;
irrevent.MouseInput.ButtonStates = This->MouseButtonStates; // TODO: not correct, but couldn't figure out the bitset of mouseEvent->buttons yet.
irrevent.MouseInput.Shift = mouseEvent->shiftKey;
irrevent.MouseInput.Control = mouseEvent->ctrlKey;
This->postEventFromUser(irrevent);
return EM_FALSE;
}
#endif
bool CIrrDeviceSDL::keyIsKnownSpecial(EKEY_CODE irrlichtKey)
{
switch (irrlichtKey) {
// keys which are known to have safe special character interpretation
// could need changes over time (removals and additions!)
case KEY_RETURN:
case KEY_PAUSE:
case KEY_ESCAPE:
case KEY_PRIOR:
case KEY_NEXT:
case KEY_HOME:
case KEY_END:
case KEY_LEFT:
case KEY_UP:
case KEY_RIGHT:
case KEY_DOWN:
case KEY_TAB:
case KEY_PRINT:
case KEY_SNAPSHOT:
case KEY_INSERT:
case KEY_BACK:
case KEY_DELETE:
case KEY_HELP:
case KEY_APPS:
case KEY_SLEEP:
case KEY_F1:
case KEY_F2:
case KEY_F3:
case KEY_F4:
case KEY_F5:
case KEY_F6:
case KEY_F7:
case KEY_F8:
case KEY_F9:
case KEY_F10:
case KEY_F11:
case KEY_F12:
case KEY_F13:
case KEY_F14:
case KEY_F15:
case KEY_F16:
case KEY_F17:
case KEY_F18:
case KEY_F19:
case KEY_F20:
case KEY_F21:
case KEY_F22:
case KEY_F23:
case KEY_F24:
case KEY_NUMLOCK:
case KEY_SCROLL:
case KEY_LCONTROL:
case KEY_RCONTROL:
return true;
default:
return false;
}
}
int CIrrDeviceSDL::findCharToPassToIrrlicht(uint32_t sdlKey, EKEY_CODE irrlichtKey, bool numlock)
{
switch (irrlichtKey) {
// special cases that always return a char regardless of how the SDL keycode
// looks
case KEY_RETURN:
case KEY_ESCAPE:
return (int)irrlichtKey;
// This is necessary for keys on the numpad because they don't use the same
// keycodes as their non-numpad versions (whose keycodes correspond to chars),
// but have their own SDL keycodes and their own Irrlicht keycodes (which
// don't correspond to chars).
case KEY_MULTIPLY:
return '*';
case KEY_ADD:
return '+';
case KEY_SUBTRACT:
return '-';
case KEY_DIVIDE:
return '/';
default:
break;
}
if (numlock) {
// Number keys on the numpad are also affected, but we only want them
// to produce number chars when numlock is enabled.
switch (irrlichtKey) {
case KEY_NUMPAD0:
return '0';
case KEY_NUMPAD1:
return '1';
case KEY_NUMPAD2:
return '2';
case KEY_NUMPAD3:
return '3';
case KEY_NUMPAD4:
return '4';
case KEY_NUMPAD5:
return '5';
case KEY_NUMPAD6:
return '6';
case KEY_NUMPAD7:
return '7';
case KEY_NUMPAD8:
return '8';
case KEY_NUMPAD9:
return '9';
default:
break;
}
}
// SDL in-place ORs values with no character representation with 1<<30
// https://wiki.libsdl.org/SDL2/SDLKeycodeLookup
// This also affects the numpad keys btw.
if (sdlKey & (1 << 30))
return 0;
switch (irrlichtKey) {
case KEY_PRIOR:
case KEY_NEXT:
case KEY_HOME:
case KEY_END:
case KEY_LEFT:
case KEY_UP:
case KEY_RIGHT:
case KEY_DOWN:
case KEY_NUMLOCK:
return 0;
default:
return sdlKey;
}
}
void CIrrDeviceSDL::resetReceiveTextInputEvents()
{
gui::IGUIElement *elem = GUIEnvironment->getFocus();
if (elem && elem->acceptsIME()) {
// IBus seems to have an issue where dead keys and compose keys do not
// work (specifically, the individual characters in the sequence are
// sent as text input events instead of the result) when
// SDL_StartTextInput() is called on the same input box.
core::rect<s32> pos = elem->getAbsolutePosition();
if (!SDL_IsTextInputActive() || lastElemPos != pos) {
lastElemPos = pos;
SDL_Rect rect;
rect.x = pos.UpperLeftCorner.X;
rect.y = pos.UpperLeftCorner.Y;
rect.w = pos.getWidth();
rect.h = pos.getHeight();
SDL_SetTextInputRect(&rect);
SDL_StartTextInput();
}
} else {
SDL_StopTextInput();
}
}
//! constructor
CIrrDeviceSDL::CIrrDeviceSDL(const SIrrlichtCreationParameters &param) :
CIrrDeviceStub(param),
Window((SDL_Window *)param.WindowId),
MouseX(0), MouseY(0), MouseXRel(0), MouseYRel(0), MouseButtonStates(0),
Width(param.WindowSize.Width), Height(param.WindowSize.Height),
Resizable(param.WindowResizable == 1 ? true : false), CurrentTouchCount(0),
IsInBackground(false)
{
#ifdef _DEBUG
setDebugName("CIrrDeviceSDL");
#endif
if (++SDLDeviceInstances == 1) {
#ifdef __ANDROID__
// Blocking on pause causes problems with multiplayer.
// See https://github.com/minetest/minetest/issues/10842.
SDL_SetHint(SDL_HINT_ANDROID_BLOCK_ON_PAUSE, "0");
SDL_SetHint(SDL_HINT_ANDROID_BLOCK_ON_PAUSE_PAUSEAUDIO, "0");
SDL_SetHint(SDL_HINT_ANDROID_TRAP_BACK_BUTTON, "1");
// Minetest does its own screen keyboard handling.
SDL_SetHint(SDL_HINT_ENABLE_SCREEN_KEYBOARD, "0");
#endif
// Minetest has its own signal handler
SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1");
// Disabling the compositor is not a good idea in windowed mode.
// See https://github.com/minetest/minetest/issues/14596
SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0");
#if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_)
// These are not interesting for our use
SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0");
SDL_SetHint(SDL_HINT_TV_REMOTE_AS_JOYSTICK, "0");
#endif
#if SDL_VERSION_ATLEAST(2, 24, 0)
// highdpi support on Windows
SDL_SetHint(SDL_HINT_WINDOWS_DPI_SCALING, "1");
#endif
// Minetest has its own code to synthesize mouse events from touch events,
// so we prevent SDL from doing it.
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
u32 flags = SDL_INIT_TIMER | SDL_INIT_EVENTS;
if (CreationParams.DriverType != video::EDT_NULL)
flags |= SDL_INIT_VIDEO;
#if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_)
flags |= SDL_INIT_JOYSTICK;
#endif
if (SDL_Init(flags) < 0) {
os::Printer::log("Unable to initialize SDL", SDL_GetError(), ELL_ERROR);
Close = true;
} else {
os::Printer::log("SDL initialized", ELL_INFORMATION);
}
}
// create keymap
createKeyMap();
// create window
if (CreationParams.DriverType != video::EDT_NULL) {
if (!createWindow()) {
Close = true;
return;
}
}
SDL_VERSION(&Info.version);
#ifndef _IRR_EMSCRIPTEN_PLATFORM_
SDL_GetWindowWMInfo(Window, &Info);
#endif //_IRR_EMSCRIPTEN_PLATFORM_
core::stringc sdlversion = "SDL Version ";
sdlversion += Info.version.major;
sdlversion += ".";
sdlversion += Info.version.minor;
sdlversion += ".";
sdlversion += Info.version.patch;
Operator = new COSOperator(sdlversion);
if (SDLDeviceInstances == 1) {
os::Printer::log(sdlversion.c_str(), ELL_INFORMATION);
}
// create cursor control
CursorControl = new CCursorControl(this);
// create driver
createDriver();
if (VideoDriver) {
createGUIAndScene();
VideoDriver->OnResize(core::dimension2d<u32>(Width, Height));
}
}
//! destructor
CIrrDeviceSDL::~CIrrDeviceSDL()
{
#if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_)
const u32 numJoysticks = Joysticks.size();
for (u32 i = 0; i < numJoysticks; ++i)
SDL_JoystickClose(Joysticks[i]);
#endif
if (Window && Context) {
SDL_GL_MakeCurrent(Window, NULL);
SDL_GL_DeleteContext(Context);
}
if (Window) {
SDL_DestroyWindow(Window);
}
if (--SDLDeviceInstances == 0) {
SDL_Quit();
os::Printer::log("Quit SDL", ELL_INFORMATION);
}
}
void CIrrDeviceSDL::logAttributes()
{
core::stringc sdl_attr("SDL attribs:");
int value = 0;
if (SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &value) == 0)
sdl_attr += core::stringc(" r:") + core::stringc(value);
if (SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &value) == 0)
sdl_attr += core::stringc(" g:") + core::stringc(value);
if (SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &value) == 0)
sdl_attr += core::stringc(" b:") + core::stringc(value);
if (SDL_GL_GetAttribute(SDL_GL_ALPHA_SIZE, &value) == 0)
sdl_attr += core::stringc(" a:") + core::stringc(value);
if (SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &value) == 0)
sdl_attr += core::stringc(" depth:") + core::stringc(value);
if (SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &value) == 0)
sdl_attr += core::stringc(" stencil:") + core::stringc(value);
if (SDL_GL_GetAttribute(SDL_GL_DOUBLEBUFFER, &value) == 0)
sdl_attr += core::stringc(" doublebuf:") + core::stringc(value);
if (SDL_GL_GetAttribute(SDL_GL_MULTISAMPLEBUFFERS, &value) == 0)
sdl_attr += core::stringc(" aa:") + core::stringc(value);
if (SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &value) == 0)
sdl_attr += core::stringc(" aa-samples:") + core::stringc(value);
os::Printer::log(sdl_attr.c_str());
}
bool CIrrDeviceSDL::createWindow()
{
if (Close)
return false;
if (createWindowWithContext())
return true;
if (CreationParams.DriverDebug) {
CreationParams.DriverDebug = false;
if (createWindowWithContext()) {
os::Printer::log("DriverDebug reduced due to lack of support!");
// Turn it back on because the GL driver can maybe still do something useful.
CreationParams.DriverDebug = true;
return true;
}
}
while (CreationParams.AntiAlias > 0) {
CreationParams.AntiAlias--;
if (createWindowWithContext()) {
os::Printer::log("AntiAlias reduced/disabled due to lack of support!");
return true;
}
}
if (CreationParams.WithAlphaChannel) {
CreationParams.WithAlphaChannel = false;
if (createWindowWithContext()) {
os::Printer::log("WithAlphaChannel disabled due to lack of support!");
return true;
}
}
if (CreationParams.Stencilbuffer) {
CreationParams.Stencilbuffer = false;
if (createWindowWithContext()) {
os::Printer::log("Stencilbuffer disabled due to lack of support!");
return true;
}
}
while (CreationParams.ZBufferBits > 16) {
CreationParams.ZBufferBits -= 8;
if (createWindowWithContext()) {
os::Printer::log("ZBufferBits reduced due to lack of support!");
return true;
}
}
while (CreationParams.Bits > 16) {
CreationParams.Bits -= 8;
if (createWindowWithContext()) {
os::Printer::log("Bits reduced due to lack of support!");
return true;
}
}
if (CreationParams.Stereobuffer) {
CreationParams.Stereobuffer = false;
if (createWindowWithContext()) {
os::Printer::log("Stereobuffer disabled due to lack of support!");
return true;
}
}
if (CreationParams.Doublebuffer) {
// Try single buffer
CreationParams.Doublebuffer = false;
if (createWindowWithContext()) {
os::Printer::log("Doublebuffer disabled due to lack of support!");
return true;
}
}
os::Printer::log("Could not create window and context!", ELL_ERROR);
return false;
}
bool CIrrDeviceSDL::createWindowWithContext()
{
u32 SDL_Flags = 0;
SDL_Flags |= SDL_WINDOW_ALLOW_HIGHDPI;
SDL_Flags |= getFullscreenFlag(CreationParams.Fullscreen);
if (Resizable)
SDL_Flags |= SDL_WINDOW_RESIZABLE;
if (CreationParams.WindowMaximized)
SDL_Flags |= SDL_WINDOW_MAXIMIZED;
SDL_Flags |= SDL_WINDOW_OPENGL;
SDL_GL_ResetAttributes();
#ifdef _IRR_EMSCRIPTEN_PLATFORM_
if (Width != 0 || Height != 0)
emscripten_set_canvas_size(Width, Height);
else {
int w, h, fs;
emscripten_get_canvas_size(&w, &h, &fs);
Width = w;
Height = h;
}
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, CreationParams.WithAlphaChannel ? 8 : 0);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, CreationParams.ZBufferBits);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, CreationParams.Stencilbuffer ? 8 : 0);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, CreationParams.Doublebuffer ? 1 : 0);
if (CreationParams.AntiAlias > 1) {
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, CreationParams.AntiAlias);
} else {
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
}
SDL_CreateWindowAndRenderer(0, 0, SDL_Flags, &Window, &Renderer); // 0,0 will use the canvas size
logAttributes();
// "#canvas" is for the opengl context
emscripten_set_mousedown_callback("#canvas", (void *)this, true, MouseUpDownCallback);
emscripten_set_mouseup_callback("#canvas", (void *)this, true, MouseUpDownCallback);
emscripten_set_mouseenter_callback("#canvas", (void *)this, false, MouseEnterCallback);
emscripten_set_mouseleave_callback("#canvas", (void *)this, false, MouseLeaveCallback);
return true;
#else // !_IRR_EMSCRIPTEN_PLATFORM_
switch (CreationParams.DriverType) {
case video::EDT_OPENGL:
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
break;
case video::EDT_OPENGL3:
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
break;
case video::EDT_OGLES1:
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
break;
case video::EDT_OGLES2:
case video::EDT_WEBGL1:
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
break;
default:;
}
if (CreationParams.DriverDebug) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG | SDL_GL_CONTEXT_ROBUST_ACCESS_FLAG);
}
if (CreationParams.Bits == 16) {
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, CreationParams.WithAlphaChannel ? 1 : 0);
} else {
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, CreationParams.WithAlphaChannel ? 8 : 0);
}
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, CreationParams.ZBufferBits);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, CreationParams.Doublebuffer);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, CreationParams.Stencilbuffer ? 8 : 0);
SDL_GL_SetAttribute(SDL_GL_STEREO, CreationParams.Stereobuffer);
if (CreationParams.AntiAlias > 1) {
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, CreationParams.AntiAlias);
} else {
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
}
Window = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, Width, Height, SDL_Flags);
if (!Window) {
os::Printer::log("Could not create window", SDL_GetError(), ELL_WARNING);
return false;
}
Context = SDL_GL_CreateContext(Window);
if (!Context) {
os::Printer::log("Could not create context", SDL_GetError(), ELL_WARNING);
SDL_DestroyWindow(Window);
Window = nullptr;
return false;
}
updateSizeAndScale();
if (ScaleX != 1.0f || ScaleY != 1.0f) {
// The given window size is in pixels, not in screen coordinates.
// We can only do the conversion now since we didn't know the scale before.
SDL_SetWindowSize(Window, CreationParams.WindowSize.Width / ScaleX,
CreationParams.WindowSize.Height / ScaleY);
// Re-center, otherwise large, non-maximized windows go offscreen.
SDL_SetWindowPosition(Window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
updateSizeAndScale();
}
return true;
#endif // !_IRR_EMSCRIPTEN_PLATFORM_
}
//! create the driver
void CIrrDeviceSDL::createDriver()
{
if (CreationParams.DriverType == video::EDT_NULL) {
VideoDriver = video::createNullDriver(FileSystem, CreationParams.WindowSize);
return;
}
ContextManager = new video::CSDLManager(this);
switch (CreationParams.DriverType) {
case video::EDT_OPENGL:
VideoDriver = video::createOpenGLDriver(CreationParams, FileSystem, ContextManager);
break;
case video::EDT_OPENGL3:
VideoDriver = video::createOpenGL3Driver(CreationParams, FileSystem, ContextManager);
break;
case video::EDT_OGLES2:
VideoDriver = video::createOGLES2Driver(CreationParams, FileSystem, ContextManager);
break;
case video::EDT_WEBGL1:
VideoDriver = video::createWebGL1Driver(CreationParams, FileSystem, ContextManager);
break;
default:;
}
if (!VideoDriver)
os::Printer::log("Could not create video driver", ELL_ERROR);
}
static int wrap_PollEvent(SDL_Event *ev)
{
u32 t0 = os::Timer::getRealTime();
int ret = SDL_PollEvent(ev);
u32 d = os::Timer::getRealTime() - t0;
if (d >= 5) {
auto msg = std::string("SDL_PollEvent took too long: ") + std::to_string(d) + "ms";
// 50ms delay => more than three missed frames (at 60fps)
os::Printer::log(msg.c_str(), d >= 50 ? ELL_WARNING : ELL_INFORMATION);
}
return ret;
}
//! runs the device. Returns false if device wants to be deleted
bool CIrrDeviceSDL::run()
{
os::Timer::tick();
SEvent irrevent;
SDL_Event SDL_event;
while (!Close && wrap_PollEvent(&SDL_event)) {
// os::Printer::log("event: ", core::stringc((int)SDL_event.type).c_str(), ELL_INFORMATION); // just for debugging
switch (SDL_event.type) {
case SDL_MOUSEMOTION: {
SDL_Keymod keymod = SDL_GetModState();
irrevent.EventType = irr::EET_MOUSE_INPUT_EVENT;
irrevent.MouseInput.Event = irr::EMIE_MOUSE_MOVED;
MouseX = irrevent.MouseInput.X = SDL_event.motion.x * ScaleX;
MouseY = irrevent.MouseInput.Y = SDL_event.motion.y * ScaleY;
MouseXRel = SDL_event.motion.xrel * ScaleX;
MouseYRel = SDL_event.motion.yrel * ScaleY;
irrevent.MouseInput.ButtonStates = MouseButtonStates;
irrevent.MouseInput.Shift = (keymod & KMOD_SHIFT) != 0;
irrevent.MouseInput.Control = (keymod & KMOD_CTRL) != 0;
postEventFromUser(irrevent);
break;
}
case SDL_MOUSEWHEEL: {
SDL_Keymod keymod = SDL_GetModState();
irrevent.EventType = irr::EET_MOUSE_INPUT_EVENT;
irrevent.MouseInput.Event = irr::EMIE_MOUSE_WHEEL;
#if SDL_VERSION_ATLEAST(2, 0, 18)
irrevent.MouseInput.Wheel = SDL_event.wheel.preciseY;
#else
irrevent.MouseInput.Wheel = SDL_event.wheel.y;
#endif
irrevent.MouseInput.ButtonStates = MouseButtonStates;
irrevent.MouseInput.Shift = (keymod & KMOD_SHIFT) != 0;
irrevent.MouseInput.Control = (keymod & KMOD_CTRL) != 0;
irrevent.MouseInput.X = MouseX;
irrevent.MouseInput.Y = MouseY;
postEventFromUser(irrevent);
break;
}
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP: {
SDL_Keymod keymod = SDL_GetModState();
irrevent.EventType = irr::EET_MOUSE_INPUT_EVENT;
irrevent.MouseInput.X = SDL_event.button.x * ScaleX;
irrevent.MouseInput.Y = SDL_event.button.y * ScaleY;
irrevent.MouseInput.Shift = (keymod & KMOD_SHIFT) != 0;
irrevent.MouseInput.Control = (keymod & KMOD_CTRL) != 0;
irrevent.MouseInput.Event = irr::EMIE_MOUSE_MOVED;
#ifdef _IRR_EMSCRIPTEN_PLATFORM_
// Handle mouselocking in emscripten in Windowed mode.
// In fullscreen SDL will handle it.
// The behavior we want windowed is - when the canvas was clicked then
// we will lock the mouse-pointer if it should be invisible.
// For security reasons this will be delayed until the next mouse-up event.
// We do not pass on this event as we don't want the activation click to do anything.
if (SDL_event.type == SDL_MOUSEBUTTONDOWN && !isFullscreen()) {
EmscriptenPointerlockChangeEvent pointerlockStatus; // let's hope that test is not expensive ...
if (emscripten_get_pointerlock_status(&pointerlockStatus) == EMSCRIPTEN_RESULT_SUCCESS) {
if (CursorControl->isVisible() && pointerlockStatus.isActive) {
emscripten_exit_pointerlock();
return !Close;
} else if (!CursorControl->isVisible() && !pointerlockStatus.isActive) {
emscripten_request_pointerlock(0, true);
return !Close;
}
}
}
#endif
auto button = SDL_event.button.button;
#ifdef __ANDROID__
// Android likes to send the right mouse button as the back button.
// According to some web searches I did, this is probably
// vendor/device-specific.
// Since a working right mouse button is very important for
// Minetest, we have this little hack.
if (button == SDL_BUTTON_X2)
button = SDL_BUTTON_RIGHT;
#endif
switch (button) {
case SDL_BUTTON_LEFT:
if (SDL_event.type == SDL_MOUSEBUTTONDOWN) {
irrevent.MouseInput.Event = irr::EMIE_LMOUSE_PRESSED_DOWN;
MouseButtonStates |= irr::EMBSM_LEFT;
} else {
irrevent.MouseInput.Event = irr::EMIE_LMOUSE_LEFT_UP;
MouseButtonStates &= ~irr::EMBSM_LEFT;
}
break;
case SDL_BUTTON_RIGHT:
if (SDL_event.type == SDL_MOUSEBUTTONDOWN) {
irrevent.MouseInput.Event = irr::EMIE_RMOUSE_PRESSED_DOWN;
MouseButtonStates |= irr::EMBSM_RIGHT;
} else {
irrevent.MouseInput.Event = irr::EMIE_RMOUSE_LEFT_UP;
MouseButtonStates &= ~irr::EMBSM_RIGHT;
}
break;
case SDL_BUTTON_MIDDLE:
if (SDL_event.type == SDL_MOUSEBUTTONDOWN) {
irrevent.MouseInput.Event = irr::EMIE_MMOUSE_PRESSED_DOWN;
MouseButtonStates |= irr::EMBSM_MIDDLE;
} else {
irrevent.MouseInput.Event = irr::EMIE_MMOUSE_LEFT_UP;
MouseButtonStates &= ~irr::EMBSM_MIDDLE;
}
break;
}
irrevent.MouseInput.ButtonStates = MouseButtonStates;
if (irrevent.MouseInput.Event != irr::EMIE_MOUSE_MOVED) {
postEventFromUser(irrevent);
if (irrevent.MouseInput.Event >= EMIE_LMOUSE_PRESSED_DOWN && irrevent.MouseInput.Event <= EMIE_MMOUSE_PRESSED_DOWN) {
u32 clicks = checkSuccessiveClicks(irrevent.MouseInput.X, irrevent.MouseInput.Y, irrevent.MouseInput.Event);
if (clicks == 2) {
irrevent.MouseInput.Event = (EMOUSE_INPUT_EVENT)(EMIE_LMOUSE_DOUBLE_CLICK + irrevent.MouseInput.Event - EMIE_LMOUSE_PRESSED_DOWN);
postEventFromUser(irrevent);
} else if (clicks == 3) {
irrevent.MouseInput.Event = (EMOUSE_INPUT_EVENT)(EMIE_LMOUSE_TRIPLE_CLICK + irrevent.MouseInput.Event - EMIE_LMOUSE_PRESSED_DOWN);
postEventFromUser(irrevent);
}
}
}
break;
}
case SDL_TEXTINPUT: {
irrevent.EventType = irr::EET_STRING_INPUT_EVENT;
irrevent.StringInput.Str = new core::stringw();
irr::core::utf8ToWString(*irrevent.StringInput.Str, SDL_event.text.text);
postEventFromUser(irrevent);
delete irrevent.StringInput.Str;
irrevent.StringInput.Str = NULL;
} break;
case SDL_KEYDOWN:
case SDL_KEYUP: {
SKeyMap mp;
mp.SDLKey = SDL_event.key.keysym.sym;
s32 idx = KeyMap.binary_search(mp);
EKEY_CODE key;
if (idx == -1)
key = (EKEY_CODE)0;
else
key = (EKEY_CODE)KeyMap[idx].Win32Key;
if (key == (EKEY_CODE)0)
os::Printer::log("keycode not mapped", core::stringc(mp.SDLKey), ELL_DEBUG);
// Make sure to only input special characters if something is in focus, as SDL_TEXTINPUT handles normal unicode already
if (SDL_IsTextInputActive() && !keyIsKnownSpecial(key) && (SDL_event.key.keysym.mod & KMOD_CTRL) == 0)
break;
irrevent.EventType = irr::EET_KEY_INPUT_EVENT;
irrevent.KeyInput.Key = key;
irrevent.KeyInput.PressedDown = (SDL_event.type == SDL_KEYDOWN);
irrevent.KeyInput.Shift = (SDL_event.key.keysym.mod & KMOD_SHIFT) != 0;
irrevent.KeyInput.Control = (SDL_event.key.keysym.mod & KMOD_CTRL) != 0;
irrevent.KeyInput.Char = findCharToPassToIrrlicht(mp.SDLKey, key,
(SDL_event.key.keysym.mod & KMOD_NUM) != 0);
postEventFromUser(irrevent);
} break;
case SDL_QUIT:
Close = true;
break;
case SDL_WINDOWEVENT:
switch (SDL_event.window.event) {
case SDL_WINDOWEVENT_RESIZED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
#if SDL_VERSION_ATLEAST(2, 0, 18)
case SDL_WINDOWEVENT_DISPLAY_CHANGED:
#endif
u32 old_w = Width, old_h = Height;
f32 old_scale_x = ScaleX, old_scale_y = ScaleY;
updateSizeAndScale();
if (old_w != Width || old_h != Height) {
if (VideoDriver)
VideoDriver->OnResize(core::dimension2d<u32>(Width, Height));
}
if (old_scale_x != ScaleX || old_scale_y != ScaleY) {
irrevent.EventType = EET_APPLICATION_EVENT;
irrevent.ApplicationEvent.EventType = EAET_DPI_CHANGED;
postEventFromUser(irrevent);
}
break;
}
break;
case SDL_USEREVENT:
irrevent.EventType = irr::EET_USER_EVENT;
irrevent.UserEvent.UserData1 = reinterpret_cast<uintptr_t>(SDL_event.user.data1);
irrevent.UserEvent.UserData2 = reinterpret_cast<uintptr_t>(SDL_event.user.data2);
postEventFromUser(irrevent);
break;
case SDL_FINGERDOWN:
irrevent.EventType = EET_TOUCH_INPUT_EVENT;
irrevent.TouchInput.Event = ETIE_PRESSED_DOWN;
irrevent.TouchInput.ID = SDL_event.tfinger.fingerId;
irrevent.TouchInput.X = SDL_event.tfinger.x * Width;
irrevent.TouchInput.Y = SDL_event.tfinger.y * Height;
CurrentTouchCount++;
irrevent.TouchInput.touchedCount = CurrentTouchCount;
postEventFromUser(irrevent);
break;
case SDL_FINGERMOTION:
irrevent.EventType = EET_TOUCH_INPUT_EVENT;
irrevent.TouchInput.Event = ETIE_MOVED;
irrevent.TouchInput.ID = SDL_event.tfinger.fingerId;
irrevent.TouchInput.X = SDL_event.tfinger.x * Width;
irrevent.TouchInput.Y = SDL_event.tfinger.y * Height;
irrevent.TouchInput.touchedCount = CurrentTouchCount;
postEventFromUser(irrevent);
break;
case SDL_FINGERUP:
irrevent.EventType = EET_TOUCH_INPUT_EVENT;
irrevent.TouchInput.Event = ETIE_LEFT_UP;
irrevent.TouchInput.ID = SDL_event.tfinger.fingerId;
irrevent.TouchInput.X = SDL_event.tfinger.x * Width;
irrevent.TouchInput.Y = SDL_event.tfinger.y * Height;
// To match Android behavior, still count the pointer that was
// just released.
irrevent.TouchInput.touchedCount = CurrentTouchCount;
if (CurrentTouchCount > 0) {
CurrentTouchCount--;
}
postEventFromUser(irrevent);
break;
// Contrary to what the SDL documentation says, SDL_APP_WILLENTERBACKGROUND
// and SDL_APP_WILLENTERFOREGROUND are actually sent in onStop/onStart,
// not onPause/onResume, on recent Android versions. This can be verified
// by testing or by looking at the org.libsdl.app.SDLActivity Java code.
// -> This means we can use them to implement isWindowVisible().
case SDL_APP_WILLENTERBACKGROUND:
IsInBackground = true;
break;
case SDL_APP_WILLENTERFOREGROUND:
IsInBackground = false;
break;
case SDL_RENDER_TARGETS_RESET:
os::Printer::log("Received SDL_RENDER_TARGETS_RESET. Rendering is probably broken.", ELL_ERROR);
break;
case SDL_RENDER_DEVICE_RESET:
os::Printer::log("Received SDL_RENDER_DEVICE_RESET. Rendering is probably broken.", ELL_ERROR);
break;
default:
break;
} // end switch
resetReceiveTextInputEvents();
} // end while
#if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_)
// TODO: Check if the multiple open/close calls are too expensive, then
// open/close in the constructor/destructor instead
// update joystick states manually
SDL_JoystickUpdate();
// we'll always send joystick input events...
SEvent joyevent;
joyevent.EventType = EET_JOYSTICK_INPUT_EVENT;
for (u32 i = 0; i < Joysticks.size(); ++i) {
SDL_Joystick *joystick = Joysticks[i];
if (joystick) {
int j;
// query all buttons
const int numButtons = core::min_(SDL_JoystickNumButtons(joystick), 32);
joyevent.JoystickEvent.ButtonStates = 0;
for (j = 0; j < numButtons; ++j)
joyevent.JoystickEvent.ButtonStates |= (SDL_JoystickGetButton(joystick, j) << j);
// query all axes, already in correct range
const int numAxes = core::min_(SDL_JoystickNumAxes(joystick), (int)SEvent::SJoystickEvent::NUMBER_OF_AXES);
joyevent.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_X] = 0;
joyevent.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_Y] = 0;
joyevent.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_Z] = 0;
joyevent.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_R] = 0;
joyevent.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_U] = 0;
joyevent.JoystickEvent.Axis[SEvent::SJoystickEvent::AXIS_V] = 0;
for (j = 0; j < numAxes; ++j)
joyevent.JoystickEvent.Axis[j] = SDL_JoystickGetAxis(joystick, j);
// we can only query one hat, SDL only supports 8 directions
if (SDL_JoystickNumHats(joystick) > 0) {
switch (SDL_JoystickGetHat(joystick, 0)) {
case SDL_HAT_UP:
joyevent.JoystickEvent.POV = 0;
break;
case SDL_HAT_RIGHTUP:
joyevent.JoystickEvent.POV = 4500;
break;
case SDL_HAT_RIGHT:
joyevent.JoystickEvent.POV = 9000;
break;
case SDL_HAT_RIGHTDOWN:
joyevent.JoystickEvent.POV = 13500;
break;
case SDL_HAT_DOWN:
joyevent.JoystickEvent.POV = 18000;
break;
case SDL_HAT_LEFTDOWN:
joyevent.JoystickEvent.POV = 22500;
break;
case SDL_HAT_LEFT:
joyevent.JoystickEvent.POV = 27000;
break;
case SDL_HAT_LEFTUP:
joyevent.JoystickEvent.POV = 31500;
break;
case SDL_HAT_CENTERED:
default:
joyevent.JoystickEvent.POV = 65535;
break;
}
} else {
joyevent.JoystickEvent.POV = 65535;
}
// we map the number directly
joyevent.JoystickEvent.Joystick = static_cast<u8>(i);
// now post the event
postEventFromUser(joyevent);
// and close the joystick
}
}
#endif
return !Close;
}
//! Activate any joysticks, and generate events for them.
bool CIrrDeviceSDL::activateJoysticks(core::array<SJoystickInfo> &joystickInfo)
{
#if defined(_IRR_COMPILE_WITH_JOYSTICK_EVENTS_)
joystickInfo.clear();
// we can name up to 256 different joysticks
const int numJoysticks = core::min_(SDL_NumJoysticks(), 256);
Joysticks.reallocate(numJoysticks);
joystickInfo.reallocate(numJoysticks);
int joystick = 0;
for (; joystick < numJoysticks; ++joystick) {
Joysticks.push_back(SDL_JoystickOpen(joystick));
SJoystickInfo info;
info.Joystick = joystick;
info.Axes = SDL_JoystickNumAxes(Joysticks[joystick]);
info.Buttons = SDL_JoystickNumButtons(Joysticks[joystick]);
info.Name = SDL_JoystickName(Joysticks[joystick]);
info.PovHat = (SDL_JoystickNumHats(Joysticks[joystick]) > 0)
? SJoystickInfo::POV_HAT_PRESENT
: SJoystickInfo::POV_HAT_ABSENT;
joystickInfo.push_back(info);
}
for (joystick = 0; joystick < (int)joystickInfo.size(); ++joystick) {
char logString[256];
snprintf_irr(logString, sizeof(logString), "Found joystick %d, %d axes, %d buttons '%s'",
joystick, joystickInfo[joystick].Axes,
joystickInfo[joystick].Buttons, joystickInfo[joystick].Name.c_str());
os::Printer::log(logString, ELL_INFORMATION);
}
return true;
#endif // _IRR_COMPILE_WITH_JOYSTICK_EVENTS_
return false;
}
void CIrrDeviceSDL::updateSizeAndScale()
{
int window_w, window_h;
SDL_GetWindowSize(Window, &window_w, &window_h);
int drawable_w, drawable_h;
SDL_GL_GetDrawableSize(Window, &drawable_w, &drawable_h);
ScaleX = (float)drawable_w / (float)window_w;
ScaleY = (float)drawable_h / (float)window_h;
Width = drawable_w;
Height = drawable_h;
}
//! Get the display density in dots per inch.
float CIrrDeviceSDL::getDisplayDensity() const
{
// assume 96 dpi
return std::max(ScaleX * 96.0f, ScaleY * 96.0f);
}
void CIrrDeviceSDL::SwapWindow()
{
SDL_GL_SwapWindow(Window);
}
//! pause execution temporarily
void CIrrDeviceSDL::yield()
{
SDL_Delay(0);
}
//! pause execution for a specified time
void CIrrDeviceSDL::sleep(u32 timeMs, bool pauseTimer)
{
const bool wasStopped = Timer ? Timer->isStopped() : true;
if (pauseTimer && !wasStopped)
Timer->stop();
SDL_Delay(timeMs);
if (pauseTimer && !wasStopped)
Timer->start();
}
//! sets the caption of the window
void CIrrDeviceSDL::setWindowCaption(const wchar_t *text)
{
core::stringc textc;
core::wStringToUTF8(textc, text);
SDL_SetWindowTitle(Window, textc.c_str());
}
//! Sets the window icon.
bool CIrrDeviceSDL::setWindowIcon(const video::IImage *img)
{
if (!Window)
return false;
u32 height = img->getDimension().Height;
u32 width = img->getDimension().Width;
SDL_Surface *surface = SDL_CreateRGBSurface(0, width, height, 32,
0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000);
if (!surface) {
os::Printer::log("Failed to create SDL suface", ELL_ERROR);
return false;
}
SDL_LockSurface(surface);
bool succ = img->copyToNoScaling(surface->pixels, width, height, video::ECF_A8R8G8B8, surface->pitch);
SDL_UnlockSurface(surface);
if (!succ) {
os::Printer::log("Could not copy icon image. Is the format not ECF_A8R8G8B8?", ELL_ERROR);
SDL_FreeSurface(surface);
return false;
}
SDL_SetWindowIcon(Window, surface);
SDL_FreeSurface(surface);
return true;
}
//! notifies the device that it should close itself
void CIrrDeviceSDL::closeDevice()
{
Close = true;
}
//! Sets if the window should be resizable in windowed mode.
void CIrrDeviceSDL::setResizable(bool resize)
{
#ifdef _IRR_EMSCRIPTEN_PLATFORM_
os::Printer::log("Resizable not available on the web.", ELL_WARNING);
return;
#else // !_IRR_EMSCRIPTEN_PLATFORM_
if (resize != Resizable) {
if (Window) {
SDL_SetWindowResizable(Window, (SDL_bool)resize);
}
Resizable = resize;
}
#endif // !_IRR_EMSCRIPTEN_PLATFORM_
}
//! Minimizes window if possible
void CIrrDeviceSDL::minimizeWindow()
{
if (Window)
SDL_MinimizeWindow(Window);
}
//! Maximize window
void CIrrDeviceSDL::maximizeWindow()
{
if (Window)
SDL_MaximizeWindow(Window);
}
//! Get the position of this window on screen
core::position2di CIrrDeviceSDL::getWindowPosition()
{
return core::position2di(-1, -1);
}
//! Restore original window size
void CIrrDeviceSDL::restoreWindow()
{
if (Window)
SDL_RestoreWindow(Window);
}
bool CIrrDeviceSDL::isWindowMaximized() const
{
return Window && (SDL_GetWindowFlags(Window) & SDL_WINDOW_MAXIMIZED) != 0;
}
bool CIrrDeviceSDL::isFullscreen() const
{
if (!Window)
return false;
u32 flags = SDL_GetWindowFlags(Window);
return (flags & SDL_WINDOW_FULLSCREEN) != 0 ||
(flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0;
}
u32 CIrrDeviceSDL::getFullscreenFlag(bool fullscreen)
{
if (!fullscreen)
return 0;
#ifdef _IRR_EMSCRIPTEN_PLATFORM_
return SDL_WINDOW_FULLSCREEN;
#else
return SDL_WINDOW_FULLSCREEN_DESKTOP;
#endif
}
bool CIrrDeviceSDL::setFullscreen(bool fullscreen)
{
if (!Window)
return false;
// The SDL wiki says that this may trigger SDL_RENDER_TARGETS_RESET, but
// looking at the SDL source, this only happens with D3D, so it's not
// relevant to us.
bool success = SDL_SetWindowFullscreen(Window, getFullscreenFlag(fullscreen)) == 0;
if (!success)
os::Printer::log("SDL_SetWindowFullscreen failed", SDL_GetError(), ELL_ERROR);
return success;
}
bool CIrrDeviceSDL::isWindowVisible() const
{
return !IsInBackground;
}
//! returns if window is active. if not, nothing need to be drawn
bool CIrrDeviceSDL::isWindowActive() const
{
#ifdef _IRR_EMSCRIPTEN_PLATFORM_
// Hidden test only does something in some browsers (when tab in background or window is minimized)
// In other browsers code automatically doesn't seem to be called anymore.
EmscriptenVisibilityChangeEvent emVisibility;
if (emscripten_get_visibility_status(&emVisibility) == EMSCRIPTEN_RESULT_SUCCESS) {
if (emVisibility.hidden)
return false;
}
#endif
const u32 windowFlags = SDL_GetWindowFlags(Window);
return windowFlags & SDL_WINDOW_SHOWN && windowFlags & SDL_WINDOW_INPUT_FOCUS;
}
//! returns if window has focus.
bool CIrrDeviceSDL::isWindowFocused() const
{
return Window && (SDL_GetWindowFlags(Window) & SDL_WINDOW_INPUT_FOCUS) != 0;
}
//! returns if window is minimized.
bool CIrrDeviceSDL::isWindowMinimized() const
{
return Window && (SDL_GetWindowFlags(Window) & SDL_WINDOW_MINIMIZED) != 0;
}
//! returns color format of the window.
video::ECOLOR_FORMAT CIrrDeviceSDL::getColorFormat() const
{
if (Window) {
SDL_Surface *surface = SDL_GetWindowSurface(Window);
if (surface->format->BitsPerPixel == 16) {
if (surface->format->Amask != 0)
return video::ECF_A1R5G5B5;
else
return video::ECF_R5G6B5;
} else {
if (surface->format->Amask != 0)
return video::ECF_A8R8G8B8;
else
return video::ECF_R8G8B8;
}
} else
return CIrrDeviceStub::getColorFormat();
}
void CIrrDeviceSDL::createKeyMap()
{
// I don't know if this is the best method to create
// the lookuptable, but I'll leave it like that until
// I find a better version.
KeyMap.reallocate(105);
// buttons missing
// Android back button = ESC
KeyMap.push_back(SKeyMap(SDLK_AC_BACK, KEY_ESCAPE));
KeyMap.push_back(SKeyMap(SDLK_BACKSPACE, KEY_BACK));
KeyMap.push_back(SKeyMap(SDLK_TAB, KEY_TAB));
KeyMap.push_back(SKeyMap(SDLK_CLEAR, KEY_CLEAR));
KeyMap.push_back(SKeyMap(SDLK_RETURN, KEY_RETURN));
// combined modifiers missing
KeyMap.push_back(SKeyMap(SDLK_PAUSE, KEY_PAUSE));
KeyMap.push_back(SKeyMap(SDLK_CAPSLOCK, KEY_CAPITAL));
// asian letter keys missing
KeyMap.push_back(SKeyMap(SDLK_ESCAPE, KEY_ESCAPE));
// asian letter keys missing
KeyMap.push_back(SKeyMap(SDLK_SPACE, KEY_SPACE));
KeyMap.push_back(SKeyMap(SDLK_PAGEUP, KEY_PRIOR));
KeyMap.push_back(SKeyMap(SDLK_PAGEDOWN, KEY_NEXT));
KeyMap.push_back(SKeyMap(SDLK_END, KEY_END));
KeyMap.push_back(SKeyMap(SDLK_HOME, KEY_HOME));
KeyMap.push_back(SKeyMap(SDLK_LEFT, KEY_LEFT));
KeyMap.push_back(SKeyMap(SDLK_UP, KEY_UP));
KeyMap.push_back(SKeyMap(SDLK_RIGHT, KEY_RIGHT));
KeyMap.push_back(SKeyMap(SDLK_DOWN, KEY_DOWN));
// select missing
KeyMap.push_back(SKeyMap(SDLK_PRINTSCREEN, KEY_PRINT));
// execute missing
KeyMap.push_back(SKeyMap(SDLK_PRINTSCREEN, KEY_SNAPSHOT));
KeyMap.push_back(SKeyMap(SDLK_INSERT, KEY_INSERT));
KeyMap.push_back(SKeyMap(SDLK_DELETE, KEY_DELETE));
KeyMap.push_back(SKeyMap(SDLK_HELP, KEY_HELP));
KeyMap.push_back(SKeyMap(SDLK_0, KEY_KEY_0));
KeyMap.push_back(SKeyMap(SDLK_1, KEY_KEY_1));
KeyMap.push_back(SKeyMap(SDLK_2, KEY_KEY_2));
KeyMap.push_back(SKeyMap(SDLK_3, KEY_KEY_3));
KeyMap.push_back(SKeyMap(SDLK_4, KEY_KEY_4));
KeyMap.push_back(SKeyMap(SDLK_5, KEY_KEY_5));
KeyMap.push_back(SKeyMap(SDLK_6, KEY_KEY_6));
KeyMap.push_back(SKeyMap(SDLK_7, KEY_KEY_7));
KeyMap.push_back(SKeyMap(SDLK_8, KEY_KEY_8));
KeyMap.push_back(SKeyMap(SDLK_9, KEY_KEY_9));
KeyMap.push_back(SKeyMap(SDLK_a, KEY_KEY_A));
KeyMap.push_back(SKeyMap(SDLK_b, KEY_KEY_B));
KeyMap.push_back(SKeyMap(SDLK_c, KEY_KEY_C));
KeyMap.push_back(SKeyMap(SDLK_d, KEY_KEY_D));
KeyMap.push_back(SKeyMap(SDLK_e, KEY_KEY_E));
KeyMap.push_back(SKeyMap(SDLK_f, KEY_KEY_F));
KeyMap.push_back(SKeyMap(SDLK_g, KEY_KEY_G));
KeyMap.push_back(SKeyMap(SDLK_h, KEY_KEY_H));
KeyMap.push_back(SKeyMap(SDLK_i, KEY_KEY_I));
KeyMap.push_back(SKeyMap(SDLK_j, KEY_KEY_J));
KeyMap.push_back(SKeyMap(SDLK_k, KEY_KEY_K));
KeyMap.push_back(SKeyMap(SDLK_l, KEY_KEY_L));
KeyMap.push_back(SKeyMap(SDLK_m, KEY_KEY_M));
KeyMap.push_back(SKeyMap(SDLK_n, KEY_KEY_N));
KeyMap.push_back(SKeyMap(SDLK_o, KEY_KEY_O));
KeyMap.push_back(SKeyMap(SDLK_p, KEY_KEY_P));
KeyMap.push_back(SKeyMap(SDLK_q, KEY_KEY_Q));
KeyMap.push_back(SKeyMap(SDLK_r, KEY_KEY_R));
KeyMap.push_back(SKeyMap(SDLK_s, KEY_KEY_S));
KeyMap.push_back(SKeyMap(SDLK_t, KEY_KEY_T));
KeyMap.push_back(SKeyMap(SDLK_u, KEY_KEY_U));
KeyMap.push_back(SKeyMap(SDLK_v, KEY_KEY_V));
KeyMap.push_back(SKeyMap(SDLK_w, KEY_KEY_W));
KeyMap.push_back(SKeyMap(SDLK_x, KEY_KEY_X));
KeyMap.push_back(SKeyMap(SDLK_y, KEY_KEY_Y));
KeyMap.push_back(SKeyMap(SDLK_z, KEY_KEY_Z));
KeyMap.push_back(SKeyMap(SDLK_LGUI, KEY_LWIN));
KeyMap.push_back(SKeyMap(SDLK_RGUI, KEY_RWIN));
// apps missing
KeyMap.push_back(SKeyMap(SDLK_POWER, KEY_SLEEP)); //??
KeyMap.push_back(SKeyMap(SDLK_KP_0, KEY_NUMPAD0));
KeyMap.push_back(SKeyMap(SDLK_KP_1, KEY_NUMPAD1));
KeyMap.push_back(SKeyMap(SDLK_KP_2, KEY_NUMPAD2));
KeyMap.push_back(SKeyMap(SDLK_KP_3, KEY_NUMPAD3));
KeyMap.push_back(SKeyMap(SDLK_KP_4, KEY_NUMPAD4));
KeyMap.push_back(SKeyMap(SDLK_KP_5, KEY_NUMPAD5));
KeyMap.push_back(SKeyMap(SDLK_KP_6, KEY_NUMPAD6));
KeyMap.push_back(SKeyMap(SDLK_KP_7, KEY_NUMPAD7));
KeyMap.push_back(SKeyMap(SDLK_KP_8, KEY_NUMPAD8));
KeyMap.push_back(SKeyMap(SDLK_KP_9, KEY_NUMPAD9));
KeyMap.push_back(SKeyMap(SDLK_KP_MULTIPLY, KEY_MULTIPLY));
KeyMap.push_back(SKeyMap(SDLK_KP_PLUS, KEY_ADD));
KeyMap.push_back(SKeyMap(SDLK_KP_ENTER, KEY_RETURN));
KeyMap.push_back(SKeyMap(SDLK_KP_MINUS, KEY_SUBTRACT));
KeyMap.push_back(SKeyMap(SDLK_KP_PERIOD, KEY_DECIMAL));
KeyMap.push_back(SKeyMap(SDLK_KP_DIVIDE, KEY_DIVIDE));
KeyMap.push_back(SKeyMap(SDLK_F1, KEY_F1));
KeyMap.push_back(SKeyMap(SDLK_F2, KEY_F2));
KeyMap.push_back(SKeyMap(SDLK_F3, KEY_F3));
KeyMap.push_back(SKeyMap(SDLK_F4, KEY_F4));
KeyMap.push_back(SKeyMap(SDLK_F5, KEY_F5));
KeyMap.push_back(SKeyMap(SDLK_F6, KEY_F6));
KeyMap.push_back(SKeyMap(SDLK_F7, KEY_F7));
KeyMap.push_back(SKeyMap(SDLK_F8, KEY_F8));
KeyMap.push_back(SKeyMap(SDLK_F9, KEY_F9));
KeyMap.push_back(SKeyMap(SDLK_F10, KEY_F10));
KeyMap.push_back(SKeyMap(SDLK_F11, KEY_F11));
KeyMap.push_back(SKeyMap(SDLK_F12, KEY_F12));
KeyMap.push_back(SKeyMap(SDLK_F13, KEY_F13));
KeyMap.push_back(SKeyMap(SDLK_F14, KEY_F14));
KeyMap.push_back(SKeyMap(SDLK_F15, KEY_F15));
// no higher F-keys
KeyMap.push_back(SKeyMap(SDLK_NUMLOCKCLEAR, KEY_NUMLOCK));
KeyMap.push_back(SKeyMap(SDLK_SCROLLLOCK, KEY_SCROLL));
KeyMap.push_back(SKeyMap(SDLK_LSHIFT, KEY_LSHIFT));
KeyMap.push_back(SKeyMap(SDLK_RSHIFT, KEY_RSHIFT));
KeyMap.push_back(SKeyMap(SDLK_LCTRL, KEY_LCONTROL));
KeyMap.push_back(SKeyMap(SDLK_RCTRL, KEY_RCONTROL));
KeyMap.push_back(SKeyMap(SDLK_LALT, KEY_LMENU));
KeyMap.push_back(SKeyMap(SDLK_RALT, KEY_RMENU));
KeyMap.push_back(SKeyMap(SDLK_PLUS, KEY_PLUS));
KeyMap.push_back(SKeyMap(SDLK_COMMA, KEY_COMMA));
KeyMap.push_back(SKeyMap(SDLK_MINUS, KEY_MINUS));
KeyMap.push_back(SKeyMap(SDLK_PERIOD, KEY_PERIOD));
// some special keys missing
KeyMap.sort();
}
void CIrrDeviceSDL::CCursorControl::initCursors()
{
Cursors.reserve(gui::ECI_COUNT);
Cursors.emplace_back(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW)); // ECI_NORMAL
Cursors.emplace_back(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_CROSSHAIR)); // ECI_CROSS
Cursors.emplace_back(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND)); // ECI_HAND
Cursors.emplace_back(nullptr); // ECI_HELP
Cursors.emplace_back(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM)); // ECI_IBEAM
Cursors.emplace_back(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_NO)); // ECI_NO
Cursors.emplace_back(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT)); // ECI_WAIT
Cursors.emplace_back(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEALL)); // ECI_SIZEALL
Cursors.emplace_back(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENESW)); // ECI_SIZENESW
Cursors.emplace_back(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE)); // ECI_SIZENWSE
Cursors.emplace_back(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS)); // ECI_SIZENS
Cursors.emplace_back(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZEWE)); // ECI_SIZEWE
Cursors.emplace_back(nullptr); // ECI_UP
}
} // end namespace irr
#endif // _IRR_COMPILE_WITH_SDL_DEVICE_