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