Handle_Command_Error :: enum {
Not_A_Command,
Unknown_Command,
+ Invalid_Arguments,
}
handle_command :: proc(app: ^App, command: string) -> Maybe(Handle_Command_Error) {
sync.mutex_lock(&app.mutex)
defer sync.mutex_unlock(&app.mutex)
+ command_split, err := strings.split(command, " ", context.temp_allocator)
+ command_name := command_split[0]
+ args := command_split[1:]
+
// State specific commands.
#partial switch app.state {
case .Invalid_Config:
- switch command {
+ switch command_name {
case ":r", ":retry":
return nil
}
case .Invalid_Profile:
- switch command {
+ switch command_name {
case ":r", ":retry":
return nil
}
case .Ask_Profile_Seed_Phrase:
- switch command {
+ switch command_name {
case ":reg", ":regen":
app_reset_seed_phrase(app)
return nil
}
case .Invalid_Host:
- switch command {
+ switch command_name {
case ":r", ":retry":
app_set_state(app, .Connect_To_Host)
return nil
}
// General commands.
- switch command {
- case ":q", ":quit":
+ switch command_name {
+ case ":conf", ":config":
+ if len(args) == 0 {
+ app_set_info_bar(app, "No field name provided.")
+ return .Invalid_Arguments
+ }
+
+ field_name := args[0]
+
+ if len(args) == 1 {
+ switch field_name {
+ case "seed_phrase":
+ app_set_info_bar(app, "TODO")
+ return nil
+
+ case "host":
+ app_set_info_bar(app, "%v", app.profile.host)
+
+ case:
+ app_set_info_bar(app, "Invalid field name.")
+ return .Invalid_Arguments
+ }
+ }
+
+ return nil
+
+ case ":q", ":quit":
app.running = false
return nil
}
use_enum_names = true,
}
+Nav_Action :: enum {
+ up,
+ down,
+ select,
+ escape,
+}
+
App :: struct {
mutex: sync.Mutex,
running: bool,
info_bar: ^nc.Window,
input_window: ^nc.Window,
+ room_list_window: ^nc.Window,
+ room_window: ^nc.Window,
+
info_bar_content: string,
config: Config,
seed_phrase_entropy: u128,
seed_phrase_checksum: u8,
+ connecting: bool,
+ nav_action: Nav_Action,
+ room_selected: Maybe(u32),
host: Maybe(net.TCP_Socket),
connection_thread: Maybe(^thread.Thread),
}
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_FIRST, 900, 900, 900)
+ nc.init_color(COLOR_SECOND, 600, 600, 600)
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)
+ nc.init_pair(3, COLOR_SECOND, COLOR_FOURTH)
screen_height, screen_width := nc.getmaxyx(screen)
sync.mutex_lock(&app.mutex)
defer sync.mutex_unlock(&app.mutex)
- net.close(app.host)
+ if connection_thread, ok := app.connection_thread.?; ok {
+ thread.destroy(connection_thread)
+ }
+
+ if host, ok := app.host.?; ok {
+ net.close(host)
+ }
profile_deinit(app.profile)
config_deinit(app.config)
+ if len(app.info_bar_content) != 0 {
+ delete(app.info_bar_content)
+ }
+
+ if app.state == .Main {
+ nc.delwin(app.room_window)
+ nc.delwin(app.room_list_window)
+ }
+
nc.delwin(app.input_window)
nc.delwin(app.info_bar)
app_clear_box_message(app)
app_update_info_bar(app)
+ screen_height, screen_width := nc.getmaxyx(app.screen)
+
+ if app.state != .Main && state == .Main {
+ app.room_list_window = nc.newwin(screen_height - 2, 30, 0, 0)
+ app.room_window = nc.newwin(screen_height - 2, screen_width - 30, 0, 30)
+
+ color := nc.COLOR_PAIR(3)
+
+ nc.wattron(app.room_list_window, color)
+ nc.box(app.room_list_window, 0, 0)
+ nc.wattroff(app.room_list_window, color)
+
+ nc.wattron(app.room_window, color)
+ nc.box(app.room_window, 0, 0)
+ nc.wattroff(app.room_window, color)
+
+ nc.wrefresh(app.room_list_window)
+ nc.wrefresh(app.room_window)
+ }
+
app.state = state
}
-app_set_info_bar :: proc(app: ^App, content: string) {
- app.info_bar_content = content
+app_set_info_bar :: proc(app: ^App, format: string, args: ..any) {
+ if len(app.info_bar_content) != 0 {
+ delete(app.info_bar_content)
+ }
+
+ info_bar_content_builder: strings.Builder
+
+ strings.builder_init_none(&info_bar_content_builder)
+ defer strings.builder_destroy(&info_bar_content_builder)
+
+ fmt.sbprintf(&info_bar_content_builder, format, ..args)
+
+ app.info_bar_content = strings.clone(strings.to_string(info_bar_content_builder))
app_update_info_bar(app)
}
}
}
-app_get_input :: proc(app: ^App, hidden := false, allow_empty := false, allocator := context.allocator) -> string {
+app_get_input :: proc(
+ app: ^App,
+ hidden := false,
+ allow_empty := false,
+ only_command := false,
+ allocator := context.allocator,
+) -> string {
context.allocator = allocator
for {
input_loop: for {
char := nc.wgetch(app.input_window)
+ if only_command && (buffer_len == 0 || buffer[0] != ':') && char != ':' {
+ continue
+ }
+
switch char {
case '\n':
break input_loop
sync.mutex_lock(&app.mutex)
defer sync.mutex_unlock(&app.mutex)
- if app.host != nil && app.connection_thread != nil {
- thread.destroy(app.connection_thread)
+ if !app.connecting && app.connection_thread != nil {
+ connection_thread := app.connection_thread.?
+
+ thread.destroy(connection_thread)
app.connection_thread = nil
}
}
import "core:slice"
import "core:strings"
import "core:sync"
+import "core:thread"
import fpath "core:path/filepath"
},
)
- input := app_get_input(app, true, true, context.temp_allocator)
+ input := app_get_input(app, true, true, allocator = context.temp_allocator)
if err, ok := handle_command(app, input).?; ok {
if err != .Not_A_Command {
}
handle_connection_to_host :: proc(app: ^App) {
+ {
+ sync.mutex_lock(&app.mutex)
+ defer sync.mutex_unlock(&app.mutex)
+
+ app.connecting = true
+ }
+
+ defer {
+ sync.mutex_lock(&app.mutex)
+ defer sync.mutex_unlock(&app.mutex)
+
+ app.connecting = false
+ }
+
app_set_info_bar(app, "Connecting to host...")
host_endpoint: net.Endpoint
- host_endpoint.port = DEFAULT_PORT
blk: { // Resolve host endpoint.
ok: bool
}
}
+ if host_endpoint.port == 0 {
+ host_endpoint.port = DEFAULT_PORT
+ }
+
host, err := net.dial_tcp_from_endpoint(host_endpoint)
if err != nil {
return
}
+ app_set_info_bar(app, "Connected to host.")
+
{
sync.mutex_lock(&app.mutex)
defer sync.mutex_unlock(&app.mutex)
}
state_connect_to_host :: proc(app: ^App) {
- app_set_box_message(
- app,
- {
- "Connecting to host.",
- }
- )
+ app_set_info_bar(app, "Connecting to host..")
{
sync.mutex_lock(&app.mutex)
- defer sunc.mutex_unlock(&app.mutex)
+ defer sync.mutex_unlock(&app.mutex)
app.connection_thread = thread.create_and_start_with_poly_data(app, handle_connection_to_host, context)
}
}
state_main :: proc(app: ^App) {
- input := app_get_input(app)
+ input := app_get_input(app, only_command = true)
if err, ok := handle_command(app, input).?; ok {
if err != .Not_A_Command {