30#ifdef SDL_JOYSTICK_DISABLED
31 (void)eventDistributor;
32 (void)stateChangeDistributor;
33 (void)commandController;
37 for (
auto i :
xrange(SDL_NumJoysticks())) {
38 if (SDL_Joystick* joystick = SDL_JoystickOpen(i)) {
47 std::make_unique<Joystick>(
49 stateChangeDistributor,
64 JoyState(EmuTime::param time_,
int joyNum_, uint8_t press_, uint8_t release_)
66 , joyNum(joyNum_), press(press_), release(release_)
68 assert((press != 0) || (release != 0));
69 assert((press & release) == 0);
72 [[nodiscard]] uint8_t
getPress()
const {
return press; }
73 [[nodiscard]] uint8_t
getRelease()
const {
return release; }
75 template<
typename Archive>
void serialize(Archive& ar,
unsigned )
77 ar.template serializeBase<StateChange>(*
this);
78 ar.serialize(
"joyNum", joyNum,
84 uint8_t press, release;
89#ifndef SDL_JOYSTICK_DISABLED
96 for (
unsigned i = 0; i < n; i += 2) {
99 if (key !=
one_of(
"A",
"B",
"LEFT",
"RIGHT",
"UP",
"DOWN")) {
100 throw CommandException(
101 "Invalid MSX joystick action: must be one of "
102 "'A', 'B', 'LEFT', 'RIGHT', 'UP', 'DOWN'.");
104 for (
auto j :
xrange(value.getListLength(interp))) {
105 std::string_view host = value.getListIndex(interp, j).getString();
106 if (!host.starts_with(
"button") &&
107 !host.starts_with(
"+axis") &&
108 !host.starts_with(
"-axis") &&
109 !host.starts_with(
"L_hat") &&
110 !host.starts_with(
"R_hat") &&
111 !host.starts_with(
"U_hat") &&
112 !host.starts_with(
"D_hat")) {
113 throw CommandException(
114 "Invalid host joystick action: must be "
115 "one of 'button<N>', '+axis<N>', '-axis<N>', "
116 "'L_hat<N>', 'R_hat<N>', 'U_hat<N>', 'D_hat<N>'");
122[[nodiscard]]
static std::string getJoystickName(
int joyNum)
124 return strCat(
"joystick", narrow<char>(
'1' + joyNum));
127[[nodiscard]]
static TclObject getConfigValue(SDL_Joystick* joystick)
129 TclObject listA, listB;
134 listB.addListElement(button);
136 listA.addListElement(button);
140 value.addDictKeyValues(
"LEFT",
makeTclList(
"-axis0",
"L_hat0"),
157 SDL_Joystick* joystick_)
158 : eventDistributor(eventDistributor_)
159 , stateChangeDistributor(stateChangeDistributor_)
160 , joystick(joystick_)
161 , joyNum(SDL_JoystickInstanceID(joystick_))
162 , deadSetting(globalSettings.getJoyDeadZoneSetting(joyNum))
163 , name(getJoystickName(joyNum))
164 , desc(SDL_JoystickName(joystick_))
165 , configSetting(commandController,
tmpStrCat(name,
"_config"),
166 "joystick configuration", getConfigValue(joystick).getString())
170 checkJoystickConfig(interp, newValue); });
179 SDL_JoystickClose(joystick);
200 status = calcState();
203void Joystick::plugHelper2()
219 return pin8 ? 0x3F : status;
224 pin8 = (value & 0x04) != 0;
227uint8_t Joystick::calcState()
232 int threshold = (deadSetting.
getInt() * 32768) / 100;
234 const auto& dict = configSetting.
getValue();
235 if (getState(interp, dict,
"A" , threshold)) result &= ~
JOY_BUTTONA;
236 if (getState(interp, dict,
"B" , threshold)) result &= ~
JOY_BUTTONB;
237 if (getState(interp, dict,
"UP" , threshold)) result &= ~
JOY_UP;
238 if (getState(interp, dict,
"DOWN" , threshold)) result &= ~
JOY_DOWN;
239 if (getState(interp, dict,
"LEFT" , threshold)) result &= ~
JOY_LEFT;
240 if (getState(interp, dict,
"RIGHT", threshold)) result &= ~
JOY_RIGHT;
245bool Joystick::getState(Interpreter& interp,
const TclObject& dict,
246 std::string_view key,
int threshold)
249 const auto& list = dict.getDictValue(interp, key);
250 for (
auto i :
xrange(list.getListLength(interp))) {
251 const auto& elem = list.getListIndex(interp, i).getString();
252 if (elem.starts_with(
"button")) {
253 if (
auto n = StringOp::stringToBase<10, int>(elem.substr(6))) {
258 }
else if (elem.starts_with(
"+axis")) {
259 if (
auto n = StringOp::stringToBase<10, int>(elem.substr(5))) {
260 if (SDL_JoystickGetAxis(joystick, *n) > threshold) {
264 }
else if (elem.starts_with(
"-axis")) {
265 if (
auto n = StringOp::stringToBase<10, int>(elem.substr(5))) {
266 if (SDL_JoystickGetAxis(joystick, *n) < -threshold) {
270 }
else if (elem.starts_with(
"L_hat")) {
271 if (
auto n = StringOp::stringToBase<10, int>(elem.substr(5))) {
272 if (SDL_JoystickGetHat(joystick, *n) & SDL_HAT_LEFT) {
276 }
else if (elem.starts_with(
"R_hat")) {
277 if (
auto n = StringOp::stringToBase<10, int>(elem.substr(5))) {
278 if (SDL_JoystickGetHat(joystick, *n) & SDL_HAT_RIGHT) {
282 }
else if (elem.starts_with(
"U_hat")) {
283 if (
auto n = StringOp::stringToBase<10, int>(elem.substr(5))) {
284 if (SDL_JoystickGetHat(joystick, *n) & SDL_HAT_UP) {
288 }
else if (elem.starts_with(
"D_hat")) {
289 if (
auto n = StringOp::stringToBase<10, int>(elem.substr(5))) {
290 if (SDL_JoystickGetHat(joystick, *n) & SDL_HAT_DOWN) {
304void Joystick::signalMSXEvent(
const Event& event,
305 EmuTime::param time)
noexcept
307 const auto* joyEvent = get_if<JoystickEvent>(event);
308 if (!joyEvent)
return;
312 if (joyEvent->getJoystick() != joyNum)
return;
321 createEvent(time, calcState());
324void Joystick::createEvent(EmuTime::param time, uint8_t newStatus)
326 uint8_t diff = status ^ newStatus;
332 uint8_t press = status & diff;
333 uint8_t release = newStatus & diff;
335 time, joyNum, press, release);
339void Joystick::signalStateChange(
const StateChange& event)
341 const auto* js =
dynamic_cast<const JoyState*
>(&event);
349 if (js->getJoystick() != joyNum)
return;
351 status = (status & ~js->getPress()) | js->getRelease();
354void Joystick::stopReplay(EmuTime::param time)
noexcept
356 createEvent(time, calcState());
362template<
typename Archive>
365 if (ar.versionAtLeast(version, 2)) {
366 ar.serialize(
"status", status);
368 if constexpr (Archive::IS_LOADER) {
virtual Interpreter & getInterpreter()=0
Represents something you can plug devices into.
This class contains settings that are used by several other class (including some singletons).
int getInt() const noexcept
uint8_t getRelease() const
void serialize(Archive &ar, unsigned)
JoyState(EmuTime::param time_, int joyNum_, uint8_t press_, uint8_t release_)
static constexpr uint8_t JOY_BUTTONA
static constexpr uint8_t JOY_RIGHT
static constexpr uint8_t JOY_LEFT
static constexpr uint8_t JOY_DOWN
static constexpr uint8_t JOY_UP
static constexpr uint8_t JOY_BUTTONB
Uses an SDL joystick to emulate an MSX joystick.
void write(uint8_t value, EmuTime::param time) override
Write a value to the joystick device.
std::string_view getName() const override
Name used to identify this pluggable.
void unplugHelper(EmuTime::param time) override
std::string_view getDescription() const override
Description for this pluggable.
uint8_t read(EmuTime::param time) override
Read from the joystick device.
void plugHelper(Connector &connector, EmuTime::param time) override
static void registerAll(MSXEventDistributor &eventDistributor, StateChangeDistributor &stateChangeDistributor, CommandController &commandController, GlobalSettings &globalSettings, PluggingController &controller)
Register all available SDL joysticks.
void serialize(Archive &ar, unsigned version)
Joystick(MSXEventDistributor &eventDistributor, StateChangeDistributor &stateChangeDistributor, CommandController &commandController, GlobalSettings &globalSettings, SDL_Joystick *joystick)
void registerEventListener(MSXEventListener &listener)
Registers a given object to receive certain events.
void unregisterEventListener(MSXEventListener &listener)
Unregisters a previously registered event listener.
Thrown when a plug action fails.
bool isPluggedIn() const
Returns true if this pluggable is currently plugged into a connector.
Central administration of Connectors and Pluggables.
void registerPluggable(std::unique_ptr< Pluggable > pluggable)
Add a Pluggable to the registry.
void setChecker(std::function< void(TclObject &)> checkFunc_)
Set value-check-callback.
Interpreter & getInterpreter() const
const TclObject & getValue() const final
Gets the current value of this setting as a TclObject.
void registerListener(StateChangeListener &listener)
(Un)registers the given object to receive state change events.
void distributeNew(EmuTime::param time, Args &&...args)
Deliver the event to all registered listeners MSX input devices should call the distributeNew() versi...
void unregisterListener(StateChangeListener &listener)
Base class for all external MSX state changing events.
unsigned getListLength(Interpreter &interp) const
TclObject getListIndex(Interpreter &interp, unsigned index) const
zstring_view getString() const
This file implemented 3 utility functions:
REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, CassettePlayer, "CassettePlayer")
REGISTER_POLYMORPHIC_CLASS(StateChange, AutofireStateChange, "AutofireStateChange")
TclObject makeTclList(Args &&... args)
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
TemporaryString tmpStrCat(Ts &&... ts)
std::string strCat(Ts &&...ts)
constexpr auto xrange(T e)