diff --git a/rpcs3/CMakeLists.txt b/rpcs3/CMakeLists.txt index 191993d9d2..29e20001b7 100644 --- a/rpcs3/CMakeLists.txt +++ b/rpcs3/CMakeLists.txt @@ -75,6 +75,7 @@ target_sources(rpcs3 Input/keyboard_pad_handler.cpp Input/mm_joystick_handler.cpp Input/pad_thread.cpp + Input/raw_mouse_handler.cpp Input/sdl_pad_handler.cpp Input/skateboard_pad_handler.cpp Input/xinput_pad_handler.cpp diff --git a/rpcs3/Emu/Cell/Modules/cellGem.cpp b/rpcs3/Emu/Cell/Modules/cellGem.cpp index 67df463d5e..be0e273488 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGem.cpp @@ -317,10 +317,16 @@ public: break; } case move_handler::mouse: + case move_handler::raw_mouse: { - connected_controllers = 1; + auto& handler = g_fxo->get(); - if (gem_num == 0) + // Make sure that the mouse handler is initialized + handler.Init(std::min(attribute.max_connect, CELL_GEM_MAX_NUM)); + + const MouseInfo& info = handler.GetInfo(); + connected_controllers = std::min({ info.now_connect, attribute.max_connect, CELL_GEM_MAX_NUM }); + if (gem_num < connected_controllers) { is_connected = true; } @@ -1417,6 +1423,7 @@ error_code cellGemGetImageState(u32 gem_num, vm::ptr gem_imag ds3_pos_to_gem_state(gem_num, gem.controllers[gem_num], gem_image_state); break; case move_handler::mouse: + case move_handler::raw_mouse: mouse_pos_to_gem_state(gem_num, gem.controllers[gem_num], gem_image_state); break; #ifdef HAVE_LIBEVDEV @@ -1471,6 +1478,7 @@ error_code cellGemGetInertialState(u32 gem_num, u32 state_flag, u64 timestamp, v ds3_input_to_pad(gem_num, inertial_state->pad.digitalbuttons, inertial_state->pad.analog_T); break; case move_handler::mouse: + case move_handler::raw_mouse: mouse_input_to_pad(gem_num, inertial_state->pad.digitalbuttons, inertial_state->pad.analog_T); break; #ifdef HAVE_LIBEVDEV @@ -1675,6 +1683,7 @@ error_code cellGemGetState(u32 gem_num, u32 flag, u64 time_parameter, vm::ptrpad.digitalbuttons, gem_state->pad.analog_T); mouse_pos_to_gem_state(gem_num, gem.controllers[gem_num], gem_state); break; diff --git a/rpcs3/Emu/Io/MouseHandler.h b/rpcs3/Emu/Io/MouseHandler.h index ad61ad4ef3..1ff3097418 100644 --- a/rpcs3/Emu/Io/MouseHandler.h +++ b/rpcs3/Emu/Io/MouseHandler.h @@ -5,6 +5,7 @@ #include #include "Utilities/mutex.h" #include "util/init_mutex.hpp" +#include "Emu/system_config_types.h" // TODO: HLE info (constants, structs, etc.) should not be available here @@ -131,23 +132,25 @@ public: SAVESTATE_INIT_POS(18); + MouseHandlerBase(){}; MouseHandlerBase(const MouseHandlerBase&) = delete; MouseHandlerBase(utils::serial* ar); MouseHandlerBase(utils::serial& ar) : MouseHandlerBase(&ar) {} void save(utils::serial& ar); - void Button(u8 button, bool pressed) + void Button(u32 index, u8 button, bool pressed) { std::lock_guard lock(mutex); - for (u32 p = 0; p < m_mice.size(); ++p) + if (index < m_mice.size()) { - if (m_info.status[p] != CELL_MOUSE_STATUS_CONNECTED) + if (m_info.status[index] != CELL_MOUSE_STATUS_CONNECTED) { - continue; + return; } - MouseDataList& datalist = GetDataList(p); + Mouse& mouse = m_mice[index]; + MouseDataList& datalist = GetDataList(index); if (datalist.size() > MOUSE_MAX_DATA_LIST_NUM) { @@ -155,30 +158,31 @@ public: } if (pressed) - m_mice[p].buttons |= button; + mouse.buttons |= button; else - m_mice[p].buttons &= ~button; + mouse.buttons &= ~button; MouseData new_data; new_data.update = CELL_MOUSE_DATA_UPDATE; - new_data.buttons = m_mice[p].buttons; + new_data.buttons = mouse.buttons; datalist.push_back(new_data); } } - void Scroll(const s8 rotation) + void Scroll(u32 index, s32 rotation) { std::lock_guard lock(mutex); - for (u32 p = 0; p < m_mice.size(); ++p) + if (index < m_mice.size()) { - if (m_info.status[p] != CELL_MOUSE_STATUS_CONNECTED) + if (m_info.status[index] != CELL_MOUSE_STATUS_CONNECTED) { - continue; + return; } - MouseDataList& datalist = GetDataList(p); + Mouse& mouse = m_mice[index]; + MouseDataList& datalist = GetDataList(index); if (datalist.size() > MOUSE_MAX_DATA_LIST_NUM) { @@ -187,25 +191,26 @@ public: MouseData new_data; new_data.update = CELL_MOUSE_DATA_UPDATE; - new_data.wheel = rotation / 120; //120=event.GetWheelDelta() - new_data.buttons = m_mice[p].buttons; + new_data.wheel = std::clamp(rotation / 120, -128, 127); //120=event.GetWheelDelta() + new_data.buttons = mouse.buttons; datalist.push_back(new_data); } } - void Move(s32 x_pos_new, s32 y_pos_new, s32 x_max, s32 y_max, const bool is_qt_fullscreen = false, s32 x_delta = 0, s32 y_delta = 0) + void Move(u32 index, s32 x_pos_new, s32 y_pos_new, s32 x_max, s32 y_max, const bool is_relative = false, s32 x_delta = 0, s32 y_delta = 0) { std::lock_guard lock(mutex); - for (u32 p = 0; p < m_mice.size(); ++p) + if (index < m_mice.size()) { - if (m_info.status[p] != CELL_MOUSE_STATUS_CONNECTED) + if (m_info.status[index] != CELL_MOUSE_STATUS_CONNECTED) { - continue; + return; } - MouseDataList& datalist = GetDataList(p); + Mouse& mouse = m_mice[index]; + MouseDataList& datalist = GetDataList(index); if (datalist.size() > MOUSE_MAX_DATA_LIST_NUM) { @@ -214,21 +219,22 @@ public: MouseData new_data; new_data.update = CELL_MOUSE_DATA_UPDATE; - new_data.buttons = m_mice[p].buttons; + new_data.buttons = mouse.buttons; - if (!is_qt_fullscreen) + if (!is_relative) { - x_delta = x_pos_new - m_mice[p].x_pos; - y_delta = y_pos_new - m_mice[p].y_pos; + // The PS3 expects relative mouse movement, so we have to calculate it with the last absolute position. + x_delta = x_pos_new - mouse.x_pos; + y_delta = y_pos_new - mouse.y_pos; } new_data.x_axis = static_cast(std::clamp(x_delta, -127, 128)); new_data.y_axis = static_cast(std::clamp(y_delta, -127, 128)); - m_mice[p].x_max = x_max; - m_mice[p].y_max = y_max; - m_mice[p].x_pos = x_pos_new; - m_mice[p].y_pos = y_pos_new; + mouse.x_max = x_max; + mouse.y_max = y_max; + mouse.x_pos = x_pos_new; + mouse.y_pos = y_pos_new; /*CellMouseRawData& rawdata = GetRawData(p); rawdata.data[rawdata.len % CELL_MOUSE_MAX_CODES] = 0; // (TODO) @@ -260,4 +266,6 @@ public: MouseRawData& GetRawData(const u32 mouse) { return m_mice[mouse].m_rawdata; } stx::init_mutex init; + + mouse_handler type = mouse_handler::null; }; diff --git a/rpcs3/Emu/system_config_types.cpp b/rpcs3/Emu/system_config_types.cpp index bb19a985d5..4194fe7c06 100644 --- a/rpcs3/Emu/system_config_types.cpp +++ b/rpcs3/Emu/system_config_types.cpp @@ -10,6 +10,7 @@ void fmt_class_string::format(std::string& out, u64 arg) { case mouse_handler::null: return "Null"; case mouse_handler::basic: return "Basic"; + case mouse_handler::raw: return "Raw"; } return unknown; @@ -412,6 +413,7 @@ void fmt_class_string::format(std::string& out, u64 arg) case move_handler::null: return "Null"; case move_handler::fake: return "Fake"; case move_handler::mouse: return "Mouse"; + case move_handler::raw_mouse: return "Raw Mouse"; #ifdef HAVE_LIBEVDEV case move_handler::gun: return "Gun"; #endif diff --git a/rpcs3/Emu/system_config_types.h b/rpcs3/Emu/system_config_types.h index c55e6d880e..e57910bc30 100644 --- a/rpcs3/Emu/system_config_types.h +++ b/rpcs3/Emu/system_config_types.h @@ -38,6 +38,7 @@ enum class mouse_handler { null, basic, + raw }; enum class video_renderer @@ -127,6 +128,7 @@ enum class move_handler null, fake, mouse, + raw_mouse, #ifdef HAVE_LIBEVDEV gun #endif diff --git a/rpcs3/Input/basic_mouse_handler.cpp b/rpcs3/Input/basic_mouse_handler.cpp index 9e06552244..cfe287d037 100644 --- a/rpcs3/Input/basic_mouse_handler.cpp +++ b/rpcs3/Input/basic_mouse_handler.cpp @@ -33,12 +33,14 @@ void basic_mouse_handler::Init(const u32 max_connect) m_info.status[0] = CELL_MOUSE_STATUS_CONNECTED; // (TODO: Support for more mice) m_info.vendor_id[0] = 0x1234; m_info.product_id[0] = 0x1234; + + type = mouse_handler::basic; } /* Sets the target window for the event handler, and also installs an event filter on the target. */ void basic_mouse_handler::SetTargetWindow(QWindow* target) { - if (target != nullptr) + if (target) { m_target = target; target->installEventFilter(this); @@ -86,33 +88,33 @@ bool basic_mouse_handler::eventFilter(QObject* target, QEvent* ev) void basic_mouse_handler::MouseButtonDown(QMouseEvent* event) { - if (event->button() == Qt::LeftButton) MouseHandlerBase::Button(CELL_MOUSE_BUTTON_1, true); - else if (event->button() == Qt::RightButton) MouseHandlerBase::Button(CELL_MOUSE_BUTTON_2, true); - else if (event->button() == Qt::MiddleButton) MouseHandlerBase::Button(CELL_MOUSE_BUTTON_3, true); + if (event->button() == Qt::LeftButton) MouseHandlerBase::Button(0, CELL_MOUSE_BUTTON_1, true); + else if (event->button() == Qt::RightButton) MouseHandlerBase::Button(0, CELL_MOUSE_BUTTON_2, true); + else if (event->button() == Qt::MiddleButton) MouseHandlerBase::Button(0, CELL_MOUSE_BUTTON_3, true); // TODO: verify these - else if (event->button() == Qt::ExtraButton1) MouseHandlerBase::Button(CELL_MOUSE_BUTTON_4, true); - else if (event->button() == Qt::ExtraButton2) MouseHandlerBase::Button(CELL_MOUSE_BUTTON_5, true); - else if (event->button() == Qt::ExtraButton3) MouseHandlerBase::Button(CELL_MOUSE_BUTTON_6, true); - else if (event->button() == Qt::ExtraButton4) MouseHandlerBase::Button(CELL_MOUSE_BUTTON_7, true); - else if (event->button() == Qt::ExtraButton5) MouseHandlerBase::Button(CELL_MOUSE_BUTTON_8, true); + else if (event->button() == Qt::ExtraButton1) MouseHandlerBase::Button(0, CELL_MOUSE_BUTTON_4, true); + else if (event->button() == Qt::ExtraButton2) MouseHandlerBase::Button(0, CELL_MOUSE_BUTTON_5, true); + else if (event->button() == Qt::ExtraButton3) MouseHandlerBase::Button(0, CELL_MOUSE_BUTTON_6, true); + else if (event->button() == Qt::ExtraButton4) MouseHandlerBase::Button(0, CELL_MOUSE_BUTTON_7, true); + else if (event->button() == Qt::ExtraButton5) MouseHandlerBase::Button(0, CELL_MOUSE_BUTTON_8, true); } void basic_mouse_handler::MouseButtonUp(QMouseEvent* event) { - if (event->button() == Qt::LeftButton) MouseHandlerBase::Button(CELL_MOUSE_BUTTON_1, false); - else if (event->button() == Qt::RightButton) MouseHandlerBase::Button(CELL_MOUSE_BUTTON_2, false); - else if (event->button() == Qt::MiddleButton) MouseHandlerBase::Button(CELL_MOUSE_BUTTON_3, false); + if (event->button() == Qt::LeftButton) MouseHandlerBase::Button(0, CELL_MOUSE_BUTTON_1, false); + else if (event->button() == Qt::RightButton) MouseHandlerBase::Button(0, CELL_MOUSE_BUTTON_2, false); + else if (event->button() == Qt::MiddleButton) MouseHandlerBase::Button(0, CELL_MOUSE_BUTTON_3, false); // TODO: verify these - else if (event->button() == Qt::ExtraButton1) MouseHandlerBase::Button(CELL_MOUSE_BUTTON_4, false); - else if (event->button() == Qt::ExtraButton2) MouseHandlerBase::Button(CELL_MOUSE_BUTTON_5, false); - else if (event->button() == Qt::ExtraButton3) MouseHandlerBase::Button(CELL_MOUSE_BUTTON_6, false); - else if (event->button() == Qt::ExtraButton4) MouseHandlerBase::Button(CELL_MOUSE_BUTTON_7, false); - else if (event->button() == Qt::ExtraButton5) MouseHandlerBase::Button(CELL_MOUSE_BUTTON_8, false); + else if (event->button() == Qt::ExtraButton1) MouseHandlerBase::Button(0, CELL_MOUSE_BUTTON_4, false); + else if (event->button() == Qt::ExtraButton2) MouseHandlerBase::Button(0, CELL_MOUSE_BUTTON_5, false); + else if (event->button() == Qt::ExtraButton3) MouseHandlerBase::Button(0, CELL_MOUSE_BUTTON_6, false); + else if (event->button() == Qt::ExtraButton4) MouseHandlerBase::Button(0, CELL_MOUSE_BUTTON_7, false); + else if (event->button() == Qt::ExtraButton5) MouseHandlerBase::Button(0, CELL_MOUSE_BUTTON_8, false); } void basic_mouse_handler::MouseScroll(QWheelEvent* event) { - MouseHandlerBase::Scroll(event->angleDelta().y()); + MouseHandlerBase::Scroll(0, event->angleDelta().y()); } bool basic_mouse_handler::get_mouse_lock_state() const @@ -152,11 +154,12 @@ void basic_mouse_handler::MouseMove(QMouseEvent* event) p_real.setY(std::clamp(p_real.y() + p_delta.y(), 0, screen.height())); // pass the 'real' position and the current delta to the screen center - MouseHandlerBase::Move(p_real.x(), p_real.y(), screen.width(), screen.height(), true, p_delta.x(), p_delta.y()); + MouseHandlerBase::Move(0, p_real.x(), p_real.y(), screen.width(), screen.height(), true, p_delta.x(), p_delta.y()); } else { - MouseHandlerBase::Move(e_pos.x(), e_pos.y(), screen.width(), screen.height()); + // pass the absolute position + MouseHandlerBase::Move(0, e_pos.x(), e_pos.y(), screen.width(), screen.height()); } } } diff --git a/rpcs3/Input/raw_mouse_config.h b/rpcs3/Input/raw_mouse_config.h new file mode 100644 index 0000000000..1bb872fa6c --- /dev/null +++ b/rpcs3/Input/raw_mouse_config.h @@ -0,0 +1,79 @@ +#pragma once + +#include "Utilities/Config.h" +#include "Utilities/mutex.h" + +#include + +LOG_CHANNEL(cfg_log, "CFG"); + +struct raw_mouse_config : cfg::node +{ +public: + using cfg::node::node; + + cfg::_float<10, 1000> mouse_acceleration{ this, "Mouse Acceleration", 100.0f, true }; +}; + +struct raw_mice_config : cfg::node +{ + raw_mice_config() + { + for (u32 i = 0; i < ::size32(players); i++) + { + players.at(i) = std::make_shared(this, fmt::format("Player %d", i + 1)); + } + } + + shared_mutex m_mutex; + static constexpr std::string_view cfg_id = "raw_mouse"; + std::array, 4> players; + + bool load() + { + m_mutex.lock(); + + bool result = false; + const std::string cfg_name = fmt::format("%sconfig/%s.yml", fs::get_config_dir(), cfg_id); + cfg_log.notice("Loading %s config: %s", cfg_id, cfg_name); + + from_default(); + + if (fs::file cfg_file{ cfg_name, fs::read }) + { + if (std::string content = cfg_file.to_string(); !content.empty()) + { + result = from_string(content); + } + + m_mutex.unlock(); + } + else + { + m_mutex.unlock(); + save(); + } + + return result; + } + + void save() + { + std::lock_guard lock(m_mutex); + + const std::string cfg_name = fmt::format("%sconfig/%s.yml", fs::get_config_dir(), cfg_id); + cfg_log.notice("Saving %s config to '%s'", cfg_id, cfg_name); + + if (!fs::create_path(fs::get_parent_dir(cfg_name))) + { + cfg_log.fatal("Failed to create path: %s (%s)", cfg_name, fs::g_tls_error); + } + + if (!cfg::node::save(cfg_name)) + { + cfg_log.error("Failed to save %s config to '%s' (error=%s)", cfg_id, cfg_name, fs::g_tls_error); + } + } +}; + +extern raw_mice_config g_cfg_raw_mouse; diff --git a/rpcs3/Input/raw_mouse_handler.cpp b/rpcs3/Input/raw_mouse_handler.cpp new file mode 100644 index 0000000000..0af58755ae --- /dev/null +++ b/rpcs3/Input/raw_mouse_handler.cpp @@ -0,0 +1,366 @@ +#include "stdafx.h" +#include "raw_mouse_handler.h" + +#include "Emu/RSX/GSRender.h" +#include "Emu/RSX/RSXThread.h" +#include "Emu/Io/interception.h" +#include "Input/raw_mouse_config.h" + +#ifdef _WIN32 +#include +#endif + +#define RAW_MOUSE_DEBUG_CURSOR_ENABLED 0 +#if RAW_MOUSE_DEBUG_CURSOR_ENABLED +#include "Emu/RSX/Overlays/overlay_cursor.h" +static inline void draw_overlay_cursor(u32 index, s32 x_pos, s32 y_pos, s32 x_max, s32 y_max) +{ + const u16 x = static_cast(x_pos / (x_max / static_cast(rsx::overlays::overlay::virtual_width))); + const u16 y = static_cast(y_pos / (y_max / static_cast(rsx::overlays::overlay::virtual_height))); + + const color4f color = { 1.0f, 0.0f, 0.0f, 1.0f }; + + rsx::overlays::set_cursor(rsx::overlays::cursor_offset::last + index, x, y, color, 2'000'000, false); +} +#else +static inline void draw_overlay_cursor(u32, s32, s32, s32, s32) {} +#endif + +LOG_CHANNEL(input_log, "Input"); + +raw_mice_config g_cfg_raw_mouse; + +raw_mouse::raw_mouse(u32 index, const std::string& device_name, void* handle, raw_mouse_handler* handler) + : m_index(index), m_device_name(device_name), m_handle(handle), m_handler(handler) +{ + if (m_index < ::size32(g_cfg_raw_mouse.players)) + { + if (const auto& player = ::at32(g_cfg_raw_mouse.players, m_index)) + { + m_mouse_acceleration = static_cast(player->mouse_acceleration.get()) / 100.0f; + } + } +} + +raw_mouse::~raw_mouse() +{ +} + +void raw_mouse::update_window_handle() +{ +#ifdef _WIN32 + if (GSRender* render = static_cast(g_fxo->try_get())) + { + if (GSFrameBase* frame = render->get_frame()) + { + if (display_handle_t window_handle = frame->handle(); window_handle && window_handle == GetActiveWindow()) + { + m_window_handle = window_handle; + m_window_width = frame->client_width(); + m_window_height = frame->client_height(); + return; + } + } + } +#endif + + m_window_handle = {}; + m_window_width = 0; + m_window_height = 0; +} + +#ifdef _WIN32 +void raw_mouse::update_values(const RAWMOUSE& state) +{ + ensure(m_handler != nullptr); + + // Update window handle and size + update_window_handle(); + + // Check if the window handle is active + if (!m_window_handle) + { + return; + } + + const auto get_button_pressed = [this](u8 button, int button_flags, int down, int up) + { + // Only update the value if either down or up flags are present + if ((button_flags & down)) + { + m_handler->Button(m_index, button, true); + } + else if ((button_flags & up)) + { + m_handler->Button(m_index, button, false); + } + }; + + // Get mouse buttons + get_button_pressed(CELL_MOUSE_BUTTON_1, state.usButtonFlags, RI_MOUSE_BUTTON_1_DOWN, RI_MOUSE_BUTTON_1_UP); + get_button_pressed(CELL_MOUSE_BUTTON_2, state.usButtonFlags, RI_MOUSE_BUTTON_2_DOWN, RI_MOUSE_BUTTON_2_UP); + get_button_pressed(CELL_MOUSE_BUTTON_3, state.usButtonFlags, RI_MOUSE_BUTTON_3_DOWN, RI_MOUSE_BUTTON_3_UP); + get_button_pressed(CELL_MOUSE_BUTTON_4, state.usButtonFlags, RI_MOUSE_BUTTON_4_DOWN, RI_MOUSE_BUTTON_4_UP); + get_button_pressed(CELL_MOUSE_BUTTON_5, state.usButtonFlags, RI_MOUSE_BUTTON_5_DOWN, RI_MOUSE_BUTTON_5_UP); + + // Get vertical mouse wheel + if ((state.usButtonFlags & RI_MOUSE_WHEEL)) + { + m_handler->Scroll(m_index, static_cast(state.usButtonData)); + } + + // Get horizontal mouse wheel. Ignored until needed. + //if ((state.usButtonFlags & RI_MOUSE_HWHEEL)) + //{ + // m_handler->Scroll(m_index, static_cast(state.usButtonData)); + //} + + // Get mouse movement + if ((state.usFlags & MOUSE_MOVE_ABSOLUTE)) + { + // Get absolute mouse movement + + if (m_window_handle && m_window_width && m_window_height) + { + // Convert virtual coordinates to screen coordinates + const bool is_virtual_desktop = (state.usFlags & MOUSE_VIRTUAL_DESKTOP) == MOUSE_VIRTUAL_DESKTOP; + const int width = GetSystemMetrics(is_virtual_desktop ? SM_CXVIRTUALSCREEN : SM_CXSCREEN); + const int height = GetSystemMetrics(is_virtual_desktop ? SM_CYVIRTUALSCREEN : SM_CYSCREEN); + + if (width && height) + { + POINT pt { + .x = long(state.lLastX / 65535.0f * width), + .y = long(state.lLastY / 65535.0f * height) + }; + + if (ScreenToClient(m_window_handle, &pt)) + { + // Move mouse with absolute position + m_handler->Move(m_index, pt.x, pt.y, m_window_width, m_window_height); + draw_overlay_cursor(m_index, pt.x, pt.y, m_window_width, m_window_height); + } + } + } + } + else if (state.lLastX || state.lLastY) + { + // Get relative mouse movement (units most likely in raw dpi) + + if (m_window_handle && m_window_width && m_window_height) + { + // Apply mouse acceleration + const int delta_x = static_cast(state.lLastX * m_mouse_acceleration); + const int delta_y = static_cast(state.lLastY * m_mouse_acceleration); + + // Calculate new position + m_pos_x = std::max(0, std::min(m_pos_x + delta_x, m_window_width - 1)); + m_pos_y = std::max(0, std::min(m_pos_y + delta_y, m_window_height - 1)); + + // Move mouse relative to old position + m_handler->Move(m_index, m_pos_x, m_pos_y, m_window_width, m_window_height, true, delta_x, delta_y); + draw_overlay_cursor(m_index, m_pos_x, m_pos_y, m_window_width, m_window_height); + } + } +} +#endif + +raw_mouse_handler::~raw_mouse_handler() +{ + if (m_raw_mice.empty()) + { + return; + } + +#ifdef _WIN32 + std::vector raw_input_devices; + raw_input_devices.push_back(RAWINPUTDEVICE { + .usUsagePage = HID_USAGE_PAGE_GENERIC, + .usUsage = HID_USAGE_GENERIC_MOUSE, + .dwFlags = RIDEV_REMOVE, + .hwndTarget = nullptr + }); + if (!RegisterRawInputDevices(raw_input_devices.data(), ::size32(raw_input_devices), sizeof(RAWINPUTDEVICE))) + { + input_log.error("raw_mouse_handler: RegisterRawInputDevices (destructor) failed: %s", fmt::win_error{GetLastError(), nullptr}); + } +#endif +} + +void raw_mouse_handler::Init(const u32 max_connect) +{ + if (m_info.max_connect > 0) + { + // Already initialized + return; + } + + if (!g_cfg_raw_mouse.load()) + { + input_log.notice("raw_mouse_handler: Could not load raw mouse config. Using defaults."); + } + + enumerate_devices(max_connect); + + m_mice.clear(); + + for (u32 i = 0; i < std::min(::size32(m_raw_mice), max_connect); i++) + { + m_mice.emplace_back(Mouse()); + } + + m_info = {}; + m_info.max_connect = max_connect; + m_info.now_connect = std::min(::size32(m_mice), max_connect); + m_info.info = input::g_mice_intercepted ? CELL_MOUSE_INFO_INTERCEPTED : 0; // Ownership of mouse data: 0=Application, 1=System + + for (u32 i = 0; i < m_info.now_connect; i++) + { + m_info.status[i] = CELL_MOUSE_STATUS_CONNECTED; + m_info.mode[i] = CELL_MOUSE_INFO_TABLET_MOUSE_MODE; + m_info.tablet_is_supported[i] = CELL_MOUSE_INFO_TABLET_NOT_SUPPORTED; + m_info.vendor_id[0] = 0x1234; + m_info.product_id[0] = 0x1234; + } + +#ifdef _WIN32 + if (max_connect && !m_raw_mice.empty()) + { + // Get the window handle of the first mouse + raw_mouse& mouse = m_raw_mice.begin()->second; + mouse.update_window_handle(); + + std::vector raw_input_devices; + raw_input_devices.push_back(RAWINPUTDEVICE { + .usUsagePage = HID_USAGE_PAGE_GENERIC, + .usUsage = HID_USAGE_GENERIC_MOUSE, + .dwFlags = 0, + .hwndTarget = mouse.window_handle() + }); + if (!RegisterRawInputDevices(raw_input_devices.data(), ::size32(raw_input_devices), sizeof(RAWINPUTDEVICE))) + { + input_log.error("raw_mouse_handler: RegisterRawInputDevices failed: %s", fmt::win_error{GetLastError(), nullptr}); + } + } +#endif + + type = mouse_handler::raw; +} + +void raw_mouse_handler::enumerate_devices(u32 max_connect) +{ + input_log.notice("raw_mouse_handler: enumerating devices (max_connect=%d)", max_connect); + + m_raw_mice.clear(); + + if (max_connect == 0) + { + return; + } + +#ifdef _WIN32 + u32 num_devices{}; + u32 res = GetRawInputDeviceList(nullptr, &num_devices, sizeof(RAWINPUTDEVICELIST)); + if (res == umax) + { + input_log.error("raw_mouse_handler: GetRawInputDeviceList (count) failed: %s", fmt::win_error{GetLastError(), nullptr}); + return; + } + + if (num_devices == 0) + { + return; + } + + std::vector device_list(num_devices); + + res = GetRawInputDeviceList(device_list.data(), &num_devices, sizeof(RAWINPUTDEVICELIST)); + if (res == umax) + { + input_log.error("raw_mouse_handler: GetRawInputDeviceList (fetch) failed: %s", fmt::win_error{GetLastError(), nullptr}); + return; + } + + for (RAWINPUTDEVICELIST& device : device_list) + { + if (m_raw_mice.size() >= max_connect) + { + return; + } + + if (device.dwType != RIM_TYPEMOUSE) + { + continue; + } + + u32 size = 0; + res = GetRawInputDeviceInfoW(device.hDevice, RIDI_DEVICENAME, nullptr, &size); + if (res == umax) + { + input_log.error("raw_mouse_handler: GetRawInputDeviceInfoA (RIDI_DEVICENAME count) failed: %s", fmt::win_error{GetLastError(), nullptr}); + continue; + } + + if (size == 0) + { + continue; + } + + std::vector buf(size); + res = GetRawInputDeviceInfoW(device.hDevice, RIDI_DEVICENAME, buf.data(), &size); + if (res == umax) + { + input_log.error("raw_mouse_handler: GetRawInputDeviceInfoA (RIDI_DEVICENAME fetch) failed: %s", fmt::win_error{GetLastError(), nullptr}); + continue; + } + + const std::string device_name = wchar_to_utf8(buf.data()); + + input_log.notice("raw_mouse_handler: adding device %d: '%s'", m_raw_mice.size(), device_name); + + m_raw_mice[device.hDevice] = raw_mouse(::size32(m_raw_mice), device_name, device.hDevice, this); + } +#endif + + input_log.notice("raw_mouse_handler: found %d devices", m_raw_mice.size()); +} + +#ifdef _WIN32 +void raw_mouse_handler::handle_native_event(const MSG& msg) +{ + if (msg.message != WM_INPUT) + { + return; + } + + if (GET_RAWINPUT_CODE_WPARAM(msg.wParam) != RIM_INPUT) + { + return; + } + + RAWINPUT raw_input{}; + UINT size = sizeof(RAWINPUT); + + u32 res = GetRawInputData(reinterpret_cast(msg.lParam), RID_INPUT, &raw_input, &size, sizeof(RAWINPUTHEADER)); + if (res == umax) + { + return; + } + + switch (raw_input.header.dwType) + { + case RIM_TYPEMOUSE: + { + if (auto it = m_raw_mice.find(raw_input.header.hDevice); it != m_raw_mice.end()) + { + it->second.update_values(raw_input.data.mouse); + } + break; + } + default: + { + break; + } + } +} +#endif diff --git a/rpcs3/Input/raw_mouse_handler.h b/rpcs3/Input/raw_mouse_handler.h new file mode 100644 index 0000000000..7b39975a40 --- /dev/null +++ b/rpcs3/Input/raw_mouse_handler.h @@ -0,0 +1,56 @@ +#pragma once + +#include "Emu/Io/MouseHandler.h" +#include "Emu/RSX/display.h" + +#ifdef _WIN32 +#include +#endif + +class raw_mouse_handler; + +class raw_mouse +{ +public: + raw_mouse() {} + raw_mouse(u32 index, const std::string& device_name, void* handle, raw_mouse_handler* handler); + virtual ~raw_mouse(); + + void update_window_handle(); + display_handle_t window_handle() const { return m_window_handle; } + +#ifdef _WIN32 + void update_values(const RAWMOUSE& state); +#endif + +private: + u32 m_index = 0; + std::string m_device_name; + void* m_handle{}; + display_handle_t m_window_handle{}; + int m_window_width{}; + int m_window_height{}; + int m_pos_x{}; + int m_pos_y{}; + float m_mouse_acceleration = 1.0f; + raw_mouse_handler* m_handler{}; +}; + +class raw_mouse_handler final : public MouseHandlerBase +{ + using MouseHandlerBase::MouseHandlerBase; + +public: + virtual ~raw_mouse_handler(); + + void Init(const u32 max_connect) override; + +#ifdef _WIN32 + void handle_native_event(const MSG& msg); +#endif + +private: + void enumerate_devices(u32 max_connect); + + std::map m_raw_mice; +}; diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 49d0628e35..9c80f18646 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -631,6 +631,7 @@ + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 4246aa1c15..602fb4f143 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -2434,6 +2434,9 @@ Emu\Io + + Emu\Io + diff --git a/rpcs3/main_application.cpp b/rpcs3/main_application.cpp index 130f3fdd32..8d012d2df5 100644 --- a/rpcs3/main_application.cpp +++ b/rpcs3/main_application.cpp @@ -19,6 +19,7 @@ #include "Emu/Io/MouseHandler.h" #include "Input/basic_keyboard_handler.h" #include "Input/basic_mouse_handler.h" +#include "Input/raw_mouse_handler.h" #include "Emu/Audio/AudioBackend.h" #include "Emu/Audio/Null/NullAudioBackend.h" @@ -141,15 +142,25 @@ EmuCallbacks main_application::CreateCallbacks() { case mouse_handler::null: { - if (g_cfg.io.move == move_handler::mouse) + switch (g_cfg.io.move) + { + case move_handler::mouse: { basic_mouse_handler* ret = g_fxo->init(Emu.DeserialManager()); ret->moveToThread(get_thread()); ret->SetTargetWindow(m_game_window); + break; } - else + case move_handler::raw_mouse: + { + g_fxo->init(Emu.DeserialManager()); + break; + } + default: { g_fxo->init(Emu.DeserialManager()); + break; + } } break; @@ -161,6 +172,11 @@ EmuCallbacks main_application::CreateCallbacks() ret->SetTargetWindow(m_game_window); break; } + case mouse_handler::raw: + { + g_fxo->init(Emu.DeserialManager()); + break; + } } }; diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index 66c8f75014..343ae6a728 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -180,6 +180,7 @@ + @@ -924,6 +925,7 @@ $(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath) $(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath) + diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index 0b0b562e50..d722c79256 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -169,6 +169,9 @@ {bd1fd182-dc0d-45cb-bb9e-f68b2282d7eb} + + {f44f5837-8651-456a-8584-5a2005ac613e} + @@ -1050,6 +1053,9 @@ Io\Skateboard + + Io\raw + @@ -1232,6 +1238,9 @@ Io\Skateboard + + Io\raw + diff --git a/rpcs3/rpcs3qt/emu_settings.cpp b/rpcs3/rpcs3qt/emu_settings.cpp index 054fd0778b..3da1f7ba9d 100644 --- a/rpcs3/rpcs3qt/emu_settings.cpp +++ b/rpcs3/rpcs3qt/emu_settings.cpp @@ -1009,6 +1009,7 @@ QString emu_settings::GetLocalizedSetting(const QString& original, emu_settings_ { case mouse_handler::null: return tr("Null", "Mouse handler"); case mouse_handler::basic: return tr("Basic", "Mouse handler"); + case mouse_handler::raw: return tr("Raw", "Mouse handler"); } break; case emu_settings_type::CameraType: @@ -1057,6 +1058,7 @@ QString emu_settings::GetLocalizedSetting(const QString& original, emu_settings_ case move_handler::null: return tr("Null", "Move handler"); case move_handler::fake: return tr("Fake", "Move handler"); case move_handler::mouse: return tr("Mouse", "Move handler"); + case move_handler::raw_mouse: return tr("Raw Mouse", "Move handler"); #ifdef HAVE_LIBEVDEV case move_handler::gun: return tr("Gun", "Gun handler"); #endif diff --git a/rpcs3/rpcs3qt/gui_application.cpp b/rpcs3/rpcs3qt/gui_application.cpp index edf0fd7fc2..52585ed92d 100644 --- a/rpcs3/rpcs3qt/gui_application.cpp +++ b/rpcs3/rpcs3qt/gui_application.cpp @@ -20,6 +20,7 @@ #include "Emu/Io/Null/null_camera_handler.h" #include "Emu/Io/Null/null_music_handler.h" #include "Emu/vfs_config.h" +#include "Input/raw_mouse_handler.h" #include "trophy_notification_helper.h" #include "save_data_dialog.h" #include "msg_dialog_frame.h" @@ -174,6 +175,11 @@ bool gui_application::Init() } #endif + // Install native event filter +#ifdef _WIN32 // Currently only needed for raw mouse input on windows + installNativeEventFilter(&m_native_event_filter); +#endif + return true; } @@ -974,3 +980,26 @@ void gui_application::OnAppStateChanged(Qt::ApplicationState state) // Delay pause so it won't immediately pause the emulated application QTimer::singleShot(1000, this, pause_callback); } + +bool gui_application::native_event_filter::nativeEventFilter([[maybe_unused]] const QByteArray& eventType, [[maybe_unused]] void* message, [[maybe_unused]] qintptr* result) +{ +#ifdef _WIN32 + if (!Emu.IsRunning()) + { + return false; + } + + if (eventType == "windows_generic_MSG") + { + if (MSG* msg = static_cast(message); msg && msg->message == WM_INPUT) + { + if (auto* handler = g_fxo->try_get(); handler && handler->type == mouse_handler::raw) + { + static_cast(handler)->handle_native_event(*msg); + } + } + } +#endif + + return false; +} diff --git a/rpcs3/rpcs3qt/gui_application.h b/rpcs3/rpcs3qt/gui_application.h index 79cb1b352b..3ced20951b 100644 --- a/rpcs3/rpcs3qt/gui_application.h +++ b/rpcs3/rpcs3qt/gui_application.h @@ -4,6 +4,7 @@ #include "util/atomic.hpp" #include +#include #include #include #include @@ -81,6 +82,13 @@ private: void UpdatePlaytime(); void StopPlaytime(); + class native_event_filter : public QAbstractNativeEventFilter + { + public: + bool nativeEventFilter(const QByteArray& eventType, void* message, qintptr* result) override; + + } m_native_event_filter; + QTranslator m_translator; QTranslator m_translator_qt; QString m_language_code; diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index d5ed31939d..d082eb1c80 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -1234,6 +1234,12 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std ui->loadSdlMappings->setVisible(false); #endif +#ifndef _WIN32 + // Remove raw mouse handler + remove_item(ui->mouseHandlerBox, static_cast(mouse_handler::raw), static_cast(g_cfg.io.mouse.def)); + remove_item(ui->moveBox, static_cast(move_handler::raw_mouse), static_cast(g_cfg.io.move.def)); +#endif + // Midi const QString midi_none = m_emu_settings->m_midi_creator.get_none(); const midi_device def_midi_device{ .type = midi_device_type::keyboard, .name = midi_none.toStdString() };