From feeba5c41b727733b68ff71ba847005047a7cda4 Mon Sep 17 00:00:00 2001 From: dariuskl Date: Sun, 16 Nov 2025 17:29:59 +0100 Subject: [PATCH 1/3] feat: don't trunc long hostnames if there is space (#87) Instead of restricting the length of the hostname field to the size of the column, the hostname is allowed to utilize all space that is not taken up by the time string. Co-authored-by: Darius Kellermann --- src/ui.c | 45 ++++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/ui.c b/src/ui.c index 4bc5512..8f8c3ee 100644 --- a/src/ui.c +++ b/src/ui.c @@ -109,8 +109,8 @@ static char* fmt_time(const char* fmt) { } } -char* trunc_gethostname(const size_t MAXLEN, const char* const ELLIPSIS) { - if (utf8len(ELLIPSIS) > MAXLEN) return NULL; +char* trunc_gethostname(size_t maxlen, const char* const ELLIPSIS) { + if (utf8len(ELLIPSIS) > maxlen) return NULL; size_t alloc_size = HOST_NAME_MAX + strlen(ELLIPSIS) + 1; char* buf = malloc(alloc_size); if (!buf) return NULL; @@ -120,8 +120,8 @@ char* trunc_gethostname(const size_t MAXLEN, const char* const ELLIPSIS) { return NULL; } - if (utf8len(buf) > MAXLEN) { - size_t end = utf8trunc(buf, MAXLEN - utf8len(ELLIPSIS)); + if (utf8len(buf) > maxlen) { + size_t end = utf8trunc(buf, maxlen - utf8len(ELLIPSIS)); strcpy(&buf[end], ELLIPSIS); } return buf; @@ -341,25 +341,40 @@ u_char get_render_pos_offset(struct opts_field* self, u_char maxlen) { return pos - ofield_display_cursor_col(self, maxlen); } -#define HOSTNAME_SIZE (VALUES_COL - VALUES_SEPR - BOX_HMARGIN - 1) void print_head() { - // hostname doesn't just change on runtime - static char* hostname = NULL; - if (!hostname) - hostname = trunc_gethostname(HOSTNAME_SIZE, g_config->strings.ellipsis); - if (!hostname) hostname = "unknown"; + char* fmtd_time = fmt_time(g_config->behavior.timefmt); + size_t len_tm = utf8len(fmtd_time); + + // calculate the space available for the host name + ssize_t hostname_size = BOX_WIDTH - (BOX_HMARGIN * 2) - len_tm - VALUES_SEPR; + if (hostname_size < 0) hostname_size = 0; + + // hostname doesn't just change on runtime, + // but the length of the time string might + static char* NULLABLE hostname = NULL; + static size_t hostname_calcd_size; + + // save the truncated hostname and the length it truncated to, + // if said length changes recalculate this (and free previous str) + if (!hostname || hostname_calcd_size != hostname_size) { + if (hostname) free(hostname); + hostname = trunc_gethostname(hostname_size, g_config->strings.ellipsis); + hostname_calcd_size = hostname_size; + } clean_line(box_start, HEAD_ROW); + // put hostname - printf("\x1b[%dG\x1b[%sm%s\x1b[%sm", - box_start.x + VALUES_COL - VALUES_SEPR - (uint)utf8len(hostname), - g_config->colors.e_hostname, hostname, g_config->colors.fg); + if (hostname_size) + printf("\x1b[%dG\x1b[%sm%s\x1b[%sm", box_start.x + 1 + BOX_HMARGIN, + g_config->colors.e_hostname, hostname ? hostname : "unknown", + g_config->colors.fg); // put date - char* fmtd_time = fmt_time(g_config->behavior.timefmt); printf("\x1b[%dG\x1b[%sm%s\x1b[%sm", - box_start.x + BOX_WIDTH - 1 - BOX_HMARGIN - (uint)utf8len(fmtd_time), + box_start.x + BOX_WIDTH - 1 - BOX_HMARGIN - (uint)len_tm, g_config->colors.e_date, fmtd_time, g_config->colors.fg); + free(fmtd_time); } From a7a1f42f0a248a461ba280d9d3441c2630b3afa1 Mon Sep 17 00:00:00 2001 From: creations Date: Sat, 13 Dec 2025 15:37:58 -0500 Subject: [PATCH 2/3] feat: add support for fido yubikeys (#89) * add support for fido keybind * add to themes * fix clang format * Update ui.c * docs: add misc stuff about the yubikey --------- Co-authored-by: javalsai --- README.md | 9 +++--- assets/man/lidm-config.5 | 4 +-- docs/yubikey.md | 30 +++++++++++++++++++ include/config.h | 2 ++ include/keys.h | 2 ++ include/ui.h | 2 ++ src/auth.c | 41 +++++++++++++++++++++---- src/ui.c | 65 +++++++++++++++++++++++++++++++++------- themes/default.ini | 2 ++ 9 files changed, 135 insertions(+), 22 deletions(-) create mode 100644 docs/yubikey.md diff --git a/README.md b/README.md index b941ec3..fc700d3 100644 --- a/README.md +++ b/README.md @@ -27,10 +27,11 @@ kmscon -l --vt /dev/tty7 --font-name "Cascadia Code" -- /usr/bin/lidm ## Features -- Simple as C, you only need a C compiler and standard unix libraries to build this. -- Fully customizable, from strings, including action keys, to colors (I hope you know ansi escape codes) -- Automatically detects xorg and wayland sessions, plus allowing to launch the default user shell (if enabled in config) -- Starts with many init systems (systemd, dinit, runit, openrc and s6). +- Simple as C, meant to depend only on standard libc and basic unix system headers. +- Fully customizable: ALL strings, colors (with its ANSI keys) and most behavior. +- Xorg[\*](https://github.com/javalsai/lidm/issues/79) and wayland sessions, while supporting the default user shell (if enabled in config) +- Init agnostinc (systemd, dinit, runit, openrc and s6). +- Supports [fido yubikeys](./docs/yubikey.md) (via pam_u2f). # Table of Contents diff --git a/assets/man/lidm-config.5 b/assets/man/lidm-config.5 index f5d6e16..8e4668e 100644 --- a/assets/man/lidm-config.5 +++ b/assets/man/lidm-config.5 @@ -69,13 +69,13 @@ Characters for the corners of the box (ctl = corner top left, cbr = corner botto .SS functions All these are of type \fBKEY\fP. .TP -\fBpoweroff, reboot, refresh\fP +\fBpoweroff, reboot, fido, refresh\fP Function key to use for such action. .SS strings Display strings to use for some elements. .TP -\fBf_poweroff, f_reboot, f_refresh\fP +\fBf_poweroff, f_reboot, f_fido, f_refresh\fP Text displayed to name such functions at the bottom of the screen. .TP \fBe_user, e_passwd\fP diff --git a/docs/yubikey.md b/docs/yubikey.md new file mode 100644 index 0000000..205b9ca --- /dev/null +++ b/docs/yubikey.md @@ -0,0 +1,30 @@ +# Yubikeys + +Quick reference explaining how yubikeys work for now. + +# Enable + +Yubikeys are disabled by default, to enable them activate a keybinding for it (`[functions] fido`) in the config file. + +Note that pressing this configured keybinding has no difference from trying to log in with an empty password, there's virtually no difference. + +`pam_u2f` must be configured with a registered key (`pamu2fcfg`). + +# Extra + +All my yubikey knowledge comes from the [pr that implemented this](https://github.com/javalsai/lidm/pull/89), please refer to it for extra details. Contributions to this documentation are welcome (explaining more in detail, potential issues... really anything that improves this). + +Allegedly this pam module configuration should work: + +```pam +#%PAM-1.0 + +auth sufficient pam_u2f.so cue +auth requisite pam_nologin.so +auth include system-local-login +account include system-local-login +session include system-local-login +password include system-local-login +``` + +Also, I recommend giving the [arch wiki](https://wiki.archlinux.org/title/YubiKey) a read anyways. diff --git a/include/config.h b/include/config.h index 2b120a9..f06d3e6 100644 --- a/include/config.h +++ b/include/config.h @@ -92,6 +92,7 @@ BUILD(chars, CHARS, TABLE_CHARS); #define TABLE_FUNCTIONS(F, name) \ F(enum keys, poweroff, KEY, F1, name) \ F(enum keys, reboot, KEY, F2, name) \ + F(enum keys, fido, KEY, NONE, name) \ F(enum keys, refresh, KEY, F5, name) BUILD(functions, FUNCTIONS, TABLE_FUNCTIONS); @@ -99,6 +100,7 @@ BUILD(functions, FUNCTIONS, TABLE_FUNCTIONS); #define TABLE_STRINGS(F, name) \ F(char* NNULLABLE, f_poweroff, STRING, "poweroff", name) \ F(char* NNULLABLE, f_reboot, STRING, "reboot", name) \ + F(char* NNULLABLE, f_fido, STRING, "fido", name) \ F(char* NNULLABLE, f_refresh, STRING, "refresh", name) \ F(char* NNULLABLE, e_user, STRING, "user", name) \ F(char* NNULLABLE, e_passwd, STRING, "password", name) \ diff --git a/include/keys.h b/include/keys.h index 8b37764..fa084fb 100644 --- a/include/keys.h +++ b/include/keys.h @@ -32,9 +32,11 @@ enum keys { END, PAGE_UP, PAGE_DOWN, + NONE, }; static const char* const KEY_NAMES[] = { + [NONE] = "NONE", [ESC] = "ESC", [F1] = "F1", [F2] = "F2", diff --git a/include/ui.h b/include/ui.h index d37df95..e9f15f2 100644 --- a/include/ui.h +++ b/include/ui.h @@ -46,6 +46,8 @@ void setup(struct config* config); int load(struct Vector* users, struct Vector* sessions); void print_err(const char* /*msg*/); void print_errno(const char* /*descr*/); +void print_pam_msg(const char* msg, int msg_style); +void clear_pam_msg(void); void ui_update_field(enum input focused_input); void ui_update_ffield(); diff --git a/src/auth.c b/src/auth.c index 0545d93..e18c309 100644 --- a/src/auth.c +++ b/src/auth.c @@ -17,6 +17,11 @@ #include "unistd.h" #include "util.h" +struct pam_conv_data { + char* password; + void (*display_pam_msg)(const char* msg, int msg_style); +}; + int pam_conversation(int num_msg, const struct pam_message** msg, struct pam_response** resp, void* appdata_ptr) { struct pam_response* reply = @@ -24,13 +29,34 @@ int pam_conversation(int num_msg, const struct pam_message** msg, if (!reply) { return PAM_BUF_ERR; } + + struct pam_conv_data* conv_data = (struct pam_conv_data*)appdata_ptr; + for (size_t i = 0; i < num_msg; i++) { reply[i].resp = NULL; reply[i].resp_retcode = 0; - if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF || - msg[i]->msg_style == PAM_PROMPT_ECHO_ON) { - char* input = (char*)appdata_ptr; - reply[i].resp = strdup(input); + + switch (msg[i]->msg_style) { + case PAM_PROMPT_ECHO_OFF: + case PAM_PROMPT_ECHO_ON: + reply[i].resp = strdup(conv_data->password); + if (!reply[i].resp) { + for (size_t j = 0; j < i; j++) + free(reply[j].resp); + free(reply); + return PAM_BUF_ERR; + } + break; + + case PAM_TEXT_INFO: + case PAM_ERROR_MSG: + if (conv_data->display_pam_msg && msg[i]->msg) { + conv_data->display_pam_msg(msg[i]->msg, msg[i]->msg_style); + } + break; + + default: + break; } } *resp = reply; @@ -54,7 +80,9 @@ void clear_screen() { pam_handle_t* get_pamh(char* user, char* passwd) { pam_handle_t* pamh = NULL; - struct pam_conv pamc = {pam_conversation, (void*)passwd}; + struct pam_conv_data conv_data = {.password = passwd, + .display_pam_msg = print_pam_msg}; + struct pam_conv pamc = {pam_conversation, (void*)&conv_data}; int ret; char* pam_service_override = getenv("LIDM_PAM_SERVICE"); @@ -192,9 +220,10 @@ bool launch(char* user, char* passwd, struct session session, void (*cb)(void), pam_handle_t* pamh = get_pamh(user, passwd); if (pamh == NULL) { - print_err("error on pam authentication"); + print_pam_msg("authentication failed", PAM_ERROR_MSG); return false; } + clear_pam_msg(); bool* reach_session = shmalloc(sizeof(bool)); if (reach_session == NULL) { diff --git a/src/ui.c b/src/ui.c index 8f8c3ee..7293fb5 100644 --- a/src/ui.c +++ b/src/ui.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -295,6 +296,21 @@ int load(struct Vector* users, struct Vector* sessions) { restore_all(); reboot(RB_POWER_OFF); exit(0); + } else if (g_config->functions.fido != NONE && + ansi_code == g_config->functions.fido) { + bool successful_write = write_launch_state((struct LaunchState){ + .username = st_user().username, + .session_opt = + st_session(g_config->behavior.include_defshell).name, + }); + if (!successful_write) log_puts("[E] failed to write launch state"); + + if (!launch(st_user().username, "", + st_session(g_config->behavior.include_defshell), + &restore_all, g_config)) { + print_passwd(utf8len(of_passwd.efield.content), true); + ui_update_cursor_focus(); + } } else if (ansi_code == A_UP || ansi_code == A_DOWN) { st_ch_focus(ansi_code == A_DOWN ? 1 : -1); } else if (ansi_code == A_RIGHT || ansi_code == A_LEFT) { @@ -509,6 +525,8 @@ static void print_box() { } static void print_footer() { + bool fido_enabled = g_config->functions.fido != NONE; + size_t bsize = utf8len(g_config->strings.f_poweroff) + utf8len(KEY_NAMES[g_config->functions.poweroff]) + utf8len(g_config->strings.f_reboot) + @@ -516,19 +534,31 @@ static void print_footer() { utf8len(g_config->strings.f_refresh) + utf8len(KEY_NAMES[g_config->functions.refresh]); - bsize += 2 * 2 + // 2 wide separators between 3 items - 3 * 1; // 3 thin separators inside every item + bsize += 2 * 2 + 3 * 1; + + if (fido_enabled) { + bsize += utf8len(g_config->strings.f_fido) + + utf8len(KEY_NAMES[g_config->functions.fido]) + 2 + 1; + } 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", - g_config->colors.e_key, g_config->colors.fg, row, col, - KEY_NAMES[g_config->functions.poweroff], - KEY_NAMES[g_config->functions.reboot], - KEY_NAMES[g_config->functions.refresh], g_config->strings.f_poweroff, - g_config->strings.f_reboot, g_config->strings.f_refresh); + + printf("\x1b[%d;%dH%s \x1b[%sm%s\x1b[%sm %s \x1b[%sm%s\x1b[%sm ", row, col, + g_config->strings.f_poweroff, g_config->colors.e_key, + KEY_NAMES[g_config->functions.poweroff], g_config->colors.fg, + g_config->strings.f_reboot, g_config->colors.e_key, + KEY_NAMES[g_config->functions.reboot], g_config->colors.fg); + + if (fido_enabled) { + printf("%s \x1b[%sm%s\x1b[%sm ", g_config->strings.f_fido, + g_config->colors.e_key, KEY_NAMES[g_config->functions.fido], + g_config->colors.fg); + } + + printf("%s \x1b[%sm%s\x1b[%sm", g_config->strings.f_refresh, + g_config->colors.e_key, KEY_NAMES[g_config->functions.refresh], + g_config->colors.fg); } void print_err(const char* msg) { @@ -536,6 +566,21 @@ void print_err(const char* msg) { msg, errno, strerror(errno)); } +void print_pam_msg(const char* msg, int msg_style) { + uint row = box_start.y + BOX_HEIGHT + 1; + const char* color = + (msg_style == PAM_ERROR_MSG) ? g_config->colors.err : g_config->colors.fg; + printf("\x1b[%d;%dH\x1b[K\x1b[%sm%.*s\x1b[%sm", row, box_start.x, color, + BOX_WIDTH, msg, g_config->colors.fg); + (void)fflush(stdout); +} + +void clear_pam_msg(void) { + uint row = box_start.y + BOX_HEIGHT + 1; + printf("\x1b[%d;%dH\x1b[K", row, box_start.x); + (void)fflush(stdout); +} + void print_errno(const char* descr) { if (descr == NULL) (void)fprintf(stderr, "\x1b[%d;%dH\x1b[%smunknown error(%d): %s", diff --git a/themes/default.ini b/themes/default.ini index a0859e6..081c989 100644 --- a/themes/default.ini +++ b/themes/default.ini @@ -25,11 +25,13 @@ [functions] # poweroff = F1 # reboot = F2 +# fido = NONE # refresh = F5 [strings] # f_poweroff = "poweroff" # f_reboot = "reboot" +# f_fido = "fido" # f_refresh = "refresh" # e_user = "user" # e_passwd = "password" From 4eca2b056ff569ea9b38e9a10f2cd872aa971d41 Mon Sep 17 00:00:00 2001 From: Martin Bogdanov Date: Fri, 16 Jan 2026 00:52:46 +0200 Subject: [PATCH 3/3] docs,makefile: put conventional systemd service path (#94) `/etc/systemd` -> `/usr/local/lib/systemd` --- Makefile | 4 ++-- assets/services/README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 185e987..aabe9a9 100644 --- a/Makefile +++ b/Makefile @@ -57,7 +57,7 @@ install: lidm uninstall: rm -rf ${DESTDIR}${PREFIX}/bin/lidm ${DESTDIR}/etc/lidm.ini rm -rf ${DESTDIR}/usr/share/man/man{1/lidm.1,5/lidm-config.5}.gz - rm -rf ${DESTDIR}/etc/systemd/system/lidm.service ${DESTDIR}/etc/dinit.d/lidm ${DESTDIR}/etc/runit/sv/lidm + rm -rf ${DESTDIR}/usr/local/lib/systemd/system/lidm.service ${DESTDIR}/etc/dinit.d/lidm ${DESTDIR}/etc/runit/sv/lidm install-service: @if command -v systemctl &> /dev/null; then \ @@ -87,7 +87,7 @@ install-service: fi install-service-systemd: - install -m644 ./assets/services/systemd.service ${DESTDIR}/etc/systemd/system/lidm.service + install -m644 ./assets/services/systemd.service ${DESTDIR}/usr/local/lib/systemd/system/lidm.service @printf '\033[1m%s\033[0m\n\n' " don't forget to run 'systemctl enable lidm'" install-service-dinit: install -m644 ./assets/services/dinit ${DESTDIR}/etc/dinit.d/lidm diff --git a/assets/services/README.md b/assets/services/README.md index bd98bbc..561eb0d 100644 --- a/assets/services/README.md +++ b/assets/services/README.md @@ -13,7 +13,7 @@ The manual steps for installation are: ## Systemd -- Copy `systemd.service` to `/etc/systemd/system/lidm.service` +- Copy `systemd.service` to `/usr/local/lib/systemd/system/lidm.service` (if the directory doesn't exist, create it first) - To enable it you can run `systemctl enable lidm` ## Dinit