diff --git a/engine/platform/platform.h b/engine/platform/platform.h index e9ea15e3..820c7c86 100644 --- a/engine/platform/platform.h +++ b/engine/platform/platform.h @@ -58,6 +58,7 @@ char *Posix_Input( void ); #if XASH_SDL void SDLash_Init( const char *basedir ); void SDLash_Shutdown( void ); +void SDLash_NanoSleep( int nsec ); #endif #if XASH_ANDROID @@ -171,9 +172,11 @@ static inline void Platform_SetupSigtermHandling( void ) static inline qboolean Platform_NanoSleep( int nsec ) { +#if XASH_SDL == 3 + SDLash_NanoSleep( nsec ); + return true; // SDL2 doesn't have nanosleep, so use low-level functions here - // When this code will be ported to SDL3, use SDL_DelayNS -#if XASH_POSIX +#elif XASH_POSIX struct timespec ts = { .tv_sec = 0, .tv_nsec = nsec, // just don't put large numbers here diff --git a/engine/platform/sdl3/in_sdl3.c b/engine/platform/sdl3/in_sdl3.c new file mode 100644 index 00000000..2efa61d7 --- /dev/null +++ b/engine/platform/sdl3/in_sdl3.c @@ -0,0 +1,215 @@ +/* +platform_sdl3.h - SDL3 platform definitions +Copyright (C) 2025 Er2off +Copyright (C) 2025 Alibek Omarov + +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. +*/ +#include "platform_sdl3.h" +#include "eiface.h" // ARRAYSIZE +#include "vid_common.h" // window_{width,height} +#include "client.h" // refState +#include "input.h" // Touch_WantVisibleCursor +#include "vgui_draw.h" // VGui_UpdateInternalCursorState + +static struct +{ + qboolean initialized; + SDL_Cursor *cursors[dc_last]; +} cursors; + +static struct +{ + float x, y; + qboolean pushed; +} in_visible_cursor_pos; + +void SDLash_InitCursors( void ) +{ + if( cursors.initialized ) + SDLash_FreeCursors(); + + // load up all default cursors + cursors.cursors[dc_none] = NULL; + cursors.cursors[dc_arrow] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_DEFAULT ); + cursors.cursors[dc_ibeam] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_TEXT ); + cursors.cursors[dc_hourglass] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_WAIT ); + cursors.cursors[dc_crosshair] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_CROSSHAIR ); + cursors.cursors[dc_up] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_DEFAULT ); + cursors.cursors[dc_sizenwse] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_NWSE_RESIZE ); + cursors.cursors[dc_sizenesw] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_NESW_RESIZE ); + cursors.cursors[dc_sizewe] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_EW_RESIZE ); + cursors.cursors[dc_sizens] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_NS_RESIZE ); + cursors.cursors[dc_sizeall] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_MOVE ); + cursors.cursors[dc_no] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_NOT_ALLOWED ); + cursors.cursors[dc_hand] = SDL_CreateSystemCursor( SDL_SYSTEM_CURSOR_POINTER ); + cursors.initialized = true; +} + +void SDLash_FreeCursors( void ) +{ + if( !cursors.initialized ) + return; + + for( int i = 0; i < ARRAYSIZE( cursors.cursors ); i++ ) + { + if( cursors.cursors[i] ) + { + SDL_DestroyCursor( cursors.cursors[i] ); + cursors.cursors[i] = NULL; + } + } + + cursors.initialized = false; +} + +qboolean Platform_GetMouseGrab( void ) +{ + return SDL_GetWindowMouseGrab( host.hWnd ); +} + +void Platform_SetMouseGrab( qboolean enable ) +{ + SDL_SetWindowMouseGrab( host.hWnd, enable ); +} + +void Platform_MouseMove( float *x, float *y ) +{ + SDL_GetRelativeMouseState( x, y ); +} + +void GAME_EXPORT Platform_SetMousePos( int x, int y ) +{ + SDL_WarpMouseInWindow( host.hWnd, x, y ); +} + +void GAME_EXPORT Platform_GetMousePos( int *x, int *y ) +{ + float m_x = 0.0f, m_y = 0.0f; + + SDL_GetMouseState( &m_x, &m_y ); + + if( x ) + { + if( window_width.value && window_width.value != refState.width ) + { + float factor = refState.width / window_width.value; + *x = m_x * factor; + } + else *x = m_x; + } + + if( y ) + { + if( window_height.value && window_height.value != refState.height ) + { + float factor = refState.height / window_height.value; + *y = m_y * factor; + } + else *y = m_y; + } +} + +void Platform_SetCursorType( VGUI_DefaultCursor type ) +{ + qboolean visible = type != dc_user && type != dc_none; + + // never disable cursor in touch emulation mode + if( !visible && Touch_WantVisibleCursor( )) + return; + + host.mouse_visible = visible; + VGui_UpdateInternalCursorState( type ); + + if( visible ) + { + if( cursors.initialized ) + SDL_SetCursor( cursors.cursors[type] ); + + SDL_ShowCursor(); + + // restore the last mouse position + if( in_visible_cursor_pos.pushed ) + { + SDL_WarpMouseInWindow( host.hWnd, in_visible_cursor_pos.x, in_visible_cursor_pos.y ); + in_visible_cursor_pos.pushed = false; + } + } + else + { + // save last mouse position and warp it to the center + if( !in_visible_cursor_pos.pushed ) + { + SDL_GetMouseState( &in_visible_cursor_pos.x, &in_visible_cursor_pos.y ); + SDL_WarpMouseInWindow( host.hWnd, host.window_center_x, host.window_center_y ); + in_visible_cursor_pos.pushed = true; + } + + SDL_HideCursor(); + } +} + +void Platform_EnableTextInput( qboolean enable ) +{ + enable ? SDL_StartTextInput( host.hWnd ) : SDL_StopTextInput( host.hWnd ); +} + +int Platform_GetClipboardText( char *buffer, size_t size ) +{ + int len; + char *text = SDL_GetClipboardText(); + + if( !text ) + return 0; + + if( buffer && size > 0 ) + len = Q_strncpy( buffer, text, size ); + else + len = Q_strlen( text ); + + SDL_free( text ); + + return len; +} + +void Platform_SetClipboardText( const char *buffer ) +{ + SDL_SetClipboardText( buffer ); +} + +key_modifier_t Platform_GetKeyModifiers( void ) +{ + SDL_Keymod mod_flags = SDL_GetModState(); + key_modifier_t result_flags = KeyModifier_None; + + if( FBitSet( mod_flags, SDL_KMOD_LCTRL )) + SetBits( result_flags, KeyModifier_LeftCtrl ); + if( FBitSet( mod_flags, SDL_KMOD_RCTRL )) + SetBits( result_flags, KeyModifier_RightCtrl ); + if( FBitSet( mod_flags, SDL_KMOD_RSHIFT )) + SetBits( result_flags, KeyModifier_RightShift ); + if( FBitSet( mod_flags, SDL_KMOD_LSHIFT )) + SetBits( result_flags, KeyModifier_LeftShift ); + if( FBitSet( mod_flags, SDL_KMOD_LALT )) + SetBits( result_flags, KeyModifier_LeftAlt ); + if( FBitSet( mod_flags, SDL_KMOD_RALT )) + SetBits( result_flags, KeyModifier_RightAlt ); + if( FBitSet( mod_flags, SDL_KMOD_NUM )) + SetBits( result_flags, KeyModifier_NumLock ); + if( FBitSet( mod_flags, SDL_KMOD_CAPS )) + SetBits( result_flags, KeyModifier_CapsLock ); + if( FBitSet( mod_flags, SDL_KMOD_RGUI )) + SetBits( result_flags, KeyModifier_RightSuper ); + if( FBitSet( mod_flags, SDL_KMOD_LGUI )) + SetBits( result_flags, KeyModifier_LeftSuper ); + + return result_flags; +} diff --git a/engine/platform/sdl3/platform_sdl3.h b/engine/platform/sdl3/platform_sdl3.h new file mode 100644 index 00000000..f578cba2 --- /dev/null +++ b/engine/platform/sdl3/platform_sdl3.h @@ -0,0 +1,28 @@ +/* +platform_sdl3.h - SDL3 platform definitions +Copyright (C) 2025 Er2off +Copyright (C) 2025 Alibek Omarov + +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. +*/ +#ifndef PLATFORM_SDL3_H +#define PLATFORM_SDL3_H + +#include +#include "platform/platform.h" + +// +// in_sdl3.c +// +void SDLash_InitCursors( void ); +void SDLash_FreeCursors( void ); + +#endif // PLATFORM_SDL3_H diff --git a/engine/platform/sdl3/sys_sdl3.c b/engine/platform/sdl3/sys_sdl3.c new file mode 100644 index 00000000..6e60fcda --- /dev/null +++ b/engine/platform/sdl3/sys_sdl3.c @@ -0,0 +1,121 @@ +/* +platform_sdl3.h - SDL3 platform definitions +Copyright (C) 2025 Er2off +Copyright (C) 2025 Alibek Omarov + +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. +*/ +#include "platform_sdl3.h" +#include "common.h" + +#if XASH_TIMER == TIMER_SDL +double Platform_DoubleTime( void ) +{ + static Uint64 g_PerformanceFrequency; + static Uint64 g_ClockStart; + Uint64 CurrentTime; + + if( !g_PerformanceFrequency ) + { + g_PerformanceFrequency = SDL_GetPerformanceFrequency(); + g_ClockStart = SDL_GetPerformanceCounter(); + + return 0.0; + } + + CurrentTime = SDL_GetPerformanceCounter(); + return (double)( CurrentTime - g_ClockStart ) / (double)( g_PerformanceFrequency ); +} + +void Platform_Sleep( int msec ) +{ + SDL_Delay( msec ); +} +#endif // XASH_TIMER == TIMER_SDL + +#if XASH_MESSAGEBOX == MSGBOX_SDL +void Platform_MessageBox( const char *title, const char *message, qboolean parentMainWindow ) +{ + SDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR, title, message, parentMainWindow ? host.hWnd : NULL ); +} +#endif + +void SDLash_NanoSleep( int nsec ) +{ + SDL_DelayNS( nsec ); +} + +static const char *SDLash_CategoryToString( int category ) +{ + switch( category ) + { + case SDL_LOG_CATEGORY_APPLICATION: return "App"; + case SDL_LOG_CATEGORY_ERROR: return "Error"; + case SDL_LOG_CATEGORY_ASSERT: return "Assert"; + case SDL_LOG_CATEGORY_SYSTEM: return "System"; + case SDL_LOG_CATEGORY_AUDIO: return "Audio"; + case SDL_LOG_CATEGORY_VIDEO: return "Video"; + case SDL_LOG_CATEGORY_RENDER: return "Render"; + case SDL_LOG_CATEGORY_INPUT: return "Input"; + case SDL_LOG_CATEGORY_TEST: return "Test"; + case SDL_LOG_CATEGORY_GPU: return "GPU"; + default: return "Unknown"; + } +} + +static void SDLCALL SDLash_LogOutputFunction( void *userdata, int category, SDL_LogPriority priority, const char *message ) +{ + switch( priority ) + { + case SDL_LOG_PRIORITY_CRITICAL: + case SDL_LOG_PRIORITY_ERROR: + Con_Printf( S_ERROR S_BLUE "SDL" S_DEFAULT ": [%s] %s\n", SDLash_CategoryToString( category ), message ); + break; + case SDL_LOG_PRIORITY_WARN: + Con_DPrintf( S_WARN S_BLUE "SDL" S_DEFAULT ": [%s] %s\n", SDLash_CategoryToString( category ), message ); + break; + case SDL_LOG_PRIORITY_INFO: + Con_Reportf( S_NOTE S_BLUE "SDL" S_DEFAULT ": [%s] %s\n", SDLash_CategoryToString( category ), message ); + break; + default: + Con_Reportf( S_BLUE "SDL" S_DEFAULT ": [%s] %s\n", SDLash_CategoryToString( category ), message ); + break; + } +} + +void SDLash_Init( const char *basedir ) +{ + SDL_SetLogOutputFunction( SDLash_LogOutputFunction, NULL ); + + if( host_developer.value >= 2 ) + SDL_SetLogPriorities( SDL_LOG_PRIORITY_VERBOSE ); + else if( host_developer.value >= 1 ) + SDL_SetLogPriorities( SDL_LOG_PRIORITY_WARN ); + else + SDL_SetLogPriorities( SDL_LOG_PRIORITY_ERROR ); + + if( !SDL_Init( SDL_INIT_VIDEO | SDL_INIT_EVENTS )) + { + Sys_Warn( "SDL_Init failed: %s", SDL_GetError( )); + host.type = HOST_DEDICATED; + } + + SDL_SetHint( SDL_HINT_MOUSE_TOUCH_EVENTS, "0" ); + SDL_SetHint( SDL_HINT_TOUCH_MOUSE_EVENTS, "0" ); + + SDLash_InitCursors(); +} + +void SDLash_Shutdown( void ) +{ + SDLash_FreeCursors(); + SDL_Quit(); +}