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