#include #include #include #include #include #include #include #include #include #include static ringbuffer_t input; static thread_t* thread; static event_t outputevent; static event_t inputevent; static int outputlock; static termios tty; void console_write(char* str, size_t count){ while(!spinlock_trytoacquire(&outputlock)){ event_wait(&outputevent, false); } liminetty_writeuser(str, count); spinlock_release(&outputlock); event_signal(&outputevent, true); } #define THREAD_BUFF_MAX 2048 #define BACKSPACE_STR "\b \b" // taken from https://en.wikipedia.org/wiki/ANSI_escape_code#Terminal_input_sequences #define HOME_STR "\e[1~" #define INSERT_STR "\e[2~" #define DELETE_STR "\e[3~" #define END_STR "\e[4~" #define PGUP_STR "\e[5~" #define PGDN_STR "\e[6~" #define UP_STR "\e[A" #define DOWN_STR "\e[B" #define RIGHT_STR "\e[C" #define LEFT_STR "\e[D" __attribute__((noreturn)) static void console_thread(){ char buff[THREAD_BUFF_MAX]; int buffpos = 0; for(;;){ kbpacket_t packet; keyboard_getandwait(0, &packet); if(KBPACKET_FLAGS_RELEASED & packet.flags) continue; char* strbufptr; size_t strbuflen; char thingy[2]; if(packet.ascii){ if(packet.ascii == '\n'){ if(IGNCR & tty.c_iflag) continue; if((ICRNL & tty.c_iflag) == 0) packet.ascii = '\r'; } strbufptr = &packet.ascii; strbuflen = 1; if(packet.flags & (KBPACKET_FLAGS_LEFTCTRL | KBPACKET_FLAGS_RIGHTCTRL)){ if(!((packet.ascii > 'a' && packet.ascii < 'z') || (packet.ascii > 'A' && packet.ascii < 'Z'))) continue; strbufptr[0] = packet.ascii > 'a' ? packet.ascii - 0x60 : packet.ascii - 0x40; } } else{ switch(packet.keycode){ case KEYCODE_HOME: strbufptr = HOME_STR; break; case KEYCODE_INSERT: strbufptr = INSERT_STR; break; case KEYCODE_DELETE: strbufptr = DELETE_STR; break; case KEYCODE_END: strbufptr = END_STR; break; case KEYCODE_PAGEUP: strbufptr = PGUP_STR; break; case KEYCODE_PAGEDOWN: strbufptr = PGDN_STR; break; case KEYCODE_UP: strbufptr = UP_STR; break; case KEYCODE_DOWN: strbufptr = DOWN_STR; break; case KEYCODE_RIGHT: strbufptr = RIGHT_STR; break; case KEYCODE_LEFT: strbufptr = LEFT_STR; break; default: continue; } strbuflen = strlen(strbufptr); } if(tty.c_lflag & ICANON){ bool flush = false; switch(packet.ascii){ case '\b': if(buffpos){ --buffpos; buff[buffpos] = '\0'; if(tty.c_lflag & ECHO) console_write(BACKSPACE_STR, strlen(BACKSPACE_STR)); } continue; case '\n': flush = true; break; } if(packet.ascii && tty.c_lflag & ECHO){ console_write(strbufptr, strbuflen); } for(size_t i = 0; i < strbuflen; ++i){ buff[buffpos++] = strbufptr[i]; if(buffpos == THREAD_BUFF_MAX){ flush = true; break; } } if(flush){ arch_interrupt_disable(); ringbuffer_write(&input, buff, buffpos); arch_interrupt_enable(); buffpos = 0; event_signal(&inputevent, true); } } else{ // not canon if(tty.c_lflag & ECHO){ console_write(strbufptr, strbuflen); } arch_interrupt_disable(); ringbuffer_write(&input, strbufptr, strbuflen); arch_interrupt_enable(); event_signal(&inputevent, true); } } } static int isatty(int minor){ return 0; } static int read(int *error, int dev, void* buff, size_t count, size_t offset){ int readc = 0; *error = 0; while(readc < count){ arch_interrupt_disable(); readc += ringbuffer_read(&input, buff+readc, count-readc); arch_interrupt_enable(); if(readc < count && readc < tty.c_cc[VMIN]) *error = event_wait(&inputevent, true); if(*error) return -1; if(readc >= tty.c_cc[VMIN]) break; } return readc; } static int write(int *error, int dev, void* buff, size_t count, size_t offset){ *error = 0; console_write(buff, count); return count; } static int isseekable(){ return ESPIPE; } #define TCGETS 0x5401 #define TCSETS 0x5402 #define TIOCGWINSZ 0x5413 typedef struct{ unsigned short ws_row; unsigned short ws_col; unsigned short ws_xpixel; unsigned short ws_ypixel; } winsize; #include static int ioctl(int minor, unsigned long req, void* arg){ switch(req){ case TIOCGWINSZ: struct limine_terminal* ltty = liminetty_get(0); winsize* w = arg; w->ws_row = ltty->rows; w->ws_col = ltty->columns; break; case TCGETS: *(termios*)arg = tty; break; case TCSETS: tty = *(termios*)arg; break; default: return ENOTTY; } return 0; } #include static int poll(int minor, pollfd* fd){ if((fd->events & POLLIN) && input.write != input.read) fd->revents |= POLLIN; if((fd->events & POLLOUT) && outputlock == 0) fd->revents |= POLLOUT; return 0; } static devcalls calls = { read, write, isatty, isseekable, ioctl, poll }; static void callback(struct limine_terminal *terminal, uint64_t type, uint64_t arg1, uint64_t arg2, uint64_t arg3){ asm("cli;hlt"); } void consoledev_init(){ if(devman_newdevice("console", TYPE_CHARDEV, MAJOR_CONSOLE, 0, &calls)) _panic("Failed to create console device", 0); if(ringbuffer_init(&input, THREAD_BUFF_MAX)) _panic("Failed to initialise console ringbuffer", 0); thread = sched_newkthread(console_thread, THREAD_DEFAULT_KSTACK_SIZE, true, THREAD_PRIORITY_KERNEL); if(!thread) _panic("Failed to initialise console thread", 0); tty.c_iflag = ICRNL; tty.c_lflag = ECHO | ICANON | ISIG; tty.c_cc[VINTR] = 0x03; tty.ibaud = 38400; tty.obaud = 38400; tty.c_cc[VMIN] = 1; liminetty_setcallback(0, callback); }