openMSX
InputEventGenerator.cc
Go to the documentation of this file.
2
3#include "Event.hh"
4#include "EventDistributor.hh"
5#include "FileOperations.hh"
6#include "GlobalSettings.hh"
7#include "IntegerSetting.hh"
8#include "SDLKey.hh"
9
10#include "one_of.hh"
11#include "outer.hh"
12#include "unreachable.hh"
13#include "utf8_unchecked.hh"
14
15#include "build-info.hh"
16
17#include <memory>
18
19namespace openmsx {
20
22 EventDistributor& eventDistributor_,
23 GlobalSettings& globalSettings_)
24 : eventDistributor(eventDistributor_)
25 , globalSettings(globalSettings_)
26 , joystickManager(commandController)
27 , grabInput(
28 commandController, "grabinput",
29 "This setting controls if openMSX takes over mouse and keyboard input",
30 false, Setting::DONT_SAVE)
31 , escapeGrabCmd(commandController)
32{
33 setGrabInput(grabInput.getBoolean());
34 eventDistributor.registerEventListener(EventType::WINDOW, *this);
35}
36
41
43{
44 // SDL bug workaround
45 if (!SDL_WasInit(SDL_INIT_VIDEO)) {
46 SDL_Delay(100);
47 }
48
49 if (SDL_WaitEvent(nullptr)) {
50 poll();
51 }
52}
53
55{
56 // Heuristic to emulate the old SDL1 behavior:
57 //
58 // SDL1 had a unicode field on each KEYDOWN event. In SDL2 that
59 // information is moved to the (new) SDL_TEXTINPUT events.
60 //
61 // Though our MSX keyboard emulation code needs to relate KEYDOWN
62 // events with the associated unicode. We try to mimic this by the
63 // following heuristic:
64 // When two successive events in a single batch (so, the same
65 // invocation of poll()) are KEYDOWN followed by TEXTINPUT, then copy
66 // the unicode (of the first character) of the TEXT event to the
67 // KEYDOWN event.
68 // Implementing this requires a lookahead of 1 event. So the code below
69 // deals with a 'current' and a 'previous' event, and keeps track of
70 // whether the previous event is still pending (not yet processed).
71 //
72 // In a previous version we also added the constraint that these two
73 // consecutive events must have the same timestamp, but that had mixed
74 // results:
75 // - on Linux it worked fine
76 // - on Windows the timestamps sometimes did not match
77 // - on Mac the timestamps mostly did not match
78 // So we removed this constraint.
79 //
80 // We also split SDL_TEXTINPUT events into (possibly) multiple KEYDOWN
81 // events because a single event type makes it easier to handle higher
82 // priority listeners that can block the event for lower priority
83 // listener (console > hotkey > msx).
84
85 SDL_Event event1, event2;
86 auto* prev = &event1;
87 auto* curr = &event2;
88 bool pending = false;
89
90 while (SDL_PollEvent(curr)) {
91 if (pending) {
92 pending = false;
93 if ((prev->type == SDL_KEYDOWN) && (curr->type == SDL_TEXTINPUT)) {
94 const char* utf8 = curr->text.text;
95 auto unicode = utf8::unchecked::next(utf8);
96 handleKeyDown(prev->key, unicode);
97 if (unicode) { // possibly there are more characters
98 splitText(curr->text.timestamp, utf8);
99 }
100 eventDistributor.distributeEvent(TextEvent(*curr));
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
118void InputEventGenerator::setNewOsdControlButtonState(unsigned newState)
119{
120 unsigned deltaState = osdControlButtonsState ^ newState;
121 for (unsigned i = OsdControlEvent::LEFT_BUTTON;
122 i <= OsdControlEvent::B_BUTTON; ++i) {
123 if (deltaState & (1 << i)) {
124 if (newState & (1 << i)) {
125 eventDistributor.distributeEvent(OsdControlReleaseEvent(i));
126 } else {
127 eventDistributor.distributeEvent(OsdControlPressEvent(i));
128 }
129 }
130 }
131 osdControlButtonsState = newState;
132}
133
134void InputEventGenerator::triggerOsdControlEventsFromJoystickAxisMotion(
135 unsigned axis, int value)
136{
137 auto [neg_button, pos_button] = [&] {
138 switch (axis) {
139 case 0:
140 return std::pair{1u << OsdControlEvent::LEFT_BUTTON,
142 case 1:
143 return std::pair{1u << OsdControlEvent::UP_BUTTON,
145 default:
146 // Ignore all other axis (3D joysticks and flight joysticks may
147 // have more than 2 axis)
148 return std::pair{0u, 0u};
149 }
150 }();
151
152 if (value > 0) {
153 // release negative button, press positive button
154 setNewOsdControlButtonState(
155 (osdControlButtonsState | neg_button) & ~pos_button);
156 } else if (value < 0) {
157 // press negative button, release positive button
158 setNewOsdControlButtonState(
159 (osdControlButtonsState | pos_button) & ~neg_button);
160 } else {
161 // release both buttons
162 setNewOsdControlButtonState(
163 osdControlButtonsState | neg_button | pos_button);
164 }
165}
166
167void InputEventGenerator::triggerOsdControlEventsFromJoystickHat(int value)
168{
169 unsigned dir = 0;
170 if (!(value & SDL_HAT_UP )) dir |= 1 << OsdControlEvent::UP_BUTTON;
171 if (!(value & SDL_HAT_DOWN )) dir |= 1 << OsdControlEvent::DOWN_BUTTON;
172 if (!(value & SDL_HAT_LEFT )) dir |= 1 << OsdControlEvent::LEFT_BUTTON;
173 if (!(value & SDL_HAT_RIGHT)) dir |= 1 << OsdControlEvent::RIGHT_BUTTON;
174 unsigned ab = osdControlButtonsState & ((1 << OsdControlEvent::A_BUTTON) |
176 setNewOsdControlButtonState(ab | dir);
177}
178
179void InputEventGenerator::osdControlChangeButton(bool down, unsigned changedButtonMask)
180{
181 auto newButtonState = down
182 ? osdControlButtonsState & ~changedButtonMask
183 : osdControlButtonsState | changedButtonMask;
184 setNewOsdControlButtonState(newButtonState);
185}
186
187void InputEventGenerator::triggerOsdControlEventsFromJoystickButtonEvent(unsigned button, bool down)
188{
189 osdControlChangeButton(
190 down,
191 ((button & 1) ? (1 << OsdControlEvent::B_BUTTON)
192 : (1 << OsdControlEvent::A_BUTTON)));
193}
194
195void InputEventGenerator::triggerOsdControlEventsFromKeyEvent(SDLKey key, bool repeat)
196{
197 unsigned buttonMask = [&] {
198 switch (key.sym.sym) {
199 case SDLK_LEFT: return 1 << OsdControlEvent::LEFT_BUTTON;
200 case SDLK_RIGHT: return 1 << OsdControlEvent::RIGHT_BUTTON;
201 case SDLK_UP: return 1 << OsdControlEvent::UP_BUTTON;
202 case SDLK_DOWN: return 1 << OsdControlEvent::DOWN_BUTTON;
203 case SDLK_SPACE: return 1 << OsdControlEvent::A_BUTTON;
204 case SDLK_RETURN: return 1 << OsdControlEvent::A_BUTTON;
205 case SDLK_ESCAPE: return 1 << OsdControlEvent::B_BUTTON;
206 default: return 0;
207 }
208 }();
209 if (buttonMask) {
210 if (repeat) {
211 osdControlChangeButton(!key.down, buttonMask);
212 }
213 osdControlChangeButton(key.down, buttonMask);
214 }
215}
216
217static constexpr Uint16 normalizeModifier(SDL_Keycode sym, Uint16 mod)
218{
219 // Apparently modifier-keys also have the corresponding
220 // modifier attribute set. See here for a discussion:
221 // https://github.com/openMSX/openMSX/issues/1202
222 // As a solution, on pure modifier keys, we now clear the
223 // modifier attributes.
224 return (sym == one_of(SDLK_LCTRL, SDLK_LSHIFT, SDLK_LALT, SDLK_LGUI,
225 SDLK_RCTRL, SDLK_RSHIFT, SDLK_RALT, SDLK_RGUI,
226 SDLK_MODE))
227 ? 0
228 : mod;
229}
230
231void InputEventGenerator::handleKeyDown(const SDL_KeyboardEvent& key, uint32_t unicode)
232{
233 /*if (PLATFORM_ANDROID && evt.key.keysym.sym == SDLK_WORLD_93) {
234 event = JoystickButtonDownEvent(0, 0);
235 triggerOsdControlEventsFromJoystickButtonEvent(
236 0, false);
237 androidButtonA = true;
238 } else if (PLATFORM_ANDROID && evt.key.keysym.sym == SDLK_WORLD_94) {
239 event = JoystickButtonDownEvent(0, 1);
240 triggerOsdControlEventsFromJoystickButtonEvent(
241 1, false);
242 androidButtonB = true;
243 } else*/ {
244 SDL_Event evt;
245 evt.key = SDL_KeyboardEvent{};
246 memcpy(&evt.key, &key, sizeof(key));
247 evt.key.keysym.mod = normalizeModifier(key.keysym.sym, key.keysym.mod);
248 evt.key.keysym.unused = unicode;
249 Event event = KeyDownEvent(evt);
250 triggerOsdControlEventsFromKeyEvent(SDLKey{key.keysym, true}, key.repeat);
251 eventDistributor.distributeEvent(std::move(event));
252 }
253}
254
255void InputEventGenerator::splitText(uint32_t timestamp, const char* utf8)
256{
257 while (true) {
258 auto unicode = utf8::unchecked::next(utf8);
259 if (unicode == 0) return;
260 eventDistributor.distributeEvent(
261 KeyDownEvent::create(timestamp, unicode));
262 }
263}
264
265void InputEventGenerator::handle(const SDL_Event& evt)
266{
267 std::optional<Event> event;
268 switch (evt.type) {
269 case SDL_KEYUP:
270 // Virtual joystick of SDL Android port does not have joystick
271 // buttons. It has however up to 6 virtual buttons that can be
272 // mapped to SDL keyboard events. Two of these virtual buttons
273 // will be mapped to keys SDLK_WORLD_93 and 94 and are
274 // interpreted here as joystick buttons (respectively button 0
275 // and 1).
276 // TODO Android code should be rewritten for SDL2
277 /*if (PLATFORM_ANDROID && evt.key.keysym.sym == SDLK_WORLD_93) {
278 event = JoystickButtonUpEvent(0, 0);
279 triggerOsdControlEventsFromJoystickButtonEvent(0, true);
280 androidButtonA = false;
281 } else if (PLATFORM_ANDROID && evt.key.keysym.sym == SDLK_WORLD_94) {
282 event = JoystickButtonUpEvent(0, 1);
283 triggerOsdControlEventsFromJoystickButtonEvent(1, true);
284 androidButtonB = false;
285 } else*/ {
286 SDL_Event e;
287 e.key = SDL_KeyboardEvent{};
288 memcpy(&e.key, &evt.key, sizeof(evt.key));
289 e.key.keysym.mod = normalizeModifier(evt.key.keysym.sym, evt.key.keysym.mod);
290 event = KeyUpEvent(e);
291 bool down = false;
292 bool repeat = false;
293 triggerOsdControlEventsFromKeyEvent(SDLKey{e.key.keysym, down}, repeat);
294 }
295 break;
296 case SDL_KEYDOWN:
297 handleKeyDown(evt.key, 0);
298 break;
299
300 case SDL_MOUSEBUTTONUP:
301 event = MouseButtonUpEvent(evt);
302 break;
303 case SDL_MOUSEBUTTONDOWN:
304 event = MouseButtonDownEvent(evt);
305 break;
306 case SDL_MOUSEWHEEL:
307 event = MouseWheelEvent(evt);
308 break;
309 case SDL_MOUSEMOTION:
310 event = MouseMotionEvent(evt);
311 break;
312
313 case SDL_JOYBUTTONUP:
314 if (joystickManager.translateSdlInstanceId(const_cast<SDL_Event&>(evt))) {
315 event = JoystickButtonUpEvent(evt);
316 triggerOsdControlEventsFromJoystickButtonEvent(
317 evt.jbutton.button, false);
318 }
319 break;
320 case SDL_JOYBUTTONDOWN:
321 if (joystickManager.translateSdlInstanceId(const_cast<SDL_Event&>(evt))) {
322 event = JoystickButtonDownEvent(evt);
323 triggerOsdControlEventsFromJoystickButtonEvent(
324 evt.jbutton.button, true);
325 }
326 break;
327 case SDL_JOYAXISMOTION: {
328 if (auto joyId = joystickManager.translateSdlInstanceId(const_cast<SDL_Event&>(evt))) {
329 auto* setting = joystickManager.getJoyDeadZoneSetting(*joyId);
330 assert(setting);
331 int deadZone = setting->getInt();
332 int threshold = (deadZone * 32768) / 100;
333 auto value = (evt.jaxis.value < -threshold) ? evt.jaxis.value
334 : (evt.jaxis.value > threshold) ? evt.jaxis.value
335 : 0;
336 event = JoystickAxisMotionEvent(evt);
337 triggerOsdControlEventsFromJoystickAxisMotion(
338 evt.jaxis.axis, value);
339 }
340 break;
341 }
342 case SDL_JOYHATMOTION:
343 if (auto joyId = joystickManager.translateSdlInstanceId(const_cast<SDL_Event&>(evt))) {
344 event = JoystickHatEvent(evt);
345 triggerOsdControlEventsFromJoystickHat(evt.jhat.value);
346 }
347 break;
348
349 case SDL_JOYDEVICEADDED:
350 joystickManager.add(evt.jdevice.which);
351 break;
352
353 case SDL_JOYDEVICEREMOVED:
354 joystickManager.remove(evt.jdevice.which);
355 break;
356
357 case SDL_TEXTINPUT:
358 splitText(evt.text.timestamp, evt.text.text);
359 event = TextEvent(evt);
360 break;
361
362 case SDL_WINDOWEVENT:
363 switch (evt.window.event) {
364 case SDL_WINDOWEVENT_CLOSE:
365 if (WindowEvent::isMainWindow(evt.window.windowID)) {
366 event = QuitEvent();
367 break;
368 }
369 [[fallthrough]];
370 default:
371 event = WindowEvent(evt);
372 break;
373 }
374 break;
375
376 case SDL_DROPFILE:
377 event = FileDropEvent(
379 SDL_free(evt.drop.file);
380 break;
381
382 case SDL_QUIT:
383 event = QuitEvent();
384 break;
385
386 default:
387 break;
388 }
389
390#if 0
391 if (event) {
392 std::cerr << "SDL event converted to: " << toString(event) << '\n';
393 } else {
394 std::cerr << "SDL event was of unknown type, not converted to an openMSX event\n";
395 }
396#endif
397
398 if (event) eventDistributor.distributeEvent(std::move(*event));
399}
400
401
403{
404 escapeGrabState = ESCAPE_GRAB_WAIT_CMD;
405 setGrabInput(grab);
406}
407
408int InputEventGenerator::signalEvent(const Event& event)
409{
410 std::visit(overloaded{
411 [&](const WindowEvent& e) {
412 const auto& evt = e.getSdlWindowEvent();
413 if (e.isMainWindow() &&
414 evt.event == one_of(SDL_WINDOWEVENT_FOCUS_GAINED, SDL_WINDOWEVENT_FOCUS_LOST)) {
415 switch (escapeGrabState) {
416 case ESCAPE_GRAB_WAIT_CMD:
417 // nothing
418 break;
419 case ESCAPE_GRAB_WAIT_LOST:
420 if (evt.event == SDL_WINDOWEVENT_FOCUS_LOST) {
421 escapeGrabState = ESCAPE_GRAB_WAIT_GAIN;
422 }
423 break;
424 case ESCAPE_GRAB_WAIT_GAIN:
425 if (evt.event == SDL_WINDOWEVENT_FOCUS_GAINED) {
426 escapeGrabState = ESCAPE_GRAB_WAIT_CMD;
427 }
428 setGrabInput(true);
429 break;
430 default:
432 }
433 }
434 },
435 [](const EventBase&) { UNREACHABLE; }
436 }, event);
437 return 0;
438}
439
440void InputEventGenerator::setGrabInput(bool grab) const
441{
442 SDL_SetRelativeMouseMode(grab ? SDL_TRUE : SDL_FALSE);
443
444 // TODO is this still the correct place in SDL2
445 // TODO get the SDL_window
446 //SDL_Window* window = ...;
447 //SDL_SetWindowGrab(window, grab ? SDL_TRUE : SDL_FALSE);
448}
449
450
451// class EscapeGrabCmd
452
453InputEventGenerator::EscapeGrabCmd::EscapeGrabCmd(
454 CommandController& commandController_)
455 : Command(commandController_, "escape_grab")
456{
457}
458
459void InputEventGenerator::EscapeGrabCmd::execute(
460 std::span<const TclObject> /*tokens*/, TclObject& /*result*/)
461{
462 auto& inputEventGenerator = OUTER(InputEventGenerator, escapeGrabCmd);
463 if (inputEventGenerator.grabInput.getBoolean()) {
464 inputEventGenerator.escapeGrabState =
465 InputEventGenerator::ESCAPE_GRAB_WAIT_LOST;
466 inputEventGenerator.setGrabInput(false);
467 }
468}
469
470std::string InputEventGenerator::EscapeGrabCmd::help(
471 std::span<const TclObject> /*tokens*/) const
472{
473 return "Temporarily release input grab.";
474}
475
476} // namespace openmsx
BaseSetting * setting
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).
InputEventGenerator(const InputEventGenerator &)=delete
void updateGrab(bool grab)
Must be called when 'grabinput' or 'fullscreen' setting changes.
void wait()
Wait for event(s) and handle it.
void add(int deviceIndex)
IntegerSetting * getJoyDeadZoneSetting(JoystickId joyId) const
void remove(int instanceId)
std::optional< JoystickId > translateSdlInstanceId(SDL_Event &evt) const
static KeyDownEvent create(SDL_Keycode code, SDL_Keymod mod=KMOD_NONE)
Definition Event.hh:81
bool isMainWindow() const
Definition Event.hh:214
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:11
std::string toString(const BooleanInput &input)
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, Rs232NetEvent, ImGuiDelayedActionEvent, ImGuiActiveEvent > Event
Definition Event.hh:444
uint32_t next(octet_iterator &it)
#define OUTER(type, member)
Definition outer.hh:42
#define UNREACHABLE
constexpr void repeat(T n, Op op)
Repeat the given operation 'op' 'n' times.
Definition xrange.hh:147