rewrite askUserNameAndPassword() to use tim

This commit is contained in:
2026-01-12 23:00:01 +05:00
parent bfcb2f931f
commit 310e4867d5
4 changed files with 159 additions and 81 deletions

View File

@@ -18,11 +18,11 @@ static const str farewell_art = STR(
"\\(_,J J L l`,)/\n" "\\(_,J J L l`,)/\n"
); );
#define FPS 30
#define is_alias(LITERAL) str_equals(command, STR(LITERAL)) #define is_alias(LITERAL) str_equals(command, STR(LITERAL))
static Result(void) ClientCLI_askUserNameAndPassword(str* username_out, str* password_out); Result(bool) start_screen(Array(char) username_buf, Array(char) password_buf,
str* out_username, str* out_password);
static Result(void) ClientCLI_openUserDB(ClientCLI* self); static Result(void) ClientCLI_openUserDB(ClientCLI* self);
static Result(void) ClientCLI_execCommand(ClientCLI* self, str command, bool* stop); static Result(void) ClientCLI_execCommand(ClientCLI* self, str command, bool* stop);
static Result(SavedServer*) ClientCLI_joinNewServer(ClientCLI* self); static Result(SavedServer*) ClientCLI_joinNewServer(ClientCLI* self);
@@ -48,48 +48,29 @@ void ClientCLI_construct(ClientCLI* self){
} }
Result(void) ClientCLI_run(ClientCLI* self) { Result(void) ClientCLI_run(ClientCLI* self) {
Deferral(FPS); Deferral(32);
TimEditState e; // ask username and password
TimEditState_init(&e, 32, "Greetings!"); Array(char) username_buf = Array_char_alloc(USERNAME_SIZE_MAX + 1);
bool edit_enabled = false; Array(char) password_buf = Array_char_alloc(PASSWORD_SIZE_MAX + 1);
while(tim_run(30)){ Defer(
TimStyle c = { .brd = TimColor16_White, .bg = TimColor16_DarkBlue, .fg = TimColor16_White }; Array_char_destroy(&username_buf);
tim_frame(0, 0, ~0, ~0, c); Array_char_destroy(&password_buf);
);
tim_label(e.s, A, 2, A, A, c); str username = str_null, password = str_null;
try(bool start, i, start_screen(username_buf, password_buf, &username, &password));
if(edit_enabled){ if(!start){
TimKey key = tim_edit(&e, A, 5, tim->scopes[tim->scope].w - 4, c); Return RESULT_VOID;
if(key == TimKey_Escape || key == TimKey_Enter)
edit_enabled = false;
}
else {
if(tim_button("[Enter] Edit text", A, 5, tim->scopes[tim->scope].w - 4, A, c) || tim_is_key_press(TimKey_Enter)){
edit_enabled = true;
tim->focus = &e;
tim->event.type = TimEvent_Void; // consume key event
}
}
if(tim_button("[Q] Quit", A, ~1, 10, A, c) || tim_is_key_press('q'))
exit(0);
} }
tim_reset_terminal();
term_clear(); term_clear();
printf(FMT_str"\n", greeting_art.len, greeting_art.data); printf(FMT_str"\n", greeting_art.len, greeting_art.data);
// create Client // create client
str username = str_null, password = str_null;
try_void(ClientCLI_askUserNameAndPassword(&username, &password));
Defer(
str_destroy(username);
str_destroy(password);
);
Client_free(self->client);
try(self->client, p, Client_create(username, password)); try(self->client, p, Client_create(username, password));
memset(password.data, 0, password.len); // erase password from memory
Array_char_memset(&password_buf, 0);
// init db // init db
try_void(ClientCLI_openUserDB(self)); try_void(ClientCLI_openUserDB(self));
@@ -120,49 +101,6 @@ Result(void) ClientCLI_run(ClientCLI* self) {
Return RESULT_VOID; Return RESULT_VOID;
} }
static Result(void) ClientCLI_askUserNameAndPassword(str* username_out, str* password_out){
Deferral(8);
bool success = false;
// ask username
Array(char) username_buf = Array_char_alloc(128);
Defer(if(!success) Array_char_destroy(&username_buf));
str username = str_null;
while(true) {
printf("username: ");
try_void(term_readLine(username_buf.data, username_buf.len));
username = str_from_cstr(username_buf.data);
str_trim(&username, true);
str name_error_str = validateUsername_str(username);
if(name_error_str.data){
printf("ERROR: "FMT_str"\n",
name_error_str.len, name_error_str.data);
}
else break;
}
// ask password
Array(char) password_buf = Array_char_alloc(128);
Defer(if(!success) Array_char_destroy(&password_buf));
str password = str_null;
while(true) {
printf("password: ");
// TODO: hide password
try_void(term_readLineHidden(password_buf.data, password_buf.len));
password = str_from_cstr(password_buf.data);
str_trim(&password, true);
if(password.len < PASSWORD_SIZE_MIN || password.len > PASSWORD_SIZE_MAX){
printf("ERROR: password length (in bytes) must be >= %i and <= %i\n",
PASSWORD_SIZE_MIN, PASSWORD_SIZE_MAX);
}
else break;
}
*username_out = username;
*password_out = password;
success = true;
Return RESULT_VOID;
}
static Result(void) ClientCLI_openUserDB(ClientCLI* self){ static Result(void) ClientCLI_openUserDB(ClientCLI* self){
Deferral(8); Deferral(8);

View File

@@ -5,6 +5,8 @@
#include "tlibc/collections/List.h" #include "tlibc/collections/List.h"
#include "db/client_db.h" #include "db/client_db.h"
#define FPS 30
typedef struct ClientCLI { typedef struct ClientCLI {
Client* client; Client* client;
tsqlite_connection* db; tsqlite_connection* db;

View File

@@ -0,0 +1,138 @@
#include "ClientCLI.h"
#include "network/tcp-chat-protocol/v1.h"
#include "tim.h"
typedef struct TextInputState {
TimEditState edit;
cstr label;
TimStyle style_default;
TimStyle style_focused;
TimKey result_key;
} TextInputState;
void draw_item_text_input(bool is_selected, TimRect place, void* data){
TextInputState* ctx = data;
TimStyle style = is_selected ? ctx->style_focused : ctx->style_default;
ctx->result_key = tim_edit(&ctx->edit, place.x, place.y, place.w, style);
tim_label(ctx->label, place.x + 3, place.y, A, 1, style);
}
typedef struct StartScreenContext {
TimStyle style_default;
TimStyle style_focused;
TimStyle style_error;
TextInputState input_username;
TextInputState input_password;
bool exit;
bool start;
char* err_msg; // heap only
} StartScreenContext;
void draw_start_screen_buttons(bool is_selected, TimRect place, void* data){
StartScreenContext* ctx = data;
TimStyle style = is_selected ? ctx->style_focused : ctx->style_default;
i32 start_w = place.w / 2;
i32 dist = 1;
i32 quit_x = place.x + start_w + dist;
i32 quit_w = place.w - start_w - dist;
if(tim_button("[Enter] Start", place.x, place.y, start_w, A, style)
|| tim_is_key_press(TimKey_Enter))
{
ctx->start = true;
}
else if(tim_button("[Esc/Q] Quit", quit_x, place.y, quit_w, A, ctx->style_default)
|| tim_is_key_press('q'))
{
ctx->exit = true;
}
}
Result(bool) start_screen(Array(char) username_buf, Array(char) password_buf,
str* out_username, str* out_password)
{
Deferral(16);
bool result = false;
StartScreenContext ctx = {
.style_default = { .brd = TimColor16_Gray, .bg = TimColor16_DarkBlue, .fg = TimColor16_Gray },
.style_focused = { .brd = TimColor16_White, .bg = TimColor16_DarkCyan, .fg = TimColor16_White },
.style_error = { .brd = TimColor16_Gray, .bg = TimColor16_DarkRed, .fg = TimColor16_White },
};
TimEditState_construct(&ctx.input_username.edit, username_buf.data, username_buf.len, NULL);
ctx.input_username.label = "[username]";
ctx.input_username.style_default = ctx.style_default;
ctx.input_username.style_focused = ctx.style_focused;
TimEditState_construct(&ctx.input_password.edit, password_buf.data, password_buf.len, NULL);
ctx.input_password.edit.masked = true;
ctx.input_password.label = "[password]";
ctx.input_password.style_default = ctx.style_default;
ctx.input_password.style_focused = ctx.style_focused;
TimScrollItem items[] = {
// username input
{ .data = &ctx.input_username, .focus_target = &ctx.input_username.edit, .h = 3, .draw = draw_item_text_input },
// password input
{ .data = &ctx.input_password, .focus_target = &ctx.input_password.edit, .h = 3, .draw = draw_item_text_input },
// buttons
{ .data = &ctx, .focus_target = NULL, .h = 3, .draw = draw_start_screen_buttons },
};
TimScrollState scroll_list = { .items = items, .len = ARRAY_LEN(items) };
while(tim_run(FPS)){
tim_fill(tim_cell(" ", ctx.style_default.fg, ctx.style_default.bg), 0, 0, A, A);
tim_scope(A, A, 40, 14) {
tim_scroll(&scroll_list, 1, 1, ~1, ~1, ctx.style_default);
}
if(ctx.err_msg){
i32 below_scroll = tim->scopes[tim->scope].h/2 + 7;
tim_label("ERROR: ", A, below_scroll, A, A, ctx.style_error);
tim_label(ctx.err_msg, A, below_scroll + 1, A, A, ctx.style_error);
}
if(ctx.exit
|| ctx.input_username.result_key == TimKey_Escape
|| ctx.input_password.result_key == TimKey_Escape)
{
result = false;
break;
}
if(ctx.start){
ctx.start = false;
free(ctx.err_msg);
ctx.err_msg = NULL;
// check username
str username = str_from_cstr(username_buf.data);
str_trim(&username, true);
str name_error_str = validateUsername_str(username);
if(name_error_str.data){
ctx.err_msg = name_error_str.data;
continue;
}
// check password
str password = str_from_cstr(password_buf.data);
str_trim(&password, true);
if(password.len < PASSWORD_SIZE_MIN || password.len > PASSWORD_SIZE_MAX){
ctx.err_msg = sprintf_malloc(
"password length (in bytes) must be >= %i and <= %i",
PASSWORD_SIZE_MIN, PASSWORD_SIZE_MAX
);
continue;
}
*out_username = username;
*out_password = password;
result = true;
break;
}
}
Return RESULT_VALUE(i, result);
}