523 lines
11 KiB
C
523 lines
11 KiB
C
/*
|
|
* ui.c - hlogin(1) user interface functions
|
|
*
|
|
* Copyleft (C) 2022 ~keith <keith@keithhacks.cyou>
|
|
*
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
#include "config.h"
|
|
#include "ui.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <errno.h>
|
|
|
|
#include <locale.h>
|
|
#include "gettext.h"
|
|
|
|
#include <unistd.h>
|
|
#include <limits.h>
|
|
#include <time.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <netdb.h>
|
|
|
|
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);
|
|
}
|