diff options
Diffstat (limited to 'TUI/module.jai')
| -rw-r--r-- | TUI/module.jai | 280 |
1 files changed, 128 insertions, 152 deletions
diff --git a/TUI/module.jai b/TUI/module.jai index 6854d59..6ca6f59 100644 --- a/TUI/module.jai +++ b/TUI/module.jai @@ -84,13 +84,8 @@ Commands :: struct { initialized := false; - -input_buffer_mutex: Mutex; -input_buffer: string; -input_counter: s64; -read_buffer: [4096] u8; - -input_process_thread: Thread; +input_buffer : [64] u8; +input_string : string; Key :: u8; // TODO To be improved. Keys :: enum u8 { @@ -98,152 +93,94 @@ Keys :: enum u8 { Resize :: 1; //410; // TODO Why?! } -key_semaphore: Semaphore; -key_mutex: Mutex; -key_input := Keys.None; -key_resize := Keys.None; -key_buffer: [64] Keys; - -// Notes -// So, the semaphore usage should indicate the items on the key_queue. -// If we don't want to use a queue, maybe we can use two Key items, one for user input, and another, with higher priority, for Resize. dam :: (msg: string) { print(msg, to_standard_error = true); } - -process_input :: (thread: *Thread) -> s64 { - buffer: [64] u8; - input: string; - // char: u8; - dam(">START signal_input\n"); - defer dam(">STOP signal_input\n"); - while(true) { - if initialized == false return 0; - if key_input != Keys.None { - dam("waiting.."); - sleep_milliseconds(100); // We could increase this value as a push-back mechanism if we're just hitting it repeateadly. - dam("!\n\r"); - continue; - } - if input.count > 0 { - lock(*key_mutex); - key_input = xx input[0]; - unlock(*key_mutex); - signal(*key_semaphore); - - advance(*input); - continue; - } - - // dam("reading.."); - bytes_read := OS_read_input(buffer.data, buffer.count); - input.data = buffer.data; - input.count = bytes_read; - } - return 0; +push_key :: (key: Key) { + // TODO } -process_resize :: (signal_code : s32) #c_call { - new_context : Context; - push_context new_context { - print("SIGNAL:%", signal_code); - if signal_code != SIGWINCH return; - print("RESIZE\n"); - lock(*key_mutex); - defer unlock(*key_mutex); - // TODO Only signal the key_semaphore if we don't already have a Resize on the queue. - if key_resize != Keys.None return; - key_resize = Keys.Resize; - signal(*key_semaphore); - } -} +get_key :: (timeout_milliseconds: s32 = -1) -> Key { -get_key :: (wait_milliseconds: s32) -> Key { - wait_for(*key_semaphore, wait_milliseconds); - - lock(*key_mutex); - defer unlock(*key_mutex); + if OS_was_terminal_resized() return xx Keys.Resize; - if key_resize != Keys.None { - defer key_resize = Keys.None; - return xx key_resize; + if input_string.count > 0 { + defer advance(*input_string, 1); + return input_string[0]; } - defer key_input = Keys.None; - return xx key_input; -} + is_input_available := OS_wait_for_input(timeout_milliseconds); + if OS_was_terminal_resized() return xx Keys.Resize; -// TODO Rename this procedure. -set_handler :: () { - sa : sigaction_t; - sa.sa_handler = process_resize; - sigemptyset(*(sa.sa_mask)); - sa.sa_flags = SA_RESTART; - sigaction(SIGWINCH, *sa, null); + if is_input_available { + bytes_read := OS_read_input(input_buffer.data, input_buffer.count); // TODO Does not check for read errors. + if bytes_read > 0 { + input_string.data = input_buffer.data; + input_string.count = bytes_read; + defer advance(*input_string, 1); + return input_string[0]; + } + } + return xx Keys.None; } -// TODO Rename this procedure. -restore_handler :: () { - sa : sigaction_t; - sa.sa_handler = SIG_DFL; - sigaction(SIGWINCH, null, *sa); +get_str :: (str_limit: int = -1, allocator: Allocator = temp) -> string { + assert(allocator.proc != null, "Argument 'allocator.proc' has invalid null value."); + + if str_limit < 0 { + builder: String_Builder(); + builder.allocator = allocator; + init_string_builder(*builder); + + while(1) { + buffer := get_current_buffer(*builder); + buffer_data := get_buffer_data(buffer); + buffer.count = OS_read_input(buffer_data, buffer.allocated); // TODO Does not check for read errors. + if buffer.count < buffer.allocated break; + expand(*builder); + } + return builder_to_string(*builder, allocator); + } + else { + buffer := alloc_string(str_limit, allocator); + buffer.count = OS_read_input(buffer.data, str_limit); + return buffer; + } } start :: () { if initialized == true return; -dam("A"); - // write_strings(Commands.HideCursor, Commands.SaveCursorPosition, Commands.EnterAlternateBuffer, Commands.SetUTF8); + + write_strings(Commands.HideCursor, Commands.SaveCursorPosition, Commands.EnterAlternateBuffer, Commands.SetUTF8); OS_prepare_terminal(); -dam("B"); - input_buffer = alloc_string(4096); - init(*input_buffer_mutex, "input_buffer_mutex"); - assert(thread_init(*input_process_thread, process_input), "Failed to initialize thread."); -dam("C"); - init(*key_semaphore); - init(*key_mutex, "key_mutex"); -dam("D"); + + was_resized = false; + init(*resize_mutex, "resize_mutex"); + input_string.data = input_buffer.data; + input_string.count = 0; + initialized = true; - thread_start(*input_process_thread); + set_handler(); // TODO Move to beter place. -dam("E"); - } stop :: () { if initialized == false return; initialized = false; - restore_handler(); // TODO Move to beter place. - print(">A<"); - thread_deinit(*input_process_thread); - print(">B<"); - destroy(*key_semaphore); - print(">C<"); OS_reset_terminal(); write_strings(Commands.EnterMainBuffer, Commands.RestoreCursorPosition, Commands.ShowCursor); } -get_str :: () -> string { - lock(*input_buffer_mutex); - defer unlock(*input_buffer_mutex); - return input_buffer; - // return tprint("%", to_string(input_buffer)); - // print("%", tprint("%", input_buffer)); - // return tprint("%", input_buffer); -} - flush_input :: () { // TODO - lock(*input_buffer_mutex); - defer unlock(*input_buffer_mutex); - input_buffer.count = 0; } draw_box :: (x: int, y: int, width: int, height: int) { @@ -299,7 +236,31 @@ clear_terminal :: inline () { // TODO Maybe rename to "get_size()" get_terminal_size :: () -> rows: int, columns: int { assert(initialized, "TUI is not ready."); - rows, columns := OS_get_terminal_size(); + rows, columns: int = ---; + #if OS == .WINDOWS { + rows, columns = OS_get_terminal_size(); + } + else { + auto_release_temp(); + + flush_input(); + write_string(Commands.QueryWindowSizeInChars); + + input := get_str(64); + + // Expected message format: [8;<r>;<c>t + // where <r> is the number of rows and <c> of columns. + assert( + input[0] == #char "\e" && + input[1] == #char "[" && + input[2] == #char "8", + //input[input.count-1] == #char "t", + "Query window size in chars returned invalid response."); + + parts := split(input, ";"); + rows = parse_int(*parts[1]); + columns = parse_int(*parts[2]); + } return rows, columns; } @@ -313,11 +274,11 @@ get_cursor_position :: () -> row: int, column: int { assert(initialized, "TUI is not ready."); // TODO Should I use this inside each and every procedure? auto_release_temp(); - + + flush_input(); write_string(Commands.QueryCursorPosition); - input := talloc_string(64); - input.count = OS_read_input(input.data, input.count); // TODO Does not check for read errors. + input := get_str(64); // Expected message format: \e[<r>;<c>R // where <r> is the number of rows and <c> of columns. @@ -334,43 +295,58 @@ get_cursor_position :: () -> row: int, column: int { return row, column; } -Input_Mode :: enum u8 { - HUMAN; // Shows cursor, echoes input, and expects an enter at the end of the line. - MACHINE; // Hides cursor, hides input, and reads right away once the first input is available. -} - -// read_input :: (allocator: Allocator = temp, $mode: Input_Mode = .HUMAN) -> string { - // #if mode == .HUMAN { - // write_string(Commands.ShowCursor); - // defer write_string(Commands.HideCursor); - // - // OS_set_input_mode(.HUMAN); - // defer OS_set_input_mode(.MACHINE); - // } -// - // assert(allocator.proc != null, "Argument 'allocator.proc' has invalid null value."); -// - // #assert(mode != .MACHINE); // TODO Keep an eye if I try to use read_input for machine read. Eventually, remove mode from the procedure arguments. - // - // builder: String_Builder(); - // builder.allocator = allocator; - // init_string_builder(*builder); - // - // while(1) { - // buffer := get_current_buffer(*builder); - // buffer_data := get_buffer_data(buffer); - // buffer.count = OS_read_input(buffer_data, buffer.allocated); // TODO Does not check for read errors. - // if buffer.count == 0 || buffer_data[buffer.count-1] == #char "\n" break; - // assert(buffer.count == buffer.allocated); // TODO If newline wasn't detected, it's because the buffer got full. - // expand(*builder); - // } - // return builder_to_string(*builder, allocator); -// } - #if OS == .WINDOWS { // Prototyping zone... keep clear! } else #if OS == .LINUX || OS == .MACOS { // Prototyping zone... keep clear! + + OS_wait_for_input :: (timeout_milliseconds: s32) -> is_input_available: bool { + fds := pollfd.[ .{ fd = STDIN_FILENO, events = POLLIN, revents = 0 } ]; + nfds := fds.count; + poll_return := poll(fds.data, xx nfds, xx timeout_milliseconds); // TODO Wait for input using poll syscall. This breaks and throws '-1 | 4 | Interrupted system call' when we resize the window while polling. + error_code, error_message := get_error_value_and_string(); + return ifx poll_return > 0 then true else false; + } + + resize_mutex : Mutex; + was_resized : bool; + + OS_was_terminal_resized :: () -> bool { + lock(*resize_mutex); + defer unlock(*resize_mutex); + defer was_resized = false; + return was_resized; + } + + process_resize :: (signal_code : s32) #c_call { + new_context : Context; + push_context new_context { + print("SIGNAL:%", signal_code); + if signal_code != SIGWINCH then return; + if was_resized == true then return; + print("RESIZE\n"); + lock(*resize_mutex); + defer unlock(*resize_mutex); + was_resized = true; + } + } + + // TODO Rename this procedure. + set_handler :: () { + sa : sigaction_t; + sa.sa_handler = process_resize; + sigemptyset(*(sa.sa_mask)); + sa.sa_flags = SA_RESTART; + sigaction(SIGWINCH, *sa, null); + } + + // TODO Rename this procedure. + restore_handler :: () { + sa : sigaction_t; + sa.sa_handler = SIG_DFL; + sigaction(SIGWINCH, null, *sa); + } + } |
