diff --git a/Makefile b/Makefile index f2e90a4..3e9ed7b 100644 --- a/Makefile +++ b/Makefile @@ -8,17 +8,17 @@ CFLAGS=-I$(IDIR) LIBS=-lm -_DEPS = util.h users.h sessions.h +_DEPS = util.h ui.h efield.h keys.h users.h sessions.h DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS)) -_OBJ = main.o util.o users.o sessions.o +_OBJ = main.o util.o ui.o efield.o users.o sessions.o OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ)) $(ODIR)/%.o: $(CDIR)/%.c $(DEPS) @mkdir -p $(ODIR) $(CC) -c -o $@ $< $(CFLAGS) -li: $(OBJ) +lidm: $(OBJ) $(CC) -o $@ $^ $(CFLAGS) $(LIBS) .PHONY: clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..f858786 --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +# lidm +Lidm is a really light display manager made in C, highly customizable and held together by hopes and prayers šŸ™ + +![demo image](assets/lidm.png) +> this is shown as in a terminal emulator, actual linux console doesn't support as much color and decorations + +## Features +* Builds fast af +* Works everywhere you can get gcc to compile +* Fast and possibly efficient +* Fully customizable, from strings to colors (I hope you know ansi escape codes), to action buttons +* Automatically detects xorg and wayland sessions, plus allowing to launch the default user shell (if enabled in config) + +## WIP +* Save last selection +* Config parsing, it's fully customizable, but everything hardcoded for now :) +* Long sessions, strings, usernames, passwords... they will just overflow or f*ck your terminal, I know it and I don't know if I'll fix it. + +## Forget it +* Any kind of arguments +* UTF characters, I'm using `strlen()` and treating characters as per byte basis, UTF-8 chars might work or not + +> [!CAUTION] +> (they should add `> [!DISCLAIMER]` fr) I wrote this readme with the same quality as the code, behing this keyboard there's half a brainrotcell left writing what it remembers of this program, so don't take this to seriously, I'm typing as I think without filter lol, but the program works, or should. Also, about any "TODO" in this readme (or the code), I didn't forget finishing it, I actually don't care + +# Backstory +I went into summer travel to visit family with an old laptop that barely supports x86_64, and ly recently added some avx2 instructions I think (I just get invalid op codes), manually building (any previous commit too) didn't work because of something in the `build.zig` file, so out of boredom I decided to craft up my own simple display manager on the only language this thing can handle, ✨C✨ (I hate this and reserve the right for the rust rewrite, actually solid). I spedrun it, basically did in in 3 days while touching *some* grass (:o), and I'm bad af in C, so this is spaghetti code on another level. I think it doesn't do almost nothing unsafe, I mean, I didn't check allocations and it's capable of reallocating memory until your username uses all memory and crashes the system due to a off by 1 error, but pretty consistent otherwise (probably). + +The name is just ly byt changing "y" with "i", that had a reason but forgot it, (maybe the i in *simple*), so I remembered this sh*tty laptop with a lid, this thing is also a display manager (dm, ly command is also `ly-dm`), so just did lidm due to all that. +![think gif](https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExcTFzaGVmb3VjN3FnOXV6OG9rMG91a2QwM3c0aDV0NWpoZjFzNDEwayZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/d3mlE7uhX8KFgEmY/giphy.gif) + +Btw, this laptop is so bad that I can't even render markdown in reasonable time, I'll just push this and fix render issues live :) + +# Index +(TODO, VSC(odium) does this automatically, I'm on nvim rn šŸ˜Ž) + +# Requirements +* A computer with unix based system. +* That system should have the resources neccessary for this program to make sense (sessions, users...) +* A compiler (optional, you can compile by hand, but I doubt you want to see the code) +* Make (also optional, but does things atomatically, make sure `gcc` and `mkdir -p` work as expected) + +# Compiling +```sh +make # šŸ‘ +``` + +# Configuring +Ugh, config will be a straigh copy of config defaults to `/etc` I think, there's no config yet :P and you need to enable the service, just do what ly does (I'm doing this on dinit, all init systems should be supported). + +# Contributing +Don't do this to yourself, but you can ofc, you can also fork or whatever (make sure to comply with GNU's GPLv3), but I want to do the rust rewrite 😔 + +# Recommendations +Hope you didn't expect actual project recommendations, but these songs are šŸ”„ +* "Sixpence None the Richer - Kiss Me" +* "Avril Lavigne - Complicated" +* "Shawn Mendes - There's Nothing Holdin' Me Back" +* "Rio Romeo - Nothing's New" +* "ElyOtto - SugarCrash!" +* "The Cranberries - Sunday" +* "Goo Goo Dolls - Iris" +* "Em Beihold - Numb Little Bug" +* "MAGIC! - Rude" +* "The Cranberries - Zombie" +* "Natalie Imbruglia - Torn" + +Oh, an actual recommendation, if you don't like a element you can change the fg color of it to be the same as the bg. + +Also (this isn't quite a recommendation lol), the default fg style shoulddisable decorators set up in other elements (cursivem underline... it's just adding 20 to the number btw, so if cursive is 4 (iirc), disabling it is 24). + +Congrats if you've managed to read through all this, wrote all this in exactly 30min. diff --git a/assets/lidm.png b/assets/lidm.png new file mode 100644 index 0000000..23a4bac Binary files /dev/null and b/assets/lidm.png differ diff --git a/compile_commands.json b/compile_commands.json index 0aae781..f7d5d7f 100644 --- a/compile_commands.json +++ b/compile_commands.json @@ -1,6 +1,6 @@ [ { - "directory": "/home/javalsai/coding/li", + "directory": "/home/javalsai/coding/lidm", "arguments": [ "gcc", "-c", @@ -12,7 +12,31 @@ "file": "src/main.c" }, { - "directory": "/home/javalsai/coding/li", + "directory": "/home/javalsai/coding/lidm", + "arguments": [ + "gcc", + "-c", + "-o", + "dist/util.o", + "src/util.c", + "-Iinclude" + ], + "file": "src/util.c" + }, + { + "directory": "/home/javalsai/coding/lidm", + "arguments": [ + "gcc", + "-c", + "-o", + "dist/ui.o", + "src/ui.c", + "-Iinclude" + ], + "file": "src/ui.c" + }, + { + "directory": "/home/javalsai/coding/lidm", "arguments": [ "gcc", "-c", @@ -24,7 +48,7 @@ "file": "src/users.c" }, { - "directory": "/home/javalsai/coding/li", + "directory": "/home/javalsai/coding/lidm", "arguments": [ "gcc", "-c", @@ -36,15 +60,15 @@ "file": "src/sessions.c" }, { - "directory": "/home/javalsai/coding/li", + "directory": "/home/javalsai/coding/lidm", "arguments": [ "gcc", "-c", "-o", - "dist/util.o", - "src/util.c", + "dist/efield.o", + "src/efield.c", "-Iinclude" ], - "file": "src/util.c" + "file": "src/efield.c" } ] diff --git a/include/efield.h b/include/efield.h new file mode 100644 index 0000000..ed03c32 --- /dev/null +++ b/include/efield.h @@ -0,0 +1,18 @@ +#ifndef _EFIELDH_ +#define _EFIELDH_ + +#include +#include + +struct editable_field { + u_char length; + u_char pos; + char content[255]; +}; + +struct editable_field field_new(char*); +void field_trim(struct editable_field*, u_char); +void field_update(struct editable_field*, char*); +bool field_seek(struct editable_field*, char); + +#endif diff --git a/include/keys.h b/include/keys.h new file mode 100644 index 0000000..5933f32 --- /dev/null +++ b/include/keys.h @@ -0,0 +1,104 @@ +#ifndef _KEYSH_ +#define _KEYSH_ + +#include + +enum keys { + ESC, + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + A_UP, + A_DOWN, + A_RIGHT, + A_LEFT, + N_CENTER, + N_UP, + N_DOWN, + N_RIGHT, + N_LEFT, + INS, + SUPR, + HOME, + END, + PAGE_UP, + PAGE_DOWN, +}; + +static const char * const key_names[] = { + [ESC] = "ESC", + [F1] = "F1", + [F2] = "F2", + [F3] = "F3", + [F4] = "F4", + [F5] = "F5", + [F6] = "F6", + [F7] = "F7", + [F8] = "F8", + [F9] = "F9", + [F10] = "F10", + [F11] = "F11", + [F12] = "F12", + [A_UP] = "A_UP", + [A_DOWN] = "A_DOWN", + [A_RIGHT] = "A_RIGHT", + [N_CENTER] = "N_CENTER", + [A_LEFT] = "A_LEFT", + [N_UP] = "N_UP", + [N_DOWN] = "N_DOWN", + [N_RIGHT] = "N_RIGHT", + [N_LEFT] = "N_LEFT", + [INS] = "INS", + [SUPR] = "SUPR", + [HOME] = "HOME", + [END] = "END", + [PAGE_UP] = "PAGE_UP", + [PAGE_DOWN] = "PAGE_DOWN", +}; + +struct key_mapping { + enum keys key; + const char *sequences[3]; +}; + +static const struct key_mapping key_mappings[] = { + { ESC, { "\x1b", NULL }}, + { F1, { "\x1bOP", "\x1b[[A", NULL }}, + { F2, { "\x1bOQ", "\x1b[[B", NULL }}, + { F3, { "\x1bOR", "\x1b[[C", NULL }}, + { F4, { "\x1bOS", "\x1b[[D", NULL }}, + { F5, { "\x1b[15~", "\x1b[[E", NULL }}, + { F6, { "\x1b[17~", NULL }}, + { F7, { "\x1b[18~", NULL }}, + { F8, { "\x1b[19~", NULL }}, + { F9, { "\x1b[20~", NULL }}, + { F10, { "\x1b[21~", NULL }}, + { F11, { "\x1b[23~", NULL }}, + { F12, { "\x1b[24~", NULL }}, + { A_UP, { "\x1b[A", NULL }}, + { A_DOWN, { "\x1b[B", NULL }}, + { A_RIGHT, { "\x1b[C", NULL }}, + { A_LEFT, { "\x1b[D", NULL }}, + { N_CENTER, { "\x1b[E", NULL }}, + { N_UP, { "\x1bOA", NULL }}, + { N_DOWN, { "\x1bOB", NULL }}, + { N_RIGHT, { "\x1bOC", NULL }}, + { N_LEFT, { "\x1bOD", NULL }}, + { INS, { "\x1b[2~", NULL }}, + { SUPR, { "\x1b[3~", NULL }}, + { HOME, { "\x1b[H", NULL }}, + { END, { "\x1b[F", NULL }}, + { PAGE_UP, { "\x1b[5~", NULL }}, + { PAGE_DOWN, { "\x1b[6~", NULL }}, +}; + +#endif diff --git a/include/sessions.h b/include/sessions.h index c4f1f6e..f6b689e 100644 --- a/include/sessions.h +++ b/include/sessions.h @@ -1,9 +1,18 @@ +#ifndef _SESSIONSH_ +#define _SESSIONSH_ + #include +enum session_type { + XORG, + WAYLAND, + SHELL, +}; + struct session { - const char *type; char *name; char *path; + enum session_type type; }; struct sessions_list { @@ -12,3 +21,5 @@ struct sessions_list { }; struct sessions_list *get_avaliable_sessions(); + +#endif diff --git a/include/ui.h b/include/ui.h new file mode 100644 index 0000000..9d6d576 --- /dev/null +++ b/include/ui.h @@ -0,0 +1,80 @@ +#ifndef _UIH_ +#define _UIH_ + +#include + +#include +#include +#include + +// should be ansi escape codes under \x1b[...m +// if not prepared accordingly, it might break +struct theme_colors { + char *bg; + char *fg; + char *err; + char *s_wl; + char *s_xorg; + char *s_shell; + char *f_other; + char *e_hostname; + char *e_date; + char *e_box; + char *e_header; + char *e_user; + char *e_passwd; + char *e_badpasswd; + char *e_key; +}; + +// even if they're multiple bytes long +// they should only take up 1 char size on display +struct theme_chars { + char *hb; + char *vb; + + char *ctl; + char *ctr; + char *cbl; + char *cbr; +}; + +struct theme { + struct theme_colors colors; + struct theme_chars chars; +}; + +struct functions { + enum keys poweroff; + enum keys reboot; + enum keys refresh; +}; + +struct strings { + char* f_poweroff; + char* f_reboot; + char* f_refresh; + char* e_user; + char* e_passwd; + char* s_xorg; + char* s_wayland; + char* s_shell; +}; + +struct behavior { + bool include_defshell; +}; + +struct config { + struct theme theme; + struct functions functions; + struct strings strings; + struct behavior behavior; +}; + +void setup(struct config); +int load(struct users_list*, struct sessions_list*); +void print_err(const char*); +void print_errno(const char*); + +#endif diff --git a/include/users.h b/include/users.h index 666a25e..d00354d 100644 --- a/include/users.h +++ b/include/users.h @@ -1,6 +1,10 @@ +#ifndef _USERSH_ +#define _USERSH_ + #include struct user { + char *shell; char *username; char *display_name; }; @@ -11,3 +15,5 @@ struct users_list { }; struct users_list *get_human_users(); + +#endif diff --git a/include/util.h b/include/util.h index 8e17069..fbfb026 100644 --- a/include/util.h +++ b/include/util.h @@ -1 +1,12 @@ +#ifndef _UTILH_ +#define _UTILH_ + +#include +#include +#include + +enum keys find_ansi(char*); +void read_press(u_char*, char*); void strcln(char **dest, const char *source); + +#endif diff --git a/src/efield.c b/src/efield.c new file mode 100644 index 0000000..fd9c891 --- /dev/null +++ b/src/efield.c @@ -0,0 +1,64 @@ +#include + +#include +#include + +struct editable_field field_new(char* content) { + struct editable_field __efield; + if(content != NULL) { + __efield.length = __efield.pos = strlen(content); + memcpy(__efield.content, content, __efield.length); + } else { + field_trim(&__efield, 0); + } + return __efield; +} + +void field_trim(struct editable_field *field, u_char pos) { + field->length = field->pos = pos; +} + +void field_update(struct editable_field *field, char *update) { + u_char insert_len = strlen(update); + if (insert_len == 0) + return; + + if (field->pos > field->length) + field->pos = field->length; // WTF + if (insert_len == 1) { + // backspace + if (*update == 127) { + if (field->pos < field->length) { + memcpy(&field->content[field->pos - 1], &field->content[field->pos], + field->length - field->pos); + } + (field->pos)--; + (field->length)--; + return; + } + } + + // append + if (field->length + field->pos >= 255) { + print_err("field too long"); + } + if (field->pos < field->length) { + // move with immediate buffer + memmove(&field->content[field->pos + insert_len], &field->content[field->pos], + field->length - field->pos); + } + memcpy(&field->content[field->pos], update, insert_len); + + field->pos += insert_len; + field->length += insert_len; +} + +// returns bool depending if it was able to "use" the seek +bool field_seek(struct editable_field *field, char seek) { + if(field->length == 0) return false; + + if(seek < 0 && -seek > field->pos) field->pos = 0; + else if(seek > 0 && 255 - field->pos < seek) field->pos = 255; + else field->pos += seek; + return true; +} diff --git a/src/main.c b/src/main.c index 721ce6c..05bc7bf 100644 --- a/src/main.c +++ b/src/main.c @@ -1,31 +1,77 @@ #include -#include +#include #include #include #include #include +#include #include -int main() { +static const struct config default_config() { + struct theme_colors __colors; + __colors.bg = "48;2;38;28;28"; + __colors.fg = "22;24;38;2;245;245;245"; + __colors.err = "1;31"; + __colors.s_wl = "38;2;255;174;66"; + __colors.s_xorg = "38;2;37;175;255"; + __colors.s_shell = "38;2;34;140;34"; + __colors.f_other = "38;2;255;64;64"; + __colors.e_hostname = "38;2;255;64;64"; + __colors.e_date = "38;2;144;144;144"; + __colors.e_box = "38;2;122;122;122"; + __colors.e_header = "4;38;2;0;255;0"; + __colors.e_user = "36"; + __colors.e_passwd = "4;38;2;245;245;205"; + __colors.e_badpasswd = "3;4;31"; + __colors.e_key = "38;2;255;174;66"; + + struct theme_chars __chars; + __chars.hb = "─"; + __chars.vb = "│"; + __chars.ctl = "ā”Œ"; + __chars.ctr = "┐"; + __chars.cbl = "ā””"; + __chars.cbr = "ā”˜"; + + struct theme __theme; + __theme.colors = __colors; + __theme.chars = __chars; + + + struct functions __functions; + __functions.poweroff = F1; + __functions.reboot = F2; + __functions.refresh = F5; + + struct strings __strings; + __strings.f_poweroff = "powewoff"; + __strings.f_reboot = "rewoot"; + __strings.f_refresh = "rewresh"; + __strings.e_user = "wuser"; + __strings.e_passwd = "passwd"; + __strings.s_xorg = "xworg"; + __strings.s_wayland = "waywand"; + __strings.s_shell = "swell"; + + struct behavior __behavior; + __behavior.include_defshell = true; + + struct config __config; + __config.theme = __theme; + __config.functions = __functions; + __config.strings = __strings; + __config.behavior = __behavior; + + return __config; +} + +int main(int argc, char* argv[]) { + setup(default_config()); + struct users_list *users = get_human_users(); struct sessions_list *sessions = get_avaliable_sessions(); - printf("users(%hu) sessions(%hu)\n", users->length, sessions->length); - - for (uint i = 0; i < users->length; i++) - printf("u[%d]: %s %s\n", i, users->users[i].username, - users->users[i].display_name); - - for (uint i = 0; i < sessions->length; i++) - printf("s[%d]: %s %s %s\n", i, sessions->sessions[i].type, - sessions->sessions[i].name, sessions->sessions[i].path); - - struct winsize w; - ioctl(STDOUT_FILENO, TIOCGWINSZ, &w); - - printf("lines %d\n", w.ws_row); - printf("columns %d\n", w.ws_col); - - return 0; + int ret = load(users, sessions); + if(ret == 0) execl(argv[0], argv[0], NULL); } diff --git a/src/sessions.c b/src/sessions.c index 0aeb8c0..64080b6 100644 --- a/src/sessions.c +++ b/src/sessions.c @@ -9,13 +9,17 @@ #include #include -static const char *sources[][2] = { - { "xorg", "/usr/share/xsessions" }, - { "wl", "/usr/share/wayland-sessions" }, +struct source_dir { + enum session_type type; + char* dir; +}; +static const struct source_dir sources[] = { + { XORG, "/usr/share/xsessions" }, + { WAYLAND, "/usr/share/wayland-sessions" }, }; static const size_t sources_size = sizeof(sources) / sizeof(sources[0]); -static struct session __new_session(const char *type, char *name, const char *path) { +static struct session __new_session(enum session_type type, char *name, const char *path) { struct session __session; __session.type = type; strcln(&__session.name, name); @@ -33,7 +37,7 @@ static u_int16_t used_size = 0; static struct session *sessions = NULL; static struct sessions_list *__sessions_list = NULL; -static const char* session_type; +static enum session_type session_type; static int fn(const char *fpath, const struct stat *sb, int typeflag) { // practically impossible to reach this // but will prevent break @@ -82,7 +86,7 @@ static int fn(const char *fpath, const struct stat *sb, int typeflag) { } static struct sessions_list __list; -// This code is designed to be run purely sinlge threaded +// This code is designed to be run purely single threaded struct sessions_list *get_avaliable_sessions() { if (sessions != NULL) return __sessions_list; @@ -90,8 +94,8 @@ struct sessions_list *get_avaliable_sessions() { sessions = malloc(alloc_size * unit_size); for (uint i = 0; i < sources_size; i++) { - session_type = sources[i][0]; - ftw(sources[i][1], &fn, 1); + session_type = sources[i].type; + ftw(sources[i].dir, &fn, 1); } sessions = realloc(sessions, used_size * unit_size); diff --git a/src/ui.c b/src/ui.c new file mode 100644 index 0000000..14c7e3f --- /dev/null +++ b/src/ui.c @@ -0,0 +1,425 @@ +// i'm sorry + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +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; +}; + +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); + +// ansi resource: https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 +static struct termios orig_term; +static struct termios term; +static struct winsize window; + +static struct theme theme; +static struct functions functions; +static struct strings strings; +static struct behavior behavior; +void setup(struct config __config) { + ioctl(STDOUT_FILENO, TIOCGWINSZ, &window); + + // 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"); + exit(1); + } + + theme = __config.theme; + functions = __config.functions; + strings = __config.strings; + + tcgetattr(STDOUT_FILENO, &orig_term); + term = orig_term; // save term + // "stty" attrs + term.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDOUT_FILENO, TCSANOW, &term); + + // save cursor pos, save screen, hide cursor, set color and reset screen + // (applying color to all screen) + printf("\x1b[s\x1b[?47h\x1b[%s;%sm\x1b[2J", theme.colors.bg, + theme.colors.fg); + + print_footer(); + atexit(restore_all); + 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; +} + +static char *fmt_time() { + time_t t = time(NULL); + struct tm tm = *localtime(&t); + + 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, + 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 ammount 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; +}; +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->efield = field_new(init); +} +// 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->current_opt = (ofield->current_opt + seek + ofield->opts + 1) % + (ofield->opts + 1); // not sure about this one + 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 { SESSION, USER, PASSWD }; +static u_char inputs_n = 3; +enum input focused_input = PASSWD; +struct opt_field of_session; +struct opt_field of_user; +struct opt_field of_passwd; + +void update_cursor_focus() { + struct uint_point bstart = box_start(); + u_char line = bstart.y; + u_char row = bstart.x + 15; + if (focused_input == SESSION) { + line += 5; + row += ofield_max_displ_pos(&of_session); + } else if (focused_input == USER) { + line += 7; + row += ofield_max_displ_pos(&of_user); + } else if (focused_input == PASSWD) { + line += 9; + row += ofield_max_displ_pos(&of_passwd); + } + printf("\x1b[%d;%dH", line, row); + fflush(stdout); +} + +// true = forward, false = backward +void change_field_focus(bool direction) { + if (direction) + focused_input = (focused_input + 1 + inputs_n) % inputs_n; + else + focused_input = (focused_input - 1 + inputs_n) % inputs_n; +} + +int load(struct users_list *users, struct sessions_list *sessions) { + /// SETUP + + // hostnames larger won't render properly + char *hostname = malloc(16); + if (gethostname(hostname, 16) != 0) { + free(hostname); + hostname = "unknown"; + } else { + hostname = realloc(hostname, strlen(hostname) + 1); + } + + of_session = ofield_new(sessions->length + behavior.include_defshell); + of_user = ofield_new(users->length); + of_passwd = ofield_new(0); + + /// PRINTING + const struct uint_point boxstart = box_start(); + + // printf box + print_box(); + + // put hostname + printf("\x1b[%d;%dH\x1b[%sm%s\x1b[%sm", boxstart.y + 2, + boxstart.x + 12 - (uint)strlen(hostname), theme.colors.e_hostname, + hostname, theme.colors.fg); + + // put date + char *fmtd_time = fmt_time(); + printf("\x1b[%d;%dH\x1b[%sm%s\x1b[%sm", boxstart.y + 2, + boxstart.x + boxw - 3 - (uint)strlen(fmtd_time), theme.colors.e_date, + fmtd_time, theme.colors.fg); + + print_session(boxstart, sessions->sessions[0], false); + print_user(boxstart, users->users[0], false); + print_passwd(boxstart, 5, false); + fflush(stdout); + update_cursor_focus(); + + /// INTERACTIVE + u_char len; + char seq[256]; + + while (true) { + read_press(&len, seq); + if (*seq == '\x1b') { + enum keys ansi_code = find_ansi(seq); + if (ansi_code != -1) { + if (ansi_code == functions.refresh) { + restore_all(); + return 0; + } else if (ansi_code == functions.reboot) { + system("reboot"); + exit(0); + } else if (ansi_code == functions.poweroff) { + system("poweroff"); + exit(0); + } else if (ansi_code == A_UP || ansi_code == A_DOWN) { + change_field_focus(ansi_code == A_UP); + } + } + } else {} + /*printf("norm(%d): %d\n", len, *seq);*/ + } +} + +static char *line_cleaner = NULL; +static void clean_line(struct uint_point origin, uint line) { + if (line_cleaner == NULL) { + line_cleaner = malloc((boxw - 2) * sizeof(char)); + memset(line_cleaner, 32, boxw - 2); + } + printf("\x1b[%d;%dH", origin.y + line, origin.x + 1); + fflush(stdout); + write(STDOUT_FILENO, line_cleaner, boxw - 2); +} + +// TODO: session_len > 32 +static void print_session(struct uint_point origin, struct session session, + bool multiple) { + clean_line(origin, 5); + const char *session_type; + if (session.type == XORG) { + session_type = strings.s_xorg; + } else if (session.type == WAYLAND) { + session_type = strings.s_wayland; + } else if (session.type == SHELL) { + session_type = strings.s_shell; + } + printf("\r\x1b[%luC\x1b[%sm%s\x1b[%sm", origin.x + 11 - strlen(session_type), + theme.colors.e_header, session_type, theme.colors.fg); + + char *session_color; + if (session.type == XORG) { + session_color = theme.colors.s_xorg; + } else if (session.type == WAYLAND) { + session_color = theme.colors.s_wl; + } else if (session.type == SHELL) { + session_color = theme.colors.s_shell; + } + + if (multiple) { + printf("\r\x1b[%dC< \x1b[%sm%s\x1b[%sm >", origin.x + 14, session_color, + session.name, theme.colors.fg); + } else { + printf("\r\x1b[%dC\x1b[%sm%s\x1b[%sm", origin.x + 14, session_color, + session.name, theme.colors.fg); + } +} + +// TODO: user_len > 32 +static void print_user(struct uint_point origin, struct user user, + bool multiple) { + clean_line(origin, 7); + printf("\r\x1b[%luC\x1b[%sm%s\x1b[%sm", + origin.x + 11 - strlen(strings.e_user), theme.colors.e_header, + strings.e_user, theme.colors.fg); + + char *user_color = theme.colors.e_user; + + if (multiple) { + printf("\r\x1b[%dC< \x1b[%sm%s\x1b[%sm >", origin.x + 14, user_color, + user.display_name, theme.colors.fg); + } else { + printf("\r\x1b[%dC\x1b[%sm%s\x1b[%sm", origin.x + 14, user_color, + user.display_name, theme.colors.fg); + } +} + +static char *passwd_prompt[32]; +// TODO: passwd_len > 32 +static void print_passwd(struct uint_point origin, uint length, bool err) { + clean_line(origin, 9); + printf("\r\x1b[%luC\x1b[%sm%s\x1b[%sm", + origin.x + 11 - strlen(strings.e_passwd), theme.colors.e_header, + strings.e_passwd, theme.colors.fg); + + char *pass_color; + if (err) + pass_color = theme.colors.e_badpasswd; + else + pass_color = theme.colors.e_passwd; + + memset(passwd_prompt, 32, 32); + memset(passwd_prompt, '*', length); + + printf("\r\x1b[%dC\x1b[%sm\x1b[4m", origin.x + 14, pass_color); + fflush(stdout); + write(STDOUT_FILENO, passwd_prompt, 32); + printf("\x1b[%sm", theme.colors.fg); +} + +// ik this code is... *quirky* +// w just accounts for filler +// if filler == NULL, it will just move cursor +static void print_row(uint w, uint n, char *edge1, char *edge2, char **filler) { + char *row; + size_t row_size; + + uint size; + if (filler == NULL) { + row_size = snprintf(NULL, 0, "%s\x1b[%dC%s\x1b[%dD\x1b[1B", edge1, w, edge2, + w + 2) + + 1; + row = malloc(row_size); + snprintf(row, row_size, "%s\x1b[%dC%s\x1b[%dD\x1b[1B", edge1, w, edge2, + w + 2); + } else { + size_t fillersize = strlen(*filler) * w; + size_t nbytes1 = snprintf(NULL, 0, "%s", edge1) + 1; + size_t nbytes2 = snprintf(NULL, 0, "%s\x1b[%dD\x1b[1B", edge2, w + 2) + 1; + row_size = nbytes1 + fillersize + nbytes2; + row = malloc(row_size); + snprintf(row, nbytes1, "%s", edge1); + for (uint i = 0; i < fillersize; i += strlen(*filler)) { + strcpy(&row[nbytes1 + i], *filler); + } + snprintf(&row[nbytes1 + fillersize], nbytes2, "%s\x1b[%dD\x1b[1B", edge2, + w + 2); + } + + for (uint i = 0; i < n; i++) { + write(STDOUT_FILENO, row, row_size); + } + free(row); +} + +static void print_box() { + // TODO: check min sizes + const struct uint_point bstart = box_start(); + + printf("\x1b[%d;%dH\x1b[%sm", bstart.y, bstart.x, theme.colors.e_box); + fflush(stdout); + print_row(boxw - 2, 1, theme.chars.ctl, theme.chars.ctr, &theme.chars.hb); + print_row(boxw - 2, boxh - 2, theme.chars.vb, theme.chars.vb, NULL); + print_row(boxw - 2, 1, theme.chars.cbl, theme.chars.cbr, &theme.chars.hb); + printf("\x1b[%sm", theme.colors.fg); + fflush(stdout); +} + +static void print_footer() { + size_t bsize = snprintf(NULL, 0, "%s %s %s %s %s %s", strings.f_poweroff, + key_names[functions.poweroff], strings.f_reboot, + key_names[functions.reboot], strings.f_refresh, + key_names[functions.refresh]); + + uint row = window.ws_row - 1; + uint col = window.ws_col - 2 - bsize; + printf("\x1b[%3$d;%4$dH%8$s \x1b[%1$sm%5$s\x1b[%2$sm %9$s " + "\x1b[%1$sm%6$s\x1b[%2$sm %10$s \x1b[%1$sm%7$s\x1b[%2$sm", + theme.colors.e_key, theme.colors.fg, row, col, + key_names[functions.poweroff], key_names[functions.reboot], + key_names[functions.refresh], strings.f_poweroff, strings.f_reboot, + strings.f_refresh); + fflush(stdout); +} + +void print_err(const char *msg) { + struct uint_point origin = box_start(); + fprintf(stderr, "\x1b[%d;%dH%s(%d): %s", origin.y - 1, origin.x, msg, errno, + strerror(errno)); +} + +void print_errno(const char *descr) { + struct uint_point origin = box_start(); + if (descr == NULL) + fprintf(stderr, "\x1b[%d;%dH\x1b[%smunknown error(%d): %s", origin.y - 1, + origin.x, theme.colors.err, errno, strerror(errno)); + else { + fprintf(stderr, "\x1b[%d;%dH\x1b[%sm%s(%d): %s", origin.y - 1, origin.x, + descr, theme.colors.err, errno, strerror(errno)); + } +} + +void restore_all() { + // restore cursor pos, restore screen and show cursor + printf("\x1b[u\x1b[?47l\x1b[?25h"); + fflush(stdout); + tcsetattr(STDOUT_FILENO, TCSANOW, &orig_term); +} + +void signal_handler(int code) { + restore_all(); + exit(code); +} diff --git a/src/users.c b/src/users.c index d073a2a..810fd00 100644 --- a/src/users.c +++ b/src/users.c @@ -10,6 +10,7 @@ static struct user __new_user(struct passwd *p) { struct user __user; + strcln(&__user.shell, p->pw_shell); strcln(&__user.username, p->pw_name); if (p->pw_gecos[0] == '\0') __user.display_name = __user.username; @@ -29,7 +30,7 @@ static struct user *users = NULL; static struct users_list *__users_list = NULL; struct users_list __list; -// This code is designed to be run purely sinlge threaded +// This code is designed to be run purely single threaded struct users_list *get_human_users() { if (users != NULL) return __users_list; diff --git a/src/util.c b/src/util.c index b9231b7..29132f2 100644 --- a/src/util.c +++ b/src/util.c @@ -1,9 +1,58 @@ +#include #include #include +#include +#include +#include +#include #include +static int selret_magic(); + void strcln(char **dest, const char *source) { *dest = malloc(strlen(source) + sizeof(char)); strcpy(*dest, source); } + +enum keys find_ansi(char *seq) { + for (size_t i = 0; i < sizeof(key_mappings) / sizeof(key_mappings[0]); i++) { + struct key_mapping mapping = key_mappings[i]; + for (size_t j = 0; mapping.sequences[j] != NULL; j++) { + if (strcmp(mapping.sequences[j], seq) == 0) { + return (enum keys)i; + } + } + } + return -1; +} + +// https://stackoverflow.com/a/48040042 +void read_press(u_char *length, char *out) { + *length = 0; + + while (true) { + if (read(STDIN_FILENO, &out[(*length)++], 1) != 1) { + print_errno("read error"); + sleep(3); + exit(1); + } + int selret = selret_magic(); + if (selret == -1) { + print_errno("selret error"); + } else if (selret != 1) { + out[*length] = '\0'; + return; + } + } +} + +static int selret_magic() { + fd_set set; + struct timeval timeout; + FD_ZERO(&set); + FD_SET(STDIN_FILENO, &set); + timeout.tv_sec = 0; + timeout.tv_usec = 0; + return select(1, &set, NULL, NULL, &timeout); +}