/* * ui.c - hlogin(1) user interface functions * * Copyleft (C) 2022 ~keith * * This file is part of hlogin. * * hlogin is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, version 3. * * hlogin is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License * along with hlogin. If not, see . */ #include "config.h" #include "ui.h" #include #include #include #include #include #include #include "gettext.h" #include #include #include #include #include #include int term_rows, term_cols; WINDOW *back_w; PANEL *back_p; WINDOW *login_w; PANEL *login_p; WINDOW *login_shadow_w; PANEL *login_shadow_p; const int login_rows = 8; const int login_cols = 48; char *input_buf; size_t input_buflen; int input_pos; int input_scroll; char *msg; bool hide_input; int btn_focus = 0; int btn_count = 2; char *hostname; char *domain; char *unknown_str = "(unknown)"; char *xgethostname() { size_t len = HOST_NAME_MAX; char *buf = malloc(sizeof(char) * (len + 1)); if (!buf) return unknown_str; while (gethostname(buf, len) != 0) { if (errno != EINVAL && errno != ENAMETOOLONG) { free(buf); return unknown_str; } len *= 2; buf = realloc(buf, sizeof(char) * (len + 1)); if (!buf) return unknown_str; } buf[len] = '\0'; return buf; } char *xgetdomainname(char *host) { struct addrinfo hints, *info = NULL; memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_CANONNAME; if ((getaddrinfo(host, NULL, &hints, &info) != 0) || !info || !info->ai_canonname) goto FAILURE; // skip the first part of the canonname (the hostname) char *domain = strchr(info->ai_canonname, '.'); if (!domain || (strlen(domain) <= 1)) goto FAILURE; domain++; // copy into a new buffer & return char *buf = malloc(sizeof(char) * (strlen(domain) + 1)); strcpy(buf, domain); freeaddrinfo(info); return buf; FAILURE: if (info) freeaddrinfo(info); return unknown_str; } char *xgetnisdomainname() { size_t len = HOST_NAME_MAX; char *buf = malloc(sizeof(char) * (len + 1)); if (!buf) return unknown_str; while (getdomainname(buf, len) != 0) { if (errno != EINVAL && errno != ENAMETOOLONG) { free(buf); return unknown_str; } len *= 2; buf = realloc(buf, sizeof(char) * (len + 1)); if (!buf) return unknown_str; } buf[len] = '\0'; return buf; } char erase_ch; void ui_setup_dialog(char *prompt, bool password) { input_buf[0] = '\0'; input_pos = 0; input_scroll = 0; btn_focus = 0; msg = prompt; hide_input = password; wclear(login_w); draw_outline(login_w, login_rows, login_cols, 0, 0, false); paint_login(); } /** ui_init - initialize the UI subsystem */ void ui_init() { initscr(); noecho(); cbreak(); halfdelay(2); start_color(); getmaxyx(stdscr, term_rows, term_cols); clear(); erase_ch = erasechar(); // set up input buffer input_pos = 0; input_buflen = 256; input_buf = malloc(input_buflen * sizeof(char)); if (!input_buf) exit(EXIT_FAILURE); input_buf[0] = '\0'; // get hostname & domain hostname = xgethostname(); domain = xgetdomainname(hostname); // set up colours init_pair(1, COLOR_WHITE, COLOR_BLUE); init_pair(2, COLOR_BLACK, COLOR_WHITE); init_pair(3, COLOR_WHITE, COLOR_WHITE); init_pair(4, COLOR_RED, COLOR_WHITE); // set up background window back_w = newwin(term_rows, term_cols, 0, 0); back_p = new_panel(back_w); wattron(back_w, COLOR_PAIR(1)); wbkgd(back_w, ' ' | COLOR_PAIR(1)); wclear(back_w); paint_back(); // set up login shadow window login_shadow_w = newwin(login_rows, login_cols, ((term_rows - login_rows) / 2) + 1, ((term_cols - login_cols) / 2) + 1); login_shadow_p = new_panel(login_shadow_w); wbkgd(login_shadow_w, ' ' | COLOR_PAIR(0)); wclear(login_shadow_w); // set up dialog window login_w = newwin(login_rows, login_cols, (term_rows - login_rows) / 2, (term_cols - login_cols) / 2); login_p = new_panel(login_w); wattron(login_w, COLOR_PAIR(2)); wbkgd(login_w, ' ' | COLOR_PAIR(2)); wclear(login_w); draw_outline(login_w, login_rows, login_cols, 0, 0, false); paint_login(); // enable keypad mode keypad(login_w, true); } /** paint_back - redraw info displayed on background */ void paint_back() { wmove(back_w, 0, 0); wclrtoeol(back_w); wattron(back_w, A_BOLD); wprintw(back_w, _("Welcome to %s.%s"), hostname, domain); wattroff(back_w, A_BOLD); char time_buf[256]; time_t now = time(NULL); if (strftime(time_buf, 256, "%c", localtime(&now)) == 0) time_buf[0] = '\0'; wmove(back_w, 0, term_cols - strlen(time_buf)); wprintw(back_w, "%s", time_buf); } /** paint_login - redraw login dialog */ void paint_login() { // Field outline draw_outline(login_w, 3, login_cols - 4, 2, 2, true); // Field label wmove(login_w, 1, 2); wattron(login_w, A_BOLD | COLOR_PAIR(2)); waddstr(login_w, msg); wattroff(login_w, A_BOLD); // Field text wmove(login_w, 3, 3); whline(login_w, ' ', login_cols - 6); if (!hide_input) waddnstr(login_w, input_buf, login_cols - 6); else { int num_dots = strlen(input_buf); if (num_dots > login_cols - 6) num_dots = login_cols - 6; whline(login_w, '*', num_dots); } // Buttons wmove(login_w, login_rows - 2, 2); if (btn_focus == 0) wattron(login_w, A_REVERSE); wprintw(login_w, "< %s >", _("OK")); wattroff(login_w, A_REVERSE); wmove(login_w, login_rows - 2, login_w->_curx + 2); if (btn_focus == 1) wattron(login_w, A_REVERSE); wprintw(login_w, "< %s >", _("Cancel")); wattroff(login_w, A_REVERSE); } int ui_update() { paint_back(); wmove(login_w, 3, 3 + input_pos); update_panels(); doupdate(); int ch = wgetch(login_w); if (ch == ERR) return -1; if (ch >= ' ' && ch < 0x7F) input_add_char(ch); else if (ch == 0x08 || ch == KEY_BACKSPACE || ch == erase_ch) input_backdel_char(); else if (ch == 0x7F || ch == KEY_DC) input_del_char(); else if (ch == KEY_LEFT && input_pos > 0) input_pos--; else if (ch == KEY_RIGHT && input_pos < strlen(input_buf)) input_pos++; else if (ch == '\t') btn_focus = (btn_focus + 1) % btn_count; else if (ch == 0x1B) // escape return -2; else if (ch == KEY_ENTER || ch == '\r' || ch == '\n') return btn_focus; paint_login(); return -1; } int ui_run() { int result; while ((result = ui_update()) == -1); return result; } char *ui_get_text() { unsigned int len = strlen(input_buf) + 1; char *buf = malloc(len * sizeof(char)); strcpy(buf, input_buf); return buf; } void input_add_char(char ch) { // resize if necessary if ((strlen(input_buf) + 2) > input_buflen) { input_buflen *= 2; input_buf = realloc(input_buf, input_buflen * sizeof(char)); } // insert new char & shift everything over for (int i = (input_pos++); i < input_buflen; i++) { char temp = input_buf[i]; input_buf[i] = ch; ch = temp; if (input_buf[i] == '\0') break; } } void input_backdel_char() { if (input_pos <= 0) return; input_pos--; input_del_char(); } void input_del_char() { if (input_buf[input_pos] == '\0') return; for (int i = input_pos; i < (input_buflen - 1); i++) { input_buf[i] = input_buf[i + 1]; if (input_buf[i] == '\0') break; } } const attr_t light = A_BOLD | COLOR_PAIR(3); const attr_t shadow = COLOR_PAIR(2); void draw_outline(WINDOW *win, int height, int width, int y, int x, bool inset) { wattron(win, A_ALTCHARSET); wattron(win, inset ? shadow : light); mvwhline(win, y, x + 1, ACS_HLINE, width - 2); mvwvline(win, y + 1, x, ACS_VLINE, height - 2); mvwaddch(win, y, x, ACS_ULCORNER); mvwaddch(win, y + height - 1, x, ACS_LLCORNER); wattroff(win, inset ? shadow : light); wattron(win, inset ? light : shadow); mvwhline(win, y + height - 1, x + 1, ACS_HLINE, width - 2); mvwvline(win, y + 1, x + width - 1, ACS_VLINE, height - 2); mvwaddch(win, y, x + width - 1, ACS_URCORNER); mvwaddch(win, y + height - 1, x + width - 1, ACS_LRCORNER); wattroff(win, inset ? light : shadow); wattroff(win, A_ALTCHARSET); }