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::Save::NO)
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 using enum OsdControlEvent::Button;
122 for (auto b : {LEFT, RIGHT, UP, DOWN, A, B}) {
123 if (deltaState & (1 << to_underlying(b))) {
124 if (newState & (1 << to_underlying(b))) {
125 eventDistributor.distributeEvent(OsdControlReleaseEvent(b));
126 } else {
127 eventDistributor.distributeEvent(OsdControlPressEvent(b));
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 using enum OsdControlEvent::Button;
140 case 0:
141 return std::pair{1u << to_underlying(LEFT),
142 1u << to_underlying(RIGHT)};
143 case 1:
144 return std::pair{1u << to_underlying(UP),
145 1u << to_underlying(DOWN)};
146 default:
147 // Ignore all other axis (3D joysticks and flight joysticks may
148 // have more than 2 axis)
149 return std::pair{0u, 0u};
150 }
151 }();
152
153 if (value > 0) {
154 // release negative button, press positive button
155 setNewOsdControlButtonState(
156 (osdControlButtonsState | neg_button) & ~pos_button);
157 } else if (value < 0) {
158 // press negative button, release positive button
159 setNewOsdControlButtonState(
160 (osdControlButtonsState | pos_button) & ~neg_button);
161 } else {
162 // release both buttons
163 setNewOsdControlButtonState(
164 osdControlButtonsState | neg_button | pos_button);
165 }
166}
167
168void InputEventGenerator::triggerOsdControlEventsFromJoystickHat(int value)
169{
170 using enum OsdControlEvent::Button;
171 unsigned dir = 0;
172 if (!(value & SDL_HAT_UP )) dir |= 1 << to_underlying(UP);
173 if (!(value & SDL_HAT_DOWN )) dir |= 1 << to_underlying(DOWN);
174 if (!(value & SDL_HAT_LEFT )) dir |= 1 << to_underlying(LEFT);
175 if (!(value & SDL_HAT_RIGHT)) dir |= 1 << to_underlying(RIGHT);
176 unsigned ab = osdControlButtonsState & ((1 << to_underlying(A)) |
177 (1 << to_underlying(B)));
178 setNewOsdControlButtonState(ab | dir);
179}
180
181void InputEventGenerator::osdControlChangeButton(bool down, unsigned changedButtonMask)
182{
183 auto newButtonState = down
184 ? osdControlButtonsState & ~changedButtonMask
185 : osdControlButtonsState | changedButtonMask;
186 setNewOsdControlButtonState(newButtonState);
187}
188
189void InputEventGenerator::triggerOsdControlEventsFromJoystickButtonEvent(unsigned button, bool down)
190{
191 using enum OsdControlEvent::Button;
192 osdControlChangeButton(
193 down,
194 ((button & 1) ? (1 << to_underlying(B))
195 : (1 << to_underlying(A))));
196}
197
198void InputEventGenerator::triggerOsdControlEventsFromKeyEvent(SDLKey key, bool repeat)
199{
200 unsigned buttonMask = [&] {
201 switch (key.sym.sym) {
202 using enum OsdControlEvent::Button;
203 case SDLK_LEFT: return 1 << to_underlying(LEFT);
204 case SDLK_RIGHT: return 1 << to_underlying(RIGHT);
205 case SDLK_UP: return 1 << to_underlying(UP);
206 case SDLK_DOWN: return 1 << to_underlying(DOWN);
207 case SDLK_SPACE: return 1 << to_underlying(A);
208 case SDLK_RETURN: return 1 << to_underlying(A);
209 case SDLK_ESCAPE: return 1 << to_underlying(B);
210 default: return 0;
211 }
212 }();
213 if (buttonMask) {
214 if (repeat) {
215 osdControlChangeButton(!key.down, buttonMask);
216 }
217 osdControlChangeButton(key.down, buttonMask);
218 }
219}
220
221static constexpr Uint16 normalizeModifier(SDL_Keycode sym, Uint16 mod)
222{
223 // Apparently modifier-keys also have the corresponding
224 // modifier attribute set. See here for a discussion:
225 // https://github.com/openMSX/openMSX/issues/1202
226 // As a solution, on pure modifier keys, we now clear the
227 // modifier attributes.
228 return (sym == one_of(SDLK_LCTRL, SDLK_LSHIFT, SDLK_LALT, SDLK_LGUI,
229 SDLK_RCTRL, SDLK_RSHIFT, SDLK_RALT, SDLK_RGUI,
230 SDLK_MODE))
231 ? 0
232 : mod;
233}
234
235void InputEventGenerator::handleKeyDown(const SDL_KeyboardEvent& key, uint32_t unicode)
236{
237 /*if (PLATFORM_ANDROID && evt.key.keysym.sym == SDLK_WORLD_93) {
238 event = JoystickButtonDownEvent(0, 0);
239 triggerOsdControlEventsFromJoystickButtonEvent(
240 0, false);
241 androidButtonA = true;
242 } else if (PLATFORM_ANDROID && evt.key.keysym.sym == SDLK_WORLD_94) {
243 event = JoystickButtonDownEvent(0, 1);
244 triggerOsdControlEventsFromJoystickButtonEvent(
245 1, false);
246 androidButtonB = true;
247 } else*/ {
248 SDL_Event evt;
249 evt.key = SDL_KeyboardEvent{};
250 memcpy(&evt.key, &key, sizeof(key));
251 evt.key.keysym.mod = normalizeModifier(key.keysym.sym, key.keysym.mod);
252 evt.key.keysym.unused = unicode;
253 Event event = KeyDownEvent(evt);
254 triggerOsdControlEventsFromKeyEvent(SDLKey{key.keysym, true}, key.repeat);
255 eventDistributor.distributeEvent(std::move(event));
256 }
257}
258
259void InputEventGenerator::splitText(uint32_t timestamp, const char* utf8)
260{
261 while (true) {
262 auto unicode = utf8::unchecked::next(utf8);
263 if (unicode == 0) return;
264 eventDistributor.distributeEvent(
265 KeyDownEvent::create(timestamp, unicode));
266 }
267}
268
269void InputEventGenerator::handle(const SDL_Event& evt)
270{
271 std::optional<Event> event;
272 switch (evt.type) {
273 case SDL_KEYUP:
274 // Virtual joystick of SDL Android port does not have joystick
275 // buttons. It has however up to 6 virtual buttons that can be
276 // mapped to SDL keyboard events. Two of these virtual buttons
277 // will be mapped to keys SDLK_WORLD_93 and 94 and are
278 // interpreted here as joystick buttons (respectively button 0
279 // and 1).
280 // TODO Android code should be rewritten for SDL2
281 /*if (PLATFORM_ANDROID && evt.key.keysym.sym == SDLK_WORLD_93) {
282 event = JoystickButtonUpEvent(0, 0);
283 triggerOsdControlEventsFromJoystickButtonEvent(0, true);
284 androidButtonA = false;
285 } else if (PLATFORM_ANDROID && evt.key.keysym.sym == SDLK_WORLD_94) {
286 event = JoystickButtonUpEvent(0, 1);
287 triggerOsdControlEventsFromJoystickButtonEvent(1, true);
288 androidButtonB = false;
289 } else*/ {
290 SDL_Event e;
291 e.key = SDL_KeyboardEvent{};
292 memcpy(&e.key, &evt.key, sizeof(evt.key));
293 e.key.keysym.mod = normalizeModifier(evt.key.keysym.sym, evt.key.keysym.mod);
294 event = KeyUpEvent(e);
295 bool down = false;
296 bool repeat = false;
297 triggerOsdControlEventsFromKeyEvent(SDLKey{e.key.keysym, down}, repeat);
298 }
299 break;
300 case SDL_KEYDOWN:
301 handleKeyDown(evt.key, 0);
302 break;
303
304 case SDL_MOUSEBUTTONUP:
305 event = MouseButtonUpEvent(evt);
306 break;
307 case SDL_MOUSEBUTTONDOWN:
308 event = MouseButtonDownEvent(evt);
309 break;
310 case SDL_MOUSEWHEEL:
311 event = MouseWheelEvent(evt);
312 break;
313 case SDL_MOUSEMOTION:
314 event = MouseMotionEvent(evt);
315 break;
316
317 case SDL_JOYBUTTONUP:
318 if (joystickManager.translateSdlInstanceId(const_cast<SDL_Event&>(evt))) {
319 event = JoystickButtonUpEvent(evt);
320 triggerOsdControlEventsFromJoystickButtonEvent(
321 evt.jbutton.button, false);
322 }
323 break;
324 case SDL_JOYBUTTONDOWN:
325 if (joystickManager.translateSdlInstanceId(const_cast<SDL_Event&>(evt))) {
326 event = JoystickButtonDownEvent(evt);
327 triggerOsdControlEventsFromJoystickButtonEvent(
328 evt.jbutton.button, true);
329 }
330 break;
331 case SDL_JOYAXISMOTION: {
332 if (auto joyId = joystickManager.translateSdlInstanceId(const_cast<SDL_Event&>(evt))) {
333 const auto* setting = joystickManager.getJoyDeadZoneSetting(*joyId);
334 assert(setting);
335 int deadZone = setting->getInt();
336 int threshold = (deadZone * 32768) / 100;
337 auto value = (evt.jaxis.value < -threshold) ? evt.jaxis.value
338 : (evt.jaxis.value > threshold) ? evt.jaxis.value
339 : 0;
340 event = JoystickAxisMotionEvent(evt);
341 triggerOsdControlEventsFromJoystickAxisMotion(
342 evt.jaxis.axis, value);
343 }
344 break;
345 }
346 case SDL_JOYHATMOTION:
347 if (auto joyId = joystickManager.translateSdlInstanceId(const_cast<SDL_Event&>(evt))) {
348 event = JoystickHatEvent(evt);
349 triggerOsdControlEventsFromJoystickHat(evt.jhat.value);
350 }
351 break;
352
353 case SDL_JOYDEVICEADDED:
354 joystickManager.add(evt.jdevice.which);
355 break;
356
357 case SDL_JOYDEVICEREMOVED:
358 joystickManager.remove(evt.jdevice.which);
359 break;
360
361 case SDL_TEXTINPUT:
362 splitText(evt.text.timestamp, evt.text.text);
363 event = TextEvent(evt);
364 break;
365
366 case SDL_WINDOWEVENT:
367 switch (evt.window.event) {
368 case SDL_WINDOWEVENT_CLOSE:
369 if (WindowEvent::isMainWindow(evt.window.windowID)) {
370 event = QuitEvent();
371 break;
372 }
373 [[fallthrough]];
374 default:
375 event = WindowEvent(evt);
376 break;
377 }
378 break;
379
380 case SDL_DROPFILE:
381 event = FileDropEvent(
383 SDL_free(evt.drop.file);
384 break;
385
386 case SDL_QUIT:
387 event = QuitEvent();
388 break;
389
390 default:
391 break;
392 }
393
394#if 0
395 if (event) {
396 std::cerr << "SDL event converted to: " << toString(event) << '\n';
397 } else {
398 std::cerr << "SDL event was of unknown type, not converted to an openMSX event\n";
399 }
400#endif
401
402 if (event) eventDistributor.distributeEvent(std::move(*event));
403}
404
405
407{
408 escapeGrabState = ESCAPE_GRAB_WAIT_CMD;
409 setGrabInput(grab);
410}
411
412bool InputEventGenerator::signalEvent(const Event& event)
413{
414 std::visit(overloaded{
415 [&](const WindowEvent& e) {
416 const auto& evt = e.getSdlWindowEvent();
417 if (e.isMainWindow() &&
418 evt.event == one_of(SDL_WINDOWEVENT_FOCUS_GAINED, SDL_WINDOWEVENT_FOCUS_LOST)) {
419 switch (escapeGrabState) {
420 case ESCAPE_GRAB_WAIT_CMD:
421 // nothing
422 break;
423 case ESCAPE_GRAB_WAIT_LOST:
424 if (evt.event == SDL_WINDOWEVENT_FOCUS_LOST) {
425 escapeGrabState = ESCAPE_GRAB_WAIT_GAIN;
426 }
427 break;
428 case ESCAPE_GRAB_WAIT_GAIN:
429 if (evt.event == SDL_WINDOWEVENT_FOCUS_GAINED) {
430 escapeGrabState = ESCAPE_GRAB_WAIT_CMD;
431 }
432 setGrabInput(true);
433 break;
434 default:
436 }
437 }
438 },
439 [](const EventBase&) { UNREACHABLE; }
440 }, event);
441 return false;
442}
443
444void InputEventGenerator::setGrabInput(bool grab) const
445{
446 SDL_SetRelativeMouseMode(grab ? SDL_TRUE : SDL_FALSE);
447
448 // TODO is this still the correct place in SDL2
449 // TODO get the SDL_window
450 //SDL_Window* window = ...;
451 //SDL_SetWindowGrab(window, grab ? SDL_TRUE : SDL_FALSE);
452}
453
454
455// class EscapeGrabCmd
456
457InputEventGenerator::EscapeGrabCmd::EscapeGrabCmd(
458 CommandController& commandController_)
459 : Command(commandController_, "escape_grab")
460{
461}
462
463void InputEventGenerator::EscapeGrabCmd::execute(
464 std::span<const TclObject> /*tokens*/, TclObject& /*result*/)
465{
466 auto& inputEventGenerator = OUTER(InputEventGenerator, escapeGrabCmd);
467 if (inputEventGenerator.grabInput.getBoolean()) {
468 inputEventGenerator.escapeGrabState =
469 InputEventGenerator::ESCAPE_GRAB_WAIT_LOST;
470 inputEventGenerator.setGrabInput(false);
471 }
472}
473
474std::string InputEventGenerator::EscapeGrabCmd::help(
475 std::span<const TclObject> /*tokens*/) const
476{
477 return "Temporarily release input grab.";
478}
479
480} // 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=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(CommandController &commandController, EventDistributor &eventDistributor, GlobalSettings &globalSettings)
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:445
std::array< const EDStorage, 4 > A
uint32_t next(octet_iterator &it)
#define OUTER(type, member)
Definition outer.hh:42
constexpr auto to_underlying(E e) noexcept
Definition stl.hh:468
#define UNREACHABLE
constexpr void repeat(T n, Op op)
Repeat the given operation 'op' 'n' times.
Definition xrange.hh:147