mirror of
https://github.com/ddeverill/SteamlessController.git
synced 2026-10-23 07:02:16 -05:00
Allow back paddles to be bound to keyboard keys
Press-to-bind only listened to the controller, so the remap window could offer gamepad buttons and mouse clicks but nothing from the keyboard. Capture a key press while a row is listening and bind that instead. Capture happens in the remap page's keydown handler rather than through a keyboard hook. The page is already focused while listening, and a low-level hook is a heavy, AV-bait mechanism for a convenience feature. The page reports the physical KeyboardEvent.code, which KeyInput turns into a virtual-key code, so a binding made on one layout means the same physical key on another. Escape stays the cancel affordance and is deliberately not bindable. Keys are delivered with SendInput as scan codes rather than virtual keys, because software that reads scan codes directly sees nothing useful otherwise. Note that injected input does not reach every game — anti-cheat and some input paths ignore it — so this is realistically a desktop, emulator, and browser feature. Gamepad bindings remain the answer in game. Extended keys are flagged from an explicit set. MapVirtualKey is documented to report the 0xE0 prefix under MAPVK_VK_TO_VSC_EX but does not do so for the navigation cluster, which shares scan codes with the numpad — a test caught Up arrow being delivered as numpad 8. Labels come from GetKeyNameText against the active layout, so a non-US keyboard shows its own key names, and are JSON-escaped on the way to the page: the backslash key really is named "\". Paddle release tracking now records what the press actually sent instead of re-reading the binding on release, so rebinding a paddle mid-hold releases the input that was pressed rather than stranding it down. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0839f79f16
commit
b5b7e28cf1
7 files changed
+328
-41
No files matched your search
@@ -2,6 +2,7 @@
|
||||
#include "EventLog.h"
|
||||
#include "VirtualController.h"
|
||||
#include "TrackpadMouse.h"
|
||||
#include "KeyInput.h"
|
||||
#include "steam/SteamController.h"
|
||||
#include <Windows.h>
|
||||
#include <algorithm>
|
||||
@@ -9,6 +10,7 @@
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
@@ -34,10 +36,11 @@ struct ControllerManager::Slot {
|
||||
// thread for that timeout on every one of those attempts.
|
||||
std::chrono::steady_clock::time_point lastSilentAt{};
|
||||
|
||||
// Mouse buttons currently held down by a back paddle, so leaving game mode
|
||||
// mid-press releases them instead of stranding the button down.
|
||||
bool paddleMouseLeftDown = false;
|
||||
bool paddleMouseRightDown = false;
|
||||
// What each paddle (L4, L5, R4, R5) is currently holding down, so leaving
|
||||
// game mode mid-press releases it instead of stranding the input down.
|
||||
// Records what the press actually sent rather than reading the binding
|
||||
// again on release, so rebinding a paddle mid-hold still releases cleanly.
|
||||
BackButtonBinding paddleHeld[4];
|
||||
|
||||
// Haptic edge-detection state for touch (movement ticks).
|
||||
bool hapticWasRightTouching = false;
|
||||
@@ -204,7 +207,7 @@ ControllerManager::~ControllerManager() {
|
||||
if (slot->gameModeActive) {
|
||||
StopReadLoop(*slot);
|
||||
slot->trackpad.Reset();
|
||||
ReleasePaddleMouseButtons(*slot);
|
||||
ReleaseHeldPaddleInputs(*slot);
|
||||
slot->vc.reset();
|
||||
slot->gameModeActive = false;
|
||||
}
|
||||
@@ -284,17 +287,44 @@ void ControllerManager::SetBackButtonConfig(const BackButtonConfig& cfg) {
|
||||
// change takes effect on the very next report.
|
||||
}
|
||||
|
||||
void ControllerManager::ReleasePaddleMouseButtons(Slot& slot) {
|
||||
auto send = [](DWORD flags) {
|
||||
// Sends the key or mouse event a binding stands for. Gamepad actions reach the
|
||||
// virtual pad instead and are ignored here. Returns whether anything was sent.
|
||||
static bool SendPaddleInput(const BackButtonBinding& binding, bool down) {
|
||||
auto sendMouse = [](DWORD flags) {
|
||||
INPUT inp{};
|
||||
inp.type = INPUT_MOUSE;
|
||||
inp.mi.dwFlags = flags;
|
||||
SendInput(1, &inp, sizeof(INPUT));
|
||||
};
|
||||
if (slot.paddleMouseLeftDown) send(MOUSEEVENTF_LEFTUP);
|
||||
if (slot.paddleMouseRightDown) send(MOUSEEVENTF_RIGHTUP);
|
||||
slot.paddleMouseLeftDown = false;
|
||||
slot.paddleMouseRightDown = false;
|
||||
|
||||
switch (binding.kind) {
|
||||
case BackButtonBinding::Kind::Key:
|
||||
SendKeyInput(binding.code, down);
|
||||
return true;
|
||||
|
||||
case BackButtonBinding::Kind::MouseButton:
|
||||
// Extended mouse buttons are not bindable yet.
|
||||
return false;
|
||||
|
||||
case BackButtonBinding::Kind::Action:
|
||||
if (binding.IsAction(BackButtonAction::LeftMouseButton)) {
|
||||
sendMouse(down ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP);
|
||||
return true;
|
||||
}
|
||||
if (binding.IsAction(BackButtonAction::RightMouseButton)) {
|
||||
sendMouse(down ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ControllerManager::ReleaseHeldPaddleInputs(Slot& slot) {
|
||||
for (auto& held : slot.paddleHeld) {
|
||||
SendPaddleInput(held, false);
|
||||
held = BackButtonBinding{};
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerManager::SetControllerPlatform(ControllerPlatform platform) {
|
||||
@@ -458,7 +488,7 @@ ControllerManager::EnableGameModeSlot(Slot& slot, bool& vigemMissingOut,
|
||||
EventLog::Write("GAMEMODE: enabled %ls", slot.path.c_str());
|
||||
slot.gameModeActive = true;
|
||||
slot.trackpad.Reset();
|
||||
ReleasePaddleMouseButtons(slot);
|
||||
ReleaseHeldPaddleInputs(slot);
|
||||
slot.trackpad.SetTrackpadEnabled(m_trackpadMouseEnabled);
|
||||
slot.trackpad.SetUseLeftTrackpad(m_useLeftTrackpad);
|
||||
StartReadLoop(slot);
|
||||
@@ -472,7 +502,7 @@ void ControllerManager::DisableGameModeSlot(Slot& slot) {
|
||||
if (slot.sc->IsOpen())
|
||||
slot.sc->SetRumble(0, 0);
|
||||
slot.trackpad.Reset();
|
||||
ReleasePaddleMouseButtons(slot);
|
||||
ReleaseHeldPaddleInputs(slot);
|
||||
slot.vc.reset();
|
||||
slot.sc->EnableLizardMode();
|
||||
// Reopen shared so Steam can obtain write access — game mode is no longer active.
|
||||
@@ -778,8 +808,9 @@ void ControllerManager::ReadLoop(Slot* slot) {
|
||||
slot->hapticWasLeftTouching = lt;
|
||||
}
|
||||
|
||||
// Fire mouse button events for back paddles mapped to LeftMouseButton/RightMouseButton.
|
||||
// Uses edge detection so we only send DOWN on press and UP on release.
|
||||
// Deliver back paddles bound to a key or a mouse button. These go out
|
||||
// through SendInput rather than the virtual pad. Edge-detected so each
|
||||
// press sends exactly one down and each release exactly one up.
|
||||
if (hasPrev) {
|
||||
struct PaddleEdge { bool cur; bool prev; const BackButtonBinding& binding; };
|
||||
const PaddleEdge edges[] = {
|
||||
@@ -788,17 +819,17 @@ void ControllerManager::ReadLoop(Slot* slot) {
|
||||
{ n>2 && (buf[2]&SteamController::BTN_R4)!=0, (prevBuf[2]&SteamController::BTN_R4)!=0, m_backConfig.r4 },
|
||||
{ n>3 && (buf[3]&SteamController::BTN_R5)!=0, (prevBuf[3]&SteamController::BTN_R5)!=0, m_backConfig.r5 },
|
||||
};
|
||||
for (const auto& e : edges) {
|
||||
for (size_t i = 0; i < std::size(edges); ++i) {
|
||||
const auto& e = edges[i];
|
||||
if (e.cur == e.prev) continue;
|
||||
DWORD flag = 0;
|
||||
if (e.binding.IsAction(BackButtonAction::LeftMouseButton)) {
|
||||
flag = e.cur ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP;
|
||||
slot->paddleMouseLeftDown = e.cur;
|
||||
} else if (e.binding.IsAction(BackButtonAction::RightMouseButton)) {
|
||||
flag = e.cur ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP;
|
||||
slot->paddleMouseRightDown = e.cur;
|
||||
if (e.cur) {
|
||||
if (SendPaddleInput(e.binding, true)) slot->paddleHeld[i] = e.binding;
|
||||
} else {
|
||||
// Release what the press sent, not what the binding says
|
||||
// now — the two differ if the user rebound mid-hold.
|
||||
SendPaddleInput(slot->paddleHeld[i], false);
|
||||
slot->paddleHeld[i] = BackButtonBinding{};
|
||||
}
|
||||
if (flag) { INPUT inp{}; inp.type = INPUT_MOUSE; inp.mi.dwFlags = flag; SendInput(1, &inp, sizeof(INPUT)); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user