File: MappingCommon.cpp

package info (click to toggle)
dolphin-emu 2503%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 111,624 kB
  • sloc: cpp: 787,747; ansic: 217,914; xml: 31,400; python: 4,226; yacc: 3,985; javascript: 2,430; makefile: 777; asm: 726; sh: 281; pascal: 257; perl: 97; objc: 75
file content (167 lines) | stat: -rw-r--r-- 5,246 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// Copyright 2022 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "InputCommon/ControllerInterface/MappingCommon.h"

#include <algorithm>
#include <chrono>
#include <string>
#include <vector>

#include <fmt/format.h>
#include <fmt/ranges.h>

#include "Common/StringUtil.h"
#include "InputCommon/ControllerInterface/CoreDevice.h"

namespace ciface::MappingCommon
{
// 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);

std::string GetExpressionForControl(const std::string& control_name,
                                    const ciface::Core::DeviceQualifier& control_device,
                                    const ciface::Core::DeviceQualifier& default_device,
                                    Quote quote)
{
  std::string expr;

  // non-default device
  if (control_device != default_device)
  {
    expr += control_device.ToString();
    expr += ':';
  }

  // append the control name
  expr += control_name;

  if (quote == Quote::On)
  {
    // If our expression contains any non-alpha characters
    // we should quote it
    if (!std::ranges::all_of(expr, Common::IsAlpha))
      expr = fmt::format("`{}`", expr);
  }

  return expr;
}

std::string BuildExpression(const Core::InputDetector::Results& detections,
                            const ciface::Core::DeviceQualifier& default_device, Quote quote)
{
  std::vector<const Core::InputDetector::Detection*> pressed_inputs;

  std::vector<std::string> 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(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;

    std::vector<std::string> 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(fmt::format("@({})", fmt::join(alternation, "+")));
    }
    else
    {
      std::ranges::sort(alternation);
      alternations.push_back(fmt::to_string(fmt::join(alternation, "&")));
    }
  };

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

    handle_press(detection);
  }

  handle_release();

  // Remove duplicates
  std::ranges::sort(alternations);
  const auto unique_result = std::ranges::unique(alternations);
  alternations.erase(unique_result.begin(), unique_result.end());

  return fmt::to_string(fmt::join(alternations, "|"));
}

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

  std::erase_if(*detections, is_spurious);
}

void RemoveDetectionsAfterTimePoint(Core::InputDetector::Results* results,
                                    Core::DeviceContainer::Clock::time_point after)
{
  const auto is_after_time = [&](const Core::InputDetector::Detection& detection) {
    return detection.release_time.value_or(after) >= after;
  };

  std::erase_if(*results, is_after_time);
}

bool ContainsCompleteDetection(const Core::InputDetector::Results& results)
{
  return std::ranges::any_of(results, [](const Core::InputDetector::Detection& detection) {
    return detection.release_time.has_value();
  });
}

}  // namespace ciface::MappingCommon