1 /** 2 * Retrograde Engine 3 * 4 * Authors: 5 * Mike Bierlee, m.bierlee@lostmoment.com 6 * Copyright: 2014-2021 Mike Bierlee 7 * License: 8 * This software is licensed under the terms of the MIT license. 9 * The full terms of the license can be found in the LICENSE.txt file. 10 */ 11 12 module retrograde.input; 13 14 import retrograde.messaging; 15 import retrograde.stringid; 16 17 import poodinis; 18 19 import std.signals; 20 import std.stdio; 21 import std.format; 22 import std.math; 23 import std.traits; 24 import std.algorithm; 25 import std.typecons; 26 27 enum InputEvent : StringId { 28 JOYSTICK_AXIS_MOVEMENT = sid("ev_joystick_axis_movement"), 29 JOYSTICK_BALL_MOVEMENT = sid("ev_joystick_ball_movement"), 30 JOYSTICK_HAT = sid("ev_joystick_hat"), 31 JOYSTICK_BUTTON = sid("ev_joystick_button"), 32 JOYSTICK_ADDED = sid("ev_joystick_added"), 33 JOYSTICK_REMOVED = sid("ev_joystick_removed"), 34 KEYBOARD_KEY = sid("ev_keyboard_key"), 35 MOUSE_MOTION = sid("ev_mouse_motion"), 36 MOUSE_BUTTON = sid("ev_mouse_button"), 37 MOUSE_WHEEL = sid("ev_mouse_wheel") 38 } 39 40 public void registerInputEventDebugSids(SidMap sidMap) { 41 sidMap.add("ev_joystick_axis_movement"); 42 sidMap.add("ev_joystick_ball_movement"); 43 sidMap.add("ev_joystick_hat"); 44 sidMap.add("ev_joystick_button"); 45 sidMap.add("ev_joystick_added"); 46 sidMap.add("ev_joystick_removed"); 47 sidMap.add("ev_keyboard_key"); 48 sidMap.add("ev_mouse_motion"); 49 sidMap.add("ev_mouse_button"); 50 sidMap.add("ev_mouse_wheel"); 51 } 52 53 class InputMessageData : MessageData { 54 public Device device; 55 } 56 57 class JoystickAxisEventData : InputMessageData { 58 public ubyte axis; 59 } 60 61 enum JoystickBallAxis { 62 X, Y 63 } 64 65 class JoystickBallEventData : InputMessageData { 66 public ubyte ball; 67 public JoystickBallAxis axis; 68 } 69 70 enum JoystickHatPosition { 71 LEFT_UP, 72 LEFT, 73 LEFT_DOWN, 74 UP, 75 CENTERED, 76 DOWN, 77 RIGHT_UP, 78 RIGHT, 79 RIGHT_DOWN 80 } 81 82 class JoystickHatEventData : InputMessageData { 83 public ubyte hat; 84 public JoystickHatPosition postion; 85 } 86 87 class JoystickButtonEventData : InputMessageData { 88 public ubyte button; 89 } 90 91 enum KeyboardKeyCode { 92 A, 93 AC_BACK, 94 AC_BOOKMARKS, 95 AC_FORWARD, 96 AC_HOME, 97 AC_REFRESH, 98 AC_SEARCH, 99 AC_STOP, 100 AGAIN, 101 ALTERASE, 102 APOSTROPHE, 103 APP1, 104 APP2, 105 APPLICATION, 106 AUDIOMUTE, 107 AUDIONEXT, 108 AUDIOPLAY, 109 AUDIOPREV, 110 AUDIOSTOP, 111 B, 112 BACKSLASH, 113 BACKSPACE, 114 BRIGHTNESSDOWN, 115 BRIGHTNESSUP, 116 C, 117 CALCULATOR, 118 CANCEL, 119 CAPSLOCK, 120 CLEAR, 121 CLEARAGAIN, 122 COMMA, 123 COMPUTER, 124 COPY, 125 CRSEL, 126 CURRENCYSUBUNIT, 127 CURRENCYUNIT, 128 CUT, 129 D, 130 DECIMALSEPARATOR, 131 DELETE, 132 DISPLAYSWITCH, 133 DOWN, 134 E, 135 EIGHT, 136 EJECT, 137 END, 138 EQUALS, 139 ESCAPE, 140 EXECUTE, 141 EXSEL, 142 F, 143 F1, 144 F10, 145 F11, 146 F12, 147 F13, 148 F14, 149 F15, 150 F16, 151 F17, 152 F18, 153 F19, 154 F2, 155 F20, 156 F21, 157 F22, 158 F23, 159 F24, 160 F3, 161 F4, 162 F5, 163 F6, 164 F7, 165 F8, 166 F9, 167 FIND, 168 FIVE, 169 FOUR, 170 G, 171 GRAVE, 172 H, 173 HELP, 174 HOME, 175 I, 176 INSERT, 177 INTERNATIONAL1, 178 INTERNATIONAL2, 179 INTERNATIONAL3, 180 INTERNATIONAL4, 181 INTERNATIONAL5, 182 INTERNATIONAL6, 183 INTERNATIONAL7, 184 INTERNATIONAL8, 185 INTERNATIONAL9, 186 J, 187 K, 188 KBDILLUMDOWN, 189 KBDILLUMTOGGLE, 190 KBDILLUMUP, 191 KEYPAD_00, 192 KEYPAD_000, 193 KEYPAD_COMMA, 194 KEYPAD_DIVIDE, 195 KEYPAD_EIGHT, 196 KEYPAD_ENTER, 197 KEYPAD_EQUALS, 198 KEYPAD_EQUALSAS400, 199 KEYPAD_FIVE, 200 KEYPAD_FOUR, 201 KEYPAD_MINUS, 202 KEYPAD_MULTIPLY, 203 KEYPAD_NINE, 204 KEYPAD_ONE, 205 KEYPAD_PERIOD, 206 KEYPAD_PLUS, 207 KEYPAD_SEVEN, 208 KEYPAD_SIX, 209 KEYPAD_THREE, 210 KEYPAD_TWO, 211 KEYPAD_ZERO, 212 KP_A, 213 KP_AMPERSAND, 214 KP_AT, 215 KP_B, 216 KP_BACKSPACE, 217 KP_BINARY, 218 KP_C, 219 KP_CLEAR, 220 KP_CLEARENTRY, 221 KP_COLON, 222 KP_D, 223 KP_DBLAMPERSAND, 224 KP_DBLVERTICALBAR, 225 KP_DECIMAL, 226 KP_E, 227 KP_EXCLAM, 228 KP_F, 229 KP_GREATER, 230 KP_HASH, 231 KP_HEXADECIMAL, 232 KP_LEFTBRACE, 233 KP_LEFTPAREN, 234 KP_LESS, 235 KP_MEMADD, 236 KP_MEMCLEAR, 237 KP_MEMDIVIDE, 238 KP_MEMMULTIPLY, 239 KP_MEMRECALL, 240 KP_MEMSTORE, 241 KP_MEMSUBTRACT, 242 KP_OCTAL, 243 KP_PERCENT, 244 KP_PLUSMINUS, 245 KP_POWER, 246 KP_RIGHTBRACE, 247 KP_RIGHTPAREN, 248 KP_SPACE, 249 KP_TAB, 250 KP_VERTICALBAR, 251 KP_XOR, 252 L, 253 LALT, 254 LANG1, 255 LANG2, 256 LANG3, 257 LANG4, 258 LANG5, 259 LANG6, 260 LANG7, 261 LANG8, 262 LANG9, 263 LCTRL, 264 LEFT, 265 LEFTBRACKET, 266 LGUI, 267 LSHIFT, 268 M, 269 MAIL, 270 MEDIASELECT, 271 MENU, 272 MINUS, 273 MODE, 274 MUTE, 275 N, 276 NINE, 277 NONUSBACKSLASH, 278 NONUSHASH, 279 NUMLOCKCLEAR, 280 O, 281 ONE, 282 OPER, 283 OUT, 284 P, 285 PAGEDOWN, 286 PAGEUP, 287 PASTE, 288 PAUSE, 289 PERIOD, 290 POWER, 291 PRINTSCREEN, 292 PRIOR, 293 Q, 294 R, 295 RALT, 296 RCTRL, 297 RETURN, 298 RETURN2, 299 RGUI, 300 RIGHT, 301 RIGHTBRACKET, 302 RSHIFT, 303 S, 304 SCROLLLOCK, 305 SELECT, 306 SEMICOLON, 307 SEPARATOR, 308 SEVEN, 309 SIX, 310 SLASH, 311 SLEEP, 312 SPACE, 313 STOP, 314 SYSREQ, 315 T, 316 TAB, 317 THOUSANDSSEPARATOR, 318 THREE, 319 TWO, 320 U, 321 UNDO, 322 UP, 323 V, 324 VOLUMEDOWN, 325 VOLUMEUP, 326 W, 327 WWW, 328 X, 329 Y, 330 Z, 331 ZERO 332 } 333 334 enum MouseButton { 335 LEFT, 336 MIDDLE, 337 RIGHT, 338 X1, 339 X2 340 } 341 342 enum KeyboardKeyModifier : int { 343 NONE = 1<<0, 344 LSHIFT = 1<<2, 345 RSHIFT = 1<<3, 346 LCTRL = 1<<4, 347 RCTRL = 1<<5, 348 LALT = 1<<6, 349 RALT = 1<<7, 350 LGUI = 1<<8, 351 RGUI = 1<<9, 352 NUM = 1<<10, 353 CAPS = 1<<11, 354 MODE = 1<<12, 355 CTRL = 1<<13, 356 SHIFT = 1<<14, 357 ALT = 1<<15, 358 GUI = 1<<16 359 } 360 361 alias InvertMagnitude = Flag!"InvertMagnitude"; 362 363 class KeyboardKeyEventData : InputMessageData { 364 public KeyboardKeyCode scanCode; 365 public KeyboardKeyModifier modifiers; 366 } 367 368 enum MouseAxis { 369 X, Y 370 } 371 372 class MouseMotionEventData : InputMessageData { 373 public MouseAxis axis; 374 public int absolutePosition; 375 } 376 377 class MouseButtonEventData : InputMessageData { 378 public MouseButton button; 379 } 380 381 struct EventMappingKey { 382 public StringId eventName; 383 public uint componentOne; 384 public uint componentTwo; 385 } 386 387 class RawInputEventChannel : EventChannel {} 388 class MappedInputCommandChannel : CommandChannel {} 389 390 struct Device { 391 DeviceType type; 392 int id; 393 } 394 395 enum DeviceType { 396 unknown, 397 joystick, 398 keyboard, 399 mouse 400 } 401 402 class MappedInputCommandData : InputMessageData {} 403 404 class InputHandler { 405 406 @Autowire 407 private MappedInputCommandChannel mappedInputCommandChannel; 408 409 @Autowire 410 private RawInputEventChannel rawInputEventChannel; 411 412 private const(Event)[] eventQueue; 413 private StringId[][EventMappingKey] eventMappings; 414 private double[ubyte] axisDeadzones; 415 private StringId[] inputEvents; 416 private bool[EventMappingKey] invertMagnitudeMap; 417 418 public this() { 419 inputEvents = [EnumMembers!InputEvent]; 420 } 421 422 public void initialize() { 423 rawInputEventChannel.connect(&queueEventHandlerEvent); 424 } 425 426 private void queueEventHandlerEvent(const(Event) event) { 427 if (inputEvents.canFind(event.type)) { 428 eventQueue ~= event; 429 } 430 } 431 432 public void handleEvents() { 433 foreach (event; eventQueue) { 434 auto eventKey = createMappingKey(event); 435 auto mappedEvents = eventKey in eventMappings; 436 if (mappedEvents) { 437 double magnitude = event.magnitude; 438 if (event.type == InputEvent.JOYSTICK_AXIS_MOVEMENT) { 439 magnitude = calculateAxisDeadzoneMagnitude(event); 440 } 441 442 auto invertMagnitude = eventKey in invertMagnitudeMap; 443 if (invertMagnitude !is null && *invertMagnitude == true) { 444 magnitude *= -1; 445 } 446 447 MappedInputCommandData mappedCommandData = null; 448 auto inputEventData = cast(InputMessageData) event.data; 449 if (inputEventData) { 450 mappedCommandData = new MappedInputCommandData(); 451 mappedCommandData.device = inputEventData.device; 452 } 453 454 foreach(mappedEvent; *mappedEvents) { 455 mappedInputCommandChannel.emit(Command(mappedEvent, magnitude, mappedCommandData)); 456 } 457 } 458 } 459 460 eventQueue.destroy(); 461 } 462 463 private double calculateAxisDeadzoneMagnitude(const ref Event event) { 464 auto magnitude = event.magnitude; 465 auto data = cast(JoystickAxisEventData) event.data; 466 auto deadzone = data.axis in axisDeadzones; 467 if (deadzone && abs(magnitude) < *deadzone) { 468 return 0; 469 } 470 471 return magnitude; 472 } 473 474 private EventMappingKey createMappingKey(const ref Event event) { 475 auto eventKey = EventMappingKey(event.type, 0); 476 477 switch (event.type) { 478 case InputEvent.JOYSTICK_AXIS_MOVEMENT: 479 auto data = cast(JoystickAxisEventData) event.data; 480 eventKey.componentOne = data.axis; 481 break; 482 case InputEvent.JOYSTICK_BALL_MOVEMENT: 483 auto data = cast(JoystickBallEventData) event.data; 484 eventKey.componentOne = data.ball; 485 eventKey.componentTwo = data.axis; 486 break; 487 case InputEvent.JOYSTICK_BUTTON: 488 auto data = cast(JoystickButtonEventData) event.data; 489 eventKey.componentOne = data.button; 490 break; 491 case InputEvent.JOYSTICK_HAT: 492 auto data = cast(JoystickHatEventData) event.data; 493 eventKey.componentOne = data.hat; 494 break; 495 case InputEvent.KEYBOARD_KEY: 496 auto data = cast(KeyboardKeyEventData) event.data; 497 eventKey.componentOne = data.scanCode; 498 break; 499 case InputEvent.MOUSE_MOTION: 500 auto data = cast(MouseMotionEventData) event.data; 501 eventKey.componentOne = data.axis; 502 break; 503 case InputEvent.MOUSE_BUTTON: 504 auto data = cast(MouseButtonEventData) event.data; 505 eventKey.componentOne = data.button; 506 break; 507 default: 508 break; 509 } 510 511 return eventKey; 512 } 513 514 public void setEventMapping(EventMappingKey sourceKey, StringId targetCommand, InvertMagnitude invertMagnitude = InvertMagnitude.no) { 515 setEventMapping(sourceKey, [targetCommand], invertMagnitude); 516 } 517 518 public void setEventMapping(EventMappingKey sourceKey, StringId[] targetCommands, InvertMagnitude invertMagnitude = InvertMagnitude.no) { 519 eventMappings[sourceKey] = targetCommands; 520 invertMagnitudeMap[sourceKey] = invertMagnitude; 521 } 522 523 public void setJoystickAxisDeadzone(ubyte axis, double deadzone) { 524 axisDeadzones[axis] = deadzone; 525 } 526 }