mirror of
https://github.com/sal063/AC6_recomp
synced 2026-08-21 23:00:53 -04:00
Hide the free cursor after idle (default 3 s)
Hide the free cursor after idle (ac6_cursor_hide_seconds, 3 s) The free cursor now disappears after N seconds without motion and returns instantly on motion or a button. Never hides while an overlay is on screen (new DialogsVisible presence flag). Default 3 s, 0 = never, live.
This commit is contained in:
@@ -78,6 +78,10 @@ REXCVAR_DEFINE_STRING(ac6_kbm_config, "ac6_input.toml", "AC6/Enhancements",
|
||||
REXCVAR_DEFINE_BOOL(ac6_kbm_padless, true, "AC6/Enhancements",
|
||||
"Provide a virtual controller when none is connected, so "
|
||||
"keyboard and mouse work on their own.");
|
||||
REXCVAR_DEFINE_DOUBLE(ac6_cursor_hide_seconds, 3.0, "AC6/Enhancements",
|
||||
"Hide the free mouse cursor after this many seconds without "
|
||||
"motion (0 = never hide).")
|
||||
.range(0.0, 300.0);
|
||||
|
||||
// The SDK's own mnk virtual-pad driver (input/mnk) - forced off while our
|
||||
// KB+M is enabled so two keyboard mappers never fight over the same pad.
|
||||
@@ -732,11 +736,47 @@ std::atomic<bool> g_hide_cursor{false};
|
||||
WNDPROC g_orig_wndproc = nullptr;
|
||||
HWND g_subclassed_hwnd = nullptr;
|
||||
|
||||
// Free-cursor idle hide (see CursorIdleHideTick below): hidden after
|
||||
// ac6_cursor_hide_seconds without motion, revealed on any real mouse input.
|
||||
std::atomic<bool> g_idle_hidden{false};
|
||||
std::atomic<int64_t> g_idle_last_motion_ms{0};
|
||||
|
||||
LRESULT CALLBACK KbmWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
|
||||
if (msg == WM_SETCURSOR && g_hide_cursor.load(std::memory_order_relaxed)) {
|
||||
SetCursor(nullptr);
|
||||
return TRUE;
|
||||
}
|
||||
if (msg == WM_MOUSEMOVE) {
|
||||
// WM_MOUSEMOVE is also sent for window-management reasons with the
|
||||
// cursor unmoved (see the SDK's note in window_win.cpp), so only a
|
||||
// POSITION CHANGE counts as real motion. Window-thread-only state.
|
||||
static LPARAM s_last_move_lp = -1;
|
||||
if (lp != s_last_move_lp) {
|
||||
s_last_move_lp = lp;
|
||||
if (g_idle_hidden.load(std::memory_order_relaxed)) {
|
||||
// Restamp the idle clock so the poll tick cannot immediately
|
||||
// re-hide. A motion event's own WM_SETCURSOR is sent BEFORE its
|
||||
// WM_MOUSEMOVE lands (still hidden then), so nudge a fresh one -
|
||||
// the arrow returns on THIS motion, not the next.
|
||||
g_idle_last_motion_ms.store(NowMs(), std::memory_order_relaxed);
|
||||
g_idle_hidden.store(false, std::memory_order_relaxed);
|
||||
PostMessageW(hwnd, WM_SETCURSOR, reinterpret_cast<WPARAM>(hwnd), HTCLIENT);
|
||||
}
|
||||
}
|
||||
} else if (msg == WM_LBUTTONDOWN || msg == WM_RBUTTONDOWN || msg == WM_MBUTTONDOWN ||
|
||||
msg == WM_XBUTTONDOWN || msg == WM_MOUSEWHEEL) {
|
||||
if (g_idle_hidden.load(std::memory_order_relaxed)) {
|
||||
g_idle_last_motion_ms.store(NowMs(), std::memory_order_relaxed);
|
||||
g_idle_hidden.store(false, std::memory_order_relaxed);
|
||||
PostMessageW(hwnd, WM_SETCURSOR, reinterpret_cast<WPARAM>(hwnd), HTCLIENT);
|
||||
}
|
||||
} else if (msg == WM_SETCURSOR && g_idle_hidden.load(std::memory_order_relaxed) &&
|
||||
LOWORD(lp) == HTCLIENT) {
|
||||
// Keep the cursor away over the client area while idle-hidden (window
|
||||
// management re-sets it otherwise). Non-client keeps normal arrows.
|
||||
SetCursor(nullptr);
|
||||
return TRUE;
|
||||
}
|
||||
return CallWindowProcW(g_orig_wndproc, hwnd, msg, wp, lp);
|
||||
}
|
||||
|
||||
@@ -759,8 +799,54 @@ void SetCursorHidden(bool hide, HWND hwnd) {
|
||||
if (hide) SetCursor(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Free-cursor idle hide --------------------------------------------------
|
||||
// The FREE cursor (no steering capture) disappears after
|
||||
// ac6_cursor_hide_seconds without motion and returns instantly on motion or
|
||||
// a button - menus and desktop-style contexts; the capture hiding above is
|
||||
// untouched. Never hides while an overlay is visible under the free cursor
|
||||
// (a vanishing pointer over the F4 menu is an anti-feature), while the
|
||||
// window is unfocused, or while capture owns the cursor. Runs on the input
|
||||
// poll; the subclass proc above answers WM_SETCURSOR while hidden and
|
||||
// reveals on real mouse input. Works for pad users too - not gated on
|
||||
// ac6_kbm_enabled.
|
||||
void CursorIdleHideTick() {
|
||||
const double secs = REXCVAR_GET(ac6_cursor_hide_seconds);
|
||||
const int64_t now = NowMs();
|
||||
bool want_hide = false;
|
||||
if (secs > 0.0 && !g_hide_cursor.load(std::memory_order_relaxed)) {
|
||||
HWND fg = GetForegroundWindow();
|
||||
DWORD pid = 0;
|
||||
if (fg) GetWindowThreadProcessId(fg, &pid);
|
||||
if (fg && pid == GetCurrentProcessId()) {
|
||||
EnsureCursorSubclass(fg);
|
||||
POINT p{};
|
||||
if (GetCursorPos(&p)) {
|
||||
static POINT s_last{}; // poll-thread only
|
||||
static bool s_have_last = false;
|
||||
if (!s_have_last || p.x != s_last.x || p.y != s_last.y) {
|
||||
s_have_last = true;
|
||||
s_last = p;
|
||||
g_idle_last_motion_ms.store(now, std::memory_order_relaxed);
|
||||
} else if (!rex::ui::ImGuiDrawer::DialogsVisible() &&
|
||||
now - g_idle_last_motion_ms.load(std::memory_order_relaxed) >=
|
||||
static_cast<int64_t>(secs * 1000.0)) {
|
||||
want_hide = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (g_idle_hidden.exchange(want_hide, std::memory_order_relaxed) != want_hide &&
|
||||
g_subclassed_hwnd) {
|
||||
// Nudge the window thread to apply the new state (hide, or re-arrow
|
||||
// after an overlay opened / the cvar changed - no mouse motion needed).
|
||||
PostMessageW(g_subclassed_hwnd, WM_SETCURSOR, reinterpret_cast<WPARAM>(g_subclassed_hwnd),
|
||||
HTCLIENT);
|
||||
}
|
||||
}
|
||||
#else
|
||||
void SetCursorHidden(bool, void*) {}
|
||||
void CursorIdleHideTick() {}
|
||||
#endif
|
||||
|
||||
// ---- Mouse steering (M3) -----------------------------------------------------
|
||||
@@ -1136,6 +1222,13 @@ PPC_FUNC_IMPL(rex_sub_82390CE0) {
|
||||
const uint32_t state_ptr = ctx.r4.u32;
|
||||
__imp__rex_sub_82390CE0(ctx, base);
|
||||
|
||||
// Free-cursor idle hide rides the input poll (user 0 = once per poll
|
||||
// round). Deliberately outside the ac6_kbm_enabled gate: pad users get
|
||||
// the timeout too.
|
||||
if (user == 0) {
|
||||
CursorIdleHideTick();
|
||||
}
|
||||
|
||||
// Pad-less operation: when no controller is connected (0x48F), present a
|
||||
// neutral synthetic pad on slot 0; the injection below then supplies the
|
||||
// keyboard/mouse state exactly as if a pad were plugged in.
|
||||
|
||||
+9
-2
@@ -77,6 +77,10 @@ class ImGuiDrawer : public WindowInputListener, public UIDrawer {
|
||||
static bool DialogsWantTextInput() {
|
||||
return dialogs_want_text_input_.load(std::memory_order_relaxed);
|
||||
}
|
||||
// Whether any dialog window is visible at all, independent of input
|
||||
// ownership - for policies that key off overlay PRESENCE (e.g. never
|
||||
// auto-hiding the cursor while an overlay is on screen), not hover/focus.
|
||||
static bool DialogsVisible() { return dialogs_visible_.load(std::memory_order_relaxed); }
|
||||
|
||||
protected:
|
||||
void OnKeyDown(KeyEvent& e) override;
|
||||
@@ -104,7 +108,8 @@ class ImGuiDrawer : public WindowInputListener, public UIDrawer {
|
||||
bool IsDrawingDialogs() const { return dialog_loop_next_index_ != SIZE_MAX; }
|
||||
void DetachIfLastDialogRemoved();
|
||||
|
||||
void PublishDialogInputOwnership(bool capture_mouse, bool capture_keyboard, bool want_text_input);
|
||||
void PublishDialogInputOwnership(bool any_visible, bool capture_mouse, bool capture_keyboard,
|
||||
bool want_text_input);
|
||||
|
||||
std::optional<ImGuiKey> VirtualKeyToImGuiKey(VirtualKey vkey);
|
||||
|
||||
@@ -142,10 +147,12 @@ class ImGuiDrawer : public WindowInputListener, public UIDrawer {
|
||||
double frame_time_tick_frequency_;
|
||||
uint64_t last_frame_time_ticks_;
|
||||
|
||||
// See DialogsCaptureMouse()/DialogsCaptureKeyboard()/DialogsWantTextInput().
|
||||
// See DialogsCaptureMouse()/DialogsCaptureKeyboard()/DialogsWantTextInput()/
|
||||
// DialogsVisible().
|
||||
static std::atomic<bool> dialogs_capture_mouse_;
|
||||
static std::atomic<bool> dialogs_capture_keyboard_;
|
||||
static std::atomic<bool> dialogs_want_text_input_;
|
||||
static std::atomic<bool> dialogs_visible_;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
+8
-6
@@ -37,9 +37,11 @@ static_assert(sizeof(ImmediateVertex) == sizeof(ImDrawVert), "Vertex types must
|
||||
std::atomic<bool> ImGuiDrawer::dialogs_capture_mouse_{false};
|
||||
std::atomic<bool> ImGuiDrawer::dialogs_capture_keyboard_{false};
|
||||
std::atomic<bool> ImGuiDrawer::dialogs_want_text_input_{false};
|
||||
std::atomic<bool> ImGuiDrawer::dialogs_visible_{false};
|
||||
|
||||
void ImGuiDrawer::PublishDialogInputOwnership(bool capture_mouse, bool capture_keyboard,
|
||||
bool want_text_input) {
|
||||
void ImGuiDrawer::PublishDialogInputOwnership(bool any_visible, bool capture_mouse,
|
||||
bool capture_keyboard, bool want_text_input) {
|
||||
dialogs_visible_.store(any_visible, std::memory_order_relaxed);
|
||||
dialogs_capture_mouse_.store(capture_mouse, std::memory_order_relaxed);
|
||||
dialogs_capture_keyboard_.store(capture_keyboard, std::memory_order_relaxed);
|
||||
dialogs_want_text_input_.store(want_text_input, std::memory_order_relaxed);
|
||||
@@ -370,12 +372,12 @@ void ImGuiDrawer::Draw(UIDrawContext& ui_draw_context) {
|
||||
if (!immediate_drawer_) {
|
||||
// A presenter has been attached, but an immediate drawer hasn't been
|
||||
// attached yet.
|
||||
PublishDialogInputOwnership(false, false, false);
|
||||
PublishDialogInputOwnership(false, false, false, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dialogs_.empty()) {
|
||||
PublishDialogInputOwnership(false, false, false);
|
||||
PublishDialogInputOwnership(false, false, false, false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -425,7 +427,7 @@ void ImGuiDrawer::Draw(UIDrawContext& ui_draw_context) {
|
||||
// focus; clicking the game void releases it).
|
||||
const bool keyboard_owned =
|
||||
io.WantCaptureKeyboard || ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow);
|
||||
PublishDialogInputOwnership(any_dialog_visible && io.WantCaptureMouse,
|
||||
PublishDialogInputOwnership(any_dialog_visible, any_dialog_visible && io.WantCaptureMouse,
|
||||
any_dialog_visible && keyboard_owned,
|
||||
any_dialog_visible && io.WantTextInput);
|
||||
|
||||
@@ -684,7 +686,7 @@ void ImGuiDrawer::DetachIfLastDialogRemoved() {
|
||||
// properties.
|
||||
ClearInput();
|
||||
// No dialogs left: nothing can own input until the drawer reattaches.
|
||||
PublishDialogInputOwnership(false, false, false);
|
||||
PublishDialogInputOwnership(false, false, false, false);
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
Reference in New Issue
Block a user