openMSX
Mouse.cc
Go to the documentation of this file.
1 #include "Mouse.hh"
2 #include "MSXEventDistributor.hh"
4 #include "Event.hh"
5 #include "StateChange.hh"
6 #include "Clock.hh"
7 #include "serialize.hh"
8 #include "serialize_meta.hh"
9 #include "unreachable.hh"
10 #include <SDL.h>
11 #include <algorithm>
12 
13 namespace openmsx {
14 
15 constexpr int TRESHOLD = 2;
16 constexpr int SCALE = 2;
17 constexpr int PHASE_XHIGH = 0;
18 constexpr int PHASE_XLOW = 1;
19 constexpr int PHASE_YHIGH = 2;
20 constexpr int PHASE_YLOW = 3;
21 constexpr int STROBE = 0x04;
22 
23 
24 class MouseState final : public StateChange
25 {
26 public:
27  MouseState() = default; // for serialize
28  MouseState(EmuTime::param time_, int deltaX_, int deltaY_,
29  byte press_, byte release_)
30  : StateChange(time_)
31  , deltaX(deltaX_), deltaY(deltaY_)
32  , press(press_), release(release_) {}
33  [[nodiscard]] int getDeltaX() const { return deltaX; }
34  [[nodiscard]] int getDeltaY() const { return deltaY; }
35  [[nodiscard]] byte getPress() const { return press; }
36  [[nodiscard]] byte getRelease() const { return release; }
37  template<typename Archive> void serialize(Archive& ar, unsigned /*version*/)
38  {
39  ar.template serializeBase<StateChange>(*this);
40  ar.serialize("deltaX", deltaX,
41  "deltaY", deltaY,
42  "press", press,
43  "release", release);
44  }
45 private:
46  int deltaX, deltaY;
47  byte press, release;
48 };
49 
51 
52 Mouse::Mouse(MSXEventDistributor& eventDistributor_,
53  StateChangeDistributor& stateChangeDistributor_)
54  : eventDistributor(eventDistributor_)
55  , stateChangeDistributor(stateChangeDistributor_)
56  , lastTime(EmuTime::zero())
57  , phase(PHASE_YLOW)
58  , xrel(0)
59  , yrel(0)
60  , curxrel(0)
61  , curyrel(0)
62  , absHostX(0)
63  , absHostY(0)
64  , status(JOY_BUTTONA | JOY_BUTTONB)
65  , mouseMode(true)
66 {
67 }
68 
70 {
71  if (isPluggedIn()) {
72  Mouse::unplugHelper(EmuTime::dummy());
73  }
74 }
75 
76 
77 // Pluggable
78 std::string_view Mouse::getName() const
79 {
80  return "mouse";
81 }
82 
83 std::string_view Mouse::getDescription() const
84 {
85  return "MSX mouse";
86 }
87 
88 void Mouse::plugHelper(Connector& /*connector*/, EmuTime::param time)
89 {
90  if (SDL_GetMouseState(nullptr, nullptr) & SDL_BUTTON(SDL_BUTTON_LEFT)) {
91  // left mouse button pressed, joystick emulation mode
92  mouseMode = false;
93  } else {
94  // not pressed, mouse mode
95  mouseMode = true;
96  lastTime = time;
97  }
98  plugHelper2();
99 }
100 
101 void Mouse::plugHelper2()
102 {
103  eventDistributor.registerEventListener(*this);
104  stateChangeDistributor.registerListener(*this);
105 }
106 
107 void Mouse::unplugHelper(EmuTime::param /*time*/)
108 {
109  stateChangeDistributor.unregisterListener(*this);
110  eventDistributor.unregisterEventListener(*this);
111 }
112 
113 
114 // JoystickDevice
115 byte Mouse::read(EmuTime::param /*time*/)
116 {
117  if (mouseMode) {
118  switch (phase) {
119  case PHASE_XHIGH:
120  return ((xrel >> 4) & 0x0F) | status;
121  case PHASE_XLOW:
122  return (xrel & 0x0F) | status;
123  case PHASE_YHIGH:
124  return ((yrel >> 4) & 0x0F) | status;
125  case PHASE_YLOW:
126  return (yrel & 0x0F) | status;
127  default:
128  UNREACHABLE; return 0;
129  }
130  } else {
131  emulateJoystick();
132  return status;
133  }
134 }
135 
136 void Mouse::emulateJoystick()
137 {
138  status &= ~(JOY_UP | JOY_DOWN | JOY_LEFT | JOY_RIGHT);
139 
140  int deltaX = curxrel; curxrel = 0;
141  int deltaY = curyrel; curyrel = 0;
142  int absX = (deltaX > 0) ? deltaX : -deltaX;
143  int absY = (deltaY > 0) ? deltaY : -deltaY;
144 
145  if ((absX < TRESHOLD) && (absY < TRESHOLD)) {
146  return;
147  }
148 
149  // tan(pi/8) ~= 5/12
150  if (deltaX > 0) {
151  if (deltaY > 0) {
152  if ((12 * absX) > (5 * absY)) {
153  status |= JOY_RIGHT;
154  }
155  if ((12 * absY) > (5 * absX)) {
156  status |= JOY_DOWN;
157  }
158  } else {
159  if ((12 * absX) > (5 * absY)) {
160  status |= JOY_RIGHT;
161  }
162  if ((12 * absY) > (5 * absX)) {
163  status |= JOY_UP;
164  }
165  }
166  } else {
167  if (deltaY > 0) {
168  if ((12 * absX) > (5 * absY)) {
169  status |= JOY_LEFT;
170  }
171  if ((12 * absY) > (5 * absX)) {
172  status |= JOY_DOWN;
173  }
174  } else {
175  if ((12 * absX) > (5 * absY)) {
176  status |= JOY_LEFT;
177  }
178  if ((12 * absY) > (5 * absX)) {
179  status |= JOY_UP;
180  }
181  }
182  }
183 }
184 
185 void Mouse::write(byte value, EmuTime::param time)
186 {
187  if (mouseMode) {
188  // TODO figure out the exact timeout value. Is there even such
189  // an exact value or can it vary between different mouse
190  // models?
191  //
192  // Initially we used a timeout of 1 full second. This caused bug
193  // [3520394] Mouse behaves badly (unusable) in HiBrid
194  // Slightly lowering the value to around 0.94s was already
195  // enough to fix that bug. Later we found that to make FRS's
196  // joytest program work we need a value that is less than the
197  // duration of one (NTSC) frame. See bug
198  // #474 Mouse doesn't work properly on Joytest v2.2
199  // We still don't know the exact value that an actual MSX mouse
200  // uses, but 1.5ms is also the timeout value that is used for
201  // JoyMega, so it seems like a reasonable value.
202  if ((time - lastTime) > EmuDuration::usec(1500)) {
203  phase = PHASE_YLOW;
204  }
205  lastTime = time;
206 
207  switch (phase) {
208  case PHASE_XHIGH:
209  if ((value & STROBE) == 0) phase = PHASE_XLOW;
210  break;
211  case PHASE_XLOW:
212  if ((value & STROBE) != 0) phase = PHASE_YHIGH;
213  break;
214  case PHASE_YHIGH:
215  if ((value & STROBE) == 0) phase = PHASE_YLOW;
216  break;
217  case PHASE_YLOW:
218  if ((value & STROBE) != 0) {
219  phase = PHASE_XHIGH;
220 #if 0
221  // Real MSX mice don't have overflow protection,
222  // verified on a Philips SBC3810 MSX mouse.
223  xrel = curxrel; yrel = curyrel;
224  curxrel = 0; curyrel = 0;
225 #else
226  // Nevertheless we do emulate it here. See
227  // sdsnatcher's post of 30 aug 2018 for a
228  // motivation for this difference:
229  // https://github.com/openMSX/openMSX/issues/892
230  xrel = std::clamp(curxrel, -127, 127);
231  yrel = std::clamp(curyrel, -127, 127);
232  curxrel -= xrel;
233  curyrel -= yrel;
234 #endif
235  }
236  break;
237  }
238  } else {
239  // ignore
240  }
241 }
242 
243 
244 // MSXEventListener
245 void Mouse::signalMSXEvent(const Event& event, EmuTime::param time) noexcept
246 {
248  [&](const MouseMotionEvent& e) {
249  if (e.getX() || e.getY()) {
250  // note: X/Y are negated, do this already in this
251  // routine to keep replays bw-compat. In a new
252  // savestate version it may (or may not) be cleaner
253  // to perform this operation closer to the MSX code.
254  createMouseStateChange(time, -e.getX(), -e.getY(), 0, 0);
255  }
256  },
257  [&](const MouseButtonDownEvent& e) {
258  switch (e.getButton()) {
260  createMouseStateChange(time, 0, 0, JOY_BUTTONA, 0);
261  break;
263  createMouseStateChange(time, 0, 0, JOY_BUTTONB, 0);
264  break;
265  default:
266  // ignore other buttons
267  break;
268  }
269  },
270  [&](const MouseButtonUpEvent& e) {
271  switch (e.getButton()) {
273  createMouseStateChange(time, 0, 0, 0, JOY_BUTTONA);
274  break;
276  createMouseStateChange(time, 0, 0, 0, JOY_BUTTONB);
277  break;
278  default:
279  // ignore other buttons
280  break;
281  }
282  },
283  [](const EventBase&) { /*ignore*/ }
284  }, event);
285 }
286 
287 void Mouse::createMouseStateChange(
288  EmuTime::param time, int deltaX, int deltaY, byte press, byte release)
289 {
290  stateChangeDistributor.distributeNew<MouseState>(
291  time, deltaX, deltaY, press, release);
292 }
293 
294 void Mouse::signalStateChange(const StateChange& event)
295 {
296  const auto* ms = dynamic_cast<const MouseState*>(&event);
297  if (!ms) return;
298 
299  // This is almost the same as
300  // relMsxXY = ms->getDeltaXY() / SCALE
301  // except that it doesn't accumulate rounding errors
302  int oldMsxX = absHostX / SCALE;
303  int oldMsxY = absHostY / SCALE;
304  absHostX += ms->getDeltaX();
305  absHostY += ms->getDeltaY();
306  int newMsxX = absHostX / SCALE;
307  int newMsxY = absHostY / SCALE;
308  int relMsxX = newMsxX - oldMsxX;
309  int relMsxY = newMsxY - oldMsxY;
310 
311  // Verified with a real MSX-mouse (Philips SBC3810):
312  // this value is not clipped to -128 .. 127.
313  curxrel += relMsxX;
314  curyrel += relMsxY;
315  status = (status & ~ms->getPress()) | ms->getRelease();
316 }
317 
318 void Mouse::stopReplay(EmuTime::param time) noexcept
319 {
320  // TODO read actual host mouse button state
321  int dx = 0 - curxrel;
322  int dy = 0 - curyrel;
323  byte release = (JOY_BUTTONA | JOY_BUTTONB) & ~status;
324  if ((dx != 0) || (dy != 0) || (release != 0)) {
325  createMouseStateChange(time, dx, dy, 0, release);
326  }
327 }
328 
329 // version 1: Initial version, the variables curxrel, curyrel and status were
330 // not serialized.
331 // version 2: Also serialize the above variables, this is required for
332 // record/replay, see comment in Keyboard.cc for more details.
333 // version 3: variables '(cur){x,y}rel' are scaled to MSX coordinates
334 // version 4: simplified type of 'lastTime' from Clock<> to EmuTime
335 template<typename Archive>
336 void Mouse::serialize(Archive& ar, unsigned version)
337 {
338  if constexpr (Archive::IS_LOADER) {
339  if (isPluggedIn()) {
340  // Do this early, because if something goes wrong while loading
341  // some state below, then unplugHelper() gets called and that
342  // will assert when plugHelper2() wasn't called yet.
343  plugHelper2();
344  }
345  }
346 
347  if (ar.versionBelow(version, 4)) {
348  assert(Archive::IS_LOADER);
349  Clock<1000> tmp(EmuTime::zero());
350  ar.serialize("lastTime", tmp);
351  lastTime = tmp.getTime();
352  } else {
353  ar.serialize("lastTime", lastTime);
354  }
355  ar.serialize("faze", phase, // TODO fix spelling if there's ever a need
356  // to bump the serialization verion
357  "xrel", xrel,
358  "yrel", yrel,
359  "mouseMode", mouseMode);
360  if (ar.versionAtLeast(version, 2)) {
361  ar.serialize("curxrel", curxrel,
362  "curyrel", curyrel,
363  "status", status);
364  }
365  if (ar.versionBelow(version, 3)) {
366  xrel /= SCALE;
367  yrel /= SCALE;
368  curxrel /= SCALE;
369  curyrel /= SCALE;
370 
371  }
372  // no need to serialize absHostX,Y
373 }
376 
377 } // namespace openmsx
Represents a clock with a fixed frequency.
Definition: Clock.hh:19
constexpr EmuTime::param getTime() const
Gets the time at which the last clock tick occurred.
Definition: Clock.hh:46
static constexpr EmuDuration usec(unsigned x)
Definition: EmuDuration.hh:44
static constexpr int JOY_RIGHT
static constexpr int JOY_LEFT
static constexpr int JOY_DOWN
static constexpr int JOY_UP
void registerEventListener(MSXEventListener &listener)
Registers a given object to receive certain events.
void unregisterEventListener(MSXEventListener &listener)
Unregisters a previously registered event listener.
static constexpr unsigned RIGHT
Definition: Event.hh:139
static constexpr unsigned LEFT
Definition: Event.hh:137
void serialize(Archive &ar, unsigned)
Definition: Mouse.cc:37
MouseState(EmuTime::param time_, int deltaX_, int deltaY_, byte press_, byte release_)
Definition: Mouse.cc:28
int getDeltaX() const
Definition: Mouse.cc:33
byte getPress() const
Definition: Mouse.cc:35
int getDeltaY() const
Definition: Mouse.cc:34
byte getRelease() const
Definition: Mouse.cc:36
Mouse(MSXEventDistributor &eventDistributor, StateChangeDistributor &stateChangeDistributor)
Definition: Mouse.cc:52
~Mouse() override
Definition: Mouse.cc:69
void serialize(Archive &ar, unsigned version)
Definition: Mouse.cc:336
bool isPluggedIn() const
Returns true if this pluggable is currently plugged into a connector.
Definition: Pluggable.hh:49
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.
Definition: StateChange.hh:20
constexpr double e
Definition: Math.hh:18
constexpr vecN< N, T > clamp(const vecN< N, T > &x, const vecN< N, T > &minVal, const vecN< N, T > &maxVal)
Definition: gl_vec.hh:292
This file implemented 3 utility functions:
Definition: Autofire.cc:9
constexpr int PHASE_YLOW
Definition: Mouse.cc:20
constexpr int PHASE_XHIGH
Definition: Mouse.cc:17
REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, CassettePlayer, "CassettePlayer")
auto visit(Visitor &&visitor, const Event &event)
Definition: Event.hh:652
constexpr int SCALE
Definition: ArkanoidPad.cc:24
constexpr int TRESHOLD
Definition: Mouse.cc:15
REGISTER_POLYMORPHIC_CLASS(StateChange, AutofireStateChange, "AutofireStateChange")
constexpr int STROBE
Definition: Mouse.cc:21
constexpr int PHASE_YHIGH
Definition: Mouse.cc:19
constexpr int PHASE_XLOW
Definition: Mouse.cc:18
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
Definition: serialize.hh:1009
#define UNREACHABLE
Definition: unreachable.hh:38