Skip to content
Permalink
main
Go to file
2 contributors

Users who have contributed to this file

@tyleretters @ngwese
331 lines (280 sloc) 6.82 KB
/*
* ui.c
*
* indebted to https://github.com/ulfalizer/readline-and-ncurses
*/
#include <assert.h>
#include <locale.h>
#include <panel.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdnoreturn.h>
#include <string.h>
#include <readline/history.h>
#include <readline/readline.h>
#include <unistd.h>
#include <wchar.h>
#include <wctype.h>
#include "io.h"
#include "page.h"
#include "pages.h"
#include "ui.h"
pthread_mutex_t exit_lock;
//--------------------
//---- defines
#define max(a, b) \
({ typeof(a)_a = a; \
typeof(b)_b = b; \
_a > _b ? _a : _b; })
// call (most) ncurses functions with hard error checking
#define CHECK(fn) \
do { \
if (fn() == ERR) { \
fail_exit(#fn "() failed");}} \
while (false)
#define CHECKV(fn, ...) \
do { \
if (fn(__VA_ARGS__) == ERR) { \
fail_exit(#fn "() failed");}} \
while (false)
//---------------------
//---- static variables
//--- flags
static bool visual_mode = false;
static bool should_exit = false;
static bool input_avail = false;
//--- windows
static WINDOW *sep_win;
static WINDOW *cmd_win;
//---- data
static unsigned char input;
//--------------------------------
//--- static function declarations
//--- lifecycle
static void init_ncurses(void);
static void deinit_ncurses(void);
static void init_readline(void);
static void deinit_readline(void);
//---- input
static int readline_input_avail(void);
static int readline_getc(FILE *dummy);
static void forward_to_readline(char c);
static void handle_cmd(char *line);
//--- display
static void cmd_win_redisplay(bool for_resize);
static void readline_redisplay(void);
static void resize(void);
static noreturn void fail_exit(const char *msg);
//--------------------------------
//---- extern function definitions
void ui_loop(void) {
CHECKV(clearok, curscr, TRUE);
resize();
while (1) {
if(should_exit) {
break;
}
int c = wgetch(cmd_win);
pages_show_key(c);
// printf("%08x\n", c);
switch(c) {
case KEY_RESIZE:
resize();
break;
case '\f': // ctl-L : manual refresh
CHECKV(clearok, curscr, TRUE);
resize();
break;
case '\t':
page_cycle();
break;
case 0x5a: // shift+tab
page_switch();
break;
default:
forward_to_readline(c);
} /* switch */
}
mvwprintw(cmd_win, 0, 0, "EXITING!");
ui_deinit();
}
void ui_init(void) {
// FIXME(?) we could add all the stuff for multibyte support...
// assume default encoding for now...
/* if (setlocale(LC_ALL, "") == NULL) */
/* fail_exit("Failed to set locale attributes from environment"); */
init_ncurses();
init_readline();
}
void ui_deinit(void) {
pthread_mutex_lock(&exit_lock);
deinit_ncurses();
deinit_readline();
pages_deinit();
pthread_mutex_unlock(&exit_lock);
}
static void page_line(int i, char *str) {
pthread_mutex_lock(&exit_lock);
if(!should_exit) {
page_append(i, str);
if( page_id() == i) {
doupdate();
}
}
pthread_mutex_unlock(&exit_lock);
}
void ui_crone_line(char *str) {
page_line(PAGE_CRONE, str);
}
void ui_matron_line(char *str) {
// FIXME: sloppy way to handle this
if(strcmp(str, " <ok>\n") == 0) {
mvwprintw(sep_win, 0, 0, "<ok>");
} else if(strcmp(str, " <incomplete>\n") == 0) {
mvwprintw(sep_win, 0, 0, "<incomplete>");
} else {
mvwprintw(sep_win, 0, 0, " ");
page_line(PAGE_MATRON, str);
}
wrefresh(sep_win);
}
//-------------------------------
//--- static function definitions
int readline_input_avail(void)
{
return input_avail;
}
int readline_getc(FILE *dummy)
{
(void)dummy;
input_avail = false;
return input;
}
void forward_to_readline(char c)
{
input = c;
input_avail = true;
rl_callback_read_char();
}
void handle_cmd(char *line)
{
if(strlen(line) == 1) {
if (line[0] == 'q') {
should_exit = true;
}
}
else {
if (*line != '\0') {
add_history(line);
}
switch( page_id() ) {
case PAGE_MATRON:
io_send_line(IO_MATRON, line);
break;
case PAGE_CRONE:
io_send_line(IO_CRONE, line);
break;
}
free(line);
}
}
void cmd_win_redisplay(bool for_resize)
{
size_t cursor_col = rl_point;
CHECKV(werase, cmd_win);
// FIXME: error check would fail when command string is wider than window
mvwprintw(cmd_win, 0, 0, "%s%s", rl_display_prompt, rl_line_buffer);
if (cursor_col >= (size_t)COLS) {
// hide the cursor if it is outside the window
curs_set(0);
} else {
CHECKV(wmove, cmd_win, 0, cursor_col);
curs_set(2);
}
if (for_resize) {
CHECKV(wnoutrefresh, cmd_win);
} else {
CHECKV(wrefresh, cmd_win);
}
}
void readline_redisplay(void)
{
cmd_win_redisplay(false);
}
void resize(void)
{
if (LINES >= 3) {
CHECKV(wresize, sep_win, 1, COLS);
CHECKV(wresize, cmd_win, 1, COLS);
CHECKV(mvwin, sep_win, LINES - 2, 0);
CHECKV(mvwin, cmd_win, LINES - 1, 0);
}
// batch refreshes and commit them with doupdate()
// FIXME: resize the page pads
CHECKV(wnoutrefresh, sep_win);
cmd_win_redisplay(true);
CHECK(doupdate);
}
void init_ncurses(void)
{
if (initscr() == NULL) {
fail_exit("failed to initialize ncurses");
}
visual_mode = true;
CHECK(cbreak);
CHECK(noecho);
CHECK(nonl);
CHECKV(intrflush, NULL, FALSE);
curs_set(1);
int nrows, ncols;
getmaxyx(stdscr, nrows, ncols);
sep_win = newwin(1, ncols, nrows - 2, 0);
cmd_win = newwin(1, ncols, nrows - 1, 0);
//nb: no keypad() because we don't want preprocessing before readline
// CHECK(keypad, cmd_win, TRUE);
pages_init(nrows, ncols);
CHECKV(wbkgd, sep_win, A_STANDOUT);
CHECKV(wrefresh, sep_win);
pages_print_greeting();
}
void deinit_ncurses(void)
{
CHECKV(delwin, sep_win);
CHECKV(delwin, cmd_win);
CHECK(endwin);
visual_mode = false;
}
void init_readline(void)
{
// disable completion. (FIXME?)
if (rl_bind_key('\t', rl_insert) != 0) {
fail_exit("invalid key passed to rl_bind_key()");
}
// let ncurses do all terminal and signal handling
rl_catch_signals = 0;
rl_catch_sigwinch = 0;
rl_deprep_term_function = NULL;
rl_prep_term_function = NULL;
// prevent readline from setting LINES, COLS
rl_change_environment = 0;
// handle input by manually feeding characters to readline
rl_getc_function = readline_getc;
rl_input_available_hook = readline_input_avail;
rl_redisplay_function = readline_redisplay;
rl_callback_handler_install("", handle_cmd);
}
void deinit_readline(void)
{
rl_callback_handler_remove();
}
noreturn void fail_exit(const char *msg)
{
if (visual_mode) {
endwin();
}
fprintf(stderr, "%s\n", msg);
exit(EXIT_FAILURE);
}