openMSX
InputEventGenerator.cc
Go to the documentation of this file.
1 #include "InputEventGenerator.hh"
2 #include "EventDistributor.hh"
3 #include "InputEvents.hh"
4 #include "IntegerSetting.hh"
5 #include "GlobalSettings.hh"
6 #include "Keys.hh"
7 #include "checked_cast.hh"
8 #include "outer.hh"
9 #include "unreachable.hh"
10 #include "utf8_unchecked.hh"
11 #include "build-info.hh"
12 #include <cassert>
13 #include <memory>
14 
15 using std::string;
16 using std::vector;
17 using std::make_shared;
18 
19 namespace openmsx {
20 
21 bool InputEventGenerator::androidButtonA = false;
22 bool InputEventGenerator::androidButtonB = false;
23 
25  EventDistributor& eventDistributor_,
26  GlobalSettings& globalSettings_)
27  : eventDistributor(eventDistributor_)
28  , globalSettings(globalSettings_)
29  , grabInput(
30  commandController, "grabinput",
31  "This setting controls if openMSX takes over mouse and keyboard input",
32  false, Setting::DONT_SAVE)
33  , escapeGrabCmd(commandController)
34  , escapeGrabState(ESCAPE_GRAB_WAIT_CMD)
35  , keyRepeat(false)
36 {
37  setGrabInput(grabInput.getBoolean());
38  grabInput.attach(*this);
39  eventDistributor.registerEventListener(OPENMSX_FOCUS_EVENT, *this);
40 
41  osdControlButtonsState = unsigned(~0); // 0 is pressed, 1 is released
42 
43 #ifndef SDL_JOYSTICK_DISABLED
44  SDL_JoystickEventState(SDL_ENABLE); // joysticks generate events
45 #endif
46 }
47 
49 {
50  eventDistributor.unregisterEventListener(OPENMSX_FOCUS_EVENT, *this);
51  grabInput.detach(*this);
52 }
53 
55 {
56  // SDL bug workaround
57  if (!SDL_WasInit(SDL_INIT_VIDEO)) {
58  SDL_Delay(100);
59  }
60 
61  if (SDL_WaitEvent(nullptr)) {
62  poll();
63  }
64 }
65 
67 {
68  // Heuristic to emulate the old SDL1 behavior:
69  //
70  // SDL1 had a unicode field on each KEYDOWN event. In SDL2 that
71  // information is moved to the (new) SDL_TEXTINPUT events.
72  //
73  // Though our MSX keyboard emulation code needs to relate KEYDOWN
74  // events with the associated unicode. We try to mimic this by the
75  // following heuristic:
76  // When two successive events in a single batch (so, the same
77  // invocation of poll()) are KEYDOWN followed by TEXTINPUT, then copy
78  // the unicode (of the first character) of the TEXT event to the
79  // KEYDOWN event.
80  // Implementing this requires a lookahead of 1 event. So the code below
81  // deals with a 'current' and a 'previous' event, and keeps track of
82  // whether the previous event is still pending (not yet processed).
83  //
84  // In a previous version we also added the constraint that these two
85  // consecutive events must have the same timestamp, but that had mixed
86  // results:
87  // - on Linux it worked fine
88  // - on Windows the timestamps sometimes did not match
89  // - on Mac the timestamps mostly did not match
90  // So we removed this constraint.
91  //
92  // We also split SDL_TEXTINPUT events into (possibly) multiple KEYDOWN
93  // events because a single event type makes it easier to handle higher
94  // priority listeners that can block the event for lower priority
95  // listener (console > hotkey > msx).
96 
97  SDL_Event event1, event2;
98  auto* prev = &event1;
99  auto* curr = &event2;
100  bool pending = false;
101 
102  while (SDL_PollEvent(curr)) {
103  if (pending) {
104  pending = false;
105  if ((prev->type == SDL_KEYDOWN) && (curr->type == SDL_TEXTINPUT)) {
106  const char* utf8 = curr->text.text;
107  auto unicode = utf8::unchecked::next(utf8);
108  handleKeyDown(prev->key, unicode);
109  if (unicode) { // possibly there are more characters
110  handleText(utf8);
111  }
112  continue;
113  } else {
114  handle(*prev);
115  }
116  }
117  if (curr->type == SDL_KEYDOWN) {
118  pending = true;
119  std::swap(curr, prev);
120  } else {
121  handle(*curr);
122  }
123  }
124  if (pending) {
125  handle(*prev);
126  }
127 }
128 
130 {
131  keyRepeat = enable;
132 }
133 
134 void InputEventGenerator::setNewOsdControlButtonState(
135  unsigned newState, const EventPtr& origEvent)
136 {
137  unsigned deltaState = osdControlButtonsState ^ newState;
138  for (unsigned i = OsdControlEvent::LEFT_BUTTON;
139  i <= OsdControlEvent::B_BUTTON; ++i) {
140  if (deltaState & (1 << i)) {
141  if (newState & (1 << i)) {
142  eventDistributor.distributeEvent(
143  make_shared<OsdControlReleaseEvent>(
144  i, origEvent));
145  } else {
146  eventDistributor.distributeEvent(
147  make_shared<OsdControlPressEvent>(
148  i, origEvent));
149  }
150  }
151  }
152  osdControlButtonsState = newState;
153 }
154 
155 void InputEventGenerator::triggerOsdControlEventsFromJoystickAxisMotion(
156  unsigned axis, int value, const EventPtr& origEvent)
157 {
158  unsigned neg_button, pos_button;
159  switch (axis) {
160  case 0:
161  neg_button = 1 << OsdControlEvent::LEFT_BUTTON;
162  pos_button = 1 << OsdControlEvent::RIGHT_BUTTON;
163  break; // axis 0
164  case 1:
165  neg_button = 1 << OsdControlEvent::UP_BUTTON;
166  pos_button = 1 << OsdControlEvent::DOWN_BUTTON;
167  break;
168  default:
169  // Ignore all other axis (3D joysticks and flight joysticks may
170  // have more than 2 axis)
171  return;
172  }
173 
174  if (value > 0) {
175  // release negative button, press positive button
176  setNewOsdControlButtonState(
177  (osdControlButtonsState | neg_button) & ~pos_button,
178  origEvent);
179  } else if (value < 0) {
180  // press negative button, release positive button
181  setNewOsdControlButtonState(
182  (osdControlButtonsState | pos_button) & ~neg_button,
183  origEvent);
184  } else {
185  // release both buttons
186  setNewOsdControlButtonState(
187  osdControlButtonsState | neg_button | pos_button,
188  origEvent);
189  }
190 }
191 
192 void InputEventGenerator::triggerOsdControlEventsFromJoystickHat(
193  int value, const EventPtr& origEvent)
194 {
195  unsigned dir = 0;
196  if (!(value & SDL_HAT_UP )) dir |= 1 << OsdControlEvent::UP_BUTTON;
197  if (!(value & SDL_HAT_DOWN )) dir |= 1 << OsdControlEvent::DOWN_BUTTON;
198  if (!(value & SDL_HAT_LEFT )) dir |= 1 << OsdControlEvent::LEFT_BUTTON;
199  if (!(value & SDL_HAT_RIGHT)) dir |= 1 << OsdControlEvent::RIGHT_BUTTON;
200  unsigned ab = osdControlButtonsState & ((1 << OsdControlEvent::A_BUTTON) |
202  setNewOsdControlButtonState(ab | dir, origEvent);
203 }
204 
205 void InputEventGenerator::osdControlChangeButton(
206  bool up, unsigned changedButtonMask, const EventPtr& origEvent)
207 {
208  auto newButtonState = up
209  ? osdControlButtonsState | changedButtonMask
210  : osdControlButtonsState & ~changedButtonMask;
211  setNewOsdControlButtonState(newButtonState, origEvent);
212 }
213 
214 void InputEventGenerator::triggerOsdControlEventsFromJoystickButtonEvent(
215  unsigned button, bool up, const EventPtr& origEvent)
216 {
217  osdControlChangeButton(
218  up,
219  ((button & 1) ? (1 << OsdControlEvent::B_BUTTON)
220  : (1 << OsdControlEvent::A_BUTTON)),
221  origEvent);
222 }
223 
224 void InputEventGenerator::triggerOsdControlEventsFromKeyEvent(
225  Keys::KeyCode keyCode, bool up, const EventPtr& origEvent)
226 {
227  keyCode = static_cast<Keys::KeyCode>(keyCode & Keys::K_MASK);
228  if (keyCode == Keys::K_LEFT) {
229  osdControlChangeButton(up, 1 << OsdControlEvent::LEFT_BUTTON,
230  origEvent);
231  } else if (keyCode == Keys::K_RIGHT) {
232  osdControlChangeButton(up, 1 << OsdControlEvent::RIGHT_BUTTON,
233  origEvent);
234  } else if (keyCode == Keys::K_UP) {
235  osdControlChangeButton(up, 1 << OsdControlEvent::UP_BUTTON,
236  origEvent);
237  } else if (keyCode == Keys::K_DOWN) {
238  osdControlChangeButton(up, 1 << OsdControlEvent::DOWN_BUTTON,
239  origEvent);
240  } else if (keyCode == Keys::K_SPACE || keyCode == Keys::K_RETURN) {
241  osdControlChangeButton(up, 1 << OsdControlEvent::A_BUTTON,
242  origEvent);
243  } else if (keyCode == Keys::K_ESCAPE) {
244  osdControlChangeButton(up, 1 << OsdControlEvent::B_BUTTON,
245  origEvent);
246  }
247 }
248 
249 void InputEventGenerator::handleKeyDown(const SDL_KeyboardEvent& key, uint32_t unicode)
250 {
251  EventPtr event;
252  /*if (PLATFORM_ANDROID && evt.key.keysym.sym == SDLK_WORLD_93) {
253  event = make_shared<JoystickButtonDownEvent>(0, 0);
254  triggerOsdControlEventsFromJoystickButtonEvent(
255  0, false, event);
256  androidButtonA = true;
257  } else if (PLATFORM_ANDROID && evt.key.keysym.sym == SDLK_WORLD_94) {
258  event = make_shared<JoystickButtonDownEvent>(0, 1);
259  triggerOsdControlEventsFromJoystickButtonEvent(
260  1, false, event);
261  androidButtonB = true;
262  } else*/ {
263  auto keyCode = Keys::getCode(
264  key.keysym.sym, key.keysym.mod,
265  key.keysym.scancode, false);
266  event = make_shared<KeyDownEvent>(keyCode, unicode);
267  triggerOsdControlEventsFromKeyEvent(keyCode, false, event);
268  }
269  eventDistributor.distributeEvent(event);
270 }
271 
272 void InputEventGenerator::handleText(const char* utf8)
273 {
274  while (true) {
275  auto unicode = utf8::unchecked::next(utf8);
276  if (unicode == 0) return;
277  eventDistributor.distributeEvent(
278  make_shared<KeyDownEvent>(Keys::K_NONE, unicode));
279  }
280 }
281 
282 void InputEventGenerator::handle(const SDL_Event& evt)
283 {
284  EventPtr event;
285  switch (evt.type) {
286  case SDL_KEYUP:
287  // Virtual joystick of SDL Android port does not have joystick
288  // buttons. It has however up to 6 virtual buttons that can be
289  // mapped to SDL keyboard events. Two of these virtual buttons
290  // will be mapped to keys SDLK_WORLD_93 and 94 and are
291  // interpeted here as joystick buttons (respectively button 0
292  // and 1).
293  // TODO Android code should be rewritten for SDL2
294  /*if (PLATFORM_ANDROID && evt.key.keysym.sym == SDLK_WORLD_93) {
295  event = make_shared<JoystickButtonUpEvent>(0, 0);
296  triggerOsdControlEventsFromJoystickButtonEvent(
297  0, true, event);
298  androidButtonA = false;
299  } else if (PLATFORM_ANDROID && evt.key.keysym.sym == SDLK_WORLD_94) {
300  event = make_shared<JoystickButtonUpEvent>(0, 1);
301  triggerOsdControlEventsFromJoystickButtonEvent(
302  1, true, event);
303  androidButtonB = false;
304  } else*/ {
305  auto keyCode = Keys::getCode(
306  evt.key.keysym.sym, evt.key.keysym.mod,
307  evt.key.keysym.scancode, true);
308  event = make_shared<KeyUpEvent>(keyCode);
309  triggerOsdControlEventsFromKeyEvent(keyCode, true, event);
310  }
311  break;
312  case SDL_KEYDOWN:
313  handleKeyDown(evt.key, 0);
314  break;
315 
316  case SDL_MOUSEBUTTONUP:
317  event = make_shared<MouseButtonUpEvent>(evt.button.button);
318  break;
319  case SDL_MOUSEBUTTONDOWN:
320  event = make_shared<MouseButtonDownEvent>(evt.button.button);
321  break;
322  case SDL_MOUSEWHEEL: {
323  int x = evt.wheel.x;
324  int y = evt.wheel.y;
325  if (evt.wheel.direction == SDL_MOUSEWHEEL_FLIPPED)
326  {
327  x = -x;
328  y = -y;
329  }
330  event = make_shared<MouseWheelEvent>(x, y);
331  break;
332  }
333  case SDL_MOUSEMOTION:
334  event = make_shared<MouseMotionEvent>(
335  evt.motion.xrel, evt.motion.yrel,
336  evt.motion.x, evt.motion.y);
337  break;
338 
339  case SDL_JOYBUTTONUP:
340  event = make_shared<JoystickButtonUpEvent>(
341  evt.jbutton.which, evt.jbutton.button);
342  triggerOsdControlEventsFromJoystickButtonEvent(
343  evt.jbutton.button, true, event);
344  break;
345  case SDL_JOYBUTTONDOWN:
346  event = make_shared<JoystickButtonDownEvent>(
347  evt.jbutton.which, evt.jbutton.button);
348  triggerOsdControlEventsFromJoystickButtonEvent(
349  evt.jbutton.button, false, event);
350  break;
351  case SDL_JOYAXISMOTION: {
352  auto& setting = globalSettings.getJoyDeadzoneSetting(evt.jaxis.which);
353  int threshold = (setting.getInt() * 32768) / 100;
354  auto value = (evt.jaxis.value < -threshold) ? evt.jaxis.value
355  : (evt.jaxis.value > threshold) ? evt.jaxis.value
356  : 0;
357  event = make_shared<JoystickAxisMotionEvent>(
358  evt.jaxis.which, evt.jaxis.axis, value);
359  triggerOsdControlEventsFromJoystickAxisMotion(
360  evt.jaxis.axis, value, event);
361  break;
362  }
363  case SDL_JOYHATMOTION:
364  event = make_shared<JoystickHatEvent>(
365  evt.jhat.which, evt.jhat.hat, evt.jhat.value);
366  triggerOsdControlEventsFromJoystickHat(evt.jhat.value, event);
367  break;
368 
369  case SDL_TEXTINPUT:
370  handleText(evt.text.text);
371  break;
372 
373  case SDL_WINDOWEVENT:
374  switch (evt.window.event) {
375  case SDL_WINDOWEVENT_FOCUS_GAINED:
376  event = make_shared<FocusEvent>(true);
377  break;
378  case SDL_WINDOWEVENT_FOCUS_LOST:
379  event = make_shared<FocusEvent>(false);
380  break;
381  case SDL_WINDOWEVENT_RESIZED:
382  event = make_shared<ResizeEvent>(
383  evt.window.data1, evt.window.data2);
384  break;
385  case SDL_WINDOWEVENT_EXPOSED:
386  event = make_shared<SimpleEvent>(OPENMSX_EXPOSE_EVENT);
387  break;
388  default:
389  break;
390  }
391  break;
392 
393  case SDL_QUIT:
394  event = make_shared<QuitEvent>();
395  break;
396 
397  default:
398  break;
399  }
400 
401 #if 0
402  if (event) {
403  std::cerr << "SDL event converted to: " << event->toString() << '\n';
404  } else {
405  std::cerr << "SDL event was of unknown type, not converted to an openMSX event\n";
406  }
407 #endif
408 
409  if (event) eventDistributor.distributeEvent(event);
410 }
411 
412 
413 void InputEventGenerator::update(const Setting& setting)
414 {
415  assert(&setting == &grabInput); (void)setting;
416  escapeGrabState = ESCAPE_GRAB_WAIT_CMD;
417  setGrabInput(grabInput.getBoolean());
418 }
419 
420 int InputEventGenerator::signalEvent(const std::shared_ptr<const Event>& event)
421 {
422  auto& focusEvent = checked_cast<const FocusEvent&>(*event);
423  switch (escapeGrabState) {
424  case ESCAPE_GRAB_WAIT_CMD:
425  // nothing
426  break;
427  case ESCAPE_GRAB_WAIT_LOST:
428  if (!focusEvent.getGain()) {
429  escapeGrabState = ESCAPE_GRAB_WAIT_GAIN;
430  }
431  break;
432  case ESCAPE_GRAB_WAIT_GAIN:
433  if (focusEvent.getGain()) {
434  escapeGrabState = ESCAPE_GRAB_WAIT_CMD;
435  }
436  setGrabInput(true);
437  break;
438  default:
439  UNREACHABLE;
440  }
441  return 0;
442 }
443 
444 void InputEventGenerator::setGrabInput(bool grab)
445 {
446  SDL_SetRelativeMouseMode(grab ? SDL_TRUE : SDL_FALSE);
447 
448  // TODO is this still the correct place in SDL2
449  // TODO get the SDL_window
450  //SDL_Window* window = ...;
451  //SDL_SetWindowGrab(window, grab ? SDL_TRUE : SDL_FALSE);
452 }
453 
454 
455 // Wrap SDL joystick button functions to handle the 'fake' android joystick
456 // buttons. The method InputEventGenerator::handle() already takes care of fake
457 // events for the andoid joystick buttons, these two wrappers handle the direct
458 // joystick button state queries.
459 int InputEventGenerator::joystickNumButtons(SDL_Joystick* joystick)
460 {
461  if (PLATFORM_ANDROID) {
462  return 2;
463  } else {
464  return SDL_JoystickNumButtons(joystick);
465  }
466 }
467 bool InputEventGenerator::joystickGetButton(SDL_Joystick* joystick, int button)
468 {
469  if (PLATFORM_ANDROID) {
470  switch (button) {
471  case 0: return androidButtonA;
472  case 1: return androidButtonB;
473  default: UNREACHABLE; return false;
474  }
475  } else {
476  return SDL_JoystickGetButton(joystick, button) != 0;
477  }
478 }
479 
480 
481 // class EscapeGrabCmd
482 
483 InputEventGenerator::EscapeGrabCmd::EscapeGrabCmd(
484  CommandController& commandController_)
485  : Command(commandController_, "escape_grab")
486 {
487 }
488 
489 void InputEventGenerator::EscapeGrabCmd::execute(
490  span<const TclObject> /*tokens*/, TclObject& /*result*/)
491 {
492  auto& inputEventGenerator = OUTER(InputEventGenerator, escapeGrabCmd);
493  if (inputEventGenerator.grabInput.getBoolean()) {
494  inputEventGenerator.escapeGrabState =
495  InputEventGenerator::ESCAPE_GRAB_WAIT_LOST;
496  inputEventGenerator.setGrabInput(false);
497  }
498 }
499 
500 string InputEventGenerator::EscapeGrabCmd::help(
501  const vector<string>& /*tokens*/) const
502 {
503  return "Temporarily release input grab.";
504 }
505 
506 } // namespace openmsx
IntegerSetting & getJoyDeadzoneSetting(int i)
void swap(optional< T > &x, optional< T > &y) noexcept(noexcept(x.swap(y)))
Definition: optional.hh:816
void registerEventListener(EventType type, EventListener &listener, Priority priority=OTHER)
Registers a given object to receive certain events.
void unregisterEventListener(EventType type, EventListener &listener)
Unregisters a previously registered event listener.
void wait()
Wait for event(s) and handle it.
Definition: span.hh:34
uint32_t next(octet_iterator &it)
void distributeEvent(const EventPtr &event)
Schedule the given event for delivery.
KeyCode
Constants that identify keys and key modifiers.
Definition: Keys.hh:26
void attach(Observer< T > &observer)
Definition: Subject.hh:45
static int joystickNumButtons(SDL_Joystick *joystick)
Normally the following two functions simply delegate to SDL_JoystickNumButtons() and SDL_JoystickGetB...
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
Send when (part of) the openMSX window gets exposed, and thus should be repainted.
Definition: Event.hh:67
void setKeyRepeat(bool enable)
Enable or disable keyboard event repeats.
static bool joystickGetButton(SDL_Joystick *joystick, int button)
void detach(Observer< T > &observer)
Definition: Subject.hh:51
#define OUTER(type, member)
Definition: outer.hh:38
This class contains settings that are used by several other class (including some singletons)...
KeyCode getCode(string_view name)
Translate key name to key code.
Definition: Keys.cc:341
InputEventGenerator(const InputEventGenerator &)=delete
#define UNREACHABLE
Definition: unreachable.hh:38