Browse Source

degesch: use libffi to unify input callbacks

And fuck you both, Readline and Editline.
tags/v0.9.3
Přemysl Janouch 3 years ago
parent
commit
584d2f0295
3 changed files with 292 additions and 334 deletions
  1. +4
    -4
      CMakeLists.txt
  2. +4
    -4
      README.adoc
  3. +284
    -326
      degesch.c

+ 4
- 4
CMakeLists.txt View File

@@ -39,7 +39,7 @@ include (AddThreads)

find_package (Curses)
find_package (PkgConfig REQUIRED)
pkg_check_modules (libssl REQUIRED libssl libcrypto)
pkg_check_modules (dependencies REQUIRED libssl libcrypto libffi)
pkg_check_modules (ncursesw ncursesw)

if ("${CMAKE_SYSTEM_NAME}" MATCHES "BSD")
@@ -50,9 +50,9 @@ if ("${CMAKE_SYSTEM_NAME}" MATCHES "BSD")
add_definitions (-D__BSD_VISIBLE=1 -D_BSD_SOURCE=1)
endif ("${CMAKE_SYSTEM_NAME}" MATCHES "BSD")

list (APPEND project_libraries ${libssl_LIBRARIES})
include_directories (${libssl_INCLUDE_DIRS})
link_directories (${libssl_LIBRARY_DIRS})
list (APPEND project_libraries ${dependencies_LIBRARIES})
include_directories (${dependencies_INCLUDE_DIRS})
link_directories (${dependencies_LIBRARY_DIRS})

# FIXME: other Lua versions may be acceptable, don't know yet
pkg_search_module (lua lua53 lua5.3 lua-5.3 lua>=5.3)

+ 4
- 4
README.adoc View File

@@ -10,7 +10,7 @@ All of them have these potentially interesting properties:

- full IPv6 support
- TLS support, including client certificates
- minimal dependencies
- lean on dependencies (with the exception of 'degesch')
- compact and arguably easy to hack on
- permissive license

@@ -66,9 +66,9 @@ support (even though socksify can add that easily to any program).
Building
--------
Build dependencies: CMake, pkg-config, help2man, awk, sh, liberty (included) +
Runtime dependencies: openssl, curses (degesch),
readline >= 6.0 or libedit >= 2013-07-12 (degesch),
lua >= 5.3 (degesch, optional)
Runtime dependencies: openssl +
Additionally for degesch: curses, libffi, lua >= 5.3 (optional),
readline >= 6.0 or libedit >= 2013-07-12

$ git clone --recursive https://github.com/pjanouch/uirc3.git
$ mkdir uirc3/build

+ 284
- 326
degesch.c View File

@@ -68,6 +68,8 @@ enum
#undef lines
#undef columns

#include <ffi.h>

#ifdef HAVE_READLINE
#include <readline/readline.h>
#include <readline/history.h>
@@ -134,6 +136,22 @@ input_buffer_destroy (struct input_buffer *self)
free (self);
}

typedef bool (*input_fn) (int count, int key, void *user_data);

struct input_fn_data
{
ffi_closure closure; ///< Closure

LIST_HEADER (struct input_fn_data)
input_fn callback; ///< Real callback
void *user_data; ///< Real callback user data

#ifdef HAVE_EDITLINE
wchar_t *name; ///< Function name
wchar_t *help; ///< Function help
#endif // HAVE_EDITLINE
};

struct input
{
bool active; ///< Are we a thing?
@@ -145,6 +163,7 @@ struct input
#elif defined HAVE_EDITLINE
EditLine *editline; ///< The EditLine object
#endif // HAVE_EDITLINE
struct input_fn_data *fns; ///< Functions

char *prompt; ///< The prompt we use
int prompt_shown; ///< Whether the prompt is shown now
@@ -164,6 +183,14 @@ input_free (struct input *self)
#ifdef HAVE_READLINE
free (self->saved_line);
#endif // HAVE_READLINE
LIST_FOR_EACH (struct input_fn_data, iter, self->fns)
{
#ifdef HAVE_EDITLINE
free (iter->name);
free (iter->help);
#endif // HAVE_EDITLINE
ffi_closure_free (iter);
}
free (self->prompt);
}

@@ -279,6 +306,45 @@ input_get_content (struct input *self)

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

static void
input_closure_forwarder (ffi_cif *cif, void *ret, void **args, void *user_data)
{
(void) cif;

struct input_fn_data *data = user_data;
if (!data->callback
(*(int *) args[0], UNMETA (*(int *) args[1]), data->user_data))
rl_ding ();
*(int *) ret = 0;
}

static void
input_add_fn (struct input *self,
const char *name, const char *help, input_fn callback, void *user_data)
{
(void) help;

void *bound_fn = NULL;
struct input_fn_data *data = ffi_closure_alloc (sizeof *data, &bound_fn);
hard_assert (data);

static ffi_cif cif;
static ffi_type *args[2] = { &ffi_type_sint, &ffi_type_sint };
hard_assert (ffi_prep_cif
(&cif, FFI_DEFAULT_ABI, 2, &ffi_type_sint, args) == FFI_OK);

data->prev = data->next = NULL;
data->callback = callback;
data->user_data = user_data;
hard_assert (ffi_prep_closure_loc (&data->closure,
&cif, input_closure_forwarder, data, bound_fn) == FFI_OK);

rl_add_defun (name, (rl_command_func_t *) bound_fn, -1);
LIST_PREPEND (self->fns, data);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

static int app_readline_init (void);
static void on_readline_input (char *line);
static char **app_readline_completion (const char *text, int start, int end);
@@ -603,6 +669,52 @@ input_get_content (struct input *self)

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

static void
input_closure_forwarder (ffi_cif *cif, void *ret, void **args, void *user_data)
{
(void) cif;

struct input_fn_data *data = user_data;
*(unsigned char *) ret = data->callback
(1, *(int *) args[1], data->user_data) ? CC_NORM : CC_ERROR;
}

static wchar_t *
ascii_to_wide (const char *ascii)
{
size_t len = strlen (ascii) + 1;
wchar_t *wide = xcalloc (sizeof *wide, len);
while (len--)
hard_assert ((wide[len] = (unsigned char) ascii[len]) < 0x80);
return wide;
}

static void
input_add_fn (struct input *self,
const char *name, const char *help, input_fn callback, void *user_data)
{
void *bound_fn = NULL;
struct input_fn_data *data = ffi_closure_alloc (sizeof *data, &bound_fn);
hard_assert (data);

static ffi_cif cif;
static ffi_type *args[2] = { &ffi_type_pointer, &ffi_type_sint };
hard_assert (ffi_prep_cif
(&cif, FFI_DEFAULT_ABI, 2, &ffi_type_uchar, args) == FFI_OK);

data->user_data = user_data;
data->callback = callback;
data->name = ascii_to_wide (name);
data->help = ascii_to_wide (help);
hard_assert (ffi_prep_closure_loc (&data->closure,
&cif, input_closure_forwarder, data, bound_fn) == FFI_OK);

el_wset (self->editline, EL_ADDFN, data->name, data->help, bound_fn);
LIST_PREPEND (self->fns, data);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

static void
input_start (struct input *self, const char *program_name)
{
@@ -11161,40 +11273,6 @@ resume_terminal (struct app_context *ctx)
input_show (&ctx->input);
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

static void
redraw_screen (struct app_context *ctx)
{
input_hide (&ctx->input);

// If by some circumstance we had the wrong idea
input_on_terminal_resized (&ctx->input);
update_screen_size ();

buffer_print_backlog (ctx, ctx->current_buffer);

input_show (&ctx->input);
}

static bool
jump_to_buffer (struct app_context *ctx, int n)
{
if (n < 0 || n > 9)
return false;

// There's no buffer zero
if (n == 0)
n = 10;

if (ctx->last_buffer && buffer_get_index (ctx, ctx->current_buffer) == n)
// Fast switching between two buffers
buffer_activate (ctx, ctx->last_buffer);
else if (!buffer_goto (ctx, n))
return false;
return true;
}

static pid_t
spawn_helper_child (struct app_context *ctx)
{
@@ -11222,69 +11300,20 @@ spawn_helper_child (struct app_context *ctx)
}

static void
launch_backlog_helper (struct app_context *ctx, int backlog_fd)
redraw_screen (struct app_context *ctx)
{
hard_assert (!ctx->running_backlog_helper);
switch (spawn_helper_child (ctx))
{
case 0:
dup2 (backlog_fd, STDIN_FILENO);
execl ("/bin/sh", "/bin/sh", "-c", get_config_string
(ctx->config.root, "behaviour.backlog_helper"), NULL);
print_error ("%s: %s",
"Failed to launch backlog helper", strerror (errno));
_exit (EXIT_FAILURE);
case -1:
log_global_error (ctx, "#s: #l",
"Failed to launch backlog helper", strerror (errno));
break;
default:
ctx->running_backlog_helper = true;
}
}
input_hide (&ctx->input);

static void
display_backlog (struct app_context *ctx)
{
FILE *backlog = tmpfile ();
if (!backlog)
{
log_global_error (ctx, "#s: #l",
"Failed to create a temporary file", strerror (errno));
return;
}
// If by some circumstance we had the wrong idea
input_on_terminal_resized (&ctx->input);
update_screen_size ();

for (struct buffer_line *line = ctx->current_buffer->lines;
line; line = line->next)
buffer_line_write_to_backlog (ctx, line, backlog);
buffer_print_backlog (ctx, ctx->current_buffer);

rewind (backlog);
set_cloexec (fileno (backlog));
launch_backlog_helper (ctx, fileno (backlog));
fclose (backlog);
input_show (&ctx->input);
}

static void
display_full_log (struct app_context *ctx)
{
char *path = buffer_get_log_path (ctx->current_buffer);
FILE *full_log = fopen (path, "rb");
free (path);

if (!full_log)
{
log_global_error (ctx, "Failed to open log file for #s: #l",
ctx->current_buffer->name, strerror (errno));
return;
}

if (ctx->current_buffer->log_file)
fflush (ctx->current_buffer->log_file);

set_cloexec (fileno (full_log));
launch_backlog_helper (ctx, fileno (full_log));
fclose (full_log);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

static bool
dump_input_to_file (struct app_context *ctx, char *template, struct error **e)
@@ -11324,12 +11353,16 @@ try_dump_input_to_file (struct app_context *ctx)
return NULL;
}

static void
launch_input_editor (struct app_context *ctx)
static bool
on_edit_input (int count, int key, void *user_data)
{
(void) count;
(void) key;
struct app_context *ctx = user_data;

char *filename;
if (!(filename = try_dump_input_to_file (ctx)))
return;
return false;

const char *command;
if (!(command = getenv ("VISUAL"))
@@ -11353,6 +11386,7 @@ launch_input_editor (struct app_context *ctx)
ctx->running_editor = true;
ctx->editor_filename = filename;
}
return true;
}

static void
@@ -11390,162 +11424,222 @@ input_editor_cleanup (struct app_context *ctx)
ctx->running_editor = false;
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

static void
bind_common_keys (struct app_context *ctx)
launch_backlog_helper (struct app_context *ctx, int backlog_fd)
{
struct input *self = &ctx->input;
input_bind_control (self, 'p', "previous-buffer");
input_bind_control (self, 'n', "next-buffer");

// Redefine M-0 through M-9 to switch buffers
for (int i = 0; i <= 9; i++)
input_bind_meta (self, '0' + i, "goto-buffer");

input_bind_meta (self, '\t', "switch-buffer");
input_bind_meta (self, 'm', "insert-attribute");
input_bind_meta (self, 'h', "display-full-log");
input_bind_meta (self, 'e', "edit-input");

if (key_f5)
input_bind (self, key_f5, "previous-buffer");
if (key_f6)
input_bind (self, key_f6, "next-buffer");
if (key_ppage)
input_bind (self, key_ppage, "display-backlog");

if (clear_screen)
input_bind_control (self, 'l', "redraw-screen");

input_bind (self, "\x1b[200~", "start-paste-mode");
hard_assert (!ctx->running_backlog_helper);
switch (spawn_helper_child (ctx))
{
case 0:
dup2 (backlog_fd, STDIN_FILENO);
execl ("/bin/sh", "/bin/sh", "-c", get_config_string
(ctx->config.root, "behaviour.backlog_helper"), NULL);
print_error ("%s: %s",
"Failed to launch backlog helper", strerror (errno));
_exit (EXIT_FAILURE);
case -1:
log_global_error (ctx, "#s: #l",
"Failed to launch backlog helper", strerror (errno));
break;
default:
ctx->running_backlog_helper = true;
}
}

// --- GNU Readline user actions -----------------------------------------------

#ifdef HAVE_READLINE

static int
on_readline_goto_buffer (int count, int key)
static bool
on_display_backlog (int count, int key, void *user_data)
{
(void) count;
(void) key;
struct app_context *ctx = user_data;

struct app_context *ctx = g_ctx;
if (!jump_to_buffer (ctx, UNMETA (key) - '0'))
input_ding (&ctx->input);
return 0;
}
FILE *backlog = tmpfile ();
if (!backlog)
{
log_global_error (ctx, "#s: #l",
"Failed to create a temporary file", strerror (errno));
return false;
}

static int
on_readline_previous_buffer (int count, int key)
{
(void) key;
for (struct buffer_line *line = ctx->current_buffer->lines;
line; line = line->next)
buffer_line_write_to_backlog (ctx, line, backlog);

struct app_context *ctx = g_ctx;
buffer_activate (ctx, buffer_previous (ctx, count));
return 0;
rewind (backlog);
set_cloexec (fileno (backlog));
launch_backlog_helper (ctx, fileno (backlog));
fclose (backlog);
return true;
}

static int
on_readline_next_buffer (int count, int key)
static bool
on_display_full_log (int count, int key, void *user_data)
{
(void) count;
(void) key;
struct app_context *ctx = user_data;

struct app_context *ctx = g_ctx;
buffer_activate (ctx, buffer_next (ctx, count));
return 0;
char *path = buffer_get_log_path (ctx->current_buffer);
FILE *full_log = fopen (path, "rb");
free (path);

if (!full_log)
{
log_global_error (ctx, "Failed to open log file for #s: #l",
ctx->current_buffer->name, strerror (errno));
return false;
}

if (ctx->current_buffer->log_file)
fflush (ctx->current_buffer->log_file);

set_cloexec (fileno (full_log));
launch_backlog_helper (ctx, fileno (full_log));
fclose (full_log);
return true;
}

static int
on_readline_switch_buffer (int count, int key)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

static bool
on_goto_buffer (int count, int key, void *user_data)
{
(void) count;
(void) key;
struct app_context *ctx = user_data;

struct app_context *ctx = g_ctx;
if (ctx->last_buffer)
buffer_activate (ctx, ctx->last_buffer);
else
input_ding (&ctx->input);
return 0;
int n = key - '0';
if (n < 0 || n > 9)
return false;

// There's no buffer zero
if (n == 0)
n = 10;

if (!ctx->last_buffer || buffer_get_index (ctx, ctx->current_buffer) != n)
return buffer_goto (ctx, n);

// Fast switching between two buffers
buffer_activate (ctx, ctx->last_buffer);
return true;
}

static int
on_readline_display_backlog (int count, int key)
static bool
on_previous_buffer (int count, int key, void *user_data)
{
(void) count;
(void) key;

struct app_context *ctx = g_ctx;
display_backlog (ctx);
return 0;
buffer_activate (user_data, buffer_previous (user_data, count));
return true;
}

static int
on_readline_display_full_log (int count, int key)
static bool
on_next_buffer (int count, int key, void *user_data)
{
(void) count;
(void) key;

struct app_context *ctx = g_ctx;
display_full_log (ctx);
return 0;
buffer_activate (user_data, buffer_next (user_data, count));
return true;
}

static int
on_readline_edit_input (int count, int key)
static bool
on_switch_buffer (int count, int key, void *user_data)
{
(void) count;
(void) key;
struct app_context *ctx = user_data;

struct app_context *ctx = g_ctx;
launch_input_editor (ctx);
return 0;
if (!ctx->last_buffer)
return false;
buffer_activate (ctx, ctx->last_buffer);
return true;
}

static int
on_readline_redraw_screen (int count, int key)
static bool
on_redraw_screen (int count, int key, void *user_data)
{
(void) count;
(void) key;

struct app_context *ctx = g_ctx;
redraw_screen (ctx);
return 0;
redraw_screen (user_data);
return true;
}

static int
on_readline_insert_attribute (int count, int key)
static bool
on_insert_attribute (int count, int key, void *user_data)
{
(void) count;
(void) key;

struct app_context *ctx = g_ctx;
struct app_context *ctx = user_data;
ctx->awaiting_mirc_escape = true;
return 0;
return true;
}

static int
on_readline_start_paste_mode (int count, int key)
static bool
on_start_paste_mode (int count, int key, void *user_data)
{
(void) count;
(void) key;

struct app_context *ctx = g_ctx;
struct app_context *ctx = user_data;
ctx->in_bracketed_paste = true;
return 0;
return true;
}

static void
bind_common_keys (struct app_context *ctx)
{
struct input *self = &ctx->input;
#define XX(...) input_add_fn (self, __VA_ARGS__, ctx);
XX ("previous-buffer", "Previous buffer", on_previous_buffer)
XX ("next-buffer", "Next buffer", on_next_buffer)
XX ("goto-buffer", "Go to buffer", on_goto_buffer)
XX ("switch-buffer", "Switch buffer", on_switch_buffer)
XX ("display-backlog", "Show backlog", on_display_backlog)
XX ("display-full-log", "Show full log", on_display_full_log)
XX ("edit-input", "Edit input", on_edit_input)
XX ("redraw-screen", "Redraw screen", on_redraw_screen)
XX ("insert-attribute", "mIRC formatting", on_insert_attribute)
XX ("start-paste-mode", "Bracketed paste", on_start_paste_mode)
#undef XX

input_bind_control (self, 'p', "previous-buffer");
input_bind_control (self, 'n', "next-buffer");

// Redefine M-0 through M-9 to switch buffers
for (int i = 0; i <= 9; i++)
input_bind_meta (self, '0' + i, "goto-buffer");

input_bind_meta (self, '\t', "switch-buffer");
input_bind_meta (self, 'm', "insert-attribute");
input_bind_meta (self, 'h', "display-full-log");
input_bind_meta (self, 'e', "edit-input");

if (key_f5) input_bind (self, key_f5, "previous-buffer");
if (key_f6) input_bind (self, key_f6, "next-buffer");
if (key_ppage) input_bind (self, key_ppage, "display-backlog");

if (clear_screen)
input_bind_control (self, 'l', "redraw-screen");

input_bind (self, "\x1b[200~", "start-paste-mode");
}

// --- GNU Readline user actions -----------------------------------------------

#ifdef HAVE_READLINE

static int
on_readline_return (int count, int key)
{
(void) count;
(void) key;

struct app_context *ctx = g_ctx;

// Let readline pass the line to our input handler
rl_done = 1;

// Hide the line, don't redisplay it
struct app_context *ctx = g_ctx;
input_hide (&ctx->input);
input_restore (&ctx->input);
return 0;
@@ -11602,18 +11696,7 @@ app_readline_init (void)
// our dear user could potentionally rig things up in a way that might
// result in some funny unspecified behaviour

rl_add_defun ("previous-buffer", on_readline_previous_buffer, -1);
rl_add_defun ("next-buffer", on_readline_next_buffer, -1);
rl_add_defun ("goto-buffer", on_readline_goto_buffer, -1);
rl_add_defun ("switch-buffer", on_readline_switch_buffer, -1);
rl_add_defun ("display-backlog", on_readline_display_backlog, -1);
rl_add_defun ("display-full-log", on_readline_display_full_log, -1);
rl_add_defun ("edit-input", on_readline_edit_input, -1);
rl_add_defun ("redraw-screen", on_readline_redraw_screen, -1);
rl_add_defun ("insert-attribute", on_readline_insert_attribute, -1);
rl_add_defun ("start-paste-mode", on_readline_start_paste_mode, -1);
rl_add_defun ("send-line", on_readline_return, -1);

rl_add_defun ("send-line", on_readline_return, -1);
bind_common_keys (ctx);

// Move native history commands
@@ -11636,113 +11719,6 @@ app_readline_init (void)

#ifdef HAVE_EDITLINE

static unsigned char
on_editline_goto_buffer (EditLine *editline, int key)
{
(void) editline;

struct app_context *ctx = g_ctx;
if (!jump_to_buffer (ctx, key - '0'))
return CC_ERROR;
return CC_NORM;
}

static unsigned char
on_editline_previous_buffer (EditLine *editline, int key)
{
(void) editline;
(void) key;

struct app_context *ctx = g_ctx;
buffer_activate (ctx, buffer_previous (ctx, 1));
return CC_NORM;
}

static unsigned char
on_editline_next_buffer (EditLine *editline, int key)
{
(void) editline;
(void) key;

struct app_context *ctx = g_ctx;
buffer_activate (ctx, buffer_next (ctx, 1));
return CC_NORM;
}

static unsigned char
on_editline_switch_buffer (EditLine *editline, int key)
{
(void) editline;
(void) key;

struct app_context *ctx = g_ctx;
if (ctx->last_buffer)
buffer_activate (ctx, ctx->last_buffer);
else
input_ding (&ctx->input);
return CC_NORM;
}

static unsigned char
on_editline_display_backlog (EditLine *editline, int key)
{
(void) editline;
(void) key;

display_backlog (g_ctx);
return CC_NORM;
}

static unsigned char
on_editline_display_full_log (EditLine *editline, int key)
{
(void) editline;
(void) key;

display_full_log (g_ctx);
return CC_NORM;
}

static unsigned char
on_editline_edit_input (EditLine *editline, int key)
{
(void) editline;
(void) key;

launch_input_editor (g_ctx);
return CC_NORM;
}

static unsigned char
on_editline_redraw_screen (EditLine *editline, int key)
{
(void) editline;
(void) key;

redraw_screen (g_ctx);
return CC_NORM;
}

static unsigned char
on_editline_insert_attribute (EditLine *editline, int key)
{
(void) editline;
(void) key;

g_ctx->awaiting_mirc_escape = true;
return CC_NORM;
}

static unsigned char
on_editline_start_paste_mode (EditLine *editline, int key)
{
(void) editline;
(void) key;

g_ctx->in_bracketed_paste = true;
return CC_NORM;
}

static unsigned char
on_editline_complete (EditLine *editline, int key)
{
@@ -11831,29 +11807,11 @@ on_editline_return (EditLine *editline, int key)
static void
app_editline_init (struct input *self)
{
#define XX(name, help, fn) { name, help, on_editline_ ## fn },

// el_set() leaks memory in 20150325 and other versions, we need wchar_t
static const struct { const wchar_t *name; const wchar_t *help;
unsigned char (*func) (EditLine *, int); } x[] =
{
XX( L"goto-buffer", L"Go to buffer", goto_buffer )
XX( L"previous-buffer", L"Previous buffer", previous_buffer )
XX( L"next-buffer", L"Next buffer", next_buffer )
XX( L"switch-buffer", L"Switch buffer", switch_buffer )
XX( L"display-backlog", L"Show backlog", display_backlog )
XX( L"display-full-log", L"Show full log", display_full_log )
XX( L"edit-input", L"Edit input", edit_input )
XX( L"redraw-screen", L"Redraw screen", redraw_screen )
XX( L"insert-attribute", L"mIRC formatting", insert_attribute )
XX( L"start-paste-mode", L"Bracketed paste", start_paste_mode )
XX( L"send-line", L"Send line", return )
XX( L"complete", L"Complete word", complete )
};
for (size_t i = 0; i < N_ELEMENTS (x); i++)
el_wset (self->editline, EL_ADDFN, x[i].name, x[i].help, x[i].func);

#undef XX
el_wset (self->editline, EL_ADDFN,
L"send-line", L"Send line", on_editline_return);
el_wset (self->editline, EL_ADDFN,
L"complete", L"Complete word", on_editline_complete);

bind_common_keys (g_ctx);


Loading…
Cancel
Save