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 "SDLKey.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::WINDOW, *this);
30
31#ifndef SDL_JOYSTICK_DISABLED
32 SDL_JoystickEventState(SDL_ENABLE); // joysticks generate events
33#endif
34}
35
37{
38 eventDistributor.unregisterEventListener(EventType::WINDOW, *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 splitText(curr->text.timestamp, utf8);
98 }
99 eventDistributor.distributeEvent(TextEvent(*curr));
100 continue;
101 } else {
102 handle(*prev);
103 }
104 }
105 if (curr->type == SDL_KEYDOWN) {
106 pending = true;
107 std::swap(curr, prev);
108 } else {
109 handle(*curr);
110 }
111 }
112 if (pending) {
113 handle(*prev);
114 }
115}
116
117void InputEventGenerator::setNewOsdControlButtonState(unsigned newState)
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(OsdControlReleaseEvent(i));
125 } else {
126 eventDistributor.distributeEvent(OsdControlPressEvent(i));
127 }
128 }
129 }
130 osdControlButtonsState = newState;
131}
132
133void InputEventGenerator::triggerOsdControlEventsFromJoystickAxisMotion(
134 unsigned axis, int value)
135{
136 auto [neg_button, pos_button] = [&] {
137 switch (axis) {
138 case 0:
139 return std::pair{1u << OsdControlEvent::LEFT_BUTTON,
141 case 1:
142 return std::pair{1u << OsdControlEvent::UP_BUTTON,
144 default:
145 // Ignore all other axis (3D joysticks and flight joysticks may
146 // have more than 2 axis)
147 return std::pair{0u, 0u};
148 }
149 }();
150
151 if (value > 0) {
152 // release negative button, press positive button
153 setNewOsdControlButtonState(
154 (osdControlButtonsState | neg_button) & ~pos_button);
155 } else if (value < 0) {
156 // press negative button, release positive button
157 setNewOsdControlButtonState(
158 (osdControlButtonsState | pos_button) & ~neg_button);
159 } else {
160 // release both buttons
161 setNewOsdControlButtonState(
162 osdControlButtonsState | neg_button | pos_button);
163 }
164}
165
166void InputEventGenerator::triggerOsdControlEventsFromJoystickHat(int value)
167{
168 unsigned dir = 0;
169 if (!(value & SDL_HAT_UP )) dir |= 1 << OsdControlEvent::UP_BUTTON;
170 if (!(value & SDL_HAT_DOWN )) dir |= 1 << OsdControlEvent::DOWN_BUTTON;
171 if (!(value & SDL_HAT_LEFT )) dir |= 1 << OsdControlEvent::LEFT_BUTTON;
172 if (!(value & SDL_HAT_RIGHT)) dir |= 1 << OsdControlEvent::RIGHT_BUTTON;
173 unsigned ab = osdControlButtonsState & ((1 << OsdControlEvent::A_BUTTON) |
175 setNewOsdControlButtonState(ab | dir);
176}
177
178void InputEventGenerator::osdControlChangeButton(bool down, unsigned changedButtonMask)
179{
180 auto newButtonState = down
181 ? osdControlButtonsState & ~changedButtonMask
182 : osdControlButtonsState | changedButtonMask;
183 setNewOsdControlButtonState(newButtonState);
184}
185
186void InputEventGenerator::triggerOsdControlEventsFromJoystickButtonEvent(unsigned button, bool down)
187{
188 osdControlChangeButton(
189 down,
190 ((button & 1) ? (1 << OsdControlEvent::B_BUTTON)
191 : (1 << OsdControlEvent::A_BUTTON)));
192}
193
194void InputEventGenerator::triggerOsdControlEventsFromKeyEvent(SDLKey key, bool repeat)
195{
196 unsigned buttonMask = [&] {
197 switch (key.sym.sym) {
198 case SDLK_LEFT: return 1 << OsdControlEvent::LEFT_BUTTON;
199 case SDLK_RIGHT: return 1 << OsdControlEvent::RIGHT_BUTTON;
200 case SDLK_UP: return 1 << OsdControlEvent::UP_BUTTON;
201 case SDLK_DOWN: return 1 << OsdControlEvent::DOWN_BUTTON;
202 case SDLK_SPACE: return 1 << OsdControlEvent::A_BUTTON;
203 case SDLK_RETURN: return 1 << OsdControlEvent::A_BUTTON;
204 case SDLK_ESCAPE: return 1 << OsdControlEvent::B_BUTTON;
205 default: return 0;
206 }
207 }();
208 if (buttonMask) {
209 if (repeat) {
210 osdControlChangeButton(!key.down, buttonMask);
211 }
212 osdControlChangeButton(key.down, buttonMask);
213 }
214}
215
216static constexpr Uint16 normalizeModifier(SDL_Keycode sym, Uint16 mod)
217{
218 // Apparently modifier-keys also have the corresponding
219 // modifier attribute set. See here for a discussion:
220 // https://github.com/openMSX/openMSX/issues/1202
221 // As a solution, on pure modifier keys, we now clear the
222 // modifier attributes.
223 return (sym == one_of(SDLK_LCTRL, SDLK_LSHIFT, SDLK_LALT, SDLK_LGUI,
224 SDLK_RCTRL, SDLK_RSHIFT, SDLK_RALT, SDLK_RGUI,
225 SDLK_MODE))
226 ? 0
227 : mod;
228}
229
230void InputEventGenerator::handleKeyDown(const SDL_KeyboardEvent& key, uint32_t unicode)
231{
232 /*if (PLATFORM_ANDROID && evt.key.keysym.sym == SDLK_WORLD_93) {
233 event = JoystickButtonDownEvent(0, 0);
234 triggerOsdControlEventsFromJoystickButtonEvent(
235 0, false);
236 androidButtonA = true;
237 } else if (PLATFORM_ANDROID && evt.key.keysym.sym == SDLK_WORLD_94) {
238 event = JoystickButtonDownEvent(0, 1);
239 triggerOsdControlEventsFromJoystickButtonEvent(
240 1, false);
241 androidButtonB = true;
242 } else*/ {
243 SDL_Event evt = {};
244 memcpy(&evt, &key, sizeof(key));
245 evt.key.keysym.mod = normalizeModifier(key.keysym.sym, key.keysym.mod);
246 evt.key.keysym.unused = unicode;
247 Event event = KeyDownEvent(evt);
248 triggerOsdControlEventsFromKeyEvent(SDLKey{key.keysym, true}, key.repeat);
249 eventDistributor.distributeEvent(std::move(event));
250 }
251}
252
253void InputEventGenerator::splitText(uint32_t timestamp, const char* utf8)
254{
255 while (true) {
256 auto unicode = utf8::unchecked::next(utf8);
257 if (unicode == 0) return;
258 eventDistributor.distributeEvent(
259 KeyDownEvent::create(timestamp, unicode));
260 }
261}
262
263void InputEventGenerator::handle(const SDL_Event& evt)
264{
265 std::optional<Event> event;
266 switch (evt.type) {
267 case SDL_KEYUP:
268 // Virtual joystick of SDL Android port does not have joystick
269 // buttons. It has however up to 6 virtual buttons that can be
270 // mapped to SDL keyboard events. Two of these virtual buttons
271 // will be mapped to keys SDLK_WORLD_93 and 94 and are
272 // interpreted here as joystick buttons (respectively button 0
273 // and 1).
274 // TODO Android code should be rewritten for SDL2
275 /*if (PLATFORM_ANDROID && evt.key.keysym.sym == SDLK_WORLD_93) {
276 event = JoystickButtonUpEvent(0, 0);
277 triggerOsdControlEventsFromJoystickButtonEvent(0, true);
278 androidButtonA = false;
279 } else if (PLATFORM_ANDROID && evt.key.keysym.sym == SDLK_WORLD_94) {
280 event = JoystickButtonUpEvent(0, 1);
281 triggerOsdControlEventsFromJoystickButtonEvent(1, true);
282 androidButtonB = false;
283 } else*/ {
284 SDL_Event e = {};
285 memcpy(&e, &evt, sizeof(evt));
286 e.key.keysym.mod = normalizeModifier(evt.key.keysym.sym, evt.key.keysym.mod);
287 event = KeyUpEvent(e);
288 bool down = false;
289 bool repeat = false;
290 triggerOsdControlEventsFromKeyEvent(SDLKey{e.key.keysym, down}, repeat);
291 }
292 break;
293 case SDL_KEYDOWN:
294 handleKeyDown(evt.key, 0);
295 break;
296
297 case SDL_MOUSEBUTTONUP:
298 event = MouseButtonUpEvent(evt);
299 break;
300 case SDL_MOUSEBUTTONDOWN:
301 event = MouseButtonDownEvent(evt);
302 break;
303 case SDL_MOUSEWHEEL:
304 event = MouseWheelEvent(evt);
305 break;
306 case SDL_MOUSEMOTION:
307 event = MouseMotionEvent(evt);
308 break;
309
310 case SDL_JOYBUTTONUP:
311 event = JoystickButtonUpEvent(evt);
312 triggerOsdControlEventsFromJoystickButtonEvent(
313 evt.jbutton.button, false);
314 break;
315 case SDL_JOYBUTTONDOWN:
316 event = JoystickButtonDownEvent(evt);
317 triggerOsdControlEventsFromJoystickButtonEvent(
318 evt.jbutton.button, true);
319 break;
320 case SDL_JOYAXISMOTION: {
321 auto& setting = globalSettings.getJoyDeadZoneSetting(evt.jaxis.which);
322 int threshold = (setting.getInt() * 32768) / 100;
323 auto value = (evt.jaxis.value < -threshold) ? evt.jaxis.value
324 : (evt.jaxis.value > threshold) ? evt.jaxis.value
325 : 0;
326 event = JoystickAxisMotionEvent(evt);
327 triggerOsdControlEventsFromJoystickAxisMotion(
328 evt.jaxis.axis, value);
329 break;
330 }
331 case SDL_JOYHATMOTION:
332 event = JoystickHatEvent(evt);
333 triggerOsdControlEventsFromJoystickHat(evt.jhat.value);
334 break;
335
336 case SDL_TEXTINPUT:
337 splitText(evt.text.timestamp, evt.text.text);
338 event = TextEvent(evt);
339 break;
340
341 case SDL_WINDOWEVENT:
342 switch (evt.window.event) {
343 case SDL_WINDOWEVENT_CLOSE:
344 if (WindowEvent::isMainWindow(evt.window.windowID)) {
345 event = QuitEvent();
346 break;
347 }
348 [[fallthrough]];
349 default:
350 event = WindowEvent(evt);
351 break;
352 }
353 break;
354
355 case SDL_DROPFILE:
356 event = FileDropEvent(
358 SDL_free(evt.drop.file);
359 break;
360
361 case SDL_QUIT:
362 event = QuitEvent();
363 break;
364
365 default:
366 break;
367 }
368
369#if 0
370 if (event) {
371 std::cerr << "SDL event converted to: " << toString(event) << '\n';
372 } else {
373 std::cerr << "SDL event was of unknown type, not converted to an openMSX event\n";
374 }
375#endif
376
377 if (event) eventDistributor.distributeEvent(std::move(*event));
378}
379
380
382{
383 escapeGrabState = ESCAPE_GRAB_WAIT_CMD;
384 setGrabInput(grab);
385}
386
387int InputEventGenerator::signalEvent(const Event& event)
388{
389 std::visit(overloaded{
390 [&](const WindowEvent& e) {
391 const auto& evt = e.getSdlWindowEvent();
392 if (e.isMainWindow() &&
393 evt.event == one_of(SDL_WINDOWEVENT_FOCUS_GAINED, SDL_WINDOWEVENT_FOCUS_LOST)) {
394 switch (escapeGrabState) {
395 case ESCAPE_GRAB_WAIT_CMD:
396 // nothing
397 break;
398 case ESCAPE_GRAB_WAIT_LOST:
399 if (evt.event == SDL_WINDOWEVENT_FOCUS_LOST) {
400 escapeGrabState = ESCAPE_GRAB_WAIT_GAIN;
401 }
402 break;
403 case ESCAPE_GRAB_WAIT_GAIN:
404 if (evt.event == SDL_WINDOWEVENT_FOCUS_GAINED) {
405 escapeGrabState = ESCAPE_GRAB_WAIT_CMD;
406 }
407 setGrabInput(true);
408 break;
409 default:
411 }
412 }
413 },
414 [](const EventBase&) { UNREACHABLE; }
415 }, event);
416 return 0;
417}
418
419void InputEventGenerator::setGrabInput(bool grab)
420{
421 SDL_SetRelativeMouseMode(grab ? SDL_TRUE : SDL_FALSE);
422
423 // TODO is this still the correct place in SDL2
424 // TODO get the SDL_window
425 //SDL_Window* window = ...;
426 //SDL_SetWindowGrab(window, grab ? SDL_TRUE : SDL_FALSE);
427}
428
429
430// Wrap SDL joystick button functions to handle the 'fake' android joystick
431// buttons. The method InputEventGenerator::handle() already takes care of fake
432// events for the android joystick buttons, these two wrappers handle the direct
433// joystick button state queries.
435{
436 if constexpr (PLATFORM_ANDROID) {
437 return 2;
438 } else {
439 return SDL_JoystickNumButtons(joystick);
440 }
441}
442bool InputEventGenerator::joystickGetButton(SDL_Joystick* joystick, int button)
443{
444 if constexpr (PLATFORM_ANDROID) {
445 switch (button) {
446 case 0: return androidButtonA;
447 case 1: return androidButtonB;
448 default: UNREACHABLE; return false;
449 }
450 } else {
451 return SDL_JoystickGetButton(joystick, button) != 0;
452 }
453}
454
455
456// class EscapeGrabCmd
457
458InputEventGenerator::EscapeGrabCmd::EscapeGrabCmd(
459 CommandController& commandController_)
460 : Command(commandController_, "escape_grab")
461{
462}
463
464void InputEventGenerator::EscapeGrabCmd::execute(
465 std::span<const TclObject> /*tokens*/, TclObject& /*result*/)
466{
467 auto& inputEventGenerator = OUTER(InputEventGenerator, escapeGrabCmd);
468 if (inputEventGenerator.grabInput.getBoolean()) {
469 inputEventGenerator.escapeGrabState =
470 InputEventGenerator::ESCAPE_GRAB_WAIT_LOST;
471 inputEventGenerator.setGrabInput(false);
472 }
473}
474
475std::string InputEventGenerator::EscapeGrabCmd::help(
476 std::span<const TclObject> /*tokens*/) const
477{
478 return "Temporarily release input grab.";
479}
480
481} // 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.
static KeyDownEvent create(SDL_Keycode code, SDL_Keymod mod=KMOD_NONE)
Definition: Event.hh:81
bool isMainWindow() const
Definition: Event.hh:221
constexpr double e
Definition: Math.hh:21
const std::string & getConventionalPath(const std::string &path)
Returns the path in conventional path-delimiter.
This file implemented 3 utility functions:
Definition: Autofire.cc:9
std::variant< KeyUpEvent, KeyDownEvent, MouseMotionEvent, MouseButtonUpEvent, MouseButtonDownEvent, MouseWheelEvent, JoystickAxisMotionEvent, JoystickHatEvent, JoystickButtonUpEvent, JoystickButtonDownEvent, OsdControlReleaseEvent, OsdControlPressEvent, WindowEvent, TextEvent, FileDropEvent, QuitEvent, FinishFrameEvent, CliCommandEvent, GroupEvent, BootEvent, FrameDrawnEvent, BreakEvent, SwitchRendererEvent, TakeReverseSnapshotEvent, AfterTimedEvent, MachineLoadedEvent, MachineActivatedEvent, MachineDeactivatedEvent, MidiInReaderEvent, MidiInWindowsEvent, MidiInCoreMidiEvent, MidiInCoreMidiVirtualEvent, MidiInALSAEvent, Rs232TesterEvent, ImGuiDelayedActionEvent, ImGuiActiveEvent > Event
Definition: Event.hh:450
std::string toString(const BooleanInput &input)
Definition: BooleanInput.cc:16
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