Compare commits

...

8 Commits

Author SHA1 Message Date
Sepalani
c66b5943a5 fixup! IOS/KBD: Add keyboard support for SDL3 backend 2025-05-25 15:36:54 +04:00
Sepalani
49fc7e78cc IOS/USB: Add "Connect USB Keyboard" support for HID 2025-05-24 23:15:23 +04:00
Sepalani
074add14ac Keyboard: Add a configuration window
Add MAIN_WII_KEYBOARD_TRANSLATION to toggle the layout translation
2025-05-24 23:14:47 +04:00
Sepalani
3a0aed2560 Common/Keyboard: Add keyboard layout support and partial translation 2025-05-24 23:14:47 +04:00
Sepalani
3469727435 IOS/KBD: Replace Windows implementation with SDL 2025-05-24 22:58:05 +04:00
Sepalani
80b8b52387 IOS/KBD: Add keyboard support for SDL3 backend 2025-05-24 22:50:05 +04:00
Sepalani
757d03896f IOS/KBD: Move keyboard logic to Common/Keyboard
The keypress loop was simplified and its keyboard state buffer removed.
2025-05-24 22:15:41 +04:00
Jordan Woyak
2fb1f17c89 InputCommon: Update to use SDL3 and bump the SDL submodule in Externals to release-3.2.14. 2025-05-23 18:39:11 -05:00
38 changed files with 1630 additions and 671 deletions

View File

@ -137,6 +137,8 @@ else()
endif()
if(APPLE)
enable_language(OBJC)
enable_language(OBJCXX)
option(MACOS_USE_DEFAULT_SEARCH_PATH "Don't prioritize system library paths" OFF)
option(SKIP_POSTPROCESS_BUNDLE "Skip postprocessing bundle for redistributability" OFF)
# Enable adhoc code signing by default (otherwise makefile builds on ARM will not work)
@ -602,7 +604,7 @@ if(UNIX)
endif()
if(ENABLE_SDL)
dolphin_find_optional_system_library(SDL2 Externals/SDL 2.30.9)
dolphin_find_optional_system_library(SDL3 Externals/SDL 3.2.0)
endif()
if(ENABLE_ANALYTICS)

View File

@ -1,34 +1,16 @@
option(SDL2_DISABLE_SDL2MAIN "" ON)
option(SDL2_DISABLE_INSTALL "" ON)
option(SDL2_DISABLE_UNINSTALL "" ON)
option(SDL_SHARED "Build a shared version of the library" OFF)
option(SDL_SHARED_ENABLED_BY_DEFAULT "" OFF)
option(SDL_STATIC "Build a static version of the library" ON)
option(SDL_STATIC_ENABLED_BY_DEFAULT "" ON)
option(SDL_TEST "Build the SDL2_test library" OFF)
option(SDL_TEST_ENABLED_BY_DEFAULT "" OFF)
# SDL fails to clean up old headers after version upgrades, so do that manually
set(EXPECTED_SDL_REVISION "SDL-release-2.30.9-0")
if (EXISTS "${CMAKE_CURRENT_BINARY_DIR}/SDL/include/SDL2/SDL_revision.h")
file(READ "${CMAKE_CURRENT_BINARY_DIR}/SDL/include/SDL2/SDL_revision.h" ACTUAL_SDL_REVISION)
if (NOT "${ACTUAL_SDL_REVISION}" MATCHES "${EXPECTED_SDL_REVISION}")
message(STATUS "Found unexpected SDL2/SDL_revision.h, removing generated includes.")
file(REMOVE_RECURSE "${CMAKE_CURRENT_BINARY_DIR}/SDL/include/")
endif()
endif()
if (EXISTS "${CMAKE_CURRENT_BINARY_DIR}/SDL/include/SDL_revision.h")
file(READ "${CMAKE_CURRENT_BINARY_DIR}/SDL/include/SDL_revision.h" ACTUAL_SDL_REVISION)
if (NOT "${ACTUAL_SDL_REVISION}" MATCHES "${EXPECTED_SDL_REVISION}")
message(STATUS "Found unexpected SDL_revision.h, removing generated includes.")
file(REMOVE_RECURSE "${CMAKE_CURRENT_BINARY_DIR}/SDL/include/")
endif()
endif()
set(SDL_SHARED OFF)
set(SDL_STATIC ON)
set(SDL_TEST_LIBRARY OFF)
set(SDL_TESTS OFF)
set(SDL_DISABLE_INSTALL ON)
set(SDL_DISABLE_INSTALL_DOCS ON)
set(SDL_INSTALL_TESTS OFF)
add_subdirectory(SDL)
if (TARGET SDL2)
dolphin_disable_warnings(SDL2)
if (TARGET SDL3)
dolphin_disable_warnings(SDL3)
endif()
if (TARGET SDL2-static)
dolphin_disable_warnings(SDL2-static)
if (TARGET SDL3-static)
dolphin_disable_warnings(SDL3-static)
endif()

2
Externals/SDL/SDL vendored

@ -1 +1 @@
Subproject commit c98c4fbff6d8f3016a3ce6685bf8f43433c3efcc
Subproject commit 8d604353a53853fa56d1bdce0363535605ca868f

View File

@ -17,147 +17,165 @@
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>SDL\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>SDL\src;SDL\include;SDL\include\build_config;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>HAVE_LIBC=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="SDL\include\begin_code.h" />
<ClInclude Include="SDL\include\close_code.h" />
<ClInclude Include="SDL\include\SDL_assert.h" />
<ClInclude Include="SDL\include\SDL_atomic.h" />
<ClInclude Include="SDL\include\SDL_audio.h" />
<ClInclude Include="SDL\include\SDL_bits.h" />
<ClInclude Include="SDL\include\SDL_blendmode.h" />
<ClInclude Include="SDL\include\SDL_clipboard.h" />
<ClInclude Include="SDL\include\SDL_config_windows.h" />
<ClInclude Include="SDL\include\SDL_config.h" />
<ClInclude Include="SDL\include\SDL_copying.h" />
<ClInclude Include="SDL\include\SDL_cpuinfo.h" />
<ClInclude Include="SDL\include\SDL_egl.h" />
<ClInclude Include="SDL\include\SDL_endian.h" />
<ClInclude Include="SDL\include\SDL_error.h" />
<ClInclude Include="SDL\include\SDL_events.h" />
<ClInclude Include="SDL\include\SDL_filesystem.h" />
<ClInclude Include="SDL\include\SDL_gamecontroller.h" />
<ClInclude Include="SDL\include\SDL_gesture.h" />
<ClInclude Include="SDL\include\SDL_guid.h" />
<ClInclude Include="SDL\include\SDL_haptic.h" />
<ClInclude Include="SDL\include\SDL_hidapi.h" />
<ClInclude Include="SDL\include\SDL_hints.h" />
<ClInclude Include="SDL\include\SDL_joystick.h" />
<ClInclude Include="SDL\include\SDL_keyboard.h" />
<ClInclude Include="SDL\include\SDL_keycode.h" />
<ClInclude Include="SDL\include\SDL_loadso.h" />
<ClInclude Include="SDL\include\SDL_locale.h" />
<ClInclude Include="SDL\include\SDL_log.h" />
<ClInclude Include="SDL\include\SDL_main.h" />
<ClInclude Include="SDL\include\SDL_messagebox.h" />
<ClInclude Include="SDL\include\SDL_metal.h" />
<ClInclude Include="SDL\include\SDL_misc.h" />
<ClInclude Include="SDL\include\SDL_mouse.h" />
<ClInclude Include="SDL\include\SDL_mutex.h" />
<ClInclude Include="SDL\include\SDL_name.h" />
<ClInclude Include="SDL\include\SDL_opengl_glext.h" />
<ClInclude Include="SDL\include\SDL_opengl.h" />
<ClInclude Include="SDL\include\SDL_opengles.h" />
<ClInclude Include="SDL\include\SDL_opengles2_gl2.h" />
<ClInclude Include="SDL\include\SDL_opengles2_gl2ext.h" />
<ClInclude Include="SDL\include\SDL_opengles2_gl2platform.h" />
<ClInclude Include="SDL\include\SDL_opengles2_khrplatform.h" />
<ClInclude Include="SDL\include\SDL_opengles2.h" />
<ClInclude Include="SDL\include\SDL_pixels.h" />
<ClInclude Include="SDL\include\SDL_platform.h" />
<ClInclude Include="SDL\include\SDL_power.h" />
<ClInclude Include="SDL\include\SDL_quit.h" />
<ClInclude Include="SDL\include\SDL_rect.h" />
<ClInclude Include="SDL\include\SDL_render.h" />
<ClInclude Include="SDL\include\SDL_revision.h" />
<ClInclude Include="SDL\include\SDL_rwops.h" />
<ClInclude Include="SDL\include\SDL_scancode.h" />
<ClInclude Include="SDL\include\SDL_sensor.h" />
<ClInclude Include="SDL\include\SDL_shape.h" />
<ClInclude Include="SDL\include\SDL_stdinc.h" />
<ClInclude Include="SDL\include\SDL_surface.h" />
<ClInclude Include="SDL\include\SDL_system.h" />
<ClInclude Include="SDL\include\SDL_syswm.h" />
<ClInclude Include="SDL\include\SDL_thread.h" />
<ClInclude Include="SDL\include\SDL_timer.h" />
<ClInclude Include="SDL\include\SDL_touch.h" />
<ClInclude Include="SDL\include\SDL_types.h" />
<ClInclude Include="SDL\include\SDL_version.h" />
<ClInclude Include="SDL\include\SDL_video.h" />
<ClInclude Include="SDL\include\SDL_vulkan.h" />
<ClInclude Include="SDL\include\SDL.h" />
<ClInclude Include="SDL\include\SDL3\SDL_begin_code.h" />
<ClInclude Include="SDL\include\SDL3\SDL_camera.h" />
<ClInclude Include="SDL\include\SDL3\SDL_close_code.h" />
<ClInclude Include="SDL\include\SDL3\SDL.h" />
<ClInclude Include="SDL\include\SDL3\SDL_assert.h" />
<ClInclude Include="SDL\include\SDL3\SDL_atomic.h" />
<ClInclude Include="SDL\include\SDL3\SDL_audio.h" />
<ClInclude Include="SDL\include\SDL3\SDL_bits.h" />
<ClInclude Include="SDL\include\SDL3\SDL_blendmode.h" />
<ClInclude Include="SDL\include\SDL3\SDL_clipboard.h" />
<ClInclude Include="SDL\include\SDL3\SDL_copying.h" />
<ClInclude Include="SDL\include\SDL3\SDL_cpuinfo.h" />
<ClInclude Include="SDL\include\SDL3\SDL_egl.h" />
<ClInclude Include="SDL\include\SDL3\SDL_endian.h" />
<ClInclude Include="SDL\include\SDL3\SDL_error.h" />
<ClInclude Include="SDL\include\SDL3\SDL_events.h" />
<ClInclude Include="SDL\include\SDL3\SDL_filesystem.h" />
<ClInclude Include="SDL\include\SDL3\SDL_gamepad.h" />
<ClInclude Include="SDL\include\SDL3\SDL_gpu.h" />
<ClInclude Include="SDL\include\SDL3\SDL_guid.h" />
<ClInclude Include="SDL\include\SDL3\SDL_haptic.h" />
<ClInclude Include="SDL\include\SDL3\SDL_hints.h" />
<ClInclude Include="SDL\include\SDL3\SDL_hidapi.h" />
<ClInclude Include="SDL\include\SDL3\SDL_asyncio.h" />
<ClInclude Include="SDL\include\SDL3\SDL_joystick.h" />
<ClInclude Include="SDL\include\SDL3\SDL_keyboard.h" />
<ClInclude Include="SDL\include\SDL3\SDL_keycode.h" />
<ClInclude Include="SDL\include\SDL3\SDL_loadso.h" />
<ClInclude Include="SDL\include\SDL3\SDL_locale.h" />
<ClInclude Include="SDL\include\SDL3\SDL_log.h" />
<ClInclude Include="SDL\include\SDL3\SDL_main.h" />
<ClInclude Include="SDL\include\SDL3\SDL_messagebox.h" />
<ClInclude Include="SDL\include\SDL3\SDL_metal.h" />
<ClInclude Include="SDL\include\SDL3\SDL_misc.h" />
<ClInclude Include="SDL\include\SDL3\SDL_mouse.h" />
<ClInclude Include="SDL\include\SDL3\SDL_mutex.h" />
<ClInclude Include="SDL\include\SDL3\SDL_opengl.h" />
<ClInclude Include="SDL\include\SDL3\SDL_opengl_glext.h" />
<ClInclude Include="SDL\include\SDL3\SDL_opengles.h" />
<ClInclude Include="SDL\include\SDL3\SDL_opengles2.h" />
<ClInclude Include="SDL\include\SDL3\SDL_opengles2_gl2.h" />
<ClInclude Include="SDL\include\SDL3\SDL_opengles2_gl2ext.h" />
<ClInclude Include="SDL\include\SDL3\SDL_opengles2_gl2platform.h" />
<ClInclude Include="SDL\include\SDL3\SDL_opengles2_khrplatform.h" />
<ClInclude Include="SDL\include\SDL3\SDL_pen.h" />
<ClInclude Include="SDL\include\SDL3\SDL_pixels.h" />
<ClInclude Include="SDL\include\SDL3\SDL_platform.h" />
<ClInclude Include="SDL\include\SDL3\SDL_platform_defines.h" />
<ClInclude Include="SDL\include\SDL3\SDL_power.h" />
<ClInclude Include="SDL\include\SDL3\SDL_process.h" />
<ClInclude Include="SDL\include\SDL3\SDL_properties.h" />
<ClInclude Include="SDL\include\SDL3\SDL_rect.h" />
<ClInclude Include="SDL\include\SDL3\SDL_render.h" />
<ClInclude Include="SDL\include\SDL3\SDL_revision.h" />
<ClInclude Include="SDL\include\SDL3\SDL_iostream.h" />
<ClInclude Include="SDL\include\SDL3\SDL_scancode.h" />
<ClInclude Include="SDL\include\SDL3\SDL_sensor.h" />
<ClInclude Include="SDL\include\SDL3\SDL_stdinc.h" />
<ClInclude Include="SDL\include\SDL3\SDL_storage.h" />
<ClInclude Include="SDL\include\SDL3\SDL_surface.h" />
<ClInclude Include="SDL\include\SDL3\SDL_system.h" />
<ClInclude Include="SDL\include\SDL3\SDL_test.h" />
<ClInclude Include="SDL\include\SDL3\SDL_test_assert.h" />
<ClInclude Include="SDL\include\SDL3\SDL_test_common.h" />
<ClInclude Include="SDL\include\SDL3\SDL_test_compare.h" />
<ClInclude Include="SDL\include\SDL3\SDL_test_crc32.h" />
<ClInclude Include="SDL\include\SDL3\SDL_test_font.h" />
<ClInclude Include="SDL\include\SDL3\SDL_test_fuzzer.h" />
<ClInclude Include="SDL\include\SDL3\SDL_test_harness.h" />
<ClInclude Include="SDL\include\SDL3\SDL_test_log.h" />
<ClInclude Include="SDL\include\SDL3\SDL_test_md5.h" />
<ClInclude Include="SDL\include\SDL3\SDL_test_memory.h" />
<ClInclude Include="SDL\include\SDL3\SDL_thread.h" />
<ClInclude Include="SDL\include\SDL3\SDL_time.h" />
<ClInclude Include="SDL\include\SDL3\SDL_timer.h" />
<ClInclude Include="SDL\include\SDL3\SDL_touch.h" />
<ClInclude Include="SDL\include\SDL3\SDL_version.h" />
<ClInclude Include="SDL\include\SDL3\SDL_video.h" />
<ClInclude Include="SDL\include\SDL3\SDL_vulkan.h" />
<ClInclude Include="SDL\src\audio\directsound\SDL_directsound.h" />
<ClInclude Include="SDL\src\audio\disk\SDL_diskaudio.h" />
<ClInclude Include="SDL\src\audio\dummy\SDL_dummyaudio.h" />
<ClInclude Include="SDL\src\audio\SDL_audio_c.h" />
<ClInclude Include="SDL\src\audio\SDL_audio_channel_converters.h" />
<ClInclude Include="SDL\src\audio\SDL_audio_resampler_filter.h" />
<ClInclude Include="SDL\src\audio\SDL_audiodev_c.h" />
<ClInclude Include="SDL\src\audio\SDL_sysaudio.h" />
<ClInclude Include="SDL\src\audio\SDL_audioqueue.h" />
<ClInclude Include="SDL\src\audio\SDL_audioresample.h" />
<ClInclude Include="SDL\src\audio\SDL_wave.h" />
<ClInclude Include="SDL\src\audio\wasapi\SDL_wasapi.h" />
<ClInclude Include="SDL\src\audio\winmm\SDL_winmm.h" />
<ClInclude Include="SDL\src\camera\SDL_camera_c.h" />
<ClInclude Include="SDL\src\camera\SDL_syscamera.h" />
<ClInclude Include="SDL\src\core\windows\SDL_directx.h" />
<ClInclude Include="SDL\src\core\windows\SDL_gameinput.h" />
<ClInclude Include="SDL\src\core\windows\SDL_hid.h" />
<ClInclude Include="SDL\src\core\windows\SDL_immdevice.h" />
<ClInclude Include="SDL\src\core\windows\SDL_windows.h" />
<ClInclude Include="SDL\src\core\windows\SDL_xinput.h" />
<ClInclude Include="SDL\src\cpuinfo\SDL_cpuinfo_c.h" />
<ClInclude Include="SDL\src\dynapi\SDL_dynapi.h" />
<ClInclude Include="SDL\src\dynapi\SDL_dynapi_overrides.h" />
<ClInclude Include="SDL\src\dynapi\SDL_dynapi_procs.h" />
<ClInclude Include="SDL\src\dynapi\SDL_dynapi.h" />
<ClInclude Include="SDL\src\dynapi\SDL_dynapi_unsupported.h" />
<ClInclude Include="SDL\src\events\blank_cursor.h" />
<ClInclude Include="SDL\src\events\default_cursor.h" />
<ClInclude Include="SDL\src\events\scancodes_ascii.h" />
<ClInclude Include="SDL\src\events\scancodes_windows.h" />
<ClInclude Include="SDL\src\events\SDL_categories_c.h" />
<ClInclude Include="SDL\src\events\SDL_clipboardevents_c.h" />
<ClInclude Include="SDL\src\events\SDL_displayevents_c.h" />
<ClInclude Include="SDL\src\events\SDL_dropevents_c.h" />
<ClInclude Include="SDL\src\events\SDL_events_c.h" />
<ClInclude Include="SDL\src\events\SDL_gesture_c.h" />
<ClInclude Include="SDL\src\events\SDL_eventwatch_c.h" />
<ClInclude Include="SDL\src\events\SDL_keyboard_c.h" />
<ClInclude Include="SDL\src\events\SDL_log.h" />
<ClInclude Include="SDL\src\events\SDL_keymap_c.h" />
<ClInclude Include="SDL\src\events\SDL_mouse_c.h" />
<ClInclude Include="SDL\src\events\SDL_touch_c.h" />
<ClInclude Include="SDL\src\events\SDL_windowevents_c.h" />
<ClInclude Include="SDL\src\filesystem\SDL_sysfilesystem.h" />
<ClInclude Include="SDL\src\gpu\SDL_sysgpu.h" />
<ClInclude Include="SDL\src\gpu\vulkan\SDL_gpu_vulkan_vkfuncs.h" />
<ClInclude Include="SDL\src\io\SDL_asyncio_c.h" />
<ClInclude Include="SDL\src\io\SDL_sysasyncio.h" />
<ClInclude Include="SDL\src\haptic\SDL_haptic_c.h" />
<ClInclude Include="SDL\src\haptic\SDL_syshaptic.h" />
<ClInclude Include="SDL\src\haptic\windows\SDL_dinputhaptic_c.h" />
<ClInclude Include="SDL\src\haptic\windows\SDL_windowshaptic_c.h" />
<ClInclude Include="SDL\src\haptic\windows\SDL_xinputhaptic_c.h" />
<ClInclude Include="SDL\src\hidapi\hidapi\hidapi.h" />
<ClInclude Include="SDL\src\hidapi\SDL_hidapi_c.h" />
<ClInclude Include="SDL\src\joystick\controller_type.h" />
<ClInclude Include="SDL\src\joystick\hidapi\steam\controller_constants.h" />
<ClInclude Include="SDL\src\joystick\hidapi\steam\controller_structs.h" />
<ClInclude Include="SDL\src\joystick\hidapi\SDL_hidapi_nintendo.h" />
<ClInclude Include="SDL\src\joystick\hidapi\SDL_hidapi_rumble.h" />
<ClInclude Include="SDL\src\joystick\hidapi\SDL_hidapijoystick_c.h" />
<ClInclude Include="SDL\src\joystick\steam\SDL_steamcontroller.h" />
<ClInclude Include="SDL\src\joystick\SDL_gamecontrollerdb.h" />
<ClInclude Include="SDL\src\joystick\hidapi\SDL_hidapi_rumble.h" />
<ClInclude Include="SDL\src\joystick\SDL_gamepad_c.h" />
<ClInclude Include="SDL\src\joystick\SDL_gamepad_db.h" />
<ClInclude Include="SDL\src\joystick\SDL_joystick_c.h" />
<ClInclude Include="SDL\src\joystick\SDL_steam_virtual_gamepad.h" />
<ClInclude Include="SDL\src\joystick\SDL_sysjoystick.h" />
<ClInclude Include="SDL\src\joystick\controller_list.h" />
<ClInclude Include="SDL\src\joystick\usb_ids.h" />
<ClInclude Include="SDL\src\joystick\virtual\SDL_virtualjoystick_c.h" />
<ClInclude Include="SDL\src\joystick\windows\SDL_dinputjoystick_c.h" />
<ClInclude Include="SDL\src\joystick\windows\SDL_rawinputjoystick_c.h" />
<ClInclude Include="SDL\src\joystick\windows\SDL_windowsjoystick_c.h" />
<ClInclude Include="SDL\src\joystick\windows\SDL_xinputjoystick_c.h" />
<ClInclude Include="SDL\src\libm\math_libm.h" />
<ClInclude Include="SDL\src\libm\math_private.h" />
<ClInclude Include="SDL\src\locale\SDL_syslocale.h" />
<ClInclude Include="SDL\src\main\SDL_main_callbacks.h" />
<ClInclude Include="SDL\src\misc\SDL_sysurl.h" />
<ClInclude Include="SDL\src\power\SDL_syspower.h" />
<ClInclude Include="SDL\src\render\direct3d\SDL_shaders_d3d.h" />
<ClInclude Include="SDL\src\render\direct3d11\SDL_shaders_d3d11.h" />
<ClInclude Include="SDL\src\render\direct3d12\SDL_render_d3d12_xbox.h" />
<ClInclude Include="SDL\src\render\direct3d12\SDL_shaders_d3d12.h" />
<ClInclude Include="SDL\src\render\opengl\SDL_glfuncs.h" />
<ClInclude Include="SDL\src\render\opengl\SDL_shaders_gl.h" />
<ClInclude Include="SDL\src\render\direct3d\SDL_shaders_d3d.h" />
<ClInclude Include="SDL\src\render\opengles2\SDL_gles2funcs.h" />
<ClInclude Include="SDL\src\render\opengles2\SDL_shaders_gles2.h" />
<ClInclude Include="SDL\src\render\opengl\SDL_glfuncs.h" />
<ClInclude Include="SDL\src\render\opengl\SDL_shaders_gl.h" />
<ClInclude Include="SDL\src\render\SDL_d3dmath.h" />
<ClInclude Include="SDL\src\render\SDL_sysrender.h" />
<ClInclude Include="SDL\src\render\SDL_yuv_sw_c.h" />
@ -170,38 +188,54 @@
<ClInclude Include="SDL\src\render\software\SDL_render_sw_c.h" />
<ClInclude Include="SDL\src\render\software\SDL_rotate.h" />
<ClInclude Include="SDL\src\render\software\SDL_triangle.h" />
<ClInclude Include="SDL\src\render\vulkan\SDL_shaders_vulkan.h" />
<ClInclude Include="SDL\src\SDL_assert_c.h" />
<ClInclude Include="SDL\src\SDL_dataqueue.h" />
<ClInclude Include="SDL\src\SDL_error_c.h" />
<ClCompile Include="SDL\src\camera\dummy\SDL_camera_dummy.c" />
<ClCompile Include="SDL\src\camera\mediafoundation\SDL_camera_mediafoundation.c" />
<ClCompile Include="SDL\src\camera\SDL_camera.c" />
<ClCompile Include="SDL\src\dialog\SDL_dialog.c" />
<ClCompile Include="SDL\src\dialog\SDL_dialog_utils.c" />
<ClCompile Include="SDL\src\filesystem\SDL_filesystem.c" />
<ClCompile Include="SDL\src\filesystem\windows\SDL_sysfsops.c" />
<ClCompile Include="SDL\src\io\windows\SDL_asyncio_windows_ioring.c" />
<ClCompile Include="SDL\src\gpu\SDL_gpu.c" />
<ClCompile Include="SDL\src\gpu\d3d12\SDL_gpu_d3d12.c" />
<ClCompile Include="SDL\src\gpu\vulkan\SDL_gpu_vulkan.c" />
<ClCompile Include="SDL\src\io\generic\SDL_asyncio_generic.c" />
<ClCompile Include="SDL\src\io\SDL_asyncio.c" />
<ClCompile Include="SDL\src\main\generic\SDL_sysmain_callbacks.c" />
<ClCompile Include="SDL\src\main\SDL_main_callbacks.c" />
<ClCompile Include="SDL\src\main\SDL_runapp.c" />
<ClCompile Include="SDL\src\main\windows\SDL_sysmain_runapp.c" />
<ClCompile Include="SDL\src\render\vulkan\SDL_render_vulkan.c" />
<ClCompile Include="SDL\src\render\vulkan\SDL_shaders_vulkan.c" />
<ClCompile Include="SDL\src\SDL_guid.c" />
<ClInclude Include="SDL\src\SDL_hashtable.h" />
<ClInclude Include="SDL\src\SDL_hints_c.h" />
<ClInclude Include="SDL\src\SDL_internal.h" />
<ClInclude Include="SDL\src\SDL_list.h" />
<ClInclude Include="SDL\src\SDL_utils_c.h" />
<ClInclude Include="SDL\src\SDL_log_c.h" />
<ClInclude Include="SDL\src\SDL_properties_c.h" />
<ClInclude Include="SDL\src\sensor\dummy\SDL_dummysensor.h" />
<ClInclude Include="SDL\src\sensor\SDL_sensor_c.h" />
<ClInclude Include="SDL\src\sensor\SDL_syssensor.h" />
<ClInclude Include="SDL\src\sensor\windows\SDL_windowssensor.h" />
<ClInclude Include="SDL\src\stdlib\SDL_vacopy.h" />
<ClInclude Include="SDL\src\thread\generic\SDL_syscond_c.h" />
<ClInclude Include="SDL\src\thread\SDL_systhread.h" />
<ClInclude Include="SDL\src\thread\SDL_thread_c.h" />
<ClInclude Include="SDL\src\thread\generic\SDL_syscond_c.h" />
<ClInclude Include="SDL\src\thread\windows\SDL_sysmutex_c.h" />
<ClInclude Include="SDL\src\thread\generic\SDL_sysrwlock_c.h" />
<ClInclude Include="SDL\src\thread\windows\SDL_systhread_c.h" />
<ClInclude Include="SDL\src\timer\SDL_timer_c.h" />
<ClInclude Include="SDL\src\video\dummy\SDL_nullevents_c.h" />
<ClInclude Include="SDL\src\video\dummy\SDL_nullframebuffer_c.h" />
<ClInclude Include="SDL\src\video\dummy\SDL_nullvideo.h" />
<ClInclude Include="SDL\src\video\khronos\vk_video\vulkan_video_codec_h264std.h" />
<ClInclude Include="SDL\src\video\khronos\vk_video\vulkan_video_codec_h264std_decode.h" />
<ClInclude Include="SDL\src\video\khronos\vk_video\vulkan_video_codec_h264std_encode.h" />
<ClInclude Include="SDL\src\video\khronos\vk_video\vulkan_video_codec_h265std.h" />
<ClInclude Include="SDL\src\video\khronos\vk_video\vulkan_video_codec_h265std_decode.h" />
<ClInclude Include="SDL\src\video\khronos\vk_video\vulkan_video_codec_h265std_encode.h" />
<ClInclude Include="SDL\src\video\khronos\vk_video\vulkan_video_codecs_common.h" />
<ClInclude Include="SDL\src\video\khronos\vulkan\vk_icd.h" />
<ClInclude Include="SDL\src\video\khronos\vulkan\vk_layer.h" />
<ClInclude Include="SDL\src\video\khronos\vulkan\vk_platform.h" />
<ClInclude Include="SDL\src\video\khronos\vulkan\vk_sdk_platform.h" />
<ClInclude Include="SDL\src\video\khronos\vulkan\vulkan.h" />
<ClInclude Include="SDL\src\video\khronos\vulkan\vulkan_android.h" />
<ClInclude Include="SDL\src\video\khronos\vulkan\vulkan_beta.h" />
<ClInclude Include="SDL\src\video\khronos\vulkan\vulkan_core.h" />
@ -211,38 +245,45 @@
<ClInclude Include="SDL\src\video\khronos\vulkan\vulkan_ios.h" />
<ClInclude Include="SDL\src\video\khronos\vulkan\vulkan_macos.h" />
<ClInclude Include="SDL\src\video\khronos\vulkan\vulkan_metal.h" />
<ClInclude Include="SDL\src\video\khronos\vulkan\vulkan_screen.h" />
<ClInclude Include="SDL\src\video\khronos\vulkan\vulkan_vi.h" />
<ClInclude Include="SDL\src\video\khronos\vulkan\vulkan_wayland.h" />
<ClInclude Include="SDL\src\video\khronos\vulkan\vulkan_win32.h" />
<ClInclude Include="SDL\src\video\khronos\vulkan\vulkan_xcb.h" />
<ClInclude Include="SDL\src\video\khronos\vulkan\vulkan_xlib_xrandr.h" />
<ClInclude Include="SDL\src\video\khronos\vulkan\vulkan_xlib.h" />
<ClInclude Include="SDL\src\video\khronos\vulkan\vulkan.h" />
<ClInclude Include="SDL\src\video\khronos\vulkan\vulkan.hpp" />
<ClInclude Include="SDL\src\video\khronos\vulkan\vulkan_xlib_xrandr.h" />
<ClInclude Include="SDL\src\video\offscreen\SDL_offscreenevents_c.h" />
<ClInclude Include="SDL\src\video\offscreen\SDL_offscreenframebuffer_c.h" />
<ClInclude Include="SDL\src\video\offscreen\SDL_offscreenopengles.h" />
<ClInclude Include="SDL\src\video\offscreen\SDL_offscreenvideo.h" />
<ClInclude Include="SDL\src\video\offscreen\SDL_offscreenvulkan.h" />
<ClInclude Include="SDL\src\video\offscreen\SDL_offscreenwindow.h" />
<ClInclude Include="SDL\src\video\SDL_blit.h" />
<ClInclude Include="SDL\src\video\SDL_blit_auto.h" />
<ClInclude Include="SDL\src\video\SDL_blit_copy.h" />
<ClInclude Include="SDL\src\video\SDL_blit_slow.h" />
<ClInclude Include="SDL\src\video\SDL_blit.h" />
<ClInclude Include="SDL\src\video\SDL_clipboard_c.h" />
<ClInclude Include="SDL\src\video\SDL_egl_c.h" />
<ClInclude Include="SDL\src\video\SDL_pixels_c.h" />
<ClInclude Include="SDL\src\video\SDL_rect_c.h" />
<ClInclude Include="SDL\src\video\SDL_RLEaccel_c.h" />
<ClInclude Include="SDL\src\video\SDL_shape_internals.h" />
<ClInclude Include="SDL\src\video\SDL_stb_c.h" />
<ClInclude Include="SDL\src\video\SDL_surface_c.h" />
<ClInclude Include="SDL\src\video\SDL_sysvideo.h" />
<ClInclude Include="SDL\src\video\SDL_vulkan_internal.h" />
<ClInclude Include="SDL\src\video\SDL_yuv_c.h" />
<ClInclude Include="SDL\src\video\windows\SDL_msctf.h" />
<ClInclude Include="SDL\src\video\windows\SDL_vkeys.h" />
<ClInclude Include="SDL\src\video\windows\SDL_surface_utils.h" />
<ClInclude Include="SDL\src\video\windows\SDL_windowsclipboard.h" />
<ClInclude Include="SDL\src\video\windows\SDL_windowsevents.h" />
<ClInclude Include="SDL\src\video\windows\SDL_windowsframebuffer.h" />
<ClInclude Include="SDL\src\video\windows\SDL_windowskeyboard.h" />
<ClInclude Include="SDL\src\video\windows\SDL_windowsgameinput.h" />
<ClInclude Include="SDL\src\video\windows\SDL_windowsmessagebox.h" />
<ClInclude Include="SDL\src\video\windows\SDL_windowsmodes.h" />
<ClInclude Include="SDL\src\video\windows\SDL_windowsmouse.h" />
<ClInclude Include="SDL\src\video\windows\SDL_windowsopengl.h" />
<ClInclude Include="SDL\src\video\windows\SDL_windowsopengles.h" />
<ClInclude Include="SDL\src\video\windows\SDL_windowsrawinput.h" />
<ClInclude Include="SDL\src\video\windows\SDL_windowsshape.h" />
<ClInclude Include="SDL\src\video\windows\SDL_windowsvideo.h" />
<ClInclude Include="SDL\src\video\windows\SDL_windowsvulkan.h" />
@ -257,8 +298,6 @@
<ClInclude Include="SDL\src\video\yuv2rgb\yuv_rgb_sse_func.h" />
<ClInclude Include="SDL\src\video\yuv2rgb\yuv_rgb_std.h" />
<ClInclude Include="SDL\src\video\yuv2rgb\yuv_rgb_std_func.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="SDL\src\atomic\SDL_atomic.c" />
<ClCompile Include="SDL\src\atomic\SDL_spinlock.c" />
<ClCompile Include="SDL\src\audio\directsound\SDL_directsound.c" />
@ -268,37 +307,49 @@
<ClCompile Include="SDL\src\audio\SDL_audiocvt.c" />
<ClCompile Include="SDL\src\audio\SDL_audiodev.c" />
<ClCompile Include="SDL\src\audio\SDL_audiotypecvt.c" />
<ClCompile Include="SDL\src\audio\SDL_audioqueue.c" />
<ClCompile Include="SDL\src\audio\SDL_audioresample.c" />
<ClCompile Include="SDL\src\audio\SDL_mixer.c" />
<ClCompile Include="SDL\src\audio\SDL_wave.c" />
<ClCompile Include="SDL\src\audio\wasapi\SDL_wasapi_win32.c" />
<ClCompile Include="SDL\src\audio\wasapi\SDL_wasapi.c" />
<ClCompile Include="SDL\src\audio\winmm\SDL_winmm.c" />
<ClCompile Include="SDL\src\core\SDL_core_unsupported.c" />
<ClCompile Include="SDL\src\core\windows\SDL_gameinput.c" />
<ClCompile Include="SDL\src\core\windows\SDL_hid.c" />
<ClCompile Include="SDL\src\core\windows\SDL_immdevice.c" />
<ClCompile Include="SDL\src\core\windows\SDL_windows.c" />
<ClCompile Include="SDL\src\core\windows\SDL_xinput.c" />
<ClCompile Include="SDL\src\cpuinfo\SDL_cpuinfo.c" />
<ClCompile Include="SDL\src\dynapi\SDL_dynapi.c" />
<ClCompile Include="SDL\src\dialog\windows\SDL_windowsdialog.c" />
<ClCompile Include="SDL\src\dynapi\SDL_dynapi.c">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
</ClCompile>
<ClCompile Include="SDL\src\events\SDL_categories.c" />
<ClCompile Include="SDL\src\events\SDL_clipboardevents.c" />
<ClCompile Include="SDL\src\events\SDL_displayevents.c" />
<ClCompile Include="SDL\src\events\SDL_dropevents.c" />
<ClCompile Include="SDL\src\events\SDL_events.c" />
<ClCompile Include="SDL\src\events\SDL_gesture.c" />
<ClCompile Include="SDL\src\events\SDL_eventwatch.c" />
<ClCompile Include="SDL\src\events\SDL_keyboard.c" />
<ClCompile Include="SDL\src\events\SDL_keymap.c" />
<ClCompile Include="SDL\src\events\SDL_mouse.c" />
<ClCompile Include="SDL\src\events\SDL_pen.c" />
<ClCompile Include="SDL\src\events\SDL_quit.c" />
<ClCompile Include="SDL\src\events\SDL_touch.c" />
<ClCompile Include="SDL\src\events\SDL_windowevents.c" />
<ClCompile Include="SDL\src\file\SDL_rwops.c" />
<ClCompile Include="SDL\src\io\SDL_iostream.c" />
<ClCompile Include="SDL\src\filesystem\windows\SDL_sysfilesystem.c" />
<ClCompile Include="SDL\src\haptic\dummy\SDL_syshaptic.c" />
<ClCompile Include="SDL\src\haptic\SDL_haptic.c" />
<ClCompile Include="SDL\src\haptic\windows\SDL_dinputhaptic.c" />
<ClCompile Include="SDL\src\haptic\windows\SDL_windowshaptic.c" />
<ClCompile Include="SDL\src\haptic\windows\SDL_xinputhaptic.c" />
<ClCompile Include="SDL\src\hidapi\SDL_hidapi.c" />
<ClCompile Include="SDL\src\joystick\controller_type.c" />
<ClCompile Include="SDL\src\joystick\dummy\SDL_sysjoystick.c" />
<ClCompile Include="SDL\src\joystick\gdk\SDL_gameinputjoystick.c" />
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapijoystick.c" />
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_combined.c" />
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_gamecube.c" />
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_luna.c" />
@ -309,23 +360,23 @@
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_shield.c" />
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_stadia.c" />
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_steam.c" />
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_steam_hori.c" />
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_steamdeck.c" />
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_switch.c" />
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_wii.c" />
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_xbox360.c" />
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_xbox360w.c" />
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapi_xboxone.c" />
<ClCompile Include="SDL\src\joystick\hidapi\SDL_hidapijoystick.c" />
<ClCompile Include="SDL\src\joystick\steam\SDL_steamcontroller.c" />
<ClCompile Include="SDL\src\joystick\SDL_gamecontroller.c" />
<ClCompile Include="SDL\src\joystick\SDL_gamepad.c" />
<ClCompile Include="SDL\src\joystick\SDL_joystick.c" />
<ClCompile Include="SDL\src\joystick\SDL_steam_virtual_gamepad.c" />
<ClCompile Include="SDL\src\joystick\virtual\SDL_virtualjoystick.c" />
<ClCompile Include="SDL\src\joystick\windows\SDL_dinputjoystick.c" />
<ClCompile Include="SDL\src\joystick\windows\SDL_rawinputjoystick.c" />
<ClCompile Include="SDL\src\joystick\windows\SDL_windows_gaming_input.c" />
<ClCompile Include="SDL\src\joystick\windows\SDL_windowsjoystick.c" />
<ClCompile Include="SDL\src\joystick\windows\SDL_windows_gaming_input.c" />
<ClCompile Include="SDL\src\joystick\windows\SDL_xinputjoystick.c" />
<ClCompile Include="SDL\src\libm\s_modf.c" />
<ClCompile Include="SDL\src\loadso\windows\SDL_sysloadso.c" />
<ClCompile Include="SDL\src\locale\SDL_locale.c" />
<ClCompile Include="SDL\src\locale\windows\SDL_syslocale.c" />
@ -333,18 +384,24 @@
<ClCompile Include="SDL\src\misc\windows\SDL_sysurl.c" />
<ClCompile Include="SDL\src\power\SDL_power.c" />
<ClCompile Include="SDL\src\power\windows\SDL_syspower.c" />
<ClCompile Include="SDL\src\render\direct3d\SDL_render_d3d.c" />
<ClCompile Include="SDL\src\render\direct3d\SDL_shaders_d3d.c" />
<ClCompile Include="SDL\src\render\direct3d11\SDL_render_d3d11.c" />
<ClCompile Include="SDL\src\process\SDL_process.c" />
<ClCompile Include="SDL\src\process\windows\SDL_windowsprocess.c" />
<ClCompile Include="SDL\src\render\direct3d11\SDL_shaders_d3d11.c" />
<ClCompile Include="SDL\src\render\direct3d12\SDL_render_d3d12.c" />
<ClCompile Include="SDL\src\render\direct3d12\SDL_shaders_d3d12.c" />
<ClCompile Include="SDL\src\render\direct3d\SDL_render_d3d.c" />
<ClCompile Include="SDL\src\render\direct3d11\SDL_render_d3d11.c" />
<ClCompile Include="SDL\src\render\direct3d\SDL_shaders_d3d.c" />
<ClCompile Include="SDL\src\render\gpu\SDL_pipeline_gpu.c" />
<ClCompile Include="SDL\src\render\gpu\SDL_render_gpu.c" />
<ClCompile Include="SDL\src\render\gpu\SDL_shaders_gpu.c" />
<ClCompile Include="SDL\src\render\opengl\SDL_render_gl.c" />
<ClCompile Include="SDL\src\render\opengl\SDL_shaders_gl.c" />
<ClCompile Include="SDL\src\render\opengles2\SDL_render_gles2.c" />
<ClCompile Include="SDL\src\render\opengles2\SDL_shaders_gles2.c" />
<ClCompile Include="SDL\src\render\SDL_d3dmath.c" />
<ClCompile Include="SDL\src\render\SDL_render.c" />
<ClCompile Include="SDL\src\render\SDL_render_unsupported.c" />
<ClCompile Include="SDL\src\render\SDL_yuv_sw.c" />
<ClCompile Include="SDL\src\render\software\SDL_blendfillrect.c" />
<ClCompile Include="SDL\src\render\software\SDL_blendline.c" />
@ -354,15 +411,15 @@
<ClCompile Include="SDL\src\render\software\SDL_render_sw.c" />
<ClCompile Include="SDL\src\render\software\SDL_rotate.c" />
<ClCompile Include="SDL\src\render\software\SDL_triangle.c" />
<ClCompile Include="SDL\src\SDL.c" />
<ClCompile Include="SDL\src\SDL_assert.c" />
<ClCompile Include="SDL\src\SDL_dataqueue.c" />
<ClCompile Include="SDL\src\SDL_error.c" />
<ClCompile Include="SDL\src\SDL_guid.c" />
<ClCompile Include="SDL\src\SDL_hashtable.c" />
<ClCompile Include="SDL\src\SDL_hints.c" />
<ClCompile Include="SDL\src\SDL_list.c" />
<ClCompile Include="SDL\src\SDL_log.c" />
<ClCompile Include="SDL\src\SDL_properties.c" />
<ClCompile Include="SDL\src\SDL_utils.c" />
<ClCompile Include="SDL\src\SDL.c" />
<ClCompile Include="SDL\src\sensor\dummy\SDL_dummysensor.c" />
<ClCompile Include="SDL\src\sensor\SDL_sensor.c" />
<ClCompile Include="SDL\src\sensor\windows\SDL_windowssensor.c" />
@ -371,23 +428,44 @@
<ClCompile Include="SDL\src\stdlib\SDL_getenv.c" />
<ClCompile Include="SDL\src\stdlib\SDL_iconv.c" />
<ClCompile Include="SDL\src\stdlib\SDL_malloc.c" />
<ClCompile Include="SDL\src\stdlib\SDL_memcpy.c" />
<ClCompile Include="SDL\src\stdlib\SDL_memmove.c" />
<ClCompile Include="SDL\src\stdlib\SDL_memset.c" />
<ClCompile Include="SDL\src\stdlib\SDL_mslibc.c" />
<ClCompile Include="SDL\src\stdlib\SDL_murmur3.c" />
<ClCompile Include="SDL\src\stdlib\SDL_qsort.c" />
<ClCompile Include="SDL\src\stdlib\SDL_random.c" />
<ClCompile Include="SDL\src\stdlib\SDL_stdlib.c" />
<ClCompile Include="SDL\src\stdlib\SDL_string.c" />
<ClCompile Include="SDL\src\stdlib\SDL_strtokr.c" />
<ClCompile Include="SDL\src\storage\generic\SDL_genericstorage.c" />
<ClCompile Include="SDL\src\storage\steam\SDL_steamstorage.c" />
<ClCompile Include="SDL\src\storage\SDL_storage.c" />
<ClCompile Include="SDL\src\thread\generic\SDL_syscond.c" />
<ClCompile Include="SDL\src\thread\generic\SDL_sysrwlock.c" />
<ClCompile Include="SDL\src\thread\SDL_thread.c" />
<ClCompile Include="SDL\src\thread\windows\SDL_syscond_cv.c" />
<ClCompile Include="SDL\src\thread\windows\SDL_sysmutex.c" />
<ClCompile Include="SDL\src\thread\windows\SDL_sysrwlock_srw.c" />
<ClCompile Include="SDL\src\thread\windows\SDL_syssem.c" />
<ClCompile Include="SDL\src\thread\windows\SDL_systhread.c" />
<ClCompile Include="SDL\src\thread\windows\SDL_systls.c" />
<ClCompile Include="SDL\src\timer\SDL_timer.c" />
<ClCompile Include="SDL\src\timer\windows\SDL_systimer.c" />
<ClCompile Include="SDL\src\time\SDL_time.c" />
<ClCompile Include="SDL\src\time\windows\SDL_systime.c" />
<ClCompile Include="SDL\src\tray\windows\SDL_tray.c" />
<ClCompile Include="SDL\src\tray\SDL_tray_utils.c" />
<ClCompile Include="SDL\src\video\dummy\SDL_nullevents.c" />
<ClCompile Include="SDL\src\video\dummy\SDL_nullframebuffer.c" />
<ClCompile Include="SDL\src\video\dummy\SDL_nullvideo.c" />
<ClCompile Include="SDL\src\video\offscreen\SDL_offscreenevents.c" />
<ClCompile Include="SDL\src\video\offscreen\SDL_offscreenframebuffer.c" />
<ClCompile Include="SDL\src\video\offscreen\SDL_offscreenopengles.c" />
<ClCompile Include="SDL\src\video\offscreen\SDL_offscreenvideo.c" />
<ClCompile Include="SDL\src\video\offscreen\SDL_offscreenvulkan.c" />
<ClCompile Include="SDL\src\video\offscreen\SDL_offscreenwindow.c" />
<ClCompile Include="SDL\src\video\SDL_blit.c" />
<ClCompile Include="SDL\src\video\SDL_blit_0.c" />
<ClCompile Include="SDL\src\video\SDL_blit_1.c" />
<ClCompile Include="SDL\src\video\SDL_blit_A.c" />
@ -395,7 +473,6 @@
<ClCompile Include="SDL\src\video\SDL_blit_copy.c" />
<ClCompile Include="SDL\src\video\SDL_blit_N.c" />
<ClCompile Include="SDL\src\video\SDL_blit_slow.c" />
<ClCompile Include="SDL\src\video\SDL_blit.c" />
<ClCompile Include="SDL\src\video\SDL_bmp.c" />
<ClCompile Include="SDL\src\video\SDL_clipboard.c" />
<ClCompile Include="SDL\src\video\SDL_egl.c" />
@ -403,21 +480,25 @@
<ClCompile Include="SDL\src\video\SDL_pixels.c" />
<ClCompile Include="SDL\src\video\SDL_rect.c" />
<ClCompile Include="SDL\src\video\SDL_RLEaccel.c" />
<ClCompile Include="SDL\src\video\SDL_shape.c" />
<ClCompile Include="SDL\src\video\SDL_stb.c" />
<ClCompile Include="SDL\src\video\SDL_stretch.c" />
<ClCompile Include="SDL\src\video\SDL_surface.c" />
<ClCompile Include="SDL\src\video\SDL_video.c" />
<ClCompile Include="SDL\src\video\SDL_video_unsupported.c" />
<ClCompile Include="SDL\src\video\SDL_vulkan_utils.c" />
<ClCompile Include="SDL\src\video\SDL_yuv.c" />
<ClCompile Include="SDL\src\video\windows\SDL_surface_utils.c" />
<ClCompile Include="SDL\src\video\windows\SDL_windowsclipboard.c" />
<ClCompile Include="SDL\src\video\windows\SDL_windowsevents.c" />
<ClCompile Include="SDL\src\video\windows\SDL_windowsframebuffer.c" />
<ClCompile Include="SDL\src\video\windows\SDL_windowskeyboard.c" />
<ClCompile Include="SDL\src\video\windows\SDL_windowsgameinput.c" />
<ClCompile Include="SDL\src\video\windows\SDL_windowsmessagebox.c" />
<ClCompile Include="SDL\src\video\windows\SDL_windowsmodes.c" />
<ClCompile Include="SDL\src\video\windows\SDL_windowsmouse.c" />
<ClCompile Include="SDL\src\video\windows\SDL_windowsopengl.c" />
<ClCompile Include="SDL\src\video\windows\SDL_windowsopengles.c" />
<ClCompile Include="SDL\src\video\windows\SDL_windowsrawinput.c" />
<ClCompile Include="SDL\src\video\windows\SDL_windowsshape.c" />
<ClCompile Include="SDL\src\video\windows\SDL_windowsvideo.c" />
<ClCompile Include="SDL\src\video\windows\SDL_windowsvulkan.c" />
@ -426,7 +507,10 @@
<ClCompile Include="SDL\src\video\yuv2rgb\yuv_rgb_sse.c" />
<ClCompile Include="SDL\src\video\yuv2rgb\yuv_rgb_std.c" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="SDL\src\core\windows\version.rc" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -6,7 +6,7 @@
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ProjectReference Include="$(ExternalsDir)SDL\SDL2.vcxproj">
<ProjectReference Include="$(ExternalsDir)SDL\SDL3.vcxproj">
<Project>{8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA}</Project>
</ProjectReference>
</ItemGroup>

View File

@ -1,21 +0,0 @@
{
"name": "SDL2",
"buildsystem": "autotools",
"config-opts": ["--disable-static"],
"sources": [
{
"type": "dir",
"path": "../../Externals/SDL/SDL"
}
],
"cleanup": [ "/bin/sdl2-config",
"/include",
"/lib/libSDL2.la",
"/lib/libSDL2main.a",
"/lib/libSDL2main.la",
"/lib/libSDL2_test.a",
"/lib/libSDL2_test.la",
"/lib/cmake",
"/share/aclocal",
"/lib/pkgconfig"]
}

View File

@ -37,10 +37,6 @@ modules:
project-id: 20540
stable-only: true
url-template: https://www.freedesktop.org/software/libevdev/libevdev-$version.tar.xz
# build the vendored SDL2 from Externals until the runtime gets 2.30.9
- SDL2/SDL2.json
- name: dolphin-emu
buildsystem: cmake-ninja
builddir: true

View File

@ -93,6 +93,8 @@ add_library(common
JitRegister.h
JsonUtil.h
JsonUtil.cpp
Keyboard.h
Keyboard.cpp
Lazy.h
LinearDiskCache.h
Logging/ConsoleListener.h
@ -347,6 +349,11 @@ if(OPROFILE_FOUND)
target_link_libraries(common PRIVATE OProfile::OProfile)
endif()
if(ENABLE_SDL)
target_link_libraries(common PRIVATE SDL3::SDL3)
target_compile_definitions(common PRIVATE -DHAVE_SDL3)
endif()
if(ENABLE_LLVM)
find_package(LLVM CONFIG)
if(LLVM_FOUND)

View File

@ -0,0 +1,330 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Common/Keyboard.h"
#include <array>
#include <map>
#include <mutex>
#include <utility>
#ifdef HAVE_SDL3
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_keyboard.h>
// Will be overridden by Dolphin's SDL InputBackend
u32 Common::KeyboardContext::s_sdl_init_event_type(-1);
u32 Common::KeyboardContext::s_sdl_update_event_type(-1);
u32 Common::KeyboardContext::s_sdl_quit_event_type(-1);
#endif
#include "Core/Config/MainSettings.h"
#include "Core/Config/SYSCONFSettings.h"
#include "DiscIO/Enums.h"
namespace
{
// Translate HID usage ID based on the host and the game keyboard layout:
// - we need to take into account the host layout as we receive raw scan codes
// - we need to consider the game layout as it might be different from the host one
u8 TranslateUsageID(u8 usage_id, int host_layout, int game_layout)
{
if (host_layout == game_layout)
return usage_id;
// Currently, the translation is partial (i.e. alpha only)
if (usage_id != Common::HIDUsageID::M_AZERTY &&
(usage_id < Common::HIDUsageID::A || usage_id > Common::HIDUsageID::Z))
{
return usage_id;
}
switch (host_layout | game_layout)
{
case Common::KeyboardLayout::AZERTY_QWERTZ:
{
static const std::map<u8, u8> TO_QWERTZ{
{Common::HIDUsageID::A_AZERTY, Common::HIDUsageID::A},
{Common::HIDUsageID::Z_AZERTY, Common::HIDUsageID::Z_QWERTZ},
{Common::HIDUsageID::Y, Common::HIDUsageID::Y_QWERTZ},
{Common::HIDUsageID::Q_AZERTY, Common::HIDUsageID::Q},
{Common::HIDUsageID::M_AZERTY, Common::HIDUsageID::M},
{Common::HIDUsageID::W_AZERTY, Common::HIDUsageID::W},
{Common::HIDUsageID::M, Common::HIDUsageID::M_AZERTY},
};
static const std::map<u8, u8> TO_AZERTY{
{Common::HIDUsageID::Q, Common::HIDUsageID::Q_AZERTY},
{Common::HIDUsageID::W, Common::HIDUsageID::W_AZERTY},
{Common::HIDUsageID::Z_QWERTZ, Common::HIDUsageID::Z_AZERTY},
{Common::HIDUsageID::A, Common::HIDUsageID::A_AZERTY},
{Common::HIDUsageID::M_AZERTY, Common::HIDUsageID::M},
{Common::HIDUsageID::Y_QWERTZ, Common::HIDUsageID::Y},
{Common::HIDUsageID::M, Common::HIDUsageID::M_AZERTY},
};
const auto& map = game_layout == Common::KeyboardLayout::QWERTZ ? TO_QWERTZ : TO_AZERTY;
if (const auto it{map.find(usage_id)}; it != map.end())
return it->second;
break;
}
case Common::KeyboardLayout::QWERTY_AZERTY:
{
static constexpr std::array<std::pair<u8, u8>, 3> BI_MAP{
{{Common::HIDUsageID::Q, Common::HIDUsageID::A},
{Common::HIDUsageID::W, Common::HIDUsageID::Z},
{Common::HIDUsageID::M, Common::HIDUsageID::M_AZERTY}}};
for (const auto& [a, b] : BI_MAP)
{
if (usage_id == a)
return b;
else if (usage_id == b)
return a;
}
break;
}
case Common::KeyboardLayout::QWERTY_QWERTZ:
{
if (usage_id == Common::HIDUsageID::Y)
return Common::HIDUsageID::Z;
else if (usage_id == Common::HIDUsageID::Z)
return Common::HIDUsageID::Y;
break;
}
default:
// Shouldn't happen
break;
}
return usage_id;
}
int GetHostLayout()
{
const int layout = Config::Get(Config::MAIN_WII_KEYBOARD_HOST_LAYOUT);
if (layout != Common::KeyboardLayout::AUTO)
return layout;
#ifdef HAVE_SDL3
if (const SDL_Keycode key_code = SDL_GetKeyFromScancode(SDL_SCANCODE_Y, SDL_KMOD_NONE, false);
key_code == SDLK_Z)
{
return Common::KeyboardLayout::QWERTZ;
}
if (const SDL_Keycode key_code = SDL_GetKeyFromScancode(SDL_SCANCODE_Q, SDL_KMOD_NONE, false);
key_code == SDLK_A)
{
return Common::KeyboardLayout::AZERTY;
}
#endif
return Common::KeyboardLayout::QWERTY;
}
int GetGameLayout()
{
const int layout = Config::Get(Config::MAIN_WII_KEYBOARD_GAME_LAYOUT);
if (layout != Common::KeyboardLayout::AUTO)
return layout;
const DiscIO::Language language =
static_cast<DiscIO::Language>(Config::Get(Config::SYSCONF_LANGUAGE));
switch (language)
{
case DiscIO::Language::French:
return Common::KeyboardLayout::AZERTY;
case DiscIO::Language::German:
return Common::KeyboardLayout::QWERTZ;
default:
return Common::KeyboardLayout::QWERTY;
}
}
u8 MapVirtualKeyToHID(u8 virtual_key, int host_layout, int game_layout)
{
// SDL3 keyboard state uses scan codes already based on HID usage id
u8 usage_id = virtual_key;
if (Config::Get(Config::MAIN_WII_KEYBOARD_TRANSLATION))
usage_id = TranslateUsageID(usage_id, host_layout, game_layout);
return usage_id;
}
std::weak_ptr<Common::KeyboardContext> s_keyboard_context;
std::mutex s_keyboard_context_mutex;
// Will be updated by DolphinQt's Host:
// - SetRenderHandle
// - SetFullscreen
Common::KeyboardContext::HandlerState s_handler_state{};
} // Anonymous namespace
namespace Common
{
KeyboardContext::KeyboardContext()
{
if (Config::Get(Config::MAIN_WII_KEYBOARD))
Init();
}
KeyboardContext::~KeyboardContext()
{
if (Config::Get(Config::MAIN_WII_KEYBOARD))
Quit();
}
void KeyboardContext::Init()
{
#ifdef HAVE_SDL3
SDL_Event event{s_sdl_init_event_type};
SDL_PushEvent(&event);
m_keyboard_state = SDL_GetKeyboardState(nullptr);
#endif
UpdateLayout();
m_is_ready = true;
}
void KeyboardContext::Quit()
{
m_is_ready = false;
#ifdef HAVE_SDL3
SDL_Event event{s_sdl_quit_event_type};
SDL_PushEvent(&event);
#endif
}
void* KeyboardContext::HandlerState::GetHandle() const
{
#ifdef _WIN32
if (is_rendering_to_main && !is_fullscreen)
return main_handle;
#endif
return renderer_handle;
}
void KeyboardContext::NotifyInit()
{
if (auto self = s_keyboard_context.lock())
self->Init();
}
void KeyboardContext::NotifyHandlerChanged(const KeyboardContext::HandlerState& state)
{
s_handler_state = state;
if (s_keyboard_context.expired())
return;
#ifdef HAVE_SDL3
SDL_Event event{s_sdl_update_event_type};
SDL_PushEvent(&event);
#endif
}
void KeyboardContext::NotifyQuit()
{
if (auto self = s_keyboard_context.lock())
self->Quit();
}
void KeyboardContext::UpdateLayout()
{
if (auto self = s_keyboard_context.lock())
{
self->m_host_layout = GetHostLayout();
self->m_game_layout = GetGameLayout();
}
}
void* KeyboardContext::GetWindowHandle()
{
return s_handler_state.GetHandle();
}
std::shared_ptr<KeyboardContext> KeyboardContext::GetInstance()
{
const std::lock_guard guard(s_keyboard_context_mutex);
std::shared_ptr<KeyboardContext> ptr = s_keyboard_context.lock();
if (!ptr)
{
ptr = std::shared_ptr<KeyboardContext>(new KeyboardContext);
s_keyboard_context = ptr;
}
return ptr;
}
HIDPressedState KeyboardContext::GetPressedState() const
{
return m_is_ready ? HIDPressedState{.modifiers = PollHIDModifiers(),
.pressed_keys = PollHIDPressedKeys()} :
HIDPressedState{};
}
bool KeyboardContext::IsVirtualKeyPressed(int virtual_key) const
{
#ifdef HAVE_SDL3
if (virtual_key >= SDL_SCANCODE_COUNT)
return false;
return m_keyboard_state[virtual_key];
#else
// TODO: Android implementation
return false;
#endif
}
u8 KeyboardContext::PollHIDModifiers() const
{
u8 modifiers = 0;
using VkHidPair = std::pair<int, u8>;
// References:
// https://wiki.libsdl.org/SDL3/SDL_Scancode
// https://www.usb.org/document-library/device-class-definition-hid-111
//
// HID modifier: Bit 0 - LEFT CTRL
// HID modifier: Bit 1 - LEFT SHIFT
// HID modifier: Bit 2 - LEFT ALT
// HID modifier: Bit 3 - LEFT GUI
// HID modifier: Bit 4 - RIGHT CTRL
// HID modifier: Bit 5 - RIGHT SHIFT
// HID modifier: Bit 6 - RIGHT ALT
// HID modifier: Bit 7 - RIGHT GUI
static const std::vector<VkHidPair> MODIFIERS_MAP{
#ifdef HAVE_SDL3
{SDL_SCANCODE_LCTRL, 0x01}, {SDL_SCANCODE_LSHIFT, 0x02}, {SDL_SCANCODE_LALT, 0x04},
{SDL_SCANCODE_LGUI, 0x08}, {SDL_SCANCODE_RCTRL, 0x10}, {SDL_SCANCODE_RSHIFT, 0x20},
{SDL_SCANCODE_RALT, 0x40}, {SDL_SCANCODE_RGUI, 0x80}
#else
// TODO: Android implementation
#endif
};
for (const auto& [virtual_key, hid_modifier] : MODIFIERS_MAP)
{
if (IsVirtualKeyPressed(virtual_key))
modifiers |= hid_modifier;
}
return modifiers;
}
HIDPressedKeys KeyboardContext::PollHIDPressedKeys() const
{
HIDPressedKeys pressed_keys{};
auto it = pressed_keys.begin();
#ifdef HAVE_SDL3
const std::size_t begin = SDL_SCANCODE_A;
const std::size_t end = SDL_SCANCODE_LCTRL;
#else
const std::size_t begin = 0;
const std::size_t end = 0;
#endif
for (std::size_t virtual_key = begin; virtual_key < end; ++virtual_key)
{
if (!IsVirtualKeyPressed(static_cast<int>(virtual_key)))
continue;
*it = MapVirtualKeyToHID(static_cast<u8>(virtual_key), m_host_layout, m_game_layout);
if (++it == pressed_keys.end())
break;
}
return pressed_keys;
}
} // namespace Common

View File

@ -0,0 +1,129 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <memory>
#include "Common/CommonTypes.h"
namespace Common
{
namespace HIDUsageID
{
// See HID Usage Tables - Keyboard (0x07):
// https://usb.org/sites/default/files/hut1_21.pdf
enum
{
A = 0x04,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
COLON = 0x33,
A_AZERTY = Q,
M_AZERTY = COLON,
Q_AZERTY = A,
W_AZERTY = Z,
Z_AZERTY = W,
Y_QWERTZ = Z,
Z_QWERTZ = Y,
};
} // namespace HIDUsageID
namespace KeyboardLayout
{
enum
{
AUTO = 0,
QWERTY = 1,
AZERTY = 2,
QWERTZ = 4,
// Translation
QWERTY_AZERTY = QWERTY | AZERTY,
QWERTY_QWERTZ = QWERTY | QWERTZ,
AZERTY_QWERTZ = AZERTY | QWERTZ
};
}
using HIDPressedKeys = std::array<u8, 6>;
#pragma pack(push, 1)
struct HIDPressedState
{
u8 modifiers = 0;
u8 oem = 0;
HIDPressedKeys pressed_keys{};
auto operator<=>(const HIDPressedState&) const = default;
};
#pragma pack(pop)
class KeyboardContext
{
public:
~KeyboardContext();
struct HandlerState
{
void* main_handle = nullptr;
void* renderer_handle = nullptr;
bool is_fullscreen = false;
bool is_rendering_to_main = false;
void* GetHandle() const;
};
static void NotifyInit();
static void NotifyHandlerChanged(const HandlerState& state);
static void NotifyQuit();
static void UpdateLayout();
static void* GetWindowHandle();
static std::shared_ptr<KeyboardContext> GetInstance();
HIDPressedState GetPressedState() const;
#ifdef HAVE_SDL3
static u32 s_sdl_init_event_type;
static u32 s_sdl_update_event_type;
static u32 s_sdl_quit_event_type;
#endif
private:
KeyboardContext();
void Init();
void Quit();
bool IsVirtualKeyPressed(int virtual_key) const;
u8 PollHIDModifiers() const;
HIDPressedKeys PollHIDPressedKeys() const;
bool m_is_ready = false;
int m_host_layout = KeyboardLayout::AUTO;
int m_game_layout = KeyboardLayout::AUTO;
#ifdef HAVE_SDL3
const bool* m_keyboard_state = nullptr;
#endif
};
} // namespace Common

View File

@ -427,6 +427,8 @@ add_library(core
IOS/USB/Bluetooth/WiimoteHIDAttr.h
IOS/USB/Common.cpp
IOS/USB/Common.h
IOS/USB/Emulated/HIDKeyboard.cpp
IOS/USB/Emulated/HIDKeyboard.h
IOS/USB/Emulated/Infinity.cpp
IOS/USB/Emulated/Infinity.h
IOS/USB/Emulated/Microphone.cpp

View File

@ -188,6 +188,10 @@ const Info<bool> MAIN_WII_SD_CARD_ENABLE_FOLDER_SYNC{
{System::Main, "Core", "WiiSDCardEnableFolderSync"}, false};
const Info<u64> MAIN_WII_SD_CARD_FILESIZE{{System::Main, "Core", "WiiSDCardFilesize"}, 0};
const Info<bool> MAIN_WII_KEYBOARD{{System::Main, "Core", "WiiKeyboard"}, false};
const Info<int> MAIN_WII_KEYBOARD_HOST_LAYOUT{{System::Main, "Core", "WiiKeyboardHostLayout"}, 0};
const Info<int> MAIN_WII_KEYBOARD_GAME_LAYOUT{{System::Main, "Core", "WiiKeyboardGameLayout"}, 0};
const Info<bool> MAIN_WII_KEYBOARD_TRANSLATION{{System::Main, "Core", "WiiKeyboardTranslation"},
false};
const Info<bool> MAIN_WIIMOTE_CONTINUOUS_SCANNING{
{System::Main, "Core", "WiimoteContinuousScanning"}, false};
const Info<std::string> MAIN_WIIMOTE_AUTO_CONNECT_ADDRESSES{

View File

@ -106,6 +106,9 @@ extern const Info<bool> MAIN_WII_SD_CARD;
extern const Info<bool> MAIN_WII_SD_CARD_ENABLE_FOLDER_SYNC;
extern const Info<u64> MAIN_WII_SD_CARD_FILESIZE;
extern const Info<bool> MAIN_WII_KEYBOARD;
extern const Info<int> MAIN_WII_KEYBOARD_HOST_LAYOUT;
extern const Info<int> MAIN_WII_KEYBOARD_GAME_LAYOUT;
extern const Info<bool> MAIN_WII_KEYBOARD_TRANSLATION;
extern const Info<bool> MAIN_WIIMOTE_CONTINUOUS_SCANNING;
extern const Info<std::string> MAIN_WIIMOTE_AUTO_CONNECT_ADDRESSES;
extern const Info<bool> MAIN_WIIMOTE_ENABLE_SPEAKER;

View File

@ -24,11 +24,34 @@ enum StandardDeviceRequestCodes
REQUEST_SET_INTERFACE = 11,
};
// See USB HID specification under "Class-Specific Requests":
// - https://www.usb.org/sites/default/files/documents/hid1_11.pdf
namespace HIDRequestCodes
{
enum
{
GET_REPORT = 1,
GET_IDLE = 2,
GET_PROTOCOL = 3,
// 0x04~0x08 - Reserved
SET_REPORT = 9,
SET_IDLE = 10,
SET_PROTOCOL = 11,
};
}
enum class HIDProtocol : u16
{
Boot = 0,
Report = 1,
};
enum ControlRequestTypes
{
DIR_HOST2DEVICE = 0,
DIR_DEVICE2HOST = 1,
TYPE_STANDARD = 0,
TYPE_CLASS = 1,
TYPE_VENDOR = 2,
REC_DEVICE = 0,
REC_INTERFACE = 1,

View File

@ -0,0 +1,316 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Core/IOS/USB/Emulated/HIDKeyboard.h"
#include "Core/Config/MainSettings.h"
#include "Core/HW/Memmap.h"
#include "Core/System.h"
#include "InputCommon/ControlReference/ControlReference.h"
namespace IOS::HLE::USB
{
HIDKeyboard::HIDKeyboard()
{
m_id = u64(m_vid) << 32 | u64(m_pid) << 16 | u64(8) << 8 | u64(1);
}
HIDKeyboard::~HIDKeyboard()
{
if (!m_device_attached)
return;
CancelPendingTransfers();
}
DeviceDescriptor HIDKeyboard::GetDeviceDescriptor() const
{
return m_device_descriptor;
}
std::vector<ConfigDescriptor> HIDKeyboard::GetConfigurations() const
{
return m_config_descriptor;
}
std::vector<InterfaceDescriptor> HIDKeyboard::GetInterfaces(u8 config) const
{
return m_interface_descriptor;
}
std::vector<EndpointDescriptor> HIDKeyboard::GetEndpoints(u8 config, u8 interface, u8 alt) const
{
if (const auto it{m_endpoint_descriptor.find(interface)}; it != m_endpoint_descriptor.end())
{
return it->second;
}
return {};
}
bool HIDKeyboard::Attach()
{
if (m_device_attached)
return true;
INFO_LOG_FMT(IOS_USB, "[{:04x}:{:04x}] Opening emulated keyboard", m_vid, m_pid);
m_keyboard_context = Common::KeyboardContext::GetInstance();
m_worker.Reset("HID Keyboard", [this](auto transfer) { HandlePendingTransfer(transfer); });
m_device_attached = true;
return true;
}
bool HIDKeyboard::AttachAndChangeInterface(const u8 interface)
{
if (!Attach())
return false;
if (interface != m_active_interface)
return ChangeInterface(interface) == 0;
return true;
}
int HIDKeyboard::CancelTransfer(const u8 endpoint)
{
if (endpoint != KEYBOARD_ENDPOINT)
{
ERROR_LOG_FMT(
IOS_USB,
"[{:04x}:{:04x} {}] Cancelling transfers for invalid endpoint {:#x} (expected: {:#x})",
m_vid, m_pid, m_active_interface, endpoint, KEYBOARD_ENDPOINT);
return IPC_SUCCESS;
}
INFO_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Cancelling transfers (endpoint {:#x})", m_vid, m_pid,
m_active_interface, endpoint);
CancelPendingTransfers();
return IPC_SUCCESS;
}
int HIDKeyboard::ChangeInterface(const u8 interface)
{
DEBUG_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Changing interface to {}", m_vid, m_pid,
m_active_interface, interface);
m_active_interface = interface;
return 0;
}
int HIDKeyboard::GetNumberOfAltSettings(u8 interface)
{
return 0;
}
int HIDKeyboard::SetAltSetting(u8 alt_setting)
{
return 0;
}
int HIDKeyboard::SubmitTransfer(std::unique_ptr<CtrlMessage> cmd)
{
DEBUG_LOG_FMT(IOS_USB,
"[{:04x}:{:04x} {}] Control: bRequestType={:02x} bRequest={:02x} wValue={:04x}"
" wIndex={:04x} wLength={:04x}",
m_vid, m_pid, m_active_interface, cmd->request_type, cmd->request, cmd->value,
cmd->index, cmd->length);
auto& ios = cmd->GetEmulationKernel();
switch (cmd->request_type << 8 | cmd->request)
{
case USBHDR(DIR_DEVICE2HOST, TYPE_STANDARD, REC_INTERFACE, REQUEST_GET_INTERFACE):
{
constexpr u8 data{1};
cmd->FillBuffer(&data, sizeof(data));
cmd->ScheduleTransferCompletion(1, 100);
break;
}
case USBHDR(DIR_HOST2DEVICE, TYPE_STANDARD, REC_INTERFACE, REQUEST_SET_INTERFACE):
{
INFO_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] REQUEST_SET_INTERFACE index={:04x} value={:04x}",
m_vid, m_pid, m_active_interface, cmd->index, cmd->value);
if (static_cast<u8>(cmd->index) != m_active_interface)
{
const int ret = ChangeInterface(static_cast<u8>(cmd->index));
if (ret < 0)
{
ERROR_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Failed to change interface to {}", m_vid, m_pid,
m_active_interface, cmd->index);
return ret;
}
}
const int ret = SetAltSetting(static_cast<u8>(cmd->value));
if (ret == 0)
ios.EnqueueIPCReply(cmd->ios_request, cmd->length);
return ret;
}
case USBHDR(DIR_HOST2DEVICE, TYPE_CLASS, REC_INTERFACE, HIDRequestCodes::SET_REPORT):
{
// According to the HID specification:
// - A device might choose to ignore input Set_Report requests as meaningless.
// - Alternatively these reports could be used to reset the origin of a control
// (that is, current position should report zero).
// - The effect of sent reports will also depend on whether the recipient controls
// are absolute or relative.
const u8 report_type = cmd->value >> 8;
const u8 report_id = cmd->value & 0xFF;
auto& memory = ios.GetSystem().GetMemory();
// The data seems to report LED status for keys such as:
// - NUM LOCK, CAPS LOCK
const u8* data = memory.GetPointerForRange(cmd->data_address, cmd->length);
INFO_LOG_FMT(IOS_USB, "SET_REPORT ignored (report_type={:02x}, report_id={:02x}, index={})\n{}",
report_type, report_id, cmd->index, HexDump(data, cmd->length));
ios.EnqueueIPCReply(cmd->ios_request, IPC_SUCCESS);
break;
}
case USBHDR(DIR_HOST2DEVICE, TYPE_CLASS, REC_INTERFACE, HIDRequestCodes::SET_IDLE):
{
WARN_LOG_FMT(IOS_USB, "SET_IDLE not implemented (value={:04x}, index={})", cmd->value,
cmd->index);
// TODO: Handle idle duration and implement NAK
ios.EnqueueIPCReply(cmd->ios_request, IPC_SUCCESS);
break;
}
case USBHDR(DIR_HOST2DEVICE, TYPE_CLASS, REC_INTERFACE, HIDRequestCodes::SET_PROTOCOL):
{
INFO_LOG_FMT(IOS_USB, "SET_PROTOCOL: value={}, index={}", cmd->value, cmd->index);
const HIDProtocol protocol = static_cast<HIDProtocol>(cmd->value);
switch (protocol)
{
case HIDProtocol::Boot:
case HIDProtocol::Report:
m_current_protocol = protocol;
break;
default:
WARN_LOG_FMT(IOS_USB, "SET_PROTOCOL: Unknown protocol {} for interface {}", cmd->value,
cmd->index);
}
ios.EnqueueIPCReply(cmd->ios_request, IPC_SUCCESS);
break;
}
default:
WARN_LOG_FMT(IOS_USB, "Unknown command, req={:02x}, type={:02x}", cmd->request,
cmd->request_type);
ios.EnqueueIPCReply(cmd->ios_request, IPC_SUCCESS);
}
return IPC_SUCCESS;
}
int HIDKeyboard::SubmitTransfer(std::unique_ptr<BulkMessage> cmd)
{
DEBUG_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Bulk: length={:04x} endpoint={:02x}", m_vid, m_pid,
m_active_interface, cmd->length, cmd->endpoint);
cmd->GetEmulationKernel().EnqueueIPCReply(cmd->ios_request, IPC_SUCCESS);
return IPC_SUCCESS;
}
int HIDKeyboard::SubmitTransfer(std::unique_ptr<IntrMessage> cmd)
{
static auto start_time = std::chrono::steady_clock::now();
const auto current_time = std::chrono::steady_clock::now();
const bool should_poll =
(current_time - start_time) >= POLLING_RATE && ControlReference::GetInputGate();
const Common::HIDPressedState state =
should_poll ? m_keyboard_context->GetPressedState() : m_last_state;
// We can't use cmd->ScheduleTransferCompletion here as it might provoke
// invalid memory access with scheduled transfers when CancelTransfer is called.
EnqueueTransfer(std::move(cmd), state);
if (should_poll)
{
m_last_state = std::move(state);
start_time = std::chrono::steady_clock::now();
}
return IPC_SUCCESS;
}
int HIDKeyboard::SubmitTransfer(std::unique_ptr<IsoMessage> cmd)
{
DEBUG_LOG_FMT(IOS_USB,
"[{:04x}:{:04x} {}] Isochronous: length={:04x} endpoint={:02x} num_packets={:02x}",
m_vid, m_pid, m_active_interface, cmd->length, cmd->endpoint, cmd->num_packets);
cmd->ScheduleTransferCompletion(IPC_SUCCESS, 2000);
return IPC_SUCCESS;
}
void HIDKeyboard::EnqueueTransfer(std::unique_ptr<IntrMessage> msg,
const Common::HIDPressedState& state)
{
msg->FillBuffer(reinterpret_cast<const u8*>(&state), sizeof(state));
auto transfer = std::make_shared<PendingTransfer>(std::move(msg));
m_worker.EmplaceItem(transfer);
{
std::lock_guard lock(m_pending_lock);
m_pending_tranfers.insert(transfer);
}
}
void HIDKeyboard::HandlePendingTransfer(std::shared_ptr<PendingTransfer> transfer)
{
std::unique_lock lock(m_pending_lock);
if (transfer->IsCanceled())
return;
while (!transfer->IsReady())
{
lock.unlock();
std::this_thread::sleep_for(POLLING_RATE / 2);
lock.lock();
if (transfer->IsCanceled())
return;
}
transfer->Do();
m_pending_tranfers.erase(transfer);
}
void HIDKeyboard::CancelPendingTransfers()
{
m_worker.Cancel();
{
std::lock_guard lock(m_pending_lock);
for (auto& transfer : m_pending_tranfers)
transfer->Cancel();
m_pending_tranfers.clear();
}
}
HIDKeyboard::PendingTransfer::PendingTransfer(std::unique_ptr<IntrMessage> msg)
{
m_time = std::chrono::steady_clock::now();
m_msg = std::move(msg);
}
HIDKeyboard::PendingTransfer::~PendingTransfer()
{
if (!m_pending)
return;
// Value based on LibusbDevice's HandleTransfer implementation
m_msg->ScheduleTransferCompletion(-5, 0);
}
bool HIDKeyboard::PendingTransfer::IsReady() const
{
return (std::chrono::steady_clock::now() - m_time) >= POLLING_RATE;
}
bool HIDKeyboard::PendingTransfer::IsCanceled() const
{
return m_is_canceled;
}
void HIDKeyboard::PendingTransfer::Do()
{
m_msg->ScheduleTransferCompletion(IPC_SUCCESS, 0);
m_pending = false;
}
void HIDKeyboard::PendingTransfer::Cancel()
{
m_is_canceled = true;
}
} // namespace IOS::HLE::USB

View File

@ -0,0 +1,96 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <chrono>
#include <map>
#include <memory>
#include <mutex>
#include <set>
#include <vector>
#include "Common/CommonTypes.h"
#include "Common/Keyboard.h"
#include "Common/WorkQueueThread.h"
#include "Core/IOS/USB/Common.h"
namespace IOS::HLE::USB
{
class HIDKeyboard final : public Device
{
public:
HIDKeyboard();
~HIDKeyboard() override;
DeviceDescriptor GetDeviceDescriptor() const override;
std::vector<ConfigDescriptor> GetConfigurations() const override;
std::vector<InterfaceDescriptor> GetInterfaces(u8 config) const override;
std::vector<EndpointDescriptor> GetEndpoints(u8 config, u8 interface, u8 alt) const override;
bool Attach() override;
bool AttachAndChangeInterface(u8 interface) override;
int CancelTransfer(u8 endpoint) override;
int ChangeInterface(u8 interface) override;
int GetNumberOfAltSettings(u8 interface) override;
int SetAltSetting(u8 alt_setting) override;
int SubmitTransfer(std::unique_ptr<CtrlMessage> message) override;
int SubmitTransfer(std::unique_ptr<BulkMessage> message) override;
int SubmitTransfer(std::unique_ptr<IntrMessage> message) override;
int SubmitTransfer(std::unique_ptr<IsoMessage> message) override;
static constexpr auto POLLING_RATE = std::chrono::milliseconds(8); // 125 Hz
private:
class PendingTransfer
{
public:
PendingTransfer(std::unique_ptr<IntrMessage> msg);
~PendingTransfer();
bool IsReady() const;
bool IsCanceled() const;
void Do();
void Cancel();
private:
std::unique_ptr<IntrMessage> m_msg;
std::chrono::steady_clock::time_point m_time;
bool m_is_canceled = false;
bool m_pending = true;
};
void EnqueueTransfer(std::unique_ptr<IntrMessage> msg, const Common::HIDPressedState& state);
void HandlePendingTransfer(std::shared_ptr<PendingTransfer> transfer);
void CancelPendingTransfers();
Common::WorkQueueThreadSP<std::shared_ptr<PendingTransfer>> m_worker;
std::mutex m_pending_lock;
std::set<std::shared_ptr<PendingTransfer>> m_pending_tranfers;
HIDProtocol m_current_protocol = HIDProtocol::Report;
Common::HIDPressedState m_last_state;
std::shared_ptr<Common::KeyboardContext> m_keyboard_context;
// Apple Extended Keyboard [Mitsumi]
// - Model A1058 / USB 1.1
const u16 m_vid = 0x05ac;
const u16 m_pid = 0x020c;
u8 m_active_interface = 0;
bool m_device_attached = false;
const DeviceDescriptor m_device_descriptor{0x12, 0x01, 0x0110, 0x00, 0x00, 0x00, 0x08,
0x05AC, 0x020C, 0x0395, 0x01, 0x03, 0x00, 0x01};
const std::vector<ConfigDescriptor> m_config_descriptor{
{0x09, 0x02, 0x003B, 0x02, 0x01, 0x00, 0xA0, 0x19}};
static constexpr u8 INTERFACE_0 = 0;
static constexpr u8 INTERFACE_1 = 1;
static constexpr u8 KEYBOARD_ENDPOINT = 0x81;
static constexpr u8 HUB_ENDPOINT = 0x82;
const std::vector<InterfaceDescriptor> m_interface_descriptor{
{0x09, 0x04, INTERFACE_0, 0x00, 0x01, 0x03, 0x01, 0x01, 0x00},
{0x09, 0x04, INTERFACE_1, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00}};
const std::map<u8, std::vector<EndpointDescriptor>> m_endpoint_descriptor{
{INTERFACE_0, {{0x07, 0x05, KEYBOARD_ENDPOINT, 0x03, 0x0008, 0x0A}}},
{INTERFACE_1, {{0x07, 0x05, HUB_ENDPOINT, 0x03, 0x0004, 0x0A}}},
};
};
} // namespace IOS::HLE::USB

View File

@ -21,6 +21,7 @@
#include "Core/Config/MainSettings.h"
#include "Core/Core.h"
#include "Core/IOS/USB/Common.h"
#include "Core/IOS/USB/Emulated/HIDKeyboard.h"
#include "Core/IOS/USB/Emulated/Infinity.h"
#include "Core/IOS/USB/Emulated/Skylanders/Skylander.h"
#include "Core/IOS/USB/Emulated/WiiSpeak.h"
@ -187,6 +188,11 @@ void USBScanner::AddEmulatedDevices(DeviceMap* new_devices)
auto wii_speak = std::make_unique<USB::WiiSpeak>();
AddDevice(std::move(wii_speak), new_devices);
}
if (Config::Get(Config::MAIN_WII_KEYBOARD) && !NetPlay::IsNetPlayRunning())
{
auto keyboard = std::make_unique<USB::HIDKeyboard>();
AddDevice(std::move(keyboard), new_devices);
}
}
void USBScanner::WakeupSantrollerDevice(libusb_device* device)

View File

@ -3,9 +3,6 @@
#include "Core/IOS/USB/USB_KBD.h"
#include <array>
#include <queue>
#include "Common/FileUtil.h"
#include "Common/IniFile.h"
#include "Common/Logging/Log.h"
@ -16,169 +13,11 @@
#include "Core/System.h"
#include "InputCommon/ControlReference/ControlReference.h" // For background input check
#ifdef _WIN32
#include <windows.h>
#endif
namespace IOS::HLE
{
namespace
{
// Crazy ugly
#ifdef _WIN32
constexpr std::array<u8, 256> s_key_codes_qwerty{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2A, // Backspace
0x2B, // Tab
0x00, 0x00,
0x00, // Clear
0x28, // Return
0x00, 0x00,
0x00, // Shift
0x00, // Control
0x00, // ALT
0x48, // Pause
0x39, // Capital
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x29, // Escape
0x00, 0x00, 0x00, 0x00,
0x2C, // Space
0x4B, // Prior
0x4E, // Next
0x4D, // End
0x4A, // Home
0x50, // Left
0x52, // Up
0x4F, // Right
0x51, // Down
0x00, 0x00, 0x00,
0x46, // Print screen
0x49, // Insert
0x4C, // Delete
0x00,
// 0 -> 9
0x27, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
// A -> Z
0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13,
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x00,
// Numpad 0 -> 9
0x62, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61,
0x55, // Multiply
0x57, // Add
0x00, // Separator
0x56, // Subtract
0x63, // Decimal
0x54, // Divide
// F1 -> F12
0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45,
// F13 -> F24
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x53, // Numlock
0x47, // Scroll lock
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Modifier keys
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x33, // ';'
0x2E, // Plus
0x36, // Comma
0x2D, // Minus
0x37, // Period
0x38, // '/'
0x35, // '~'
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2F, // '['
0x32, // '\'
0x30, // ']'
0x34, // '''
0x00, //
0x00, // Nothing interesting past this point.
};
constexpr std::array<u8, 256> s_key_codes_azerty{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2A, // Backspace
0x2B, // Tab
0x00, 0x00,
0x00, // Clear
0x28, // Return
0x00, 0x00,
0x00, // Shift
0x00, // Control
0x00, // ALT
0x48, // Pause
0x39, // Capital
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x29, // Escape
0x00, 0x00, 0x00, 0x00,
0x2C, // Space
0x4B, // Prior
0x4E, // Next
0x4D, // End
0x4A, // Home
0x50, // Left
0x52, // Up
0x4F, // Right
0x51, // Down
0x00, 0x00, 0x00,
0x46, // Print screen
0x49, // Insert
0x4C, // Delete
0x00,
// 0 -> 9
0x27, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
// A -> Z
0x14, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x33, 0x11, 0x12, 0x13,
0x04, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1D, 0x1B, 0x1C, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00,
// Numpad 0 -> 9
0x62, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61,
0x55, // Multiply
0x57, // Add
0x00, // Separator
0x56, // Substract
0x63, // Decimal
0x54, // Divide
// F1 -> F12
0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45,
// F13 -> F24
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x53, // Numlock
0x47, // Scroll lock
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Modifier keys
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x30, // '$'
0x2E, // Plus
0x10, // Comma
0x00, // Minus
0x36, // Period
0x37, // '/'
0x34, // ' '
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2D, // ')'
0x32, // '\'
0x2F, // '^'
0x00, // ' '
0x38, // '!'
0x00, // Nothing interesting past this point.
};
#else
constexpr std::array<u8, 256> s_key_codes_qwerty{};
constexpr std::array<u8, 256> s_key_codes_azerty{};
#endif
} // Anonymous namespace
USB_KBD::MessageData::MessageData(MessageType type, u8 modifiers_, PressedKeyData pressed_keys_)
: msg_type{Common::swap32(static_cast<u32>(type))}, modifiers{modifiers_},
pressed_keys{pressed_keys_}
USB_KBD::MessageData::MessageData(MessageType type, const Common::HIDPressedState& state)
: msg_type{Common::swap32(static_cast<u32>(type))}, modifiers{state.modifiers},
pressed_keys{state.pressed_keys}
{
}
@ -192,18 +31,24 @@ USB_KBD::USB_KBD(EmulationKernel& ios, const std::string& device_name)
std::optional<IPCReply> USB_KBD::Open(const OpenRequest& request)
{
INFO_LOG_FMT(IOS, "USB_KBD: Open");
Common::IniFile ini;
ini.Load(File::GetUserPath(F_DOLPHINCONFIG_IDX));
ini.GetOrCreateSection("USB Keyboard")->Get("Layout", &m_keyboard_layout, KBD_LAYOUT_QWERTY);
m_message_queue = {};
m_old_key_buffer.fill(false);
m_old_modifiers = 0x00;
m_previous_state = {};
m_keyboard_context = Common::KeyboardContext::GetInstance();
// m_message_queue.emplace(MessageType::KeyboardConnect, 0, PressedKeyData{});
// m_message_queue.emplace(MessageType::KeyboardConnect, {});
return Device::Open(request);
}
std::optional<IPCReply> USB_KBD::Close(u32 fd)
{
INFO_LOG_FMT(IOS, "USB_KBD: Close");
m_keyboard_context.reset();
return Device::Close(fd);
}
std::optional<IPCReply> USB_KBD::Write(const ReadWriteRequest& request)
{
// Stubbed.
@ -223,91 +68,20 @@ std::optional<IPCReply> USB_KBD::IOCtl(const IOCtlRequest& request)
return IPCReply(IPC_SUCCESS);
}
bool USB_KBD::IsKeyPressed(int key) const
{
#ifdef _WIN32
return (GetAsyncKeyState(key) & 0x8000) != 0;
#else
// TODO: do it for non-Windows platforms
return false;
#endif
}
void USB_KBD::Update()
{
if (!Config::Get(Config::MAIN_WII_KEYBOARD) || Core::WantsDeterminism() || !m_is_active)
if (!Config::Get(Config::MAIN_WII_KEYBOARD) || Core::WantsDeterminism() || !m_is_active ||
!m_keyboard_context)
{
return;
}
const auto current_state = m_keyboard_context->GetPressedState();
if (current_state == m_previous_state)
return;
u8 modifiers = 0x00;
PressedKeyData pressed_keys{0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
bool got_event = false;
size_t num_pressed_keys = 0;
for (size_t i = 0; i < m_old_key_buffer.size(); i++)
{
const bool key_pressed_now = IsKeyPressed(static_cast<int>(i));
const bool key_pressed_before = m_old_key_buffer[i];
u8 key_code = 0;
if (key_pressed_now ^ key_pressed_before)
{
if (key_pressed_now)
{
switch (m_keyboard_layout)
{
case KBD_LAYOUT_QWERTY:
key_code = s_key_codes_qwerty[i];
break;
case KBD_LAYOUT_AZERTY:
key_code = s_key_codes_azerty[i];
break;
}
if (key_code == 0x00)
continue;
pressed_keys[num_pressed_keys] = key_code;
num_pressed_keys++;
if (num_pressed_keys == pressed_keys.size())
break;
}
got_event = true;
}
m_old_key_buffer[i] = key_pressed_now;
}
#ifdef _WIN32
if (GetAsyncKeyState(VK_LCONTROL) & 0x8000)
modifiers |= 0x01;
if (GetAsyncKeyState(VK_LSHIFT) & 0x8000)
modifiers |= 0x02;
if (GetAsyncKeyState(VK_MENU) & 0x8000)
modifiers |= 0x04;
if (GetAsyncKeyState(VK_LWIN) & 0x8000)
modifiers |= 0x08;
if (GetAsyncKeyState(VK_RCONTROL) & 0x8000)
modifiers |= 0x10;
if (GetAsyncKeyState(VK_RSHIFT) & 0x8000)
modifiers |= 0x20;
// TODO: VK_MENU is for ALT, not for ALT GR (ALT GR seems to work though...)
if (GetAsyncKeyState(VK_MENU) & 0x8000)
modifiers |= 0x40;
if (GetAsyncKeyState(VK_RWIN) & 0x8000)
modifiers |= 0x80;
#else
// TODO: modifiers for non-Windows platforms
#endif
if (modifiers ^ m_old_modifiers)
{
got_event = true;
m_old_modifiers = modifiers;
}
if (got_event)
m_message_queue.emplace(MessageType::Event, modifiers, pressed_keys);
m_message_queue.emplace(MessageType::Event, current_state);
m_previous_state = std::move(current_state);
}
} // namespace IOS::HLE

View File

@ -3,12 +3,11 @@
#pragma once
#include <array>
#include <queue>
#include <string>
#include <type_traits>
#include "Common/CommonTypes.h"
#include "Common/Keyboard.h"
#include "Core/IOS/Device.h"
#include "Core/IOS/IOS.h"
@ -20,6 +19,7 @@ public:
USB_KBD(EmulationKernel& ios, const std::string& device_name);
std::optional<IPCReply> Open(const OpenRequest& request) override;
std::optional<IPCReply> Close(u32 fd) override;
std::optional<IPCReply> Write(const ReadWriteRequest& request) override;
std::optional<IPCReply> IOCtl(const IOCtlRequest& request) override;
void Update() override;
@ -32,8 +32,6 @@ private:
Event = 2
};
using PressedKeyData = std::array<u8, 6>;
#pragma pack(push, 1)
struct MessageData
{
@ -41,26 +39,15 @@ private:
u32 unk1 = 0;
u8 modifiers = 0;
u8 unk2 = 0;
PressedKeyData pressed_keys{};
Common::HIDPressedKeys pressed_keys{};
MessageData(MessageType msg_type, u8 modifiers, PressedKeyData pressed_keys);
MessageData(MessageType msg_type, const Common::HIDPressedState& state);
};
static_assert(std::is_trivially_copyable_v<MessageData>,
"MessageData must be trivially copyable, as it's copied into emulated memory.");
#pragma pack(pop)
std::queue<MessageData> m_message_queue;
std::array<bool, 256> m_old_key_buffer{};
u8 m_old_modifiers = 0;
bool IsKeyPressed(int key) const;
// This stuff should probably die
enum
{
KBD_LAYOUT_QWERTY = 0,
KBD_LAYOUT_AZERTY = 1
};
int m_keyboard_layout = KBD_LAYOUT_QWERTY;
Common::HIDPressedState m_previous_state;
std::shared_ptr<Common::KeyboardContext> m_keyboard_context;
};
} // namespace IOS::HLE

View File

@ -132,6 +132,7 @@
<ClInclude Include="Common\IOFile.h" />
<ClInclude Include="Common\JitRegister.h" />
<ClInclude Include="Common\JsonUtil.h" />
<ClInclude Include="Common\Keyboard.h" />
<ClInclude Include="Common\Lazy.h" />
<ClInclude Include="Common\LdrWatcher.h" />
<ClInclude Include="Common\LinearDiskCache.h" />
@ -406,6 +407,7 @@
<ClInclude Include="Core\IOS\USB\Bluetooth\WiimoteDevice.h" />
<ClInclude Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.h" />
<ClInclude Include="Core\IOS\USB\Common.h" />
<ClInclude Include="Core\IOS\USB\Emulated\HIDKeyboard.h" />
<ClInclude Include="Core\IOS\USB\Emulated\Infinity.h" />
<ClInclude Include="Core\IOS\USB\Emulated\Microphone.h" />
<ClInclude Include="Core\IOS\USB\Emulated\Skylanders\Skylander.h" />
@ -830,6 +832,7 @@
<ClCompile Include="Common\IOFile.cpp" />
<ClCompile Include="Common\JitRegister.cpp" />
<ClCompile Include="Common\JsonUtil.cpp" />
<ClCompile Include="Common\Keyboard.cpp" />
<ClCompile Include="Common\LdrWatcher.cpp" />
<ClCompile Include="Common\Logging\ConsoleListenerWin.cpp" />
<ClCompile Include="Common\Logging\LogManager.cpp" />
@ -1076,6 +1079,7 @@
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteDevice.cpp" />
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.cpp" />
<ClCompile Include="Core\IOS\USB\Common.cpp" />
<ClCompile Include="Core\IOS\USB\Emulated\HIDKeyboard.cpp" />
<ClCompile Include="Core\IOS\USB\Emulated\Infinity.cpp" />
<ClCompile Include="Core\IOS\USB\Emulated\Microphone.cpp" />
<ClCompile Include="Core\IOS\USB\Emulated\Skylanders\Skylander.cpp" />

View File

@ -247,6 +247,8 @@ add_executable(dolphin-emu
DiscordHandler.h
DiscordJoinRequestDialog.cpp
DiscordJoinRequestDialog.h
EmulatedUSB/Keyboard.cpp
EmulatedUSB/Keyboard.h
EmulatedUSB/WiiSpeakWindow.cpp
EmulatedUSB/WiiSpeakWindow.h
FIFO/FIFOAnalyzer.cpp

View File

@ -157,6 +157,7 @@
<ClCompile Include="Debugger\WatchWidget.cpp" />
<ClCompile Include="DiscordHandler.cpp" />
<ClCompile Include="DiscordJoinRequestDialog.cpp" />
<ClCompile Include="EmulatedUSB\Keyboard.cpp" />
<ClCompile Include="EmulatedUSB\WiiSpeakWindow.cpp" />
<ClCompile Include="FIFO\FIFOAnalyzer.cpp" />
<ClCompile Include="FIFO\FIFOPlayerWindow.cpp" />
@ -376,6 +377,7 @@
<QtMoc Include="Debugger\WatchWidget.h" />
<QtMoc Include="DiscordHandler.h" />
<QtMoc Include="DiscordJoinRequestDialog.h" />
<QtMoc Include="EmulatedUSB\Keyboard.h" />
<QtMoc Include="EmulatedUSB\WiiSpeakWindow.h" />
<QtMoc Include="FIFO\FIFOAnalyzer.h" />
<QtMoc Include="FIFO\FIFOPlayerWindow.h" />

View File

@ -0,0 +1,90 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "DolphinQt/EmulatedUSB/Keyboard.h"
#include <QCheckBox>
#include <QComboBox>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QVBoxLayout>
#include "Common/Keyboard.h"
#include "Core/Config/MainSettings.h"
#include "DolphinQt/Config/ConfigControls/ConfigBool.h"
#include "DolphinQt/Settings.h"
KeyboardWindow::KeyboardWindow(QWidget* parent) : QWidget(parent)
{
// i18n: Window for managing the Wii keyboard emulation
setWindowTitle(tr("Keyboard Manager"));
setObjectName(QStringLiteral("keyboard_manager"));
setMinimumSize(QSize(500, 200));
auto* main_layout = new QVBoxLayout();
{
auto* group = new QGroupBox();
auto* hbox_layout = new QHBoxLayout();
hbox_layout->setAlignment(Qt::AlignHCenter);
auto* checkbox_emulate = new QCheckBox(tr("Emulate USB keyboard"), this);
checkbox_emulate->setChecked(Settings::Instance().IsUSBKeyboardConnected());
connect(checkbox_emulate, &QCheckBox::toggled, this, &KeyboardWindow::EmulateKeyboard);
connect(&Settings::Instance(), &Settings::USBKeyboardConnectionChanged, checkbox_emulate,
&QCheckBox::setChecked);
hbox_layout->addWidget(checkbox_emulate);
group->setLayout(hbox_layout);
main_layout->addWidget(group);
}
{
auto* group = new QGroupBox(tr("Layout Configuration"));
auto* grid_layout = new QGridLayout();
auto* checkbox_translate =
new ConfigBool(tr("Enable partial translation"), Config::MAIN_WII_KEYBOARD_TRANSLATION);
grid_layout->addWidget(checkbox_translate, 0, 0, 1, 2, Qt::AlignLeft);
auto create_combo = [checkbox_translate, grid_layout](int row, const QString& name,
const auto& config_info) {
grid_layout->addWidget(new QLabel(name), row, 0);
auto* combo = new QComboBox();
combo->addItem(tr("Automatic detection"), Common::KeyboardLayout::AUTO);
combo->addItem(QStringLiteral("QWERTY"), Common::KeyboardLayout::QWERTY);
combo->addItem(QStringLiteral("AZERTY"), Common::KeyboardLayout::AZERTY);
combo->addItem(QStringLiteral("QWERTZ"), Common::KeyboardLayout::QWERTZ);
combo->setCurrentIndex(combo->findData(Config::Get(config_info)));
combo->setEnabled(checkbox_translate->isChecked());
connect(combo, &QComboBox::currentIndexChanged, combo, [combo, config_info] {
const auto keyboard_layout = combo->currentData();
if (!keyboard_layout.isValid())
return;
Config::SetBaseOrCurrent(config_info, keyboard_layout.toInt());
Common::KeyboardContext::UpdateLayout();
});
connect(checkbox_translate, &QCheckBox::toggled, combo, &QComboBox::setEnabled);
grid_layout->addWidget(combo, row, 1);
};
create_combo(1, tr("Host layout:"), Config::MAIN_WII_KEYBOARD_HOST_LAYOUT);
create_combo(2, tr("Game layout:"), Config::MAIN_WII_KEYBOARD_GAME_LAYOUT);
group->setLayout(grid_layout);
main_layout->addWidget(group);
}
setLayout(main_layout);
}
KeyboardWindow::~KeyboardWindow() = default;
void KeyboardWindow::EmulateKeyboard(bool emulate) const
{
Settings::Instance().SetUSBKeyboardConnected(emulate);
}

View File

@ -0,0 +1,17 @@
// Copyright 2025 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QWidget>
class KeyboardWindow : public QWidget
{
Q_OBJECT
public:
explicit KeyboardWindow(QWidget* parent = nullptr);
~KeyboardWindow() override;
private:
void EmulateKeyboard(bool emulate) const;
};

View File

@ -16,6 +16,7 @@
#endif
#include "Common/Common.h"
#include "Common/Keyboard.h"
#include "Core/Config/MainSettings.h"
#include "Core/ConfigManager.h"
@ -62,6 +63,11 @@ void Host::SetRenderHandle(void* handle)
{
m_render_to_main = Config::Get(Config::MAIN_RENDER_TO_MAIN);
Common::KeyboardContext::NotifyHandlerChanged({.main_handle = m_main_window_handle.load(),
.renderer_handle = handle,
.is_fullscreen = m_render_fullscreen.load(),
.is_rendering_to_main = m_render_to_main.load()});
if (m_render_handle == handle)
return;
@ -176,6 +182,11 @@ void Host::SetRenderFullscreen(bool fullscreen)
{
m_render_fullscreen = fullscreen;
Common::KeyboardContext::NotifyHandlerChanged({.main_handle = m_main_window_handle.load(),
.renderer_handle = m_render_handle.load(),
.is_fullscreen = fullscreen,
.is_rendering_to_main = m_render_to_main.load()});
if (g_gfx && g_gfx->IsFullscreen() != fullscreen && g_ActiveConfig.ExclusiveFullscreenEnabled())
{
RunWithGPUThreadInactive([fullscreen] { g_gfx->SetFullscreen(fullscreen); });

View File

@ -95,6 +95,7 @@
#include "DolphinQt/Debugger/ThreadWidget.h"
#include "DolphinQt/Debugger/WatchWidget.h"
#include "DolphinQt/DiscordHandler.h"
#include "DolphinQt/EmulatedUSB/Keyboard.h"
#include "DolphinQt/EmulatedUSB/WiiSpeakWindow.h"
#include "DolphinQt/FIFO/FIFOPlayerWindow.h"
#include "DolphinQt/GCMemcardManager.h"
@ -583,6 +584,7 @@ void MainWindow::ConnectMenuBar()
connect(m_menu_bar, &MenuBar::ShowSkylanderPortal, this, &MainWindow::ShowSkylanderPortal);
connect(m_menu_bar, &MenuBar::ShowInfinityBase, this, &MainWindow::ShowInfinityBase);
connect(m_menu_bar, &MenuBar::ShowWiiSpeakWindow, this, &MainWindow::ShowWiiSpeakWindow);
connect(m_menu_bar, &MenuBar::ShowKeyboard, this, &MainWindow::ShowKeyboard);
connect(m_menu_bar, &MenuBar::ConnectWiiRemote, this, &MainWindow::OnConnectWiiRemote);
#ifdef USE_RETRO_ACHIEVEMENTS
@ -1454,6 +1456,19 @@ void MainWindow::ShowWiiSpeakWindow()
m_wii_speak_window->activateWindow();
}
void MainWindow::ShowKeyboard()
{
if (!m_keyboard_window)
{
m_keyboard_window = new KeyboardWindow();
}
SetQWidgetWindowDecorations(m_keyboard_window);
m_keyboard_window->show();
m_keyboard_window->raise();
m_keyboard_window->activateWindow();
}
void MainWindow::StateLoad()
{
QString dialog_path = (Config::Get(Config::MAIN_CURRENT_STATE_PATH).empty()) ?

View File

@ -38,6 +38,7 @@ class GCTASInputWindow;
class GraphicsWindow;
class HotkeyScheduler;
class InfinityBaseWindow;
class KeyboardWindow;
class JITWidget;
class LogConfigWidget;
class LogWidget;
@ -179,6 +180,7 @@ private:
void ShowSkylanderPortal();
void ShowInfinityBase();
void ShowWiiSpeakWindow();
void ShowKeyboard();
void ShowMemcardManager();
void ShowResourcePackManager();
void ShowCheatsManager();
@ -253,6 +255,7 @@ private:
SkylanderPortalWindow* m_skylander_window = nullptr;
InfinityBaseWindow* m_infinity_window = nullptr;
WiiSpeakWindow* m_wii_speak_window = nullptr;
KeyboardWindow* m_keyboard_window = nullptr;
MappingWindow* m_hotkey_window = nullptr;
FreeLookWindow* m_freelook_window = nullptr;

View File

@ -282,6 +282,7 @@ void MenuBar::AddToolsMenu()
usb_device_menu->addAction(tr("&Skylanders Portal"), this, &MenuBar::ShowSkylanderPortal);
usb_device_menu->addAction(tr("&Infinity Base"), this, &MenuBar::ShowInfinityBase);
usb_device_menu->addAction(tr("&Wii Speak"), this, &MenuBar::ShowWiiSpeakWindow);
usb_device_menu->addAction(tr("&Keyboard"), this, &MenuBar::ShowKeyboard);
tools_menu->addMenu(usb_device_menu);
tools_menu->addSeparator();

View File

@ -95,6 +95,7 @@ signals:
void ShowSkylanderPortal();
void ShowInfinityBase();
void ShowWiiSpeakWindow();
void ShowKeyboard();
void ConnectWiiRemote(int id);
#ifdef USE_RETRO_ACHIEVEMENTS

View File

@ -24,6 +24,7 @@
#include "Common/Config/Config.h"
#include "Common/Contains.h"
#include "Common/FileUtil.h"
#include "Common/Keyboard.h"
#include "Common/StringUtil.h"
#include "Core/AchievementManager.h"
@ -791,6 +792,10 @@ void Settings::SetUSBKeyboardConnected(bool connected)
if (IsUSBKeyboardConnected() != connected)
{
Config::SetBaseOrCurrent(Config::MAIN_WII_KEYBOARD, connected);
if (connected)
Common::KeyboardContext::NotifyInit();
else
Common::KeyboardContext::NotifyQuit();
emit USBKeyboardConnectionChanged(connected);
}
}

View File

@ -180,8 +180,8 @@ if(ENABLE_SDL)
ControllerInterface/SDL/SDLGamepad.cpp
ControllerInterface/SDL/SDLGamepad.h
)
target_link_libraries(inputcommon PRIVATE SDL2::SDL2)
target_compile_definitions(inputcommon PUBLIC HAVE_SDL2=1)
target_link_libraries(inputcommon PRIVATE SDL3::SDL3)
target_compile_definitions(inputcommon PUBLIC HAVE_SDL3=1)
endif()
if(MSVC)

View File

@ -31,7 +31,7 @@
#define CIFACE_USE_PIPES
#endif
#define CIFACE_USE_DUALSHOCKUDPCLIENT
#if defined(HAVE_SDL2)
#if defined(HAVE_SDL3)
#define CIFACE_USE_SDL
#endif
#if defined(HAVE_HIDAPI)

View File

@ -3,6 +3,8 @@
#include "InputCommon/ControllerInterface/SDL/SDL.h"
#include <optional>
#include <span>
#include <thread>
#include <vector>
@ -10,15 +12,76 @@
#include <Windows.h>
#endif
#include <SDL.h>
#include <SDL3/SDL.h>
#include "Common/Event.h"
#include "Common/Keyboard.h"
#include "Common/Logging/Log.h"
#include "Common/ScopeGuard.h"
#include "Core/Host.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h"
#include "InputCommon/ControllerInterface/SDL/SDLGamepad.h"
namespace
{
using UniqueSDLWindow = std::unique_ptr<SDL_Window, void (*)(SDL_Window*)>;
// Based on sdl2-compat
SDL_Window* SDL_CreateWindowFrom(void* handle)
{
SDL_Window* window;
{
SDL_PropertiesID props;
props = SDL_CreateProperties();
if (!props)
{
return NULL;
}
SDL_SetPointerProperty(props, "sdl2-compat.external_window", handle);
window = SDL_CreateWindowWithProperties(props);
SDL_DestroyProperties(props);
}
if (SDL_TextInputActive(window))
{
SDL_PropertiesID props = SDL_CreateProperties();
SDL_SetNumberProperty(props, SDL_PROP_TEXTINPUT_TYPE_NUMBER, SDL_TEXTINPUT_TYPE_TEXT);
SDL_SetNumberProperty(props, SDL_PROP_TEXTINPUT_CAPITALIZATION_NUMBER, SDL_CAPITALIZE_NONE);
SDL_SetBooleanProperty(props, SDL_PROP_TEXTINPUT_AUTOCORRECT_BOOLEAN, false);
SDL_StartTextInputWithProperties(window, props);
SDL_DestroyProperties(props);
}
return window;
}
std::optional<const char*> UpdateKeyboardHandle(UniqueSDLWindow* unique_window)
{
std::optional<const char*> error;
// Doesn't seem to work with X11 until SDL 3.10:
// - https://github.com/libsdl-org/SDL/pull/10467
void* keyboard_handle = Common::KeyboardContext::GetWindowHandle();
SDL_Window* keyboard_window = SDL_CreateWindowFrom(keyboard_handle);
if (keyboard_window == nullptr)
error = SDL_GetError();
unique_window->reset(keyboard_window);
if (error.has_value())
return error;
// SDL aggressive hooking might make the window borderless sometimes
if (!Host_RendererIsFullscreen())
{
SDL_SetWindowFullscreen(keyboard_window, 0);
SDL_SetWindowBordered(keyboard_window, true);
}
Common::KeyboardContext::UpdateLayout();
return error;
}
} // namespace
namespace ciface::SDL
{
@ -31,7 +94,7 @@ public:
void UpdateInput(std::vector<std::weak_ptr<ciface::Core::Device>>& devices_to_remove) override;
private:
void OpenAndAddDevice(int index);
void OpenAndAddDevice(SDL_JoystickID instance_id);
bool HandleEventAndContinue(const SDL_Event& e);
@ -39,6 +102,7 @@ private:
Uint32 m_stop_event_type;
Uint32 m_populate_event_type;
std::thread m_hotplug_thread;
UniqueSDLWindow m_keyboard_window{nullptr, SDL_DestroyWindow};
};
std::unique_ptr<ciface::InputBackend> CreateInputBackend(ControllerInterface* controller_interface)
@ -48,10 +112,10 @@ std::unique_ptr<ciface::InputBackend> CreateInputBackend(ControllerInterface* co
static void EnableSDLLogging()
{
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE);
SDL_LogSetOutputFunction(
SDL_SetLogPriorities(SDL_LOG_PRIORITY_VERBOSE);
SDL_SetLogOutputFunction(
[](void*, int category, SDL_LogPriority priority, const char* message) {
std::string category_name;
std::string_view category_name{};
switch (category)
{
case SDL_LOG_CATEGORY_APPLICATION:
@ -81,8 +145,10 @@ static void EnableSDLLogging()
case SDL_LOG_CATEGORY_TEST:
category_name = "test";
break;
case SDL_LOG_CATEGORY_GPU:
category_name = "gpu";
break;
default:
category_name = fmt::format("unknown({})", category);
break;
}
@ -108,8 +174,16 @@ static void EnableSDLLogging()
break;
}
GENERIC_LOG_FMT(Common::Log::LogType::CONTROLLERINTERFACE, log_level, "{}: {}",
category_name, message);
if (category_name.empty())
{
GENERIC_LOG_FMT(Common::Log::LogType::CONTROLLERINTERFACE, log_level, "unknown({}): {}",
category, message);
}
else
{
GENERIC_LOG_FMT(Common::Log::LogType::CONTROLLERINTERFACE, log_level, "{}: {}",
category_name, message);
}
},
nullptr);
}
@ -122,9 +196,8 @@ InputBackend::InputBackend(ControllerInterface* controller_interface)
// This is required on windows so that SDL's joystick code properly pumps window messages
SDL_SetHint(SDL_HINT_JOYSTICK_THREAD, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
// We want buttons to come in as positions, not labels
SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0");
SDL_SetHint(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, "1");
// We have our own WGI backend. Enabling SDL's WGI handling creates even more redundant devices.
SDL_SetHint(SDL_HINT_JOYSTICK_WGI, "0");
@ -139,13 +212,13 @@ InputBackend::InputBackend(ControllerInterface* controller_interface)
{
Common::ScopeGuard init_guard([this] { m_init_event.Set(); });
if (SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER) != 0)
if (!SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMEPAD))
{
ERROR_LOG_FMT(CONTROLLERINTERFACE, "SDL failed to initialize");
return;
}
const Uint32 custom_events_start = SDL_RegisterEvents(2);
const Uint32 custom_events_start = SDL_RegisterEvents(5);
if (custom_events_start == static_cast<Uint32>(-1))
{
ERROR_LOG_FMT(CONTROLLERINTERFACE, "SDL failed to register custom events");
@ -153,6 +226,9 @@ InputBackend::InputBackend(ControllerInterface* controller_interface)
}
m_stop_event_type = custom_events_start;
m_populate_event_type = custom_events_start + 1;
Common::KeyboardContext::s_sdl_init_event_type = custom_events_start + 2;
Common::KeyboardContext::s_sdl_update_event_type = custom_events_start + 3;
Common::KeyboardContext::s_sdl_quit_event_type = custom_events_start + 4;
// Drain all of the events and add the initial joysticks before returning. Otherwise, the
// individual joystick events as well as the custom populate event will be handled _after_
@ -160,7 +236,7 @@ InputBackend::InputBackend(ControllerInterface* controller_interface)
// duplicate devices. Adding devices will actually "fail" here, as the ControllerInterface
// hasn't finished initializing yet.
SDL_Event e;
while (SDL_PollEvent(&e) != 0)
while (SDL_PollEvent(&e))
{
if (!HandleEventAndContinue(e))
return;
@ -179,7 +255,7 @@ InputBackend::InputBackend(ControllerInterface* controller_interface)
#endif
SDL_Event e;
while (SDL_WaitEvent(&e) != 0)
while (SDL_WaitEvent(&e))
{
if (!HandleEventAndContinue(e))
return;
@ -221,20 +297,20 @@ void InputBackend::PopulateDevices()
SDL_PushEvent(&populate_event);
}
void InputBackend::UpdateInput(std::vector<std::weak_ptr<ciface::Core::Device>>& devices_to_remove)
void InputBackend::UpdateInput(std::vector<std::weak_ptr<ciface::Core::Device>>&)
{
SDL_GameControllerUpdate();
SDL_UpdateGamepads();
}
void InputBackend::OpenAndAddDevice(int index)
void InputBackend::OpenAndAddDevice(SDL_JoystickID instance_id)
{
SDL_GameController* gc = SDL_GameControllerOpen(index);
SDL_Joystick* js = SDL_JoystickOpen(index);
SDL_Gamepad* gc = SDL_OpenGamepad(instance_id);
SDL_Joystick* js = SDL_OpenJoystick(instance_id);
if (js)
if (js != nullptr)
{
if (SDL_JoystickNumButtons(js) > 255 || SDL_JoystickNumAxes(js) > 255 ||
SDL_JoystickNumHats(js) > 255 || SDL_JoystickNumBalls(js) > 255)
if (SDL_GetNumJoystickButtons(js) > 255 || SDL_GetNumJoystickAxes(js) > 255 ||
SDL_GetNumJoystickHats(js) > 255 || SDL_GetNumJoystickBalls(js) > 255)
{
// This device is invalid, don't use it
// Some crazy devices (HP webcam 2100) end up as HID devices
@ -249,17 +325,12 @@ void InputBackend::OpenAndAddDevice(int index)
bool InputBackend::HandleEventAndContinue(const SDL_Event& e)
{
if (e.type == SDL_JOYDEVICEADDED)
if (e.type == SDL_EVENT_JOYSTICK_ADDED)
{
// NOTE: SDL_JOYDEVICEADDED's `jdevice.which` is a device index in SDL2.
// It will change to an "instance ID" in SDL3.
// OpenAndAddDevice impl and calls will need refactoring when changing to SDL3.
static_assert(!SDL_VERSION_ATLEAST(3, 0, 0), "Refactoring is needed for SDL3.");
OpenAndAddDevice(e.jdevice.which);
}
else if (e.type == SDL_JOYDEVICEREMOVED)
else if (e.type == SDL_EVENT_JOYSTICK_REMOVED)
{
// NOTE: SDL_JOYDEVICEREMOVED's `jdevice.which` is an "instance ID".
GetControllerInterface().RemoveDevice([&e](const auto* device) {
return device->GetSource() == "SDL" &&
static_cast<const GameController*>(device)->GetSDLInstanceID() == e.jdevice.which;
@ -268,14 +339,56 @@ bool InputBackend::HandleEventAndContinue(const SDL_Event& e)
else if (e.type == m_populate_event_type)
{
GetControllerInterface().PlatformPopulateDevices([this] {
for (int i = 0; i < SDL_NumJoysticks(); ++i)
OpenAndAddDevice(i);
int joystick_count = 0;
auto* const joystick_ids = SDL_GetJoysticks(&joystick_count);
for (auto instance_id : std::span(joystick_ids, joystick_count))
OpenAndAddDevice(instance_id);
SDL_free(joystick_ids);
});
}
else if (e.type == m_stop_event_type)
{
return false;
}
else if (e.type == Common::KeyboardContext::s_sdl_init_event_type)
{
if (!SDL_InitSubSystem(SDL_INIT_VIDEO))
{
ERROR_LOG_FMT(IOS_USB, "SDL failed to init subsystem to capture keyboard input: {}",
SDL_GetError());
return true;
}
if (const auto error = UpdateKeyboardHandle(&m_keyboard_window); error.has_value())
{
ERROR_LOG_FMT(IOS_USB, "SDL failed to attach window to capture keyboard input: {}", *error);
return true;
}
}
else if (e.type == Common::KeyboardContext::s_sdl_update_event_type)
{
if (!SDL_WasInit(SDL_INIT_VIDEO))
return true;
// Release previous SDLWindow
m_keyboard_window.reset();
if (const auto error = UpdateKeyboardHandle(&m_keyboard_window); error.has_value())
{
ERROR_LOG_FMT(IOS_USB, "SDL failed to switch window to capture keyboard input: {}", *error);
return true;
}
}
else if (e.type == Common::KeyboardContext::s_sdl_quit_event_type)
{
m_keyboard_window.reset();
SDL_QuitSubSystem(SDL_INIT_VIDEO);
}
else if (e.type == SDL_EVENT_KEYMAP_CHANGED)
{
Common::KeyboardContext::UpdateLayout();
}
return true;
}

View File

@ -4,9 +4,11 @@
#include "InputCommon/ControllerInterface/SDL/SDLGamepad.h"
#include <array>
#include <span>
#include <unordered_set>
#include "Common/Logging/Log.h"
#include "Common/ScopeGuard.h"
namespace ciface::SDL
{
@ -17,16 +19,12 @@ bool IsTriggerAxis(int index)
return index >= 4;
}
GameController::GameController(SDL_GameController* const gamecontroller,
SDL_Joystick* const joystick)
GameController::GameController(SDL_Gamepad* const gamecontroller, SDL_Joystick* const joystick)
: m_gamecontroller(gamecontroller), m_joystick(joystick)
{
const char* name;
if (gamecontroller)
name = SDL_GameControllerName(gamecontroller);
else
name = SDL_JoystickName(joystick);
m_name = name != nullptr ? name : "Unknown";
const char* const sdl_name = (gamecontroller != nullptr) ? SDL_GetGamepadName(gamecontroller) :
SDL_GetJoystickName(joystick);
m_name = (sdl_name != nullptr) ? sdl_name : "Unknown";
// If a Joystick input has a GameController equivalent button/hat we don't add it.
// "Equivalent" axes are still added as hidden/undetectable inputs to handle
@ -35,17 +33,17 @@ GameController::GameController(SDL_GameController* const gamecontroller,
std::unordered_set<int> registered_buttons;
std::unordered_set<int> registered_hats;
std::unordered_set<int> registered_axes;
const auto register_mapping = [&](const SDL_GameControllerButtonBind& bind) {
switch (bind.bindType)
const auto register_mapping = [&](const SDL_GamepadBinding& bind) {
switch (bind.input_type)
{
case SDL_CONTROLLER_BINDTYPE_BUTTON:
registered_buttons.insert(bind.value.button);
case SDL_GAMEPAD_BINDTYPE_BUTTON:
registered_buttons.insert(bind.input.button);
break;
case SDL_CONTROLLER_BINDTYPE_HAT:
registered_hats.insert(bind.value.hat.hat);
case SDL_GAMEPAD_BINDTYPE_HAT:
registered_hats.insert(bind.input.hat.hat);
break;
case SDL_CONTROLLER_BINDTYPE_AXIS:
registered_axes.insert(bind.value.axis);
case SDL_GAMEPAD_BINDTYPE_AXIS:
registered_axes.insert(bind.input.axis.axis);
break;
default:
break;
@ -55,25 +53,22 @@ GameController::GameController(SDL_GameController* const gamecontroller,
if (gamecontroller != nullptr)
{
// Inputs
int binding_count = 0;
auto** bindings = SDL_GetGamepadBindings(gamecontroller, &binding_count);
Common::ScopeGuard free_bindings([&] { SDL_free(bindings); });
// Buttons
for (u8 i = 0; i != size(s_sdl_button_names); ++i)
for (auto* const binding : std::span(bindings, binding_count))
{
SDL_GameControllerButton button = static_cast<SDL_GameControllerButton>(i);
if (SDL_GameControllerHasButton(m_gamecontroller, button))
{
AddInput(new Button(gamecontroller, button));
register_mapping(*binding);
register_mapping(SDL_GameControllerGetBindForButton(gamecontroller, button));
}
}
// Axes
for (u8 i = 0; i != size(s_sdl_axis_names); ++i)
{
SDL_GameControllerAxis axis = static_cast<SDL_GameControllerAxis>(i);
if (SDL_GameControllerHasAxis(m_gamecontroller, axis))
switch (binding->output_type)
{
case SDL_GAMEPAD_BINDTYPE_BUTTON:
AddInput(new Button(gamecontroller, *binding));
break;
case SDL_GAMEPAD_BINDTYPE_AXIS:
{
const auto axis = binding->output.axis.axis;
if (IsTriggerAxis(axis))
{
AddInput(new Axis(m_gamecontroller, 32767, axis));
@ -84,19 +79,23 @@ GameController::GameController(SDL_GameController* const gamecontroller,
AddInput(new Axis(m_gamecontroller, -32768, axis));
AddInput(new Axis(m_gamecontroller, 32767, axis));
}
register_mapping(SDL_GameControllerGetBindForAxis(gamecontroller, axis));
break;
}
default:
break;
}
}
const auto properties = SDL_GetGamepadProperties(m_gamecontroller);
// Rumble
if (SDL_GameControllerHasRumble(m_gamecontroller))
if (SDL_GetBooleanProperty(properties, SDL_PROP_GAMEPAD_CAP_RUMBLE_BOOLEAN, false))
{
AddOutput(new CombinedMotor(*this, &m_low_freq_rumble, &m_high_freq_rumble));
AddOutput(new Rumble("Motor L", *this, &m_low_freq_rumble, &GameController::UpdateRumble));
AddOutput(new Rumble("Motor R", *this, &m_high_freq_rumble, &GameController::UpdateRumble));
}
if (SDL_GameControllerHasRumbleTriggers(m_gamecontroller))
if (SDL_GetBooleanProperty(properties, SDL_PROP_GAMEPAD_CAP_TRIGGER_RUMBLE_BOOLEAN, false))
{
AddOutput(new Rumble("Trigger L", *this, &m_trigger_l_rumble,
&GameController::UpdateRumbleTriggers));
@ -105,7 +104,7 @@ GameController::GameController(SDL_GameController* const gamecontroller,
}
// Touchpad
if (SDL_GameControllerGetNumTouchpads(m_gamecontroller) > 0)
if (SDL_GetNumGamepadTouchpads(m_gamecontroller) > 0)
{
const char* const name_x = "Touchpad X";
AddInput(new NonDetectableDirectionalInput<-1>(name_x, &m_touchpad_x));
@ -119,7 +118,7 @@ GameController::GameController(SDL_GameController* const gamecontroller,
// Motion
const auto add_sensor = [this](SDL_SensorType type, std::string_view sensor_name,
const SDLMotionAxisList& axes) {
if (SDL_GameControllerSetSensorEnabled(m_gamecontroller, type, SDL_TRUE) == 0)
if (SDL_SetGamepadSensorEnabled(m_gamecontroller, type, true))
{
for (const SDLMotionAxis& axis : axes)
{
@ -140,7 +139,7 @@ GameController::GameController(SDL_GameController* const gamecontroller,
// Legacy inputs
// Buttons
int n_legacy_buttons = SDL_JoystickNumButtons(joystick);
int n_legacy_buttons = SDL_GetNumJoystickButtons(joystick);
if (n_legacy_buttons < 0)
{
ERROR_LOG_FMT(CONTROLLERINTERFACE, "Error in SDL_JoystickNumButtons(): {}", SDL_GetError());
@ -155,7 +154,7 @@ GameController::GameController(SDL_GameController* const gamecontroller,
}
// Axes
int n_legacy_axes = SDL_JoystickNumAxes(joystick);
int n_legacy_axes = SDL_GetNumJoystickAxes(joystick);
if (n_legacy_axes < 0)
{
ERROR_LOG_FMT(CONTROLLERINTERFACE, "Error in SDL_JoystickNumAxes(): {}", SDL_GetError());
@ -171,7 +170,7 @@ GameController::GameController(SDL_GameController* const gamecontroller,
}
// Hats
int n_legacy_hats = SDL_JoystickNumHats(joystick);
int n_legacy_hats = SDL_GetNumJoystickHats(joystick);
if (n_legacy_hats < 0)
{
ERROR_LOG_FMT(CONTROLLERINTERFACE, "Error in SDL_JoystickNumHats(): {}", SDL_GetError());
@ -188,16 +187,16 @@ GameController::GameController(SDL_GameController* const gamecontroller,
}
// Haptics
if (SDL_JoystickIsHaptic(m_joystick))
if (SDL_IsJoystickHaptic(m_joystick))
{
m_haptic = SDL_HapticOpenFromJoystick(m_joystick);
m_haptic = SDL_OpenHapticFromJoystick(m_joystick);
if (m_haptic)
{
const unsigned int supported_effects = SDL_HapticQuery(m_haptic);
const unsigned int supported_effects = SDL_GetMaxHapticEffects(m_haptic);
// Disable autocenter:
if (supported_effects & SDL_HAPTIC_AUTOCENTER)
SDL_HapticSetAutocenter(m_haptic, 0);
SDL_SetHapticAutocenter(m_haptic, 0);
// Constant
if (supported_effects & SDL_HAPTIC_CONSTANT)
@ -224,16 +223,19 @@ GameController::GameController(SDL_GameController* const gamecontroller,
}
}
// Needed to make the below power level not "UNKNOWN".
SDL_JoystickUpdate();
// Battery
if (SDL_JoystickPowerLevel const power_level = SDL_JoystickCurrentPowerLevel(m_joystick);
power_level != SDL_JOYSTICK_POWER_UNKNOWN)
{
m_battery_value = GetBatteryValueFromSDLPowerLevel(power_level);
if (UpdateBatteryLevel())
AddInput(new BatteryInput{&m_battery_value});
}
}
bool GameController::UpdateBatteryLevel()
{
int battery_percent = 0;
if (SDL_GetJoystickPowerInfo(m_joystick, &battery_percent) == SDL_POWERSTATE_ERROR)
return false;
m_battery_value = std::max(0, battery_percent);
return true;
}
GameController::~GameController()
@ -241,20 +243,18 @@ GameController::~GameController()
if (m_haptic)
{
// stop/destroy all effects
SDL_HapticStopAll(m_haptic);
SDL_StopHapticEffects(m_haptic);
// close haptic before joystick
SDL_HapticClose(m_haptic);
SDL_CloseHaptic(m_haptic);
m_haptic = nullptr;
}
if (m_gamecontroller)
{
// stop all rumble
SDL_GameControllerRumble(m_gamecontroller, 0, 0, 0);
// close game controller
SDL_GameControllerClose(m_gamecontroller);
SDL_RumbleGamepad(m_gamecontroller, 0, 0, 0);
SDL_CloseGamepad(m_gamecontroller);
}
// close joystick
SDL_JoystickClose(m_joystick);
SDL_CloseJoystick(m_joystick);
}
std::string GameController::GetName() const
@ -267,20 +267,28 @@ std::string GameController::GetSource() const
return "SDL";
}
int GameController::GetSDLInstanceID() const
SDL_JoystickID GameController::GetSDLInstanceID() const
{
return SDL_JoystickInstanceID(m_joystick);
return SDL_GetJoystickID(m_joystick);
}
std::string GameController::Button::GetName() const
{
return s_sdl_button_names[m_button];
const auto button = m_binding.output.button;
if (std::size_t(button) >= std::size(s_sdl_button_names))
return GetLegacyButtonName(button);
return s_sdl_button_names[button];
}
std::string GameController::Axis::GetName() const
{
if (std::size_t(m_axis) >= std::size(s_sdl_axis_names))
return GetLegacyAxisName(m_axis, m_range);
if (IsTriggerAxis(m_axis))
return std::string(s_sdl_axis_names[m_axis]);
return s_sdl_axis_names[m_axis];
bool negative = m_range < 0;
@ -293,12 +301,12 @@ std::string GameController::Axis::GetName() const
ControlState GameController::Button::GetState() const
{
return SDL_GameControllerGetButton(m_gc, m_button);
return SDL_GetGamepadButton(m_gc, m_binding.output.button);
}
ControlState GameController::Axis::GetState() const
{
return ControlState(SDL_GameControllerGetAxis(m_gc, m_axis)) / m_range;
return ControlState(SDL_GetGamepadAxis(m_gc, m_axis)) / m_range;
}
bool GameController::Button::IsMatchingName(std::string_view name) const
@ -306,25 +314,26 @@ bool GameController::Button::IsMatchingName(std::string_view name) const
if (GetName() == name)
return true;
// So that SDL can be a superset of XInput
if (name == "Button A")
return GetName() == "Button S";
if (name == "Button B")
return GetName() == "Button E";
if (name == "Button X")
return GetName() == "Button W";
if (name == "Button Y")
return GetName() == "Button N";
// Positionally match XInput button names.
// e.g. Switch Pro controller A-button matches "Button B".
// e.g. PlayStation controller Circle-button matches "Button B".
if (m_binding.output.button == SDL_GAMEPAD_BUTTON_SOUTH && name == "Button A")
return true;
if (m_binding.output.button == SDL_GAMEPAD_BUTTON_EAST && name == "Button B")
return true;
if (m_binding.output.button == SDL_GAMEPAD_BUTTON_WEST && name == "Button X")
return true;
if (m_binding.output.button == SDL_GAMEPAD_BUTTON_NORTH && name == "Button Y")
return true;
// Match legacy names.
const auto bind = SDL_GameControllerGetBindForButton(m_gc, m_button);
switch (bind.bindType)
// Match the old "Button 0"-like names.
switch (m_binding.input_type)
{
case SDL_CONTROLLER_BINDTYPE_BUTTON:
return name == GetLegacyButtonName(bind.value.button);
case SDL_CONTROLLER_BINDTYPE_HAT:
return name == GetLegacyHatName(bind.value.hat.hat,
GetDirectionFromHatMask(u8(bind.value.hat.hat_mask)));
case SDL_GAMEPAD_BINDTYPE_BUTTON:
return name == GetLegacyButtonName(m_binding.input.button);
case SDL_GAMEPAD_BINDTYPE_HAT:
return name == GetLegacyHatName(m_binding.input.hat.hat,
GetDirectionFromHatMask(m_binding.input.hat.hat_mask));
default:
return false;
}
@ -333,24 +342,24 @@ bool GameController::Button::IsMatchingName(std::string_view name) const
ControlState GameController::MotionInput::GetState() const
{
std::array<float, 3> data{};
SDL_GameControllerGetSensorData(m_gc, m_type, data.data(), (int)data.size());
SDL_GetGamepadSensorData(m_gc, m_type, data.data(), (int)data.size());
return m_scale * data[m_index];
}
// Legacy input
ControlState GameController::LegacyButton::GetState() const
{
return SDL_JoystickGetButton(m_js, m_index);
return SDL_GetJoystickButton(m_js, m_index);
}
ControlState GameController::LegacyAxis::GetState() const
{
return ControlState(SDL_JoystickGetAxis(m_js, m_index)) / m_range;
return ControlState(SDL_GetJoystickAxis(m_js, m_index)) / m_range;
}
ControlState GameController::LegacyHat::GetState() const
{
return (SDL_JoystickGetHat(m_js, m_index) & (1 << m_direction)) > 0;
return (SDL_GetJoystickHat(m_js, m_index) & (1 << m_direction)) > 0;
}
void GameController::HapticEffect::UpdateEffect()
@ -360,22 +369,22 @@ void GameController::HapticEffect::UpdateEffect()
if (m_id < 0)
{
// Upload and try to play the effect.
m_id = SDL_HapticNewEffect(m_haptic, &m_effect);
m_id = SDL_CreateHapticEffect(m_haptic, &m_effect);
if (m_id >= 0)
SDL_HapticRunEffect(m_haptic, m_id, 1);
SDL_RunHapticEffect(m_haptic, m_id, 1);
}
else
{
// Effect is already playing. Update parameters.
SDL_HapticUpdateEffect(m_haptic, m_id, &m_effect);
SDL_UpdateHapticEffect(m_haptic, m_id, &m_effect);
}
}
else if (m_id >= 0)
{
// Stop and remove the effect.
SDL_HapticStopEffect(m_haptic, m_id);
SDL_HapticDestroyEffect(m_haptic, m_id);
SDL_StopHapticEffect(m_haptic, m_id);
SDL_DestroyHapticEffect(m_haptic, m_id);
m_id = -1;
}
}

View File

@ -5,8 +5,8 @@
#include <array>
#include <SDL.h>
#include <SDL_haptic.h>
#include <SDL3/SDL_gamepad.h>
#include <SDL3/SDL_haptic.h>
#include "Common/MathUtil.h"
@ -29,7 +29,7 @@ std::string GetLegacyHatName(int index, int direction)
return "Hat " + std::to_string(index) + ' ' + "NESW"[direction];
}
constexpr int GetDirectionFromHatMask(u8 mask)
constexpr int GetDirectionFromHatMask(int mask)
{
return MathUtil::IntLog2(mask);
}
@ -37,38 +37,6 @@ constexpr int GetDirectionFromHatMask(u8 mask)
static_assert(GetDirectionFromHatMask(SDL_HAT_UP) == 0);
static_assert(GetDirectionFromHatMask(SDL_HAT_LEFT) == 3);
ControlState GetBatteryValueFromSDLPowerLevel(SDL_JoystickPowerLevel sdl_power_level)
{
// Values come from comments in SDL_joystick.h
// A proper percentage will be exposed in SDL3.
ControlState result;
switch (sdl_power_level)
{
case SDL_JOYSTICK_POWER_EMPTY:
result = 0.025;
break;
case SDL_JOYSTICK_POWER_LOW:
result = 0.125;
break;
case SDL_JOYSTICK_POWER_MEDIUM:
result = 0.45;
break;
case SDL_JOYSTICK_POWER_FULL:
result = 0.85;
break;
case SDL_JOYSTICK_POWER_WIRED:
case SDL_JOYSTICK_POWER_MAX:
result = 1.0;
break;
case SDL_JOYSTICK_POWER_UNKNOWN:
default:
result = 0.0;
break;
}
return result * ciface::BATTERY_INPUT_MAX_VALUE;
}
} // namespace
namespace ciface::SDL
@ -82,29 +50,29 @@ private:
{
public:
std::string GetName() const override;
Button(SDL_GameController* gc, SDL_GameControllerButton button) : m_gc(gc), m_button(button) {}
Button(SDL_Gamepad* gc, const SDL_GamepadBinding& binding) : m_gc(gc), m_binding(binding) {}
ControlState GetState() const override;
bool IsMatchingName(std::string_view name) const override;
private:
SDL_GameController* const m_gc;
const SDL_GameControllerButton m_button;
SDL_Gamepad* const m_gc;
const SDL_GamepadBinding m_binding;
};
class Axis : public Core::Device::Input
{
public:
std::string GetName() const override;
Axis(SDL_GameController* gc, Sint16 range, SDL_GameControllerAxis axis)
Axis(SDL_Gamepad* gc, Sint16 range, SDL_GamepadAxis axis)
: m_gc(gc), m_range(range), m_axis(axis)
{
}
ControlState GetState() const override;
private:
SDL_GameController* const m_gc;
SDL_Gamepad* const m_gc;
const Sint16 m_range;
const SDL_GameControllerAxis m_axis;
const SDL_GamepadAxis m_axis;
};
// Legacy inputs
@ -180,7 +148,7 @@ private:
std::string GetName() const override { return m_name; }
void SetState(ControlState state) override
{
const auto new_state = state * std::numeric_limits<Uint16>::max();
const auto new_state = std::lround(state * std::numeric_limits<Uint16>::max());
if (m_state == new_state)
return;
@ -205,7 +173,7 @@ private:
std::string GetName() const override { return "Motor"; }
void SetState(ControlState state) override
{
const auto new_state = state * std::numeric_limits<Uint16>::max();
const auto new_state = std::lround(state * std::numeric_limits<Uint16>::max());
if (m_low_state == new_state && m_high_state == new_state)
return;
@ -327,7 +295,7 @@ private:
class MotionInput : public Input
{
public:
MotionInput(std::string name, SDL_GameController* gc, SDL_SensorType type, int index,
MotionInput(std::string name, SDL_Gamepad* gc, SDL_SensorType type, int index,
ControlState scale)
: m_name(std::move(name)), m_gc(gc), m_type(type), m_index(index), m_scale(scale)
{
@ -340,7 +308,7 @@ private:
private:
std::string m_name;
SDL_GameController* const m_gc;
SDL_Gamepad* const m_gc;
SDL_SensorType const m_type;
int const m_index;
@ -348,26 +316,25 @@ private:
};
public:
GameController(SDL_GameController* const gamecontroller, SDL_Joystick* const joystick);
~GameController();
GameController(SDL_Gamepad* gamecontroller, SDL_Joystick* joystick);
~GameController() override;
std::string GetName() const override;
std::string GetSource() const override;
int GetSDLInstanceID() const;
SDL_JoystickID GetSDLInstanceID() const;
Core::DeviceRemoval UpdateInput() override
{
m_battery_value = GetBatteryValueFromSDLPowerLevel(SDL_JoystickCurrentPowerLevel(m_joystick));
UpdateBatteryLevel();
// We only support one touchpad and one finger.
const int touchpad_index = 0;
const int finger_index = 0;
if (SDL_GameControllerGetNumTouchpads(m_gamecontroller) > touchpad_index &&
SDL_GameControllerGetNumTouchpadFingers(m_gamecontroller, touchpad_index) > finger_index)
if (SDL_GetNumGamepadTouchpads(m_gamecontroller) > touchpad_index &&
SDL_GetNumGamepadTouchpadFingers(m_gamecontroller, touchpad_index) > finger_index)
{
Uint8 state = 0;
SDL_GameControllerGetTouchpadFinger(m_gamecontroller, touchpad_index, finger_index, &state,
&m_touchpad_x, &m_touchpad_y, &m_touchpad_pressure);
SDL_GetGamepadTouchpadFinger(m_gamecontroller, touchpad_index, finger_index, nullptr,
&m_touchpad_x, &m_touchpad_y, &m_touchpad_pressure);
m_touchpad_x = m_touchpad_x * 2 - 1;
m_touchpad_y = m_touchpad_y * 2 - 1;
}
@ -378,23 +345,24 @@ public:
private:
void UpdateRumble()
{
SDL_GameControllerRumble(m_gamecontroller, m_low_freq_rumble, m_high_freq_rumble,
RUMBLE_LENGTH_MS);
SDL_RumbleGamepad(m_gamecontroller, m_low_freq_rumble, m_high_freq_rumble, RUMBLE_LENGTH_MS);
}
void UpdateRumbleTriggers()
{
SDL_GameControllerRumbleTriggers(m_gamecontroller, m_trigger_l_rumble, m_trigger_r_rumble,
RUMBLE_LENGTH_MS);
SDL_RumbleGamepadTriggers(m_gamecontroller, m_trigger_l_rumble, m_trigger_r_rumble,
RUMBLE_LENGTH_MS);
}
bool UpdateBatteryLevel();
Uint16 m_low_freq_rumble = 0;
Uint16 m_high_freq_rumble = 0;
Uint16 m_trigger_l_rumble = 0;
Uint16 m_trigger_r_rumble = 0;
SDL_GameController* const m_gamecontroller;
SDL_Gamepad* const m_gamecontroller;
std::string m_name;
SDL_Joystick* const m_joystick;
SDL_Haptic* m_haptic = nullptr;
@ -413,35 +381,35 @@ struct SDLMotionAxis
using SDLMotionAxisList = std::array<SDLMotionAxis, 6>;
static constexpr std::array<const char*, 21> s_sdl_button_names = {
"Button S", // SDL_CONTROLLER_BUTTON_A
"Button E", // SDL_CONTROLLER_BUTTON_B
"Button W", // SDL_CONTROLLER_BUTTON_X
"Button N", // SDL_CONTROLLER_BUTTON_Y
"Back", // SDL_CONTROLLER_BUTTON_BACK
"Guide", // SDL_CONTROLLER_BUTTON_GUIDE
"Start", // SDL_CONTROLLER_BUTTON_START
"Thumb L", // SDL_CONTROLLER_BUTTON_LEFTSTICK
"Thumb R", // SDL_CONTROLLER_BUTTON_RIGHTSTICK
"Shoulder L", // SDL_CONTROLLER_BUTTON_LEFTSHOULDER
"Shoulder R", // SDL_CONTROLLER_BUTTON_RIGHTSHOULDER
"Pad N", // SDL_CONTROLLER_BUTTON_DPAD_UP
"Pad S", // SDL_CONTROLLER_BUTTON_DPAD_DOWN
"Pad W", // SDL_CONTROLLER_BUTTON_DPAD_LEFT
"Pad E", // SDL_CONTROLLER_BUTTON_DPAD_RIGHT
"Misc 1", // SDL_CONTROLLER_BUTTON_MISC1
"Paddle 1", // SDL_CONTROLLER_BUTTON_PADDLE1
"Paddle 2", // SDL_CONTROLLER_BUTTON_PADDLE2
"Paddle 3", // SDL_CONTROLLER_BUTTON_PADDLE3
"Paddle 4", // SDL_CONTROLLER_BUTTON_PADDLE4
"Touchpad", // SDL_CONTROLLER_BUTTON_TOUCHPAD
"Button S", // SDL_GAMEPAD_BUTTON_SOUTH
"Button E", // SDL_GAMEPAD_BUTTON_EAST
"Button W", // SDL_GAMEPAD_BUTTON_WEST
"Button N", // SDL_GAMEPAD_BUTTON_NORTH
"Back", // SDL_GAMEPAD_BUTTON_BACK
"Guide", // SDL_GAMEPAD_BUTTON_GUIDE
"Start", // SDL_GAMEPAD_BUTTON_START
"Thumb L", // SDL_GAMEPAD_BUTTON_LEFT_STICK
"Thumb R", // SDL_GAMEPAD_BUTTON_RIGHT_STICK
"Shoulder L", // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER
"Shoulder R", // SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER
"Pad N", // SDL_GAMEPAD_BUTTON_DPAD_UP
"Pad S", // SDL_GAMEPAD_BUTTON_DPAD_DOWN
"Pad W", // SDL_GAMEPAD_BUTTON_DPAD_LEFT
"Pad E", // SDL_GAMEPAD_BUTTON_DPAD_RIGHT
"Misc 1", // SDL_GAMEPAD_BUTTON_MISC1
"Paddle 1", // SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1
"Paddle 2", // SDL_GAMEPAD_BUTTON_LEFT_PADDLE1
"Paddle 3", // SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2
"Paddle 4", // SDL_GAMEPAD_BUTTON_LEFT_PADDLE2
"Touchpad", // SDL_GAMEPAD_BUTTON_TOUCHPAD
};
static constexpr std::array<const char*, 6> s_sdl_axis_names = {
"Left X", // SDL_CONTROLLER_AXIS_LEFTX
"Left Y", // SDL_CONTROLLER_AXIS_LEFTY
"Right X", // SDL_CONTROLLER_AXIS_RIGHTX
"Right Y", // SDL_CONTROLLER_AXIS_RIGHTY
"Trigger L", // SDL_CONTROLLER_AXIS_TRIGGERLEFT
"Trigger R", // SDL_CONTROLLER_AXIS_TRIGGERRIGHT
"Left X", // SDL_GAMEPAD_AXIS_LEFTX
"Left Y", // SDL_GAMEPAD_AXIS_LEFTY
"Right X", // SDL_GAMEPAD_AXIS_RIGHTX
"Right Y", // SDL_GAMEPAD_AXIS_RIGHTY
"Trigger L", // SDL_GAMEPAD_AXIS_LEFT_TRIGGER
"Trigger R", // SDL_GAMEPAD_AXIS_RIGHT_TRIGGER
};
static constexpr SDLMotionAxisList SDL_AXES_ACCELEROMETER = {{
{"Up", 1, 1},

View File

@ -19,8 +19,6 @@ set_target_properties(MacUpdater PROPERTIES
MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in"
OUTPUT_NAME "Dolphin Updater")
target_compile_options(MacUpdater PRIVATE -x objective-c++)
# Copy icon into the bundle
target_sources(MacUpdater PRIVATE "${CMAKE_SOURCE_DIR}/Data/Dolphin.icns")
set_source_files_properties("${CMAKE_SOURCE_DIR}/Data/Dolphin.icns" PROPERTIES MACOSX_PACKAGE_LOCATION Resources)

View File

@ -44,7 +44,7 @@
<PreprocessorDefinitions>HAS_VULKAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>HAS_LIBMGBA;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(AutoUpdate)'!='false'">AUTOUPDATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>HAVE_SDL2;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>HAVE_SDL3;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>USE_RETRO_ACHIEVEMENTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>RC_CLIENT_SUPPORTS_HASH;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions>RC_CLIENT_SUPPORTS_RAINTEGRATION;%(PreprocessorDefinitions)</PreprocessorDefinitions>

View File

@ -81,7 +81,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fmt", "..\Externals\fmt\fmt
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "spirv_cross", "..\Externals\spirv_cross\spirv_cross.vcxproj", "{3D780617-EC8C-4721-B9FD-DFC9BB658C7C}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SDL2", "..\Externals\SDL\SDL2.vcxproj", "{8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA}"
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SDL3", "..\Externals\SDL\SDL3.vcxproj", "{8DC244EE-A0BD-4038-BAF7-CFAFA5EB2BAA}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FatFs", "..\Externals\FatFs\FatFs.vcxproj", "{3F17D282-A77D-4931-B844-903AD0809A5E}"
EndProject