353 lines
11 KiB
C
353 lines
11 KiB
C
// ke_input.c - kernel-owned canonical input core
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
|
|
#include <kernel/ke_input.h>
|
|
#include <kernel/ke_debug.h>
|
|
|
|
#define KEY_QUEUE_SIZE 256u
|
|
#define KEY_QUEUE_MASK (KEY_QUEUE_SIZE - 1u)
|
|
#define MOUSE_QUEUE_SIZE 128u
|
|
#define MOUSE_QUEUE_MASK (MOUSE_QUEUE_SIZE - 1u)
|
|
#define MAX_INPUT_KEYS 256u
|
|
#define MAX_INPUT_DEVICES 16u
|
|
#define KEY_REPEAT_DELAY_US 500000ull
|
|
#define KEY_REPEAT_PERIOD_US 33000ull
|
|
|
|
typedef struct KEY_CHAR_PAIR {
|
|
INPUT_KEY key;
|
|
char normal;
|
|
char shifted;
|
|
} KEY_CHAR_PAIR;
|
|
|
|
static const HAL_API* gInputHal;
|
|
|
|
static volatile uint8_t gKeyDown[MAX_INPUT_KEYS];
|
|
static volatile uint32_t gModifiers;
|
|
|
|
typedef struct INPUT_KEY_QUEUE_ENTRY {
|
|
INPUT_KEY key;
|
|
char ch;
|
|
} INPUT_KEY_QUEUE_ENTRY;
|
|
|
|
static INPUT_KEY_QUEUE_ENTRY gKeyQueue[KEY_QUEUE_SIZE];
|
|
static volatile int gKeyHead;
|
|
static volatile int gKeyTail;
|
|
static volatile INPUT_KEY gRepeatKey;
|
|
static volatile uint64_t gRepeatNextTick;
|
|
|
|
static INPUT_MOUSE_EVENT gMouseQueue[MOUSE_QUEUE_SIZE];
|
|
static volatile int gMouseHead;
|
|
static volatile int gMouseTail;
|
|
static volatile int gMouseX;
|
|
static volatile int gMouseY;
|
|
static volatile int gMouseBoundsW = 1920;
|
|
static volatile int gMouseBoundsH = 1080;
|
|
static volatile uint8_t gMouseButtons;
|
|
|
|
static volatile uint32_t gNextInputDeviceId = 1;
|
|
|
|
static char KiInputKeyToChar(INPUT_KEY key);
|
|
|
|
static const KEY_CHAR_PAIR gCharMap[] = {
|
|
{ INPUT_KEY_1, '1', '!' }, { INPUT_KEY_2, '2', '@' },
|
|
{ INPUT_KEY_3, '3', '#' }, { INPUT_KEY_4, '4', '$' },
|
|
{ INPUT_KEY_5, '5', '%' }, { INPUT_KEY_6, '6', '^' },
|
|
{ INPUT_KEY_7, '7', '&' }, { INPUT_KEY_8, '8', '*' },
|
|
{ INPUT_KEY_9, '9', '(' }, { INPUT_KEY_0, '0', ')' },
|
|
{ INPUT_KEY_MINUS, '-', '_' }, { INPUT_KEY_EQUAL, '=', '+' },
|
|
{ INPUT_KEY_LEFT_BRACKET, '[', '{' }, { INPUT_KEY_RIGHT_BRACKET, ']', '}' },
|
|
{ INPUT_KEY_BACKSLASH, '\\', '|' }, { INPUT_KEY_SEMICOLON, ';', ':' },
|
|
{ INPUT_KEY_APOSTROPHE, '\'', '"' }, { INPUT_KEY_GRAVE, '`', '~' },
|
|
{ INPUT_KEY_COMMA, ',', '<' }, { INPUT_KEY_PERIOD, '.', '>' },
|
|
{ INPUT_KEY_SLASH, '/', '?' },
|
|
{ INPUT_KEY_SPACE, ' ', ' ' }, { INPUT_KEY_TAB, '\t', '\t' },
|
|
{ INPUT_KEY_ENTER, '\n', '\n' }, { INPUT_KEY_BACKSPACE, '\b', '\b' },
|
|
{ INPUT_KEY_KP_DIVIDE, '/', '/' }, { INPUT_KEY_KP_MULTIPLY, '*', '*' },
|
|
{ INPUT_KEY_KP_MINUS, '-', '-' }, { INPUT_KEY_KP_PLUS, '+', '+' },
|
|
{ INPUT_KEY_KP_ENTER, '\n', '\n' }, { INPUT_KEY_KP_1, '1', '1' },
|
|
{ INPUT_KEY_KP_2, '2', '2' }, { INPUT_KEY_KP_3, '3', '3' },
|
|
{ INPUT_KEY_KP_4, '4', '4' }, { INPUT_KEY_KP_5, '5', '5' },
|
|
{ INPUT_KEY_KP_6, '6', '6' }, { INPUT_KEY_KP_7, '7', '7' },
|
|
{ INPUT_KEY_KP_8, '8', '8' }, { INPUT_KEY_KP_9, '9', '9' },
|
|
{ INPUT_KEY_KP_0, '0', '0' }, { INPUT_KEY_KP_PERIOD, '.', '.' },
|
|
};
|
|
|
|
static void KiInputQueueKey(INPUT_KEY key, char ch) {
|
|
if (key == INPUT_KEY_NONE) return;
|
|
int next = (gKeyTail + 1) & (int)KEY_QUEUE_MASK;
|
|
if (next == gKeyHead) return;
|
|
gKeyQueue[gKeyTail].key = key;
|
|
gKeyQueue[gKeyTail].ch = ch;
|
|
gKeyTail = next;
|
|
}
|
|
|
|
static bool KiInputQueueHasSpace(void) {
|
|
int next = (gKeyTail + 1) & (int)KEY_QUEUE_MASK;
|
|
return next != gKeyHead;
|
|
}
|
|
|
|
static bool KiInputKeyIsRepeatable(INPUT_KEY key) {
|
|
if (key == INPUT_KEY_NONE) return false;
|
|
|
|
switch (key) {
|
|
case INPUT_KEY_LEFT_SHIFT:
|
|
case INPUT_KEY_RIGHT_SHIFT:
|
|
case INPUT_KEY_LEFT_CTRL:
|
|
case INPUT_KEY_RIGHT_CTRL:
|
|
case INPUT_KEY_LEFT_ALT:
|
|
case INPUT_KEY_RIGHT_ALT:
|
|
case INPUT_KEY_LEFT_GUI:
|
|
case INPUT_KEY_RIGHT_GUI:
|
|
case INPUT_KEY_CAPS_LOCK:
|
|
case INPUT_KEY_NUM_LOCK:
|
|
return false;
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
static uint64_t KiInputNowTicks(void) {
|
|
if (gInputHal && gInputHal->Timer.QueryTicks)
|
|
return gInputHal->Timer.QueryTicks();
|
|
return 0;
|
|
}
|
|
|
|
static void KiInputStartRepeat(INPUT_KEY key, uint64_t now) {
|
|
if (!KiInputKeyIsRepeatable(key)) return;
|
|
gRepeatKey = key;
|
|
gRepeatNextTick = now + KEY_REPEAT_DELAY_US;
|
|
}
|
|
|
|
static void KiInputStopRepeat(INPUT_KEY key) {
|
|
if (gRepeatKey == key) {
|
|
gRepeatKey = INPUT_KEY_NONE;
|
|
gRepeatNextTick = 0;
|
|
}
|
|
}
|
|
|
|
static void KiInputServiceRepeat(void) {
|
|
INPUT_KEY key = gRepeatKey;
|
|
if (!KiInputKeyIsRepeatable(key) || key >= MAX_INPUT_KEYS || !gKeyDown[key]) {
|
|
gRepeatKey = INPUT_KEY_NONE;
|
|
gRepeatNextTick = 0;
|
|
return;
|
|
}
|
|
|
|
uint64_t now = KiInputNowTicks();
|
|
if (now == 0 || now < gRepeatNextTick)
|
|
return;
|
|
|
|
if (!KiInputQueueHasSpace()) {
|
|
gRepeatNextTick = now + KEY_REPEAT_PERIOD_US;
|
|
return;
|
|
}
|
|
|
|
KiInputQueueKey(key, KiInputKeyToChar(key));
|
|
gRepeatNextTick = now + KEY_REPEAT_PERIOD_US;
|
|
}
|
|
|
|
static void KiInputSetModifier(INPUT_KEY key, bool pressed) {
|
|
uint32_t bit = 0;
|
|
switch (key) {
|
|
case INPUT_KEY_LEFT_SHIFT: bit = INPUT_MOD_LSHIFT; break;
|
|
case INPUT_KEY_RIGHT_SHIFT: bit = INPUT_MOD_RSHIFT; break;
|
|
case INPUT_KEY_LEFT_CTRL: bit = INPUT_MOD_LCTRL; break;
|
|
case INPUT_KEY_RIGHT_CTRL: bit = INPUT_MOD_RCTRL; break;
|
|
case INPUT_KEY_LEFT_ALT: bit = INPUT_MOD_LALT; break;
|
|
case INPUT_KEY_RIGHT_ALT: bit = INPUT_MOD_RALT; break;
|
|
case INPUT_KEY_LEFT_GUI: bit = INPUT_MOD_LGUI; break;
|
|
case INPUT_KEY_RIGHT_GUI: bit = INPUT_MOD_RGUI; break;
|
|
default: break;
|
|
}
|
|
|
|
if (bit) {
|
|
if (pressed) gModifiers |= bit;
|
|
else gModifiers &= ~bit;
|
|
}
|
|
|
|
if (pressed && key == INPUT_KEY_CAPS_LOCK) gModifiers ^= INPUT_MOD_CAPS_LOCK;
|
|
if (pressed && key == INPUT_KEY_NUM_LOCK) gModifiers ^= INPUT_MOD_NUM_LOCK;
|
|
}
|
|
|
|
static char KiInputKeyToChar(INPUT_KEY key) {
|
|
if (key >= INPUT_KEY_A && key <= INPUT_KEY_Z) {
|
|
bool shifted = (gModifiers & INPUT_MOD_SHIFT) != 0;
|
|
bool caps = (gModifiers & INPUT_MOD_CAPS_LOCK) != 0;
|
|
char c = (char)('a' + (key - INPUT_KEY_A));
|
|
if (shifted ^ caps) c = (char)(c - 'a' + 'A');
|
|
return c;
|
|
}
|
|
|
|
bool shifted = (gModifiers & INPUT_MOD_SHIFT) != 0;
|
|
for (size_t i = 0; i < sizeof(gCharMap) / sizeof(gCharMap[0]); i++) {
|
|
if (gCharMap[i].key == key)
|
|
return shifted ? gCharMap[i].shifted : gCharMap[i].normal;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void KeInputInitialize(const HAL_API* hal) {
|
|
gInputHal = hal;
|
|
memset((void*)gKeyDown, 0, sizeof(gKeyDown));
|
|
gModifiers = 0;
|
|
gKeyHead = gKeyTail = 0;
|
|
gRepeatKey = INPUT_KEY_NONE;
|
|
gRepeatNextTick = 0;
|
|
gMouseHead = gMouseTail = 0;
|
|
gMouseButtons = 0;
|
|
|
|
int w = 1920, h = 1080;
|
|
if (hal && hal->Screen.GetSize) hal->Screen.GetSize(&w, &h);
|
|
KeInputMouseSetBounds(w, h);
|
|
KeInputMouseSetPosition(w / 2, h / 2);
|
|
|
|
if (hal && hal->Screen.DrawCursor)
|
|
hal->Screen.DrawCursor(gMouseX, gMouseY);
|
|
|
|
DbgPrint("Input: kernel input core initialized (%dx%d)\n", w, h);
|
|
}
|
|
|
|
__declspec(dllexport) KSTATUS KeInputRegisterDevice(const char* name, uint32_t flags, uint32_t* outDeviceId) {
|
|
(void)flags;
|
|
uint32_t id = gNextInputDeviceId++;
|
|
if (id >= MAX_INPUT_DEVICES) return STATUS_NO_MEMORY;
|
|
if (outDeviceId) *outDeviceId = id;
|
|
DbgPrint("Input: registered device %u (%s)\n", id, name ? name : "unnamed");
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
__declspec(dllexport) void KeInputSubmitKey(const INPUT_KEY_EVENT* event) {
|
|
if (!event) return;
|
|
INPUT_KEY key = event->Key;
|
|
if (key == INPUT_KEY_NONE || key >= MAX_INPUT_KEYS) return;
|
|
|
|
bool wasDown = gKeyDown[key] != 0;
|
|
bool pressed = event->Pressed;
|
|
|
|
if (pressed) {
|
|
// The input core owns typematic repeat. Treat repeated hardware make
|
|
// events as idempotent so PS/2 typematic and USB HID share one policy.
|
|
if (wasDown)
|
|
return;
|
|
|
|
gKeyDown[key] = 1u;
|
|
KiInputSetModifier(key, true);
|
|
KiInputQueueKey(key, KiInputKeyToChar(key));
|
|
KiInputStartRepeat(key, KiInputNowTicks());
|
|
} else {
|
|
if (!wasDown)
|
|
return;
|
|
|
|
gKeyDown[key] = 0u;
|
|
KiInputSetModifier(key, false);
|
|
KiInputStopRepeat(key);
|
|
}
|
|
}
|
|
|
|
__declspec(dllexport) void KeInputSubmitMouse(const INPUT_MOUSE_EVENT* event) {
|
|
if (!event) return;
|
|
|
|
int nx = gMouseX + event->dx;
|
|
int ny = gMouseY + event->dy;
|
|
int bw = gMouseBoundsW;
|
|
int bh = gMouseBoundsH;
|
|
|
|
if (bw < 1) bw = 1;
|
|
if (bh < 1) bh = 1;
|
|
if (nx < 0) nx = 0;
|
|
if (ny < 0) ny = 0;
|
|
if (nx >= bw) nx = bw - 1;
|
|
if (ny >= bh) ny = bh - 1;
|
|
|
|
if (gInputHal && gInputHal->Screen.EraseCursor)
|
|
gInputHal->Screen.EraseCursor();
|
|
|
|
gMouseX = nx;
|
|
gMouseY = ny;
|
|
gMouseButtons = event->buttons;
|
|
|
|
if (gInputHal && gInputHal->Screen.DrawCursor)
|
|
gInputHal->Screen.DrawCursor(nx, ny);
|
|
|
|
int next = (gMouseTail + 1) & (int)MOUSE_QUEUE_MASK;
|
|
if (next != gMouseHead) {
|
|
gMouseQueue[gMouseTail] = *event;
|
|
gMouseTail = next;
|
|
}
|
|
}
|
|
|
|
INPUT_KEY KeInputKbPoll(void) {
|
|
if (gKeyHead == gKeyTail) KiInputServiceRepeat();
|
|
return (gKeyHead == gKeyTail) ? INPUT_KEY_NONE : gKeyQueue[gKeyHead].key;
|
|
}
|
|
|
|
bool KeInputIsKeyDown(INPUT_KEY key) {
|
|
if (key == INPUT_KEY_NONE || key >= MAX_INPUT_KEYS) return false;
|
|
return gKeyDown[key] != 0;
|
|
}
|
|
|
|
INPUT_KEY KeInputReadKey(void) {
|
|
if (gKeyHead == gKeyTail) KiInputServiceRepeat();
|
|
if (gKeyHead == gKeyTail) return INPUT_KEY_NONE;
|
|
INPUT_KEY key = gKeyQueue[gKeyHead].key;
|
|
gKeyHead = (gKeyHead + 1) & (int)KEY_QUEUE_MASK;
|
|
return key;
|
|
}
|
|
|
|
char KeInputReadChar(void) {
|
|
if (gKeyHead == gKeyTail) KiInputServiceRepeat();
|
|
if (gKeyHead == gKeyTail) return 0;
|
|
char ch = gKeyQueue[gKeyHead].ch;
|
|
gKeyHead = (gKeyHead + 1) & (int)KEY_QUEUE_MASK;
|
|
return ch;
|
|
}
|
|
|
|
uint32_t KeInputGetModifiers(void) {
|
|
return gModifiers;
|
|
}
|
|
|
|
bool KeInputMousePoll(void) {
|
|
return gMouseHead != gMouseTail;
|
|
}
|
|
|
|
bool KeInputMouseRead(int* dx, int* dy, uint8_t* buttons) {
|
|
if (!dx || !dy || !buttons) return false;
|
|
if (gMouseHead == gMouseTail) return false;
|
|
|
|
INPUT_MOUSE_EVENT ev = gMouseQueue[gMouseHead];
|
|
gMouseHead = (gMouseHead + 1) & (int)MOUSE_QUEUE_MASK;
|
|
*dx = ev.dx;
|
|
*dy = ev.dy;
|
|
*buttons = ev.buttons;
|
|
return true;
|
|
}
|
|
|
|
void KeInputMouseGetPosition(int* x, int* y) {
|
|
if (x) *x = gMouseX;
|
|
if (y) *y = gMouseY;
|
|
}
|
|
|
|
void KeInputMouseSetPosition(int x, int y) {
|
|
int bw = gMouseBoundsW;
|
|
int bh = gMouseBoundsH;
|
|
if (bw < 1) bw = 1;
|
|
if (bh < 1) bh = 1;
|
|
if (x < 0) x = 0;
|
|
if (y < 0) y = 0;
|
|
if (x >= bw) x = bw - 1;
|
|
if (y >= bh) y = bh - 1;
|
|
gMouseX = x;
|
|
gMouseY = y;
|
|
}
|
|
|
|
void KeInputMouseSetBounds(int w, int h) {
|
|
if (w < 1) w = 1;
|
|
if (h < 1) h = 1;
|
|
gMouseBoundsW = w;
|
|
gMouseBoundsH = h;
|
|
KeInputMouseSetPosition(gMouseX, gMouseY);
|
|
}
|