#include "tim.h" #ifdef TIM_WINDOWS i64 tim_time_usec(void) { LARGE_INTEGER ticks = {0}; LARGE_INTEGER freq = {0}; QueryPerformanceCounter(&ticks); QueryPerformanceFrequency(&freq); return 1000000 * ticks.QuadPart / freq.QuadPart; } void tim_write_str(cstr s, i32 size) { HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE); WriteFile(h, s, size, NULL, NULL); FlushFileBuffers(h); } void tim_update_screen_size(void) { HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE); CONSOLE_SCREEN_BUFFER_INFO csbi = {0}; if (GetConsoleScreenBufferInfo(hout, &csbi) == 0) { printf("ERROR: can't get console buffer size\n"); exit(1); } i32 w = csbi.srWindow.Right - csbi.srWindow.Left + 1; i32 h = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; tim->resized = (u32)(w * h) <= TIM_MAX_CELLS && (w != tim->w || h != tim->h); if (tim->resized) { tim->w = tim->scopes[0].w = w; tim->h = tim->scopes[0].h = h; tim->window = csbi.srWindow; } } void tim_init_terminal(void) { DWORD mode = 0; HANDLE hin = GetStdHandle(STD_INPUT_HANDLE); GetConsoleMode(hin, &tim->mode_in); // get current input mode mode = tim->mode_in; // mode &= ~ENABLE_ECHO_INPUT; // disable echo mode &= ~ENABLE_LINE_INPUT; // disable line buffer // TODO: enable ctrl-c again mode &= ~ENABLE_PROCESSED_INPUT; // disable ctrl-c mode |= ENABLE_WINDOW_INPUT; // enable resize event mode |= ENABLE_MOUSE_INPUT; // enable mouse event mode |= ENABLE_EXTENDED_FLAGS; // for ENABLE_QUICK_EDIT mode &= ~ENABLE_QUICK_EDIT_MODE; // disable select mode SetConsoleMode(hin, mode); // set input mode // HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE); // GetConsoleMode(hout, &tim->mode_out); // get current output mode mode = tim->mode_out; // mode |= ENABLE_PROCESSED_OUTPUT; // enable ascii sequences mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; // enable vt sequences SetConsoleMode(hout, mode); // set output mode // tim->cp_in = GetConsoleCP(); // get current code page tim->cp_out = GetConsoleOutputCP(); // SetConsoleCP(CP_UTF8); // set utf8 in/out code page SetConsoleOutputCP(CP_UTF8); // tim_write_str(S("\33[?1049h")); // use alternate buffer tim_update_screen_size(); // } void tim_reset_terminal(void) { tim_write_str(S("\33[m")); // reset colors tim_write_str(S("\33[?25h")); // show cursor tim_write_str(S("\33[?1049l")); // exit alternate buffer HANDLE hin = GetStdHandle(STD_INPUT_HANDLE); // HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE); // SetConsoleMode(hin, tim->mode_in); // set original mode SetConsoleMode(hout, tim->mode_out); // SetConsoleCP(tim->cp_in); // set original code page SetConsoleOutputCP(tim->cp_out); // } void tim_read_event(i32 timeout_ms) { TimEvent* e = &tim->event; HANDLE h = GetStdHandle(STD_INPUT_HANDLE); const i8 key_table[256] = { [VK_BACK] = TimKey_Backspace, [VK_TAB] = TimKey_Tab, [VK_RETURN] = TimKey_Enter, [VK_ESCAPE] = TimKey_Escape, [VK_PRIOR] = TimKey_PageUp, [VK_NEXT] = TimKey_PageDown, [VK_END] = TimKey_End, [VK_HOME] = TimKey_Home, [VK_LEFT] = TimKey_Left, [VK_UP] = TimKey_Up, [VK_RIGHT] = TimKey_Right, [VK_DOWN] = TimKey_Down, [VK_INSERT] = TimKey_Insert, [VK_DELETE] = TimKey_Delete, }; while (true) { memset(e, 0, sizeof(*e)); // In cmd.exe the cursor somtimes reappears. This reliably hides it. tim_write_str(S("\33[?25l")); DWORD r = WaitForSingleObject(h, timeout_ms); if (r == WAIT_TIMEOUT) { e->type = TimEvent_Draw; tim_update_screen_size(); // workaround, see WINDOW_BUFFER_SIZE_EVENT return; } else if (r != WAIT_OBJECT_0) { continue; } // received input INPUT_RECORD rec = {0}; DWORD n = 0; ReadConsoleInputW(h, &rec, 1, &n); switch (rec.EventType) { case KEY_EVENT: { if (!rec.Event.KeyEvent.bKeyDown) { // only interested in key press continue; } i32 key = key_table[(u8)rec.Event.KeyEvent.wVirtualKeyCode]; WCHAR chr = rec.Event.KeyEvent.uChar.UnicodeChar; if (!key && chr < ' ') { // non printable key continue; } tim_update_screen_size(); // workaround, see WINDOW_BUFFER_SIZE_EVENT e->type = TimEvent_Key; if (key) { e->key = key; return; } e->key = chr; WideCharToMultiByte(CP_UTF8, 0, &chr, 1, e->s, sizeof(e->s), NULL, NULL); return; } case MOUSE_EVENT: { bool wheel = rec.Event.MouseEvent.dwEventFlags & MOUSE_WHEELED; if(wheel){ i16 scroll_value = HIWORD(rec.Event.MouseEvent.dwButtonState); e->type = TimEvent_Mouse; e->key = scroll_value > 0 ? TimKey_MouseScrollUp : TimKey_MouseScrollDown; return; } bool move = rec.Event.MouseEvent.dwEventFlags & ~DOUBLE_CLICK; bool left = rec.Event.MouseEvent.dwButtonState & FROM_LEFT_1ST_BUTTON_PRESSED; if (move || !left) { // ignore move events and buttons other than left continue; } tim_update_screen_size(); // workaround, see WINDOW_BUFFER_SIZE_EVENT e->type = TimEvent_Mouse; e->key = TimKey_MouseButtonLeft; e->x = rec.Event.MouseEvent.dwMousePosition.X - tim->window.Left; e->y = rec.Event.MouseEvent.dwMousePosition.Y - tim->window.Top; return; } case WINDOW_BUFFER_SIZE_EVENT: e->type = TimEvent_Draw; // cmd.exe screen buffer and window size are separate, making this // event a bit unreliable. Effectively it is only emitted when the // terminal width changes and not for the height. As a workaround // the screen size is updated every time an event is emitted. tim_update_screen_size(); return; } } // while } #endif // TIM_WINDOWS