Skip to content
Permalink
Browse files
Merge pull request #9697 from Filoppi/cursor_locking
Implement Cursor Locking and input focus checks for it
  • Loading branch information
JMC47 committed May 27, 2021
2 parents 52a388a + 3c7c2df commit 45a5c9c
Show file tree
Hide file tree
Showing 25 changed files with 400 additions and 15 deletions.
@@ -151,6 +151,12 @@ bool Host_RendererHasFocus()
return true;
}

bool Host_RendererHasFullFocus()
{
// Mouse cursor locking actually exists in Android but we don't implement (nor need) that
return true;
}

bool Host_RendererIsFullscreen()
{
return false;
@@ -147,6 +147,7 @@ void SConfig::SaveInterfaceSettings(IniFile& ini)

interface->Set("ConfirmStop", bConfirmStop);
interface->Set("HideCursor", bHideCursor);
interface->Set("LockCursor", bLockCursor);
interface->Set("LanguageCode", m_InterfaceLanguage);
interface->Set("ExtendedFPSInfo", m_InterfaceExtendedFPSInfo);
interface->Set("ShowActiveTitle", m_show_active_title);
@@ -401,6 +402,7 @@ void SConfig::LoadInterfaceSettings(IniFile& ini)

interface->Get("ConfirmStop", &bConfirmStop, true);
interface->Get("HideCursor", &bHideCursor, false);
interface->Get("LockCursor", &bLockCursor, false);
interface->Get("LanguageCode", &m_InterfaceLanguage, "");
interface->Get("ExtendedFPSInfo", &m_InterfaceExtendedFPSInfo, false);
interface->Get("ShowActiveTitle", &m_show_active_title, true);
@@ -150,6 +150,7 @@ struct SConfig
// Interface settings
bool bConfirmStop = false;
bool bHideCursor = false;
bool bLockCursor = false;
std::string theme_name;

// Bluetooth passthrough mode settings
@@ -1113,10 +1113,15 @@ void DoFrameStep()
}
}

void UpdateInputGate(bool require_focus)
void UpdateInputGate(bool require_focus, bool require_full_focus)
{
ControlReference::SetInputGate((!require_focus || Host_RendererHasFocus()) &&
!Host_UIBlocksControllerState());
// If the user accepts background input, controls should pass even if an on screen interface is on
const bool focus_passes =
!require_focus || (Host_RendererHasFocus() && !Host_UIBlocksControllerState());
// Ignore full focus if we don't require basic focus
const bool full_focus_passes =
!require_focus || !require_full_focus || (focus_passes && Host_RendererHasFullFocus());
ControlReference::SetInputGate(focus_passes && full_focus_passes);
}

} // namespace Core
@@ -169,6 +169,6 @@ void HostDispatchJobs();

void DoFrameStep();

void UpdateInputGate(bool require_focus);
void UpdateInputGate(bool require_focus, bool require_full_focus = false);

} // namespace Core
@@ -867,7 +867,8 @@ void Update(u64 ticks)

if (s_half_line_of_next_si_poll == s_half_line_count)
{
Core::UpdateInputGate(!SConfig::GetInstance().m_BackgroundInput);
Core::UpdateInputGate(!SConfig::GetInstance().m_BackgroundInput,
SConfig::GetInstance().bLockCursor);
SerialInterface::UpdateDevices();
s_half_line_of_next_si_poll += 2 * SerialInterface::GetPollXLines();
}
@@ -36,6 +36,7 @@ enum class HostMessageID
std::vector<std::string> Host_GetPreferredLocales();
bool Host_UIBlocksControllerState();
bool Host_RendererHasFocus();
bool Host_RendererHasFullFocus();
bool Host_RendererIsFullscreen();

void Host_Message(HostMessageID id);
@@ -24,7 +24,7 @@
#include "InputCommon/GCPadStatus.h"

// clang-format off
constexpr std::array<const char*, 125> s_hotkey_labels{{
constexpr std::array<const char*, 126> s_hotkey_labels{{
_trans("Open"),
_trans("Change Disc"),
_trans("Eject Disc"),
@@ -35,6 +35,7 @@ constexpr std::array<const char*, 125> s_hotkey_labels{{
_trans("Toggle Fullscreen"),
_trans("Take Screenshot"),
_trans("Exit"),
_trans("Unlock Cursor"),
_trans("Activate NetPlay Chat"),
_trans("Control NetPlay Golf Mode"),

@@ -29,6 +29,7 @@ enum Hotkey
HK_FULLSCREEN,
HK_SCREENSHOT,
HK_EXIT,
HK_UNLOCK_CURSOR,
HK_ACTIVATE_CHAT,
HK_REQUEST_GOLF_CONTROL,

@@ -98,6 +98,12 @@ bool Host_RendererHasFocus()
return s_platform->IsWindowFocused();
}

bool Host_RendererHasFullFocus()
{
// Mouse capturing isn't implemented
return Host_RendererHasFocus();
}

bool Host_RendererIsFullscreen()
{
return s_platform->IsWindowFullscreen();
@@ -51,6 +51,6 @@ class Platform
Common::Flag m_shutdown_requested{false};
Common::Flag m_tried_graceful_shutdown{false};

bool m_window_focus = true;
bool m_window_focus = true; // Should be made atomic if actually implemented
bool m_window_fullscreen = false;
};
@@ -10,6 +10,10 @@

#include <imgui.h>

#ifdef _WIN32
#include <windows.h>
#endif

#include "Common/Common.h"

#include "Core/Config/MainSettings.h"
@@ -49,6 +53,8 @@ Host* Host::GetInstance()

void Host::SetRenderHandle(void* handle)
{
m_render_to_main = Config::Get(Config::MAIN_RENDER_TO_MAIN);

if (m_render_handle == handle)
return;

@@ -61,9 +67,27 @@ void Host::SetRenderHandle(void* handle)
}
}

void Host::SetMainWindowHandle(void* handle)
{
m_main_window_handle = handle;
}

bool Host::GetRenderFocus()
{
#ifdef _WIN32
// Unfortunately Qt calls SetRenderFocus() with a slight delay compared to what we actually need
// to avoid inputs that cause a focus loss to be processed by the emulation
if (m_render_to_main)
return GetForegroundWindow() == (HWND)m_main_window_handle.load();
return GetForegroundWindow() == (HWND)m_render_handle.load();
#else
return m_render_focus;
#endif
}

bool Host::GetRenderFullFocus()
{
return m_render_full_focus;
}

void Host::SetRenderFocus(bool focus)
@@ -76,6 +100,11 @@ void Host::SetRenderFocus(bool focus)
});
}

void Host::SetRenderFullFocus(bool focus)
{
m_render_full_focus = focus;
}

bool Host::GetRenderFullscreen()
{
return m_render_fullscreen;
@@ -131,6 +160,11 @@ bool Host_RendererHasFocus()
return Host::GetInstance()->GetRenderFocus();
}

bool Host_RendererHasFullFocus()
{
return Host::GetInstance()->GetRenderFullFocus();
}

bool Host_RendererIsFullscreen()
{
return Host::GetInstance()->GetRenderFullscreen();
@@ -23,10 +23,13 @@ class Host final : public QObject
static Host* GetInstance();

bool GetRenderFocus();
bool GetRenderFullFocus();
bool GetRenderFullscreen();

void SetMainWindowHandle(void* handle);
void SetRenderHandle(void* handle);
void SetRenderFocus(bool focus);
void SetRenderFullFocus(bool focus);
void SetRenderFullscreen(bool fullscreen);
void ResizeSurface(int new_width, int new_height);
void RequestNotifyMapLoaded();
@@ -42,6 +45,9 @@ class Host final : public QObject
Host();

std::atomic<void*> m_render_handle{nullptr};
std::atomic<void*> m_main_window_handle{nullptr};
std::atomic<bool> m_render_to_main{false};
std::atomic<bool> m_render_focus{false};
std::atomic<bool> m_render_full_focus{false};
std::atomic<bool> m_render_fullscreen{false};
};
@@ -213,6 +213,10 @@ void HotkeyScheduler::Run()
if (IsHotkey(HK_EXIT))
emit ExitHotkey();

// Unlock Cursor
if (IsHotkey(HK_UNLOCK_CURSOR))
emit UnlockCursor();

auto& settings = Settings::Instance();

// Toggle Chat
@@ -27,6 +27,7 @@ class HotkeyScheduler : public QObject
void ChangeDisc();

void ExitHotkey();
void UnlockCursor();
void ActivateChat();
void RequestGolfControl();
void FullScreenHotkey();
@@ -269,6 +269,8 @@ MainWindow::MainWindow(std::unique_ptr<BootParameters> boot_parameters,
return;
}
}

Host::GetInstance()->SetMainWindowHandle(reinterpret_cast<void*>(winId()));
}

MainWindow::~MainWindow()
@@ -547,6 +549,7 @@ void MainWindow::ConnectHotkeys()
connect(m_hotkey_scheduler, &HotkeyScheduler::ChangeDisc, this, &MainWindow::ChangeDisc);
connect(m_hotkey_scheduler, &HotkeyScheduler::EjectDisc, this, &MainWindow::EjectDisc);
connect(m_hotkey_scheduler, &HotkeyScheduler::ExitHotkey, this, &MainWindow::close);
connect(m_hotkey_scheduler, &HotkeyScheduler::UnlockCursor, this, &MainWindow::UnlockCursor);
connect(m_hotkey_scheduler, &HotkeyScheduler::TogglePauseHotkey, this, &MainWindow::TogglePause);
connect(m_hotkey_scheduler, &HotkeyScheduler::ActivateChat, this, &MainWindow::OnActivateChat);
connect(m_hotkey_scheduler, &HotkeyScheduler::RequestGolfControl, this,
@@ -813,6 +816,13 @@ bool MainWindow::RequestStop()
return true;
}

const bool rendered_widget_was_active =
m_render_widget->isActiveWindow() && !m_render_widget->isFullScreen();
QWidget* confirm_parent = (!m_rendering_to_main && rendered_widget_was_active) ?
m_render_widget :
static_cast<QWidget*>(this);
const bool was_cursor_locked = m_render_widget->IsCursorLocked();

if (!m_render_widget->isFullScreen())
m_render_widget_geometry = m_render_widget->saveGeometry();
else
@@ -833,21 +843,44 @@ bool MainWindow::RequestStop()
if (pause)
Core::SetState(Core::State::Paused);

if (rendered_widget_was_active)
{
// We have to do this before creating the message box, otherwise we might receive the window
// activation event before we know we need to lock the cursor again.
m_render_widget->SetCursorLockedOnNextActivation(was_cursor_locked);
}

// This is to avoid any "race conditions" between the "Window Activate" message and the
// message box returning, which could break cursor locking depending on the order
m_render_widget->SetWaitingForMessageBox(true);
auto confirm = ModalMessageBox::question(
m_rendering_to_main ? static_cast<QWidget*>(this) : m_render_widget, tr("Confirm"),
confirm_parent, tr("Confirm"),
m_stop_requested ? tr("A shutdown is already in progress. Unsaved data "
"may be lost if you stop the current emulation "
"before it completes. Force stop?") :
tr("Do you want to stop the current emulation?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::NoButton, Qt::ApplicationModal);

// If a user confirmed stopping the emulation, we do not capture the cursor again,
// even if the render widget will stay alive for a while.
// If a used rejected stopping the emulation, we instead capture the cursor again,
// and let them continue playing as if nothing had happened
// (assuming cursor locking is on).
if (confirm != QMessageBox::Yes)
{
m_render_widget->SetWaitingForMessageBox(false);

if (pause)
Core::SetState(state);

return false;
}
else
{
m_render_widget->SetCursorLockedOnNextActivation(false);
// This needs to be after SetCursorLockedOnNextActivation(false) as it depends on it
m_render_widget->SetWaitingForMessageBox(false);
}
}

OnStopRecording();
@@ -917,6 +950,12 @@ void MainWindow::FullScreen()
}
}

void MainWindow::UnlockCursor()
{
if (!m_render_widget->isFullScreen())
m_render_widget->SetCursorLocked(false);
}

void MainWindow::ScreenShot()
{
Core::SaveScreenShot();
@@ -108,6 +108,7 @@ class MainWindow final : public QMainWindow
void SetFullScreenResolution(bool fullscreen);

void FullScreen();
void UnlockCursor();
void ScreenShot();

void CreateComponents();

0 comments on commit 45a5c9c

Please sign in to comment.