Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 3 additions & 41 deletions common/Windows/WinMisc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,54 +100,16 @@ void Common::SetMousePosition(int x, int y)
SetCursorPos(x, y);
}

/*
static HHOOK mouseHook = nullptr;
static std::function<void(int, int)> 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<void(int, int)> 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)
Expand Down
87 changes: 51 additions & 36 deletions pcsx2-qt/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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<BYTE> 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<const RAWINPUT*>(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);
}
}
}
}
Expand Down Expand Up @@ -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()
Expand Down
32 changes: 23 additions & 9 deletions pcsx2-qt/Settings/InterfaceSettingsWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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.<br><b>Unavailable on Linux Wayland.</b><br><b>Requires accessibility permissions on macOS.</b>"));
tr("Locks the mouse cursor to the windows when PCSX2 is in focus and all other windows are closed.<br><b>Unavailable on Linux Wayland.</b><br><b>Requires accessibility permissions on macOS.</b><br><b>Limited support for mixed-resolution with non-100% DPI configurations.</b>"));
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."));
Expand Down Expand Up @@ -251,4 +265,4 @@ void InterfaceSettingsWidget::onClearGameListBackgroundTriggered()
Host::RemoveBaseSettingValue("UI", "GameListBackgroundPath");
Host::CommitBaseSettingChanges();
emit backgroundChanged();
}
}