degesch: add backlog/scrollback functionality
Finally! I went with possibly the simplest solution, which is to run less, instead of badly reimplementing its functionality.
This commit is contained in:
parent
27f185e8aa
commit
a28528d260
221
degesch.c
221
degesch.c
|
@ -1364,6 +1364,8 @@ struct app_context
|
||||||
bool awaiting_mirc_escape; ///< Awaiting a mIRC attribute escape
|
bool awaiting_mirc_escape; ///< Awaiting a mIRC attribute escape
|
||||||
char char_buf[MB_LEN_MAX + 1]; ///< Buffered multibyte char
|
char char_buf[MB_LEN_MAX + 1]; ///< Buffered multibyte char
|
||||||
size_t char_buf_len; ///< How much of an MB char is buffered
|
size_t char_buf_len; ///< How much of an MB char is buffered
|
||||||
|
|
||||||
|
bool running_backlog_helper; ///< Running a backlog helper
|
||||||
}
|
}
|
||||||
*g_ctx;
|
*g_ctx;
|
||||||
|
|
||||||
|
@ -1640,6 +1642,10 @@ static struct config_schema g_config_behaviour[] =
|
||||||
.type = CONFIG_ITEM_BOOLEAN,
|
.type = CONFIG_ITEM_BOOLEAN,
|
||||||
.default_ = "off",
|
.default_ = "off",
|
||||||
.on_change = on_config_logging_change },
|
.on_change = on_config_logging_change },
|
||||||
|
{ .name = "backlog_helper",
|
||||||
|
.comment = "Shell command to display a buffer's history",
|
||||||
|
.type = CONFIG_ITEM_STRING,
|
||||||
|
.default_ = "\"LESSSECURE=1 less -M -R +G\"" },
|
||||||
{ .name = "save_on_quit",
|
{ .name = "save_on_quit",
|
||||||
.comment = "Save configuration before quitting",
|
.comment = "Save configuration before quitting",
|
||||||
.type = CONFIG_ITEM_BOOLEAN,
|
.type = CONFIG_ITEM_BOOLEAN,
|
||||||
|
@ -2839,11 +2845,18 @@ log_formatter (struct app_context *ctx,
|
||||||
&& buffer == ctx->current_buffer->server->buffer))
|
&& buffer == ctx->current_buffer->server->buffer))
|
||||||
can_leak = true;
|
can_leak = true;
|
||||||
|
|
||||||
if (buffer == ctx->current_buffer)
|
bool displayed = true;
|
||||||
|
if (ctx->running_backlog_helper)
|
||||||
|
// Another process is using the terminal
|
||||||
|
displayed = false;
|
||||||
|
else if (buffer == ctx->current_buffer)
|
||||||
buffer_line_display (ctx, line, false);
|
buffer_line_display (ctx, line, false);
|
||||||
else if (!ctx->isolate_buffers && can_leak)
|
else if (!ctx->isolate_buffers && can_leak)
|
||||||
buffer_line_display (ctx, line, true);
|
buffer_line_display (ctx, line, true);
|
||||||
else
|
else
|
||||||
|
displayed = false;
|
||||||
|
|
||||||
|
if (!displayed)
|
||||||
{
|
{
|
||||||
buffer->unseen_messages_count++;
|
buffer->unseen_messages_count++;
|
||||||
if (flags & BUFFER_LINE_HIGHLIGHT)
|
if (flags & BUFFER_LINE_HIGHLIGHT)
|
||||||
|
@ -2957,6 +2970,8 @@ buffer_open_log_file (struct app_context *ctx, struct buffer *buffer)
|
||||||
if (!(buffer->log_file = fopen (path.str, "ab")))
|
if (!(buffer->log_file = fopen (path.str, "ab")))
|
||||||
log_global_error (ctx, "Couldn't open log file `#s': #s",
|
log_global_error (ctx, "Couldn't open log file `#s': #s",
|
||||||
path.str, strerror (errno));
|
path.str, strerror (errno));
|
||||||
|
else
|
||||||
|
set_cloexec (fileno (buffer->log_file));
|
||||||
str_free (&path);
|
str_free (&path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8807,6 +8822,38 @@ make_completions (struct app_context *ctx, char *line, int start, int end)
|
||||||
|
|
||||||
// --- Common code for user actions --------------------------------------------
|
// --- Common code for user actions --------------------------------------------
|
||||||
|
|
||||||
|
static void
|
||||||
|
suspend_terminal (struct app_context *ctx)
|
||||||
|
{
|
||||||
|
#ifdef HAVE_READLINE
|
||||||
|
rl_deprep_terminal ();
|
||||||
|
#elif defined (HAVE_EDITLINE)
|
||||||
|
el_set (ctx->input.editline, EL_PREP_TERM, 0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
input_hide (&ctx->input);
|
||||||
|
poller_fd_reset (&ctx->tty_event);
|
||||||
|
// TODO: also disable the date change timer
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
resume_terminal (struct app_context *ctx)
|
||||||
|
{
|
||||||
|
#ifdef HAVE_READLINE
|
||||||
|
rl_prep_terminal (true);
|
||||||
|
#elif defined (HAVE_EDITLINE)
|
||||||
|
el_set (ctx->input.editline, EL_PREP_TERM, 1);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// In theory we could just print all unseen messages but this is safer
|
||||||
|
buffer_print_backlog (ctx, ctx->current_buffer);
|
||||||
|
// Now it's safe to process any user input
|
||||||
|
poller_fd_set (&ctx->tty_event, POLLIN);
|
||||||
|
input_show (&ctx->input);
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
redraw_screen (struct app_context *ctx)
|
redraw_screen (struct app_context *ctx)
|
||||||
{
|
{
|
||||||
|
@ -8849,6 +8896,59 @@ await_mirc_escape (struct app_context *ctx)
|
||||||
ctx->char_buf_len = 0;
|
ctx->char_buf_len = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
exec_backlog_helper (const char *command, FILE *backlog)
|
||||||
|
{
|
||||||
|
dup2 (fileno (backlog), STDIN_FILENO);
|
||||||
|
|
||||||
|
// Put the child into a new foreground process group
|
||||||
|
hard_assert (setpgid (0, 0)!= -1);
|
||||||
|
hard_assert (tcsetpgrp (STDOUT_FILENO, getpgid (0)) != -1);
|
||||||
|
|
||||||
|
execl ("/bin/sh", "/bin/sh", "-c", command, NULL);
|
||||||
|
print_error ("%s: %s", "Failed to launch backlog helper", strerror (errno));
|
||||||
|
_exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
display_backlog (struct app_context *ctx)
|
||||||
|
{
|
||||||
|
hard_assert (!ctx->running_backlog_helper);
|
||||||
|
|
||||||
|
FILE *backlog = tmpfile ();
|
||||||
|
set_cloexec (fileno (backlog));
|
||||||
|
|
||||||
|
// TODO: retrieve the lines with formatting
|
||||||
|
for (struct buffer_line *line = ctx->current_buffer->lines;
|
||||||
|
line; line = line->next)
|
||||||
|
buffer_line_write_to_log (ctx, line, backlog);
|
||||||
|
|
||||||
|
rewind (backlog);
|
||||||
|
suspend_terminal (ctx);
|
||||||
|
|
||||||
|
pid_t child = fork ();
|
||||||
|
if (child == 0)
|
||||||
|
exec_backlog_helper (get_config_string
|
||||||
|
(ctx->config.root, "behaviour.backlog_helper"), backlog);
|
||||||
|
|
||||||
|
if (child == -1)
|
||||||
|
{
|
||||||
|
int saved_errno = errno;
|
||||||
|
resume_terminal (ctx);
|
||||||
|
log_global_error (ctx, "#s: #s",
|
||||||
|
"Failed to launch backlog helper", strerror (saved_errno));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Make sure the child has its own process group
|
||||||
|
(void) setpgid (child, child);
|
||||||
|
|
||||||
|
ctx->running_backlog_helper = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose (backlog);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
bind_common_keys (struct app_context *ctx)
|
bind_common_keys (struct app_context *ctx)
|
||||||
{
|
{
|
||||||
|
@ -8866,6 +8966,8 @@ bind_common_keys (struct app_context *ctx)
|
||||||
input_bind (self, key_f5, "previous-buffer");
|
input_bind (self, key_f5, "previous-buffer");
|
||||||
if (key_f6)
|
if (key_f6)
|
||||||
input_bind (self, key_f6, "next-buffer");
|
input_bind (self, key_f6, "next-buffer");
|
||||||
|
if (key_ppage)
|
||||||
|
input_bind (self, key_ppage, "display-backlog");
|
||||||
|
|
||||||
if (clear_screen)
|
if (clear_screen)
|
||||||
input_bind_control (self, 'l', "redraw-screen");
|
input_bind_control (self, 'l', "redraw-screen");
|
||||||
|
@ -8906,6 +9008,17 @@ on_readline_next_buffer (int count, int key)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
on_readline_display_backlog (int count, int key)
|
||||||
|
{
|
||||||
|
(void) count;
|
||||||
|
(void) key;
|
||||||
|
|
||||||
|
struct app_context *ctx = g_ctx;
|
||||||
|
display_backlog (ctx);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
on_readline_redraw_screen (int count, int key)
|
on_readline_redraw_screen (int count, int key)
|
||||||
{
|
{
|
||||||
|
@ -8998,6 +9111,7 @@ app_readline_init (void)
|
||||||
rl_add_defun ("previous-buffer", on_readline_previous_buffer, -1);
|
rl_add_defun ("previous-buffer", on_readline_previous_buffer, -1);
|
||||||
rl_add_defun ("next-buffer", on_readline_next_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 ("goto-buffer", on_readline_goto_buffer, -1);
|
||||||
|
rl_add_defun ("display-backlog", on_readline_display_backlog, -1);
|
||||||
rl_add_defun ("redraw-screen", on_readline_redraw_screen, -1);
|
rl_add_defun ("redraw-screen", on_readline_redraw_screen, -1);
|
||||||
rl_add_defun ("insert-attribute", on_readline_insert_attribute, -1);
|
rl_add_defun ("insert-attribute", on_readline_insert_attribute, -1);
|
||||||
rl_add_defun ("send-line", on_readline_return, -1);
|
rl_add_defun ("send-line", on_readline_return, -1);
|
||||||
|
@ -9057,6 +9171,15 @@ on_editline_next_buffer (EditLine *editline, int key)
|
||||||
return CC_NORM;
|
return CC_NORM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static unsigned char
|
||||||
|
on_editline_display_backlog (EditLine *editline, int key)
|
||||||
|
{
|
||||||
|
(void) editline;
|
||||||
|
(void) key;
|
||||||
|
|
||||||
|
display_backlog (g_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
static unsigned char
|
static unsigned char
|
||||||
on_editline_redraw_screen (EditLine *editline, int key)
|
on_editline_redraw_screen (EditLine *editline, int key)
|
||||||
{
|
{
|
||||||
|
@ -9173,6 +9296,7 @@ app_editline_init (struct input *self)
|
||||||
{ "goto-buffer", "Go to buffer", on_editline_goto_buffer },
|
{ "goto-buffer", "Go to buffer", on_editline_goto_buffer },
|
||||||
{ "previous-buffer", "Previous buffer", on_editline_previous_buffer },
|
{ "previous-buffer", "Previous buffer", on_editline_previous_buffer },
|
||||||
{ "next-buffer", "Next buffer", on_editline_next_buffer },
|
{ "next-buffer", "Next buffer", on_editline_next_buffer },
|
||||||
|
{ "display-backlog", "Display backlog", on_editline_display_backlog },
|
||||||
{ "redraw-screen", "Redraw screen", on_editline_redraw_screen },
|
{ "redraw-screen", "Redraw screen", on_editline_redraw_screen },
|
||||||
{ "insert-attribute", "mIRC formatting", on_editline_insert_attribute },
|
{ "insert-attribute", "mIRC formatting", on_editline_insert_attribute },
|
||||||
{ "send-line", "Send line", on_editline_return },
|
{ "send-line", "Send line", on_editline_return },
|
||||||
|
@ -9376,29 +9500,34 @@ static volatile sig_atomic_t g_termination_requested;
|
||||||
static volatile sig_atomic_t g_winch_received;
|
static volatile sig_atomic_t g_winch_received;
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sigterm_handler (int signum)
|
postpone_signal_handling (char id)
|
||||||
{
|
{
|
||||||
(void) signum;
|
|
||||||
|
|
||||||
g_termination_requested = true;
|
|
||||||
|
|
||||||
int original_errno = errno;
|
int original_errno = errno;
|
||||||
if (write (g_signal_pipe[1], "t", 1) == -1)
|
if (write (g_signal_pipe[1], &id, 1) == -1)
|
||||||
soft_assert (errno == EAGAIN);
|
soft_assert (errno == EAGAIN);
|
||||||
errno = original_errno;
|
errno = original_errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
sigwinch_handler (int signum)
|
signal_superhandler (int signum)
|
||||||
{
|
{
|
||||||
(void) signum;
|
switch (signum)
|
||||||
|
{
|
||||||
|
case SIGWINCH:
|
||||||
g_winch_received = true;
|
g_winch_received = true;
|
||||||
|
postpone_signal_handling ('w');
|
||||||
int original_errno = errno;
|
break;
|
||||||
if (write (g_signal_pipe[1], "w", 1) == -1)
|
case SIGINT:
|
||||||
soft_assert (errno == EAGAIN);
|
case SIGTERM:
|
||||||
errno = original_errno;
|
g_termination_requested = true;
|
||||||
|
postpone_signal_handling ('t');
|
||||||
|
break;
|
||||||
|
case SIGCHLD:
|
||||||
|
postpone_signal_handling ('c');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
hard_assert (!"unhandled signal");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -9418,28 +9547,76 @@ setup_signal_handlers (void)
|
||||||
|
|
||||||
signal (SIGPIPE, SIG_IGN);
|
signal (SIGPIPE, SIG_IGN);
|
||||||
|
|
||||||
|
// So that we can write to the terminal while we're running a backlog
|
||||||
|
// helper. This is also inherited by the child so that it doesn't stop
|
||||||
|
// when it calls tcsetpgrp().
|
||||||
|
signal (SIGTTOU, SIG_IGN);
|
||||||
|
|
||||||
struct sigaction sa;
|
struct sigaction sa;
|
||||||
sa.sa_flags = SA_RESTART;
|
sa.sa_flags = SA_RESTART;
|
||||||
sa.sa_handler = sigwinch_handler;
|
sa.sa_handler = signal_superhandler;
|
||||||
sigemptyset (&sa.sa_mask);
|
sigemptyset (&sa.sa_mask);
|
||||||
|
|
||||||
if (sigaction (SIGWINCH, &sa, NULL) == -1)
|
if (sigaction (SIGWINCH, &sa, NULL) == -1
|
||||||
exit_fatal ("sigaction: %s", strerror (errno));
|
|| sigaction (SIGINT, &sa, NULL) == -1
|
||||||
|
|| sigaction (SIGTERM, &sa, NULL) == -1
|
||||||
sa.sa_handler = sigterm_handler;
|
|| sigaction (SIGCHLD, &sa, NULL) == -1)
|
||||||
if (sigaction (SIGINT, &sa, NULL) == -1
|
|
||||||
|| sigaction (SIGTERM, &sa, NULL) == -1)
|
|
||||||
exit_fatal ("sigaction: %s", strerror (errno));
|
exit_fatal ("sigaction: %s", strerror (errno));
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- I/O event handlers ------------------------------------------------------
|
// --- I/O event handlers ------------------------------------------------------
|
||||||
|
|
||||||
|
static bool
|
||||||
|
try_reap_child (struct app_context *ctx)
|
||||||
|
{
|
||||||
|
int status;
|
||||||
|
pid_t zombie = waitpid (-1, &status, WNOHANG | WUNTRACED);
|
||||||
|
|
||||||
|
if (zombie == -1)
|
||||||
|
{
|
||||||
|
if (errno == ECHILD) return false;
|
||||||
|
if (errno == EINTR) return true;
|
||||||
|
exit_fatal ("%s: %s", "waitpid", strerror (errno));
|
||||||
|
}
|
||||||
|
if (!zombie)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!ctx->running_backlog_helper)
|
||||||
|
{
|
||||||
|
print_debug ("an unknown child has died");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (WIFSTOPPED (status))
|
||||||
|
{
|
||||||
|
// We could also send SIGCONT but what's the point
|
||||||
|
kill (-zombie, SIGKILL);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->running_backlog_helper = false;
|
||||||
|
hard_assert (tcsetpgrp (STDOUT_FILENO, getpgid (0)) != -1);
|
||||||
|
resume_terminal (ctx);
|
||||||
|
|
||||||
|
if (WIFSIGNALED (status))
|
||||||
|
log_global_error (ctx,
|
||||||
|
"Child died from signal #d", WTERMSIG (status));
|
||||||
|
else if (WIFEXITED (status) && WEXITSTATUS (status) != 0)
|
||||||
|
log_global_error (ctx,
|
||||||
|
"Child returned status #d", WEXITSTATUS (status));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
on_signal_pipe_readable (const struct pollfd *fd, struct app_context *ctx)
|
on_signal_pipe_readable (const struct pollfd *fd, struct app_context *ctx)
|
||||||
{
|
{
|
||||||
char dummy;
|
char dummy;
|
||||||
(void) read (fd->fd, &dummy, 1);
|
(void) read (fd->fd, &dummy, 1);
|
||||||
|
|
||||||
|
// Reap all dead children (since the signal pipe may overflow etc. we run
|
||||||
|
// waitpid() in a loop to return all the zombies it knows about).
|
||||||
|
while (try_reap_child (ctx))
|
||||||
|
;
|
||||||
|
|
||||||
if (g_termination_requested && !ctx->quitting)
|
if (g_termination_requested && !ctx->quitting)
|
||||||
// TODO: this way we don't send a QUIT message but just close the
|
// TODO: this way we don't send a QUIT message but just close the
|
||||||
// connection from our side and wait for a full close.
|
// connection from our side and wait for a full close.
|
||||||
|
|
Loading…
Reference in New Issue