Viewing: keyboard.c
📄 keyboard.c (Read Only) ⬅ To go back
#include "hal/keyboard.h"

#if defined(__i386__)
#include "arch/x86/idt.h"
#include "io.h"
#include "vga_console.h"

static hal_keyboard_char_cb_t g_cb = 0;
static hal_keyboard_scan_cb_t g_scan_cb = 0;

/* Modifier state */
static volatile int shift_held = 0;
static volatile int ctrl_held = 0;
static volatile int alt_held = 0;

/* Extended scancode state (0xE0 prefix) */
static volatile int e0_prefix = 0;

static char scancode_map[128] = {
    0,  27, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '\b',
    '\t',
    'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',
    0,
    'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`',   0,
    '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/',   0,
    '*',
    0,
    ' ',
    0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    -1,
    0,
    0,
    0,
    '+',
    0,
    0,
    0,
    0,
    0,
    0, 0, 0,
    0,
    0,
    0,
};

static char scancode_map_shift[128] = {
    0,  27, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '\b',
    '\t',
    'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n',
    0,
    'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~',   0,
    '|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?',   0,
    '*',
    0,
    ' ',
    0,
    0, 0, 0, 0, 0, 0, 0, 0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    0,
    -1,
    0,
    0,
    0,
    '+',
    0,
    0,
    0,
    0,
    0,
    0, 0, 0,
    0,
    0,
    0,
};

/* Emit a VT100 escape sequence through the char callback */
static void emit_escape_seq(const char* seq) {
    if (!g_cb) return;
    for (int i = 0; seq[i]; i++) {
        g_cb(seq[i]);
    }
}

/* PS/2 scan set 1 scancodes */
#define SC_LSHIFT_PRESS  0x2A
#define SC_RSHIFT_PRESS  0x36
#define SC_LSHIFT_REL    0xAA
#define SC_RSHIFT_REL    0xB6
#define SC_LCTRL_PRESS   0x1D
#define SC_LCTRL_REL     0x9D
#define SC_LALT_PRESS    0x38
#define SC_LALT_REL      0xB8

/* Extended (0xE0-prefixed) scancodes */
#define SC_E0_UP    0x48
#define SC_E0_DOWN  0x50
#define SC_E0_LEFT  0x4B
#define SC_E0_RIGHT 0x4D
#define SC_E0_HOME  0x47
#define SC_E0_END   0x4F
#define SC_E0_PGUP  0x49
#define SC_E0_PGDN  0x51
#define SC_E0_DEL   0x53

static void handle_extended_press(uint8_t sc) {
    switch (sc) {
    case SC_E0_UP:    emit_escape_seq("\033[A"); break;
    case SC_E0_DOWN:  emit_escape_seq("\033[B"); break;
    case SC_E0_RIGHT: emit_escape_seq("\033[C"); break;
    case SC_E0_LEFT:  emit_escape_seq("\033[D"); break;
    case SC_E0_HOME:  emit_escape_seq("\033[H"); break;
    case SC_E0_END:   emit_escape_seq("\033[F"); break;
    case SC_E0_PGUP:
        if (shift_held) {
            vga_scroll_back();
        } else {
            emit_escape_seq("\033[5~");
        }
        break;
    case SC_E0_PGDN:
        if (shift_held) {
            vga_scroll_fwd();
        } else {
            emit_escape_seq("\033[6~");
        }
        break;
    case SC_E0_DEL:   emit_escape_seq("\033[3~"); break;
    default: break;
    }
}

static void kbd_irq(struct registers* regs) {
    (void)regs;

    uint8_t status = inb(0x64);
    if (!(status & 0x01)) return;

    uint8_t scancode = inb(0x60);

    /* Raw scancode callback (key press and release) */
    if (g_scan_cb) {
        g_scan_cb(scancode);
    }

    /* 0xE0 prefix: next byte is an extended scancode */
    if (scancode == 0xE0) {
        e0_prefix = 1;
        return;
    }

    if (e0_prefix) {
        e0_prefix = 0;
        /* Right CTRL (E0 1D / E0 9D) */
        if (scancode == SC_LCTRL_PRESS) { ctrl_held = 1; return; }
        if (scancode == SC_LCTRL_REL)  { ctrl_held = 0; return; }
        /* Right ALT (E0 38 / E0 B8) */
        if (scancode == SC_LALT_PRESS) { alt_held = 1; return; }
        if (scancode == SC_LALT_REL)  { alt_held = 0; return; }
        if (!(scancode & 0x80)) {
            handle_extended_press(scancode);
        }
        return;
    }

    /* Track modifier state */
    if (scancode == SC_LSHIFT_PRESS || scancode == SC_RSHIFT_PRESS) {
        shift_held = 1;
        return;
    }
    if (scancode == SC_LSHIFT_REL || scancode == SC_RSHIFT_REL) {
        shift_held = 0;
        return;
    }
    if (scancode == SC_LCTRL_PRESS) {
        ctrl_held = 1;
        return;
    }
    if (scancode == SC_LCTRL_REL) {
        ctrl_held = 0;
        return;
    }
    if (scancode == SC_LALT_PRESS) {
        alt_held = 1;
        return;
    }
    if (scancode == SC_LALT_REL) {
        alt_held = 0;
        return;
    }

    /* Ignore key releases for normal keys */
    if (scancode & 0x80) return;

    if (scancode < 128) {
        char c = shift_held ? scancode_map_shift[scancode] : scancode_map[scancode];
        if (c != 0 && g_cb) {
            if (ctrl_held && c >= 'a' && c <= 'z') {
                g_cb(c - 'a' + 1);  /* Ctrl+A=0x01 .. Ctrl+Z=0x1A */
            } else if (ctrl_held && c >= 'A' && c <= 'Z') {
                g_cb(c - 'A' + 1);
            } else if (alt_held) {
                g_cb('\033');  /* ESC prefix for Alt+key */
                g_cb(c);
            } else {
                g_cb(c);
            }
        }
    }
}

void hal_keyboard_init(hal_keyboard_char_cb_t cb) {
    g_cb = cb;
    register_interrupt_handler(33, kbd_irq);
}

void hal_keyboard_set_scancode_cb(hal_keyboard_scan_cb_t cb) {
    g_scan_cb = cb;
}
#else
void hal_keyboard_init(hal_keyboard_char_cb_t cb) {
    (void)cb;
}

void hal_keyboard_set_scancode_cb(hal_keyboard_scan_cb_t cb) {
    (void)cb;
}
#endif