Implement the ADB keyboard

Besides generating KeyboardEvents in the SDL event handler and
returning the key state in the register 0 reads of the AdbKeyboard
device, we also needed to generalize the ADB bus polling a bit. We now
check all devices that have the service request bit set, instead of
hardcoding the mouse.

The SDL key event -> ADB raw key code mapping is based on BasiliskII/
SheepShaver's, but cleaned up a bit.
This commit is contained in:
Mihai Parparita 2023-10-11 23:43:20 -07:00
parent 37cca00e13
commit 73272b28dd
8 changed files with 346 additions and 20 deletions

View File

@ -22,10 +22,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <core/hostevents.h>
#include <core/coresignal.h>
#include <cpu/ppc/ppcemu.h>
#include <devices/common/adb/adbkeyboard.h>
#include <loguru.hpp>
#include <SDL.h>
EventManager* EventManager::event_manager;
static int get_sdl_event_key_code(const SDL_KeyboardEvent &event);
void EventManager::poll_events()
{
SDL_Event event;
@ -47,11 +51,28 @@ void EventManager::poll_events()
break;
case SDL_KEYDOWN:
case SDL_KEYUP: {
int key_code = get_sdl_event_key_code(event.key);
if (key_code != -1) {
KeyboardEvent ke;
ke.key = key_code;
if (event.type == SDL_KEYDOWN) {
ke.flags = KEYBOARD_EVENT_DOWN;
key_downs++;
break;
case SDL_KEYUP:
} else {
ke.flags = KEYBOARD_EVENT_UP;
key_ups++;
}
// Caps Lock is a special case, since it's a toggle key
if (ke.key == AdbKey_CapsLock) {
ke.flags = SDL_GetModState() & KMOD_CAPS ?
KEYBOARD_EVENT_DOWN : KEYBOARD_EVENT_UP;
}
this->_keyboard_signal.emit(ke);
} else {
LOG_F(WARNING, "Unknow key %x pressed", event.key.keysym.sym);
}
}
break;
case SDL_MOUSEMOTION: {
@ -87,3 +108,138 @@ void EventManager::poll_events()
// perform post-processing
this->_post_signal.emit();
}
static int get_sdl_event_key_code(const SDL_KeyboardEvent &event)
{
switch (event.keysym.sym) {
case SDLK_a: return AdbKey_A;
case SDLK_b: return AdbKey_B;
case SDLK_c: return AdbKey_C;
case SDLK_d: return AdbKey_D;
case SDLK_e: return AdbKey_E;
case SDLK_f: return AdbKey_F;
case SDLK_g: return AdbKey_G;
case SDLK_h: return AdbKey_H;
case SDLK_i: return AdbKey_I;
case SDLK_j: return AdbKey_J;
case SDLK_k: return AdbKey_K;
case SDLK_l: return AdbKey_L;
case SDLK_m: return AdbKey_M;
case SDLK_n: return AdbKey_N;
case SDLK_o: return AdbKey_O;
case SDLK_p: return AdbKey_P;
case SDLK_q: return AdbKey_Q;
case SDLK_r: return AdbKey_R;
case SDLK_s: return AdbKey_S;
case SDLK_t: return AdbKey_T;
case SDLK_u: return AdbKey_U;
case SDLK_v: return AdbKey_V;
case SDLK_w: return AdbKey_W;
case SDLK_x: return AdbKey_X;
case SDLK_y: return AdbKey_Y;
case SDLK_z: return AdbKey_Z;
case SDLK_1: return AdbKey_1;
case SDLK_2: return AdbKey_2;
case SDLK_3: return AdbKey_3;
case SDLK_4: return AdbKey_4;
case SDLK_5: return AdbKey_5;
case SDLK_6: return AdbKey_6;
case SDLK_7: return AdbKey_7;
case SDLK_8: return AdbKey_8;
case SDLK_9: return AdbKey_9;
case SDLK_0: return AdbKey_0;
case SDLK_ESCAPE: return AdbKey_Escape;
case SDLK_BACKQUOTE: return AdbKey_Grave;
case SDLK_MINUS: return AdbKey_Minus;
case SDLK_EQUALS: return AdbKey_Equal;
case SDLK_LEFTBRACKET: return AdbKey_LeftBracket;
case SDLK_RIGHTBRACKET: return AdbKey_RightBracket;
case SDLK_BACKSLASH: return AdbKey_Backslash;
case SDLK_SEMICOLON: return AdbKey_Semicolon;
case SDLK_QUOTE: return AdbKey_Quote;
case SDLK_COMMA: return AdbKey_Comma;
case SDLK_PERIOD: return AdbKey_Period;
case SDLK_SLASH: return AdbKey_Slash;
// Convert shifted variants to unshifted
case SDLK_EXCLAIM: return AdbKey_1;
case SDLK_AT: return AdbKey_2;
case SDLK_HASH: return AdbKey_3;
case SDLK_DOLLAR: return AdbKey_4;
case SDLK_UNDERSCORE: return AdbKey_Minus;
case SDLK_PLUS: return AdbKey_Equal;
case SDLK_COLON: return AdbKey_Semicolon;
case SDLK_QUOTEDBL: return AdbKey_Quote;
case SDLK_LESS: return AdbKey_Comma;
case SDLK_GREATER: return AdbKey_Period;
case SDLK_QUESTION: return AdbKey_Slash;
case SDLK_TAB: return AdbKey_Tab;
case SDLK_RETURN: return AdbKey_Return;
case SDLK_SPACE: return AdbKey_Space;
case SDLK_BACKSPACE: return AdbKey_Delete;
case SDLK_DELETE: return AdbKey_ForwardDelete;
case SDLK_INSERT: return AdbKey_Help;
case SDLK_HOME: return AdbKey_Home;
case SDLK_HELP: return AdbKey_Home;
case SDLK_END: return AdbKey_End;
case SDLK_PAGEUP: return AdbKey_PageUp;
case SDLK_PAGEDOWN: return AdbKey_PageDown;
case SDLK_LCTRL: return AdbKey_Control;
case SDLK_RCTRL: return AdbKey_Control;
case SDLK_LSHIFT: return AdbKey_Shift;
case SDLK_RSHIFT: return AdbKey_Shift;
case SDLK_LALT: return AdbKey_Option;
case SDLK_RALT: return AdbKey_Option;
case SDLK_LGUI: return AdbKey_Command;
case SDLK_RGUI: return AdbKey_Command;
case SDLK_MENU: return AdbKey_Grave;
case SDLK_CAPSLOCK: return AdbKey_CapsLock;
case SDLK_UP: return AdbKey_ArrowUp;
case SDLK_DOWN: return AdbKey_ArrowDown;
case SDLK_LEFT: return AdbKey_ArrowLeft;
case SDLK_RIGHT: return AdbKey_ArrowRight;
;
case SDLK_KP_0: return AdbKey_Keypad0;
case SDLK_KP_1: return AdbKey_Keypad1;
case SDLK_KP_2: return AdbKey_Keypad2;
case SDLK_KP_3: return AdbKey_Keypad3;
case SDLK_KP_4: return AdbKey_Keypad4;
case SDLK_KP_5: return AdbKey_Keypad5;
case SDLK_KP_6: return AdbKey_Keypad6;
case SDLK_KP_7: return AdbKey_Keypad7;
case SDLK_KP_9: return AdbKey_Keypad9;
case SDLK_KP_8: return AdbKey_Keypad8;
case SDLK_KP_PERIOD: return AdbKey_KeypadDecimal;
case SDLK_KP_PLUS: return AdbKey_KeypadPlus;
case SDLK_KP_MINUS: return AdbKey_KeypadMinus;
case SDLK_KP_MULTIPLY: return AdbKey_KeypadMultiply;
case SDLK_KP_DIVIDE: return AdbKey_KeypadDivide;
case SDLK_KP_ENTER: return AdbKey_KeypadEnter;
case SDLK_KP_EQUALS: return AdbKey_KeypadEquals;
case SDLK_NUMLOCKCLEAR: return AdbKey_KeypadClear;
;
case SDLK_F1: return AdbKey_F1;
case SDLK_F2: return AdbKey_F2;
case SDLK_F3: return AdbKey_F3;
case SDLK_F4: return AdbKey_F4;
case SDLK_F5: return AdbKey_F5;
case SDLK_F6: return AdbKey_F6;
case SDLK_F7: return AdbKey_F7;
case SDLK_F8: return AdbKey_F8;
case SDLK_F9: return AdbKey_F9;
case SDLK_F10: return AdbKey_F10;
case SDLK_F11: return AdbKey_F11;
case SDLK_F12: return AdbKey_F12;
case SDLK_PRINTSCREEN: return AdbKey_F13;
case SDLK_SCROLLLOCK: return AdbKey_F14;
case SDLK_PAUSE: return AdbKey_F15;
}
return -1;
}

View File

@ -36,6 +36,16 @@ void AdbBus::register_device(AdbDevice* dev_obj) {
this->devices.push_back(dev_obj);
}
uint8_t AdbBus::poll() {
for (auto dev : this->devices) {
uint8_t dev_poll = dev->poll();
if (dev_poll) {
return dev_poll;
}
}
return 0;
}
uint8_t AdbBus::process_command(const uint8_t* in_data, int data_size) {
uint8_t dev_addr, dev_reg;
@ -76,8 +86,12 @@ uint8_t AdbBus::process_command(const uint8_t* in_data, int data_size) {
this->got_answer = false;
for (auto dev : this->devices)
for (auto dev : this->devices) {
this->got_answer = dev->talk(dev_addr, dev_reg);
if (this->got_answer) {
break;
}
}
if (!this->got_answer)
return ADB_STAT_TIMEOUT;

View File

@ -55,6 +55,11 @@ public:
uint8_t process_command(const uint8_t* in_data, int data_size);
uint8_t get_output_count() { return this->output_count; };
// Polls devices that have a service request flag set. Returns the talk
// command corresponding to the first device that responded with data, or
// 0 if no device responded.
uint8_t poll();
// callbacks meant to be called by devices
const uint8_t* get_input_buf() { return this->input_buf; };
uint8_t* get_output_buf() { return this->output_buf; };

View File

@ -39,6 +39,21 @@ int AdbDevice::device_postinit() {
return 0;
};
uint8_t AdbDevice::poll() {
if (!this->srq_flag) {
return 0;
}
bool has_data = this->get_register_0();
if (!has_data) {
return 0;
}
// Register 0 in bits 0-1 (both 0)
// Talk command in bits 2-3 (both 1)
// Device address in bits 4-7
return 0xC | (this->my_addr << 4);
}
bool AdbDevice::talk(const uint8_t dev_addr, const uint8_t reg_num) {
if (dev_addr == this->my_addr && !this->got_collision) {
// see if another device already responded to this command

View File

@ -49,6 +49,10 @@ public:
virtual void listen(const uint8_t dev_addr, const uint8_t reg_num);
virtual uint8_t get_address() { return this->my_addr; };
// Attempts to oolls the device. Returns the talk command corresponding to
// the device if it has data, 0 otherwise.
uint8_t poll();
protected:
uint8_t gen_random_address();

View File

@ -34,20 +34,45 @@ AdbKeyboard::AdbKeyboard(std::string name) : AdbDevice(name) {
}
void AdbKeyboard::event_handler(const KeyboardEvent& event) {
this->key = event.key;
if (event.flags & KEYBOARD_EVENT_DOWN) {
this->key_state = 0;
}
else if (event.flags & KEYBOARD_EVENT_UP) {
this->key_state = 1;
}
this->changed = true;
}
void AdbKeyboard::reset() {
this->my_addr = ADB_ADDR_KBD;
this->dev_handler_id = 2; // Extended ADB keyboard
this->exc_event_flag = 2;
this->exc_event_flag = 1;
this->srq_flag = 1; // enable service requests
this->key = 0;
this->key_state = 0;
this->changed = false;
}
bool AdbKeyboard::get_register_0() {
if (this->changed) {
uint8_t* out_buf = this->host_obj->get_output_buf();
out_buf[0] = (this->key_state << 7) | (this->key & 0x7F);
// It's possible that we get two events before the host polls us, but
// in practice it has not come up. We need to set the key status bit to
// 1 (released), otherwise if we leave it empty, the host will think
// that the a key (code 0) is pressed.
out_buf[1] = 1 << 7;
this->key = 0;
this->key_state = 0;
this->changed = false;
this->host_obj->set_output_count(2);
return true;
}
return false;
}

View File

@ -50,10 +50,122 @@ public:
private:
int32_t x_rel = 0;
int32_t y_rel = 0;
uint8_t buttons_state = 0;
uint32_t key = 0;
uint8_t key_state = 0;
bool changed = false;
};
// ADB Extended Keyboard raw key codes (most of which eventually became virtual
// key codes, see HIToolbox/Events.h).
enum AdbKey {
AdbKey_A = 0x00,
AdbKey_B = 0x0b,
AdbKey_C = 0x08,
AdbKey_D = 0x02,
AdbKey_E = 0x0e,
AdbKey_F = 0x03,
AdbKey_G = 0x05,
AdbKey_H = 0x04,
AdbKey_I = 0x22,
AdbKey_J = 0x26,
AdbKey_K = 0x28,
AdbKey_L = 0x25,
AdbKey_M = 0x2e,
AdbKey_N = 0x2d,
AdbKey_O = 0x1f,
AdbKey_P = 0x23,
AdbKey_Q = 0x0c,
AdbKey_R = 0x0f,
AdbKey_S = 0x01,
AdbKey_T = 0x11,
AdbKey_U = 0x20,
AdbKey_V = 0x09,
AdbKey_W = 0x0d,
AdbKey_X = 0x07,
AdbKey_Y = 0x10,
AdbKey_Z = 0x06,
AdbKey_1 = 0x12,
AdbKey_2 = 0x13,
AdbKey_3 = 0x14,
AdbKey_4 = 0x15,
AdbKey_5 = 0x17,
AdbKey_6 = 0x16,
AdbKey_7 = 0x1a,
AdbKey_8 = 0x1c,
AdbKey_9 = 0x19,
AdbKey_0 = 0x1d,
AdbKey_Minus = 0x1b,
AdbKey_Equal = 0x18,
AdbKey_LeftBracket = 0x21,
AdbKey_RightBracket = 0x1e,
AdbKey_Backslash = 0x2a,
AdbKey_Semicolon = 0x29,
AdbKey_Quote = 0x27,
AdbKey_Comma = 0x2b,
AdbKey_Period = 0x2f,
AdbKey_Slash = 0x2c,
AdbKey_Tab = 0x30,
AdbKey_Return = 0x24,
AdbKey_Space = 0x31,
AdbKey_Delete = 0x33,
AdbKey_ForwardDelete = 0x75,
AdbKey_Help = 0x72,
AdbKey_Home = 0x73,
AdbKey_End = 0x77,
AdbKey_PageUp = 0x74,
AdbKey_PageDown = 0x79,
AdbKey_Grave = 0x32,
AdbKey_Escape = 0x35,
AdbKey_Control = 0x36,
AdbKey_Shift = 0x38,
AdbKey_Option = 0x3a,
AdbKey_Command = 0x37,
AdbKey_CapsLock = 0x39,
AdbKey_ArrowUp = 0x3e,
AdbKey_ArrowDown = 0x3d,
AdbKey_ArrowLeft = 0x3b,
AdbKey_ArrowRight = 0x3c,
AdbKey_Keypad0 = 0x52,
AdbKey_Keypad1 = 0x53,
AdbKey_Keypad2 = 0x54,
AdbKey_Keypad3 = 0x55,
AdbKey_Keypad4 = 0x56,
AdbKey_Keypad5 = 0x57,
AdbKey_Keypad6 = 0x58,
AdbKey_Keypad7 = 0x59,
AdbKey_Keypad9 = 0x5c,
AdbKey_Keypad8 = 0x5b,
AdbKey_KeypadDecimal = 0x41,
AdbKey_KeypadPlus = 0x45,
AdbKey_KeypadMinus = 0x4e,
AdbKey_KeypadMultiply = 0x43,
AdbKey_KeypadDivide = 0x4b,
AdbKey_KeypadEnter = 0x4c,
AdbKey_KeypadEquals = 0x51,
AdbKey_KeypadClear = 0x47,
AdbKey_F1 = 0x7a,
AdbKey_F2 = 0x78,
AdbKey_F3 = 0x63,
AdbKey_F4 = 0x76,
AdbKey_F5 = 0x60,
AdbKey_F6 = 0x61,
AdbKey_F7 = 0x62,
AdbKey_F8 = 0x64,
AdbKey_F9 = 0x65,
AdbKey_F10 = 0x6d,
AdbKey_F11 = 0x67,
AdbKey_F12 = 0x6f,
AdbKey_F13 = 0x69,
AdbKey_F14 = 0x6b,
AdbKey_F15 = 0x71,
};
#endif // ADB_KEYBOARD_H

View File

@ -473,25 +473,20 @@ void ViaCuda::process_adb_command() {
}
void ViaCuda::autopoll_handler() {
uint8_t adb_status, output_size;
if (!this->autopoll_enabled)
return;
// send TALK R0 command to address 3 (mouse)
uint8_t in_data[2] = { 0x3C, 0};
uint8_t poll_command = this->adb_bus_obj->poll();
adb_status = this->adb_bus_obj->process_command(in_data, 1);
if (adb_status == ADB_STAT_OK) {
if (poll_command) {
if (!this->old_tip || !this->treq) {
LOG_F(WARNING, "Cuda transaction probably in progress");
}
// prepare autopoll packet
response_header(CUDA_PKT_ADB, adb_status | ADB_STAT_AUTOPOLL);
this->out_buf[2] = 0x3C; // put the proper ADB command
output_size = this->adb_bus_obj->get_output_count();
response_header(CUDA_PKT_ADB, ADB_STAT_OK | ADB_STAT_AUTOPOLL);
this->out_buf[2] = poll_command; // put the proper ADB command
uint8_t output_size = this->adb_bus_obj->get_output_count();
if (output_size) {
std::memcpy(&this->out_buf[3], this->adb_bus_obj->get_output_buf(), output_size);
this->out_count += output_size;