Rewrite to use termo

Also get rid of some silliness that I'm only able to see now.
This commit is contained in:
Přemysl Eric Janouch 2014-10-14 22:53:53 +02:00
parent a3348d888b
commit b352a0fc8d
4 changed files with 424 additions and 352 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "termo"]
path = termo
url = git://github.com/pjanouch/termo.git

View File

@ -8,6 +8,9 @@ if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUC)
"${CMAKE_C_FLAGS_DEBUG} -Wall -Wextra -Wno-missing-field-initializers") "${CMAKE_C_FLAGS_DEBUG} -Wall -Wextra -Wno-missing-field-initializers")
endif ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUC) endif ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUC)
# Build options
option (USE_SYSTEM_TERMO "Don't compile our own termo, use the system one" OFF)
# Version # Version
set (project_VERSION_MAJOR "0") set (project_VERSION_MAJOR "0")
set (project_VERSION_MINOR "1") set (project_VERSION_MINOR "1")
@ -21,13 +24,27 @@ set (project_VERSION "${project_VERSION}.${project_VERSION_PATCH}")
include (CheckFunctionExists) include (CheckFunctionExists)
CHECK_FUNCTION_EXISTS ("wcwidth" HAVE_WCWIDTH) CHECK_FUNCTION_EXISTS ("wcwidth" HAVE_WCWIDTH)
# Dependecies # Dependencies
find_package (ZLIB REQUIRED) find_package (ZLIB REQUIRED)
find_package (PkgConfig REQUIRED) find_package (PkgConfig REQUIRED)
pkg_check_modules (dependencies REQUIRED ncursesw glib-2.0 gio-2.0 pango) pkg_check_modules (dependencies REQUIRED ncursesw glib-2.0 gio-2.0 pango)
include_directories (${ZLIB_INCLUDE_DIRS} ${dependencies_INCLUDE_DIRS}) if (USE_SYSTEM_TERMO)
find_package (Termo REQUIRED)
else (USE_SYSTEM_TERMO)
add_subdirectory (termo EXCLUDE_FROM_ALL)
# We don't have many good choices when we don't want to install it and want
# to support older versions of CMake; this is a relatively clean approach
# (other possibilities: setting a variable in the parent scope, using a
# cache variable, writing a special config file with build paths in it and
# including it here, or setting a custom property on the targets).
get_directory_property (Termo_INCLUDE_DIRS
DIRECTORY termo INCLUDE_DIRECTORIES)
set (Termo_LIBRARIES termo-static)
endif (USE_SYSTEM_TERMO)
include_directories (${ZLIB_INCLUDE_DIRS}
${dependencies_INCLUDE_DIRS} ${Termo_INCLUDE_DIRS})
# Localization # Localization
find_package (Gettext REQUIRED) find_package (Gettext REQUIRED)
@ -76,7 +93,8 @@ set (project_common_headers
src/utils.h) src/utils.h)
# Project libraries # Project libraries
set (project_common_libraries ${ZLIB_LIBRARIES} ${dependencies_LIBRARIES}) set (project_common_libraries
${ZLIB_LIBRARIES} ${dependencies_LIBRARIES} termo-static)
# Create a common project library so that source files are only compiled once # Create a common project library so that source files are only compiled once
if (${CMAKE_VERSION} VERSION_GREATER "2.8.7") if (${CMAKE_VERSION} VERSION_GREATER "2.8.7")

View File

@ -31,7 +31,6 @@
#include <glib.h> #include <glib.h>
#include <gio/gio.h> #include <gio/gio.h>
#include <pango/pango.h> #include <pango/pango.h>
#include <ncurses.h>
#include <glib/gi18n.h> #include <glib/gi18n.h>
#include <unistd.h> #include <unistd.h>
@ -39,70 +38,16 @@
#include <errno.h> #include <errno.h>
#include <signal.h> #include <signal.h>
#include <termo.h> // input
#include <ncurses.h> // output
#include "config.h" #include "config.h"
#include "stardict.h" #include "stardict.h"
#define CTRL_KEY(x) ((x) - 'A' + 1) #define CTRL_KEY(x) ((x) - 'A' + 1)
#define KEY_CTRL_A CTRL_KEY ('A') //!< Ctrl-A (SOH)
#define KEY_CTRL_B CTRL_KEY ('B') //!< Ctrl-B (STX)
#define KEY_CTRL_E CTRL_KEY ('E') //!< Ctrl-E (ENQ)
#define KEY_CTRL_F CTRL_KEY ('F') //!< Ctrl-F (ACK)
#define KEY_CTRL_H CTRL_KEY ('H') //!< Ctrl-H (BS)
#define KEY_CTRL_K CTRL_KEY ('K') //!< Ctrl-K (VT)
#define KEY_CTRL_L CTRL_KEY ('L') //!< Ctrl-L (FF)
#define KEY_CTRL_N CTRL_KEY ('N') //!< Ctrl-N (SO)
#define KEY_CTRL_P CTRL_KEY ('P') //!< Ctrl-P (DLE)
#define KEY_CTRL_T CTRL_KEY ('T') //!< Ctrl-T (DC4)
#define KEY_CTRL_U CTRL_KEY ('U') //!< Ctrl-U (NAK)
#define KEY_CTRL_W CTRL_KEY ('W') //!< Ctrl-W (ETB)
#define KEY_RETURN 13 //!< Enter
#define KEY_ESCAPE 27 //!< Esc
typedef enum {
TERMINAL_UNKNOWN, //!< No extra handling
TERMINAL_XTERM, //!< xterm and VTE extra keycodes
TERMINAL_RXVT //!< rxvt extra keycodes
} TerminalType; //!< Type of the terminal
typedef enum {
KEY_NOT_RECOGNISED, //!< Not recognised
KEY_CTRL_UP, //!< Ctrl + Up arrow
KEY_CTRL_DOWN, //!< Ctrl + Down arrow
KEY_CTRL_LEFT, //!< Ctrl + Left arrow
KEY_CTRL_RIGHT, //!< Ctrl + Right arrow
KEY_ALT_UP, //!< Alt + Up arrow
KEY_ALT_DOWN, //!< Alt + Down arrow
KEY_ALT_LEFT, //!< Alt + Left arrow
KEY_ALT_RIGHT //!< Alt + Right arrow
} ExtraKeyCode; //!< Translated key codes above KEY_MAX
// --- Utilities --------------------------------------------------------------- // --- Utilities ---------------------------------------------------------------
static int
poll_restart (struct pollfd *fds, nfds_t nfds, int timeout)
{
int ret;
do
ret = poll (fds, nfds, timeout);
while (ret == -1 && errno == EINTR);
return ret;
}
/** Wrapper for curses event data. */
typedef struct curses_event CursesEvent;
struct curses_event
{
wint_t code;
guint is_char : 1;
MEVENT mouse;
};
static size_t static size_t
unichar_width (gunichar ch) unichar_width (gunichar ch)
{ {
@ -122,58 +67,6 @@ is_character_in_locale (wchar_t c)
return wcstombs (NULL, s, 0) != (size_t) -1; return wcstombs (NULL, s, 0) != (size_t) -1;
} }
/** Translate key codes above KEY_MAX returned from ncurses into something
* meaningful, based on the terminal type. The values have been obtained
* experimentally. Some keycodes make ncurses return KEY_ESCAPE, even
* depending on actual terminal settings, thus this is not reliable at all.
* xterm/VTE seems to behave nicely, though.
*/
static guint
translate_extra_keycode (wchar_t code, TerminalType terminal)
{
switch (terminal)
{
case TERMINAL_XTERM:
switch (code)
{
case 565: return KEY_CTRL_UP;
case 524: return KEY_CTRL_DOWN;
case 544: return KEY_CTRL_LEFT;
case 559: return KEY_CTRL_RIGHT;
case 563: return KEY_ALT_UP;
case 522: return KEY_ALT_DOWN;
case 542: return KEY_ALT_LEFT;
case 557: return KEY_ALT_RIGHT;
}
break;
case TERMINAL_RXVT:
switch (code)
{
case 521: return KEY_CTRL_UP;
case 514: return KEY_CTRL_DOWN;
}
break;
case TERMINAL_UNKNOWN:
break;
}
return KEY_NOT_RECOGNISED;
}
/** Get the type of the terminal based on the TERM environment variable. */
static TerminalType
get_terminal_type (void)
{
const gchar *term = g_getenv ("TERM");
if (!term) return TERMINAL_UNKNOWN;
gchar term_copy[strcspn (term, "-") + 1];
g_strlcpy (term_copy, term, sizeof term_copy);
if (!strcmp (term_copy, "xterm")) return TERMINAL_XTERM;
if (!strcmp (term_copy, "rxvt")) return TERMINAL_RXVT;
return TERMINAL_UNKNOWN;
}
// --- Application ------------------------------------------------------------- // --- Application -------------------------------------------------------------
/** Data relating to one entry within the dictionary. */ /** Data relating to one entry within the dictionary. */
@ -190,9 +83,10 @@ struct view_entry
struct application struct application
{ {
TerminalType terminal_type; //!< Type of the terminal GMainLoop * loop; //!< Main loop
termo_t * tk; //!< termo handle
guint tk_timeout; //!< termo timeout
GIConv utf8_to_wchar; //!< utf-8 -> wchar_t conversion GIConv utf8_to_wchar; //!< utf-8 -> wchar_t conversion
GIConv wchar_to_utf8; //!< wchar_t -> utf-8 conversion
StardictDict * dict; //!< The current dictionary StardictDict * dict; //!< The current dictionary
guint show_help : 1; //!< Whether help can be shown guint show_help : 1; //!< Whether help can be shown
@ -312,6 +206,10 @@ app_reload_view (Application *self)
static void static void
app_init (Application *self, const gchar *filename) app_init (Application *self, const gchar *filename)
{ {
self->loop = g_main_loop_new (NULL, FALSE);
self->tk = NULL;
self->tk_timeout = 0;
GError *error = NULL; GError *error = NULL;
self->dict = stardict_dict_new (filename, &error); self->dict = stardict_dict_new (filename, &error);
if (!self->dict) if (!self->dict)
@ -320,7 +218,6 @@ app_init (Application *self, const gchar *filename)
exit (EXIT_FAILURE); exit (EXIT_FAILURE);
} }
self->terminal_type = get_terminal_type ();
self->show_help = TRUE; self->show_help = TRUE;
self->top_position = 0; self->top_position = 0;
@ -337,7 +234,6 @@ app_init (Application *self, const gchar *filename)
self->division = 0.5; self->division = 0.5;
self->wchar_to_utf8 = g_iconv_open ("utf-8//translit", "wchar_t");
self->utf8_to_wchar = g_iconv_open ("wchar_t//translit", "utf-8"); self->utf8_to_wchar = g_iconv_open ("wchar_t//translit", "utf-8");
app_reload_view (self); app_reload_view (self);
@ -347,12 +243,17 @@ app_init (Application *self, const gchar *filename)
static void static void
app_destroy (Application *self) app_destroy (Application *self)
{ {
g_main_loop_unref (self->loop);
if (self->tk)
termo_destroy (self->tk);
if (self->tk_timeout)
g_source_remove (self->tk_timeout);
g_object_unref (self->dict); g_object_unref (self->dict);
g_ptr_array_free (self->entries, TRUE); g_ptr_array_free (self->entries, TRUE);
g_free (self->search_label); g_free (self->search_label);
g_array_free (self->input, TRUE); g_array_free (self->input, TRUE);
g_iconv_close (self->wchar_to_utf8);
g_iconv_close (self->utf8_to_wchar); g_iconv_close (self->utf8_to_wchar);
} }
@ -519,7 +420,7 @@ app_show_help (Application *self)
{ {
PROJECT_NAME " " PROJECT_VERSION, PROJECT_NAME " " PROJECT_VERSION,
_("Terminal UI for StarDict dictionaries"), _("Terminal UI for StarDict dictionaries"),
"Copyright (c) 2013, Přemysl Janouch", "Copyright (c) 2013 - 2014, Přemysl Janouch",
"", "",
_("Type to search") _("Type to search")
}; };
@ -795,98 +696,126 @@ app_search_for_entry (Application *self)
move (last_y, last_x); \ move (last_y, last_x); \
refresh (); refresh ();
/** Process input above KEY_MAX. */ /** The terminal has been resized, make appropriate changes. */
static gboolean static gboolean
app_process_extra_code (Application *self, CursesEvent *event) app_process_resize (Application *self)
{ {
SAVE_CURSOR app_reload_view (self);
switch (translate_extra_keycode (event->code, self->terminal_type))
{
case KEY_CTRL_UP:
app_one_entry_up (self);
RESTORE_CURSOR
break;
case KEY_CTRL_DOWN:
app_one_entry_down (self);
RESTORE_CURSOR
break;
case KEY_ALT_LEFT: guint n_visible = app_count_view_items (self) - self->top_offset;
self->division = (app_get_left_column_width (self) - 1.) / COLS; if ((gint) n_visible > LINES - 1)
n_visible = LINES - 1;
if (self->selected >= n_visible)
{
app_scroll_down (self, self->selected - n_visible + 1);
self->selected = n_visible - 1;
}
app_redraw (self);
return TRUE;
}
/** Process mouse input. */
static gboolean
app_process_mouse (Application *self, termo_key_t *event)
{
int line, column, button;
termo_mouse_event_t type;
termo_interpret_mouse (self->tk, event, &type, &button, &line, &column);
if (type != TERMO_MOUSE_PRESS || button != 1)
return TRUE;
SAVE_CURSOR
if (line == 0)
{
gsize label_len = g_utf8_strlen (self->search_label, -1);
gint pos = column - label_len;
if (pos >= 0)
{
self->input_pos = MIN ((guint) pos, self->input->len);
move (0, label_len + self->input_pos);
refresh ();
}
}
else if (line <= (int) (app_count_view_items (self) - self->top_offset))
{
self->selected = line - 1;
app_redraw_view (self); app_redraw_view (self);
RESTORE_CURSOR RESTORE_CURSOR
break;
case KEY_ALT_RIGHT:
self->division = (app_get_left_column_width (self) + 1.) / COLS;
app_redraw_view (self);
RESTORE_CURSOR
break;
} }
return TRUE; return TRUE;
} }
/** Process input that's not a character or is a control code. */ // --- User input handling -----------------------------------------------------
/** All the actions that can be performed by the user. */
typedef enum user_action UserAction;
enum user_action
{
USER_ACTION_NONE,
USER_ACTION_QUIT,
USER_ACTION_REDRAW,
USER_ACTION_MOVE_SPLITTER_LEFT,
USER_ACTION_MOVE_SPLITTER_RIGHT,
USER_ACTION_GOTO_ENTRY_PREVIOUS,
USER_ACTION_GOTO_ENTRY_NEXT,
USER_ACTION_GOTO_DEFINITION_PREVIOUS,
USER_ACTION_GOTO_DEFINITION_NEXT,
USER_ACTION_GOTO_PAGE_PREVIOUS,
USER_ACTION_GOTO_PAGE_NEXT,
USER_ACTION_INPUT_CONFIRM,
USER_ACTION_INPUT_HOME,
USER_ACTION_INPUT_END,
USER_ACTION_INPUT_LEFT,
USER_ACTION_INPUT_RIGHT,
USER_ACTION_INPUT_DELETE_PREVIOUS,
USER_ACTION_INPUT_DELETE_NEXT,
USER_ACTION_INPUT_DELETE_TO_HOME,
USER_ACTION_INPUT_DELETE_TO_END,
USER_ACTION_INPUT_DELETE_PREVIOUS_WORD,
USER_ACTION_INPUT_TRANSPOSE,
USER_ACTION_COUNT
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static gboolean static gboolean
app_process_nonchar_code (Application *self, CursesEvent *event) app_process_user_action (Application *self, UserAction action)
{ {
SAVE_CURSOR SAVE_CURSOR
switch (event->code) switch (action)
{ {
case KEY_RESIZE: case USER_ACTION_QUIT:
{
app_reload_view (self);
guint n_visible = app_count_view_items (self) - self->top_offset;
if ((gint) n_visible > LINES - 1)
n_visible = LINES - 1;
if (self->selected >= n_visible)
{
app_scroll_down (self, self->selected - n_visible + 1);
self->selected = n_visible - 1;
}
app_redraw (self);
break;
}
case KEY_MOUSE:
if (!(event->mouse.bstate & BUTTON1_PRESSED))
break;
if (event->mouse.y == 0)
{
gsize label_len = g_utf8_strlen (self->search_label, -1);
gint pos = event->mouse.x - label_len;
if (pos >= 0)
{
self->input_pos = MIN ((guint) pos, self->input->len);
move (0, label_len + self->input_pos);
refresh ();
}
}
else if (event->mouse.y <= (int)
(app_count_view_items (self) - self->top_offset))
{
self->selected = event->mouse.y - 1;
app_redraw_view (self);
RESTORE_CURSOR
}
break;
case KEY_ESCAPE:
return FALSE; return FALSE;
case KEY_RETURN: case USER_ACTION_REDRAW:
self->input_confirmed = TRUE;
app_redraw_top (self);
break;
case KEY_CTRL_L: // redraw everything
clear (); clear ();
app_redraw (self); app_redraw (self);
break; return TRUE;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
case KEY_UP:
case KEY_CTRL_P: // previous case USER_ACTION_MOVE_SPLITTER_LEFT:
self->division = (app_get_left_column_width (self) - 1.) / COLS;
app_redraw_view (self);
RESTORE_CURSOR
return TRUE;
case USER_ACTION_MOVE_SPLITTER_RIGHT:
self->division = (app_get_left_column_width (self) + 1.) / COLS;
app_redraw_view (self);
RESTORE_CURSOR
return TRUE;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
case USER_ACTION_GOTO_DEFINITION_PREVIOUS:
if (self->selected > 0) if (self->selected > 0)
{ {
self->selected--; self->selected--;
@ -895,9 +824,8 @@ app_process_nonchar_code (Application *self, CursesEvent *event)
else else
app_scroll_up (self, 1); app_scroll_up (self, 1);
RESTORE_CURSOR RESTORE_CURSOR
break; return TRUE;
case KEY_DOWN: case USER_ACTION_GOTO_DEFINITION_NEXT:
case KEY_CTRL_N: // next
if ((gint) self->selected < LINES - 2 && if ((gint) self->selected < LINES - 2 &&
self->selected < app_count_view_items (self) - self->top_offset - 1) self->selected < app_count_view_items (self) - self->top_offset - 1)
{ {
@ -907,103 +835,61 @@ app_process_nonchar_code (Application *self, CursesEvent *event)
else else
app_scroll_down (self, 1); app_scroll_down (self, 1);
RESTORE_CURSOR RESTORE_CURSOR
break; return TRUE;
case KEY_PPAGE:
case KEY_CTRL_B: // back case USER_ACTION_GOTO_ENTRY_PREVIOUS:
app_one_entry_up (self);
RESTORE_CURSOR
return TRUE;
case USER_ACTION_GOTO_ENTRY_NEXT:
app_one_entry_down (self);
RESTORE_CURSOR
return TRUE;
case USER_ACTION_GOTO_PAGE_PREVIOUS:
app_scroll_up (self, LINES - 1); app_scroll_up (self, LINES - 1);
// FIXME selection // FIXME: selection
RESTORE_CURSOR RESTORE_CURSOR
break; return TRUE;
case KEY_NPAGE: case USER_ACTION_GOTO_PAGE_NEXT:
case KEY_CTRL_F: // forward
app_scroll_down (self, LINES - 1); app_scroll_down (self, LINES - 1);
// FIXME selection // FIXME: selection
RESTORE_CURSOR RESTORE_CURSOR
break; return TRUE;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
case KEY_HOME:
case KEY_CTRL_A: case USER_ACTION_INPUT_HOME:
self->input_pos = 0; self->input_pos = 0;
app_redraw_top (self); app_redraw_top (self);
break; return TRUE;
case KEY_END: case USER_ACTION_INPUT_END:
case KEY_CTRL_E:
self->input_pos = self->input->len; self->input_pos = self->input->len;
app_redraw_top (self); app_redraw_top (self);
break; return TRUE;
case KEY_LEFT: case USER_ACTION_INPUT_LEFT:
if (self->input_pos > 0) if (self->input_pos > 0)
{ {
self->input_pos--; self->input_pos--;
app_redraw_top (self); app_redraw_top (self);
} }
break; return TRUE;
case KEY_RIGHT: case USER_ACTION_INPUT_RIGHT:
if (self->input_pos < self->input->len) if (self->input_pos < self->input->len)
{ {
self->input_pos++; self->input_pos++;
app_redraw_top (self); app_redraw_top (self);
} }
break;
case KEY_BACKSPACE:
case KEY_CTRL_H:
if (self->input_pos > 0)
{
g_array_remove_index (self->input, --self->input_pos);
app_search_for_entry (self);
app_redraw_top (self);
}
break;
case KEY_DC:
if (self->input_pos < self->input->len)
{
g_array_remove_index (self->input, self->input_pos);
app_search_for_entry (self);
app_redraw_top (self);
}
break;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
case KEY_CTRL_K: // delete until the end of line
if (self->input_pos < self->input->len)
{
g_array_remove_range (self->input,
self->input_pos, self->input->len - self->input_pos);
app_search_for_entry (self);
app_redraw_top (self);
}
return TRUE; return TRUE;
case KEY_CTRL_W: // delete word before cursor
{
if (self->input_pos == 0)
return TRUE;
gint i = self->input_pos; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
while (i)
if (g_array_index (self->input, gunichar, --i) != L' ')
break;
while (i--)
if (g_array_index (self->input, gunichar, i) == L' ')
break;
i++; case USER_ACTION_INPUT_CONFIRM:
g_array_remove_range (self->input, i, self->input_pos - i); self->input_confirmed = TRUE;
self->input_pos = i;
app_search_for_entry (self);
app_redraw_top (self); app_redraw_top (self);
return TRUE; return TRUE;
}
case KEY_CTRL_U: // delete everything before the cursor
if (self->input->len != 0)
{
g_array_remove_range (self->input, 0, self->input_pos);
self->input_pos = 0;
app_search_for_entry (self); case USER_ACTION_INPUT_TRANSPOSE:
app_redraw_top (self);
}
return TRUE;
case KEY_CTRL_T: // transposition
{ {
if (!self->input_pos || self->input->len < 2) if (!self->input_pos || self->input->len < 2)
break; break;
@ -1025,60 +911,209 @@ app_process_nonchar_code (Application *self, CursesEvent *event)
return TRUE; return TRUE;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
case USER_ACTION_INPUT_DELETE_PREVIOUS:
if (self->input_pos > 0)
{
g_array_remove_index (self->input, --self->input_pos);
app_search_for_entry (self);
app_redraw_top (self);
}
return TRUE;
case USER_ACTION_INPUT_DELETE_NEXT:
if (self->input_pos < self->input->len)
{
g_array_remove_index (self->input, self->input_pos);
app_search_for_entry (self);
app_redraw_top (self);
}
return TRUE;
case USER_ACTION_INPUT_DELETE_TO_HOME:
if (self->input->len != 0)
{
g_array_remove_range (self->input, 0, self->input_pos);
self->input_pos = 0;
app_search_for_entry (self);
app_redraw_top (self);
}
return TRUE;
case USER_ACTION_INPUT_DELETE_TO_END:
if (self->input_pos < self->input->len)
{
g_array_remove_range (self->input,
self->input_pos, self->input->len - self->input_pos);
app_search_for_entry (self);
app_redraw_top (self);
}
return TRUE;
case USER_ACTION_INPUT_DELETE_PREVIOUS_WORD:
{
if (self->input_pos == 0)
return TRUE;
gint i = self->input_pos;
while (i)
if (g_array_index (self->input, gunichar, --i) != L' ')
break;
while (i--)
if (g_array_index (self->input, gunichar, i) == L' ')
break;
i++;
g_array_remove_range (self->input, i, self->input_pos - i);
self->input_pos = i;
app_search_for_entry (self);
app_redraw_top (self);
return TRUE;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
case USER_ACTION_NONE:
return TRUE;
default: default:
return app_process_extra_code (self, event); g_assert_not_reached ();
} }
return TRUE; return TRUE;
} }
/** Process input events from ncurses. */ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static gboolean static gboolean
app_process_curses_event (Application *self, CursesEvent *event) app_process_keysym (Application *self, termo_key_t *event)
{ {
// Characters below the space are ASCII control codes UserAction action = USER_ACTION_NONE;
if (!event->is_char || event->code < L' ') typedef const UserAction ActionMap[TERMO_N_SYMS];
return app_process_nonchar_code (self, event);
wchar_t code = event->code; static ActionMap actions =
gchar *letter = g_convert_with_iconv ((gchar *) &code, sizeof code,
self->wchar_to_utf8, NULL, NULL, NULL);
g_return_val_if_fail (letter != NULL, FALSE);
gunichar c = g_utf8_get_char (letter);
if (g_unichar_isprint (c))
{ {
self->show_help = FALSE; [TERMO_SYM_ESCAPE] = USER_ACTION_QUIT,
if (self->input_confirmed) [TERMO_SYM_UP] = USER_ACTION_GOTO_DEFINITION_PREVIOUS,
{ [TERMO_SYM_DOWN] = USER_ACTION_GOTO_DEFINITION_NEXT,
if (self->input->len != 0) [TERMO_SYM_PAGEUP] = USER_ACTION_GOTO_PAGE_PREVIOUS,
g_array_remove_range (self->input, 0, self->input->len); [TERMO_SYM_PAGEDOWN] = USER_ACTION_GOTO_PAGE_NEXT,
self->input_pos = 0;
self->input_confirmed = FALSE;
}
g_array_insert_val (self->input, self->input_pos++, c); [TERMO_SYM_ENTER] = USER_ACTION_INPUT_CONFIRM,
app_search_for_entry (self);
app_redraw_top (self); [TERMO_SYM_HOME] = USER_ACTION_INPUT_HOME,
} [TERMO_SYM_END] = USER_ACTION_INPUT_END,
g_free (letter); [TERMO_SYM_LEFT] = USER_ACTION_INPUT_LEFT,
[TERMO_SYM_RIGHT] = USER_ACTION_INPUT_RIGHT,
[TERMO_SYM_BACKSPACE] = USER_ACTION_INPUT_DELETE_PREVIOUS,
// XXX: what's the difference?
[TERMO_SYM_DELETE] = USER_ACTION_INPUT_DELETE_NEXT,
[TERMO_SYM_DEL] = USER_ACTION_INPUT_DELETE_NEXT,
};
static ActionMap actions_alt =
{
[TERMO_SYM_LEFT] = USER_ACTION_MOVE_SPLITTER_LEFT,
[TERMO_SYM_RIGHT] = USER_ACTION_MOVE_SPLITTER_RIGHT,
};
static ActionMap actions_ctrl =
{
[TERMO_SYM_UP] = USER_ACTION_GOTO_ENTRY_PREVIOUS,
[TERMO_SYM_DOWN] = USER_ACTION_GOTO_ENTRY_NEXT,
};
if (!event->modifiers)
action = actions[event->code.sym];
else if (event->modifiers == TERMO_KEYMOD_ALT)
action = actions_alt[event->code.sym];
else if (event->modifiers == TERMO_KEYMOD_CTRL)
action = actions_ctrl[event->code.sym];
return app_process_user_action (self, action);
}
static gboolean
app_process_ctrl_key (Application *self, termo_key_t *event)
{
static const UserAction actions[32] =
{
[CTRL_KEY ('L')] = USER_ACTION_REDRAW,
[CTRL_KEY ('P')] = USER_ACTION_GOTO_DEFINITION_PREVIOUS,
[CTRL_KEY ('N')] = USER_ACTION_GOTO_DEFINITION_NEXT,
[CTRL_KEY ('B')] = USER_ACTION_GOTO_PAGE_PREVIOUS,
[CTRL_KEY ('F')] = USER_ACTION_GOTO_PAGE_NEXT,
[CTRL_KEY ('A')] = USER_ACTION_INPUT_HOME,
[CTRL_KEY ('E')] = USER_ACTION_INPUT_END,
[CTRL_KEY ('H')] = USER_ACTION_INPUT_DELETE_PREVIOUS,
[CTRL_KEY ('K')] = USER_ACTION_INPUT_DELETE_TO_END,
[CTRL_KEY ('W')] = USER_ACTION_INPUT_DELETE_PREVIOUS_WORD,
[CTRL_KEY ('U')] = USER_ACTION_INPUT_DELETE_TO_HOME,
[CTRL_KEY ('T')] = USER_ACTION_INPUT_TRANSPOSE,
};
gint64 i = (gint64) event->code.codepoint - 'a' + 1;
if (i > 0 && i < (gint64) G_N_ELEMENTS (actions))
return app_process_user_action (self, actions[i]);
return TRUE; return TRUE;
} }
static gboolean
app_process_key (Application *self, termo_key_t *event)
{
if (event->modifiers == TERMO_KEYMOD_CTRL)
return app_process_ctrl_key (self, event);
if (event->modifiers)
return TRUE;
gunichar c = event->code.codepoint;
if (!g_unichar_isprint (c))
{
beep ();
return TRUE;
}
self->show_help = FALSE;
if (self->input_confirmed)
{
if (self->input->len != 0)
g_array_remove_range (self->input, 0, self->input->len);
self->input_pos = 0;
self->input_confirmed = FALSE;
}
g_array_insert_val (self->input, self->input_pos++, c);
app_search_for_entry (self);
app_redraw_top (self);
return TRUE;
}
/** Process input events from the terminal. */
static gboolean
app_process_termo_event (Application *self, termo_key_t *event)
{
switch (event->type)
{
case TERMO_TYPE_MOUSE:
return app_process_mouse (self, event);
case TERMO_TYPE_KEY:
return app_process_key (self, event);
case TERMO_TYPE_KEYSYM:
return app_process_keysym (self, event);
default:
return TRUE;
}
}
// --- SIGWINCH ---------------------------------------------------------------- // --- SIGWINCH ----------------------------------------------------------------
static int g_winch_pipe[2]; /**< SIGWINCH signalling pipe. */ static int g_winch_pipe[2]; /**< SIGWINCH signalling pipe. */
static void (*g_old_winch_handler) (int);
static void static void
winch_handler (int signum) winch_handler (int signum)
{ {
/* Call the ncurses handler. */ (void) signum;
if (g_old_winch_handler)
g_old_winch_handler (signum);
/* And wake up the poll() call. */
write (g_winch_pipe[1], "x", 1); write (g_winch_pipe[1], "x", 1);
} }
@ -1091,43 +1126,73 @@ install_winch_handler (void)
act.sa_flags = SA_RESTART; act.sa_flags = SA_RESTART;
sigemptyset (&act.sa_mask); sigemptyset (&act.sa_mask);
sigaction (SIGWINCH, &act, &oldact); sigaction (SIGWINCH, &act, &oldact);
/* Save the ncurses handler. */
if (oldact.sa_handler != SIG_DFL
&& oldact.sa_handler != SIG_IGN)
g_old_winch_handler = oldact.sa_handler;
} }
// --- Initialisation, event handling ------------------------------------------ // --- Initialisation, event handling ------------------------------------------
Application g_application; static gboolean process_stdin_input_timeout (gpointer data);
static gboolean static gboolean
process_stdin_input (void) process_stdin_input (G_GNUC_UNUSED GIOChannel *source,
G_GNUC_UNUSED GIOCondition condition, gpointer data)
{ {
CursesEvent event; Application *app = data;
int sta; if (app->tk_timeout)
while ((sta = get_wch (&event.code)) != ERR)
{ {
event.is_char = (sta == OK); g_source_remove (app->tk_timeout);
if (sta == KEY_CODE_YES && event.code == KEY_MOUSE app->tk_timeout = 0;
&& getmouse (&event.mouse) == ERR)
abort ();
if (!app_process_curses_event (&g_application, &event))
return FALSE;
} }
termo_advisereadable (app->tk);
termo_key_t event;
termo_result_t res;
while ((res = termo_getkey (app->tk, &event)) == TERMO_RES_KEY)
if (!app_process_termo_event (app, &event))
goto quit;
if (res == TERMO_RES_AGAIN)
app->tk_timeout = g_timeout_add (termo_get_waittime (app->tk),
process_stdin_input_timeout, app);
else if (res == TERMO_RES_ERROR || res == TERMO_RES_EOF)
goto quit;
return TRUE; return TRUE;
quit:
g_main_loop_quit (app->loop);
return false;
} }
static gboolean static gboolean
process_winch_input (int fd) process_stdin_input_timeout (gpointer data)
{ {
char c; Application *app = data;
termo_key_t event;
if (termo_getkey_force (app->tk, &event) == TERMO_RES_KEY)
if (!app_process_termo_event (app, &event))
g_main_loop_quit (app->loop);
read (fd, &c, 1); app->tk_timeout = 0;
return process_stdin_input (); return FALSE;
}
static gboolean
process_winch_input (GIOChannel *source,
G_GNUC_UNUSED GIOCondition condition, gpointer data)
{
Application *app = data;
char c;
read (g_io_channel_unix_get_fd (source), &c, 1);
// TODO: look for resizeterm() and use it if available for flicker-free
// resize; endwin() escapes curses mode.
endwin ();
refresh ();
app_process_resize (app);
return TRUE;
} }
int int
@ -1182,49 +1247,34 @@ G_GNUC_END_IGNORE_DEPRECATIONS
g_option_context_free (ctx); g_option_context_free (ctx);
app_init (&g_application, argv[1]); Application app;
app_init (&app, argv[1]);
TERMO_CHECK_VERSION;
if (!(app.tk = termo_new (STDIN_FILENO, NULL, 0)))
abort ();
if (!initscr () if (!initscr ()
|| cbreak () == ERR
|| noecho () == ERR || noecho () == ERR
|| nonl () == ERR) || nonl () == ERR)
abort (); abort ();
keypad (stdscr, TRUE); /* Enable character processing. */ // TODO: catch SIGINT as well
nodelay (stdscr, TRUE); /* Don't block on get_wch(). */
mousemask (ALL_MOUSE_EVENTS, NULL); /* Register mouse events. */
mouseinterval (0);
if (pipe (g_winch_pipe) == -1) if (pipe (g_winch_pipe) == -1)
abort (); abort ();
install_winch_handler (); install_winch_handler ();
app_redraw (&g_application); app_redraw (&app);
/* Message loop. */ /* Message loop. */
struct pollfd pollfd[2]; g_io_add_watch (g_io_channel_unix_new (STDIN_FILENO),
G_IO_IN, process_stdin_input, &app);
pollfd[0].fd = fileno (stdin); g_io_add_watch (g_io_channel_unix_new (g_winch_pipe[0]),
pollfd[0].events = POLLIN; G_IO_IN, process_winch_input, &app);
pollfd[1].fd = g_winch_pipe[0]; g_main_loop_run (app.loop);
pollfd[1].events = POLLIN;
while (TRUE)
{
if (poll_restart (pollfd, 2, -1) == -1)
abort ();
if ((pollfd[0].revents & POLLIN)
&& !process_stdin_input ())
break;
if ((pollfd[1].revents & POLLIN)
&& !process_winch_input (pollfd[1].fd))
break;
}
endwin (); endwin ();
app_destroy (&g_application); app_destroy (&app);
if (close (g_winch_pipe[0]) == -1 if (close (g_winch_pipe[0]) == -1
|| close (g_winch_pipe[1]) == -1) || close (g_winch_pipe[1]) == -1)

1
termo Submodule

@ -0,0 +1 @@
Subproject commit 828f03a063ef5e1e9bb113614083c3f4e59d5317