Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1a69378
SDL3 Input: Complete backport with Gamepad support and hardening
githubawn Apr 20, 2026
8df9b86
SDL3 Input: Complete backport with Gamepad support and hardening
githubawn Apr 20, 2026
e3b674b
added build guards
githubawn Apr 20, 2026
2499e0b
greptile feedback
githubawn Apr 20, 2026
f8fb43f
greptile feedback
githubawn Apr 20, 2026
4ac91e0
fixed build error
githubawn Apr 20, 2026
a0ac8f4
greptile feedback
githubawn Apr 20, 2026
d9d9ff0
added vcpkg
githubawn Apr 20, 2026
d048867
vcpkg fix
githubawn Apr 20, 2026
534e694
changed dpad group numbers
githubawn Apr 20, 2026
09fd4e6
switch back to static linking
githubawn Apr 20, 2026
9b0262c
move SDL3_image out SDL3input
githubawn Apr 21, 2026
3a40b11
properly fail on SDL_INIT failure
githubawn Apr 21, 2026
4d82b81
removed custom ICO decoder
githubawn Apr 21, 2026
884218b
fixes SDL_WarpMouseInWindow mismatch
githubawn Apr 21, 2026
b268cda
removed some dead declarations
githubawn Apr 21, 2026
a7883e8
temporary re-added custom decoder
githubawn Apr 21, 2026
038aea0
dont early return for headless mode
githubawn Apr 22, 2026
390e2ee
applied clang to files new in this PR
githubawn May 14, 2026
0ead8ea
Merge remote-tracking branch 'origin/main' into feature/sdl3-input-ba…
githubawn May 14, 2026
6f04138
Merge remote-tracking branch 'upstream/main' into feature/sdl3-input-…
githubawn May 14, 2026
ed87bcb
updated SDL to latest version
githubawn Jun 1, 2026
c0f22b9
renamed SAGE_USE_SDL3 to RTS_SDL3_ENABLE
githubawn Jun 1, 2026
e578a8b
cleaned up comments
githubawn Jun 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ include(cmake/config.cmake)
include(cmake/gamespy.cmake)
include(cmake/lzhl.cmake)

if(RTS_SDL3_ENABLE AND NOT IS_VS6_BUILD)
include(cmake/sdl3.cmake)
endif()

if (IS_VS6_BUILD)
# The original max sdk does not compile against a modern compiler.
# If there is a desire to make this work, then a fixed max sdk needs to be created.
Expand Down
5 changes: 5 additions & 0 deletions Core/GameEngine/Include/GameClient/Display.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ class Display : public SubsystemInterface
virtual UnsignedInt getBitDepth() { return m_bitDepth; }
virtual void setWindowed( Bool windowed ) { m_windowed = windowed; } ///< set windowed/fullscreen flag
virtual Bool getWindowed() { return m_windowed; } ///< return widowed/fullscreen flag

#if RTS_SDL3_ENABLE
virtual Bool getViewportRect( Int& x, Int& y, Int& width, Int& height ) const { return FALSE; }
#endif

virtual Bool setDisplayMode( UnsignedInt xres, UnsignedInt yres, UnsignedInt bitdepth, Bool windowed ); ///<sets screen resolution/mode
virtual Int getDisplayModeCount() {return 0;} ///<return number of display modes/resolutions supported by video card.
virtual void getDisplayModeDescription(Int modeIndex, Int *xres, Int *yres, Int *bitDepth) {} ///<return description of mode
Expand Down
20 changes: 20 additions & 0 deletions Core/GameEngineDevice/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,18 @@ set(GAMEENGINEDEVICE_SRC
Source/Win32Device/GameClient/Win32Mouse.cpp
)

# Add Core-level SDL3 implementation
if(RTS_SDL3_ENABLE AND NOT IS_VS6_BUILD)
list(APPEND GAMEENGINEDEVICE_SRC
Include/SDL3GameEngine.h
Include/SDL3Device/GameClient/SDL3Input.h
Include/SDL3Device/GameClient/SDL3Cursor.h
Source/SDL3GameEngine.cpp
Source/SDL3Device/GameClient/SDL3Input.cpp
Source/SDL3Device/GameClient/SDL3Cursor.cpp
)
endif()

# Add C++ 17 FileSystem implementation for non-VS6 builds
if(NOT IS_VS6_BUILD)
list(APPEND GAMEENGINEDEVICE_SRC
Expand Down Expand Up @@ -229,6 +241,14 @@ target_link_libraries(corei_gameenginedevice_public INTERFACE
milesstub
)

# Export SDL3 dependencies for modern builds
if(RTS_SDL3_ENABLE AND NOT IS_VS6_BUILD)
target_link_libraries(corei_gameenginedevice_public INTERFACE
SDL3::SDL3
SDL3_image::SDL3_image
)
endif()

if(RTS_BUILD_OPTION_FFMPEG)
find_package(FFMPEG REQUIRED)

Expand Down
63 changes: 63 additions & 0 deletions Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Cursor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2026 TheSuperHackers
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

// Derived from the GeneralsX branch by fbraz3

#pragma once

#include "Lib/BaseType.h"
#include <SDL3/SDL.h>
#include <array>

// USER INCLUDES
#include "GameClient/Mouse.h"

struct AnimatedCursor
{
SDL_Cursor* m_cursor;

AnimatedCursor()
: m_cursor(nullptr)
{}
~AnimatedCursor()
{
if (m_cursor)
{
SDL_DestroyCursor(m_cursor);
m_cursor = nullptr;
}
}

SDL_Cursor* getCursor() const { return m_cursor; }
};

class SDL3CursorManager
{
public:
static void init();
static void shutdown();

static SDL_Cursor* getCursor(Mouse::MouseCursor cursor, int direction);

// Internal loader used by Mouse implementation
static void initResources(Mouse* mouse);

private:
static AnimatedCursor* loadANI(const char* filepath);
static AnimatedCursor* m_cursorResources[Mouse::NUM_MOUSE_CURSORS][MAX_2D_CURSOR_DIRECTIONS];
};
184 changes: 184 additions & 0 deletions Core/GameEngineDevice/Include/SDL3Device/GameClient/SDL3Input.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2026 TheSuperHackers
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

// Derived from the GeneralsX branch by fbraz3

#pragma once

#include "Lib/BaseType.h"

// SYSTEM INCLUDES
#include <SDL3/SDL.h>
#include <array>
#include <functional>

// USER INCLUDES
#include "GameClient/Mouse.h"
#include "GameClient/Keyboard.h"
#include "GameClient/KeyDefs.h"

// FORWARD REFERENCES
class SDL3InputManager;

extern SDL3InputManager* TheSDL3InputManager;

typedef KeyDefType KeyVal;

class SDL3Mouse : public Mouse
{
public:
SDL3Mouse(SDL_Window* window);
virtual ~SDL3Mouse(void);

// SubsystemInterface
virtual void init(void) override;
virtual void reset(void) override;
virtual void update(void) override;
virtual void initCursorResources(void) override;
static void freeCursorResources(void);

// Mouse interface
virtual void setCursor(MouseCursor cursor) override;
virtual void setVisibility(Bool visible) override;
virtual void loseFocus() override;
virtual void regainFocus() override;

// SDL3-specific methods
void addSDLEvent(SDL_Event* event);

protected:
virtual void capture(void) override;
virtual void releaseCapture(void) override;
virtual UnsignedByte getMouseEvent(MouseIO* result, Bool flush) override;

private:
// Event translation from SDL_Event (Clean Slate implementation)
void translateEvent(const SDL_Event& event, MouseIO* result);

// Scale raw SDL window coordinates to game internal resolution
void scaleMouseCoordinates(int rawX, int rawY, Uint32 windowID, int& scaledX, int& scaledY);

SDL_Window* m_Window;
Bool m_IsCaptured;
Bool m_IsVisible;
Bool m_LostFocus;

Int m_directionFrame;

float m_accumulatedDeltaX;
float m_accumulatedDeltaY;

SDL_Cursor* m_activeSDLCursor;
};

class SDL3Keyboard : public Keyboard
{
public:
SDL3Keyboard(void);
virtual ~SDL3Keyboard(void);

// SubsystemInterface
virtual void init(void) override;
virtual void reset(void) override;
virtual void update(void) override;

// Keyboard interface
virtual Bool getCapsState(void) override;

// SDL3-specific methods
void addSDLEvent(SDL_Event* event);

protected:
virtual void getKey(KeyboardIO* key) override;
virtual KeyVal translateScanCodeToKeyVal(unsigned char scan);

private:
void translateKeyEvent(const SDL_KeyboardEvent& event);
};

class SDL3InputManager
{
public:
SDL3InputManager(SDL_Window* window);
virtual ~SDL3InputManager();

void update();

// Buffer access
Bool getNextMouseEvent(SDL_Event& outEvent);
Bool getNextKeyboardEvent(SDL_Event& outEvent);

void addMouseSDLEvent(const SDL_Event& event);
void addKeyboardSDLEvent(const SDL_Event& event);

Bool isQuitting() const { return m_isQuitting; }

// Constants
static constexpr float AXIS_MAX = 32767.0f;
static constexpr int TRIGGER_THRESHOLD = 16384;
static constexpr float DEFAULT_DEADZONE = 0.15f;
static constexpr float DEFAULT_CURSOR_SPEED = 800.0f;

private:
struct GamepadState
{
bool buttonState[SDL_GAMEPAD_BUTTON_COUNT];
bool stickLeft, stickRight, stickUp, stickDown;
bool ltDown, rtDown;

GamepadState()
{
memset(buttonState, 0, sizeof(buttonState));
stickLeft = stickRight = stickUp = stickDown = false;
ltDown = rtDown = false;
}
};

private:
// Gamepad management
void openFirstGamepad();
void closeGamepad();

SDL_Window* m_window;
SDL_Gamepad* m_gamepad;
void processGamepadInput();
void handleGamepadButton(SDL_GamepadButton button, bool& currentState, bool isDown, std::function<void(bool)> action);

// Virtual event injection
void virtualPulseKey(SDL_Scancode scancode, bool down);
void virtualPulseMouse(Uint8 button, bool down);

// Event buffers
static const UnsignedInt MAX_MOUSE_EVENTS = 256;
static const UnsignedInt MAX_KEY_EVENTS = 256;

SDL_Event m_mouseEvents[MAX_MOUSE_EVENTS];
UnsignedInt m_mouseNextFree;
UnsignedInt m_mouseNextGet;

SDL_Event m_keyEvents[MAX_KEY_EVENTS];
UnsignedInt m_keyNextFree;
UnsignedInt m_keyNextGet;

// Gamepad state
GamepadState m_state;

Bool m_precisionMode;
Uint64 m_lastUpdateTime;
Bool m_isQuitting;
};
87 changes: 87 additions & 0 deletions Core/GameEngineDevice/Include/SDL3GameEngine.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2026 TheSuperHackers
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

// Derived from the GeneralsX branch by fbraz3

#pragma once

#include "Lib/BaseType.h"

#include "Common/GameEngine.h"
#include <SDL3/SDL.h>

// EXTERNALS
// SDL3 window typically provided by WinMain integration
extern SDL_Window* TheSDL3Window;

// Forward declarations for base classes
class AudioManager;
class Mouse;
class Keyboard;
class GameWindow;
class LocalFileSystem;
class ArchiveFileSystem;
class ThingFactory;
class ModuleFactory;
class FunctionLexicon;
class Radar;
class WebBrowser;
class ParticleSystemManager;

class SDL3GameEngine : public GameEngine
{
public:
SDL3GameEngine();
virtual ~SDL3GameEngine();

// GameEngine interface
virtual void init(void) override;
virtual void reset(void) override;
virtual void update(void) override;
virtual void serviceWindowsOS(void) override;
virtual Bool isActive(void) override;
virtual void setIsActive(Bool isActive) override;

// Factory methods (override GameEngine)
virtual LocalFileSystem* createLocalFileSystem(void) override;
virtual ArchiveFileSystem* createArchiveFileSystem(void) override;
virtual GameLogic* createGameLogic(void) override;
virtual GameClient* createGameClient(void) override;
virtual ModuleFactory* createModuleFactory(void) override;
virtual ThingFactory* createThingFactory(void) override;
virtual FunctionLexicon* createFunctionLexicon(void) override;
virtual Radar* createRadar(Bool dummy) override;
virtual WebBrowser* createWebBrowser(void) override;
virtual ParticleSystemManager* createParticleSystemManager(Bool dummy) override;
virtual AudioManager* createAudioManager(Bool dummy) override;

// SDL3 specific
virtual SDL_Window* getSDLWindow(void) const { return m_SDLWindow; }
virtual void forwardTextInputEvent(const char* utf8Text);

protected:
SDL_Window* m_SDLWindow;
Bool m_IsInitialized;
Bool m_IsActive;
Bool m_IsTextInputActive;
GameWindow* m_TextInputFocusWindow;

// Event processing
void pollSDL3Events(void);
void updateTextInputState(void);
};
Loading
Loading