virtual Logitech G27 for Gran Turismo 6 (#17135)
Some checks are pending
Generate Translation Template / Generate Translation Template (push) Waiting to run
Build RPCS3 / RPCS3 Linux ubuntu-24.04 gcc (push) Waiting to run
Build RPCS3 / RPCS3 Linux ubuntu-24.04-arm clang (push) Waiting to run
Build RPCS3 / RPCS3 Linux ubuntu-24.04 clang (push) Waiting to run
Build RPCS3 / RPCS3 Windows (push) Waiting to run

This commit is contained in:
Katharine Chui 2025-05-02 21:27:40 +02:00 committed by GitHub
parent 7a9733dcca
commit c0a1e8d5a5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 3162 additions and 118 deletions

View file

@ -418,6 +418,8 @@ target_sources(rpcs3_emu PRIVATE
Io/usb_device.cpp
Io/usb_vfs.cpp
Io/usio.cpp
Io/LogitechG27.cpp
Io/LogitechG27Config.cpp
)
# Np

View file

@ -38,6 +38,9 @@
#include "Emu/Io/usio.h"
#include "Emu/Io/usio_config.h"
#include "Emu/Io/midi_config_types.h"
#ifdef HAVE_SDL3
#include "Emu/Io/LogitechG27.h"
#endif
#include <libusb.h>
@ -208,7 +211,11 @@ private:
// GT5 Wheels&co
#ifdef HAVE_SDL3
{0x046D, 0xC283, 0xC29B, "lgFF_c283_c29b", &usb_device_logitech_g27::get_num_emu_devices, &usb_device_logitech_g27::make_instance},
#else
{0x046D, 0xC283, 0xC29B, "lgFF_c283_c29b", nullptr, nullptr},
#endif
{0x044F, 0xB653, 0xB653, "Thrustmaster RGT FFB Pro", nullptr, nullptr},
{0x044F, 0xB65A, 0xB65A, "Thrustmaster F430", nullptr, nullptr},
{0x044F, 0xB65D, 0xB65D, "Thrustmaster FFB", nullptr, nullptr},

1554
rpcs3/Emu/Io/LogitechG27.cpp Normal file

File diff suppressed because it is too large Load diff

123
rpcs3/Emu/Io/LogitechG27.h Normal file
View file

@ -0,0 +1,123 @@
#pragma once
#include "Emu/Io/usb_device.h"
#include "LogitechG27Config.h"
#ifndef _MSC_VER
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wold-style-cast"
#endif
#include "SDL3/SDL.h"
#ifndef _MSC_VER
#pragma GCC diagnostic pop
#endif
#include <map>
#include <vector>
#include <thread>
enum logitech_g27_ffb_state
{
G27_FFB_INACTIVE,
G27_FFB_DOWNLOADED,
G27_FFB_PLAYING
};
struct logitech_g27_ffb_slot
{
logitech_g27_ffb_state state;
uint64_t last_update;
SDL_HapticEffect last_effect;
int effect_id;
};
struct sdl_mapping
{
uint32_t device_type_id; // (vendor_id << 16) | product_id
sdl_mapping_type type;
uint64_t id;
hat_component hat;
bool reverse;
bool positive_axis;
};
struct logitech_g27_sdl_mapping
{
sdl_mapping steering;
sdl_mapping throttle;
sdl_mapping brake;
sdl_mapping clutch;
sdl_mapping shift_up;
sdl_mapping shift_down;
sdl_mapping up;
sdl_mapping down;
sdl_mapping left;
sdl_mapping right;
sdl_mapping triangle;
sdl_mapping cross;
sdl_mapping square;
sdl_mapping circle;
// mappings based on g27 compat mode on g29
sdl_mapping l2;
sdl_mapping l3;
sdl_mapping r2;
sdl_mapping r3;
sdl_mapping plus;
sdl_mapping minus;
sdl_mapping dial_clockwise;
sdl_mapping dial_anticlockwise;
sdl_mapping select;
sdl_mapping pause;
sdl_mapping shifter_1;
sdl_mapping shifter_2;
sdl_mapping shifter_3;
sdl_mapping shifter_4;
sdl_mapping shifter_5;
sdl_mapping shifter_6;
sdl_mapping shifter_r;
};
class usb_device_logitech_g27 : public usb_device_emulated
{
public:
usb_device_logitech_g27(u32 controller_index, const std::array<u8, 7>& location);
~usb_device_logitech_g27();
static std::shared_ptr<usb_device> make_instance(u32 controller_index, const std::array<u8, 7>& location);
static u16 get_num_emu_devices();
void control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer) override;
void interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint, UsbTransfer* transfer) override;
bool open_device() override;
private:
u32 m_controller_index;
logitech_g27_sdl_mapping m_mapping;
bool m_reverse_effects;
std::mutex m_sdl_handles_mutex;
SDL_Joystick* m_led_joystick_handle = nullptr;
SDL_Haptic* m_haptic_handle = nullptr;
std::map<uint32_t, std::vector<SDL_Joystick*>> m_joysticks;
bool m_fixed_loop = false;
uint16_t m_wheel_range = 200;
logitech_g27_ffb_slot m_effect_slots[4];
SDL_HapticEffect m_default_spring_effect = {0};
int m_default_spring_effect_id = -1;
bool m_enabled = false;
std::thread m_house_keeping_thread;
std::mutex m_thread_control_mutex;
bool m_stop_thread;
void sdl_refresh();
};

View file

@ -0,0 +1,66 @@
#include "stdafx.h"
#ifdef HAVE_SDL3
#include "Utilities/File.h"
#include "LogitechG27Config.h"
emulated_logitech_g27_config g_cfg_logitech_g27;
LOG_CHANNEL(cfg_log, "CFG");
emulated_logitech_g27_config::emulated_logitech_g27_config()
: m_path(fmt::format("%s%s.yml", fs::get_config_dir(true), "LogitechG27"))
{
}
void emulated_logitech_g27_config::reset()
{
const std::lock_guard<std::mutex> lock(m_mutex);
cfg::node::from_default();
}
void emulated_logitech_g27_config::save(bool lock_mutex)
{
std::unique_lock lock(m_mutex, std::defer_lock);
if (lock_mutex)
{
lock.lock();
}
cfg_log.notice("Saving LogitechG27 config: '%s'", m_path);
if (!fs::create_path(fs::get_parent_dir(m_path)))
{
cfg_log.fatal("Failed to create path: '%s' (%s)", m_path, fs::g_tls_error);
}
if (!cfg::node::save(m_path))
{
cfg_log.error("Failed to save LogitechG27 config to '%s' (error=%s)", m_path, fs::g_tls_error);
}
}
bool emulated_logitech_g27_config::load()
{
const std::lock_guard lock(m_mutex);
cfg_log.notice("Loading LogitechG27 config: %s", m_path);
from_default();
if (fs::file cfg_file{m_path, fs::read})
{
if (const std::string content = cfg_file.to_string(); !content.empty())
{
return from_string(content);
}
}
else
{
save(false);
}
return true;
}
#endif

View file

@ -0,0 +1,104 @@
#pragma once
#include "Utilities/Config.h"
#include <mutex>
enum sdl_mapping_type
{
MAPPING_BUTTON = 0,
MAPPING_HAT,
MAPPING_AXIS,
};
enum hat_component
{
HAT_NONE = 0,
HAT_UP,
HAT_DOWN,
HAT_LEFT,
HAT_RIGHT
};
struct emulated_logitech_g27_mapping : cfg::node
{
cfg::uint<0, 0xFFFFFFFF> device_type_id;
cfg::uint<0, 0xFFFFFFFF> type;
cfg::uint<0, 0xFFFFFFFFFFFFFFFF> id;
cfg::uint<0, 0xFFFFFFFF> hat;
cfg::_bool reverse;
emulated_logitech_g27_mapping(cfg::node* owner, std::string name, uint32_t device_type_id_def, sdl_mapping_type type_def, uint64_t id_def, hat_component hat_def, bool reverse_def)
: cfg::node(owner, std::move(name)),
device_type_id(this, "device_type_id", device_type_id_def),
type(this, "type", type_def),
id(this, "id", id_def),
hat(this, "hat", hat_def),
reverse(this, "reverse", reverse_def)
{
}
};
struct emulated_logitech_g27_config : cfg::node
{
public:
std::mutex m_mutex;
// TODO these defaults are for a shifter-less G29 + a xbox controller for shifter testing, perhaps find a new default
emulated_logitech_g27_mapping steering{this, "steering", 0x046dc24f, MAPPING_AXIS, 0, HAT_NONE, false};
emulated_logitech_g27_mapping throttle{this, "throttle", 0x046dc24f, MAPPING_AXIS, 2, HAT_NONE, false};
emulated_logitech_g27_mapping brake{this, "brake", 0x046dc24f, MAPPING_AXIS, 3, HAT_NONE, false};
emulated_logitech_g27_mapping clutch{this, "clutch", 0x046dc24f, MAPPING_AXIS, 1, HAT_NONE, false};
emulated_logitech_g27_mapping shift_up{this, "shift_up", 0x046dc24f, MAPPING_BUTTON, 4, HAT_NONE, false};
emulated_logitech_g27_mapping shift_down{this, "shift_down", 0x046dc24f, MAPPING_BUTTON, 5, HAT_NONE, false};
emulated_logitech_g27_mapping up{this, "up", 0x046dc24f, MAPPING_HAT, 0, HAT_UP, false};
emulated_logitech_g27_mapping down{this, "down", 0x046dc24f, MAPPING_HAT, 0, HAT_DOWN, false};
emulated_logitech_g27_mapping left{this, "left", 0x046dc24f, MAPPING_HAT, 0, HAT_LEFT, false};
emulated_logitech_g27_mapping right{this, "right", 0x046dc24f, MAPPING_HAT, 0, HAT_RIGHT, false};
emulated_logitech_g27_mapping triangle{this, "triangle", 0x046dc24f, MAPPING_BUTTON, 3, HAT_NONE, false};
emulated_logitech_g27_mapping cross{this, "cross", 0x046dc24f, MAPPING_BUTTON, 0, HAT_NONE, false};
emulated_logitech_g27_mapping square{this, "square", 0x046dc24f, MAPPING_BUTTON, 1, HAT_NONE, false};
emulated_logitech_g27_mapping circle{this, "circle", 0x046dc24f, MAPPING_BUTTON, 2, HAT_NONE, false};
emulated_logitech_g27_mapping l2{this, "l2", 0x046dc24f, MAPPING_BUTTON, 7, HAT_NONE, false};
emulated_logitech_g27_mapping l3{this, "l3", 0x046dc24f, MAPPING_BUTTON, 11, HAT_NONE, false};
emulated_logitech_g27_mapping r2{this, "r2", 0x046dc24f, MAPPING_BUTTON, 6, HAT_NONE, false};
emulated_logitech_g27_mapping r3{this, "r3", 0x046dc24f, MAPPING_BUTTON, 10, HAT_NONE, false};
emulated_logitech_g27_mapping plus{this, "plus", 0x046dc24f, MAPPING_BUTTON, 19, HAT_NONE, false};
emulated_logitech_g27_mapping minus{this, "minus", 0x046dc24f, MAPPING_BUTTON, 20, HAT_NONE, false};
emulated_logitech_g27_mapping dial_clockwise{this, "dial_clockwise", 0x046dc24f, MAPPING_BUTTON, 21, HAT_NONE, false};
emulated_logitech_g27_mapping dial_anticlockwise{this, "dial_anticlockwise", 0x046dc24f, MAPPING_BUTTON, 22, HAT_NONE, false};
emulated_logitech_g27_mapping select{this, "select", 0x046dc24f, MAPPING_BUTTON, 8, HAT_NONE, false};
emulated_logitech_g27_mapping pause{this, "pause", 0x046dc24f, MAPPING_BUTTON, 9, HAT_NONE, false};
emulated_logitech_g27_mapping shifter_1{this, "shifter_1", 0x045e028e, MAPPING_BUTTON, 3, HAT_NONE, false};
emulated_logitech_g27_mapping shifter_2{this, "shifter_2", 0x045e028e, MAPPING_BUTTON, 0, HAT_NONE, false};
emulated_logitech_g27_mapping shifter_3{this, "shifter_3", 0x045e028e, MAPPING_BUTTON, 2, HAT_NONE, false};
emulated_logitech_g27_mapping shifter_4{this, "shifter_4", 0x045e028e, MAPPING_BUTTON, 1, HAT_NONE, false};
emulated_logitech_g27_mapping shifter_5{this, "shifter_5", 0x045e028e, MAPPING_HAT, 0, HAT_UP, false};
emulated_logitech_g27_mapping shifter_6{this, "shifter_6", 0x045e028e, MAPPING_HAT, 0, HAT_DOWN, false};
emulated_logitech_g27_mapping shifter_r{this, "shifter_r", 0x045e028e, MAPPING_HAT, 0, HAT_LEFT, false};
cfg::_bool reverse_effects{this, "reverse_effects", true};
cfg::uint<0, 0xFFFFFFFF> ffb_device_type_id{this, "ffb_device_type_id", 0x046dc24f};
cfg::uint<0, 0xFFFFFFFF> led_device_type_id{this, "led_device_type_id", 0x046dc24f};
cfg::_bool enabled{this, "enabled", false};
emulated_logitech_g27_config();
bool load();
void save(bool lock_mutex = true);
void reset();
private:
const std::string m_path;
};
extern emulated_logitech_g27_config g_cfg_logitech_g27;

View file

@ -0,0 +1,132 @@
#ifdef HAVE_SDL3
#include "stdafx.h"
#ifndef _MSC_VER
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wold-style-cast"
#endif
#include "SDL3/SDL.h"
#ifndef _MSC_VER
#pragma GCC diagnostic pop
#endif
#include "util/logs.hpp"
#include "sdl_instance.h"
LOG_CHANNEL(sdl_log, "SDL");
sdl_instance::~sdl_instance()
{
// Only quit SDL once on exit. SDL uses a global state internally...
if (m_initialized)
{
sdl_log.notice("Quitting SDL ...");
SDL_Quit();
}
}
sdl_instance& sdl_instance::get_instance()
{
static sdl_instance instance{};
return instance;
}
void sdl_instance::pump_events()
{
const std::lock_guard<std::mutex> lock(m_instance_mutex);
if (m_initialized)
SDL_PumpEvents();
}
bool sdl_instance::initialize()
{
const std::lock_guard<std::mutex> lock(m_instance_mutex);
// Only init SDL once. SDL uses a global state internally...
if (m_initialized)
{
return true;
}
sdl_log.notice("Initializing SDL ...");
// Set non-dynamic hints before SDL_Init
if (!SDL_SetHint(SDL_HINT_JOYSTICK_THREAD, "1"))
{
sdl_log.error("Could not set SDL_HINT_JOYSTICK_THREAD: %s", SDL_GetError());
}
if (!SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD))
{
sdl_log.error("Could not initialize! SDL Error: %s", SDL_GetError());
return false;
}
SDL_SetLogPriorities(SDL_LOG_PRIORITY_VERBOSE);
SDL_SetLogOutputFunction([](void*, int category, SDL_LogPriority priority, const char* message)
{
std::string category_name;
switch (category)
{
case SDL_LOG_CATEGORY_APPLICATION:
category_name = "app";
break;
case SDL_LOG_CATEGORY_ERROR:
category_name = "error";
break;
case SDL_LOG_CATEGORY_ASSERT:
category_name = "assert";
break;
case SDL_LOG_CATEGORY_SYSTEM:
category_name = "system";
break;
case SDL_LOG_CATEGORY_AUDIO:
category_name = "audio";
break;
case SDL_LOG_CATEGORY_VIDEO:
category_name = "video";
break;
case SDL_LOG_CATEGORY_RENDER:
category_name = "render";
break;
case SDL_LOG_CATEGORY_INPUT:
category_name = "input";
break;
case SDL_LOG_CATEGORY_TEST:
category_name = "test";
break;
default:
category_name = fmt::format("unknown(%d)", category);
break;
}
switch (priority)
{
case SDL_LOG_PRIORITY_VERBOSE:
case SDL_LOG_PRIORITY_DEBUG:
sdl_log.trace("%s: %s", category_name, message);
break;
case SDL_LOG_PRIORITY_INFO:
sdl_log.notice("%s: %s", category_name, message);
break;
case SDL_LOG_PRIORITY_WARN:
sdl_log.warning("%s: %s", category_name, message);
break;
case SDL_LOG_PRIORITY_ERROR:
sdl_log.error("%s: %s", category_name, message);
break;
case SDL_LOG_PRIORITY_CRITICAL:
sdl_log.error("%s: %s", category_name, message);
break;
default:
break;
}
},
nullptr);
m_initialized = true;
return true;
}
#endif

View file

@ -0,0 +1,19 @@
#pragma once
#include <mutex>
struct sdl_instance
{
public:
sdl_instance() = default;
~sdl_instance();
static sdl_instance& get_instance();
bool initialize();
void pump_events();
private:
bool m_initialized = false;
std::mutex m_instance_mutex;
};

View file

@ -5,122 +5,12 @@
#include "Emu/system_utils.hpp"
#include "Emu/system_config.h"
#include "Emu/System.h"
#include "sdl_instance.h"
#include <mutex>
LOG_CHANNEL(sdl_log, "SDL");
struct sdl_instance
{
public:
sdl_instance() = default;
~sdl_instance()
{
// Only quit SDL once on exit. SDL uses a global state internally...
if (m_initialized)
{
sdl_log.notice("Quitting SDL ...");
SDL_Quit();
}
}
static sdl_instance& get_instance()
{
static sdl_instance instance {};
return instance;
}
bool initialize()
{
// Only init SDL once. SDL uses a global state internally...
if (m_initialized)
{
return true;
}
sdl_log.notice("Initializing SDL ...");
// Set non-dynamic hints before SDL_Init
if (!SDL_SetHint(SDL_HINT_JOYSTICK_THREAD, "1"))
{
sdl_log.error("Could not set SDL_HINT_JOYSTICK_THREAD: %s", SDL_GetError());
}
if (!SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD))
{
sdl_log.error("Could not initialize! SDL Error: %s", SDL_GetError());
return false;
}
SDL_SetLogPriorities(SDL_LOG_PRIORITY_VERBOSE);
SDL_SetLogOutputFunction([](void*, int category, SDL_LogPriority priority, const char* message)
{
std::string category_name;
switch (category)
{
case SDL_LOG_CATEGORY_APPLICATION:
category_name = "app";
break;
case SDL_LOG_CATEGORY_ERROR:
category_name = "error";
break;
case SDL_LOG_CATEGORY_ASSERT:
category_name = "assert";
break;
case SDL_LOG_CATEGORY_SYSTEM:
category_name = "system";
break;
case SDL_LOG_CATEGORY_AUDIO:
category_name = "audio";
break;
case SDL_LOG_CATEGORY_VIDEO:
category_name = "video";
break;
case SDL_LOG_CATEGORY_RENDER:
category_name = "render";
break;
case SDL_LOG_CATEGORY_INPUT:
category_name = "input";
break;
case SDL_LOG_CATEGORY_TEST:
category_name = "test";
break;
default:
category_name = fmt::format("unknown(%d)", category);
break;
}
switch (priority)
{
case SDL_LOG_PRIORITY_VERBOSE:
case SDL_LOG_PRIORITY_DEBUG:
sdl_log.trace("%s: %s", category_name, message);
break;
case SDL_LOG_PRIORITY_INFO:
sdl_log.notice("%s: %s", category_name, message);
break;
case SDL_LOG_PRIORITY_WARN:
sdl_log.warning("%s: %s", category_name, message);
break;
case SDL_LOG_PRIORITY_ERROR:
sdl_log.error("%s: %s", category_name, message);
break;
case SDL_LOG_PRIORITY_CRITICAL:
sdl_log.error("%s: %s", category_name, message);
break;
default:
break;
}
}, nullptr);
m_initialized = true;
return true;
}
private:
bool m_initialized = false;
};
sdl_pad_handler::sdl_pad_handler() : PadHandlerBase(pad_handler::sdl)
{
button_list =
@ -314,7 +204,7 @@ void sdl_pad_handler::process()
if (!m_is_init)
return;
SDL_PumpEvents();
sdl_instance::get_instance().pump_events();
PadHandlerBase::process();
}
@ -771,7 +661,7 @@ void sdl_pad_handler::get_motion_sensors(const std::string& pad_id, const motion
if (!m_is_init)
return;
SDL_PumpEvents();
sdl_instance::get_instance().pump_events();
PadHandlerBase::get_motion_sensors(pad_id, callback, fail_callback, preview_values, sensors);
}
@ -781,7 +671,7 @@ PadHandlerBase::connection sdl_pad_handler::get_next_button_press(const std::str
if (!m_is_init)
return connection::disconnected;
SDL_PumpEvents();
sdl_instance::get_instance().pump_events();
return PadHandlerBase::get_next_button_press(padId, callback, fail_callback, call_type, buttons);
}

View file

@ -40,7 +40,7 @@
<ItemDefinitionGroup>
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<AdditionalIncludeDirectories>..\3rdparty\miniupnp\miniupnp\miniupnpc\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\flatbuffers\include;..\3rdparty\libusb\libusb\libusb;..\3rdparty\yaml-cpp\yaml-cpp\include;..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\rtmidi\rtmidi;..\3rdparty\zlib\zlib;$(SolutionDir)build\lib\$(Configuration)-$(Platform)\llvm_build\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm_build\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm_build\include;$(VULKAN_SDK)\Include;..\3rdparty\zstd\zstd\lib;$(SolutionDir)3rdparty\fusion\fusion\Fusion;$(SolutionDir)3rdparty\wolfssl\extra\win32</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\3rdparty\miniupnp\miniupnp\miniupnpc\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\flatbuffers\include;..\3rdparty\libusb\libusb\libusb;..\3rdparty\yaml-cpp\yaml-cpp\include;..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\rtmidi\rtmidi;..\3rdparty\zlib\zlib;$(SolutionDir)build\lib\$(Configuration)-$(Platform)\llvm_build\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm_build\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm_build\include;$(VULKAN_SDK)\Include;..\3rdparty\zstd\zstd\lib;$(SolutionDir)3rdparty\fusion\fusion\Fusion;$(SolutionDir)3rdparty\wolfssl\extra\win32;$(SolutionDir)3rdparty\libsdl-org\SDL\include</AdditionalIncludeDirectories>
<Optimization Condition="'$(Configuration)|$(Platform)'=='Release|x64'">MaxSpeed</Optimization>
<PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">AL_LIBTYPE_STATIC;MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL3;ZLIB_CONST;WOLFSSL_USER_SETTINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<PreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='Release|x64'">AL_LIBTYPE_STATIC;MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL3;ZLIB_CONST;WOLFSSL_USER_SETTINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
@ -99,6 +99,8 @@
<ClCompile Include="Emu\Io\TopShotElite.cpp" />
<ClCompile Include="Emu\Io\TopShotFearmaster.cpp" />
<ClCompile Include="Emu\Io\Turntable.cpp" />
<ClCompile Include="Emu\Io\LogitechG27.cpp" />
<ClCompile Include="Emu\Io\LogitechG27Config.cpp" />
<ClCompile Include="Emu\Io\usio.cpp" />
<ClCompile Include="Emu\Audio\AudioBackend.cpp" />
<ClCompile Include="Emu\Io\interception.cpp" />
@ -608,6 +610,8 @@
<ClInclude Include="Emu\Io\turntable_config.h" />
<ClInclude Include="Emu\Io\usio.h" />
<ClInclude Include="Emu\Io\usio_config.h" />
<ClInclude Include="Emu\Io\LogitechG27.h" />
<ClInclude Include="Emu\Io\LogitechG27Config.h" />
<ClInclude Include="Emu\IPC_config.h" />
<ClInclude Include="Emu\IPC_socket.h" />
<ClInclude Include="Emu\localized_string.h" />
@ -1070,4 +1074,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View file

@ -945,6 +945,12 @@
<ClCompile Include="Emu\Io\Buzz.cpp">
<Filter>Emu\Io</Filter>
</ClCompile>
<ClCompile Include="Emu\Io\LogitechG27.cpp">
<Filter>Emu\Io</Filter>
</ClCompile>
<ClCompile Include="Emu\Io\LogitechG27Config.cpp">
<Filter>Emu\Io</Filter>
</ClCompile>
<ClCompile Include="Emu\Io\usio.cpp">
<Filter>Emu\Io</Filter>
</ClCompile>
@ -2160,6 +2166,12 @@
<ClInclude Include="Emu\Io\Buzz.h">
<Filter>Emu\Io</Filter>
</ClInclude>
<ClInclude Include="Emu\Io\LogitechG27.h">
<Filter>Emu\Io</Filter>
</ClInclude>
<ClInclude Include="Emu\Io\LogitechG27Config.h">
<Filter>Emu\Io</Filter>
</ClInclude>
<ClInclude Include="Emu\Io\usio.h">
<Filter>Emu\Io</Filter>
</ClInclude>
@ -2866,4 +2878,4 @@
<Filter>Emu\GPU\RSX\Program\MSAA</Filter>
</None>
</ItemGroup>
</Project>
</Project>

View file

@ -199,6 +199,7 @@
<ClCompile Include="Input\raw_mouse_handler.cpp" />
<ClCompile Include="Input\ps_move_handler.cpp" />
<ClCompile Include="Input\sdl_pad_handler.cpp" />
<ClCompile Include="Input\sdl_instance.cpp" />
<ClCompile Include="Input\skateboard_pad_handler.cpp" />
<ClCompile Include="main.cpp" />
<ClCompile Include="main_application.cpp" />
@ -266,6 +267,9 @@
<ClCompile Include="QTGeneratedFiles\Debug\moc_emulated_pad_settings_dialog.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_emulated_logitech_g27_settings_dialog.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_emu_settings.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
@ -551,6 +555,9 @@
<ClCompile Include="QTGeneratedFiles\Release\moc_emulated_pad_settings_dialog.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_emulated_logitech_g27_settings_dialog.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_emu_settings.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
@ -797,6 +804,7 @@
<ClCompile Include="rpcs3qt\downloader.cpp" />
<ClCompile Include="rpcs3qt\elf_memory_dumping_dialog.cpp" />
<ClCompile Include="rpcs3qt\emulated_pad_settings_dialog.cpp" />
<ClCompile Include="rpcs3qt\emulated_logitech_g27_settings_dialog.cpp" />
<ClCompile Include="rpcs3qt\fatal_error_dialog.cpp" />
<ClCompile Include="rpcs3qt\flow_layout.cpp" />
<ClCompile Include="rpcs3qt\flow_widget.cpp" />
@ -1037,6 +1045,7 @@
<ClInclude Include="Input\raw_mouse_handler.h" />
<ClInclude Include="Input\ps_move_handler.h" />
<ClInclude Include="Input\sdl_pad_handler.h" />
<ClInclude Include="Input\sdl_instance.h" />
<ClInclude Include="Input\skateboard_pad_handler.h" />
<ClInclude Include="main_application.h" />
<ClInclude Include="Input/mm_joystick_handler.h" />
@ -1353,6 +1362,16 @@
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DMINIUPNP_STATICLIB -DHAVE_SDL3 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\rtmidi\rtmidi" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\libsdl-org\SDL\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg"</Command>
</CustomBuild>
<CustomBuild Include="rpcs3qt\emulated_logitech_g27_settings_dialog.h">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DMINIUPNP_STATICLIB -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\rtmidi\rtmidi" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Moc%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DMINIUPNP_STATICLIB -DHAVE_SDL3 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\rtmidi\rtmidi" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\libsdl-org\SDL\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg"</Command>
</CustomBuild>
<ClInclude Include="rpcs3qt\emu_settings_type.h" />
<CustomBuild Include="rpcs3qt\render_creator.h">
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing %(Identity)...</Message>

View file

@ -996,6 +996,9 @@
<ClCompile Include="Input\sdl_pad_handler.cpp">
<Filter>Io\SDL</Filter>
</ClCompile>
<ClCompile Include="Input\sdl_instance.cpp">
<Filter>Io\SDL</Filter>
</ClCompile>
<ClCompile Include="rpcs3qt\config_checker.cpp">
<Filter>Gui\log</Filter>
</ClCompile>
@ -1140,6 +1143,15 @@
<ClCompile Include="QTGeneratedFiles\Release\moc_emulated_pad_settings_dialog.cpp">
<Filter>Generated Files\Release</Filter>
</ClCompile>
<ClCompile Include="rpcs3qt\emulated_logitech_g27_settings_dialog.cpp">
<Filter>Gui\settings</Filter>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Debug\moc_emulated_logitech_g27_settings_dialog.cpp">
<Filter>Generated Files\Debug</Filter>
</ClCompile>
<ClCompile Include="QTGeneratedFiles\Release\moc_emulated_logitech_g27_settings_dialog.cpp">
<Filter>Generated Files\Release</Filter>
</ClCompile>
<ClCompile Include="rpcs3qt\localized_emu.cpp">
<Filter>Gui\settings</Filter>
</ClCompile>
@ -1352,6 +1364,9 @@
<ClInclude Include="Input\sdl_pad_handler.h">
<Filter>Io\SDL</Filter>
</ClInclude>
<ClInclude Include="Input\sdl_instance.h">
<Filter>Io\SDL</Filter>
</ClInclude>
<ClInclude Include="QTGeneratedFiles\ui_shortcut_dialog.h">
<Filter>Generated Files</Filter>
</ClInclude>
@ -1735,6 +1750,9 @@
<CustomBuild Include="rpcs3qt\emulated_pad_settings_dialog.h">
<Filter>Gui\settings</Filter>
</CustomBuild>
<CustomBuild Include="rpcs3qt\emulated_logitech_g27_settings_dialog.h">
<Filter>Gui\settings</Filter>
</CustomBuild>
<CustomBuild Include="rpcs3qt\midi_creator.h">
<Filter>Gui\settings</Filter>
</CustomBuild>
@ -1896,4 +1914,4 @@
<Filter>buildfiles\cmake</Filter>
</Text>
</ItemGroup>
</Project>
</Project>

View file

@ -22,6 +22,7 @@ add_library(rpcs3_ui STATIC
emu_settings.cpp
elf_memory_dumping_dialog.cpp
emulated_pad_settings_dialog.cpp
emulated_logitech_g27_settings_dialog.cpp
fatal_error_dialog.cpp
find_dialog.cpp
flow_layout.cpp
@ -158,6 +159,7 @@ add_library(rpcs3_ui STATIC
../Input/raw_mouse_config.cpp
../Input/raw_mouse_handler.cpp
../Input/sdl_pad_handler.cpp
../Input/sdl_instance.cpp
../Input/skateboard_pad_handler.cpp
../Input/xinput_pad_handler.cpp

View file

@ -0,0 +1,972 @@
#include "stdafx.h"
#ifdef HAVE_SDL3
#include "emulated_logitech_g27_settings_dialog.h"
#include "Emu/Io/LogitechG27.h"
#include "Input/sdl_instance.h"
#include <QDialogButtonBox>
#include <QGroupBox>
#include <QLabel>
#include <QMessageBox>
#include <QPushButton>
#include <QVBoxLayout>
#include <QScrollBar>
#include <QTimer>
#include <QSlider>
#include <QComboBox>
LOG_CHANNEL(logitech_g27_cfg_log, "LOGIG27");
static constexpr const char* DEFAULT_STATUS = " ";
enum mapping_device_choice
{
CHOICE_NONE = -1,
CHOICE_STEERING = 0,
CHOICE_THROTTLE,
CHOICE_BRAKE,
CHOICE_CLUTCH,
CHOICE_SHIFT_UP,
CHOICE_SHIFT_DOWN,
CHOICE_UP,
CHOICE_DOWN,
CHOICE_LEFT,
CHOICE_RIGHT,
CHOICE_TRIANGLE,
CHOICE_CROSS,
CHOICE_SQUARE,
CHOICE_CIRCLE,
CHOICE_L2,
CHOICE_L3,
CHOICE_R2,
CHOICE_R3,
CHOICE_PLUS,
CHOICE_MINUS,
CHOICE_DIAL_CLOCKWISE,
CHOICE_DIAL_ANTICLOCKWISE,
CHOICE_SELECT,
CHOICE_PAUSE,
CHOICE_SHIFTER_1,
CHOICE_SHIFTER_2,
CHOICE_SHIFTER_3,
CHOICE_SHIFTER_4,
CHOICE_SHIFTER_5,
CHOICE_SHIFTER_6,
CHOICE_SHIFTER_R
};
class DeviceChoice : public QWidget
{
public:
DeviceChoice(QWidget* parent, const char* name)
: QWidget(parent)
{
auto layout = new QHBoxLayout(this);
setLayout(layout);
QLabel* label = new QLabel(this);
label->setText(QString(name));
label->setMinimumWidth(400);
layout->addWidget(label);
m_dropdown = new QComboBox(this);
layout->addWidget(m_dropdown);
m_disable_button = new QPushButton(QString("DISABLE"), this);
layout->addWidget(m_disable_button);
connect(m_dropdown, &QComboBox::currentIndexChanged, this, [this]()
{
m_device_choice = static_cast<mapping_device_choice>(this->m_dropdown->currentIndex());
update_display();
});
connect(m_disable_button, &QPushButton::clicked, this, [this]()
{
m_device_choice = CHOICE_NONE;
update_display();
});
m_dropdown->setPlaceholderText(QString("-- Disabled --"));
m_dropdown->addItem(QString("Steering"), QVariant(CHOICE_STEERING));
m_dropdown->addItem(QString("Throttle"), QVariant(CHOICE_THROTTLE));
m_dropdown->addItem(QString("Brake"), QVariant(CHOICE_BRAKE));
m_dropdown->addItem(QString("Clutch"), QVariant(CHOICE_CLUTCH));
m_dropdown->addItem(QString("Shift up"), QVariant(CHOICE_SHIFT_UP));
m_dropdown->addItem(QString("Shift down"), QVariant(CHOICE_SHIFT_DOWN));
m_dropdown->addItem(QString("Up"), QVariant(CHOICE_UP));
m_dropdown->addItem(QString("Down"), QVariant(CHOICE_DOWN));
m_dropdown->addItem(QString("Left"), QVariant(CHOICE_LEFT));
m_dropdown->addItem(QString("Right"), QVariant(CHOICE_RIGHT));
m_dropdown->addItem(QString("Triangle"), QVariant(CHOICE_TRIANGLE));
m_dropdown->addItem(QString("Cross"), QVariant(CHOICE_CROSS));
m_dropdown->addItem(QString("Square"), QVariant(CHOICE_SQUARE));
m_dropdown->addItem(QString("Circle"), QVariant(CHOICE_CIRCLE));
m_dropdown->addItem(QString("L2"), QVariant(CHOICE_L2));
m_dropdown->addItem(QString("L3"), QVariant(CHOICE_L3));
m_dropdown->addItem(QString("R2"), QVariant(CHOICE_R2));
m_dropdown->addItem(QString("R3"), QVariant(CHOICE_R3));
m_dropdown->addItem(QString("L4"), QVariant(CHOICE_PLUS));
m_dropdown->addItem(QString("L5"), QVariant(CHOICE_MINUS));
m_dropdown->addItem(QString("R4"), QVariant(CHOICE_DIAL_CLOCKWISE));
m_dropdown->addItem(QString("R5"), QVariant(CHOICE_DIAL_ANTICLOCKWISE));
m_dropdown->addItem(QString("Select"), QVariant(CHOICE_SELECT));
m_dropdown->addItem(QString("Pause"), QVariant(CHOICE_PAUSE));
m_dropdown->addItem(QString("Gear 1"), QVariant(CHOICE_SHIFTER_1));
m_dropdown->addItem(QString("Gear 2"), QVariant(CHOICE_SHIFTER_2));
m_dropdown->addItem(QString("Gear 3"), QVariant(CHOICE_SHIFTER_3));
m_dropdown->addItem(QString("Gear 4"), QVariant(CHOICE_SHIFTER_4));
m_dropdown->addItem(QString("Gear 5"), QVariant(CHOICE_SHIFTER_5));
m_dropdown->addItem(QString("Gear 6"), QVariant(CHOICE_SHIFTER_6));
m_dropdown->addItem(QString("Gear r"), QVariant(CHOICE_SHIFTER_R));
update_display();
}
mapping_device_choice get_device_choice()
{
return m_device_choice;
}
void set_device_choice(mapping_device_choice choice)
{
m_device_choice = choice;
update_display();
}
void set_enable(bool enable)
{
m_dropdown->setEnabled(enable);
m_disable_button->setEnabled(enable);
}
private:
mapping_device_choice m_device_choice = CHOICE_NONE;
QComboBox* m_dropdown = nullptr;
QPushButton* m_disable_button = nullptr;
void update_display()
{
m_dropdown->setCurrentIndex(static_cast<int>(m_device_choice));
}
};
class Mapping : public QGroupBox
{
public:
Mapping(QWidget* parent, emulated_logitech_g27_settings_dialog* dialog, bool is_axis, const char* name, bool flip_axis_display)
: QGroupBox(parent), m_setting_dialog(dialog), m_is_axis(is_axis), m_name(name), m_flip_axis_display(flip_axis_display)
{
QVBoxLayout* layout = new QVBoxLayout(this);
setLayout(layout);
QWidget* horizontal_container = new QWidget(this);
QHBoxLayout* horizontal_layout = new QHBoxLayout(horizontal_container);
horizontal_container->setLayout(horizontal_layout);
layout->addWidget(horizontal_container);
QLabel* label = new QLabel(horizontal_container);
label->setText(QString(name));
m_display_box = new QLabel(horizontal_container);
m_display_box->setTextFormat(Qt::RichText);
m_display_box->setWordWrap(true);
m_display_box->setFrameStyle(QFrame::Box);
m_display_box->setMinimumWidth(150);
m_map_button = new QPushButton(QString("MAP"), horizontal_container);
m_unmap_button = new QPushButton(QString("UNMAP"), horizontal_container);
m_reverse_checkbox = new QCheckBox(QString("Reverse"), horizontal_container);
if (!this->m_is_axis)
{
m_axis_status = nullptr;
m_button_status = new QCheckBox(QString("Pressed"), horizontal_container);
m_button_status->setDisabled(true);
}
else
{
m_button_status = nullptr;
m_axis_status = new QSlider(Qt::Horizontal, this);
m_axis_status->setDisabled(true);
m_axis_status->setMinimum(-0x8000);
m_axis_status->setMaximum(0x7FFF);
m_axis_status->setValue(-0x8000);
}
update_display();
horizontal_layout->addWidget(label);
horizontal_layout->addWidget(m_display_box);
if (!this->m_is_axis)
horizontal_layout->addWidget(m_button_status);
horizontal_layout->addWidget(m_map_button);
horizontal_layout->addWidget(m_unmap_button);
horizontal_layout->addWidget(m_reverse_checkbox);
if (this->m_is_axis)
layout->addWidget(m_axis_status);
connect(m_map_button, &QPushButton::clicked, this, [this]()
{
this->m_mapping_in_progress = true;
this->m_timeout_msec = 5500;
this->m_setting_dialog->set_enable(false);
this->m_last_joystick_states = this->m_setting_dialog->get_joystick_states();
});
connect(m_unmap_button, &QPushButton::clicked, this, [this]()
{
this->m_mapping.device_type_id = 0;
update_display();
});
connect(m_reverse_checkbox, &QCheckBox::clicked, this, [this]()
{
this->m_mapping.reverse = this->m_reverse_checkbox->isChecked();
});
m_tick_timer = new QTimer(this);
connect(m_tick_timer, &QTimer::timeout, this, [this]()
{
if (this->m_mapping_in_progress)
{
char text_buf[128];
int timeout_sec = this->m_timeout_msec / 1000;
const std::map<uint32_t, joystick_state>& new_joystick_states = this->m_setting_dialog->get_joystick_states();
sprintf(text_buf, "Input %s for %s, timeout in %d %s", this->m_is_axis ? "axis" : "button/hat", this->m_name.c_str(), timeout_sec, timeout_sec >= 2 ? "seconds" : "second");
this->m_setting_dialog->set_state_text(text_buf);
for (auto new_joystick_state : new_joystick_states)
{
auto last_joystick_state = this->m_last_joystick_states.find(new_joystick_state.first);
if (last_joystick_state == this->m_last_joystick_states.end())
{
continue;
}
if (this->m_is_axis)
{
const static int16_t axis_change_threshold = 0x7FFF / 5;
if (last_joystick_state->second.axes.size() != new_joystick_state.second.axes.size())
{
logitech_g27_cfg_log.error("during input state change diff, number of axes on %04x:%04x changed", new_joystick_state.first >> 16, new_joystick_state.first & 0xFFFF);
continue;
}
for (std::vector<int16_t>::size_type i = 0; i < new_joystick_state.second.axes.size(); i++)
{
int32_t diff = std::abs(last_joystick_state->second.axes[i] - new_joystick_state.second.axes[i]);
if (diff > axis_change_threshold)
{
this->m_mapping_in_progress = false;
this->m_setting_dialog->set_state_text(DEFAULT_STATUS);
this->m_setting_dialog->set_enable(true);
this->m_mapping.device_type_id = new_joystick_state.first;
this->m_mapping.type = MAPPING_AXIS;
this->m_mapping.id = i;
this->m_mapping.hat = HAT_NONE;
break;
}
}
}
else
{
if (last_joystick_state->second.buttons.size() != new_joystick_state.second.buttons.size())
{
logitech_g27_cfg_log.error("during input state change diff, number of buttons on %04x:%04x changed", new_joystick_state.first >> 16, new_joystick_state.first & 0xFFFF);
continue;
}
if (last_joystick_state->second.hats.size() != new_joystick_state.second.hats.size())
{
logitech_g27_cfg_log.error("during input state change diff, number of hats on %04x:%04x changed", new_joystick_state.first >> 16, new_joystick_state.first & 0xFFFF);
continue;
}
for (std::vector<int16_t>::size_type i = 0; i < new_joystick_state.second.buttons.size(); i++)
{
if (last_joystick_state->second.buttons[i] != new_joystick_state.second.buttons[i])
{
this->m_mapping_in_progress = false;
this->m_setting_dialog->set_state_text(DEFAULT_STATUS);
this->m_setting_dialog->set_enable(true);
this->m_mapping.device_type_id = new_joystick_state.first;
this->m_mapping.type = MAPPING_BUTTON;
this->m_mapping.id = i;
this->m_mapping.hat = HAT_NONE;
break;
}
}
for (std::vector<int16_t>::size_type i = 0; i < new_joystick_state.second.hats.size(); i++)
{
if (last_joystick_state->second.hats[i] != new_joystick_state.second.hats[i] && new_joystick_state.second.hats[i] != HAT_NONE)
{
this->m_mapping_in_progress = false;
this->m_setting_dialog->set_state_text(DEFAULT_STATUS);
this->m_setting_dialog->set_enable(true);
this->m_mapping.device_type_id = new_joystick_state.first;
this->m_mapping.type = MAPPING_HAT;
this->m_mapping.id = i;
this->m_mapping.hat = new_joystick_state.second.hats[i];
break;
}
}
}
}
this->m_timeout_msec = this->m_timeout_msec - 25;
if (this->m_timeout_msec <= 0)
{
this->m_mapping_in_progress = false;
this->m_setting_dialog->set_state_text(DEFAULT_STATUS);
this->m_setting_dialog->set_enable(true);
}
}
update_display();
});
m_tick_timer->start(25);
}
void set_enable(bool enable)
{
m_map_button->setEnabled(enable);
m_unmap_button->setEnabled(enable);
m_reverse_checkbox->setEnabled(enable);
}
void set_mapping(const sdl_mapping& mapping)
{
this->m_mapping = mapping;
update_display();
}
const sdl_mapping& get_mapping()
{
return m_mapping;
}
private:
emulated_logitech_g27_settings_dialog* m_setting_dialog = nullptr;
DeviceChoice* m_ffb_device = nullptr;
DeviceChoice* m_led_device = nullptr;
sdl_mapping m_mapping = {0};
bool m_is_axis = false;
std::string m_name = "";
bool m_flip_axis_display = false;
QLabel* m_display_box = nullptr;
QPushButton* m_map_button = nullptr;
QPushButton* m_unmap_button = nullptr;
QCheckBox* m_reverse_checkbox = nullptr;
bool m_mapping_in_progress = false;
int m_timeout_msec = 5500;
QTimer* m_tick_timer = nullptr;
std::map<uint32_t, joystick_state> m_last_joystick_states;
QCheckBox* m_button_status = nullptr;
QSlider* m_axis_status = nullptr;
void update_display()
{
char text_buf[64];
const char* type_string = nullptr;
switch (m_mapping.type)
{
case MAPPING_BUTTON:
type_string = "button";
break;
case MAPPING_HAT:
type_string = "hat";
break;
case MAPPING_AXIS:
type_string = "axis";
break;
}
const char* hat_string = nullptr;
switch (m_mapping.hat)
{
case HAT_UP:
hat_string = "up";
break;
case HAT_DOWN:
hat_string = "down";
break;
case HAT_LEFT:
hat_string = "left";
break;
case HAT_RIGHT:
hat_string = "right";
break;
case HAT_NONE:
hat_string = "";
break;
}
sprintf(text_buf, "%04x:%04x, %s %u %s", m_mapping.device_type_id >> 16, m_mapping.device_type_id & 0xFFFF, type_string, m_mapping.id, hat_string);
m_display_box->setText(QString(text_buf));
m_reverse_checkbox->setChecked(m_mapping.reverse);
if (m_button_status)
m_button_status->setChecked(m_mapping.reverse);
if (m_axis_status)
{
int32_t axis_value = (-0x8000);
if (m_mapping.reverse)
axis_value = axis_value * (-1);
if (m_flip_axis_display)
axis_value = axis_value * (-1);
if (axis_value > 0x7FFF)
axis_value = 0x7FFF;
if (axis_value < (-0x8000))
axis_value = (-0x8000);
m_axis_status->setValue(axis_value);
}
const std::map<uint32_t, joystick_state>& joystick_states = m_setting_dialog->get_joystick_states();
auto joystick_state = joystick_states.find(m_mapping.device_type_id);
if (joystick_state != joystick_states.end())
{
switch (m_mapping.type)
{
case MAPPING_BUTTON:
{
if (joystick_state->second.buttons.size() <= m_mapping.id)
break;
bool value = joystick_state->second.buttons[m_mapping.id];
if (m_mapping.reverse)
value = !value;
m_button_status->setChecked(value);
break;
}
case MAPPING_HAT:
{
if (joystick_state->second.hats.size() <= m_mapping.id)
break;
bool value = joystick_state->second.hats[m_mapping.id] == m_mapping.hat;
if (m_mapping.reverse)
value = !value;
m_button_status->setChecked(value);
break;
}
case MAPPING_AXIS:
{
if (joystick_state->second.axes.size() <= m_mapping.id)
break;
int32_t value = joystick_state->second.axes[m_mapping.id];
if (m_mapping.reverse)
value = value * (-1);
if (m_flip_axis_display)
value = value * (-1);
if (value > 0x7FFF)
value = 0x7FFF;
else if (value < (-0x8000))
value = (-0x8000);
m_axis_status->setValue(value);
break;
}
}
}
}
};
void emulated_logitech_g27_settings_dialog::save_ui_state_to_config()
{
#define SAVE_MAPPING(name, device_choice) \
{ \
const sdl_mapping& m = m_##name->get_mapping(); \
g_cfg_logitech_g27.name.device_type_id.set(m.device_type_id); \
g_cfg_logitech_g27.name.type.set(m.type); \
g_cfg_logitech_g27.name.id.set(m.id); \
g_cfg_logitech_g27.name.hat.set(m.hat); \
g_cfg_logitech_g27.name.reverse.set(m.reverse); \
if (m_ffb_device->get_device_choice() == device_choice) \
{ \
g_cfg_logitech_g27.ffb_device_type_id.set(m.device_type_id); \
} \
if (m_led_device->get_device_choice() == device_choice) \
{ \
g_cfg_logitech_g27.led_device_type_id.set(m.device_type_id); \
} \
}
SAVE_MAPPING(steering, CHOICE_STEERING);
SAVE_MAPPING(throttle, CHOICE_THROTTLE);
SAVE_MAPPING(brake, CHOICE_BRAKE);
SAVE_MAPPING(clutch, CHOICE_CLUTCH);
SAVE_MAPPING(shift_up, CHOICE_SHIFT_UP);
SAVE_MAPPING(shift_down, CHOICE_SHIFT_DOWN);
SAVE_MAPPING(up, CHOICE_UP);
SAVE_MAPPING(down, CHOICE_DOWN);
SAVE_MAPPING(left, CHOICE_LEFT);
SAVE_MAPPING(right, CHOICE_RIGHT);
SAVE_MAPPING(triangle, CHOICE_TRIANGLE);
SAVE_MAPPING(cross, CHOICE_CROSS);
SAVE_MAPPING(square, CHOICE_SQUARE);
SAVE_MAPPING(circle, CHOICE_CIRCLE);
SAVE_MAPPING(l2, CHOICE_L2);
SAVE_MAPPING(l3, CHOICE_L3);
SAVE_MAPPING(r2, CHOICE_R2);
SAVE_MAPPING(r3, CHOICE_R3);
SAVE_MAPPING(plus, CHOICE_PLUS);
SAVE_MAPPING(minus, CHOICE_MINUS);
SAVE_MAPPING(dial_clockwise, CHOICE_DIAL_CLOCKWISE);
SAVE_MAPPING(dial_anticlockwise, CHOICE_DIAL_ANTICLOCKWISE);
SAVE_MAPPING(select, CHOICE_SELECT);
SAVE_MAPPING(pause, CHOICE_PAUSE);
SAVE_MAPPING(shifter_1, CHOICE_SHIFTER_1);
SAVE_MAPPING(shifter_2, CHOICE_SHIFTER_2);
SAVE_MAPPING(shifter_3, CHOICE_SHIFTER_3);
SAVE_MAPPING(shifter_4, CHOICE_SHIFTER_4);
SAVE_MAPPING(shifter_5, CHOICE_SHIFTER_5);
SAVE_MAPPING(shifter_6, CHOICE_SHIFTER_6);
SAVE_MAPPING(shifter_r, CHOICE_SHIFTER_R);
#undef SAVE_MAPPING
g_cfg_logitech_g27.enabled.set(m_enabled->isChecked());
g_cfg_logitech_g27.reverse_effects.set(m_reverse_effects->isChecked());
if (m_ffb_device->get_device_choice() == CHOICE_NONE)
{
g_cfg_logitech_g27.ffb_device_type_id.set(0);
}
if (m_led_device->get_device_choice() == CHOICE_NONE)
{
g_cfg_logitech_g27.led_device_type_id.set(0);
}
}
void emulated_logitech_g27_settings_dialog::load_ui_state_from_config()
{
#define LOAD_MAPPING(name, device_choice) \
{ \
const sdl_mapping m = { \
.device_type_id = static_cast<uint32_t>(g_cfg_logitech_g27.name.device_type_id.get()), \
.type = static_cast<sdl_mapping_type>(g_cfg_logitech_g27.name.type.get()), \
.id = static_cast<uint8_t>(g_cfg_logitech_g27.name.id.get()), \
.hat = static_cast<hat_component>(g_cfg_logitech_g27.name.hat.get()), \
.reverse = g_cfg_logitech_g27.name.reverse.get(), \
.positive_axis = false}; \
m_##name->set_mapping(m); \
if (g_cfg_logitech_g27.ffb_device_type_id.get() == m.device_type_id && m_ffb_device->get_device_choice() == CHOICE_NONE) \
{ \
m_ffb_device->set_device_choice(device_choice); \
} \
if (g_cfg_logitech_g27.led_device_type_id.get() == m.device_type_id && m_led_device->get_device_choice() == CHOICE_NONE) \
{ \
m_led_device->set_device_choice(device_choice); \
} \
}
LOAD_MAPPING(steering, CHOICE_STEERING);
LOAD_MAPPING(throttle, CHOICE_THROTTLE);
LOAD_MAPPING(brake, CHOICE_BRAKE);
LOAD_MAPPING(clutch, CHOICE_CLUTCH);
LOAD_MAPPING(shift_up, CHOICE_SHIFT_UP);
LOAD_MAPPING(shift_down, CHOICE_SHIFT_DOWN);
LOAD_MAPPING(up, CHOICE_UP);
LOAD_MAPPING(down, CHOICE_DOWN);
LOAD_MAPPING(left, CHOICE_LEFT);
LOAD_MAPPING(right, CHOICE_RIGHT);
LOAD_MAPPING(triangle, CHOICE_TRIANGLE);
LOAD_MAPPING(cross, CHOICE_CROSS);
LOAD_MAPPING(square, CHOICE_SQUARE);
LOAD_MAPPING(circle, CHOICE_CIRCLE);
LOAD_MAPPING(l2, CHOICE_L2);
LOAD_MAPPING(l3, CHOICE_L3);
LOAD_MAPPING(r2, CHOICE_R2);
LOAD_MAPPING(r3, CHOICE_R3);
LOAD_MAPPING(plus, CHOICE_PLUS);
LOAD_MAPPING(minus, CHOICE_MINUS);
LOAD_MAPPING(dial_clockwise, CHOICE_DIAL_CLOCKWISE);
LOAD_MAPPING(dial_anticlockwise, CHOICE_DIAL_ANTICLOCKWISE);
LOAD_MAPPING(select, CHOICE_SELECT);
LOAD_MAPPING(pause, CHOICE_PAUSE);
LOAD_MAPPING(shifter_1, CHOICE_SHIFTER_1);
LOAD_MAPPING(shifter_2, CHOICE_SHIFTER_2);
LOAD_MAPPING(shifter_3, CHOICE_SHIFTER_3);
LOAD_MAPPING(shifter_4, CHOICE_SHIFTER_4);
LOAD_MAPPING(shifter_5, CHOICE_SHIFTER_5);
LOAD_MAPPING(shifter_6, CHOICE_SHIFTER_6);
LOAD_MAPPING(shifter_r, CHOICE_SHIFTER_R);
#undef LOAD_MAPPING
m_enabled->setChecked(g_cfg_logitech_g27.enabled.get());
m_reverse_effects->setChecked(g_cfg_logitech_g27.reverse_effects.get());
}
emulated_logitech_g27_settings_dialog::emulated_logitech_g27_settings_dialog(QWidget* parent)
: QDialog(parent)
{
setObjectName("emulated_logitech_g27_settings_dialog");
setWindowTitle(tr("Configure Emulated Logitech G27 Wheel"));
setAttribute(Qt::WA_DeleteOnClose);
setAttribute(Qt::WA_StyledBackground);
setModal(true);
QVBoxLayout* v_layout = new QVBoxLayout(this);
QDialogButtonBox* buttons = new QDialogButtonBox(this);
buttons->setStandardButtons(QDialogButtonBox::Apply | QDialogButtonBox::Cancel | QDialogButtonBox::Save | QDialogButtonBox::RestoreDefaults);
g_cfg_logitech_g27.load();
connect(buttons, &QDialogButtonBox::clicked, this, [this, buttons](QAbstractButton* button)
{
if (button == buttons->button(QDialogButtonBox::Apply))
{
save_ui_state_to_config();
g_cfg_logitech_g27.save();
load_ui_state_from_config();
}
else if (button == buttons->button(QDialogButtonBox::Save))
{
save_ui_state_to_config();
g_cfg_logitech_g27.save();
accept();
}
else if (button == buttons->button(QDialogButtonBox::RestoreDefaults))
{
if (QMessageBox::question(this, tr("Confirm Reset"), tr("Reset all?")) != QMessageBox::Yes)
return;
g_cfg_logitech_g27.reset();
load_ui_state_from_config();
g_cfg_logitech_g27.save();
}
else if (button == buttons->button(QDialogButtonBox::Cancel))
{
reject();
}
});
QLabel* warning = new QLabel(QString("Warning: Force feedback output were meant for Logitech G27, on stronger wheels please adjust force strength accordingly in your wheel software."), this);
warning->setStyleSheet("color: red;");
warning->setWordWrap(true);
v_layout->addWidget(warning);
m_enabled = new QCheckBox(QString("Enabled (requires game restart)"), this);
v_layout->addWidget(m_enabled);
m_reverse_effects = new QCheckBox(QString("Reverse force feedback effects"), this);
v_layout->addWidget(m_reverse_effects);
m_state_text = new QLabel(QString(DEFAULT_STATUS), this);
v_layout->addWidget(m_state_text);
m_ffb_device = new DeviceChoice(this, "Use the device with the following mapping for force feedback:");
m_led_device = new DeviceChoice(this, "Use the device with the following mapping for LED:");
m_mapping_scroll_area = new QScrollArea(this);
QWidget* mapping_widget = new QWidget(m_mapping_scroll_area);
QVBoxLayout* mapping_layout = new QVBoxLayout(mapping_widget);
mapping_widget->setLayout(mapping_layout);
m_mapping_scroll_area->setWidget(mapping_widget);
m_mapping_scroll_area->setWidgetResizable(true);
m_mapping_scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_mapping_scroll_area->setMinimumHeight(400);
m_mapping_scroll_area->setMinimumWidth(700);
v_layout->addWidget(m_mapping_scroll_area);
QLabel* axis_label = new QLabel(QString("Axes:"), mapping_widget);
mapping_layout->addWidget(axis_label);
auto add_mapping_setting = [mapping_widget, this, mapping_layout](Mapping*& target, bool is_axis, const char* display_name, bool flip_axis_display)
{
target = new Mapping(mapping_widget, this, is_axis, display_name, flip_axis_display);
mapping_layout->addWidget(target);
};
add_mapping_setting(m_steering, true, "Steering", false);
add_mapping_setting(m_throttle, true, "Throttle", true);
add_mapping_setting(m_brake, true, "Brake", true);
add_mapping_setting(m_clutch, true, "Clutch", true);
QLabel* button_label = new QLabel(QString("Buttons:"), mapping_widget);
mapping_layout->addWidget(button_label);
add_mapping_setting(m_shift_up, false, "Shift up", false);
add_mapping_setting(m_shift_down, false, "Shift down", false);
add_mapping_setting(m_up, false, "Up", false);
add_mapping_setting(m_down, false, "Down", false);
add_mapping_setting(m_left, false, "Left", false);
add_mapping_setting(m_right, false, "Right", false);
add_mapping_setting(m_triangle, false, "Triangle", false);
add_mapping_setting(m_cross, false, "Cross", false);
add_mapping_setting(m_square, false, "Square", false);
add_mapping_setting(m_circle, false, "Circle", false);
add_mapping_setting(m_l2, false, "L2", false);
add_mapping_setting(m_l3, false, "L3", false);
add_mapping_setting(m_r2, false, "R2", false);
add_mapping_setting(m_r3, false, "R3", false);
add_mapping_setting(m_plus, false, "L4", false);
add_mapping_setting(m_minus, false, "L5", false);
add_mapping_setting(m_dial_clockwise, false, "R4", false);
add_mapping_setting(m_dial_anticlockwise, false, "R5", false);
add_mapping_setting(m_select, false, "Select", false);
add_mapping_setting(m_pause, false, "Start", false);
add_mapping_setting(m_shifter_1, false, "Gear 1", false);
add_mapping_setting(m_shifter_2, false, "Gear 2", false);
add_mapping_setting(m_shifter_3, false, "Gear 3", false);
add_mapping_setting(m_shifter_4, false, "Gear 4", false);
add_mapping_setting(m_shifter_5, false, "Gear 5", false);
add_mapping_setting(m_shifter_6, false, "Gear 6", false);
add_mapping_setting(m_shifter_r, false, "Gear R", false);
v_layout->addSpacing(20);
v_layout->addWidget(m_ffb_device);
v_layout->addWidget(m_led_device);
v_layout->addWidget(buttons);
setLayout(v_layout);
load_ui_state_from_config();
m_sdl_initialized = sdl_instance::get_instance().initialize();
if (m_sdl_initialized)
get_joystick_states();
}
emulated_logitech_g27_settings_dialog::~emulated_logitech_g27_settings_dialog()
{
for (auto joystick_handle : m_joystick_handles)
{
if (joystick_handle)
SDL_CloseJoystick(reinterpret_cast<SDL_Joystick*>(joystick_handle));
}
}
static inline hat_component get_sdl_hat_component(uint8_t sdl_hat)
{
if (sdl_hat & SDL_HAT_UP)
{
return HAT_UP;
}
if (sdl_hat & SDL_HAT_DOWN)
{
return HAT_DOWN;
}
if (sdl_hat & SDL_HAT_LEFT)
{
return HAT_LEFT;
}
if (sdl_hat & SDL_HAT_RIGHT)
{
return HAT_RIGHT;
}
return HAT_NONE;
}
const std::map<uint32_t, joystick_state>& emulated_logitech_g27_settings_dialog::get_joystick_states()
{
if (!m_sdl_initialized)
{
return m_last_joystick_states;
}
uint64_t now = SDL_GetTicks();
if (SDL_GetTicks() - m_last_joystick_states_update < 25)
{
return m_last_joystick_states;
}
m_last_joystick_states_update = now;
std::map<uint32_t, joystick_state> new_joystick_states;
sdl_instance::get_instance().pump_events();
int joystick_count;
SDL_JoystickID* joystick_ids = SDL_GetJoysticks(&joystick_count);
std::vector<void*> new_joystick_handles;
if (joystick_ids != nullptr)
{
for (int i = 0; i < joystick_count; i++)
{
SDL_Joystick* cur_joystick = SDL_OpenJoystick(joystick_ids[i]);
if (cur_joystick == nullptr)
{
continue;
}
new_joystick_handles.push_back(cur_joystick);
uint32_t device_type_id = (SDL_GetJoystickVendor(cur_joystick) << 16) | SDL_GetJoystickProduct(cur_joystick);
auto cur_state = new_joystick_states.find(device_type_id);
if (cur_state == new_joystick_states.end())
{
joystick_state s;
int num_axes = SDL_GetNumJoystickAxes(cur_joystick);
int num_buttons = SDL_GetNumJoystickButtons(cur_joystick);
int num_hats = SDL_GetNumJoystickHats(cur_joystick);
for (int j = 0; j < num_axes; j++)
{
s.axes.push_back(SDL_GetJoystickAxis(cur_joystick, j));
}
for (int j = 0; j < num_buttons; j++)
{
s.buttons.push_back(SDL_GetJoystickButton(cur_joystick, j));
}
for (int j = 0; j < num_hats; j++)
{
uint8_t sdl_hat = SDL_GetJoystickHat(cur_joystick, j);
s.hats.push_back(get_sdl_hat_component(sdl_hat));
}
new_joystick_states[device_type_id] = s;
}
else
{
for (std::vector<int16_t>::size_type j = 0; j < cur_state->second.axes.size(); j++)
{
cur_state->second.axes[j] = (cur_state->second.axes[j] + SDL_GetJoystickAxis(cur_joystick, j)) / 2;
}
for (std::vector<bool>::size_type j = 0; j < cur_state->second.buttons.size(); j++)
{
cur_state->second.buttons[j] = cur_state->second.buttons[j] || SDL_GetJoystickButton(cur_joystick, j);
}
for (std::vector<hat_component>::size_type j = 0; j < cur_state->second.hats.size(); j++)
{
if (cur_state->second.hats[j] != HAT_NONE)
continue;
uint8_t sdl_hat = SDL_GetJoystickHat(cur_joystick, j);
cur_state->second.hats[j] = get_sdl_hat_component(sdl_hat);
}
}
}
}
for (auto joystick_handle : m_joystick_handles)
{
if (joystick_handle)
SDL_CloseJoystick(reinterpret_cast<SDL_Joystick*>(joystick_handle));
}
m_joystick_handles = new_joystick_handles;
m_last_joystick_states = new_joystick_states;
return m_last_joystick_states;
}
void emulated_logitech_g27_settings_dialog::set_state_text(const char* text)
{
m_state_text->setText(QString(text));
}
void emulated_logitech_g27_settings_dialog::set_enable(bool enable)
{
const int slider_position = m_mapping_scroll_area->verticalScrollBar()->sliderPosition();
m_steering->set_enable(enable);
m_throttle->set_enable(enable);
m_brake->set_enable(enable);
m_clutch->set_enable(enable);
m_shift_up->set_enable(enable);
m_shift_down->set_enable(enable);
m_up->set_enable(enable);
m_down->set_enable(enable);
m_left->set_enable(enable);
m_right->set_enable(enable);
m_triangle->set_enable(enable);
m_cross->set_enable(enable);
m_square->set_enable(enable);
m_circle->set_enable(enable);
m_l2->set_enable(enable);
m_l3->set_enable(enable);
m_r2->set_enable(enable);
m_r3->set_enable(enable);
m_plus->set_enable(enable);
m_minus->set_enable(enable);
m_dial_clockwise->set_enable(enable);
m_dial_anticlockwise->set_enable(enable);
m_select->set_enable(enable);
m_pause->set_enable(enable);
m_shifter_1->set_enable(enable);
m_shifter_2->set_enable(enable);
m_shifter_3->set_enable(enable);
m_shifter_4->set_enable(enable);
m_shifter_5->set_enable(enable);
m_shifter_6->set_enable(enable);
m_shifter_r->set_enable(enable);
m_enabled->setEnabled(enable);
m_reverse_effects->setEnabled(enable);
m_ffb_device->set_enable(enable);
m_led_device->set_enable(enable);
m_mapping_scroll_area->verticalScrollBar()->setEnabled(enable);
m_mapping_scroll_area->verticalScrollBar()->setSliderPosition(slider_position);
}
#else
// minimal symbols for sdl-less builds automoc
#include "emulated_logitech_g27_settings_dialog.h"
emulated_logitech_g27_settings_dialog::emulated_logitech_g27_settings_dialog(QWidget* parent)
: QDialog(parent) {}
emulated_logitech_g27_settings_dialog::~emulated_logitech_g27_settings_dialog() {};
#endif

View file

@ -0,0 +1,103 @@
#pragma once
#include <QDialog>
#include <QLabel>
#include <QCheckBox>
#include <QScrollArea>
#ifdef HAVE_SDL3
#ifndef _MSC_VER
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wold-style-cast"
#endif
#include "SDL3/SDL.h"
#ifndef _MSC_VER
#pragma GCC diagnostic pop
#endif
#endif
#include <map>
#include <vector>
#include <Emu/Io/LogitechG27Config.h>
struct joystick_state
{
std::vector<int16_t> axes;
std::vector<bool> buttons;
std::vector<hat_component> hats;
};
class Mapping;
class DeviceChoice;
class emulated_logitech_g27_settings_dialog : public QDialog
{
Q_OBJECT
public:
emulated_logitech_g27_settings_dialog(QWidget* parent = nullptr);
~emulated_logitech_g27_settings_dialog();
void set_state_text(const char*);
const std::map<uint32_t, joystick_state>& get_joystick_states();
void set_enable(bool enable);
private:
std::map<uint32_t, joystick_state> m_last_joystick_states;
// hack: need a completed dummy class when linking automoc generated with sdl-less build
std::vector<void*> m_joystick_handles;
uint64_t m_last_joystick_states_update = 0;
bool m_sdl_initialized = false;
// ui elements
QLabel* m_state_text = nullptr;
QCheckBox* m_enabled = nullptr;
QCheckBox* m_reverse_effects = nullptr;
Mapping* m_steering = nullptr;
Mapping* m_throttle = nullptr;
Mapping* m_brake = nullptr;
Mapping* m_clutch = nullptr;
Mapping* m_shift_up = nullptr;
Mapping* m_shift_down = nullptr;
Mapping* m_up = nullptr;
Mapping* m_down = nullptr;
Mapping* m_left = nullptr;
Mapping* m_right = nullptr;
Mapping* m_triangle = nullptr;
Mapping* m_cross = nullptr;
Mapping* m_square = nullptr;
Mapping* m_circle = nullptr;
Mapping* m_l2 = nullptr;
Mapping* m_l3 = nullptr;
Mapping* m_r2 = nullptr;
Mapping* m_r3 = nullptr;
Mapping* m_plus = nullptr;
Mapping* m_minus = nullptr;
Mapping* m_dial_clockwise = nullptr;
Mapping* m_dial_anticlockwise = nullptr;
Mapping* m_select = nullptr;
Mapping* m_pause = nullptr;
Mapping* m_shifter_1 = nullptr;
Mapping* m_shifter_2 = nullptr;
Mapping* m_shifter_3 = nullptr;
Mapping* m_shifter_4 = nullptr;
Mapping* m_shifter_5 = nullptr;
Mapping* m_shifter_6 = nullptr;
Mapping* m_shifter_r = nullptr;
DeviceChoice* m_ffb_device = nullptr;
DeviceChoice* m_led_device = nullptr;
QScrollArea* m_mapping_scroll_area = nullptr;
void load_ui_state_from_config();
void save_ui_state_to_config();
};

View file

@ -38,6 +38,7 @@
#include "shortcut_dialog.h"
#include "system_cmd_dialog.h"
#include "emulated_pad_settings_dialog.h"
#include "emulated_logitech_g27_settings_dialog.h"
#include "basic_mouse_settings_dialog.h"
#include "vfs_tool_dialog.h"
#include "welcome_dialog.h"
@ -2902,6 +2903,16 @@ void main_window::CreateConnects()
dlg->show();
});
#ifndef HAVE_SDL3
ui->confLogitechG27Act->setVisible(false);
#else
connect(ui->confLogitechG27Act, &QAction::triggered, this, [this]
{
emulated_logitech_g27_settings_dialog* dlg = new emulated_logitech_g27_settings_dialog(this);
dlg->show();
});
#endif
connect(ui->actionBasic_Mouse, &QAction::triggered, this, [this]
{
basic_mouse_settings_dialog* dlg = new basic_mouse_settings_dialog(this);

View file

@ -257,6 +257,7 @@
<addaction name="confGunCon3Act"/>
<addaction name="confTopShotEliteAct"/>
<addaction name="confTopShotFearmasterAct"/>
<addaction name="confLogitechG27Act"/>
</widget>
<widget class="QMenu" name="menuMice">
<property name="title">
@ -1366,6 +1367,11 @@
<string>Top Shot Fearmaster</string>
</property>
</action>
<action name="confLogitechG27Act">
<property name="text">
<string>Logitech G27 Wheel</string>
</property>
</action>
<action name="actionBasic_Mouse">
<property name="text">
<string>Basic Mouse</string>