Files
profanity/src/ui/inputwin.c
nia ce67753423 Avoid passing NULL pointers to curses functions.
This allows profanity to work without segfaulting from NULL
pointer dereferences when used with NetBSD libcurses.

Basic functionality was tested, there may be more NULL pointer
issues hiding.
2020-09-04 12:59:20 +02:00

850 lines
21 KiB
C

/*
* inputwin.c
* vim: expandtab:ts=4:sts=4:sw=4
*
* Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
*
* This file is part of Profanity.
*
* Profanity 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, either version 3 of the License, or
* (at your option) any later version.
*
* Profanity 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 Profanity. If not, see <https://www.gnu.org/licenses/>.
*
* In addition, as a special exception, the copyright holders give permission to
* link the code of portions of this program with the OpenSSL library under
* certain conditions as described in each individual source file, and
* distribute linked combinations including the two.
*
* You must obey the GNU General Public License in all respects for all of the
* code used other than OpenSSL. If you modify file(s) with this exception, you
* may extend this exception to your version of the file(s), but you are not
* obligated to do so. If you do not wish to do so, delete this exception
* statement from your version. If you delete this exception statement from all
* source files in the program, then also delete it here.
*
*/
#define _XOPEN_SOURCE_EXTENDED
#include "config.h"
#include <stdio.h>
#include <sys/select.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <sys/time.h>
#include <errno.h>
#include <pthread.h>
#include <readline/readline.h>
#include <readline/history.h>
#ifdef HAVE_NCURSESW_NCURSES_H
#include <ncursesw/ncurses.h>
#elif HAVE_NCURSES_H
#include <ncurses.h>
#elif HAVE_CURSES_H
#include <curses.h>
#endif
#include "profanity.h"
#include "log.h"
#include "common.h"
#include "command/cmd_ac.h"
#include "config/files.h"
#include "config/accounts.h"
#include "config/preferences.h"
#include "config/theme.h"
#include "ui/ui.h"
#include "ui/screen.h"
#include "ui/statusbar.h"
#include "ui/inputwin.h"
#include "ui/window.h"
#include "ui/window_list.h"
#include "xmpp/xmpp.h"
#include "xmpp/muc.h"
#include "xmpp/roster_list.h"
#include "xmpp/chat_state.h"
static WINDOW* inp_win;
static int pad_start = 0;
static struct timeval p_rl_timeout;
/* Timeout in ms. Shows how long select() may block. */
static gint inp_timeout = 0;
static gint no_input_count = 0;
static FILE* discard;
static fd_set fds;
static int r;
static char* inp_line = NULL;
static gboolean get_password = FALSE;
static void _inp_win_update_virtual(void);
static int _inp_edited(const wint_t ch);
static void _inp_win_handle_scroll(void);
static int _inp_offset_to_col(char* str, int offset);
static void _inp_write(char* line, int offset);
static void _inp_rl_addfuncs(void);
static int _inp_rl_getc(FILE* stream);
static void _inp_rl_linehandler(char* line);
static int _inp_rl_tab_handler(int count, int key);
static int _inp_rl_shift_tab_handler(int count, int key);
static int _inp_rl_win_clear_handler(int count, int key);
static int _inp_rl_win_close_handler(int count, int key);
static int _inp_rl_win_1_handler(int count, int key);
static int _inp_rl_win_2_handler(int count, int key);
static int _inp_rl_win_3_handler(int count, int key);
static int _inp_rl_win_4_handler(int count, int key);
static int _inp_rl_win_5_handler(int count, int key);
static int _inp_rl_win_6_handler(int count, int key);
static int _inp_rl_win_7_handler(int count, int key);
static int _inp_rl_win_8_handler(int count, int key);
static int _inp_rl_win_9_handler(int count, int key);
static int _inp_rl_win_0_handler(int count, int key);
static int _inp_rl_win_11_handler(int count, int key);
static int _inp_rl_win_12_handler(int count, int key);
static int _inp_rl_win_13_handler(int count, int key);
static int _inp_rl_win_14_handler(int count, int key);
static int _inp_rl_win_15_handler(int count, int key);
static int _inp_rl_win_16_handler(int count, int key);
static int _inp_rl_win_17_handler(int count, int key);
static int _inp_rl_win_18_handler(int count, int key);
static int _inp_rl_win_19_handler(int count, int key);
static int _inp_rl_win_20_handler(int count, int key);
static int _inp_rl_win_prev_handler(int count, int key);
static int _inp_rl_win_next_handler(int count, int key);
static int _inp_rl_win_next_unread_handler(int count, int key);
static int _inp_rl_win_pageup_handler(int count, int key);
static int _inp_rl_win_pagedown_handler(int count, int key);
static int _inp_rl_subwin_pageup_handler(int count, int key);
static int _inp_rl_subwin_pagedown_handler(int count, int key);
static int _inp_rl_startup_hook(void);
static int _inp_rl_down_arrow_handler(int count, int key);
void
create_input_window(void)
{
#ifdef NCURSES_REENTRANT
set_escdelay(25);
#else
ESCDELAY = 25;
#endif
discard = fopen("/dev/null", "a");
rl_outstream = discard;
rl_readline_name = "profanity";
_inp_rl_addfuncs();
rl_getc_function = _inp_rl_getc;
rl_startup_hook = _inp_rl_startup_hook;
rl_callback_handler_install(NULL, _inp_rl_linehandler);
inp_win = newpad(1, INP_WIN_MAX);
wbkgd(inp_win, theme_attrs(THEME_INPUT_TEXT));
;
keypad(inp_win, TRUE);
wmove(inp_win, 0, 0);
_inp_win_update_virtual();
}
char*
inp_readline(void)
{
free(inp_line);
inp_line = NULL;
p_rl_timeout.tv_sec = inp_timeout / 1000;
p_rl_timeout.tv_usec = inp_timeout % 1000 * 1000;
FD_ZERO(&fds);
FD_SET(fileno(rl_instream), &fds);
errno = 0;
pthread_mutex_unlock(&lock);
r = select(FD_SETSIZE, &fds, NULL, NULL, &p_rl_timeout);
pthread_mutex_lock(&lock);
if (r < 0) {
if (errno != EINTR) {
char* err_msg = strerror(errno);
log_error("Readline failed: %s", err_msg);
}
return NULL;
}
if (FD_ISSET(fileno(rl_instream), &fds)) {
rl_callback_read_char();
if (rl_line_buffer && rl_line_buffer[0] != '/' && rl_line_buffer[0] != '\0' && rl_line_buffer[0] != '\n') {
chat_state_activity();
}
ui_reset_idle_time();
if (!get_password) {
_inp_write(rl_line_buffer, rl_point);
}
inp_nonblocking(TRUE);
} else {
inp_nonblocking(FALSE);
chat_state_idle();
}
if (inp_line) {
if (!get_password && prefs_get_boolean(PREF_SLASH_GUARD)) {
if (strlen(inp_line) > 1) {
char* res = (char*)memchr(inp_line + 1, '/', 3);
if (res) {
cons_show("Your text contains a slash in the first 4 characters");
return NULL;
}
}
}
return strdup(inp_line);
} else {
return NULL;
}
}
void
inp_win_resize(void)
{
int col = getcurx(inp_win);
int wcols = getmaxx(stdscr);
// if lost cursor off screen, move contents to show it
if (col >= pad_start + wcols) {
pad_start = col - (wcols / 2);
if (pad_start < 0) {
pad_start = 0;
}
}
wbkgd(inp_win, theme_attrs(THEME_INPUT_TEXT));
;
_inp_win_update_virtual();
}
void
inp_nonblocking(gboolean reset)
{
if (!prefs_get_boolean(PREF_INPBLOCK_DYNAMIC)) {
inp_timeout = prefs_get_inpblock();
return;
}
if (reset) {
inp_timeout = 0;
no_input_count = 0;
}
if (inp_timeout < prefs_get_inpblock()) {
no_input_count++;
if (no_input_count % 10 == 0) {
inp_timeout += no_input_count;
if (inp_timeout > prefs_get_inpblock()) {
inp_timeout = prefs_get_inpblock();
}
}
}
}
void
inp_close(void)
{
rl_callback_handler_remove();
fclose(discard);
}
char*
inp_get_line(void)
{
werase(inp_win);
wmove(inp_win, 0, 0);
_inp_win_update_virtual();
doupdate();
char* line = NULL;
while (!line) {
line = inp_readline();
ui_update();
}
status_bar_clear_prompt();
return line;
}
char*
inp_get_password(void)
{
werase(inp_win);
wmove(inp_win, 0, 0);
_inp_win_update_virtual();
doupdate();
char* password = NULL;
get_password = TRUE;
while (!password) {
password = inp_readline();
ui_update();
}
get_password = FALSE;
status_bar_clear_prompt();
return password;
}
void
inp_put_back(void)
{
_inp_win_update_virtual();
}
static void
_inp_win_update_virtual(void)
{
int wcols = getmaxx(stdscr);
int row = screen_inputwin_row();
if (inp_win != NULL) {
pnoutrefresh(inp_win, 0, pad_start, row, 0, row, wcols - 2);
}
}
static void
_inp_write(char* line, int offset)
{
int col = _inp_offset_to_col(line, offset);
werase(inp_win);
waddstr(inp_win, line);
wmove(inp_win, 0, col);
_inp_win_handle_scroll();
_inp_win_update_virtual();
doupdate();
}
static int
_inp_edited(const wint_t ch)
{
// Use own state to be thread-safe with possible pthreads. C standard
// guarantees that initial value of the state will be zeroed buffer.
static mbstate_t mbstate;
// backspace
if (ch == 127) {
return 1;
}
// ctrl-w
if (ch == 23) {
return 1;
}
// enter
if (ch == 13) {
return 1;
}
// printable
char bytes[MB_CUR_MAX + 1];
size_t utf_len = wcrtomb(bytes, ch, &mbstate);
if (utf_len == (size_t)-1) {
return 0;
}
bytes[utf_len] = '\0';
gunichar unichar = g_utf8_get_char(bytes);
return g_unichar_isprint(unichar);
}
static int
_inp_offset_to_col(char* str, int offset)
{
int i = 0;
int col = 0;
while (i < offset && str[i] != '\0') {
gunichar uni = g_utf8_get_char(&str[i]);
size_t ch_len = mbrlen(&str[i], MB_CUR_MAX, NULL);
i += ch_len;
col++;
if (g_unichar_iswide(uni)) {
col++;
}
}
return col;
}
static void
_inp_win_handle_scroll(void)
{
int col = getcurx(inp_win);
int wcols = getmaxx(stdscr);
if (col == 0) {
pad_start = 0;
} else if (col >= pad_start + (wcols - 2)) {
pad_start = col - (wcols / 2);
if (pad_start < 0) {
pad_start = 0;
}
} else if (col <= pad_start) {
pad_start = pad_start - (wcols / 2);
if (pad_start < 0) {
pad_start = 0;
}
}
}
static void
_inp_rl_addfuncs(void)
{
rl_add_funmap_entry("prof_win_1", _inp_rl_win_1_handler);
rl_add_funmap_entry("prof_win_2", _inp_rl_win_2_handler);
rl_add_funmap_entry("prof_win_3", _inp_rl_win_3_handler);
rl_add_funmap_entry("prof_win_4", _inp_rl_win_4_handler);
rl_add_funmap_entry("prof_win_5", _inp_rl_win_5_handler);
rl_add_funmap_entry("prof_win_6", _inp_rl_win_6_handler);
rl_add_funmap_entry("prof_win_7", _inp_rl_win_7_handler);
rl_add_funmap_entry("prof_win_8", _inp_rl_win_8_handler);
rl_add_funmap_entry("prof_win_9", _inp_rl_win_9_handler);
rl_add_funmap_entry("prof_win_0", _inp_rl_win_0_handler);
rl_add_funmap_entry("prof_win_11", _inp_rl_win_11_handler);
rl_add_funmap_entry("prof_win_12", _inp_rl_win_12_handler);
rl_add_funmap_entry("prof_win_13", _inp_rl_win_13_handler);
rl_add_funmap_entry("prof_win_14", _inp_rl_win_14_handler);
rl_add_funmap_entry("prof_win_15", _inp_rl_win_15_handler);
rl_add_funmap_entry("prof_win_16", _inp_rl_win_16_handler);
rl_add_funmap_entry("prof_win_17", _inp_rl_win_17_handler);
rl_add_funmap_entry("prof_win_18", _inp_rl_win_18_handler);
rl_add_funmap_entry("prof_win_19", _inp_rl_win_19_handler);
rl_add_funmap_entry("prof_win_20", _inp_rl_win_20_handler);
rl_add_funmap_entry("prof_win_prev", _inp_rl_win_prev_handler);
rl_add_funmap_entry("prof_win_next", _inp_rl_win_next_handler);
rl_add_funmap_entry("prof_win_next_unread", _inp_rl_win_next_unread_handler);
rl_add_funmap_entry("prof_win_pageup", _inp_rl_win_pageup_handler);
rl_add_funmap_entry("prof_win_pagedown", _inp_rl_win_pagedown_handler);
rl_add_funmap_entry("prof_subwin_pageup", _inp_rl_subwin_pageup_handler);
rl_add_funmap_entry("prof_subwin_pagedown", _inp_rl_subwin_pagedown_handler);
rl_add_funmap_entry("prof_win_clear", _inp_rl_win_clear_handler);
rl_add_funmap_entry("prof_win_close", _inp_rl_win_close_handler);
}
// Readline callbacks
static int
_inp_rl_startup_hook(void)
{
rl_bind_keyseq("\\e1", _inp_rl_win_1_handler);
rl_bind_keyseq("\\e2", _inp_rl_win_2_handler);
rl_bind_keyseq("\\e3", _inp_rl_win_3_handler);
rl_bind_keyseq("\\e4", _inp_rl_win_4_handler);
rl_bind_keyseq("\\e5", _inp_rl_win_5_handler);
rl_bind_keyseq("\\e6", _inp_rl_win_6_handler);
rl_bind_keyseq("\\e7", _inp_rl_win_7_handler);
rl_bind_keyseq("\\e8", _inp_rl_win_8_handler);
rl_bind_keyseq("\\e9", _inp_rl_win_9_handler);
rl_bind_keyseq("\\e0", _inp_rl_win_0_handler);
rl_bind_keyseq("\\eq", _inp_rl_win_11_handler);
rl_bind_keyseq("\\ew", _inp_rl_win_12_handler);
rl_bind_keyseq("\\ee", _inp_rl_win_13_handler);
rl_bind_keyseq("\\er", _inp_rl_win_14_handler);
rl_bind_keyseq("\\et", _inp_rl_win_15_handler);
rl_bind_keyseq("\\ey", _inp_rl_win_16_handler);
rl_bind_keyseq("\\eu", _inp_rl_win_17_handler);
rl_bind_keyseq("\\ei", _inp_rl_win_18_handler);
rl_bind_keyseq("\\eo", _inp_rl_win_19_handler);
rl_bind_keyseq("\\ep", _inp_rl_win_20_handler);
rl_bind_keyseq("\\eOP", _inp_rl_win_1_handler);
rl_bind_keyseq("\\eOQ", _inp_rl_win_2_handler);
rl_bind_keyseq("\\eOR", _inp_rl_win_3_handler);
rl_bind_keyseq("\\eOS", _inp_rl_win_4_handler);
rl_bind_keyseq("\\e[15~", _inp_rl_win_5_handler);
rl_bind_keyseq("\\e[17~", _inp_rl_win_6_handler);
rl_bind_keyseq("\\e[18~", _inp_rl_win_7_handler);
rl_bind_keyseq("\\e[19~", _inp_rl_win_8_handler);
rl_bind_keyseq("\\e[20~", _inp_rl_win_9_handler);
rl_bind_keyseq("\\e[21~", _inp_rl_win_0_handler);
rl_bind_keyseq("\\e[1;9D", _inp_rl_win_prev_handler);
rl_bind_keyseq("\\e[1;3D", _inp_rl_win_prev_handler);
rl_bind_keyseq("\\e\\e[D", _inp_rl_win_prev_handler);
rl_bind_keyseq("\\e[1;9C", _inp_rl_win_next_handler);
rl_bind_keyseq("\\e[1;3C", _inp_rl_win_next_handler);
rl_bind_keyseq("\\e\\e[C", _inp_rl_win_next_handler);
rl_bind_keyseq("\\ea", _inp_rl_win_next_unread_handler);
rl_bind_keyseq("\\e\\e[5~", _inp_rl_subwin_pageup_handler);
rl_bind_keyseq("\\e[5;3~", _inp_rl_subwin_pageup_handler);
rl_bind_keyseq("\\e\\eOy", _inp_rl_subwin_pageup_handler);
rl_bind_keyseq("\\e\\e[6~", _inp_rl_subwin_pagedown_handler);
rl_bind_keyseq("\\e[6;3~", _inp_rl_subwin_pagedown_handler);
rl_bind_keyseq("\\e\\eOs", _inp_rl_subwin_pagedown_handler);
rl_bind_keyseq("\\e[5~", _inp_rl_win_pageup_handler);
rl_bind_keyseq("\\eOy", _inp_rl_win_pageup_handler);
rl_bind_keyseq("\\e[6~", _inp_rl_win_pagedown_handler);
rl_bind_keyseq("\\eOs", _inp_rl_win_pagedown_handler);
rl_bind_key('\t', _inp_rl_tab_handler);
rl_bind_keyseq("\\e[Z", _inp_rl_shift_tab_handler);
rl_bind_keyseq("\\e[1;5B", _inp_rl_down_arrow_handler); // ctrl+arrow down
// unbind unwanted mappings
rl_bind_keyseq("\\e=", NULL);
// disable readline completion
rl_variable_bind("disable-completion", "on");
// check for and load ~/.config/profanity/inputrc
gchar* inputrc = files_get_inputrc_file();
if (inputrc) {
rl_read_init_file(inputrc);
g_free(inputrc);
}
return 0;
}
static void
_inp_rl_linehandler(char* line)
{
if (line && *line) {
if (!get_password) {
add_history(line);
}
}
inp_line = line;
}
static gboolean shift_tab = FALSE;
static int
_inp_rl_getc(FILE* stream)
{
int ch = rl_getc(stream);
// 27, 91, 90 = Shift tab
if (ch == 27) {
shift_tab = TRUE;
return ch;
}
if (shift_tab && ch == 91) {
return ch;
}
if (shift_tab && ch == 90) {
return ch;
}
shift_tab = FALSE;
if (_inp_edited(ch)) {
ProfWin* window = wins_get_current();
cmd_ac_reset(window);
}
return ch;
}
static int
_inp_rl_win_clear_handler(int count, int key)
{
ProfWin* win = wins_get_current();
win_clear(win);
return 0;
}
static int
_inp_rl_win_close_handler(int count, int key)
{
ProfWin* win = wins_get_current();
gchar* args = 0;
cmd_close(win, 0, &args);
return 0;
}
static int
_inp_rl_tab_handler(int count, int key)
{
if (rl_point != rl_end || !rl_line_buffer) {
return 0;
}
ProfWin* current = wins_get_current();
if ((strncmp(rl_line_buffer, "/", 1) != 0) && (current->type == WIN_MUC)) {
char* result = muc_autocomplete(current, rl_line_buffer, FALSE);
if (result) {
rl_replace_line(result, 1);
rl_point = rl_end;
free(result);
}
} else if (strncmp(rl_line_buffer, "/", 1) == 0) {
ProfWin* window = wins_get_current();
char* result = cmd_ac_complete(window, rl_line_buffer, FALSE);
if (result) {
rl_replace_line(result, 1);
rl_point = rl_end;
free(result);
}
}
return 0;
}
static int
_inp_rl_shift_tab_handler(int count, int key)
{
if (rl_point != rl_end || !rl_line_buffer) {
return 0;
}
ProfWin* current = wins_get_current();
if ((strncmp(rl_line_buffer, "/", 1) != 0) && (current->type == WIN_MUC)) {
char* result = muc_autocomplete(current, rl_line_buffer, TRUE);
if (result) {
rl_replace_line(result, 1);
rl_point = rl_end;
free(result);
}
} else if (strncmp(rl_line_buffer, "/", 1) == 0) {
ProfWin* window = wins_get_current();
char* result = cmd_ac_complete(window, rl_line_buffer, TRUE);
if (result) {
rl_replace_line(result, 1);
rl_point = rl_end;
free(result);
}
}
return 0;
}
static void
_go_to_win(int i)
{
ProfWin* window = wins_get_by_num(i);
if (window) {
ui_focus_win(window);
}
}
static int
_inp_rl_win_1_handler(int count, int key)
{
_go_to_win(1);
return 0;
}
static int
_inp_rl_win_2_handler(int count, int key)
{
_go_to_win(2);
return 0;
}
static int
_inp_rl_win_3_handler(int count, int key)
{
_go_to_win(3);
return 0;
}
static int
_inp_rl_win_4_handler(int count, int key)
{
_go_to_win(4);
return 0;
}
static int
_inp_rl_win_5_handler(int count, int key)
{
_go_to_win(5);
return 0;
}
static int
_inp_rl_win_6_handler(int count, int key)
{
_go_to_win(6);
return 0;
}
static int
_inp_rl_win_7_handler(int count, int key)
{
_go_to_win(7);
return 0;
}
static int
_inp_rl_win_8_handler(int count, int key)
{
_go_to_win(8);
return 0;
}
static int
_inp_rl_win_9_handler(int count, int key)
{
_go_to_win(9);
return 0;
}
static int
_inp_rl_win_0_handler(int count, int key)
{
_go_to_win(0);
return 0;
}
static int
_inp_rl_win_11_handler(int count, int key)
{
_go_to_win(11);
return 0;
}
static int
_inp_rl_win_12_handler(int count, int key)
{
_go_to_win(12);
return 0;
}
static int
_inp_rl_win_13_handler(int count, int key)
{
_go_to_win(13);
return 0;
}
static int
_inp_rl_win_14_handler(int count, int key)
{
_go_to_win(14);
return 0;
}
static int
_inp_rl_win_15_handler(int count, int key)
{
_go_to_win(15);
return 0;
}
static int
_inp_rl_win_16_handler(int count, int key)
{
_go_to_win(16);
return 0;
}
static int
_inp_rl_win_17_handler(int count, int key)
{
_go_to_win(17);
return 0;
}
static int
_inp_rl_win_18_handler(int count, int key)
{
_go_to_win(18);
return 0;
}
static int
_inp_rl_win_19_handler(int count, int key)
{
_go_to_win(19);
return 0;
}
static int
_inp_rl_win_20_handler(int count, int key)
{
_go_to_win(20);
return 0;
}
static int
_inp_rl_win_prev_handler(int count, int key)
{
ProfWin* window = wins_get_previous();
if (window) {
ui_focus_win(window);
}
return 0;
}
static int
_inp_rl_win_next_handler(int count, int key)
{
ProfWin* window = wins_get_next();
if (window) {
ui_focus_win(window);
}
return 0;
}
static int
_inp_rl_win_next_unread_handler(int count, int key)
{
ProfWin* window = wins_get_next_unread();
if (window) {
ui_focus_win(window);
}
return 0;
}
static int
_inp_rl_win_pageup_handler(int count, int key)
{
ProfWin* current = wins_get_current();
win_page_up(current);
return 0;
}
static int
_inp_rl_win_pagedown_handler(int count, int key)
{
ProfWin* current = wins_get_current();
win_page_down(current);
return 0;
}
static int
_inp_rl_subwin_pageup_handler(int count, int key)
{
ProfWin* current = wins_get_current();
win_sub_page_up(current);
return 0;
}
static int
_inp_rl_subwin_pagedown_handler(int count, int key)
{
ProfWin* current = wins_get_current();
win_sub_page_down(current);
return 0;
}
static int
_inp_rl_down_arrow_handler(int count, int key)
{
add_history(rl_line_buffer);
using_history();
rl_replace_line("", 0);
rl_redisplay();
return 0;
}