mirror of
https://github.com/TomHarte/CLK.git
synced 2025-04-09 00:37:27 +00:00
Merge pull request #765 from TomHarte/SDLKeyInput
Attempts to make use of SDL_StartTextInput.
This commit is contained in:
commit
e47aa7653b
@ -35,7 +35,9 @@ class Keyboard {
|
||||
Keypad4, Keypad5, Keypad6, KeypadMinus,
|
||||
Keypad1, Keypad2, Keypad3, KeypadEnter,
|
||||
Keypad0, KeypadDecimalPoint, KeypadEquals,
|
||||
Help
|
||||
Help,
|
||||
|
||||
Max = Help
|
||||
};
|
||||
|
||||
/// Constructs a Keyboard that declares itself to observe all keys.
|
||||
|
@ -143,8 +143,8 @@ uint16_t *CharacterMapper::sequence_for_character(char character) {
|
||||
/* t */ KEYS(KeyT), /* u */ KEYS(KeyU),
|
||||
/* v */ KEYS(KeyV), /* w */ KEYS(KeyW),
|
||||
/* x */ KEYS(KeyX), /* y */ KEYS(KeyY),
|
||||
/* z */ KEYS(KeyZ), /* { */ X,
|
||||
/* | */ SHIFT(KeyAt), /* } */ X,
|
||||
/* z */ KEYS(KeyZ), /* { */ SHIFT(KeyLeftSquareBracket),
|
||||
/* | */ SHIFT(KeyAt), /* } */ SHIFT(KeyRightSquareBracket),
|
||||
/* ~ */ X
|
||||
};
|
||||
#undef KEYS
|
||||
|
@ -115,13 +115,36 @@ class ConcreteMachine:
|
||||
}
|
||||
|
||||
void set_key_state(uint16_t key, bool isPressed) final {
|
||||
if(key == KeyBreak) {
|
||||
m6502_.set_reset_line(isPressed);
|
||||
} else {
|
||||
if(isPressed)
|
||||
key_states_[key >> 4] |= key&0xf;
|
||||
else
|
||||
key_states_[key >> 4] &= ~(key&0xf);
|
||||
switch(key) {
|
||||
default:
|
||||
if(isPressed)
|
||||
key_states_[key >> 4] |= key&0xf;
|
||||
else
|
||||
key_states_[key >> 4] &= ~(key&0xf);
|
||||
break;
|
||||
|
||||
case KeyBreak:
|
||||
m6502_.set_reset_line(isPressed);
|
||||
break;
|
||||
|
||||
#define ShiftedKey(source, dest) \
|
||||
case source: \
|
||||
set_key_state(KeyShift, isPressed); \
|
||||
set_key_state(dest, isPressed); \
|
||||
break;
|
||||
|
||||
ShiftedKey(KeyF1, Key1);
|
||||
ShiftedKey(KeyF2, Key2);
|
||||
ShiftedKey(KeyF3, Key3);
|
||||
ShiftedKey(KeyF4, Key4);
|
||||
ShiftedKey(KeyF5, Key5);
|
||||
ShiftedKey(KeyF6, Key6);
|
||||
ShiftedKey(KeyF7, Key7);
|
||||
ShiftedKey(KeyF8, Key8);
|
||||
ShiftedKey(KeyF9, Key9);
|
||||
ShiftedKey(KeyF0, Key0);
|
||||
|
||||
#undef ShiftedKey
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,6 +52,18 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
|
||||
BIND(KeypadMinus, KeyMinus); BIND(KeypadPlus, KeyColon);
|
||||
|
||||
BIND(Space, KeySpace);
|
||||
|
||||
// Virtual mappings.
|
||||
BIND(F1, KeyF1);
|
||||
BIND(F2, KeyF2);
|
||||
BIND(F3, KeyF3);
|
||||
BIND(F4, KeyF4);
|
||||
BIND(F5, KeyF5);
|
||||
BIND(F6, KeyF6);
|
||||
BIND(F7, KeyF7);
|
||||
BIND(F8, KeyF8);
|
||||
BIND(F9, KeyF9);
|
||||
BIND(F10, KeyF0);
|
||||
}
|
||||
#undef BIND
|
||||
return KeyboardMachine::MappedMachine::KeyNotMapped;
|
||||
|
@ -30,6 +30,9 @@ enum Key: uint16_t {
|
||||
KeyZ = 0x00c0 | 0x08, KeyA = 0x00c0 | 0x04, KeyQ = 0x00c0 | 0x02, Key1 = 0x00c0 | 0x01,
|
||||
KeyShift = 0x00d0 | 0x08, KeyControl = 0x00d0 | 0x04, KeyFunc = 0x00d0 | 0x02, KeyEscape = 0x00d0 | 0x01,
|
||||
|
||||
// Virtual keys.
|
||||
KeyF1 = 0xfff0, KeyF2, KeyF3, KeyF4, KeyF5, KeyF6, KeyF7, KeyF8, KeyF9, KeyF0,
|
||||
|
||||
KeyBreak = 0xfffd,
|
||||
};
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
#ifndef KeyboardMachine_h
|
||||
#define KeyboardMachine_h
|
||||
|
||||
#include <bitset>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <set>
|
||||
@ -54,6 +55,52 @@ class Machine: public KeyActions {
|
||||
Provides a destination for keyboard input.
|
||||
*/
|
||||
virtual Inputs::Keyboard &get_keyboard() = 0;
|
||||
|
||||
/*!
|
||||
Provides a standard bundle of logic for hosts that are able to correlate typed symbols
|
||||
with keypresses. Specifically:
|
||||
|
||||
If map_logically is false:
|
||||
|
||||
(i) initially try to set @c key as @c is_pressed;
|
||||
(ii) if this machine doesn't map @c key to anything but @c symbol is a printable ASCII character, attempt to @c type_string it.
|
||||
|
||||
If map_logically is true:
|
||||
|
||||
(i) if @c symbol can be typed and this is a key down, @c type_string it;
|
||||
(ii) if @c symbol cannot be typed, set @c key as @c is_pressed
|
||||
*/
|
||||
bool apply_key(Inputs::Keyboard::Key key, char symbol, bool is_pressed, bool map_logically) {
|
||||
Inputs::Keyboard &keyboard = get_keyboard();
|
||||
|
||||
if(!map_logically) {
|
||||
// Try a regular keypress first, and stop if that works.
|
||||
if(keyboard.set_key_pressed(key, symbol, is_pressed)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// That having failed, if a symbol has been supplied then try typing it.
|
||||
if(is_pressed && symbol && can_type(symbol)) {
|
||||
char string[2] = { symbol, 0 };
|
||||
type_string(string);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} else {
|
||||
// Try to type first.
|
||||
if(is_pressed && symbol && can_type(symbol)) {
|
||||
char string[2] = { symbol, 0 };
|
||||
type_string(string);
|
||||
return true;
|
||||
}
|
||||
|
||||
// That didn't work. Forward as a keypress. As, either:
|
||||
// (i) this is a key down, but doesn't have a symbol, or is an untypeable symbol; or
|
||||
// (ii) this is a key up, which it won't be an issue to miscommunicate.
|
||||
return keyboard.set_key_pressed(key, symbol, is_pressed);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
|
@ -31,7 +31,7 @@
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
buildConfiguration = "Release"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
disableMainThreadChecker = "YES"
|
||||
@ -56,6 +56,10 @@
|
||||
argument = "/Users/thomasharte/Downloads/test-dsk-for-rw-and-50-60-hz/TEST-RW-60Hz.DSK"
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = ""/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Desktop/Soft/ColecoVision/Galaxian (1983)(Atari).col""
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = ""/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Desktop/Soft/Master System/R-Type (NTSC).sms""
|
||||
isEnabled = "NO">
|
||||
|
@ -67,7 +67,7 @@
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Release"
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
enableASanStackUseAfterReturn = "YES"
|
||||
|
@ -488,55 +488,19 @@ struct ActivityObserver: public Activity::Observer {
|
||||
}
|
||||
#undef BIND
|
||||
|
||||
Inputs::Keyboard &keyboard = keyboard_machine->get_keyboard();
|
||||
|
||||
if(keyboard.observed_keys().find(mapped_key) != keyboard.observed_keys().end()) {
|
||||
// Don't pass anything on if this is not new information.
|
||||
if(_depressedKeys[key] == !!isPressed) return;
|
||||
_depressedKeys[key] = !!isPressed;
|
||||
|
||||
// Pick an ASCII code, if any.
|
||||
char pressedKey = '\0';
|
||||
if(characters.length) {
|
||||
unichar firstCharacter = [characters characterAtIndex:0];
|
||||
if(firstCharacter < 128) {
|
||||
pressedKey = (char)firstCharacter;
|
||||
}
|
||||
// Pick an ASCII code, if any.
|
||||
char pressedKey = '\0';
|
||||
if(characters.length) {
|
||||
unichar firstCharacter = [characters characterAtIndex:0];
|
||||
if(firstCharacter < 128) {
|
||||
pressedKey = (char)firstCharacter;
|
||||
}
|
||||
}
|
||||
|
||||
// Decide whether to try to 'type' (in the logical mapping sense) in the first instance.
|
||||
bool shouldTryToType = self.inputMode == CSMachineKeyboardInputModeKeyboardLogical;
|
||||
|
||||
// Even if the default wasn't to try to type, have a go anyway if the key wasn't
|
||||
// recognised directly. E.g. if the user hits their square bracket key on a machine that
|
||||
// doesn't have a correspondingly-placed key, then try to type a square bracket.
|
||||
if(!shouldTryToType) {
|
||||
@synchronized(self) {
|
||||
shouldTryToType = !keyboard.set_key_pressed(mapped_key, pressedKey, isPressed);
|
||||
}
|
||||
@synchronized(self) {
|
||||
if(keyboard_machine->apply_key(mapped_key, pressedKey, isPressed, self.inputMode == CSMachineKeyboardInputModeKeyboardLogical)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If this should try to type, give that a go. But typing may fail, e.g. because the user
|
||||
// has pressed something like the cursor keys, which don't actually map to a typeable symbol.
|
||||
if(shouldTryToType) {
|
||||
@synchronized(self) {
|
||||
if(pressedKey && keyboard_machine->can_type(pressedKey)) {
|
||||
if(isPressed) {
|
||||
char string[2] = { pressedKey, 0 };
|
||||
keyboard_machine->type_string(string);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Okay, so at this point either: set_key_pressed was already tried but will fail anyway,
|
||||
// or else it hasn't been tried yet and is worth a go.
|
||||
@synchronized(self) {
|
||||
shouldTryToType = !keyboard.set_key_pressed(mapped_key, pressedKey, isPressed);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include <memory>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <map>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
@ -294,7 +295,7 @@ class ActivityObserver: public Activity::Observer {
|
||||
std::mutex mutex;
|
||||
};
|
||||
|
||||
bool KeyboardKeyForSDLScancode(SDL_Keycode scancode, Inputs::Keyboard::Key &key) {
|
||||
bool KeyboardKeyForSDLScancode(SDL_Scancode scancode, Inputs::Keyboard::Key &key) {
|
||||
#define BIND(x, y) case SDL_SCANCODE_##x: key = Inputs::Keyboard::Key::y; break;
|
||||
switch(scancode) {
|
||||
default: return false;
|
||||
@ -611,10 +612,8 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
// Check whether a 'logical' keyboard has been requested.
|
||||
constexpr bool logical_keyboard = false; //arguments.selections.find("logical-keyboard") != arguments.selections.end();
|
||||
/* Logical keyboard entry is currently disabled; the attempt below to get logical input via SDL_GetKeyName is
|
||||
too flawed — all letters are always capitals, shifted symbols are reported correctly on their first
|
||||
press only, etc. I need to see whether other options are available. If not then SDL may not allow this feature.*/
|
||||
const bool logical_keyboard = arguments.selections.find("logical-keyboard") != arguments.selections.end();
|
||||
SDL_StartTextInput();
|
||||
|
||||
// Wire up the best-effort updater, its delegate, and the speaker delegate.
|
||||
machine_runner.machine = machine.get();
|
||||
@ -767,6 +766,15 @@ int main(int argc, char *argv[]) {
|
||||
activity_observer = std::make_unique<ActivityObserver>(activity_source, 4.0f / 3.0f);
|
||||
}
|
||||
|
||||
// SDL 2.x delivers key up/down events and text inputs separately even when they're correlated;
|
||||
// this struct and map is used to correlate them by time.
|
||||
struct KeyPress {
|
||||
bool is_down = true;
|
||||
std::string input;
|
||||
SDL_Scancode scancode = SDL_SCANCODE_UNKNOWN;
|
||||
};
|
||||
std::map<uint32_t, KeyPress> keypresses;
|
||||
|
||||
// Run the main event loop until the OS tells us to quit.
|
||||
const bool uses_mouse = !!machine->mouse_machine();
|
||||
bool should_quit = false;
|
||||
@ -789,6 +797,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
// Grab the machine lock and process all pending events.
|
||||
std::lock_guard<std::mutex> lock_guard(machine_mutex);
|
||||
const auto keyboard_machine = machine->keyboard_machine();
|
||||
SDL_Event event;
|
||||
while(SDL_PollEvent(&event)) {
|
||||
switch(event.type) {
|
||||
@ -813,10 +822,12 @@ int main(int argc, char *argv[]) {
|
||||
machine->media_target()->insert_media(media);
|
||||
} break;
|
||||
|
||||
case SDL_TEXTINPUT:
|
||||
keypresses[event.text.timestamp].input = event.text.text;
|
||||
break;
|
||||
|
||||
case SDL_KEYDOWN:
|
||||
case SDL_KEYUP: {
|
||||
const auto keyboard_machine = machine->keyboard_machine();
|
||||
|
||||
if(event.type == SDL_KEYDOWN) {
|
||||
// Syphon off the key-press if it's control+shift+V (paste).
|
||||
if(event.key.keysym.sym == SDLK_v && (SDL_GetModState()&KMOD_CTRL) && (SDL_GetModState()&KMOD_SHIFT)) {
|
||||
@ -893,51 +904,8 @@ int main(int argc, char *argv[]) {
|
||||
break;
|
||||
}
|
||||
|
||||
const bool is_pressed = event.type == SDL_KEYDOWN;
|
||||
|
||||
if(keyboard_machine) {
|
||||
// Grab the key's symbol.
|
||||
char key_value = '\0';
|
||||
const char *key_name = SDL_GetKeyName(event.key.keysym.sym);
|
||||
if(key_name[0] >= 0 && key_name[1] == 0) key_value = key_name[0];
|
||||
|
||||
// If a logical mapping was selected and a symbol was found, type it.
|
||||
if(logical_keyboard && key_value != '\0' && keyboard_machine->can_type(key_value)) {
|
||||
if(is_pressed) {
|
||||
char string[] = { key_value, 0 };
|
||||
keyboard_machine->type_string(string);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Otherwise, supply as a normal keypress.
|
||||
Inputs::Keyboard::Key key = Inputs::Keyboard::Key::Space;
|
||||
if(!KeyboardKeyForSDLScancode(event.key.keysym.scancode, key)) break;
|
||||
if(keyboard_machine->get_keyboard().observed_keys().find(key) != keyboard_machine->get_keyboard().observed_keys().end()) {
|
||||
keyboard_machine->get_keyboard().set_key_pressed(key, key_value, is_pressed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
JoystickMachine::Machine *const joystick_machine = machine->joystick_machine();
|
||||
if(joystick_machine) {
|
||||
auto &joysticks = joystick_machine->get_joysticks();
|
||||
if(!joysticks.empty()) {
|
||||
switch(event.key.keysym.scancode) {
|
||||
case SDL_SCANCODE_LEFT: joysticks[0]->set_input(Inputs::Joystick::Input::Left, is_pressed); break;
|
||||
case SDL_SCANCODE_RIGHT: joysticks[0]->set_input(Inputs::Joystick::Input::Right, is_pressed); break;
|
||||
case SDL_SCANCODE_UP: joysticks[0]->set_input(Inputs::Joystick::Input::Up, is_pressed); break;
|
||||
case SDL_SCANCODE_DOWN: joysticks[0]->set_input(Inputs::Joystick::Input::Down, is_pressed); break;
|
||||
case SDL_SCANCODE_SPACE: joysticks[0]->set_input(Inputs::Joystick::Input::Fire, is_pressed); break;
|
||||
case SDL_SCANCODE_A: joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 0), is_pressed); break;
|
||||
case SDL_SCANCODE_S: joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 1), is_pressed); break;
|
||||
default: {
|
||||
const char *key_name = SDL_GetKeyName(event.key.keysym.sym);
|
||||
joysticks[0]->set_input(Inputs::Joystick::Input(key_name[0]), is_pressed);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
}
|
||||
keypresses[event.text.timestamp].scancode = event.key.keysym.scancode;
|
||||
keypresses[event.text.timestamp].is_down = event.type == SDL_KEYDOWN;
|
||||
} break;
|
||||
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
@ -969,8 +937,81 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
// Push new joystick state, if any.
|
||||
// Look for potential keypress merges; SDL doesn't in any capacity guarantee that keypresses that produce
|
||||
// symbols will be delivered with the same timestamp. So look for any pairs of recorded kepresses that are
|
||||
// close together temporally and otherwise seem to match.
|
||||
std::vector<KeyPress> matched_keypresses;
|
||||
if(keypresses.size()) {
|
||||
auto next_keypress = keypresses.begin();
|
||||
|
||||
while(next_keypress != keypresses.end()) {
|
||||
auto keypress = next_keypress;
|
||||
++next_keypress;
|
||||
|
||||
// If the two appear to pair off, push a combination and advance twice.
|
||||
// Otherwise, keep just the first and advance once.
|
||||
if(
|
||||
next_keypress != keypresses.end() &&
|
||||
keypress->first >= next_keypress->first - 5 &&
|
||||
keypress->second.is_down && next_keypress->second.is_down &&
|
||||
!keypress->second.input.size() != !next_keypress->second.input.size() &&
|
||||
(keypress->second.scancode != SDL_SCANCODE_UNKNOWN) != (next_keypress->second.scancode != SDL_SCANCODE_UNKNOWN)) {
|
||||
|
||||
KeyPress combined_keypress;
|
||||
|
||||
if(keypress->second.scancode != SDL_SCANCODE_UNKNOWN) {
|
||||
combined_keypress.scancode = keypress->second.scancode;
|
||||
combined_keypress.input = std::move(next_keypress->second.input);
|
||||
} else {
|
||||
combined_keypress.scancode = next_keypress->second.scancode;
|
||||
combined_keypress.input = std::move(keypress->second.input);
|
||||
};
|
||||
++next_keypress;
|
||||
} else {
|
||||
matched_keypresses.push_back(keypress->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle accumulated key states.
|
||||
JoystickMachine::Machine *const joystick_machine = machine->joystick_machine();
|
||||
for (const auto &keypress: matched_keypresses) {
|
||||
// Try to set this key on the keyboard first, if there is one.
|
||||
if(keyboard_machine) {
|
||||
Inputs::Keyboard::Key key = Inputs::Keyboard::Key::Space;
|
||||
if( KeyboardKeyForSDLScancode(keypress.scancode, key) &&
|
||||
keyboard_machine->apply_key(key, keypress.input.size() ? keypress.input[0] : 0, keypress.is_down, logical_keyboard)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Having failed that, try converting it into a joystick action.
|
||||
if(joystick_machine) {
|
||||
auto &joysticks = joystick_machine->get_joysticks();
|
||||
if(!joysticks.empty()) {
|
||||
const bool is_pressed = keypress.is_down;
|
||||
switch(keypress.scancode) {
|
||||
case SDL_SCANCODE_LEFT: joysticks[0]->set_input(Inputs::Joystick::Input::Left, is_pressed); break;
|
||||
case SDL_SCANCODE_RIGHT: joysticks[0]->set_input(Inputs::Joystick::Input::Right, is_pressed); break;
|
||||
case SDL_SCANCODE_UP: joysticks[0]->set_input(Inputs::Joystick::Input::Up, is_pressed); break;
|
||||
case SDL_SCANCODE_DOWN: joysticks[0]->set_input(Inputs::Joystick::Input::Down, is_pressed); break;
|
||||
case SDL_SCANCODE_SPACE: joysticks[0]->set_input(Inputs::Joystick::Input::Fire, is_pressed); break;
|
||||
case SDL_SCANCODE_A: joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 0), is_pressed); break;
|
||||
case SDL_SCANCODE_S: joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 1), is_pressed); break;
|
||||
case SDL_SCANCODE_D: joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 2), is_pressed); break;
|
||||
case SDL_SCANCODE_F: joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 3), is_pressed); break;
|
||||
default: {
|
||||
if(keypress.input.size()) {
|
||||
joysticks[0]->set_input(Inputs::Joystick::Input(keypress.input[0]), is_pressed);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
keypresses.clear();
|
||||
|
||||
// Push new joystick state, if any.
|
||||
if(joystick_machine) {
|
||||
auto &machine_joysticks = joystick_machine->get_joysticks();
|
||||
for(size_t c = 0; c < joysticks.size(); ++c) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user