From 787b41200517c4229faf495a94daf5992514ef67 Mon Sep 17 00:00:00 2001 From: javalsai Date: Fri, 20 Jun 2025 21:31:03 +0200 Subject: [PATCH] feat: utf8 support & partial ui.c refactor --- .clang-tidy | 1 + Makefile | 4 +- README.md | 1 - include/config.h | 4 +- include/efield.h | 10 +- include/ofield.h | 23 ++++ include/ui.h | 13 +++ include/ui_state.h | 28 +++++ include/util.h | 6 + src/auth.c | 1 - src/config.c | 1 - src/efield.c | 75 +++++++------ src/ofield.c | 61 ++++++++++ src/sessions.c | 1 - src/ui.c | 275 ++++++++++----------------------------------- src/ui_state.c | 92 +++++++++++++++ src/util.c | 34 ++++++ 17 files changed, 366 insertions(+), 264 deletions(-) create mode 100644 include/ofield.h create mode 100644 include/ui_state.h create mode 100644 src/ofield.c create mode 100644 src/ui_state.c diff --git a/.clang-tidy b/.clang-tidy index 7d9c8bd..cb9a6a2 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -4,6 +4,7 @@ Checks: > -clang-analyzer-security.insecureAPI.mem*, -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling, bugprone-*, + -bugprone-easily-swappable-parameters, cert-*, modernize-*, performance-*, diff --git a/Makefile b/Makefile index ac5f7ff..bc1f57d 100644 --- a/Makefile +++ b/Makefile @@ -12,10 +12,10 @@ ALLFLAGS=$(CFLAGS) -I$(IDIR) LIBS=-lpam -_DEPS = log.h util.h ui.h config.h desktop.h auth.h efield.h keys.h users.h sessions.h chvt.h macros.h +_DEPS = log.h util.h ui.h ui_state.h config.h desktop.h auth.h ofield.h efield.h keys.h users.h sessions.h chvt.h macros.h DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS)) -_OBJ = main.o log.o util.o ui.o config.o desktop.o auth.o efield.o users.o sessions.o chvt.o +_OBJ = main.o log.o util.o ui.o ui_state.o config.o desktop.o auth.o ofield.o efield.o users.o sessions.o chvt.o OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ)) $(ODIR)/%.o: $(CDIR)/%.c $(DEPS) diff --git a/README.md b/README.md index 5042d5b..dd27f72 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,6 @@ LiDM is like any [X Display Manager](https://en.wikipedia.org/wiki/X_display_man * Save last selection. * Show/hide passwd switch. * Long sessions, strings, usernames, passwords... they will just overflow or fuck your terminal, I know it and I don't know if I'll fix it. -* UTF characters or any multi-byte character, not yet supported properly, everything treats characters as a single byte, some chars might work or not depending on the context, but it's not designed to. # Index diff --git a/include/config.h b/include/config.h index 7a1a65d..917a507 100644 --- a/include/config.h +++ b/include/config.h @@ -109,7 +109,9 @@ BUILD(functions, TABLE_FUNCTIONS); F(char*, e_passwd, STRING, "password", name) \ F(char*, s_wayland, STRING, "wayland", name) \ F(char*, s_xorg, STRING, "xorg", name) \ - F(char*, s_shell, STRING, "shell", name) + F(char*, s_shell, STRING, "shell", name) \ + F(char*, opts_pre, STRING, "< ", name) \ + F(char*, opts_post, STRING, " >", name) BUILD(strings, TABLE_STRINGS); diff --git a/include/efield.h b/include/efield.h index 8295275..b792339 100644 --- a/include/efield.h +++ b/include/efield.h @@ -4,15 +4,15 @@ #include #include +// holds also the max string buffer in itself, not dynamic struct editable_field { - u_char length; u_char pos; char content[255]; }; -struct editable_field field_new(char* content); -void field_trim(struct editable_field* self, u_char pos); -void field_update(struct editable_field* self, char* update); -bool field_seek(struct editable_field* self, char seek); +struct editable_field efield_new(char* content); +void efield_trim(struct editable_field* self, u_char pos); +void efield_update(struct editable_field* self, char* update); +bool efield_seek(struct editable_field* self, char seek); #endif diff --git a/include/ofield.h b/include/ofield.h new file mode 100644 index 0000000..cb8915a --- /dev/null +++ b/include/ofield.h @@ -0,0 +1,23 @@ +#ifndef OFIELD_ +#define OFIELD_ + +#include +#include "efield.h" + +// related vector is external, indexing at 1, 0 means empty and hence points to +// the editable_field +struct opts_field { + size_t opts; + size_t current_opt; + struct editable_field efield; +}; + +struct opts_field ofield_new(size_t opts); +void ofield_toedit(struct opts_field* self, char* init); +void ofield_kbd_type(struct opts_field* self, char* typed, char* empty_default); +bool ofield_opts_seek(struct opts_field* self, char seek); +bool ofield_seek(struct opts_field* self, char seek); + +u_char ofield_display_cursor_col(struct opts_field* self); + +#endif diff --git a/include/ui.h b/include/ui.h index a6962e4..945a561 100644 --- a/include/ui.h +++ b/include/ui.h @@ -2,11 +2,24 @@ #define UIH_ #include "config.h" +#include "ofield.h" #include "util.h" +enum input { SESSION, USER, PASSWD }; +extern const u_char inputs_n; + +// not customizable (for now) +extern const uint boxw; +extern const uint boxh; + void setup(struct config* config); int load(struct Vector* users, struct Vector* sessions); void print_err(const char*); void print_errno(const char*); +void ui_update_field(enum input focused_input); +void ui_update_ffield(); +void ui_update_ofield(struct opts_field* self); +void ui_update_cursor_focus(); + #endif diff --git a/include/ui_state.h b/include/ui_state.h new file mode 100644 index 0000000..aa920ef --- /dev/null +++ b/include/ui_state.h @@ -0,0 +1,28 @@ +#ifndef UISTATEH_ +#define UISTATEH_ + +#include + +#include "ui.h" + +extern enum input focused_input; + +extern struct opts_field of_session; +extern struct opts_field of_user; +extern struct opts_field of_passwd; + +extern struct Vector* gusers; +extern struct Vector* gsessions; + +struct opts_field* NNULLABLE get_opts_field(enum input from); +struct opts_field* NNULLABLE get_opts_ffield(); + +struct user st_user(); +struct session st_session(bool include_defshell); + +void st_ch_focus(char direction); +void st_ch_of_opts(char direction); +void st_ch_ef_col(char direction); +void st_kbd_type(char* text, bool cfg_include_defshell); + +#endif diff --git a/include/util.h b/include/util.h index 1f5de5f..2186afc 100644 --- a/include/util.h +++ b/include/util.h @@ -13,6 +13,12 @@ int find_keyname(enum keys* at, char* name); enum keys find_ansi(char*); void read_press(u_char*, char*); +bool utf8_iscont(char byte); +size_t utf8len(char* str); +size_t utf8len_until(char* str, char* until); +char* utf8back(char* str); +char* utf8seek(char* str); + struct Vector { uint32_t length; uint32_t capacity; diff --git a/src/auth.c b/src/auth.c index 4957db4..40e7ebe 100644 --- a/src/auth.c +++ b/src/auth.c @@ -43,7 +43,6 @@ void clear_screen() { printf("\x1b[H\x1b[J"); } -// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) pam_handle_t* get_pamh(char* user, char* passwd) { pam_handle_t* pamh = NULL; struct pam_conv pamc = {pam_conversation, (void*)passwd}; diff --git a/src/config.c b/src/config.c index 88d0c8c..5dc3245 100644 --- a/src/config.c +++ b/src/config.c @@ -117,7 +117,6 @@ union typ_ptr { struct Vector* vec; uintptr_t ptr; }; -// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) struct parser_error parse_key(enum introspection_type typ, union typ_ptr at, char* key, size_t offset) { char* aux_str = NULL; diff --git a/src/efield.c b/src/efield.c index 858db72..c32752b 100644 --- a/src/efield.c +++ b/src/efield.c @@ -2,78 +2,83 @@ #include "efield.h" #include "ui.h" +#include "util.h" // NOLINTBEGIN(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) -struct editable_field field_new(char* content) { +struct editable_field efield_new(char* content) { struct editable_field efield; if (content != NULL) { - efield.length = efield.pos = strlen(content); - memcpy(efield.content, content, efield.length); + efield.pos = strlen(content); + memcpy(efield.content, content, strlen(content) + 1); } else { - field_trim(&efield, 0); + efield_trim(&efield, 0); } - efield.content[efield.length] = '\0'; + return efield; } -void field_trim(struct editable_field* self, u_char pos) { - self->length = self->pos = pos; - self->content[self->length] = '\0'; +void efield_trim(struct editable_field* self, u_char pos) { + self->pos = pos; + self->content[pos + 1] = '\0'; } // NOLINTNEXTLINE(modernize-macro-to-enum) #define BACKSPACE_CODE 127 -void field_update(struct editable_field* self, char* update) { +void efield_update(struct editable_field* self, char* update) { u_char insert_len = strlen(update); if (insert_len == 0) return; - if (self->pos > self->length) self->pos = self->length; // WTF + if (self->pos > strlen(self->content)) + self->pos = strlen(self->content); // WTF tho + if (insert_len == 1) { // backspace if (*update == BACKSPACE_CODE) { if (self->pos == 0) return; - if (self->pos < self->length) { - memmove(&self->content[self->pos - 1], &self->content[self->pos], - self->length - self->pos); - } - (self->pos)--; - (self->length)--; - self->content[self->length] = '\0'; + char* curr = &self->content[self->pos]; + char* prev = utf8back(curr); + memmove(prev, curr, strlen(self->content) - self->pos + 1); + + self->pos -= curr - prev; return; } + // TODO: Del } // append - if (self->length + self->pos >= 255) { + if (strlen(update) + self->pos + 1 >= 255) { print_err("field too long"); } - if (self->pos < self->length) { - // move with immediate buffer - memmove(&self->content[self->pos + insert_len], &self->content[self->pos], - self->length - self->pos); - } + + // move the after pos, including nullbyte + memmove(&self->content[self->pos + insert_len], &self->content[self->pos], + strlen(self->content) - self->pos + 1); memcpy(&self->content[self->pos], update, insert_len); self->pos += insert_len; - self->length += insert_len; - self->content[self->length] = '\0'; } // returns bool depending if it was able to "use" the seek -bool field_seek(struct editable_field* self, char seek) { - if (self->length == 0) return false; +bool efield_seek(struct editable_field* self, char seek) { + if (strlen(self->content) == 0) return false; - if (seek < 0 && -seek > self->pos) - self->pos = 0; - else if (seek > 0 && 255 - self->pos < seek) - self->pos = 255; - else - self->pos += seek; + u_char count = seek; + if (seek < 0) { + count = -seek; + } - if (self->pos > self->length) self->pos = self->length; + char* orig = &self->content[self->pos]; + char* ptr = orig; + while (ptr > self->content && orig > 0 && *ptr != '\0') { + if (seek < 0) + ptr = utf8back(ptr); + else + ptr = utf8seek(ptr); + count--; + } - return true; + return ptr != orig; } // NOLINTEND(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) diff --git a/src/ofield.c b/src/ofield.c new file mode 100644 index 0000000..4246f46 --- /dev/null +++ b/src/ofield.c @@ -0,0 +1,61 @@ +#include + +#include "efield.h" +#include "ofield.h" +#include "ui.h" +#include "util.h" + +struct opts_field ofield_new(size_t opts) { + if (opts == 0) { + return (struct opts_field){ + .opts = 0, + .current_opt = 0, + .efield = efield_new(""), + }; + } + + return (struct opts_field){ + .opts = opts, + .current_opt = 1, + }; +} + +void ofield_toedit(struct opts_field* self, char* init) { + self->current_opt = 0; + self->efield = efield_new(init); +} + +void ofield_kbd_type(struct opts_field* self, char* typed, + char* empty_default) { + if (self->current_opt != 0) ofield_toedit(self, empty_default); + efield_update(&self->efield, typed); +} + +bool ofield_opts_seek(struct opts_field* self, char seek) { + // no options or (a single option but its selected isntead of on edit) + if (self->opts == 0 || (self->opts == 1 && self->current_opt != 0)) + return false; + + self->current_opt = 1 + ((self->current_opt - 1 + seek) % self->opts); + ui_update_ofield(self); + return true; +} + +bool ofield_seek(struct opts_field* self, char seek) { + if (self->current_opt == 0) { + if (efield_seek(&self->efield, seek)) { + return true; + } + } + + if (self->opts == 0) return false; + ofield_opts_seek(self, seek); + return true; +} + +u_char ofield_display_cursor_col(struct opts_field* self) { + if (self->current_opt == 0) + return utf8len_until(self->efield.content, + &self->efield.content[self->efield.pos]); + return 0; +} diff --git a/src/sessions.c b/src/sessions.c index a188013..da7eff3 100644 --- a/src/sessions.c +++ b/src/sessions.c @@ -28,7 +28,6 @@ struct ctx_typ { char* NULLABLE exec; char* NULLABLE tryexec; }; -// NOLINTNEXTLINE(bugprone-easily-swappable-parameters) struct status cb(void* _ctx, char* NULLABLE table, char* key, char* value) { struct ctx_typ* ctx = (struct ctx_typ*)_ctx; struct status ret; diff --git a/src/ui.c b/src/ui.c index 35a7a33..4e91cda 100644 --- a/src/ui.c +++ b/src/ui.c @@ -19,19 +19,22 @@ #include "auth.h" #include "efield.h" #include "keys.h" +#include "ofield.h" #include "sessions.h" #include "ui.h" +#include "ui_state.h" #include "users.h" #include "util.h" +const u_char inputs_n = 3; +const uint boxw = 50; +const uint boxh = 12; + static void print_box(); static void print_footer(); static void restore_all(); static void signal_handler(int); -const uint boxw = 50; -const uint boxh = 12; - struct uint_point { uint x; uint y; @@ -41,9 +44,6 @@ static void print_session(struct uint_point, struct session, bool); static void print_user(struct uint_point, struct user, bool); static void print_passwd(struct uint_point, uint, bool); -enum input { SESSION, USER, PASSWD }; -static u_char inputs_n = 3; - // ansi resource: https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 static struct termios orig_term; static struct termios term; @@ -57,7 +57,7 @@ void setup(struct config* config) { // 2 padding top and bottom for footer and vertical compensation // 2 padding left & right to not overflow footer width if (window.ws_row < boxh + 4 || window.ws_col < boxw + 4) { - fprintf(stderr, "\x1b[1;31mScreen too small\x1b[0m\n"); + (void)fprintf(stderr, "\x1b[1;31mScreen too small\x1b[0m\n"); exit(1); } @@ -73,243 +73,82 @@ void setup(struct config* config) { g_config->colors.fg); print_footer(); - atexit(restore_all); - signal(SIGINT, signal_handler); + (void)atexit(restore_all); + (void)signal(SIGINT, signal_handler); } static struct uint_point box_start() { - struct uint_point __start; - __start.x = (window.ws_col - boxw) / 2 + 1; - __start.y = (window.ws_row - boxh) / 2 + 1; - return __start; + return (struct uint_point){ + .x = ((window.ws_col - boxw) / 2) + 1, + .y = ((window.ws_row - boxh) / 2) + 1, + }; } static char* fmt_time() { - time_t t = time(NULL); - struct tm tm = *localtime(&t); + time_t tme = time(NULL); + struct tm tm = *localtime(&tme); - size_t bsize = - snprintf(NULL, 0, "%d-%02d-%02d %02d:%02d:%02d", tm.tm_year + 1900, - tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec) + - 1; - char* buf = malloc(bsize); - snprintf(buf, bsize, "%d-%02d-%02d %02d:%02d:%02d", tm.tm_year + 1900, + // TODO: use strftime and a cfg template string + char* buf; + asprintf(&buf, "%d-%02d-%02d %02d:%02d:%02d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); + return buf; } -// TODO: handle buffers longer than the buffer (cut str to the end, change -// cursor pos...) should just overlap for now - -// ugh, this represent a field which might have options -// opts is the amount of other options possible (0 will behave as a passwd) -// aaaand (it's an abstract idea, letme think), also holds the status of a -// custom content, like custom launch command or user or smth -struct opt_field { - uint opts; - uint current_opt; // 0 is edit mode btw - struct editable_field efield; -}; -void print_ofield(struct opt_field* focused_input); - -static struct opt_field ofield_new(uint opts) { - struct opt_field __field; - __field.opts = opts; - __field.current_opt = 1; - if (opts == 0) { - __field.current_opt = 0; - __field.efield = field_new(""); - } - return __field; -} -static void ofield_toedit(struct opt_field* ofield, char* init) { - ofield->current_opt = 0; - ofield->efield = field_new(init); -} -static void ofield_type(struct opt_field* ofield, char* new, char* startstr) { - if (ofield->current_opt != 0) ofield_toedit(ofield, startstr); - field_update(&ofield->efield, new); -} -// true if it changed anything, single opt fields return false -static bool ofield_opt_seek(struct opt_field* ofield, char seek) { - // TODO: think this - if (ofield->opts == 0 || (ofield->opts == 1 && ofield->current_opt != 0)) - return false; - - ofield->current_opt = - 1 + ((ofield->current_opt - 1 + seek + ofield->opts) % ofield->opts); - - print_ofield(ofield); - return true; -} -// true in case it was able to "use" the seek (a empty only editable field -// wouldn't) -static bool ofield_seek(struct opt_field* ofield, char seek) { - if (ofield->current_opt == 0) { - if (field_seek(&ofield->efield, seek)) { - return true; - } - } - - if (ofield->opts == 0) return false; - - ofield_opt_seek(ofield, seek); - - return true; -} - -static u_char ofield_max_displ_pos(struct opt_field* ofield) { - // TODO: set max cursor pos too - // keep in mind that also have to keep in mind scrolling and ughhh, mentally - // blocked, but this is complex - if (ofield->current_opt == 0) - return ofield->efield.pos; - else - return 0; -} - -enum input focused_input = PASSWD; -struct opt_field of_session; -struct opt_field of_user; -struct opt_field of_passwd; - -struct Vector* gusers; -struct Vector* gsessions; - -// not *that* OF tho -struct opt_field* get_of(enum input from) { - if (from == SESSION) return &of_session; - if (from == USER) return &of_user; - if (from == PASSWD) return &of_passwd; - return NULL; -} - -void ffield_cursor_focus() { +void ui_update_cursor_focus() { struct uint_point bstart = box_start(); u_char line = bstart.y; - u_char row = bstart.x + 15; + u_char col = bstart.x + 15; + + struct opts_field* ofield = get_opts_ffield(); + col += ofield_display_cursor_col(ofield); + if (ofield->opts > 1) col += utf8len(g_config->strings.opts_pre); // rows in here quite bodged if (focused_input == SESSION) { line += 5; - row += (of_session.opts > 1) * 2; } else if (focused_input == USER) { line += 7; - row += (of_user.opts > 1) * 2; } else if (focused_input == PASSWD) line += 9; - struct opt_field* ofield = get_of(focused_input); - row += ofield->current_opt == 0 ? ofield_max_displ_pos(ofield) : 0; - - printf("\x1b[%d;%dH", line, row); - fflush(stdout); + (void)printf("\x1b[%d;%dH", line, col); + (void)fflush(stdout); } -struct user get_current_user() { - if (of_user.current_opt != 0) - return *(struct user*)vec_get(gusers, of_user.current_opt - 1); - else { - struct user custom_user; - custom_user.shell = "/usr/bin/bash"; - custom_user.username = custom_user.display_name = of_user.efield.content; - return custom_user; - } -} - -struct session get_current_session() { - if (of_session.current_opt != 0) { - // this is for the default user shell :P, not the greatest - // implementation but I want to get his done - if (g_config->behavior.include_defshell && - of_session.current_opt == gsessions->length + 1) { - struct session shell_session; - shell_session.type = SHELL; - shell_session.exec = shell_session.name = get_current_user().shell; - return shell_session; - } else - return *(struct session*)vec_get(gsessions, of_session.current_opt - 1); - } else { - struct session custom_session; - custom_session.type = SHELL; - custom_session.name = custom_session.exec = of_session.efield.content; - return custom_session; - } -} - -void print_field(enum input focused_input) { +void ui_update_field(enum input focused_input) { struct uint_point origin = box_start(); if (focused_input == PASSWD) { - print_passwd(origin, of_passwd.efield.length, false); + print_passwd(origin, utf8len(of_passwd.efield.content), false); } else if (focused_input == SESSION) { - print_session(origin, get_current_session(), of_session.opts > 1); + print_session(origin, st_session(g_config->behavior.include_defshell), + of_session.opts > 1); } else if (focused_input == USER) { - print_user(origin, get_current_user(), of_user.opts > 1); - print_field(SESSION); + print_user(origin, st_user(), of_user.opts > 1); + ui_update_field(SESSION); } - ffield_cursor_focus(); + ui_update_cursor_focus(); } -void print_ffield() { - print_field(focused_input); +void ui_update_ffield() { + ui_update_field(focused_input); } -void print_ofield(struct opt_field* ofield) { + +void ui_update_ofield(struct opts_field* self) { enum input input; - if (ofield == &of_session) + if (self == &of_session) input = SESSION; - else if (ofield == &of_user) + else if (self == &of_user) input = USER; - else if (ofield == &of_passwd) + else if (self == &of_passwd) input = PASSWD; else return; - print_field(input); -} - -// true = forward, false = backward -void ffield_move(bool direction) { - if (direction) - focused_input = (focused_input + 1 + inputs_n) % inputs_n; - else - focused_input = (focused_input - 1 + inputs_n) % inputs_n; - - ffield_cursor_focus(); -} - -// tf I'm doing -void ffield_change_opt(bool direction) { - struct opt_field* ffield = get_of(focused_input); - if (focused_input == PASSWD) ffield = &of_session; - if (!ofield_opt_seek(ffield, direction ? 1 : -1)) { - if (focused_input == PASSWD || focused_input == SESSION) - ofield_opt_seek(&of_user, direction ? 1 : -1); - else - ofield_opt_seek(&of_session, direction ? 1 : -1); - } -} -void ffield_change_pos(bool direction) { - struct opt_field* ffield = get_of(focused_input); - if (!ofield_seek(ffield, direction ? 1 : -1)) - if (!ofield_opt_seek(&of_session, direction ? 1 : -1)) - ofield_opt_seek(&of_user, direction ? 1 : -1); - - ffield_cursor_focus(); -} - -void ffield_type(char* text) { - struct opt_field* field = get_of(focused_input); - char* start = ""; - if (focused_input == USER && of_user.current_opt != 0) - start = get_current_user().username; - if (focused_input == SESSION && of_session.current_opt != 0 && - get_current_session().type == SHELL) - start = get_current_session().exec; - - ofield_type(field, text, start); - print_ffield(); + ui_update_field(input); } static char* unknown_str = "unknown"; @@ -351,10 +190,10 @@ int load(struct Vector* users, struct Vector* sessions) { g_config->colors.e_date, fmtd_time, g_config->colors.fg); free(fmtd_time); - print_field(SESSION); - print_field(USER); - print_field(PASSWD); - ffield_cursor_focus(); + ui_update_field(SESSION); + ui_update_field(USER); + ui_update_field(PASSWD); + ui_update_cursor_focus(); /// INTERACTIVE u_char len; @@ -379,23 +218,24 @@ int load(struct Vector* users, struct Vector* sessions) { reboot(RB_POWER_OFF); exit(0); } else if (ansi_code == A_UP || ansi_code == A_DOWN) { - ffield_move(ansi_code == A_DOWN); + st_ch_focus(ansi_code == A_DOWN ? 1 : -1); } else if (ansi_code == A_RIGHT || ansi_code == A_LEFT) { if (esc) - ffield_change_opt(ansi_code == A_RIGHT); + st_ch_of_opts(ansi_code == A_RIGHT ? 1 : -1); else - ffield_change_pos(ansi_code == A_RIGHT); + st_ch_ef_col(ansi_code == A_RIGHT ? 1 : -1); } } } else { if (len == 1 && *seq == '\n') { - if (!launch(get_current_user().username, of_passwd.efield.content, - get_current_session(), &restore_all, g_config)) { - print_passwd(box_start(), of_passwd.efield.length, true); - ffield_cursor_focus(); + if (!launch(st_user().username, of_passwd.efield.content, + st_session(g_config->behavior.include_defshell), + &restore_all, g_config)) { + print_passwd(box_start(), utf8len(of_passwd.efield.content), true); + ui_update_cursor_focus(); } } else - ffield_type(seq); + st_kbd_type(seq, g_config->behavior.include_defshell); } if (esc != 0) esc--; @@ -439,8 +279,9 @@ static void print_session(struct uint_point origin, struct session session, } if (multiple) { - printf("\r\x1b[%dC< \x1b[%sm%s\x1b[%sm >", origin.x + 14, session_color, - session.name, g_config->colors.fg); + printf("\r\x1b[%dC%s\x1b[%sm%s\x1b[%sm%s", origin.x + 14, + g_config->strings.opts_pre, session_color, session.name, + g_config->colors.fg, g_config->strings.opts_post); } else { printf("\r\x1b[%dC\x1b[%sm%s\x1b[%sm", origin.x + 14, session_color, session.name, g_config->colors.fg); diff --git a/src/ui_state.c b/src/ui_state.c new file mode 100644 index 0000000..69198b2 --- /dev/null +++ b/src/ui_state.c @@ -0,0 +1,92 @@ +#include "ui_state.h" +#include "ofield.h" +#include "sessions.h" +#include "ui.h" +#include "users.h" + +enum input focused_input = PASSWD; + +struct Vector* gusers; +struct Vector* gsessions; + +struct opts_field of_session; +struct opts_field of_user; +struct opts_field of_passwd; + +struct opts_field* NNULLABLE get_opts_field(enum input from) { + if (from == SESSION) return &of_session; + if (from == USER) return &of_user; + if (from == PASSWD) return &of_passwd; + __builtin_unreachable(); +} + +struct opts_field* NNULLABLE get_opts_ffield() { + return get_opts_field(focused_input); +} + +struct user st_user() { + if (of_user.current_opt != 0) + return *(struct user*)vec_get(gusers, of_user.current_opt - 1); + + struct user custom_user; + custom_user.shell = "/usr/bin/bash"; + custom_user.username = custom_user.display_name = of_user.efield.content; + return custom_user; +} +struct session st_session(bool include_defshell) { + if (of_session.current_opt != 0) { + // this is for the default user shell :P, not the greatest + // implementation but I want to get his done + if (include_defshell && of_session.current_opt == gsessions->length + 1) { + struct session shell_session; + shell_session.type = SHELL; + shell_session.exec = shell_session.name = st_user().shell; + return shell_session; + } + + return *(struct session*)vec_get(gsessions, of_session.current_opt - 1); + } + + struct session custom_session; + custom_session.type = SHELL; + custom_session.name = custom_session.exec = of_session.efield.content; + return custom_session; +} + +void st_ch_focus(char direction) { + focused_input = (focused_input + direction) % inputs_n; + ui_update_cursor_focus(); +} + +void st_ch_of_opts(char direction) { + struct opts_field* ffield = get_opts_ffield(); + if (focused_input == PASSWD) ffield = &of_session; + if (!ofield_opts_seek(ffield, direction)) { + if (focused_input == PASSWD || focused_input == SESSION) + ofield_opts_seek(&of_user, direction); + else + ofield_opts_seek(&of_session, direction); + } +} + +void st_ch_ef_col(char direction) { + struct opts_field* ffield = get_opts_ffield(); + if (!ofield_seek(ffield, direction)) + if (!ofield_opts_seek(&of_session, direction)) + ofield_opts_seek(&of_user, direction); + + ui_update_cursor_focus(); +} + +void st_kbd_type(char* text, bool cfg_include_defshell) { + struct opts_field* field = get_opts_ffield(); + char* start = ""; + if (focused_input == USER && of_user.current_opt != 0) + start = st_user().username; + if (focused_input == SESSION && of_session.current_opt != 0 && + st_session(cfg_include_defshell).type == SHELL) + start = st_session(cfg_include_defshell).exec; + + ofield_kbd_type(field, text, start); + ui_update_ffield(); +} diff --git a/src/util.c b/src/util.c index 79b19a7..97bc986 100644 --- a/src/util.c +++ b/src/util.c @@ -64,6 +64,40 @@ static int selret_magic() { return select(1, &set, NULL, NULL, &timeout); } +// UTF-8 shii+ +#define UTF8_CONT_MSK 0b11000000 +#define UTF8_CONT_VAL 0b10000000 +bool utf8_iscont(char byte) { + return (byte & UTF8_CONT_MSK) == UTF8_CONT_VAL; +} + +size_t utf8len(char* str) { + size_t len = 0; + while (*str != '\0') { + if (!utf8_iscont(*(str++))) len++; + } + + return len; +} + +size_t utf8len_until(char* str, char* until) { + size_t len = 0; + while (str < until) { + if (!utf8_iscont(*(str++))) len++; + } + + return len; +} + +char* utf8back(char* str) { + while(utf8_iscont(*(--str))) {} + return str; +} +char* utf8seek(char* str) { + while(utf8_iscont(*(++str))) {} + return str; +} + // Vector shii const struct Vector VEC_NEW = { .length = 0,