From 17207939e50b64592f93c623219b70d26272df4d Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Thu, 16 Feb 2023 13:38:50 -0600
Subject: [PATCH] input_common: Split mouse input into individual devices

---
 src/core/hid/emulated_console.cpp             |  3 +-
 src/core/hid/emulated_devices.cpp             |  3 +
 src/input_common/drivers/mouse.cpp            | 67 +++++++++++++------
 src/input_common/drivers/mouse.h              | 38 +++++++++--
 src/input_common/input_mapping.cpp            |  4 ++
 src/yuzu/bootmanager.cpp                      | 10 ++-
 .../configuration/configure_input_player.cpp  |  2 +-
 src/yuzu/configuration/configure_ringcon.cpp  |  2 +-
 src/yuzu/main.cpp                             |  8 +++
 src/yuzu_cmd/emu_window/emu_window_sdl2.cpp   |  8 ++-
 10 files changed, 114 insertions(+), 31 deletions(-)

diff --git a/src/core/hid/emulated_console.cpp b/src/core/hid/emulated_console.cpp
index 1c91bbe40c..17d6633798 100644
--- a/src/core/hid/emulated_console.cpp
+++ b/src/core/hid/emulated_console.cpp
@@ -23,7 +23,8 @@ void EmulatedConsole::SetTouchParams() {
 
     // We can't use mouse as touch if native mouse is enabled
     if (!Settings::values.mouse_enabled) {
-        touch_params[index++] = Common::ParamPackage{"engine:mouse,axis_x:10,axis_y:11,button:0"};
+        touch_params[index++] =
+            Common::ParamPackage{"engine:mouse,axis_x:0,axis_y:1,button:0,port:2"};
     }
 
     touch_params[index++] =
diff --git a/src/core/hid/emulated_devices.cpp b/src/core/hid/emulated_devices.cpp
index 836f32c0f8..578a6ff612 100644
--- a/src/core/hid/emulated_devices.cpp
+++ b/src/core/hid/emulated_devices.cpp
@@ -34,9 +34,12 @@ void EmulatedDevices::ReloadInput() {
     // First two axis are reserved for mouse position
     key_index = 2;
     for (auto& mouse_device : mouse_analog_devices) {
+        // Mouse axis are only mapped on port 1, pad 0
         Common::ParamPackage mouse_params;
         mouse_params.Set("engine", "mouse");
         mouse_params.Set("axis", static_cast<int>(key_index));
+        mouse_params.Set("port", 1);
+        mouse_params.Set("pad", 0);
         mouse_device = Common::Input::CreateInputDevice(mouse_params);
         key_index++;
     }
diff --git a/src/input_common/drivers/mouse.cpp b/src/input_common/drivers/mouse.cpp
index faf9cbdc33..da50e0a249 100644
--- a/src/input_common/drivers/mouse.cpp
+++ b/src/input_common/drivers/mouse.cpp
@@ -15,23 +15,39 @@ constexpr int mouse_axis_y = 1;
 constexpr int wheel_axis_x = 2;
 constexpr int wheel_axis_y = 3;
 constexpr int motion_wheel_y = 4;
-constexpr int touch_axis_x = 10;
-constexpr int touch_axis_y = 11;
 constexpr PadIdentifier identifier = {
     .guid = Common::UUID{},
     .port = 0,
     .pad = 0,
 };
 
+constexpr PadIdentifier real_mouse_identifier = {
+    .guid = Common::UUID{},
+    .port = 1,
+    .pad = 0,
+};
+
+constexpr PadIdentifier touch_identifier = {
+    .guid = Common::UUID{},
+    .port = 2,
+    .pad = 0,
+};
+
 Mouse::Mouse(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
     PreSetController(identifier);
+    PreSetController(real_mouse_identifier);
+    PreSetController(touch_identifier);
+
+    // Initialize all mouse axis
     PreSetAxis(identifier, mouse_axis_x);
     PreSetAxis(identifier, mouse_axis_y);
     PreSetAxis(identifier, wheel_axis_x);
     PreSetAxis(identifier, wheel_axis_y);
     PreSetAxis(identifier, motion_wheel_y);
-    PreSetAxis(identifier, touch_axis_x);
-    PreSetAxis(identifier, touch_axis_y);
+    PreSetAxis(real_mouse_identifier, mouse_axis_x);
+    PreSetAxis(real_mouse_identifier, mouse_axis_y);
+    PreSetAxis(touch_identifier, mouse_axis_x);
+    PreSetAxis(touch_identifier, mouse_axis_y);
     update_thread = std::jthread([this](std::stop_token stop_token) { UpdateThread(stop_token); });
 }
 
@@ -39,7 +55,7 @@ void Mouse::UpdateThread(std::stop_token stop_token) {
     Common::SetCurrentThreadName("Mouse");
     constexpr int update_time = 10;
     while (!stop_token.stop_requested()) {
-        if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) {
+        if (Settings::values.mouse_panning) {
             // Slow movement by 4%
             last_mouse_change *= 0.96f;
             const float sensitivity =
@@ -57,17 +73,7 @@ void Mouse::UpdateThread(std::stop_token stop_token) {
     }
 }
 
-void Mouse::MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int center_y) {
-    // If native mouse is enabled just set the screen coordinates
-    if (Settings::values.mouse_enabled) {
-        SetAxis(identifier, mouse_axis_x, touch_x);
-        SetAxis(identifier, mouse_axis_y, touch_y);
-        return;
-    }
-
-    SetAxis(identifier, touch_axis_x, touch_x);
-    SetAxis(identifier, touch_axis_y, touch_y);
-
+void Mouse::Move(int x, int y, int center_x, int center_y) {
     if (Settings::values.mouse_panning) {
         auto mouse_change =
             (Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y)).Cast<float>();
@@ -113,20 +119,41 @@ void Mouse::MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int
     }
 }
 
-void Mouse::PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton button) {
-    SetAxis(identifier, touch_axis_x, touch_x);
-    SetAxis(identifier, touch_axis_y, touch_y);
+void Mouse::MouseMove(f32 touch_x, f32 touch_y) {
+    SetAxis(real_mouse_identifier, mouse_axis_x, touch_x);
+    SetAxis(real_mouse_identifier, mouse_axis_y, touch_y);
+}
+
+void Mouse::TouchMove(f32 touch_x, f32 touch_y) {
+    SetAxis(touch_identifier, mouse_axis_x, touch_x);
+    SetAxis(touch_identifier, mouse_axis_y, touch_y);
+}
+
+void Mouse::PressButton(int x, int y, MouseButton button) {
     SetButton(identifier, static_cast<int>(button), true);
+
     // Set initial analog parameters
     mouse_origin = {x, y};
     last_mouse_position = {x, y};
     button_pressed = true;
 }
 
+void Mouse::PressMouseButton(MouseButton button) {
+    SetButton(real_mouse_identifier, static_cast<int>(button), true);
+}
+
+void Mouse::PressTouchButton(f32 touch_x, f32 touch_y, MouseButton button) {
+    SetAxis(touch_identifier, mouse_axis_x, touch_x);
+    SetAxis(touch_identifier, mouse_axis_y, touch_y);
+    SetButton(touch_identifier, static_cast<int>(button), true);
+}
+
 void Mouse::ReleaseButton(MouseButton button) {
     SetButton(identifier, static_cast<int>(button), false);
+    SetButton(real_mouse_identifier, static_cast<int>(button), false);
+    SetButton(touch_identifier, static_cast<int>(button), false);
 
-    if (!Settings::values.mouse_panning && !Settings::values.mouse_enabled) {
+    if (!Settings::values.mouse_panning) {
         SetAxis(identifier, mouse_axis_x, 0);
         SetAxis(identifier, mouse_axis_y, 0);
     }
diff --git a/src/input_common/drivers/mouse.h b/src/input_common/drivers/mouse.h
index 72073cc232..f3b65bdd1d 100644
--- a/src/input_common/drivers/mouse.h
+++ b/src/input_common/drivers/mouse.h
@@ -37,13 +37,43 @@ public:
      * @param center_x the x-coordinate of the middle of the screen
      * @param center_y the y-coordinate of the middle of the screen
      */
-    void MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int center_y);
+    void Move(int x, int y, int center_x, int center_y);
 
     /**
-     * Sets the status of all buttons bound with the key to pressed
-     * @param key_code the code of the key to press
+     * Signals that real mouse has moved.
+     * @param x the absolute position on the touchscreen of the cursor
+     * @param y the absolute position on the touchscreen of the cursor
      */
-    void PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton button);
+    void MouseMove(f32 touch_x, f32 touch_y);
+
+    /**
+     * Signals that touch finger has moved.
+     * @param x the absolute position on the touchscreen of the cursor
+     * @param y the absolute position on the touchscreen of the cursor
+     */
+    void TouchMove(f32 touch_x, f32 touch_y);
+
+    /**
+     * Sets the status of a button to pressed
+     * @param x the x-coordinate of the cursor
+     * @param y the y-coordinate of the cursor
+     * @param button the id of the button to press
+     */
+    void PressButton(int x, int y, MouseButton button);
+
+    /**
+     * Sets the status of a mouse button to pressed
+     * @param button the id of the button to press
+     */
+    void PressMouseButton(MouseButton button);
+
+    /**
+     * Sets the status of touch finger to pressed
+     * @param x the absolute position on the touchscreen of the cursor
+     * @param y the absolute position on the touchscreen of the cursor
+     * @param button the id of the button to press
+     */
+    void PressTouchButton(f32 touch_x, f32 touch_y, MouseButton button);
 
     /**
      * Sets the status of all buttons bound with the key to released
diff --git a/src/input_common/input_mapping.cpp b/src/input_common/input_mapping.cpp
index d6e49d2c5b..6990a86b9e 100644
--- a/src/input_common/input_mapping.cpp
+++ b/src/input_common/input_mapping.cpp
@@ -194,6 +194,10 @@ bool MappingFactory::IsDriverValid(const MappingData& data) const {
     if (data.engine == "keyboard" && data.pad.port != 0) {
         return false;
     }
+    // Only port 0 can be mapped on the mouse
+    if (data.engine == "mouse" && data.pad.port != 0) {
+        return false;
+    }
     // To prevent mapping with two devices we disable any UDP except motion
     if (!Settings::values.enable_udp_controller && data.engine == "cemuhookudp" &&
         data.type != EngineInputType::Motion) {
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index a64e63a399..17acd3933b 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -645,7 +645,10 @@ void GRenderWindow::mousePressEvent(QMouseEvent* event) {
     const auto pos = mapFromGlobal(QCursor::pos());
     const auto [touch_x, touch_y] = MapToTouchScreen(pos.x(), pos.y());
     const auto button = QtButtonToMouseButton(event->button());
-    input_subsystem->GetMouse()->PressButton(pos.x(), pos.y(), touch_x, touch_y, button);
+
+    input_subsystem->GetMouse()->PressMouseButton(button);
+    input_subsystem->GetMouse()->PressButton(pos.x(), pos.y(), button);
+    input_subsystem->GetMouse()->PressTouchButton(touch_x, touch_y, button);
 
     emit MouseActivity();
 }
@@ -661,7 +664,10 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
     const auto [touch_x, touch_y] = MapToTouchScreen(pos.x(), pos.y());
     const int center_x = width() / 2;
     const int center_y = height() / 2;
-    input_subsystem->GetMouse()->MouseMove(pos.x(), pos.y(), touch_x, touch_y, center_x, center_y);
+
+    input_subsystem->GetMouse()->MouseMove(touch_x, touch_y);
+    input_subsystem->GetMouse()->TouchMove(touch_x, touch_y);
+    input_subsystem->GetMouse()->Move(pos.x(), pos.y(), center_x, center_y);
 
     if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) {
         QCursor::setPos(mapToGlobal(QPoint{center_x, center_y}));
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index 723690e710..50b62293eb 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -1490,7 +1490,7 @@ void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) {
     }
 
     const auto button = GRenderWindow::QtButtonToMouseButton(event->button());
-    input_subsystem->GetMouse()->PressButton(0, 0, 0, 0, button);
+    input_subsystem->GetMouse()->PressButton(0, 0, button);
 }
 
 void ConfigureInputPlayer::wheelEvent(QWheelEvent* event) {
diff --git a/src/yuzu/configuration/configure_ringcon.cpp b/src/yuzu/configuration/configure_ringcon.cpp
index 1275f10c89..71afbc4232 100644
--- a/src/yuzu/configuration/configure_ringcon.cpp
+++ b/src/yuzu/configuration/configure_ringcon.cpp
@@ -371,7 +371,7 @@ void ConfigureRingController::mousePressEvent(QMouseEvent* event) {
     }
 
     const auto button = GRenderWindow::QtButtonToMouseButton(event->button());
-    input_subsystem->GetMouse()->PressButton(0, 0, 0, 0, button);
+    input_subsystem->GetMouse()->PressButton(0, 0, button);
 }
 
 void ConfigureRingController::keyPressEvent(QKeyEvent* event) {
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index a1c18ff90d..a689a32dbd 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -1165,6 +1165,14 @@ void GMainWindow::InitializeHotkeys() {
         Settings::values.use_speed_limit.SetValue(!Settings::values.use_speed_limit.GetValue());
     });
     connect_shortcut(QStringLiteral("Toggle Mouse Panning"), [&] {
+        if (Settings::values.mouse_enabled) {
+            Settings::values.mouse_panning = false;
+            QMessageBox::warning(
+                this, tr("Emulated mouse is enabled"),
+                tr("Real mouse input and mouse panning are incompatible. Please disable the "
+                   "emulated mouse in input advanced settings to allow mouse panning."));
+            return;
+        }
         Settings::values.mouse_panning = !Settings::values.mouse_panning;
         if (Settings::values.mouse_panning) {
             render_window->installEventFilter(render_window);
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index 5450b8c38a..5153cdb792 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -62,7 +62,9 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
     const auto mouse_button = SDLButtonToMouseButton(button);
     if (state == SDL_PRESSED) {
         const auto [touch_x, touch_y] = MouseToTouchPos(x, y);
-        input_subsystem->GetMouse()->PressButton(x, y, touch_x, touch_y, mouse_button);
+        input_subsystem->GetMouse()->PressButton(x, y, mouse_button);
+        input_subsystem->GetMouse()->PressMouseButton(mouse_button);
+        input_subsystem->GetMouse()->PressTouchButton(touch_x, touch_y, mouse_button);
     } else {
         input_subsystem->GetMouse()->ReleaseButton(mouse_button);
     }
@@ -70,7 +72,9 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
 
 void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
     const auto [touch_x, touch_y] = MouseToTouchPos(x, y);
-    input_subsystem->GetMouse()->MouseMove(x, y, touch_x, touch_y, 0, 0);
+    input_subsystem->GetMouse()->Move(x, y, 0, 0);
+    input_subsystem->GetMouse()->MouseMove(touch_x, touch_y);
+    input_subsystem->GetMouse()->TouchMove(touch_x, touch_y);
 }
 
 void EmuWindow_SDL2::OnFingerDown(float x, float y, std::size_t id) {