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