From: jxnshi Date: Wed, 1 Jan 2025 19:52:05 +0000 (+0100) Subject: Update X-Git-Url: https://jxnshi.xyz/repos?a=commitdiff_plain;h=e0ecb247f3bba044e066b5d9bd42e64d3e0d107b;p=mesange.git Update --- diff --git a/client-cli/client-cli b/client-cli/client-cli index 3c95ce5..7fdf5e6 100755 Binary files a/client-cli/client-cli and b/client-cli/client-cli differ diff --git a/client-cli/command.odin b/client-cli/command.odin new file mode 100644 index 0000000..a9e076f --- /dev/null +++ b/client-cli/command.odin @@ -0,0 +1,30 @@ +package main + +import "core:os" +import "core:sync" + +import fpath "core:path/filepath" + +handle_command :: proc(app: ^App, command: string) -> bool { + sync.mutex_lock(&app.mutex) + defer sync.mutex_unlock(&app.mutex) + + // State specific commands. + #partial switch app.state { + case .Ask_Config: + switch command { + case ":r", ":retry": + case ":del", ":delete": + config_path := fpath.join({ app.storage_path, "config.json" }, context.temp_allocator) + os.remove_directory(config_path) + } + } + + // General commands. + switch command { + case ":q", ":quit": app.running = false + case: return false + } + + return true +} diff --git a/client-cli/main.odin b/client-cli/main.odin index e06d47d..2b6a629 100644 --- a/client-cli/main.odin +++ b/client-cli/main.odin @@ -8,12 +8,17 @@ import "core:log" import "core:net" import "core:os" import "core:strings" +import "core:sync" +import "core:thread" import fpath "core:path/filepath" -import nc "ncurses" +import nc "../ncurses" -import "../common" +COLOR_FIRST :: 16 +COLOR_SECOND :: 17 +COLOR_THIRD :: 18 +COLOR_FOURTH :: 19 Config :: struct { selected_profile: Maybe(string), @@ -28,7 +33,7 @@ config_deinit :: proc(config: Config) { config_update :: proc(config: Config) { home_path := os.get_env("HOME") - storage_path := fpath.join({ home_path, "mesange-cli" }, context.temp_allocator) + storage_path := fpath.join({ home_path, "mesange" }, context.temp_allocator) config_path := fpath.join({ storage_path, "config.json" }, context.temp_allocator) config_file, err1 := os.open(config_path, os.O_WRONLY | os.O_CREATE | os.O_TRUNC, 0777) @@ -60,12 +65,16 @@ Profile :: struct { } App :: struct { + mutex: sync.Mutex, + running: bool, + screen: ^nc.Window, storage_path: string, state: State, box_message: Maybe(^nc.Window), info_bar: ^nc.Window, + input_window: ^nc.Window, config: Config, profile_password: Maybe(string), @@ -75,15 +84,27 @@ App :: struct { app_init :: proc() -> App { screen := nc.initscr() + nc.cbreak() nc.keypad(screen, true) nc.curs_set(0) + nc.use_default_colors() + nc.start_color() + + nc.init_color(COLOR_FIRST, 800, 800, 800) + nc.init_color(COLOR_SECOND, 500, 500, 500) + nc.init_color(COLOR_THIRD, 200, 200, 200) + nc.init_color(COLOR_FOURTH, 000, 000, 000) + + nc.init_pair(1, COLOR_FIRST, COLOR_THIRD) + nc.init_pair(2, COLOR_FIRST, COLOR_FOURTH) + screen_height, screen_width := nc.getmaxyx(screen) - info_bar := nc.newwin(0, 0, 3, screen_width) + info_bar := nc.newwin(1, screen_width, screen_height - 2, 0) + input_window := nc.newwin(1, screen_width, screen_height - 1, 0) - nc.box(info_bar, 0, 0) - nc.wrefresh(info_bar) + nc.refresh() home_path := os.get_env("HOME") storage_path := fpath.join({ home_path, "mesange" }) @@ -97,15 +118,25 @@ app_init :: proc() -> App { } } - return { + app := App{ + running = true, + screen = screen, storage_path = storage_path, + + info_bar = info_bar, + input_window = input_window, } + + app_clear_info_bar(app) + + return app } app_deinit :: proc(app: App) { config_deinit(app.config) + nc.delwin(app.input_window) nc.delwin(app.info_bar) if box_message, ok := app.box_message.?; ok { @@ -121,11 +152,21 @@ app_set_state :: proc(app: ^App, state: State) { nc.clear() nc.refresh() + app_clear_info_bar(app^) + app.state = state } app_set_info_bar :: proc(app: ^App, content: string) { - + app_clear_info_bar(app^) + + c_content := strings.clone_to_cstring(content, context.temp_allocator) + + nc.wattron(app.info_bar, nc.COLOR_PAIR(1)) + nc.wprintw(app.info_bar, c_content) + nc.wattroff(app.info_bar, nc.COLOR_PAIR(1)) + + nc.wrefresh(app.info_bar) } app_set_box_message :: proc(app: ^App, lines: []string) { @@ -150,19 +191,38 @@ app_set_box_message :: proc(app: ^App, lines: []string) { (screen_width - width) / 2, ) + color := nc.COLOR_PAIR(2) + + nc.wattron(box_message, color) + for line, i in lines { c_line := strings.clone_to_cstring(line, context.temp_allocator) nc.mvwprintw(box_message, i32(i + 1), 1, c_line) } nc.box(box_message, 0, 0) + nc.wattroff(box_message, color) + nc.wrefresh(box_message) + sync.mutex_lock(&app.mutex) app.box_message = box_message + sync.mutex_unlock(&app.mutex) } app_clear_info_bar :: proc(app: App) { - + height, width := nc.getmaxyx(app.info_bar) + color := nc.COLOR_PAIR(1) + + nc.wclear(app.info_bar) + nc.wattron(app.info_bar, color) + + for _ in 0.. string { context.allocator = allocator + nc.wclear(app.input_window) + nc.wrefresh(app.input_window) + buffer: [256]u8 screen_height, screen_width := nc.getmaxyx(app.screen) nc.curs_set(1) - nc.mvgetnstr(screen_height - 1, 0, raw_data(buffer[:]), len(buffer)) + nc.wgetnstr(app.input_window, raw_data(buffer[:]), len(buffer)) nc.curs_set(0) return strings.clone_from_cstring(cstring(raw_data(&buffer))) } +handle_state :: proc(app: ^App) { + for { + switch app.state { + case .Load_Config: state_load_config(app) + case .Ask_Config: state_ask_config(app) + + case .Load_Profile: state_load_profile(app) + case .Ask_Profile: state_ask_profile(app) + case .Ask_Profile_Password: state_ask_profile_password(app) + case .Ask_Profile_Seed_Phrase: state_ask_profile_seed_phrase(app) + } + } +} + main :: proc() { app := app_init() defer app_deinit(app) - for { - switch app.state { - case .Load_Config: state_load_config(&app) - case .Ask_Config: state_ask_config(&app) + handle_state_thread := thread.create_and_start_with_poly_data(&app, handle_state, context) + defer thread.terminate(handle_state_thread, 0) + + for app.running { - case .Load_Profile: state_load_profile(&app) - case .Ask_Profile: state_ask_profile(&app) - case .Ask_Profile_Password: state_ask_profile_password(&app) - case .Ask_Profile_Seed_Phrase: state_ask_profile_seed_phrase(&app) - } } } diff --git a/client-cli/ncurses b/client-cli/ncurses deleted file mode 160000 index 8712a2b..0000000 --- a/client-cli/ncurses +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8712a2b7a368771baf35ce0e860b577c3f87052d diff --git a/client-cli/state.odin b/client-cli/state.odin index 1cf3dde..86ea699 100644 --- a/client-cli/state.odin +++ b/client-cli/state.odin @@ -50,13 +50,13 @@ state_ask_config :: proc(app: ^App) { input := app_get_input(app^, context.temp_allocator) - switch input { - case ":retry": - case ":delete": - config_path := fpath.join({ app.storage_path, "config.json" }, context.temp_allocator) - os.remove_directory(config_path) - - case: + if input[0] == ':' { + if !handle_command(app, input) { + app_set_info_bar(app, "Invalid command.") + return + } + } else { + app_set_info_bar(app, "Invalid input.") return } @@ -100,10 +100,22 @@ state_ask_profile :: proc(app: ^App) { input := app_get_input(app^) + if input[0] == ':' { + if !handle_command(app, input) { + app_set_info_bar(app, "Invalid command.") + return + } + } + if len(input) == 0 { return } + if !is_identifier(input) { + app_set_info_bar(app, "Invalid profile name.") + return + } + app.config.selected_profile = app_get_input(app^) config_update(app.config) diff --git a/client-cli/util.odin b/client-cli/util.odin new file mode 100644 index 0000000..009a89d --- /dev/null +++ b/client-cli/util.odin @@ -0,0 +1,39 @@ +package main + +is_alphabetic :: proc(char: u8) -> bool { + switch char { + case 'A'..='Z', 'a'..='z': return true + case: return false + } +} + +is_alphanumeric :: proc(char: u8) -> bool { + if is_alphabetic(char) { + return true + } + + switch char { + case '0'..='1': return true + case: return false + } +} + +is_identifier :: proc(input: string) -> bool { + if len(input) == 0 { + return false + } + + if !is_alphabetic(input[0]) && input[0] != '_' { + return false + } + + for char in input { + char := u8(char) + + if !is_alphanumeric(char) && char != '_' { + return false + } + } + + return true +}