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