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