386 lines
8.2 KiB
C
386 lines
8.2 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 <panel.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;
|
||
|
|
||
|
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);
|
||
|
}
|