mirror of
https://github.com/mkxp-z/mkxp-z.git
synced 2025-08-04 22:15:33 +02:00
1363 lines
28 KiB
C++
1363 lines
28 KiB
C++
/*
|
|
** input.cpp
|
|
**
|
|
** This file is part of mkxp.
|
|
**
|
|
** Copyright (C) 2013 Jonas Kulla <Nyocurio@gmail.com>
|
|
**
|
|
** mkxp is free software: you can redistribute it and/or modify
|
|
** it under the terms of the GNU General Public License as published by
|
|
** the Free Software Foundation, either version 2 of the License, or
|
|
** (at your option) any later version.
|
|
**
|
|
** mkxp is distributed in the hope that it will be useful,
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
** GNU General Public License for more details.
|
|
**
|
|
** You should have received a copy of the GNU General Public License
|
|
** along with mkxp. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "input.h"
|
|
#include "config.h"
|
|
#include "sharedstate.h"
|
|
#include "eventthread.h"
|
|
#include "input/keybindings.h"
|
|
#include "util/exception.h"
|
|
#include "util/util.h"
|
|
|
|
#include <SDL_scancode.h>
|
|
#include <SDL_keyboard.h>
|
|
#include <SDL_mouse.h>
|
|
#include <SDL_clipboard.h>
|
|
|
|
#include <vector>
|
|
#include <cmath>
|
|
#include <unordered_map>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
#define BUTTON_CODE_COUNT 24
|
|
|
|
#define m(vk,sc) { vk, SDL_SCANCODE_##sc }
|
|
std::unordered_map<int, int> vKeyToScancode{
|
|
// 0x01 LEFT MOUSE
|
|
// 0x02 RIGHT MOUSE
|
|
m(0x03, CANCEL),
|
|
// 0x04 MIDDLE MOUSE
|
|
// 0x05 XBUTTON 1
|
|
// 0x06 XBUTTON 2
|
|
// 0x07 undef
|
|
m(0x08, BACKSPACE),
|
|
m(0x09, TAB),
|
|
// 0x0a reserved
|
|
// 0x0b reserved
|
|
m(0x0c, CLEAR),
|
|
m(0x0d, RETURN),
|
|
// 0x0e undefined
|
|
// 0x0f undefined
|
|
// 0x10 SHIFT (both)
|
|
// 0x11 CONTROL (both)
|
|
// 0x12 ALT (both)
|
|
m(0x13, PAUSE),
|
|
m(0x14, CAPSLOCK),
|
|
// 0x15 KANA, HANGUL
|
|
// 0x16 undefined
|
|
// 0x17 JUNJA
|
|
// 0x18 FINAL
|
|
// 0x19 HANJA, KANJI
|
|
// 0x1a undefined
|
|
m(0x1b, ESCAPE),
|
|
// 0x1c CONVERT
|
|
// 0x1d NONCONVERT
|
|
// 0x1e ACCEPT
|
|
// 0x1f MODECHANGE
|
|
m(0x20, SPACE),
|
|
m(0x21, PAGEUP),
|
|
m(0x22, PAGEDOWN),
|
|
m(0x23, END),
|
|
m(0x24, HOME),
|
|
m(0x25, LEFT),
|
|
m(0x26, UP),
|
|
m(0x27, RIGHT),
|
|
m(0x28, DOWN),
|
|
m(0x29, SELECT),
|
|
// 0x2A print
|
|
m(0x2b, EXECUTE),
|
|
m(0x2c, PRINTSCREEN),
|
|
m(0x2d, INSERT),
|
|
m(0x2e, DELETE),
|
|
m(0x2f, HELP),
|
|
m(0x30, 0), m(0x31, 1),
|
|
m(0x32, 2), m(0x33, 3),
|
|
m(0x34, 4), m(0x35, 5),
|
|
m(0x36, 6), m(0x37, 7),
|
|
m(0x38, 8), m(0x39, 9),
|
|
// 0x3a-0x40 undefined
|
|
m(0x41, A), m(0x42, B),
|
|
m(0x43, C), m(0x44, D),
|
|
m(0x45, E), m(0x46, F),
|
|
m(0x47, G), m(0x48, H),
|
|
m(0x49, I), m(0x4a, J),
|
|
m(0x4b, K), m(0x4c, L),
|
|
m(0x4d, M), m(0x4e, N),
|
|
m(0x4f, O), m(0x50, P),
|
|
m(0x51, Q), m(0x52, R),
|
|
m(0x53, S), m(0x54, T),
|
|
m(0x55, U), m(0x56, V),
|
|
m(0x57, W), m(0x58, X),
|
|
m(0x59, Y), m(0x5a, Z),
|
|
m(0x5b, LGUI), m(0x5c, RGUI),
|
|
m(0x5d, APPLICATION),
|
|
// 0x5e reserved
|
|
m(0x5f, SLEEP),
|
|
m(0x60, KP_0), m(0x61, KP_1),
|
|
m(0x62, KP_2), m(0x63, KP_3),
|
|
m(0x64, KP_4), m(0x65, KP_5),
|
|
m(0x66, KP_6), m(0x67, KP_7),
|
|
m(0x68, KP_8), m(0x69, KP_9),
|
|
m(0x6a, KP_MULTIPLY),
|
|
m(0x6b, KP_PLUS),
|
|
m(0x6c, RETURN), // SEPARATOR
|
|
m(0x6d, KP_MINUS),
|
|
m(0x6e, KP_DECIMAL),
|
|
m(0x6f, KP_DIVIDE),
|
|
m(0x70, F1), m(0x71, F2),
|
|
m(0x72, F3), m(0x73, F4),
|
|
m(0x74, F5), m(0x75, F6),
|
|
m(0x76, F7), m(0x77, F8),
|
|
m(0x78, F9), m(0x79, F10),
|
|
m(0x7a, F11), m(0x7b, F12),
|
|
m(0x7c, F13), m(0x7d, F14),
|
|
m(0x7e, F15), m(0x7f, F16),
|
|
m(0x80, F17), m(0x81, F18),
|
|
m(0x82, F19), m(0x83, F20),
|
|
m(0x84, F21), m(0x85, F22),
|
|
m(0x86, F23), m(0x87, F24),
|
|
// 0x88-0x8f unassigned
|
|
m(0x90, NUMLOCKCLEAR),
|
|
m(0x91, SCROLLLOCK),
|
|
// 0x92-0x96 oem specific
|
|
// 0x97-0x9f unassigned
|
|
m(0xa0, LSHIFT),
|
|
m(0xa1, RSHIFT),
|
|
m(0xa2, LCTRL),
|
|
m(0xa3, RCTRL),
|
|
m(0xa4, LALT),
|
|
m(0xa5, RALT),
|
|
m(0xa6, AC_BACK),
|
|
m(0xa7, AC_FORWARD),
|
|
m(0xa8, AC_REFRESH),
|
|
m(0xa9, AC_STOP),
|
|
m(0xaa, AC_SEARCH),
|
|
// 0xab BROWSER_FAVORITES
|
|
m(0xac, AC_HOME),
|
|
m(0xad, AUDIOMUTE),
|
|
m(0xae, VOLUMEDOWN),
|
|
m(0xaf, VOLUMEUP),
|
|
m(0xb0, AUDIONEXT),
|
|
m(0xb1, AUDIOPREV),
|
|
m(0xb2, AUDIOSTOP),
|
|
m(0xb3, AUDIOPLAY),
|
|
m(0xb4, MAIL),
|
|
m(0xb5, MEDIASELECT),
|
|
// 0xb6 LAUNCH_APP1
|
|
// 0xb7 LAUNCH_APP2
|
|
// 0xb8-0xb9 reserved
|
|
|
|
// Everything below here is OEM
|
|
// and can vary by country
|
|
m(0xba, SEMICOLON),
|
|
m(0xbb, EQUALS),
|
|
m(0xbc, COMMA),
|
|
m(0xbd, MINUS),
|
|
m(0xbe, PERIOD),
|
|
m(0xbf, SLASH),
|
|
m(0xc0, GRAVE),
|
|
// 0xc1-0xd7 reserved
|
|
// 0xd8-0xda unassigned
|
|
m(0xdb, LEFTBRACKET),
|
|
m(0xdc, BACKSLASH),
|
|
m(0xdd, RIGHTBRACKET),
|
|
m(0xde, APOSTROPHE),
|
|
// 0xdf OEM_8
|
|
// 0xe0 reserved
|
|
// 0xe1 oem-specific
|
|
// 0xe2 OEM_102
|
|
// 0xe3-0xe4 oem-specific
|
|
// 0xe5 PROCESSKEY
|
|
// 0xe6 oem-specific
|
|
// 0xe7 PACKET
|
|
// 0xe8 unassigned
|
|
// 0xe9-0xf5 oem_specific
|
|
// 0xf6 ATTN
|
|
m(0xf7, CRSEL),
|
|
m(0xf8, EXSEL),
|
|
// 0xf9 EREOF
|
|
m(0xfa, AUDIOPLAY), // PLAY, guessing
|
|
// 0xfb ZOOM
|
|
// 0xfc NONAME
|
|
// 0xfd PA1
|
|
m(0xfe, CLEAR)
|
|
};
|
|
#undef m
|
|
#define m(keycode) { #keycode, SDL_SCANCODE_##keycode }
|
|
#define m_number(keycode) { "NUMBER_" #keycode, SDL_SCANCODE_##keycode }
|
|
std::unordered_map<std::string, int> strToScancode{
|
|
m_number(0), m_number(1),
|
|
m_number(2), m_number(3),
|
|
m_number(4), m_number(5),
|
|
m_number(6), m_number(7),
|
|
m_number(8), m_number(9),
|
|
m(A),
|
|
m(AC_BACK),
|
|
m(AC_BOOKMARKS),
|
|
m(AC_FORWARD),
|
|
m(AC_HOME),
|
|
m(AC_REFRESH),
|
|
m(AC_SEARCH),
|
|
m(AC_STOP),
|
|
m(AGAIN),
|
|
m(ALTERASE),
|
|
m(APOSTROPHE),
|
|
m(APPLICATION),
|
|
m(AUDIOMUTE),
|
|
m(AUDIONEXT),
|
|
m(AUDIOPLAY),
|
|
m(AUDIOPREV),
|
|
m(AUDIOSTOP),
|
|
m(B),
|
|
m(BACKSLASH),
|
|
m(BACKSPACE),
|
|
m(BRIGHTNESSDOWN),
|
|
m(BRIGHTNESSUP),
|
|
m(C),
|
|
m(CALCULATOR),
|
|
m(CANCEL),
|
|
m(CAPSLOCK),
|
|
m(CLEAR),
|
|
m(CLEARAGAIN),
|
|
m(COMMA),
|
|
m(COMPUTER),
|
|
m(COPY),
|
|
m(CRSEL),
|
|
m(CURRENCYSUBUNIT),
|
|
m(CURRENCYUNIT),
|
|
m(CUT),
|
|
m(D),
|
|
m(DECIMALSEPARATOR),
|
|
m(DELETE),
|
|
m(DISPLAYSWITCH),
|
|
m(DOWN),
|
|
m(E),
|
|
m(EJECT),
|
|
m(END),
|
|
m(EQUALS),
|
|
m(ESCAPE),
|
|
m(EXECUTE),
|
|
m(EXSEL),
|
|
m(F),
|
|
m(F1), m(F2),
|
|
m(F3), m(F4),
|
|
m(F5), m(F6),
|
|
m(F7), m(F8),
|
|
m(F9), m(F10),
|
|
m(F11), m(F12),
|
|
m(F13), m(F14),
|
|
m(F15), m(F16),
|
|
m(F17), m(F18),
|
|
m(F19), m(F20),
|
|
m(F21), m(F22),
|
|
m(F23), m(F24),
|
|
m(FIND),
|
|
m(G),
|
|
m(GRAVE),
|
|
m(H),
|
|
m(HELP),
|
|
m(HOME),
|
|
m(I),
|
|
m(INSERT),
|
|
m(J),
|
|
m(K),
|
|
m(KBDILLUMDOWN),
|
|
m(KBDILLUMTOGGLE),
|
|
m(KBDILLUMUP),
|
|
m(KP_0),
|
|
m(KP_00),
|
|
m(KP_000),
|
|
m(KP_1), m(KP_2),
|
|
m(KP_3), m(KP_4),
|
|
m(KP_5), m(KP_6),
|
|
m(KP_7), m(KP_8),
|
|
m(KP_9),
|
|
m(KP_A),
|
|
m(KP_AMPERSAND),
|
|
m(KP_AT),
|
|
m(KP_B),
|
|
m(KP_BACKSPACE),
|
|
m(KP_BINARY),
|
|
m(KP_C),
|
|
m(KP_CLEAR),
|
|
m(KP_CLEARENTRY),
|
|
m(KP_COLON),
|
|
m(KP_COMMA),
|
|
m(KP_D),
|
|
m(KP_DBLAMPERSAND),
|
|
m(KP_DBLVERTICALBAR),
|
|
m(KP_DECIMAL),
|
|
m(KP_DIVIDE),
|
|
m(KP_E),
|
|
m(KP_ENTER),
|
|
m(KP_EQUALS),
|
|
m(KP_EQUALSAS400),
|
|
m(KP_EXCLAM),
|
|
m(KP_F),
|
|
m(KP_GREATER),
|
|
m(KP_HASH),
|
|
m(KP_HEXADECIMAL),
|
|
m(KP_LEFTBRACE),
|
|
m(KP_LEFTPAREN),
|
|
m(KP_LESS),
|
|
m(KP_MEMADD),
|
|
m(KP_MEMCLEAR),
|
|
m(KP_MEMDIVIDE),
|
|
m(KP_MEMMULTIPLY),
|
|
m(KP_MEMRECALL),
|
|
m(KP_MEMSTORE),
|
|
m(KP_MEMSUBTRACT),
|
|
m(KP_MINUS),
|
|
m(KP_MULTIPLY),
|
|
m(KP_OCTAL),
|
|
m(KP_PERCENT),
|
|
m(KP_PERIOD),
|
|
m(KP_PLUS),
|
|
m(KP_PLUSMINUS),
|
|
m(KP_POWER),
|
|
m(KP_RIGHTBRACE),
|
|
m(KP_RIGHTPAREN),
|
|
m(KP_SPACE),
|
|
m(KP_TAB),
|
|
m(KP_VERTICALBAR),
|
|
m(KP_XOR),
|
|
m(L),
|
|
m(LALT),
|
|
m(LCTRL),
|
|
m(LEFT),
|
|
m(LEFTBRACKET),
|
|
m(LGUI),
|
|
m(LSHIFT),
|
|
m(M),
|
|
m(MAIL),
|
|
m(MEDIASELECT),
|
|
m(MENU),
|
|
m(MINUS),
|
|
m(MODE),
|
|
m(MUTE),
|
|
m(N),
|
|
m(NUMLOCKCLEAR),
|
|
m(O),
|
|
m(OPER),
|
|
m(OUT),
|
|
m(P),
|
|
m(PAGEDOWN),
|
|
m(PAGEUP),
|
|
m(PASTE),
|
|
m(PAUSE),
|
|
m(PERIOD),
|
|
m(POWER),
|
|
m(PRINTSCREEN),
|
|
m(PRIOR),
|
|
m(Q),
|
|
m(R),
|
|
m(RALT),
|
|
m(RCTRL),
|
|
m(RETURN),
|
|
m(RETURN2),
|
|
m(RGUI),
|
|
m(RIGHT),
|
|
m(RIGHTBRACKET),
|
|
m(RSHIFT),
|
|
m(S),
|
|
m(SCROLLLOCK),
|
|
m(SELECT),
|
|
m(SEMICOLON),
|
|
m(SEPARATOR),
|
|
m(SLASH),
|
|
m(SLEEP),
|
|
m(SPACE),
|
|
m(STOP),
|
|
m(SYSREQ),
|
|
m(T),
|
|
m(TAB),
|
|
m(THOUSANDSSEPARATOR),
|
|
m(U),
|
|
m(UNDO),
|
|
m(UNKNOWN),
|
|
m(UP),
|
|
m(V),
|
|
m(VOLUMEDOWN),
|
|
m(VOLUMEUP),
|
|
m(W),
|
|
m(WWW),
|
|
m(X),
|
|
m(Y),
|
|
m(Z),
|
|
m(INTERNATIONAL1),
|
|
m(INTERNATIONAL2),
|
|
m(INTERNATIONAL3),
|
|
m(INTERNATIONAL4),
|
|
m(INTERNATIONAL5),
|
|
m(INTERNATIONAL6),
|
|
m(INTERNATIONAL7),
|
|
m(INTERNATIONAL8),
|
|
m(INTERNATIONAL9),
|
|
m(LANG1), m(LANG2),
|
|
m(LANG3), m(LANG4),
|
|
m(LANG5), m(LANG6),
|
|
m(LANG7), m(LANG8),
|
|
m(LANG9),
|
|
m(NONUSBACKSLASH),
|
|
m(NONUSHASH)
|
|
};
|
|
|
|
#undef m
|
|
|
|
struct ButtonState
|
|
{
|
|
bool pressed;
|
|
bool triggered;
|
|
bool repeated;
|
|
bool released;
|
|
|
|
ButtonState()
|
|
: pressed(false),
|
|
triggered(false),
|
|
repeated(false),
|
|
released(false)
|
|
{}
|
|
};
|
|
|
|
struct KbBindingData
|
|
{
|
|
SDL_Scancode source;
|
|
Input::ButtonCode target;
|
|
};
|
|
|
|
struct Binding
|
|
{
|
|
Binding(Input::ButtonCode target = Input::None)
|
|
: target(target)
|
|
{}
|
|
|
|
virtual bool sourceActive() const = 0;
|
|
virtual bool sourceRepeatable() const = 0;
|
|
|
|
Input::ButtonCode target;
|
|
};
|
|
|
|
/* Keyboard binding */
|
|
struct KbBinding : public Binding
|
|
{
|
|
KbBinding() {}
|
|
|
|
KbBinding(const KbBindingData &data)
|
|
: Binding(data.target),
|
|
source(data.source)
|
|
{}
|
|
|
|
bool sourceActive() const
|
|
{
|
|
/* Special case aliases */
|
|
if (source == SDL_SCANCODE_LSHIFT)
|
|
return EventThread::keyStates[source]
|
|
|| EventThread::keyStates[SDL_SCANCODE_RSHIFT];
|
|
|
|
if (source == SDL_SCANCODE_RETURN)
|
|
return EventThread::keyStates[source]
|
|
|| EventThread::keyStates[SDL_SCANCODE_KP_ENTER];
|
|
|
|
return EventThread::keyStates[source];
|
|
}
|
|
|
|
bool sourceRepeatable() const
|
|
{
|
|
return true;
|
|
/*
|
|
return (source >= SDL_SCANCODE_A && source <= SDL_SCANCODE_0) ||
|
|
(source >= SDL_SCANCODE_RIGHT && source <= SDL_SCANCODE_UP) ||
|
|
(source >= SDL_SCANCODE_F1 && source <= SDL_SCANCODE_F12);
|
|
*/
|
|
}
|
|
|
|
SDL_Scancode source;
|
|
};
|
|
|
|
/* Joystick button binding */
|
|
struct JsButtonBinding : public Binding
|
|
{
|
|
JsButtonBinding() {}
|
|
|
|
bool sourceActive() const
|
|
{
|
|
return EventThread::joyState.buttons[source];
|
|
}
|
|
|
|
bool sourceRepeatable() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
uint8_t source;
|
|
};
|
|
|
|
/* Joystick axis binding */
|
|
struct JsAxisBinding : public Binding
|
|
{
|
|
JsAxisBinding() {}
|
|
|
|
JsAxisBinding(uint8_t source,
|
|
AxisDir dir,
|
|
Input::ButtonCode target)
|
|
: Binding(target),
|
|
source(source),
|
|
dir(dir)
|
|
{}
|
|
|
|
bool sourceActive() const
|
|
{
|
|
int val = EventThread::joyState.axes[source];
|
|
|
|
if (dir == Negative)
|
|
return val < -JAXIS_THRESHOLD;
|
|
else /* dir == Positive */
|
|
return val > JAXIS_THRESHOLD;
|
|
}
|
|
|
|
bool sourceRepeatable() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
uint8_t source;
|
|
AxisDir dir;
|
|
};
|
|
|
|
/* Joystick hat binding */
|
|
struct JsHatBinding : public Binding
|
|
{
|
|
JsHatBinding() {}
|
|
|
|
JsHatBinding(uint8_t source,
|
|
uint8_t pos,
|
|
Input::ButtonCode target)
|
|
: Binding(target),
|
|
source(source),
|
|
pos(pos)
|
|
{}
|
|
|
|
bool sourceActive() const
|
|
{
|
|
/* For a diagonal input accept it as an input for both the axes */
|
|
return (pos & EventThread::joyState.hats[source]) != 0;
|
|
}
|
|
|
|
bool sourceRepeatable() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
uint8_t source;
|
|
uint8_t pos;
|
|
};
|
|
|
|
/* Mouse button binding */
|
|
struct MsBinding : public Binding
|
|
{
|
|
MsBinding() {}
|
|
|
|
MsBinding(int buttonIndex,
|
|
Input::ButtonCode target)
|
|
: Binding(target),
|
|
index(buttonIndex)
|
|
{}
|
|
|
|
bool sourceActive() const
|
|
{
|
|
return EventThread::mouseState.buttons[index];
|
|
}
|
|
|
|
bool sourceRepeatable() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
int index;
|
|
};
|
|
|
|
/* Not rebindable */
|
|
static const KbBindingData staticKbBindings[] =
|
|
{
|
|
{ SDL_SCANCODE_LSHIFT, Input::Shift },
|
|
{ SDL_SCANCODE_RSHIFT, Input::Shift },
|
|
{ SDL_SCANCODE_LCTRL, Input::Ctrl },
|
|
{ SDL_SCANCODE_RCTRL, Input::Ctrl },
|
|
{ SDL_SCANCODE_LALT, Input::Alt },
|
|
{ SDL_SCANCODE_RALT, Input::Alt },
|
|
{ SDL_SCANCODE_F5, Input::F5 },
|
|
{ SDL_SCANCODE_F6, Input::F6 },
|
|
{ SDL_SCANCODE_F7, Input::F7 },
|
|
{ SDL_SCANCODE_F8, Input::F8 },
|
|
{ SDL_SCANCODE_F9, Input::F9 }
|
|
};
|
|
|
|
static elementsN(staticKbBindings);
|
|
|
|
/* Maps ButtonCode enum values to indices
|
|
* in the button state array */
|
|
static const int mapToIndex[] =
|
|
{
|
|
0, 0,
|
|
1, 0, 2, 0, 3, 0, 4, 0,
|
|
0,
|
|
5, 6, 7, 8, 9, 10, 11, 12,
|
|
0, 0,
|
|
13, 14, 15,
|
|
0,
|
|
16, 17, 18, 19, 20,
|
|
0, 0, 0, 0, 0, 0, 0, 0,
|
|
21, 22, 23
|
|
};
|
|
|
|
static elementsN(mapToIndex);
|
|
|
|
static const Input::ButtonCode dirs[] =
|
|
{ Input::Down, Input::Left, Input::Right, Input::Up };
|
|
|
|
static const int dirFlags[] =
|
|
{
|
|
1 << Input::Down,
|
|
1 << Input::Left,
|
|
1 << Input::Right,
|
|
1 << Input::Up
|
|
};
|
|
|
|
/* Dir4 is always zero on these combinations */
|
|
static const int deadDirFlags[] =
|
|
{
|
|
dirFlags[0] | dirFlags[3],
|
|
dirFlags[1] | dirFlags[2]
|
|
};
|
|
|
|
static const Input::ButtonCode otherDirs[4][3] =
|
|
{
|
|
{ Input::Left, Input::Right, Input::Up }, /* Down */
|
|
{ Input::Down, Input::Up, Input::Right }, /* Left */
|
|
{ Input::Down, Input::Up, Input::Left }, /* Right */
|
|
{ Input::Left, Input::Right, Input::Up } /* Up */
|
|
};
|
|
|
|
struct InputPrivate
|
|
{
|
|
std::vector<KbBinding> kbStatBindings;
|
|
std::vector<KbBinding> kbBindings;
|
|
std::vector<JsAxisBinding> jsABindings;
|
|
std::vector<JsHatBinding> jsHBindings;
|
|
std::vector<JsButtonBinding> jsBBindings;
|
|
std::vector<MsBinding> msBindings;
|
|
|
|
/* Collective binding array */
|
|
std::vector<Binding*> bindings;
|
|
|
|
ButtonState stateArray[BUTTON_CODE_COUNT*2];
|
|
|
|
ButtonState *states;
|
|
ButtonState *statesOld;
|
|
|
|
// Raw keystates
|
|
uint8_t rawStateArray[SDL_NUM_SCANCODES*2];
|
|
|
|
uint8_t *rawStates;
|
|
uint8_t *rawStatesOld;
|
|
|
|
Input::ButtonCode repeating;
|
|
int rawRepeating;
|
|
unsigned int repeatCount;
|
|
unsigned int rawRepeatCount;
|
|
unsigned long long repeatTime;
|
|
unsigned long long rawRepeatTime;
|
|
|
|
unsigned int repeatStart;
|
|
unsigned int repeatDelay;
|
|
|
|
unsigned long long last_update;
|
|
|
|
struct
|
|
{
|
|
int active;
|
|
Input::ButtonCode previous;
|
|
} dir4Data;
|
|
|
|
struct
|
|
{
|
|
int active;
|
|
} dir8Data;
|
|
|
|
void recalcRepeatTime(unsigned int fps) {
|
|
double framems = 1.f / fps;
|
|
|
|
// Approximate time in milliseconds
|
|
double start = (rgssVer >= 2) ? 0.375 : 0.400;
|
|
double delay = 0.100;
|
|
|
|
repeatStart = ceil(start / framems);
|
|
repeatDelay = ceil(delay / framems);
|
|
}
|
|
|
|
InputPrivate(const RGSSThreadData &rtData)
|
|
{
|
|
last_update = 0;
|
|
|
|
initStaticKbBindings();
|
|
initMsBindings();
|
|
|
|
/* Main thread should have these posted by now */
|
|
checkBindingChange(rtData);
|
|
|
|
int fps = rtData.config.fixedFramerate;
|
|
if (!fps) fps = (rgssVer >= 2) ? 60 : 40;
|
|
recalcRepeatTime(fps);
|
|
|
|
states = stateArray;
|
|
statesOld = stateArray + BUTTON_CODE_COUNT;
|
|
|
|
rawStates = rawStateArray;
|
|
rawStatesOld = rawStateArray + SDL_NUM_SCANCODES;
|
|
|
|
/* Clear buffers */
|
|
clearBuffer();
|
|
swapBuffers();
|
|
clearBuffer();
|
|
|
|
repeating = Input::None;
|
|
repeatCount = 0;
|
|
|
|
dir4Data.active = 0;
|
|
dir4Data.previous = Input::None;
|
|
|
|
dir8Data.active = 0;
|
|
}
|
|
|
|
inline ButtonState &getStateCheck(int code)
|
|
{
|
|
int index;
|
|
|
|
if (code < 0 || (size_t) code > mapToIndexN-1)
|
|
index = 0;
|
|
else
|
|
index = mapToIndex[code];
|
|
|
|
return states[index];
|
|
}
|
|
|
|
inline ButtonState &getState(Input::ButtonCode code)
|
|
{
|
|
return states[mapToIndex[code]];
|
|
}
|
|
|
|
inline ButtonState &getOldState(Input::ButtonCode code)
|
|
{
|
|
return statesOld[mapToIndex[code]];
|
|
}
|
|
|
|
inline ButtonState getStateRaw(int code, bool useVKey)
|
|
{
|
|
ButtonState b;
|
|
int scancode = (useVKey) ? -1 : code;
|
|
if (scancode < 0)
|
|
{
|
|
switch (code)
|
|
{
|
|
case 0x10:
|
|
return getState(Input::Shift);
|
|
break;
|
|
|
|
case 0x11:
|
|
return getState(Input::Ctrl);
|
|
break;
|
|
|
|
case 0x12:
|
|
return getState(Input::Alt);
|
|
break;
|
|
|
|
case 0x1:
|
|
return getState(Input::MouseLeft);
|
|
break;
|
|
|
|
case 0x2:
|
|
return getState(Input::MouseRight);
|
|
break;
|
|
|
|
case 0x4:
|
|
return getState(Input::MouseMiddle);
|
|
break;
|
|
|
|
default:
|
|
ButtonState b;
|
|
try
|
|
{
|
|
scancode = vKeyToScancode[code];
|
|
}
|
|
catch (...) {}
|
|
if (scancode == -1) return b;
|
|
break;
|
|
}
|
|
}
|
|
|
|
b.pressed = rawStates[scancode];
|
|
b.triggered = (rawStates[scancode] && !rawStatesOld[scancode]);
|
|
b.released = (!rawStates[scancode] && rawStatesOld[scancode]);
|
|
|
|
b.repeated = rawRepeatCount >= repeatStart && ((rawRepeatCount+1) % repeatDelay) == 0;
|
|
|
|
return b;
|
|
}
|
|
|
|
void swapBuffers()
|
|
{
|
|
ButtonState *tmp = states;
|
|
states = statesOld;
|
|
statesOld = tmp;
|
|
|
|
uint8_t *tmpr = rawStates;
|
|
rawStates = rawStatesOld;
|
|
rawStatesOld = tmpr;
|
|
}
|
|
|
|
void clearBuffer()
|
|
{
|
|
const size_t size = sizeof(ButtonState) * BUTTON_CODE_COUNT;
|
|
memset(states, 0, size);
|
|
|
|
memset(rawStates, 0, SDL_NUM_SCANCODES);
|
|
}
|
|
|
|
void checkBindingChange(const RGSSThreadData &rtData)
|
|
{
|
|
BDescVec d;
|
|
|
|
if (!rtData.bindingUpdateMsg.poll(d))
|
|
return;
|
|
|
|
applyBindingDesc(d);
|
|
}
|
|
|
|
template<class B>
|
|
void appendBindings(std::vector<B> &bind)
|
|
{
|
|
for (size_t i = 0; i < bind.size(); ++i)
|
|
bindings.push_back(&bind[i]);
|
|
}
|
|
|
|
void applyBindingDesc(const BDescVec &d)
|
|
{
|
|
kbBindings.clear();
|
|
jsABindings.clear();
|
|
jsHBindings.clear();
|
|
jsBBindings.clear();
|
|
|
|
for (size_t i = 0; i < d.size(); ++i)
|
|
{
|
|
const BindingDesc &desc = d[i];
|
|
const SourceDesc &src = desc.src;
|
|
|
|
if (desc.target == Input::None)
|
|
continue;
|
|
|
|
switch (desc.src.type)
|
|
{
|
|
case Invalid :
|
|
break;
|
|
case Key :
|
|
{
|
|
KbBinding bind;
|
|
bind.source = src.d.scan;
|
|
bind.target = desc.target;
|
|
kbBindings.push_back(bind);
|
|
|
|
break;
|
|
}
|
|
case JAxis :
|
|
{
|
|
JsAxisBinding bind;
|
|
bind.source = src.d.ja.axis;
|
|
bind.dir = src.d.ja.dir;
|
|
bind.target = desc.target;
|
|
jsABindings.push_back(bind);
|
|
|
|
break;
|
|
}
|
|
case JHat :
|
|
{
|
|
JsHatBinding bind;
|
|
bind.source = src.d.jh.hat;
|
|
bind.pos = src.d.jh.pos;
|
|
bind.target = desc.target;
|
|
jsHBindings.push_back(bind);
|
|
|
|
break;
|
|
}
|
|
case JButton :
|
|
{
|
|
JsButtonBinding bind;
|
|
bind.source = src.d.jb;
|
|
bind.target = desc.target;
|
|
jsBBindings.push_back(bind);
|
|
|
|
break;
|
|
}
|
|
default :
|
|
assert(!"unreachable");
|
|
}
|
|
}
|
|
|
|
bindings.clear();
|
|
|
|
appendBindings(kbStatBindings);
|
|
appendBindings(msBindings);
|
|
|
|
appendBindings(kbBindings);
|
|
appendBindings(jsABindings);
|
|
appendBindings(jsHBindings);
|
|
appendBindings(jsBBindings);
|
|
}
|
|
|
|
void initStaticKbBindings()
|
|
{
|
|
kbStatBindings.clear();
|
|
|
|
for (size_t i = 0; i < staticKbBindingsN; ++i)
|
|
kbStatBindings.push_back(KbBinding(staticKbBindings[i]));
|
|
}
|
|
|
|
void initMsBindings()
|
|
{
|
|
msBindings.resize(3);
|
|
|
|
size_t i = 0;
|
|
msBindings[i++] = MsBinding(SDL_BUTTON_LEFT, Input::MouseLeft);
|
|
msBindings[i++] = MsBinding(SDL_BUTTON_MIDDLE, Input::MouseMiddle);
|
|
msBindings[i++] = MsBinding(SDL_BUTTON_RIGHT, Input::MouseRight);
|
|
}
|
|
|
|
void pollBindings(Input::ButtonCode &repeatCand)
|
|
{
|
|
for (Binding *bind : bindings) {
|
|
// Get all binding states
|
|
pollBindingPriv(*bind, repeatCand);
|
|
|
|
// Check for released buttons
|
|
ButtonState &state = getState(bind->target);
|
|
ButtonState &oldState = getOldState(bind->target);
|
|
if (!state.pressed && oldState.pressed)
|
|
state.released = true;
|
|
}
|
|
|
|
updateDir4();
|
|
updateDir8();
|
|
}
|
|
|
|
void pollBindingPriv(const Binding &b,
|
|
Input::ButtonCode &repeatCand)
|
|
{
|
|
if (!b.sourceActive())
|
|
return;
|
|
|
|
if (b.target == Input::None)
|
|
return;
|
|
|
|
ButtonState &state = getState(b.target);
|
|
ButtonState &oldState = getOldState(b.target);
|
|
|
|
state.pressed = true;
|
|
|
|
/* Must have been released before to trigger */
|
|
if (!oldState.pressed)
|
|
state.triggered = true;
|
|
|
|
/* Unbound keys don't create/break repeat */
|
|
if (repeatCand != Input::None)
|
|
return;
|
|
|
|
if (repeating != b.target &&
|
|
!oldState.pressed)
|
|
{
|
|
if (b.sourceRepeatable())
|
|
repeatCand = b.target;
|
|
else
|
|
/* Unrepeatable keys still break current repeat */
|
|
repeating = Input::None;
|
|
}
|
|
}
|
|
|
|
void updateRaw()
|
|
{
|
|
memcpy(rawStates, shState->eThread().keyStates, SDL_NUM_SCANCODES);
|
|
|
|
for (int i = 0; i < SDL_NUM_SCANCODES; i++)
|
|
{
|
|
if (rawStates[i] && rawStatesOld[i])
|
|
{
|
|
if (rawRepeating == i)
|
|
{
|
|
rawRepeatCount++;
|
|
}
|
|
else
|
|
{
|
|
rawRepeatCount = 0;
|
|
rawRepeatTime = shState->runTime();
|
|
rawRepeating = i;
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
rawRepeating = -1;
|
|
}
|
|
|
|
void updateDir4()
|
|
{
|
|
int dirFlag = 0;
|
|
|
|
for (size_t i = 0; i < 4; ++i)
|
|
dirFlag |= (getState(dirs[i]).pressed ? dirFlags[i] : 0);
|
|
|
|
if (dirFlag == deadDirFlags[0] || dirFlag == deadDirFlags[1])
|
|
{
|
|
dir4Data.active = Input::None;
|
|
return;
|
|
}
|
|
|
|
if (dir4Data.previous != Input::None)
|
|
{
|
|
/* Check if prev still pressed */
|
|
if (getState(dir4Data.previous).pressed)
|
|
{
|
|
for (size_t i = 0; i < 3; ++i)
|
|
{
|
|
Input::ButtonCode other =
|
|
otherDirs[(dir4Data.previous/2)-1][i];
|
|
|
|
if (!getState(other).pressed)
|
|
continue;
|
|
|
|
dir4Data.active = other;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < 4; ++i)
|
|
{
|
|
if (!getState(dirs[i]).pressed)
|
|
continue;
|
|
|
|
dir4Data.active = dirs[i];
|
|
dir4Data.previous = dirs[i];
|
|
return;
|
|
}
|
|
|
|
dir4Data.active = Input::None;
|
|
dir4Data.previous = Input::None;
|
|
}
|
|
|
|
void updateDir8()
|
|
{
|
|
static const int combos[4][4] =
|
|
{
|
|
{ 2, 1, 3, 0 },
|
|
{ 1, 4, 0, 7 },
|
|
{ 3, 0, 6, 9 },
|
|
{ 0, 7, 9, 8 }
|
|
};
|
|
|
|
dir8Data.active = 0;
|
|
|
|
for (size_t i = 0; i < 4; ++i)
|
|
{
|
|
Input::ButtonCode one = dirs[i];
|
|
|
|
if (!getState(one).pressed)
|
|
continue;
|
|
|
|
for (int j = 0; j < 3; ++j)
|
|
{
|
|
Input::ButtonCode other = otherDirs[i][j];
|
|
|
|
if (!getState(other).pressed)
|
|
continue;
|
|
|
|
dir8Data.active = combos[(one/2)-1][(other/2)-1];
|
|
return;
|
|
}
|
|
|
|
dir8Data.active = one;
|
|
return;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
Input::Input(const RGSSThreadData &rtData)
|
|
{
|
|
p = new InputPrivate(rtData);
|
|
}
|
|
|
|
unsigned long long Input::getDelta() {
|
|
return shState->runTime() - p->last_update;
|
|
}
|
|
|
|
void Input::recalcRepeat(unsigned int fps) {
|
|
p->recalcRepeatTime(fps);
|
|
}
|
|
|
|
void Input::update()
|
|
{
|
|
shState->checkShutdown();
|
|
p->checkBindingChange(shState->rtData());
|
|
|
|
p->swapBuffers();
|
|
p->clearBuffer();
|
|
|
|
ButtonCode repeatCand = None;
|
|
|
|
/* Poll all bindings */
|
|
p->pollBindings(repeatCand);
|
|
|
|
// Get raw keystates
|
|
p->updateRaw();
|
|
|
|
/* Check for new repeating key */
|
|
if (repeatCand != None && repeatCand != p->repeating)
|
|
{
|
|
p->repeating = repeatCand;
|
|
p->repeatCount = 0;
|
|
p->repeatTime = shState->runTime();
|
|
p->getState(repeatCand).repeated = true;
|
|
|
|
p->last_update = p->repeatTime;
|
|
return;
|
|
}
|
|
|
|
/* Check if repeating key is still pressed */
|
|
if (p->getState(p->repeating).pressed)
|
|
{
|
|
p->repeatCount++;
|
|
|
|
/*
|
|
bool repeated;
|
|
if (rgssVer >= 2)
|
|
repeated = p->repeatCount >= 23 && ((p->repeatCount+1) % 6) == 0;
|
|
else
|
|
repeated = p->repeatCount >= 15 && ((p->repeatCount+1) % 4) == 0;
|
|
*/
|
|
bool repeated = p->repeatCount >= p->repeatStart && ((p->repeatCount+1) & p->repeatDelay) == 0;
|
|
p->getState(p->repeating).repeated |= repeated;
|
|
|
|
p->last_update = shState->runTime();
|
|
return;
|
|
}
|
|
|
|
p->repeating = None;
|
|
p->last_update = shState->runTime();
|
|
}
|
|
|
|
std::vector<std::string> Input::getBindings(ButtonCode code) {
|
|
std::vector<std::string> ret;
|
|
for (const auto &b : p->kbBindings) {
|
|
if (b.target != code) continue;
|
|
ret.push_back(SDL_GetScancodeName(b.source));
|
|
}
|
|
|
|
for (const auto &b : p->jsBBindings) {
|
|
if (b.target != code) continue;
|
|
ret.push_back(std::string("JSBUTTON") + std::to_string(b.source));
|
|
}
|
|
|
|
for (const auto &b : p->jsABindings) {
|
|
if (b.target != code) continue;
|
|
ret.push_back(std::string("JSAXIS") + std::to_string(b.source));
|
|
}
|
|
|
|
for (const auto &b : p->jsHBindings) {
|
|
if (b.target != code) continue;
|
|
ret.push_back(std::string("JSHAT") + std::to_string(b.source));
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool Input::isPressed(int button)
|
|
{
|
|
return p->getStateCheck(button).pressed;
|
|
}
|
|
|
|
bool Input::isTriggered(int button)
|
|
{
|
|
return p->getStateCheck(button).triggered;
|
|
}
|
|
|
|
bool Input::isRepeated(int button)
|
|
{
|
|
return p->getStateCheck(button).repeated;
|
|
}
|
|
|
|
bool Input::isReleased(int button) {
|
|
return p->getStateCheck(button).released;
|
|
}
|
|
|
|
unsigned int Input::count(int button) {
|
|
if (button != p->repeating)
|
|
return 0;
|
|
|
|
return p->repeatCount;
|
|
}
|
|
|
|
unsigned long long Input::repeatTime(int button) {
|
|
if (button != p->repeating)
|
|
return 0;
|
|
|
|
return shState->runTime() - p->repeatTime;
|
|
}
|
|
|
|
bool Input::isPressedEx(int code, bool isVKey)
|
|
{
|
|
return p->getStateRaw(code, isVKey).pressed;
|
|
}
|
|
|
|
bool Input::isTriggeredEx(int code, bool isVKey)
|
|
{
|
|
return p->getStateRaw(code, isVKey).triggered;
|
|
}
|
|
|
|
bool Input::isRepeatedEx(int code, bool isVKey)
|
|
{
|
|
return p->getStateRaw(code, isVKey).repeated;
|
|
}
|
|
|
|
bool Input::isReleasedEx(int code, bool isVKey)
|
|
{
|
|
return p->getStateRaw(code, isVKey).released;
|
|
}
|
|
|
|
unsigned int Input::repeatcount(int code, bool isVKey) {
|
|
int c = code;
|
|
if (isVKey) {
|
|
try {
|
|
c = vKeyToScancode[code];
|
|
}
|
|
catch (...) {
|
|
return 0;
|
|
}
|
|
}
|
|
if (c != p->rawRepeating)
|
|
return 0;
|
|
|
|
return p->rawRepeatCount;
|
|
}
|
|
|
|
unsigned long long Input::repeatTimeEx(int code, bool isVKey) {
|
|
int c = code;
|
|
if (isVKey) {
|
|
try { c = vKeyToScancode[code]; }
|
|
catch (...) { return 0; }
|
|
}
|
|
|
|
if (c != p->rawRepeating)
|
|
return 0;
|
|
|
|
return shState->runTime() - p->rawRepeatTime;
|
|
}
|
|
|
|
int Input::dir4Value()
|
|
{
|
|
return p->dir4Data.active;
|
|
}
|
|
|
|
int Input::dir8Value()
|
|
{
|
|
return p->dir8Data.active;
|
|
}
|
|
|
|
int Input::mouseX()
|
|
{
|
|
RGSSThreadData &rtData = shState->rtData();
|
|
|
|
return (EventThread::mouseState.x - rtData.screenOffset.x) * rtData.sizeResoRatio.x;
|
|
}
|
|
|
|
int Input::mouseY()
|
|
{
|
|
RGSSThreadData &rtData = shState->rtData();
|
|
|
|
return (EventThread::mouseState.y - rtData.screenOffset.y) * rtData.sizeResoRatio.y;
|
|
}
|
|
|
|
bool Input::getJoystickConnected()
|
|
{
|
|
return shState->eThread().getJoystickConnected();
|
|
}
|
|
|
|
const char *Input::getJoystickName()
|
|
{
|
|
return (getJoystickConnected()) ?
|
|
SDL_JoystickName(shState->eThread().joystick()) :
|
|
0;
|
|
}
|
|
|
|
int Input::getJoystickPowerLevel()
|
|
{
|
|
return (getJoystickConnected()) ?
|
|
SDL_JoystickCurrentPowerLevel(shState->eThread().joystick()) :
|
|
SDL_JOYSTICK_POWER_UNKNOWN;
|
|
}
|
|
|
|
bool Input::getTextInputMode()
|
|
{
|
|
return (SDL_IsTextInputActive() == SDL_TRUE);
|
|
}
|
|
|
|
void Input::setTextInputMode(bool mode)
|
|
{
|
|
shState->eThread().requestTextInputMode(mode);
|
|
}
|
|
|
|
const char *Input::getText()
|
|
{
|
|
return shState->eThread().textInputBuffer.c_str();
|
|
}
|
|
|
|
void Input::clearText()
|
|
{
|
|
shState->eThread().textInputBuffer.clear();
|
|
}
|
|
|
|
char *Input::getClipboardText()
|
|
{
|
|
char *tx = SDL_GetClipboardText();
|
|
if (!tx)
|
|
throw Exception(Exception::SDLError, "Failed to get clipboard text: %s", SDL_GetError());
|
|
return tx;
|
|
}
|
|
|
|
void Input::setClipboardText(char *text)
|
|
{
|
|
if (SDL_SetClipboardText(text) < 0)
|
|
throw Exception(Exception::SDLError, "Failed to set clipboard text: %s", SDL_GetError());
|
|
}
|
|
|
|
Input::~Input()
|
|
{
|
|
delete p;
|
|
}
|