openMSX
MSXJoystick.cc
Go to the documentation of this file.
1#include "MSXJoystick.hh"
2
4#include "Event.hh"
5#include "IntegerSetting.hh"
6#include "JoystickManager.hh"
8#include "StateChange.hh"
10#include "serialize.hh"
11#include "serialize_meta.hh"
12#include "build-info.hh"
13
14#include "join.hh"
15#include "ranges.hh"
16#include "unreachable.hh"
17#include "xrange.hh"
18
19namespace openmsx {
20
21class MSXJoyState final : public StateChange
22{
23public:
24 MSXJoyState() = default; // for serialize
25 MSXJoyState(EmuTime::param time_, uint8_t id_,
26 uint8_t press_, uint8_t release_)
27 : StateChange(time_), id(id_)
28 , press(press_), release(release_) {}
29
30 [[nodiscard]] auto getId() const { return id; }
31 [[nodiscard]] auto getPress() const { return press; }
32 [[nodiscard]] auto getRelease() const { return release; }
33
34 template<typename Archive> void serialize(Archive& ar, unsigned /*version*/) {
35 ar.template serializeBase<StateChange>(*this);
36 ar.serialize("id", id,
37 "press", press,
38 "release", release);
39 }
40
41private:
42 uint8_t id, press, release;
43};
45
47{
48 auto buttons = joystickManager.getNumButtons(joyId);
49 if (!buttons) return {};
50
51 TclObject listA, listB;
52 auto joy = joyId.str();
53 for (auto b : xrange(*buttons)) {
54 ((b & 1) ? listB : listA).addListElement(tmpStrCat(joy, " button", b));
55 }
57 "UP", makeTclList(tmpStrCat(joy, " -axis1"), tmpStrCat(joy, " hat0 up")),
58 "DOWN", makeTclList(tmpStrCat(joy, " +axis1"), tmpStrCat(joy, " hat0 down")),
59 "LEFT", makeTclList(tmpStrCat(joy, " -axis0"), tmpStrCat(joy, " hat0 left")),
60 "RIGHT", makeTclList(tmpStrCat(joy, " +axis0"), tmpStrCat(joy, " hat0 right")),
61 "A", listA,
62 "B", listB);
63}
64
66 MSXEventDistributor& eventDistributor_,
67 StateChangeDistributor& stateChangeDistributor_,
68 JoystickManager& joystickManager_,
69 uint8_t id_)
70 : commandController(commandController_)
71 , eventDistributor(eventDistributor_)
72 , stateChangeDistributor(stateChangeDistributor_)
73 , joystickManager(joystickManager_)
74 , configSetting(commandController, tmpStrCat("msxjoystick", id_, "_config"),
75 "msxjoystick mapping configuration", getDefaultConfig(JoystickId(id_ - 1), joystickManager).getString())
76 , description(strCat("MSX joystick ", id_, ". Mapping is fully configurable."))
77 , id(id_)
78{
79 configSetting.setChecker([this](const TclObject& newValue) {
80 this->checkJoystickConfig(newValue); });
81 // fill in 'bindings'
82 checkJoystickConfig(configSetting.getValue());
83}
84
86{
87 if (isPluggedIn()) {
88 MSXJoystick::unplugHelper(EmuTime::dummy());
89 }
90}
91
92void MSXJoystick::checkJoystickConfig(const TclObject& newValue)
93{
94 std::array<std::vector<BooleanInput>, 6> newBindings;
95
96 auto& interp = commandController.getInterpreter();
97 unsigned n = newValue.getListLength(interp);
98 if (n & 1) {
99 throw CommandException("Need an even number of elements");
100 }
101 for (unsigned i = 0; i < n; i += 2) {
102 static constexpr std::array<std::string_view, 6> keys = {
103 // order is important!
104 "UP", "DOWN", "LEFT", "RIGHT", "A", "B"
105 };
106 std::string_view key = newValue.getListIndex(interp, i + 0).getString();
107 auto it = ranges::find(keys, key);
108 if (it == keys.end()) {
109 throw CommandException(
110 "Invalid key: must be one of ", join(keys, ", "));
111 }
112 auto idx = std::distance(keys.begin(), it);
113
114 TclObject value = newValue.getListIndex(interp, i + 1);
115 for (auto j : xrange(value.getListLength(interp))) {
116 std::string_view val = value.getListIndex(interp, j).getString();
117 auto bind = parseBooleanInput(val);
118 if (!bind) {
119 throw CommandException("Invalid binding: ", val);
120 }
121 newBindings[idx].push_back(*bind);
122 }
123 }
124
125 // only change current bindings when parsing was fully successful
126 ranges::copy(newBindings, bindings);
127}
128
129// Pluggable
130std::string_view MSXJoystick::getName() const
131{
132 switch (id) {
133 case 1: return "msxjoystick1";
134 case 2: return "msxjoystick2";
135 default: UNREACHABLE;
136 }
137}
138
139std::string_view MSXJoystick::getDescription() const
140{
141 return description;
142}
143
144void MSXJoystick::plugHelper(Connector& /*connector*/, EmuTime::param /*time*/)
145{
146 eventDistributor.registerEventListener(*this);
147 stateChangeDistributor.registerListener(*this);
148}
149
150void MSXJoystick::unplugHelper(EmuTime::param /*time*/)
151{
152 stateChangeDistributor.unregisterListener(*this);
153 eventDistributor.unregisterEventListener(*this);
154}
155
156
157// MSXJoystickDevice
158uint8_t MSXJoystick::read(EmuTime::param /*time*/)
159{
160 return pin8 ? 0x3F : status;
161}
162
163void MSXJoystick::write(uint8_t value, EmuTime::param /*time*/)
164{
165 pin8 = (value & 0x04) != 0;
166}
167
168
169// MSXEventListener
170void MSXJoystick::signalMSXEvent(const Event& event,
171 EmuTime::param time) noexcept
172{
173 uint8_t press = 0;
174 uint8_t release = 0;
175
176 auto getJoyDeadZone = [&](JoystickId joyId) {
177 const auto* setting = joystickManager.getJoyDeadZoneSetting(joyId);
178 return setting ? setting->getInt() : 0;
179 };
180 for (int i : xrange(6)) {
181 for (const auto& binding : bindings[i]) {
182 if (auto onOff = match(binding, event, getJoyDeadZone)) {
183 (*onOff ? press : release) |= 1 << i;
184 }
185 }
186 }
187
188 if (((status & ~press) | release) != status) {
189 stateChangeDistributor.distributeNew<MSXJoyState>(
190 time, id, press, release);
191 }
192}
193
194// StateChangeListener
195void MSXJoystick::signalStateChange(const StateChange& event)
196{
197 const auto* kjs = dynamic_cast<const MSXJoyState*>(&event);
198 if (!kjs) return;
199 if (kjs->getId() != id) return;
200
201 status = (status & ~kjs->getPress()) | kjs->getRelease();
202}
203
204void MSXJoystick::stopReplay(EmuTime::param time) noexcept
205{
206 uint8_t newStatus = JOY_UP | JOY_DOWN | JOY_LEFT | JOY_RIGHT |
207 JOY_BUTTONA | JOY_BUTTONB;
208 if (newStatus != status) {
209 uint8_t release = newStatus & ~status;
210 stateChangeDistributor.distributeNew<MSXJoyState>(
211 time, id, uint8_t(0), release);
212 }
213}
214
215
216template<typename Archive>
217void MSXJoystick::serialize(Archive& ar, unsigned /*version*/)
218{
219 ar.serialize("status", status);
220 if constexpr (Archive::IS_LOADER) {
221 if (isPluggedIn()) {
222 plugHelper(*getConnector(), EmuTime::dummy());
223 }
224 }
225 // no need to serialize 'pin8'
226}
229
230} // namespace openmsx
BaseSetting * setting
uintptr_t id
virtual Interpreter & getInterpreter()=0
std::string str() const
Definition JoystickId.hh:16
std::optional< unsigned > getNumButtons(JoystickId joyId) const
void registerEventListener(MSXEventListener &listener)
Registers a given object to receive certain events.
void unregisterEventListener(MSXEventListener &listener)
Unregisters a previously registered event listener.
auto getPress() const
MSXJoyState(EmuTime::param time_, uint8_t id_, uint8_t press_, uint8_t release_)
void serialize(Archive &ar, unsigned)
auto getRelease() const
static TclObject getDefaultConfig(JoystickId joyId, const JoystickManager &joystickManager)
~MSXJoystick() override
MSXJoystick(CommandController &commandController, MSXEventDistributor &eventDistributor, StateChangeDistributor &stateChangeDistributor, JoystickManager &joystickManager, uint8_t id)
void serialize(Archive &ar, unsigned version)
bool isPluggedIn() const
Returns true if this pluggable is currently plugged into a connector.
Definition Pluggable.hh:49
Connector * getConnector() const
Get the connector this Pluggable is plugged into.
Definition Pluggable.hh:43
void setChecker(std::function< void(TclObject &)> checkFunc_)
Set value-check-callback.
Definition Setting.hh:146
const TclObject & getValue() const final
Gets the current value of this setting as a TclObject.
Definition Setting.hh:134
void registerListener(StateChangeListener &listener)
(Un)registers the given object to receive state change events.
void unregisterListener(StateChangeListener &listener)
Base class for all external MSX state changing events.
unsigned getListLength(Interpreter &interp) const
Definition TclObject.cc:155
TclObject getListIndex(Interpreter &interp, unsigned index) const
Definition TclObject.cc:173
zstring_view getString() const
Definition TclObject.cc:141
detail::Joiner< Collection, Separator > join(Collection &&col, Separator &&sep)
Definition join.hh:60
This file implemented 3 utility functions:
Definition Autofire.cc:11
std::optional< bool > match(const BooleanInput &binding, const Event &event, function_ref< int(JoystickId)> getJoyDeadZone)
std::optional< BooleanInput > parseBooleanInput(std::string_view text)
std::variant< KeyUpEvent, KeyDownEvent, MouseMotionEvent, MouseButtonUpEvent, MouseButtonDownEvent, MouseWheelEvent, JoystickAxisMotionEvent, JoystickHatEvent, JoystickButtonUpEvent, JoystickButtonDownEvent, OsdControlReleaseEvent, OsdControlPressEvent, WindowEvent, TextEvent, FileDropEvent, QuitEvent, FinishFrameEvent, CliCommandEvent, GroupEvent, BootEvent, FrameDrawnEvent, BreakEvent, SwitchRendererEvent, TakeReverseSnapshotEvent, AfterTimedEvent, MachineLoadedEvent, MachineActivatedEvent, MachineDeactivatedEvent, MidiInReaderEvent, MidiInWindowsEvent, MidiInCoreMidiEvent, MidiInCoreMidiVirtualEvent, MidiInALSAEvent, Rs232TesterEvent, Rs232NetEvent, ImGuiDelayedActionEvent, ImGuiActiveEvent > Event
Definition Event.hh:446
TclObject makeTclList(Args &&... args)
Definition TclObject.hh:293
auto copy(InputRange &&range, OutputIter out)
Definition ranges.hh:252
auto find(InputRange &&range, const T &value)
Definition ranges.hh:162
constexpr auto keys(Map &&map)
Definition view.hh:525
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
#define REGISTER_POLYMORPHIC_INITIALIZER(BASE, CLASS, NAME)
#define REGISTER_POLYMORPHIC_CLASS(BASE, CLASS, NAME)
std::string strCat()
Definition strCat.hh:703
TemporaryString tmpStrCat(Ts &&... ts)
Definition strCat.hh:742
#define UNREACHABLE
constexpr auto xrange(T e)
Definition xrange.hh:132