Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #8428 from jordan-woyak/better-hotkeys
InputCommon: Add hotkey support to input expressions.
  • Loading branch information
JMC47 committed Sep 26, 2020
2 parents 57f14b2 + 431eb4d commit c64d41d
Show file tree
Hide file tree
Showing 12 changed files with 576 additions and 109 deletions.
4 changes: 2 additions & 2 deletions Source/Core/Core/HW/GCPadEmu.cpp
Expand Up @@ -187,10 +187,10 @@ void GCPad::LoadDefaults(const ControllerInterface& ciface)
m_buttons->SetControlExpression(3, "S"); // Y
m_buttons->SetControlExpression(4, "D"); // Z
#ifdef _WIN32
m_buttons->SetControlExpression(5, "!LMENU & RETURN"); // Start
m_buttons->SetControlExpression(5, "RETURN"); // Start
#else
// OS X/Linux
m_buttons->SetControlExpression(5, "!`Alt_L` & Return"); // Start
m_buttons->SetControlExpression(5, "Return"); // Start
#endif

// stick modifiers to 50 %
Expand Down
12 changes: 6 additions & 6 deletions Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp
Expand Up @@ -603,9 +603,9 @@ void Wiimote::LoadDefaults(const ControllerInterface& ciface)
m_buttons->SetControlExpression(5, "E"); // +

#ifdef _WIN32
m_buttons->SetControlExpression(6, "!LMENU & RETURN"); // Home
m_buttons->SetControlExpression(6, "RETURN"); // Home
#else
m_buttons->SetControlExpression(6, "!`Alt_L` & Return"); // Home
m_buttons->SetControlExpression(6, "Return"); // Home
#endif

// Shake
Expand All @@ -625,10 +625,10 @@ void Wiimote::LoadDefaults(const ControllerInterface& ciface)
m_dpad->SetControlExpression(2, "LEFT"); // Left
m_dpad->SetControlExpression(3, "RIGHT"); // Right
#elif __APPLE__
m_dpad->SetControlExpression(0, "Up Arrow"); // Up
m_dpad->SetControlExpression(1, "Down Arrow"); // Down
m_dpad->SetControlExpression(2, "Left Arrow"); // Left
m_dpad->SetControlExpression(3, "Right Arrow"); // Right
m_dpad->SetControlExpression(0, "Up Arrow"); // Up
m_dpad->SetControlExpression(1, "Down Arrow"); // Down
m_dpad->SetControlExpression(2, "Left Arrow"); // Left
m_dpad->SetControlExpression(3, "Right Arrow"); // Right
#else
m_dpad->SetControlExpression(0, "Up"); // Up
m_dpad->SetControlExpression(1, "Down"); // Down
Expand Down
107 changes: 48 additions & 59 deletions Source/Core/Core/HotkeyManager.cpp
Expand Up @@ -409,83 +409,72 @@ void HotkeyManager::LoadDefaults(const ControllerInterface& ciface)
{
EmulatedController::LoadDefaults(ciface);

#ifdef _WIN32
const std::string NON = "(!(LMENU | RMENU) & !(LSHIFT | RSHIFT) & !(LCONTROL | RCONTROL))";
const std::string ALT = "((LMENU | RMENU) & !(LSHIFT | RSHIFT) & !(LCONTROL | RCONTROL))";
const std::string SHIFT = "(!(LMENU | RMENU) & (LSHIFT | RSHIFT) & !(LCONTROL | RCONTROL))";
const std::string CTRL = "(!(LMENU | RMENU) & !(LSHIFT | RSHIFT) & (LCONTROL | RCONTROL))";
#elif __APPLE__
const std::string NON =
"(!`Left Alt` & !(`Left Shift`| `Right Shift`) & !(`Left Control` | `Right Control`))";
const std::string ALT =
"(`Left Alt` & !(`Left Shift`| `Right Shift`) & !(`Left Control` | `Right Control`))";
const std::string SHIFT =
"(!`Left Alt` & (`Left Shift`| `Right Shift`) & !(`Left Control` | `Right Control`))";
const std::string CTRL =
"(!`Left Alt` & !(`Left Shift`| `Right Shift`) & (`Left Control` | `Right Control`))";
#else
const std::string NON = "(!`Alt_L` & !(`Shift_L` | `Shift_R`) & !(`Control_L` | `Control_R` ))";
const std::string ALT = "(`Alt_L` & !(`Shift_L` | `Shift_R`) & !(`Control_L` | `Control_R` ))";
const std::string SHIFT = "(!`Alt_L` & (`Shift_L` | `Shift_R`) & !(`Control_L` | `Control_R` ))";
const std::string CTRL = "(!`Alt_L` & !(`Shift_L` | `Shift_R`) & (`Control_L` | `Control_R` ))";
#endif

auto set_key_expression = [this](int index, const std::string& expression) {
m_keys[FindGroupByID(index)]
->controls[GetIndexForGroup(FindGroupByID(index), index)]
->control_ref->SetExpression(expression);
};

auto hotkey_string = [](std::vector<std::string_view> inputs) {
std::string result;
for (auto& input : inputs)
{
if (!result.empty())
result += '+';
result += input;
}
return "@(" + result + ')';
};

// General hotkeys
set_key_expression(HK_OPEN, CTRL + " & O");
set_key_expression(HK_PLAY_PAUSE, NON + " & `F10`");
set_key_expression(HK_OPEN, hotkey_string({"Ctrl", "O"}));
set_key_expression(HK_PLAY_PAUSE, "F10");
#ifdef _WIN32
set_key_expression(HK_STOP, NON + " & ESCAPE");
set_key_expression(HK_FULLSCREEN, ALT + " & RETURN");
set_key_expression(HK_STOP, "ESCAPE");
set_key_expression(HK_FULLSCREEN, hotkey_string({"Alt", "RETURN"}));
#else
set_key_expression(HK_STOP, NON + " & Escape");
set_key_expression(HK_FULLSCREEN, ALT + " & Return");
set_key_expression(HK_STOP, "Escape");
set_key_expression(HK_FULLSCREEN, hotkey_string({"Alt", "Return"}));
#endif
set_key_expression(HK_STEP, NON + " & `F11`");
set_key_expression(HK_STEP_OVER, SHIFT + " & `F10`");
set_key_expression(HK_STEP_OUT, SHIFT + " & `F11`");
set_key_expression(HK_BP_TOGGLE, SHIFT + " & `F9`");
set_key_expression(HK_SCREENSHOT, NON + " & `F9`");
set_key_expression(HK_WIIMOTE1_CONNECT, ALT + " & `F5`");
set_key_expression(HK_WIIMOTE2_CONNECT, ALT + " & `F6`");
set_key_expression(HK_WIIMOTE3_CONNECT, ALT + " & `F7`");
set_key_expression(HK_WIIMOTE4_CONNECT, ALT + " & `F8`");
set_key_expression(HK_BALANCEBOARD_CONNECT, ALT + " & `F9`");
set_key_expression(HK_STEP, "F11");
set_key_expression(HK_STEP_OVER, hotkey_string({"Shift", "F10"}));
set_key_expression(HK_STEP_OUT, hotkey_string({"Shift", "F11"}));
set_key_expression(HK_BP_TOGGLE, hotkey_string({"Shift", "F9"}));
set_key_expression(HK_SCREENSHOT, "F9");
set_key_expression(HK_WIIMOTE1_CONNECT, hotkey_string({"Alt", "F5"}));
set_key_expression(HK_WIIMOTE2_CONNECT, hotkey_string({"Alt", "F6"}));
set_key_expression(HK_WIIMOTE3_CONNECT, hotkey_string({"Alt", "F7"}));
set_key_expression(HK_WIIMOTE4_CONNECT, hotkey_string({"Alt", "F8"}));
set_key_expression(HK_BALANCEBOARD_CONNECT, hotkey_string({"Alt", "F9"}));
#ifdef _WIN32
set_key_expression(HK_TOGGLE_THROTTLE, NON + " & TAB");
set_key_expression(HK_TOGGLE_THROTTLE, "TAB");
#else
set_key_expression(HK_TOGGLE_THROTTLE, NON + " & Tab");
set_key_expression(HK_TOGGLE_THROTTLE, "Tab");
#endif

// Freelook
set_key_expression(HK_FREELOOK_DECREASE_SPEED, SHIFT + " & `1`");
set_key_expression(HK_FREELOOK_INCREASE_SPEED, SHIFT + " & `2`");
set_key_expression(HK_FREELOOK_RESET_SPEED, SHIFT + " & F");
set_key_expression(HK_FREELOOK_UP, SHIFT + " & E");
set_key_expression(HK_FREELOOK_DOWN, SHIFT + " & Q");
set_key_expression(HK_FREELOOK_LEFT, SHIFT + " & A");
set_key_expression(HK_FREELOOK_RIGHT, SHIFT + " & D");
set_key_expression(HK_FREELOOK_ZOOM_IN, SHIFT + " & W");
set_key_expression(HK_FREELOOK_ZOOM_OUT, SHIFT + " & S");
set_key_expression(HK_FREELOOK_RESET, SHIFT + " & R");
set_key_expression(HK_FREELOOK_INCREASE_FOV_X, SHIFT + " & `Axis Z+`");
set_key_expression(HK_FREELOOK_DECREASE_FOV_X, SHIFT + " & `Axis Z-`");
set_key_expression(HK_FREELOOK_INCREASE_FOV_Y, SHIFT + " & `Axis Z+`");
set_key_expression(HK_FREELOOK_DECREASE_FOV_Y, SHIFT + " & `Axis Z-`");
set_key_expression(HK_FREELOOK_DECREASE_SPEED, hotkey_string({"Shift", "1"}));
set_key_expression(HK_FREELOOK_INCREASE_SPEED, hotkey_string({"Shift", "2"}));
set_key_expression(HK_FREELOOK_RESET_SPEED, hotkey_string({"Shift", "F"}));
set_key_expression(HK_FREELOOK_UP, hotkey_string({"Shift", "E"}));
set_key_expression(HK_FREELOOK_DOWN, hotkey_string({"Shift", "Q"}));
set_key_expression(HK_FREELOOK_LEFT, hotkey_string({"Shift", "A"}));
set_key_expression(HK_FREELOOK_RIGHT, hotkey_string({"Shift", "D"}));
set_key_expression(HK_FREELOOK_ZOOM_IN, hotkey_string({"Shift", "W"}));
set_key_expression(HK_FREELOOK_ZOOM_OUT, hotkey_string({"Shift", "S"}));
set_key_expression(HK_FREELOOK_RESET, hotkey_string({"Shift", "R"}));
set_key_expression(HK_FREELOOK_INCREASE_FOV_X, hotkey_string({"Shift", "`Axis Z+`"}));
set_key_expression(HK_FREELOOK_DECREASE_FOV_X, hotkey_string({"Shift", "`Axis Z-`"}));
set_key_expression(HK_FREELOOK_INCREASE_FOV_Y, hotkey_string({"Shift", "`Axis Z+`"}));
set_key_expression(HK_FREELOOK_DECREASE_FOV_Y, hotkey_string({"Shift", "`Axis Z-`"}));

// Savestates
const std::string non_fmt = NON + " & `F{}`";
const std::string shift_fmt = SHIFT + " & `F{}`";
for (int i = 0; i < 8; i++)
{
set_key_expression(HK_LOAD_STATE_SLOT_1 + i, fmt::format(non_fmt, i + 1));
set_key_expression(HK_SAVE_STATE_SLOT_1 + i, fmt::format(shift_fmt, i + 1));
set_key_expression(HK_LOAD_STATE_SLOT_1 + i, fmt::format("F%d", i + 1));
set_key_expression(HK_SAVE_STATE_SLOT_1 + i,
hotkey_string({"Shift", fmt::format("F%d", i + 1)}));
}
set_key_expression(HK_UNDO_LOAD_STATE, NON + " & `F12`");
set_key_expression(HK_UNDO_SAVE_STATE, SHIFT + " & `F12`");
set_key_expression(HK_UNDO_LOAD_STATE, "F12");
set_key_expression(HK_UNDO_SAVE_STATE, hotkey_string({"Shift", "F12"}));
}
126 changes: 113 additions & 13 deletions Source/Core/DolphinQt/Config/Mapping/MappingCommon.cpp
Expand Up @@ -5,6 +5,7 @@
#include "DolphinQt/Config/Mapping/MappingCommon.h"

#include <tuple>
#include <vector>

#include <QApplication>
#include <QPushButton>
Expand All @@ -14,14 +15,23 @@

#include "DolphinQt/QtUtils/BlockUserInputFilter.h"
#include "InputCommon/ControlReference/ControlReference.h"
#include "InputCommon/ControllerInterface/Device.h"

#include "Common/Thread.h"

namespace MappingCommon
{
constexpr int INPUT_DETECT_TIME = 3000;
constexpr int OUTPUT_TEST_TIME = 2000;
constexpr auto INPUT_DETECT_INITIAL_TIME = std::chrono::seconds(3);
constexpr auto INPUT_DETECT_CONFIRMATION_TIME = std::chrono::milliseconds(500);
constexpr auto INPUT_DETECT_MAXIMUM_TIME = std::chrono::seconds(5);

constexpr auto OUTPUT_TEST_TIME = std::chrono::seconds(2);

// Pressing inputs at the same time will result in the & operator vs a hotkey expression.
constexpr auto HOTKEY_VS_CONJUNCION_THRESHOLD = std::chrono::milliseconds(50);

// Some devices (e.g. DS4) provide an analog and digital input for the trigger.
// We prefer just the analog input for simultaneous digital+analog input detections.
constexpr auto SPURIOUS_TRIGGER_COMBO_THRESHOLD = std::chrono::milliseconds(150);

QString GetExpressionForControl(const QString& control_name,
const ciface::Core::DeviceQualifier& control_device,
Expand Down Expand Up @@ -68,7 +78,11 @@ QString DetectExpression(QPushButton* button, ciface::Core::DeviceContainer& dev
// Avoid that the button press itself is registered as an event
Common::SleepCurrentThread(50);

const auto [device, input] = device_container.DetectInput(INPUT_DETECT_TIME, device_strings);
auto detections =
device_container.DetectInput(device_strings, INPUT_DETECT_INITIAL_TIME,
INPUT_DETECT_CONFIRMATION_TIME, INPUT_DETECT_MAXIMUM_TIME);

RemoveSpuriousTriggerCombinations(&detections);

const auto timer = new QTimer(button);

Expand All @@ -83,14 +97,7 @@ QString DetectExpression(QPushButton* button, ciface::Core::DeviceContainer& dev

button->setText(old_text);

if (!input)
return {};

ciface::Core::DeviceQualifier device_qualifier;
device_qualifier.FromDevice(device.get());

return MappingCommon::GetExpressionForControl(QString::fromStdString(input->GetName()),
device_qualifier, default_device, quote);
return BuildExpression(detections, default_device, quote);
}

void TestOutput(QPushButton* button, OutputReference* reference)
Expand All @@ -102,10 +109,103 @@ void TestOutput(QPushButton* button, OutputReference* reference)
QApplication::processEvents();

reference->State(1.0);
Common::SleepCurrentThread(OUTPUT_TEST_TIME);
std::this_thread::sleep_for(OUTPUT_TEST_TIME);
reference->State(0.0);

button->setText(old_text);
}

void RemoveSpuriousTriggerCombinations(
std::vector<ciface::Core::DeviceContainer::InputDetection>* detections)
{
const auto is_spurious = [&](auto& detection) {
return std::any_of(detections->begin(), detections->end(), [&](auto& d) {
// This is a suprious digital detection if a "smooth" (analog) detection is temporally near.
return &d != &detection && d.smoothness > 1 &&
abs(d.press_time - detection.press_time) < SPURIOUS_TRIGGER_COMBO_THRESHOLD;
});
};

detections->erase(std::remove_if(detections->begin(), detections->end(), is_spurious),
detections->end());
}

QString
BuildExpression(const std::vector<ciface::Core::DeviceContainer::InputDetection>& detections,
const ciface::Core::DeviceQualifier& default_device, Quote quote)
{
std::vector<const ciface::Core::DeviceContainer::InputDetection*> pressed_inputs;

QStringList alternations;

const auto get_control_expression = [&](auto& detection) {
// Return the parent-most name if there is one for better hotkey strings.
// Detection of L/R_Ctrl will be changed to just Ctrl.
// Users can manually map L_Ctrl if they so desire.
const auto input = (quote == Quote::On) ?
detection.device->GetParentMostInput(detection.input) :
detection.input;

ciface::Core::DeviceQualifier device_qualifier;
device_qualifier.FromDevice(detection.device.get());

return MappingCommon::GetExpressionForControl(QString::fromStdString(input->GetName()),
device_qualifier, default_device, quote);
};

bool new_alternation = false;

const auto handle_press = [&](auto& detection) {
pressed_inputs.emplace_back(&detection);
new_alternation = true;
};

const auto handle_release = [&]() {
if (!new_alternation)
return;

new_alternation = false;

QStringList alternation;
for (auto* input : pressed_inputs)
alternation.push_back(get_control_expression(*input));

const bool is_hotkey = pressed_inputs.size() >= 2 &&
(pressed_inputs[1]->press_time - pressed_inputs[0]->press_time) >
HOTKEY_VS_CONJUNCION_THRESHOLD;

if (is_hotkey)
{
alternations.push_back(QStringLiteral("@(%1)").arg(alternation.join(QLatin1Char('+'))));
}
else
{
alternation.sort();
alternations.push_back(alternation.join(QLatin1Char('&')));
}
};

for (auto& detection : detections)
{
// Remove since released inputs.
for (auto it = pressed_inputs.begin(); it != pressed_inputs.end();)
{
if (!((*it)->release_time > detection.press_time))
{
handle_release();
it = pressed_inputs.erase(it);
}
else
++it;
}

handle_press(detection);
}

handle_release();

alternations.removeDuplicates();
return alternations.join(QLatin1Char('|'));
}

} // namespace MappingCommon
13 changes: 7 additions & 6 deletions Source/Core/DolphinQt/Config/Mapping/MappingCommon.h
Expand Up @@ -7,16 +7,12 @@
#include <string>
#include <vector>

#include "InputCommon/ControllerInterface/Device.h"

class QString;
class OutputReference;
class QPushButton;

namespace ciface::Core
{
class DeviceContainer;
class DeviceQualifier;
} // namespace ciface::Core

namespace MappingCommon
{
enum class Quote
Expand All @@ -37,4 +33,9 @@ QString DetectExpression(QPushButton* button, ciface::Core::DeviceContainer& dev

void TestOutput(QPushButton* button, OutputReference* reference);

void RemoveSpuriousTriggerCombinations(std::vector<ciface::Core::DeviceContainer::InputDetection>*);

QString BuildExpression(const std::vector<ciface::Core::DeviceContainer::InputDetection>&,
const ciface::Core::DeviceQualifier& default_device, Quote quote);

} // namespace MappingCommon

0 comments on commit c64d41d

Please sign in to comment.