// Kernel input core. This file owns canonical keyboard and mouse state shared by input drivers and syscalls. #include #include #include #include #include #include #include #include #include #include #include #include #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 KE_INPUT_CONTEXT gDefaultInputContext; static volatile uint32_t gNextInputDeviceId = 1; static char KiInputKeyToChar(KE_INPUT_CONTEXT* context, INPUT_KEY key); static void KiInputServiceRepeat(KE_INPUT_CONTEXT* context); // Advances the physical input snapshot generation after visible state changes. static void KiInputBumpGeneration(KE_INPUT_CONTEXT* context) { _InterlockedIncrement64(&context->Generation); } 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, '.', '.' }, }; // Updates the waitable keyboard event to match queued keyboard input. static void KiInputRefreshKeyboardEvent(KE_INPUT_CONTEXT* context) { if (!context->WaitEvent) return; if (context->KeyHead != context->KeyTail) KeSignalEvent(context->WaitEvent); else KeClearEvent(context->WaitEvent); } // Queues a keyboard event and wakes waiters when the event was accepted. static void KiInputQueueKey(KE_INPUT_CONTEXT* context, INPUT_KEY key, char ch) { if (key == INPUT_KEY_NONE) return; int next = (context->KeyTail + 1) & (int)KEY_QUEUE_MASK; if (next == context->KeyHead) return; context->KeyQueue[context->KeyTail].Key = key; context->KeyQueue[context->KeyTail].Character = ch; context->KeyQueue[context->KeyTail].Modifiers = context->Modifiers; context->KeyTail = next; KiInputRefreshKeyboardEvent(context); } // Implements the input Queue Has Space operation. static bool KiInputQueueHasSpace(KE_INPUT_CONTEXT* context) { int next = (context->KeyTail + 1) & (int)KEY_QUEUE_MASK; return next != context->KeyHead; } // Implements the input Key Is Repeatable operation. 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; } } // Implements the input Now Ticks operation. static uint64_t KiInputNowTicks(KE_INPUT_CONTEXT* context) { if (context->Hal && context->Hal->Timer.QueryTicks) return context->Hal->Timer.QueryTicks(); return 0; } // Implements the input Start Repeat operation. static void KiInputStartRepeat(KE_INPUT_CONTEXT* context, INPUT_KEY key, uint64_t now) { if (!KiInputKeyIsRepeatable(key)) return; context->RepeatKey = key; context->RepeatNextTick = now + KEY_REPEAT_DELAY_US; } // Implements the input Stop Repeat operation. static void KiInputStopRepeat(KE_INPUT_CONTEXT* context, INPUT_KEY key) { if (context->RepeatKey == key) { context->RepeatKey = INPUT_KEY_NONE; context->RepeatNextTick = 0; } } // Implements the input Service Repeat operation. static void KiInputServiceRepeat(KE_INPUT_CONTEXT* context) { INPUT_KEY key = context->RepeatKey; if (!KiInputKeyIsRepeatable(key) || key >= MAX_INPUT_KEYS || !context->KeyDown[key]) { context->RepeatKey = INPUT_KEY_NONE; context->RepeatNextTick = 0; return; } uint64_t now = KiInputNowTicks(context); if (now == 0 || now < context->RepeatNextTick) return; if (!KiInputQueueHasSpace(context)) { context->RepeatNextTick = now + KEY_REPEAT_PERIOD_US; return; } KiInputQueueKey(context, key, KiInputKeyToChar(context, key)); context->RepeatNextTick = now + KEY_REPEAT_PERIOD_US; } // Implements the input Repeat Dpc Routine operation. static void KiInputRepeatDpcRoutine(KDPC* dpc, void* context, void* systemArgument1, void* systemArgument2) { (void)dpc; (void)systemArgument1; (void)systemArgument2; KiInputServiceRepeat((KE_INPUT_CONTEXT*)context); } // Implements the input Set Modifier operation. static void KiInputSetModifier(KE_INPUT_CONTEXT* context, 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) context->Modifiers |= bit; else context->Modifiers &= ~bit; } if (pressed && key == INPUT_KEY_CAPS_LOCK) context->Modifiers ^= INPUT_MOD_CAPS_LOCK; if (pressed && key == INPUT_KEY_NUM_LOCK) context->Modifiers ^= INPUT_MOD_NUM_LOCK; } // Implements the input Key To Char operation. static char KiInputKeyToChar(KE_INPUT_CONTEXT* context, INPUT_KEY key) { if (key >= INPUT_KEY_A && key <= INPUT_KEY_Z) { bool shifted = (context->Modifiers & INPUT_MOD_SHIFT) != 0; bool caps = (context->Modifiers & 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 = (context->Modifiers & 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; } // Implements the input Initialize operation. void KeInputInitialize(const HAL_API* hal) { KE_INPUT_CONTEXT* context = &gDefaultInputContext; context->Hal = hal; memset((void*)context->KeyDown, 0, sizeof(context->KeyDown)); context->Modifiers = 0; context->Generation = 0; context->KeyHead = context->KeyTail = 0; context->RepeatKey = INPUT_KEY_NONE; context->RepeatNextTick = 0; context->MouseHead = context->MouseTail = 0; context->MouseBoundsWidth = 1920; context->MouseBoundsHeight = 1080; context->MouseWheel = 0; context->MouseButtons = 0; KeInitializeDpc(&context->RepeatDpc, KiInputRepeatDpcRoutine, context); if (!context->WaitEvent) { KSTATUS s = KeEventCreate(EventManualReset, 0, &context->WaitEvent); if (!KSUCCESS(s)) DbgPrint("Input: keyboard wait event create failed st=0x%x\n", s); } KiInputRefreshKeyboardEvent(context); int w = 1920, h = 1080; DISPLAY_MODE mode; if (KSUCCESS(KeDisplayGetMode(&mode))) { w = (int)mode.Width; h = (int)mode.Height; } KeInputMouseSetBounds(w, h); KeInputMouseSetPosition(w / 2, h / 2); KeDisplayShowCursor(true); DbgPrint("Input: kernel input core initialized (%dx%d)\n", w, h); } // Implements the input Register Device operation. __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 NTLSTATUS_NO_MEMORY; if (outDeviceId) *outDeviceId = id; DbgPrint("Input: registered device %u (%s)\n", id, name ? name : "unnamed"); return NTLSTATUS_SUCCESS; } // Implements the input Submit Key operation. __declspec(dllexport) void KeInputSubmitKey(const INPUT_KEY_EVENT* event) { KE_INPUT_CONTEXT* context = &gDefaultInputContext; if (!event) return; INPUT_KEY key = event->Key; if (key == INPUT_KEY_NONE || key >= MAX_INPUT_KEYS) return; bool wasDown = context->KeyDown[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; context->KeyDown[key] = 1u; KiInputSetModifier(context, key, true); KiInputBumpGeneration(context); KiInputQueueKey(context, key, KiInputKeyToChar(context, key)); KiInputStartRepeat(context, key, KiInputNowTicks(context)); } else { if (!wasDown) return; context->KeyDown[key] = 0u; KiInputSetModifier(context, key, false); KiInputBumpGeneration(context); KiInputStopRepeat(context, key); } } // Implements the input Commit Mouse Position operation. static bool KiInputCommitMousePosition(KE_INPUT_CONTEXT* context, int nx, int ny, uint8_t buttons) { int bw = context->MouseBoundsWidth; int bh = context->MouseBoundsHeight; 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; bool changed = (nx != context->MouseX) || (ny != context->MouseY) || (buttons != context->MouseButtons); context->MouseX = nx; context->MouseY = ny; context->MouseButtons = buttons; KeDisplaySetCursorPosition(nx, ny); return changed; } // Implements the input Submit Mouse operation. __declspec(dllexport) void KeInputSubmitMouse(const INPUT_MOUSE_EVENT* event) { KE_INPUT_CONTEXT* context = &gDefaultInputContext; if (!event) return; int oldX = context->MouseX; int oldY = context->MouseY; int nx = oldX + event->dx; int ny = oldY + event->dy; bool changed = KiInputCommitMousePosition(context, nx, ny, event->buttons); if (event->wheel != 0) { context->MouseWheel += event->wheel; changed = true; } if (changed) KiInputBumpGeneration(context); int next = (context->MouseTail + 1) & (int)MOUSE_QUEUE_MASK; if (next != context->MouseHead) { context->MouseQueue[context->MouseTail] = *event; context->MouseTail = next; } } // Implements the input Submit Mouse Absolute operation. __declspec(dllexport) void KeInputSubmitMouseAbsolute(const INPUT_MOUSE_ABSOLUTE_EVENT* event) { KE_INPUT_CONTEXT* context = &gDefaultInputContext; if (!event) return; int oldX = context->MouseX; int oldY = context->MouseY; bool changed = KiInputCommitMousePosition(context, event->x, event->y, event->buttons); if (event->wheel != 0) { context->MouseWheel += event->wheel; changed = true; } if (changed) KiInputBumpGeneration(context); INPUT_MOUSE_EVENT rel; rel.dx = context->MouseX - oldX; rel.dy = context->MouseY - oldY; rel.wheel = event->wheel; rel.buttons = event->buttons; int next = (context->MouseTail + 1) & (int)MOUSE_QUEUE_MASK; if (next != context->MouseHead) { context->MouseQueue[context->MouseTail] = rel; context->MouseTail = next; } } // Implements the input Submit Mouse Absolute Scaled operation. __declspec(dllexport) void KeInputSubmitMouseAbsoluteScaled(uint32_t rawX, uint32_t rawY, uint32_t maxX, uint32_t maxY, int wheel, uint8_t buttons) { KE_INPUT_CONTEXT* context = &gDefaultInputContext; int bw = context->MouseBoundsWidth; int bh = context->MouseBoundsHeight; if (bw < 1) bw = 1; if (bh < 1) bh = 1; INPUT_MOUSE_ABSOLUTE_EVENT ev; ev.x = maxX ? (int)(((uint64_t)rawX * (uint64_t)(bw - 1)) / (uint64_t)maxX) : 0; ev.y = maxY ? (int)(((uint64_t)rawY * (uint64_t)(bh - 1)) / (uint64_t)maxY) : 0; ev.wheel = wheel; ev.buttons = buttons; KeInputSubmitMouseAbsolute(&ev); } // Returns the next queued key without consuming it. INPUT_KEY KeInputKbPoll(void) { KE_INPUT_CONTEXT* context = &gDefaultInputContext; if (context->KeyHead == context->KeyTail) KiInputServiceRepeat(context); return (context->KeyHead == context->KeyTail) ? INPUT_KEY_NONE : context->KeyQueue[context->KeyHead].Key; } // Waits for keyboard input in one context or until the timeout expires. static KSTATUS KiInputWaitKey(KE_INPUT_CONTEXT* context, uint8_t alertable, uint64_t timeoutMs) { if (!context) return NTLSTATUS_INVALID_PARAMETER; KiInputServiceRepeat(context); if (context->KeyHead != context->KeyTail) return NTLSTATUS_SUCCESS; if (!context->WaitEvent) return NTLSTATUS_FAILURE; while (true) { EVENT* event = context->WaitEvent; uint32_t selected = 0; KSTATUS s = KeWaitForMultipleEvents(&event, 1, alertable, timeoutMs, &selected); if (!KSUCCESS(s)) return s; KiInputServiceRepeat(context); if (context->KeyHead != context->KeyTail) return NTLSTATUS_SUCCESS; } } // Waits for keyboard input in the default input context. KSTATUS KeInputWaitKey(uint8_t alertable, uint64_t timeoutMs) { return KiInputWaitKey(&gDefaultInputContext, alertable, timeoutMs); } // Services typematic repeat from the scheduler timer bottom half. void KeInputTimerTick(void) { KE_INPUT_CONTEXT* context = &gDefaultInputContext; INPUT_KEY key = context->RepeatKey; if (!KiInputKeyIsRepeatable(key) || key >= MAX_INPUT_KEYS || !context->KeyDown[key]) return; uint64_t now = KiInputNowTicks(context); if (now != 0 && now >= context->RepeatNextTick) KeInsertQueueDpc(&context->RepeatDpc, (void*)0, (void*)0); } // Implements the input Is Key Down operation. bool KeInputIsKeyDown(INPUT_KEY key) { KE_INPUT_CONTEXT* context = &gDefaultInputContext; if (key == INPUT_KEY_NONE || key >= MAX_INPUT_KEYS) return false; return context->KeyDown[key] != 0; } // Peeks one keyboard event from an input context without consuming it. static bool KiInputPeekEvent(KE_INPUT_CONTEXT* context, INPUT_KEY_RECORD* record) { if (!context || !record) return false; if (context->KeyHead == context->KeyTail) KiInputServiceRepeat(context); if (context->KeyHead == context->KeyTail) return false; INPUT_KEY_QUEUE_ENTRY entry = context->KeyQueue[context->KeyHead]; record->Key = entry.Key; record->Char = entry.Character; record->Modifiers = entry.Modifiers; return true; } // Reads one keyboard event from an input context. static bool KiInputReadEvent(KE_INPUT_CONTEXT* context, INPUT_KEY_RECORD* record) { if (!KiInputPeekEvent(context, record)) return false; context->KeyHead = (context->KeyHead + 1) & (int)KEY_QUEUE_MASK; KiInputRefreshKeyboardEvent(context); return true; } // Peeks one keyboard event from the default input context. bool KeInputPeekEvent(INPUT_KEY_RECORD* record) { return KiInputPeekEvent(&gDefaultInputContext, record); } // Reads one keyboard event from the default input context. bool KeInputReadEvent(INPUT_KEY_RECORD* record) { return KiInputReadEvent(&gDefaultInputContext, record); } // Implements the input Read Key operation. INPUT_KEY KeInputReadKey(void) { INPUT_KEY_RECORD record; return KeInputReadEvent(&record) ? record.Key : INPUT_KEY_NONE; } // Implements the input Read Char operation. char KeInputReadChar(void) { INPUT_KEY_RECORD record; return KeInputReadEvent(&record) ? record.Char : 0; } // Implements the input Get Modifiers operation. uint32_t KeInputGetModifiers(void) { KE_INPUT_CONTEXT* context = &gDefaultInputContext; return context->Modifiers; } // Snapshots the current physical global input state without consuming events. KSTATUS KeInputQueryGlobalState(SYS_GLOBAL_INPUT_STATE* out) { KE_INPUT_CONTEXT* context = &gDefaultInputContext; if (!out) return NTLSTATUS_INVALID_PARAMETER; out->Size = sizeof(*out); out->Flags = SYS_GLOBAL_INPUT_STATE_FLAG_NONE; out->Generation = (uint64_t)context->Generation; out->Modifiers = context->Modifiers; memset(out->KeyDown, 0, sizeof(out->KeyDown)); for (uint32_t key = 0; key < MAX_INPUT_KEYS; key++) { if (context->KeyDown[key]) out->KeyDown[key >> 3] |= (uint8_t)(1u << (key & 7u)); } out->MouseX = context->MouseX; out->MouseY = context->MouseY; out->MouseWheel = context->MouseWheel; out->MouseButtons = context->MouseButtons; return NTLSTATUS_SUCCESS; } // Implements the input Mouse Peek operation. bool KeInputMousePeek(INPUT_MOUSE_EVENT* event) { KE_INPUT_CONTEXT* context = &gDefaultInputContext; if (!event) return false; if (context->MouseHead == context->MouseTail) return false; *event = context->MouseQueue[context->MouseHead]; return true; } // Implements the input Mouse Read operation. bool KeInputMouseRead(INPUT_MOUSE_EVENT* event) { KE_INPUT_CONTEXT* context = &gDefaultInputContext; if (!KeInputMousePeek(event)) return false; context->MouseHead = (context->MouseHead + 1) & (int)MOUSE_QUEUE_MASK; return true; } // Implements the input Mouse Get Position operation. void KeInputMouseGetPosition(int* x, int* y) { KE_INPUT_CONTEXT* context = &gDefaultInputContext; if (x) *x = context->MouseX; if (y) *y = context->MouseY; } // Implements the input Mouse Set Position operation. void KeInputMouseSetPosition(int x, int y) { KE_INPUT_CONTEXT* context = &gDefaultInputContext; int bw = context->MouseBoundsWidth; int bh = context->MouseBoundsHeight; 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; bool changed = (x != context->MouseX) || (y != context->MouseY); context->MouseX = x; context->MouseY = y; KeDisplaySetCursorPosition(x, y); if (changed) KiInputBumpGeneration(context); } // Implements the input Mouse Set Bounds operation. void KeInputMouseSetBounds(int w, int h) { KE_INPUT_CONTEXT* context = &gDefaultInputContext; if (w < 1) w = 1; if (h < 1) h = 1; context->MouseBoundsWidth = w; context->MouseBoundsHeight = h; KeInputMouseSetPosition(context->MouseX, context->MouseY); } // Destroys an input queue handle. static void KiInputQueueDestroy(OBJECT* object) { INPUT_QUEUE* queue = CONTAINING_RECORD(object, INPUT_QUEUE, Header); if (queue->WaitEvent) { ObDereferenceObject(&queue->WaitEvent->Header); queue->WaitEvent = (EVENT*)0; } queue->Context = (KE_INPUT_CONTEXT*)0; } // Opens the default keyboard input queue as a waitable object. KSTATUS KeInputOpenQueue(INPUT_QUEUE** out) { KE_INPUT_CONTEXT* context = &gDefaultInputContext; if (!out) return NTLSTATUS_INVALID_PARAMETER; *out = (INPUT_QUEUE*)0; if (!context->WaitEvent) return NTLSTATUS_FAILURE; INPUT_QUEUE* queue = (INPUT_QUEUE*)ObAllocateObject(ObjectTypeInputQueue, sizeof(INPUT_QUEUE)); if (!queue) return NTLSTATUS_NO_MEMORY; queue->Header.Destroy = KiInputQueueDestroy; queue->Context = context; ObReferenceObject(&context->WaitEvent->Header); queue->WaitEvent = context->WaitEvent; *out = queue; return NTLSTATUS_SUCCESS; } // Peeks one keyboard event from the queue's owning context. bool KeInputQueuePeekEvent(INPUT_QUEUE* queue, INPUT_KEY_RECORD* record) { return queue ? KiInputPeekEvent(queue->Context, record) : false; } // Reads one keyboard event from the queue's owning context. bool KeInputQueueReadEvent(INPUT_QUEUE* queue, INPUT_KEY_RECORD* record) { return queue ? KiInputReadEvent(queue->Context, record) : false; } // Waits for keyboard input in the queue's owning context. KSTATUS KeInputQueueWaitKey(INPUT_QUEUE* queue, uint8_t alertable, uint64_t timeoutMs) { return queue ? KiInputWaitKey(queue->Context, alertable, timeoutMs) : NTLSTATUS_INVALID_PARAMETER; } // Returns the event that wakes when keyboard input can be read. EVENT* KeInputQueueWaitEvent(INPUT_QUEUE* queue) { return queue ? queue->WaitEvent : (EVENT*)0; }