diff --git a/common/Windows/WinMisc.cpp b/common/Windows/WinMisc.cpp index b7bdb89d24c26..5b302cf52c7f2 100644 --- a/common/Windows/WinMisc.cpp +++ b/common/Windows/WinMisc.cpp @@ -100,54 +100,16 @@ void Common::SetMousePosition(int x, int y) SetCursorPos(x, y); } -/* -static HHOOK mouseHook = nullptr; -static std::function fnMouseMoveCb; -LRESULT CALLBACK Mousecb(int nCode, WPARAM wParam, LPARAM lParam) -{ - if (nCode >= 0 && wParam == WM_MOUSEMOVE) - { - MSLLHOOKSTRUCT* mouse = (MSLLHOOKSTRUCT*)lParam; - fnMouseMoveCb(mouse->pt.x, mouse->pt.y); - } - return CallNextHookEx(mouseHook, nCode, wParam, lParam); -} -*/ - -// This (and the above) works, but is not recommended on Windows and is only here for consistency. -// Defer to using raw input instead. bool Common::AttachMousePositionCb(std::function cb) { - /* - if (mouseHook) - Common::DetachMousePositionCb(); - - fnMouseMoveCb = cb; - mouseHook = SetWindowsHookEx(WH_MOUSE_LL, Mousecb, GetModuleHandle(NULL), 0); - if (!mouseHook) - { - Console.Warning("Failed to set mouse hook: %d", GetLastError()); - return false; - } - - #if defined(PCSX2_DEBUG) || defined(PCSX2_DEVBUILD) - static bool warned = false; - if (!warned) - { - Console.Warning("Mouse hooks are enabled, and this isn't a release build! Using a debugger, or loading symbols, _will_ stall the hook and cause global mouse lag."); - warned = true; - } - #endif - */ + // We use raw input messages which are handled by the windows message loop. + // The alternative is to use a low-level mouse hook, but this passes Windows all mouse messages to PCSX2. + // If PCSX2 hangs, or you attach a debugger, the mouse will stop working system-wide. return true; } void Common::DetachMousePositionCb() { - /* - UnhookWindowsHookEx(mouseHook); - mouseHook = nullptr; - */ } bool Common::PlaySoundAsync(const char* path) diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp index 89a9e24acbbf2..e4a9200d2c758 100644 --- a/pcsx2-qt/MainWindow.cpp +++ b/pcsx2-qt/MainWindow.cpp @@ -1151,11 +1151,19 @@ bool MainWindow::shouldMouseLock() const if (!Host::GetBoolSettingValue("EmuCore", "EnableMouseLock", false)) return false; + if(m_display_created == false || m_display_widget == nullptr && !isRenderingToMain()) + return false; + bool windowsHidden = (!g_debugger_window || g_debugger_window->isHidden()) && (!m_controller_settings_window || m_controller_settings_window->isHidden()) && (!m_settings_window || m_settings_window->isHidden()); - return windowsHidden && (isActiveWindow() || isRenderingFullscreen()); + auto* displayWindow = isRenderingToMain() ? window() : m_display_widget->window(); + + if(displayWindow == nullptr) + return false; + + return windowsHidden && (displayWindow->isActiveWindow() || displayWindow->isFullScreen()); } bool MainWindow::shouldAbortForMemcardBusy(const VMLock& lock) @@ -2322,19 +2330,24 @@ bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr if (msg->message == WM_INPUT) { - UINT dwSize = 40; - static BYTE lpb[40]; - if (GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER))) + UINT dwSize = 0; + GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, nullptr, &dwSize, sizeof(RAWINPUTHEADER)); + + if (dwSize > 0) { - const RAWINPUT* raw = (RAWINPUT*)lpb; - if (raw->header.dwType == RIM_TYPEMOUSE) + std::vector lpb(dwSize); + if (GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, lpb.data(), &dwSize, sizeof(RAWINPUTHEADER)) == dwSize) { - const RAWMOUSE& mouse = raw->data.mouse; - if (mouse.usFlags == MOUSE_MOVE_ABSOLUTE || mouse.usFlags == MOUSE_MOVE_RELATIVE) + const RAWINPUT* raw = reinterpret_cast(lpb.data()); + if (raw->header.dwType == RIM_TYPEMOUSE) { - POINT cursorPos; - GetCursorPos(&cursorPos); - checkMousePosition(cursorPos.x, cursorPos.y); + const RAWMOUSE& mouse = raw->data.mouse; + if (mouse.usFlags == MOUSE_MOVE_ABSOLUTE || mouse.usFlags == MOUSE_MOVE_RELATIVE) + { + POINT cursorPos; + if (GetCursorPos(&cursorPos)) + checkMousePosition(cursorPos.x, cursorPos.y); + } } } } @@ -2635,33 +2648,35 @@ void MainWindow::setupMouseMoveHandler() void MainWindow::checkMousePosition(int x, int y) { - if (!shouldMouseLock()) - return; + // This function is called from a different thread on Linux/macOS + // kaboom can happen when the widget is destroyed after shouldMouseLock is called, so queue everything to the UI thread + QtHost::RunOnUIThread([this, x, y]() { + if (!shouldMouseLock()) + return; - const QPoint globalCursorPos = {x, y}; - QRect windowBounds = isRenderingFullscreen() ? screen()->geometry() : geometry(); - if (windowBounds.contains(globalCursorPos)) - return; + // physical mouse position + const QPoint physicalPos(x, y); - Common::SetMousePosition( - std::clamp(globalCursorPos.x(), windowBounds.left(), windowBounds.right()), - std::clamp(globalCursorPos.y(), windowBounds.top(), windowBounds.bottom())); - - /* - Provided below is how we would handle this if we were using low level hooks (What is used in Common::AttachMouseCb) - We currently use rawmouse on Windows, so Common::SetMousePosition called directly works fine. - */ -#if 0 - // We are currently in a low level hook. SetCursorPos here (what is in Common::SetMousePosition) will not work! - // Let's (a)buse Qt's event loop to dispatch the call at a later time, outside of the hook. - QMetaObject::invokeMethod( - this, [=]() { - Common::SetMousePosition( - std::clamp(globalCursorPos.x(), windowBounds.left(), windowBounds.right()), - std::clamp(globalCursorPos.y(), windowBounds.top(), windowBounds.bottom())); - }, - Qt::QueuedConnection); -#endif + const auto* displayWindow = getDisplayContainer()->window(); + + // logical (DIP) frame rect + QRectF logicalBounds = displayWindow->geometry(); + + // physical frame rect + const qreal scale = displayWindow->devicePixelRatioF(); + QRectF physicalBounds( + logicalBounds.x() * scale, + logicalBounds.y() * scale, + logicalBounds.width() * scale, + logicalBounds.height() * scale); + + if (physicalBounds.contains(physicalPos)) + return; + + Common::SetMousePosition( + std::clamp(physicalPos.x(), (int)physicalBounds.left(), (int)physicalBounds.right()), + std::clamp(physicalPos.y(), (int)physicalBounds.top(), (int)physicalBounds.bottom())); + }); } void MainWindow::saveDisplayWindowGeometryToConfig() diff --git a/pcsx2-qt/Settings/InterfaceSettingsWidget.cpp b/pcsx2-qt/Settings/InterfaceSettingsWidget.cpp index abfa7e5cc336e..1d3c47a96a714 100644 --- a/pcsx2-qt/Settings/InterfaceSettingsWidget.cpp +++ b/pcsx2-qt/Settings/InterfaceSettingsWidget.cpp @@ -88,13 +88,27 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pauseOnControllerDisconnection, "UI", "PauseOnControllerDisconnection", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.discordPresence, "EmuCore", "EnableDiscordPresence", false); - SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.mouseLock, "EmuCore", "EnableMouseLock", false); - connect(m_ui.mouseLock, &QCheckBox::checkStateChanged, [](Qt::CheckState state) { - if (state == Qt::Checked) - Common::AttachMousePositionCb([](int x, int y) { g_main_window->checkMousePosition(x, y); }); - else - Common::DetachMousePositionCb(); - }); +#ifdef __linux__ // Mouse locking is only supported on X11 + const bool mouse_lock_supported = QGuiApplication::platformName().toLower() == "xcb"; +#else + const bool mouse_lock_supported = true; +#endif + + if(mouse_lock_supported) + { + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.mouseLock, "EmuCore", "EnableMouseLock", false); + connect(m_ui.mouseLock, &QCheckBox::checkStateChanged, [](Qt::CheckState state) { + if (state == Qt::Checked) + Common::AttachMousePositionCb([](int x, int y) { g_main_window->checkMousePosition(x, y); }); + else + Common::DetachMousePositionCb(); + }); + } + else + { + m_ui.mouseLock->setEnabled(false); + } + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.startFullscreen, "UI", "StartFullscreen", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.doubleClickTogglesFullscreen, "UI", "DoubleClickTogglesFullscreen", true); @@ -186,7 +200,7 @@ InterfaceSettingsWidget::InterfaceSettingsWidget(SettingsWindow* settings_dialog tr("Shows the game you are currently playing as part of your profile in Discord.")); dialog()->registerWidgetHelp( m_ui.mouseLock, tr("Enable Mouse Lock"), tr("Unchecked"), - tr("Locks the mouse cursor to the windows when PCSX2 is in focus and all other windows are closed.
Unavailable on Linux Wayland.
Requires accessibility permissions on macOS.")); + tr("Locks the mouse cursor to the windows when PCSX2 is in focus and all other windows are closed.
Unavailable on Linux Wayland.
Requires accessibility permissions on macOS.
Limited support for mixed-resolution with non-100% DPI configurations.")); dialog()->registerWidgetHelp( m_ui.doubleClickTogglesFullscreen, tr("Double-Click Toggles Fullscreen"), tr("Checked"), tr("Allows switching in and out of fullscreen mode by double-clicking the game window.")); @@ -251,4 +265,4 @@ void InterfaceSettingsWidget::onClearGameListBackgroundTriggered() Host::RemoveBaseSettingValue("UI", "GameListBackgroundPath"); Host::CommitBaseSettingChanges(); emit backgroundChanged(); -} \ No newline at end of file +}