/* * 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 "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; WINDOW *error_w; PANEL *error_p; WINDOW *error_shadow_w; PANEL *error_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(); } void ui_setup_message(char *text) { msg = text; // TODO insert word wrap logic here int msg_len = strlen(text); } /** 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 + input_scroll, login_cols - 6); else { int num_dots = strlen(input_buf) - input_scroll; 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 - input_scroll); 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--; if (input_pos < input_scroll) input_scroll--; } else if (ch == KEY_RIGHT && input_pos < strlen(input_buf)) { input_pos++; if (input_pos >= (input_scroll + login_cols - 6)) input_scroll++; } 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; } if (input_pos >= (input_scroll + login_cols - 6)) input_scroll++; } 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; } if ((input_scroll + login_cols - 6) > strlen(input_buf) && input_scroll > 0) input_scroll--; } 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); } /** output_word - used in word_wrap */ void output_word(char *word, size_t len, char **out, size_t *out_size, int *out_pos, int *line_pos, int *lines, int width) { int i = 0; while (i < len) { int space = width - (*line_pos); int remain = len - i; if (space > remain) { // can fit on line if ((*out_size) < (*out_pos) + remain) { *out_size *= 2; *out = realloc(*out, sizeof(char) * (*out_size)); } // copy word memcpy((*out) + (*out_pos), word + i, sizeof(char) * remain); i += remain; *out_pos += remain; *line_pos += remain; } else { if ((*out_size) < (*out_pos) + space + 1) { *out_size *= 2; *out = realloc(*out, sizeof(char) * (*out_size)); } // copy part of word memcpy((*out) + (*out_pos), word + i, sizeof(char) * space); i += space; *out_pos += space; // add newline (*out)[(*out_pos)++] = '\n'; *line_pos = 0; (*lines)++; } } } char *word_wrap(char *text, int width, int max_word, int *lines) { // output buffer size_t out_size = 256; char *out = malloc(sizeof(char) * out_size); int out_pos = 0; // word buffer char *word = malloc(sizeof(char) * max_word); int word_len = 0; int line_pos = 0; for (char ch; (ch = *text) != '\0'; text++) { switch (ch) { case '\n': case ' ': if (word[0] == '\0') word_len = 0; // ensure buffer is large enough if (out_size < out_pos + word_len + 2) { out_size *= 2; out = realloc(out, sizeof(char) * out_size); } if (word_len > 0) { // do we have to wrap to a new line? if (line_pos + word_len > width) { out[out_pos++] = '\n'; line_pos = 0; (*lines)++; } memcpy(out + out_pos, word, sizeof(char) * word_len); word_len = 0; out_pos += word_len; line_pos += word_len; } // output \n if (ch == '\n' || line_pos >= width) { out[out_pos++] = '\n'; line_pos = 0; (*lines)++; } else { out[out_pos++] = ' '; line_pos++; } break; default: // word longer than word buffer if (word_len >= max_word) { if (word[0] != '\0') { // write what we have output_word(word, word_len, &out, &out_size, &out_pos, &line_pos, lines, width); word[0] = '\0'; } // ensure buffer is large enough if (out_size < out_pos + 2) { out_size *= 2; out = realloc(out, sizeof(char) * out_size); } // write char and maybe newline out[out_pos++] = ch; if ((++line_pos) >= width) { out[out_pos++] = '\n'; line_pos = 0; (*lines)++; } } else word[word_len++] = ch; } } free(word); }