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
223
degesch.c
223
degesch.c
@ -1364,6 +1364,8 @@ struct app_context
|
||||
bool awaiting_mirc_escape; ///< Awaiting a mIRC attribute escape
|
||||
char char_buf[MB_LEN_MAX + 1]; ///< Buffered multibyte char
|
||||
size_t char_buf_len; ///< How much of an MB char is buffered
|
||||
|
||||
bool running_backlog_helper; ///< Running a backlog helper
|
||||
}
|
||||
*g_ctx;
|
||||
|
||||
@ -1640,6 +1642,10 @@ static struct config_schema g_config_behaviour[] =
|
||||
.type = CONFIG_ITEM_BOOLEAN,
|
||||
.default_ = "off",
|
||||
.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",
|
||||
.comment = "Save configuration before quitting",
|
||||
.type = CONFIG_ITEM_BOOLEAN,
|
||||
@ -2839,11 +2845,18 @@ log_formatter (struct app_context *ctx,
|
||||
&& buffer == ctx->current_buffer->server->buffer))
|
||||
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);
|
||||
else if (!ctx->isolate_buffers && can_leak)
|
||||
buffer_line_display (ctx, line, true);
|
||||
else
|
||||
displayed = false;
|
||||
|
||||
if (!displayed)
|
||||
{
|
||||
buffer->unseen_messages_count++;
|
||||
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")))
|
||||
log_global_error (ctx, "Couldn't open log file `#s': #s",
|
||||
path.str, strerror (errno));
|
||||
else
|
||||
set_cloexec (fileno (buffer->log_file));
|
||||
str_free (&path);
|
||||
}
|
||||
|
||||
@ -8807,6 +8822,38 @@ make_completions (struct app_context *ctx, char *line, int start, int end)
|
||||
|
||||
// --- 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
|
||||
redraw_screen (struct app_context *ctx)
|
||||
{
|
||||
@ -8849,6 +8896,59 @@ await_mirc_escape (struct app_context *ctx)
|
||||
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
|
||||
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");
|
||||
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");
|
||||
@ -8906,6 +9008,17 @@ on_readline_next_buffer (int count, int key)
|
||||
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
|
||||
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 ("next-buffer", on_readline_next_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 ("insert-attribute", on_readline_insert_attribute, -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;
|
||||
}
|
||||
|
||||
static unsigned char
|
||||
on_editline_display_backlog (EditLine *editline, int key)
|
||||
{
|
||||
(void) editline;
|
||||
(void) key;
|
||||
|
||||
display_backlog (g_ctx);
|
||||
}
|
||||
|
||||
static unsigned char
|
||||
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 },
|
||||
{ "previous-buffer", "Previous buffer", on_editline_previous_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 },
|
||||
{ "insert-attribute", "mIRC formatting", on_editline_insert_attribute },
|
||||
{ "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 void
|
||||
sigterm_handler (int signum)
|
||||
postpone_signal_handling (char id)
|
||||
{
|
||||
(void) signum;
|
||||
|
||||
g_termination_requested = true;
|
||||
|
||||
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);
|
||||
errno = original_errno;
|
||||
}
|
||||
|
||||
static void
|
||||
sigwinch_handler (int signum)
|
||||
signal_superhandler (int signum)
|
||||
{
|
||||
(void) signum;
|
||||
|
||||
g_winch_received = true;
|
||||
|
||||
int original_errno = errno;
|
||||
if (write (g_signal_pipe[1], "w", 1) == -1)
|
||||
soft_assert (errno == EAGAIN);
|
||||
errno = original_errno;
|
||||
switch (signum)
|
||||
{
|
||||
case SIGWINCH:
|
||||
g_winch_received = true;
|
||||
postpone_signal_handling ('w');
|
||||
break;
|
||||
case SIGINT:
|
||||
case SIGTERM:
|
||||
g_termination_requested = true;
|
||||
postpone_signal_handling ('t');
|
||||
break;
|
||||
case SIGCHLD:
|
||||
postpone_signal_handling ('c');
|
||||
break;
|
||||
default:
|
||||
hard_assert (!"unhandled signal");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
@ -9418,28 +9547,76 @@ setup_signal_handlers (void)
|
||||
|
||||
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;
|
||||
sa.sa_flags = SA_RESTART;
|
||||
sa.sa_handler = sigwinch_handler;
|
||||
sa.sa_handler = signal_superhandler;
|
||||
sigemptyset (&sa.sa_mask);
|
||||
|
||||
if (sigaction (SIGWINCH, &sa, NULL) == -1)
|
||||
exit_fatal ("sigaction: %s", strerror (errno));
|
||||
|
||||
sa.sa_handler = sigterm_handler;
|
||||
if (sigaction (SIGINT, &sa, NULL) == -1
|
||||
|| sigaction (SIGTERM, &sa, NULL) == -1)
|
||||
if (sigaction (SIGWINCH, &sa, NULL) == -1
|
||||
|| sigaction (SIGINT, &sa, NULL) == -1
|
||||
|| sigaction (SIGTERM, &sa, NULL) == -1
|
||||
|| sigaction (SIGCHLD, &sa, NULL) == -1)
|
||||
exit_fatal ("sigaction: %s", strerror (errno));
|
||||
}
|
||||
|
||||
// --- 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
|
||||
on_signal_pipe_readable (const struct pollfd *fd, struct app_context *ctx)
|
||||
{
|
||||
char dummy;
|
||||
(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)
|
||||
// TODO: this way we don't send a QUIT message but just close the
|
||||
// connection from our side and wait for a full close.
|
||||
|
Loading…
Reference in New Issue
Block a user