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