2015-04-12 04:52:39 +02:00
|
|
|
/*
|
2021-08-06 16:12:15 +02:00
|
|
|
* xC.c: a terminal-based IRC client
|
2015-04-12 04:52:39 +02:00
|
|
|
*
|
2024-07-28 07:06:13 +02:00
|
|
|
* Copyright (c) 2015 - 2024, Přemysl Eric Janouch <p@janouch.name>
|
2015-04-12 04:52:39 +02:00
|
|
|
*
|
|
|
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
2018-06-21 22:42:05 +02:00
|
|
|
* purpose with or without fee is hereby granted.
|
2015-04-12 04:52:39 +02:00
|
|
|
*
|
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
|
|
|
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
|
|
|
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
|
|
|
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
|
2015-04-29 21:25:57 +02:00
|
|
|
// A table of all attributes we use for output
|
2020-09-20 13:24:02 +02:00
|
|
|
#define ATTR_TABLE(XX) \
|
|
|
|
|
XX( PROMPT, prompt, Terminal attrs for the prompt ) \
|
|
|
|
|
XX( DATE_CHANGE, date_change, Terminal attrs for date change ) \
|
|
|
|
|
XX( READ_MARKER, read_marker, Terminal attrs for the read marker ) \
|
|
|
|
|
XX( WARNING, warning, Terminal attrs for warnings ) \
|
|
|
|
|
XX( ERROR, error, Terminal attrs for errors ) \
|
|
|
|
|
XX( EXTERNAL, external, Terminal attrs for external lines ) \
|
|
|
|
|
XX( TIMESTAMP, timestamp, Terminal attrs for timestamps ) \
|
|
|
|
|
XX( HIGHLIGHT, highlight, Terminal attrs for highlights ) \
|
|
|
|
|
XX( ACTION, action, Terminal attrs for user actions ) \
|
|
|
|
|
XX( USERHOST, userhost, Terminal attrs for user@host ) \
|
|
|
|
|
XX( JOIN, join, Terminal attrs for joins ) \
|
|
|
|
|
XX( PART, part, Terminal attrs for parts )
|
2015-04-12 04:52:39 +02:00
|
|
|
|
2015-04-29 20:56:26 +02:00
|
|
|
enum
|
|
|
|
|
{
|
2022-09-10 15:55:13 +02:00
|
|
|
ATTR_RESET,
|
2015-04-29 20:56:26 +02:00
|
|
|
#define XX(x, y, z) ATTR_ ## x,
|
|
|
|
|
ATTR_TABLE (XX)
|
|
|
|
|
#undef XX
|
|
|
|
|
ATTR_COUNT
|
|
|
|
|
};
|
|
|
|
|
|
2015-04-12 04:52:39 +02:00
|
|
|
// User data for logger functions to enable formatted logging
|
2015-04-29 20:56:26 +02:00
|
|
|
#define print_fatal_data ((void *) ATTR_ERROR)
|
|
|
|
|
#define print_error_data ((void *) ATTR_ERROR)
|
|
|
|
|
#define print_warning_data ((void *) ATTR_WARNING)
|
2015-04-12 04:52:39 +02:00
|
|
|
|
|
|
|
|
#include "config.h"
|
2021-08-06 16:12:15 +02:00
|
|
|
#define PROGRAM_NAME "xC"
|
2015-04-12 04:52:39 +02:00
|
|
|
|
2022-09-10 15:55:13 +02:00
|
|
|
// fmemopen
|
2023-07-07 09:46:04 +02:00
|
|
|
#define _POSIX_C_SOURCE 200809L
|
2023-07-04 06:29:18 +02:00
|
|
|
#define _XOPEN_SOURCE 700
|
2022-09-10 15:55:13 +02:00
|
|
|
|
2015-04-12 04:52:39 +02:00
|
|
|
#include "common.c"
|
2021-08-06 16:12:15 +02:00
|
|
|
#include "xD-replies.c"
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
#include "xC-proto.c"
|
2015-04-12 04:52:39 +02:00
|
|
|
|
2021-06-15 04:35:41 +02:00
|
|
|
#include <math.h>
|
2015-04-12 04:52:39 +02:00
|
|
|
#include <langinfo.h>
|
|
|
|
|
#include <locale.h>
|
|
|
|
|
#include <pwd.h>
|
2015-04-27 01:36:33 +02:00
|
|
|
#include <sys/utsname.h>
|
2015-05-07 05:19:13 +02:00
|
|
|
#include <wchar.h>
|
2015-04-12 04:52:39 +02:00
|
|
|
|
2015-05-05 03:42:40 +02:00
|
|
|
#include <termios.h>
|
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
|
|
2015-04-12 04:52:39 +02:00
|
|
|
#include <curses.h>
|
|
|
|
|
#include <term.h>
|
2015-04-13 00:06:08 +02:00
|
|
|
|
|
|
|
|
// Literally cancer
|
|
|
|
|
#undef lines
|
|
|
|
|
#undef columns
|
|
|
|
|
|
2016-03-06 17:59:45 +01:00
|
|
|
#include <ffi.h>
|
|
|
|
|
|
2015-11-19 15:45:32 +01:00
|
|
|
#ifdef HAVE_LUA
|
|
|
|
|
#include <lua.h>
|
|
|
|
|
#include <lualib.h>
|
|
|
|
|
#include <lauxlib.h>
|
|
|
|
|
#endif // HAVE_LUA
|
|
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
// --- Terminal information ----------------------------------------------------
|
|
|
|
|
|
|
|
|
|
static struct
|
|
|
|
|
{
|
|
|
|
|
bool initialized; ///< Terminal is available
|
|
|
|
|
bool stdout_is_tty; ///< `stdout' is a terminal
|
|
|
|
|
bool stderr_is_tty; ///< `stderr' is a terminal
|
|
|
|
|
|
|
|
|
|
struct termios termios; ///< Terminal attributes
|
|
|
|
|
char *color_set_fg[256]; ///< Codes to set the foreground colour
|
|
|
|
|
char *color_set_bg[256]; ///< Codes to set the background colour
|
|
|
|
|
|
|
|
|
|
int lines; ///< Number of lines
|
|
|
|
|
int columns; ///< Number of columns
|
|
|
|
|
}
|
|
|
|
|
g_terminal;
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
update_screen_size (void)
|
|
|
|
|
{
|
|
|
|
|
#ifdef TIOCGWINSZ
|
|
|
|
|
if (!g_terminal.stdout_is_tty)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
struct winsize size;
|
|
|
|
|
if (!ioctl (STDOUT_FILENO, TIOCGWINSZ, (char *) &size))
|
|
|
|
|
{
|
|
|
|
|
char *row = getenv ("LINES");
|
|
|
|
|
char *col = getenv ("COLUMNS");
|
|
|
|
|
unsigned long tmp;
|
|
|
|
|
g_terminal.lines =
|
|
|
|
|
(row && xstrtoul (&tmp, row, 10)) ? tmp : size.ws_row;
|
|
|
|
|
g_terminal.columns =
|
|
|
|
|
(col && xstrtoul (&tmp, col, 10)) ? tmp : size.ws_col;
|
|
|
|
|
}
|
|
|
|
|
#endif // TIOCGWINSZ
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
init_terminal (void)
|
|
|
|
|
{
|
|
|
|
|
int tty_fd = -1;
|
|
|
|
|
if ((g_terminal.stderr_is_tty = isatty (STDERR_FILENO)))
|
|
|
|
|
tty_fd = STDERR_FILENO;
|
|
|
|
|
if ((g_terminal.stdout_is_tty = isatty (STDOUT_FILENO)))
|
|
|
|
|
tty_fd = STDOUT_FILENO;
|
|
|
|
|
|
|
|
|
|
int err;
|
|
|
|
|
if (tty_fd == -1 || setupterm (NULL, tty_fd, &err) == ERR)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// Make sure all terminal features used by us are supported
|
|
|
|
|
if (!set_a_foreground || !set_a_background
|
|
|
|
|
|| !enter_bold_mode || !exit_attribute_mode
|
|
|
|
|
|| tcgetattr (tty_fd, &g_terminal.termios))
|
|
|
|
|
{
|
|
|
|
|
del_curterm (cur_term);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Make sure newlines are output correctly
|
|
|
|
|
g_terminal.termios.c_oflag |= ONLCR;
|
|
|
|
|
(void) tcsetattr (tty_fd, TCSADRAIN, &g_terminal.termios);
|
|
|
|
|
|
|
|
|
|
g_terminal.lines = tigetnum ("lines");
|
|
|
|
|
g_terminal.columns = tigetnum ("cols");
|
|
|
|
|
update_screen_size ();
|
|
|
|
|
|
|
|
|
|
int max = MIN (256, max_colors);
|
|
|
|
|
for (int i = 0; i < max; i++)
|
|
|
|
|
{
|
|
|
|
|
g_terminal.color_set_fg[i] = xstrdup (tparm (set_a_foreground,
|
|
|
|
|
i, 0, 0, 0, 0, 0, 0, 0, 0));
|
|
|
|
|
g_terminal.color_set_bg[i] = xstrdup (tparm (set_a_background,
|
|
|
|
|
i, 0, 0, 0, 0, 0, 0, 0, 0));
|
|
|
|
|
}
|
|
|
|
|
return g_terminal.initialized = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
free_terminal (void)
|
|
|
|
|
{
|
|
|
|
|
if (!g_terminal.initialized)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < 256; i++)
|
|
|
|
|
{
|
|
|
|
|
free (g_terminal.color_set_fg[i]);
|
|
|
|
|
free (g_terminal.color_set_bg[i]);
|
|
|
|
|
}
|
|
|
|
|
del_curterm (cur_term);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- User interface ----------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
// I'm not sure which one of these backends is worse: whether it's GNU Readline
|
|
|
|
|
// or BSD Editline. They both have their own annoying problems. We use lots
|
|
|
|
|
// of hacks to get the results we want and need.
|
|
|
|
|
//
|
|
|
|
|
// The abstraction is a necessary evil. It's still not 100%, though.
|
|
|
|
|
|
|
|
|
|
/// Some arbitrary limit for the history
|
2015-05-06 22:20:02 +02:00
|
|
|
#define HISTORY_LIMIT 10000
|
|
|
|
|
|
|
|
|
|
/// Characters that separate words
|
|
|
|
|
#define WORD_BREAKING_CHARS " \f\n\r\t\v"
|
|
|
|
|
|
2015-05-05 03:23:53 +02:00
|
|
|
struct input
|
|
|
|
|
{
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input_vtable *vtable; ///< Virtual methods
|
2016-03-10 00:06:28 +01:00
|
|
|
void (*add_functions) (void *); ///< Define functions for binding
|
2016-03-07 01:08:52 +01:00
|
|
|
void *user_data; ///< User data for callbacks
|
2015-05-05 03:23:53 +02:00
|
|
|
};
|
|
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
typedef void *input_buffer_t; ///< Pointer alias for input buffers
|
2015-05-05 03:23:53 +02:00
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
/// Named function that can be bound to a sequence of characters
|
|
|
|
|
typedef bool (*input_fn) (int count, int key, void *user_data);
|
|
|
|
|
|
|
|
|
|
// A little bit better than tons of forwarder functions in our case
|
|
|
|
|
#define CALL(self, name) ((self)->vtable->name ((self)))
|
|
|
|
|
#define CALL_(self, name, ...) ((self)->vtable->name ((self), __VA_ARGS__))
|
|
|
|
|
|
|
|
|
|
struct input_vtable
|
2015-05-05 03:23:53 +02:00
|
|
|
{
|
2016-03-07 01:08:52 +01:00
|
|
|
/// Start the interface under the given program name
|
|
|
|
|
void (*start) (void *input, const char *program_name);
|
|
|
|
|
/// Stop the interface
|
|
|
|
|
void (*stop) (void *input);
|
|
|
|
|
/// Prepare or unprepare terminal for our needs
|
|
|
|
|
void (*prepare) (void *input, bool enabled);
|
|
|
|
|
/// Destroy the object
|
|
|
|
|
void (*destroy) (void *input);
|
|
|
|
|
|
|
|
|
|
/// Hide the prompt if shown
|
|
|
|
|
void (*hide) (void *input);
|
|
|
|
|
/// Show the prompt if hidden
|
|
|
|
|
void (*show) (void *input);
|
2016-03-07 23:15:57 +01:00
|
|
|
/// Retrieve current prompt string
|
2016-03-07 01:08:52 +01:00
|
|
|
const char *(*get_prompt) (void *input);
|
|
|
|
|
/// Change the prompt string; takes ownership
|
|
|
|
|
void (*set_prompt) (void *input, char *prompt);
|
|
|
|
|
/// Ring the terminal bell
|
|
|
|
|
void (*ding) (void *input);
|
|
|
|
|
|
|
|
|
|
/// Create a new input buffer
|
|
|
|
|
input_buffer_t (*buffer_new) (void *input);
|
|
|
|
|
/// Destroy an input buffer
|
2022-10-05 00:16:57 +02:00
|
|
|
void (*buffer_destroy) (void *input, input_buffer_t);
|
2016-03-07 01:08:52 +01:00
|
|
|
/// Switch to a different input buffer
|
2022-10-05 00:16:57 +02:00
|
|
|
void (*buffer_switch) (void *input, input_buffer_t);
|
2022-09-30 17:16:16 +02:00
|
|
|
/// Return all history lines in the locale encoding
|
2022-10-05 00:16:57 +02:00
|
|
|
struct strv (*buffer_get_history) (void *input, input_buffer_t);
|
|
|
|
|
/// Add a history line in the locale encoding
|
|
|
|
|
void (*buffer_add_history) (void *input, input_buffer_t, const char *);
|
2016-03-07 01:08:52 +01:00
|
|
|
|
|
|
|
|
/// Register a function that can be bound to character sequences
|
|
|
|
|
void (*register_fn) (void *input,
|
|
|
|
|
const char *name, const char *help, input_fn fn, void *user_data);
|
|
|
|
|
/// Bind an arbitrary sequence of characters to the given named function
|
|
|
|
|
void (*bind) (void *input, const char *seq, const char *fn);
|
|
|
|
|
/// Bind Ctrl+key to the given named function
|
|
|
|
|
void (*bind_control) (void *input, char key, const char *fn);
|
|
|
|
|
/// Bind Alt+key to the given named function
|
|
|
|
|
void (*bind_meta) (void *input, char key, const char *fn);
|
|
|
|
|
|
xC: allow passing the cursor position to editors
Add a configuration option to set a custom editor command,
different from EDITOR or VISUAL--those remain as defaults.
Implement substitutions allowing to convey cursor information
to VIM and Emacs (the latter of which is fairly painful to cater to),
and put usage hints in the configuration option's description.
This should make the editing experience a bit more seamless
for users, even though the position is carried over in one way only.
No sophisticated quoting capabilities were deemed necessary,
it is a lot of code already. The particular syntax is inspired
by .desktop files and systemd.
["/bin/sh", "-c", "vim +$2go \"$1\"", filename, position, line, column]
would be a slightly simpler but cryptic way of implementing this.
2021-10-30 08:34:14 +02:00
|
|
|
/// Get the current line input and position within
|
|
|
|
|
char *(*get_line) (void *input, int *position);
|
2016-03-07 01:08:52 +01:00
|
|
|
/// Clear the current line input
|
|
|
|
|
void (*clear_line) (void *input);
|
|
|
|
|
/// Insert text at current position
|
|
|
|
|
bool (*insert) (void *input, const char *text);
|
|
|
|
|
|
|
|
|
|
/// Handle terminal resize
|
|
|
|
|
void (*on_tty_resized) (void *input);
|
|
|
|
|
/// Handle terminal input
|
|
|
|
|
void (*on_tty_readable) (void *input);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define INPUT_VTABLE(XX) \
|
|
|
|
|
XX (start) XX (stop) XX (prepare) XX (destroy) \
|
|
|
|
|
XX (hide) XX (show) XX (get_prompt) XX (set_prompt) XX (ding) \
|
2022-10-05 00:16:57 +02:00
|
|
|
XX (buffer_new) XX (buffer_destroy) XX (buffer_switch) \
|
|
|
|
|
XX (buffer_get_history) XX (buffer_add_history) \
|
2016-03-07 01:08:52 +01:00
|
|
|
XX (register_fn) XX (bind) XX (bind_control) XX (bind_meta) \
|
|
|
|
|
XX (get_line) XX (clear_line) XX (insert) \
|
|
|
|
|
XX (on_tty_resized) XX (on_tty_readable)
|
2015-05-05 03:23:53 +02:00
|
|
|
|
2022-08-25 12:28:17 +02:00
|
|
|
// ~~~ GNU Readline ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2015-05-05 03:23:53 +02:00
|
|
|
|
2015-05-05 08:46:59 +02:00
|
|
|
#ifdef HAVE_READLINE
|
|
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
#include <readline/readline.h>
|
|
|
|
|
#include <readline/history.h>
|
|
|
|
|
|
2015-05-05 08:46:59 +02:00
|
|
|
#define INPUT_START_IGNORE RL_PROMPT_START_IGNORE
|
|
|
|
|
#define INPUT_END_IGNORE RL_PROMPT_END_IGNORE
|
|
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input_rl_fn
|
|
|
|
|
{
|
|
|
|
|
ffi_closure closure; ///< Closure
|
|
|
|
|
|
|
|
|
|
LIST_HEADER (struct input_rl_fn)
|
|
|
|
|
input_fn callback; ///< Real callback
|
|
|
|
|
void *user_data; ///< Real callback user data
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct input_rl_buffer
|
|
|
|
|
{
|
|
|
|
|
HISTORY_STATE *history; ///< Saved history state
|
|
|
|
|
char *saved_line; ///< Saved line content
|
|
|
|
|
int saved_point; ///< Saved cursor position
|
|
|
|
|
int saved_mark; ///< Saved mark
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct input_rl
|
|
|
|
|
{
|
|
|
|
|
struct input super; ///< Parent class
|
|
|
|
|
|
|
|
|
|
bool active; ///< Interface has been started
|
|
|
|
|
char *prompt; ///< The prompt we use
|
|
|
|
|
int prompt_shown; ///< Whether the prompt is shown now
|
|
|
|
|
|
|
|
|
|
char *saved_line; ///< Saved line content
|
|
|
|
|
int saved_point; ///< Saved cursor position
|
|
|
|
|
int saved_mark; ///< Saved mark
|
|
|
|
|
|
|
|
|
|
struct input_rl_fn *fns; ///< Named functions
|
|
|
|
|
struct input_rl_buffer *current; ///< Current input buffer
|
|
|
|
|
};
|
2015-05-05 03:23:53 +02:00
|
|
|
|
|
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_rl_ding (void *input)
|
2015-05-05 03:23:53 +02:00
|
|
|
{
|
2016-03-07 01:08:52 +01:00
|
|
|
(void) input;
|
|
|
|
|
rl_ding ();
|
|
|
|
|
}
|
2015-05-05 03:23:53 +02:00
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
static const char *
|
|
|
|
|
input_rl_get_prompt (void *input)
|
|
|
|
|
{
|
|
|
|
|
struct input_rl *self = input;
|
|
|
|
|
return self->prompt;
|
2015-05-05 03:23:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_rl_set_prompt (void *input, char *prompt)
|
2015-05-05 03:23:53 +02:00
|
|
|
{
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input_rl *self = input;
|
2018-01-08 22:15:29 +01:00
|
|
|
cstr_set (&self->prompt, prompt);
|
2015-05-05 03:23:53 +02:00
|
|
|
|
2020-10-06 13:37:07 +02:00
|
|
|
if (!self->active || self->prompt_shown <= 0)
|
2015-05-12 07:07:02 +02:00
|
|
|
return;
|
|
|
|
|
|
2015-05-05 03:23:53 +02:00
|
|
|
// First reset the prompt to work around a bug in readline
|
|
|
|
|
rl_set_prompt ("");
|
2020-10-06 13:37:07 +02:00
|
|
|
rl_redisplay ();
|
2015-05-05 03:23:53 +02:00
|
|
|
|
|
|
|
|
rl_set_prompt (self->prompt);
|
2020-10-06 13:37:07 +02:00
|
|
|
rl_redisplay ();
|
2015-05-05 03:23:53 +02:00
|
|
|
}
|
|
|
|
|
|
2015-12-25 04:21:18 +01:00
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_rl_clear_line (void *input)
|
2015-12-25 04:21:18 +01:00
|
|
|
{
|
2016-03-07 01:08:52 +01:00
|
|
|
(void) input;
|
2015-12-25 04:21:18 +01:00
|
|
|
rl_replace_line ("", false);
|
|
|
|
|
rl_redisplay ();
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-05 03:23:53 +02:00
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_rl__erase (struct input_rl *self)
|
2015-05-05 03:23:53 +02:00
|
|
|
{
|
|
|
|
|
rl_set_prompt ("");
|
2016-03-07 01:08:52 +01:00
|
|
|
input_rl_clear_line (self);
|
2015-05-08 06:23:38 +02:00
|
|
|
}
|
|
|
|
|
|
2015-11-15 00:59:11 +01:00
|
|
|
static bool
|
2016-03-07 01:08:52 +01:00
|
|
|
input_rl_insert (void *input, const char *s)
|
2015-05-12 03:48:52 +02:00
|
|
|
{
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input_rl *self = input;
|
2015-05-12 03:48:52 +02:00
|
|
|
rl_insert_text (s);
|
2015-07-05 17:29:44 +02:00
|
|
|
if (self->prompt_shown > 0)
|
2015-05-12 03:48:52 +02:00
|
|
|
rl_redisplay ();
|
2015-11-15 00:59:11 +01:00
|
|
|
|
|
|
|
|
// GNU Readline, contrary to Editline, doesn't care about validity
|
|
|
|
|
return true;
|
2015-05-12 03:48:52 +02:00
|
|
|
}
|
|
|
|
|
|
2015-12-25 04:21:18 +01:00
|
|
|
static char *
|
xC: allow passing the cursor position to editors
Add a configuration option to set a custom editor command,
different from EDITOR or VISUAL--those remain as defaults.
Implement substitutions allowing to convey cursor information
to VIM and Emacs (the latter of which is fairly painful to cater to),
and put usage hints in the configuration option's description.
This should make the editing experience a bit more seamless
for users, even though the position is carried over in one way only.
No sophisticated quoting capabilities were deemed necessary,
it is a lot of code already. The particular syntax is inspired
by .desktop files and systemd.
["/bin/sh", "-c", "vim +$2go \"$1\"", filename, position, line, column]
would be a slightly simpler but cryptic way of implementing this.
2021-10-30 08:34:14 +02:00
|
|
|
input_rl_get_line (void *input, int *position)
|
2015-12-25 04:21:18 +01:00
|
|
|
{
|
2016-03-07 01:08:52 +01:00
|
|
|
(void) input;
|
xC: allow passing the cursor position to editors
Add a configuration option to set a custom editor command,
different from EDITOR or VISUAL--those remain as defaults.
Implement substitutions allowing to convey cursor information
to VIM and Emacs (the latter of which is fairly painful to cater to),
and put usage hints in the configuration option's description.
This should make the editing experience a bit more seamless
for users, even though the position is carried over in one way only.
No sophisticated quoting capabilities were deemed necessary,
it is a lot of code already. The particular syntax is inspired
by .desktop files and systemd.
["/bin/sh", "-c", "vim +$2go \"$1\"", filename, position, line, column]
would be a slightly simpler but cryptic way of implementing this.
2021-10-30 08:34:14 +02:00
|
|
|
if (position) *position = rl_point;
|
2015-12-25 04:21:18 +01:00
|
|
|
return rl_copy_text (0, rl_end);
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-05 03:23:53 +02:00
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
2016-03-06 17:59:45 +01:00
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_rl_bind (void *input, const char *seq, const char *function_name)
|
|
|
|
|
{
|
|
|
|
|
(void) input;
|
|
|
|
|
rl_bind_keyseq (seq, rl_named_function (function_name));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
input_rl_bind_meta (void *input, char key, const char *function_name)
|
|
|
|
|
{
|
|
|
|
|
// This one seems to actually work
|
|
|
|
|
char keyseq[] = { '\\', 'e', key, 0 };
|
|
|
|
|
input_rl_bind (input, keyseq, function_name);
|
|
|
|
|
#if 0
|
|
|
|
|
// While this one only fucks up UTF-8
|
|
|
|
|
// Tested with urxvt and xterm, on Debian Jessie/Arch, default settings
|
|
|
|
|
// \M-<key> behaves exactly the same
|
|
|
|
|
rl_bind_key (META (key), rl_named_function (function_name));
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
input_rl_bind_control (void *input, char key, const char *function_name)
|
|
|
|
|
{
|
|
|
|
|
char keyseq[] = { '\\', 'C', '-', key, 0 };
|
|
|
|
|
input_rl_bind (input, keyseq, function_name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
input_rl__forward (ffi_cif *cif, void *ret, void **args, void *user_data)
|
2016-03-06 17:59:45 +01:00
|
|
|
{
|
|
|
|
|
(void) cif;
|
|
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input_rl_fn *data = user_data;
|
2016-03-06 17:59:45 +01:00
|
|
|
if (!data->callback
|
|
|
|
|
(*(int *) args[0], UNMETA (*(int *) args[1]), data->user_data))
|
|
|
|
|
rl_ding ();
|
|
|
|
|
*(int *) ret = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_rl_register_fn (void *input,
|
2016-03-06 17:59:45 +01:00
|
|
|
const char *name, const char *help, input_fn callback, void *user_data)
|
|
|
|
|
{
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input_rl *self = input;
|
2016-03-06 17:59:45 +01:00
|
|
|
(void) help;
|
|
|
|
|
|
|
|
|
|
void *bound_fn = NULL;
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input_rl_fn *data = ffi_closure_alloc (sizeof *data, &bound_fn);
|
2016-03-06 17:59:45 +01:00
|
|
|
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,
|
2016-03-07 01:08:52 +01:00
|
|
|
&cif, input_rl__forward, data, bound_fn) == FFI_OK);
|
2016-03-06 17:59:45 +01:00
|
|
|
|
|
|
|
|
rl_add_defun (name, (rl_command_func_t *) bound_fn, -1);
|
|
|
|
|
LIST_PREPEND (self->fns, data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
2015-05-05 03:23:53 +02:00
|
|
|
static int app_readline_init (void);
|
|
|
|
|
static void on_readline_input (char *line);
|
2015-05-06 17:32:29 +02:00
|
|
|
static char **app_readline_completion (const char *text, int start, int end);
|
2015-05-05 03:23:53 +02:00
|
|
|
|
|
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_rl_start (void *input, const char *program_name)
|
2015-05-05 03:23:53 +02:00
|
|
|
{
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input_rl *self = input;
|
2015-05-05 08:46:59 +02:00
|
|
|
using_history ();
|
|
|
|
|
// This can cause memory leaks, or maybe even a segfault. Funny, eh?
|
|
|
|
|
stifle_history (HISTORY_LIMIT);
|
|
|
|
|
|
2015-05-06 22:01:48 +02:00
|
|
|
const char *slash = strrchr (program_name, '/');
|
|
|
|
|
rl_readline_name = slash ? ++slash : program_name;
|
2015-05-05 03:23:53 +02:00
|
|
|
rl_startup_hook = app_readline_init;
|
|
|
|
|
rl_catch_sigwinch = false;
|
2020-03-21 22:02:02 +01:00
|
|
|
rl_change_environment = false;
|
2015-05-06 22:01:48 +02:00
|
|
|
|
2015-05-06 22:20:02 +02:00
|
|
|
rl_basic_word_break_characters = WORD_BREAKING_CHARS;
|
2015-05-06 22:01:48 +02:00
|
|
|
rl_completer_word_break_characters = NULL;
|
2015-05-06 17:32:29 +02:00
|
|
|
rl_attempted_completion_function = app_readline_completion;
|
2015-05-06 22:01:48 +02:00
|
|
|
|
2021-07-23 18:42:21 +02:00
|
|
|
// We shouldn't produce any duplicates that the library would help us
|
|
|
|
|
// autofilter, and we don't generally want alphabetic ordering at all
|
|
|
|
|
rl_sort_completion_matches = false;
|
|
|
|
|
|
2024-12-17 03:28:12 +01:00
|
|
|
// Readline >= 8.0 otherwise prints spurious newlines on EOF.
|
|
|
|
|
if (RL_VERSION_MAJOR >= 8)
|
|
|
|
|
rl_variable_bind ("enable-bracketed-paste", "off");
|
|
|
|
|
|
2015-05-06 22:01:48 +02:00
|
|
|
hard_assert (self->prompt != NULL);
|
2016-03-10 00:06:28 +01:00
|
|
|
// The inputrc is read before any callbacks are called, so we need to
|
|
|
|
|
// register all functions that our user may want to map up front
|
|
|
|
|
self->super.add_functions (self->super.user_data);
|
2015-05-06 22:01:48 +02:00
|
|
|
rl_callback_handler_install (self->prompt, on_readline_input);
|
|
|
|
|
|
2015-05-05 03:23:53 +02:00
|
|
|
self->prompt_shown = 1;
|
|
|
|
|
self->active = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_rl_stop (void *input)
|
2015-05-05 03:23:53 +02:00
|
|
|
{
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input_rl *self = input;
|
2015-05-05 03:23:53 +02:00
|
|
|
if (self->prompt_shown > 0)
|
2016-03-07 01:08:52 +01:00
|
|
|
input_rl__erase (self);
|
2015-05-05 03:23:53 +02:00
|
|
|
|
|
|
|
|
// This is okay as long as we're not called from within readline
|
|
|
|
|
rl_callback_handler_remove ();
|
|
|
|
|
self->active = false;
|
2015-05-12 07:07:02 +02:00
|
|
|
self->prompt_shown = false;
|
2015-05-05 03:23:53 +02:00
|
|
|
}
|
|
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
static void
|
|
|
|
|
input_rl_prepare (void *input, bool enabled)
|
|
|
|
|
{
|
|
|
|
|
(void) input;
|
|
|
|
|
if (enabled)
|
|
|
|
|
rl_prep_terminal (true);
|
|
|
|
|
else
|
|
|
|
|
rl_deprep_terminal ();
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-05 03:23:53 +02:00
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
|
|
// The following part shows you why it's not a good idea to use
|
|
|
|
|
// GNU Readline for this kind of software. Or for anything else, really.
|
|
|
|
|
|
|
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_rl__save_buffer (struct input_rl *self, struct input_rl_buffer *buffer)
|
2015-05-05 03:23:53 +02:00
|
|
|
{
|
|
|
|
|
(void) self;
|
|
|
|
|
|
|
|
|
|
buffer->history = history_get_history_state ();
|
|
|
|
|
buffer->saved_line = rl_copy_text (0, rl_end);
|
|
|
|
|
buffer->saved_point = rl_point;
|
|
|
|
|
buffer->saved_mark = rl_mark;
|
2015-11-24 03:04:14 +01:00
|
|
|
|
|
|
|
|
rl_replace_line ("", true);
|
|
|
|
|
if (self->prompt_shown > 0)
|
|
|
|
|
rl_redisplay ();
|
2015-05-05 03:23:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_rl__restore_buffer (struct input_rl *self, struct input_rl_buffer *buffer)
|
2015-05-05 03:23:53 +02:00
|
|
|
{
|
|
|
|
|
if (buffer->history)
|
|
|
|
|
{
|
|
|
|
|
// history_get_history_state() just allocates a new HISTORY_STATE
|
|
|
|
|
// and fills it with its current internal data. We don't need that
|
|
|
|
|
// shell anymore after reviving it.
|
|
|
|
|
history_set_history_state (buffer->history);
|
|
|
|
|
free (buffer->history);
|
|
|
|
|
buffer->history = NULL;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// This should get us a clean history while keeping the flags.
|
|
|
|
|
// Note that we've either saved the previous history entries, or we've
|
|
|
|
|
// cleared them altogether, so there should be nothing to leak.
|
|
|
|
|
HISTORY_STATE *state = history_get_history_state ();
|
|
|
|
|
state->offset = state->length = state->size = 0;
|
2015-07-04 01:06:51 +02:00
|
|
|
state->entries = NULL;
|
2015-05-05 03:23:53 +02:00
|
|
|
history_set_history_state (state);
|
|
|
|
|
free (state);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (buffer->saved_line)
|
|
|
|
|
{
|
2015-11-24 03:04:14 +01:00
|
|
|
rl_replace_line (buffer->saved_line, true);
|
2015-05-05 03:23:53 +02:00
|
|
|
rl_point = buffer->saved_point;
|
|
|
|
|
rl_mark = buffer->saved_mark;
|
2018-01-08 22:15:29 +01:00
|
|
|
cstr_set (&buffer->saved_line, NULL);
|
2015-05-05 03:23:53 +02:00
|
|
|
|
2015-07-05 17:29:44 +02:00
|
|
|
if (self->prompt_shown > 0)
|
2015-05-05 03:23:53 +02:00
|
|
|
rl_redisplay ();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_rl_buffer_switch (void *input, input_buffer_t input_buffer)
|
2015-05-05 03:23:53 +02:00
|
|
|
{
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input_rl *self = input;
|
|
|
|
|
struct input_rl_buffer *buffer = input_buffer;
|
2022-10-05 00:16:57 +02:00
|
|
|
|
2015-05-05 03:23:53 +02:00
|
|
|
// There could possibly be occurences of the current undo list in some
|
|
|
|
|
// history entry. We either need to free the undo list, or move it
|
|
|
|
|
// somewhere else to load back later, as the buffer we're switching to
|
|
|
|
|
// has its own history state.
|
|
|
|
|
rl_free_undo_list ();
|
|
|
|
|
|
|
|
|
|
// Save this buffer's history so that it's independent for each buffer
|
|
|
|
|
if (self->current)
|
2016-03-07 01:08:52 +01:00
|
|
|
input_rl__save_buffer (self, self->current);
|
2015-05-05 03:23:53 +02:00
|
|
|
else
|
|
|
|
|
// Just throw it away; there should always be an active buffer however
|
|
|
|
|
#if RL_READLINE_VERSION >= 0x0603
|
|
|
|
|
rl_clear_history ();
|
|
|
|
|
#else // RL_READLINE_VERSION < 0x0603
|
|
|
|
|
// At least something... this may leak undo entries
|
|
|
|
|
clear_history ();
|
|
|
|
|
#endif // RL_READLINE_VERSION < 0x0603
|
|
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
input_rl__restore_buffer (self, buffer);
|
2015-05-05 03:23:53 +02:00
|
|
|
self->current = buffer;
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-30 17:16:16 +02:00
|
|
|
static struct strv
|
2022-10-05 00:16:57 +02:00
|
|
|
input_rl_buffer_get_history (void *input, input_buffer_t input_buffer)
|
2022-09-30 17:16:16 +02:00
|
|
|
{
|
2022-10-05 00:16:57 +02:00
|
|
|
(void) input;
|
2022-09-30 17:16:16 +02:00
|
|
|
struct input_rl_buffer *buffer = input_buffer;
|
|
|
|
|
HIST_ENTRY **p =
|
|
|
|
|
buffer->history ? buffer->history->entries : history_list();
|
|
|
|
|
struct strv v = strv_make ();
|
|
|
|
|
while (p && *p)
|
|
|
|
|
strv_append (&v, (*p++)->line);
|
|
|
|
|
return v;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-05 00:16:57 +02:00
|
|
|
static void
|
|
|
|
|
input_rl_buffer_add_history (void *input, input_buffer_t input_buffer,
|
|
|
|
|
const char *line)
|
|
|
|
|
{
|
|
|
|
|
(void) input;
|
|
|
|
|
struct input_rl_buffer *buffer = input_buffer;
|
|
|
|
|
|
|
|
|
|
// For inactive buffers, we'd have to either alloc_history_entry(),
|
|
|
|
|
// construe a timestamp, and manually insert it into saved HISTORY_STATEs,
|
|
|
|
|
// or temporarily switch histories.
|
|
|
|
|
if (!buffer->history)
|
|
|
|
|
{
|
2023-07-24 07:58:59 +02:00
|
|
|
bool at_end = where_history () == history_length;
|
2022-10-05 00:16:57 +02:00
|
|
|
add_history (line);
|
|
|
|
|
if (at_end)
|
|
|
|
|
next_history ();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-05 03:23:53 +02:00
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_rl__buffer_destroy_wo_history (struct input_rl_buffer *self)
|
2015-05-05 03:23:53 +02:00
|
|
|
{
|
2016-03-07 01:08:52 +01:00
|
|
|
free (self->history);
|
|
|
|
|
free (self->saved_line);
|
|
|
|
|
free (self);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
input_rl_buffer_destroy (void *input, input_buffer_t input_buffer)
|
|
|
|
|
{
|
|
|
|
|
(void) input;
|
|
|
|
|
struct input_rl_buffer *buffer = input_buffer;
|
2015-05-05 03:23:53 +02:00
|
|
|
|
|
|
|
|
// rl_clear_history, being the only way I know of to get rid of the complete
|
|
|
|
|
// history including attached data, is a pretty recent addition. *sigh*
|
|
|
|
|
#if RL_READLINE_VERSION >= 0x0603
|
|
|
|
|
if (buffer->history)
|
|
|
|
|
{
|
2016-03-07 01:08:52 +01:00
|
|
|
// See input_rl_buffer_switch() for why we need to do this BS
|
2015-05-05 03:23:53 +02:00
|
|
|
rl_free_undo_list ();
|
|
|
|
|
|
|
|
|
|
// This is probably the only way we can free the history fully
|
|
|
|
|
HISTORY_STATE *state = history_get_history_state ();
|
|
|
|
|
|
|
|
|
|
history_set_history_state (buffer->history);
|
|
|
|
|
rl_clear_history ();
|
2015-06-28 23:00:46 +02:00
|
|
|
// rl_clear_history just removes history entries,
|
|
|
|
|
// we have to reclaim memory for their actual container ourselves
|
|
|
|
|
free (buffer->history->entries);
|
2015-06-20 19:50:45 +02:00
|
|
|
free (buffer->history);
|
|
|
|
|
buffer->history = NULL;
|
2015-05-05 03:23:53 +02:00
|
|
|
|
|
|
|
|
history_set_history_state (state);
|
|
|
|
|
free (state);
|
|
|
|
|
}
|
|
|
|
|
#endif // RL_READLINE_VERSION
|
|
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
input_rl__buffer_destroy_wo_history (buffer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static input_buffer_t
|
|
|
|
|
input_rl_buffer_new (void *input)
|
|
|
|
|
{
|
|
|
|
|
(void) input;
|
|
|
|
|
struct input_rl_buffer *self = xcalloc (1, sizeof *self);
|
|
|
|
|
return self;
|
2015-05-05 03:23:53 +02:00
|
|
|
}
|
|
|
|
|
|
2015-05-05 08:46:59 +02:00
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
// Since {save,restore}_buffer() store history, we can't use them here like we
|
|
|
|
|
// do with libedit, because then buffer_destroy() can free memory that's still
|
|
|
|
|
// being used by readline. This situation is bound to happen on quit.
|
|
|
|
|
|
2015-05-05 08:46:59 +02:00
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_rl__save (struct input_rl *self)
|
2015-05-05 08:46:59 +02:00
|
|
|
{
|
|
|
|
|
hard_assert (!self->saved_line);
|
|
|
|
|
|
|
|
|
|
self->saved_point = rl_point;
|
|
|
|
|
self->saved_mark = rl_mark;
|
|
|
|
|
self->saved_line = rl_copy_text (0, rl_end);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_rl__restore (struct input_rl *self)
|
2015-05-05 08:46:59 +02:00
|
|
|
{
|
|
|
|
|
hard_assert (self->saved_line);
|
|
|
|
|
|
|
|
|
|
rl_set_prompt (self->prompt);
|
2015-11-24 03:04:14 +01:00
|
|
|
rl_replace_line (self->saved_line, false);
|
2015-05-05 08:46:59 +02:00
|
|
|
rl_point = self->saved_point;
|
|
|
|
|
rl_mark = self->saved_mark;
|
2018-01-08 22:15:29 +01:00
|
|
|
cstr_set (&self->saved_line, NULL);
|
2015-05-05 08:46:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_rl_hide (void *input)
|
2015-05-05 08:46:59 +02:00
|
|
|
{
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input_rl *self = input;
|
2015-05-05 08:46:59 +02:00
|
|
|
if (!self->active || self->prompt_shown-- < 1)
|
|
|
|
|
return;
|
|
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
input_rl__save (self);
|
|
|
|
|
input_rl__erase (self);
|
2015-05-05 08:46:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_rl_show (void *input)
|
2015-05-05 08:46:59 +02:00
|
|
|
{
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input_rl *self = input;
|
2015-05-05 08:46:59 +02:00
|
|
|
if (!self->active || ++self->prompt_shown < 1)
|
|
|
|
|
return;
|
|
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
input_rl__restore (self);
|
2015-05-05 08:46:59 +02:00
|
|
|
rl_redisplay ();
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
input_rl_on_tty_resized (void *input)
|
|
|
|
|
{
|
|
|
|
|
(void) input;
|
|
|
|
|
// This fucks up big time on terminals with automatic wrapping such as
|
|
|
|
|
// rxvt-unicode or newer VTE when the current line overflows, however we
|
|
|
|
|
// can't do much about that
|
|
|
|
|
rl_resize_terminal ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
input_rl_on_tty_readable (void *input)
|
|
|
|
|
{
|
|
|
|
|
(void) input;
|
|
|
|
|
rl_callback_read_char ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
input_rl_destroy (void *input)
|
|
|
|
|
{
|
|
|
|
|
struct input_rl *self = input;
|
|
|
|
|
free (self->saved_line);
|
|
|
|
|
LIST_FOR_EACH (struct input_rl_fn, iter, self->fns)
|
|
|
|
|
ffi_closure_free (iter);
|
|
|
|
|
free (self->prompt);
|
|
|
|
|
free (self);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define XX(a) .a = input_rl_ ## a,
|
|
|
|
|
static struct input_vtable input_rl_vtable = { INPUT_VTABLE (XX) };
|
|
|
|
|
#undef XX
|
|
|
|
|
|
|
|
|
|
static struct input *
|
|
|
|
|
input_rl_new (void)
|
|
|
|
|
{
|
|
|
|
|
struct input_rl *self = xcalloc (1, sizeof *self);
|
|
|
|
|
self->super.vtable = &input_rl_vtable;
|
|
|
|
|
return &self->super;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define input_new input_rl_new
|
2015-05-05 08:46:59 +02:00
|
|
|
#endif // HAVE_READLINE
|
|
|
|
|
|
2022-08-25 12:28:17 +02:00
|
|
|
// ~~~ BSD Editline ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2015-05-05 08:46:59 +02:00
|
|
|
|
|
|
|
|
#ifdef HAVE_EDITLINE
|
|
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
#include <histedit.h>
|
|
|
|
|
|
2015-05-05 08:46:59 +02:00
|
|
|
#define INPUT_START_IGNORE '\x01'
|
|
|
|
|
#define INPUT_END_IGNORE '\x01'
|
|
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input_el_fn
|
2015-05-05 08:46:59 +02:00
|
|
|
{
|
2016-03-07 01:08:52 +01:00
|
|
|
ffi_closure closure; ///< Closure
|
2015-05-05 08:46:59 +02:00
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
LIST_HEADER (struct input_el_fn)
|
|
|
|
|
input_fn callback; ///< Real callback
|
|
|
|
|
void *user_data; ///< Real callback user data
|
|
|
|
|
|
|
|
|
|
wchar_t *name; ///< Function name
|
|
|
|
|
wchar_t *help; ///< Function help
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct input_el_buffer
|
|
|
|
|
{
|
|
|
|
|
HistoryW *history; ///< The history object
|
|
|
|
|
wchar_t *saved_line; ///< Saved line content
|
|
|
|
|
int saved_len; ///< Length of the saved line
|
|
|
|
|
int saved_point; ///< Saved cursor position
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct input_el
|
|
|
|
|
{
|
|
|
|
|
struct input super; ///< Parent class
|
|
|
|
|
EditLine *editline; ///< The EditLine object
|
|
|
|
|
|
|
|
|
|
bool active; ///< Are we a thing?
|
|
|
|
|
char *prompt; ///< The prompt we use
|
|
|
|
|
int prompt_shown; ///< Whether the prompt is shown now
|
|
|
|
|
|
|
|
|
|
struct input_el_fn *fns; ///< Named functions
|
|
|
|
|
struct input_el_buffer *current; ///< Current input buffer
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void app_editline_init (struct input_el *self);
|
|
|
|
|
|
2015-05-05 08:46:59 +02:00
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_el__redisplay (void *input)
|
2015-05-05 08:46:59 +02:00
|
|
|
{
|
2021-10-28 08:18:15 +02:00
|
|
|
// See rl_redisplay(), however NetBSD editline's map.c v1.54 breaks VREPRINT
|
|
|
|
|
// so we bind redisplay somewhere else in app_editline_init()
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input_el *self = input;
|
2022-08-27 14:35:07 +02:00
|
|
|
wchar_t x[] = { L'q' & 31, 0 };
|
|
|
|
|
el_wpush (self->editline, x);
|
2015-05-05 08:46:59 +02:00
|
|
|
|
|
|
|
|
// We have to do this or it gets stuck and nothing is done
|
2022-08-27 14:35:07 +02:00
|
|
|
int dummy_count = 0;
|
|
|
|
|
(void) el_wgets (self->editline, &dummy_count);
|
2015-05-05 08:46:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static char *
|
2016-03-07 01:08:52 +01:00
|
|
|
input_el__make_prompt (EditLine *editline)
|
2015-05-05 08:46:59 +02:00
|
|
|
{
|
2022-08-29 14:04:50 +02:00
|
|
|
struct input_el *self = NULL;
|
2015-05-05 08:46:59 +02:00
|
|
|
el_get (editline, EL_CLIENTDATA, &self);
|
2015-05-05 22:35:51 +02:00
|
|
|
if (!self->prompt)
|
|
|
|
|
return "";
|
2015-05-05 08:46:59 +02:00
|
|
|
return self->prompt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static char *
|
2016-03-07 01:08:52 +01:00
|
|
|
input_el__make_empty_prompt (EditLine *editline)
|
2015-05-05 08:46:59 +02:00
|
|
|
{
|
|
|
|
|
(void) editline;
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
2015-05-05 08:46:59 +02:00
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_el_ding (void *input)
|
2015-05-05 08:46:59 +02:00
|
|
|
{
|
2016-03-07 01:08:52 +01:00
|
|
|
// XXX: this isn't probably very portable;
|
|
|
|
|
// we could use "bell" from terminfo but that creates a dependency
|
|
|
|
|
(void) input;
|
|
|
|
|
write (STDOUT_FILENO, "\a", 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const char *
|
|
|
|
|
input_el_get_prompt (void *input)
|
|
|
|
|
{
|
|
|
|
|
struct input_el *self = input;
|
|
|
|
|
return self->prompt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
input_el_set_prompt (void *input, char *prompt)
|
|
|
|
|
{
|
|
|
|
|
struct input_el *self = input;
|
2018-01-08 22:15:29 +01:00
|
|
|
cstr_set (&self->prompt, prompt);
|
2016-03-07 01:08:52 +01:00
|
|
|
|
|
|
|
|
if (self->prompt_shown > 0)
|
|
|
|
|
input_el__redisplay (self);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
input_el_clear_line (void *input)
|
|
|
|
|
{
|
|
|
|
|
struct input_el *self = input;
|
2015-05-05 08:46:59 +02:00
|
|
|
const LineInfoW *info = el_wline (self->editline);
|
|
|
|
|
int len = info->lastchar - info->buffer;
|
|
|
|
|
int point = info->cursor - info->buffer;
|
|
|
|
|
el_cursor (self->editline, len - point);
|
|
|
|
|
el_wdeletestr (self->editline, len);
|
2016-03-07 01:08:52 +01:00
|
|
|
input_el__redisplay (self);
|
2015-05-05 08:46:59 +02:00
|
|
|
}
|
|
|
|
|
|
2015-12-25 04:21:18 +01:00
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_el__erase (struct input_el *self)
|
2015-12-25 04:21:18 +01:00
|
|
|
{
|
2016-03-07 01:08:52 +01:00
|
|
|
el_set (self->editline, EL_PROMPT, input_el__make_empty_prompt);
|
|
|
|
|
input_el_clear_line (self);
|
2015-12-25 04:21:18 +01:00
|
|
|
}
|
|
|
|
|
|
2015-11-15 00:59:11 +01:00
|
|
|
static bool
|
2016-03-07 01:08:52 +01:00
|
|
|
input_el_insert (void *input, const char *s)
|
2015-05-12 03:48:52 +02:00
|
|
|
{
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input_el *self = input;
|
2015-12-25 04:21:18 +01:00
|
|
|
bool success = !*s || !el_insertstr (self->editline, s);
|
2015-07-06 01:54:02 +02:00
|
|
|
if (self->prompt_shown > 0)
|
2016-03-07 01:08:52 +01:00
|
|
|
input_el__redisplay (self);
|
2015-11-15 00:59:11 +01:00
|
|
|
return success;
|
2015-05-12 03:48:52 +02:00
|
|
|
}
|
|
|
|
|
|
2015-12-25 04:21:18 +01:00
|
|
|
static char *
|
xC: allow passing the cursor position to editors
Add a configuration option to set a custom editor command,
different from EDITOR or VISUAL--those remain as defaults.
Implement substitutions allowing to convey cursor information
to VIM and Emacs (the latter of which is fairly painful to cater to),
and put usage hints in the configuration option's description.
This should make the editing experience a bit more seamless
for users, even though the position is carried over in one way only.
No sophisticated quoting capabilities were deemed necessary,
it is a lot of code already. The particular syntax is inspired
by .desktop files and systemd.
["/bin/sh", "-c", "vim +$2go \"$1\"", filename, position, line, column]
would be a slightly simpler but cryptic way of implementing this.
2021-10-30 08:34:14 +02:00
|
|
|
input_el_get_line (void *input, int *position)
|
2015-12-25 04:21:18 +01:00
|
|
|
{
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input_el *self = input;
|
2015-12-25 04:21:18 +01:00
|
|
|
const LineInfo *info = el_line (self->editline);
|
xC: allow passing the cursor position to editors
Add a configuration option to set a custom editor command,
different from EDITOR or VISUAL--those remain as defaults.
Implement substitutions allowing to convey cursor information
to VIM and Emacs (the latter of which is fairly painful to cater to),
and put usage hints in the configuration option's description.
This should make the editing experience a bit more seamless
for users, even though the position is carried over in one way only.
No sophisticated quoting capabilities were deemed necessary,
it is a lot of code already. The particular syntax is inspired
by .desktop files and systemd.
["/bin/sh", "-c", "vim +$2go \"$1\"", filename, position, line, column]
would be a slightly simpler but cryptic way of implementing this.
2021-10-30 08:34:14 +02:00
|
|
|
int point = info->cursor - info->buffer;
|
|
|
|
|
if (position) *position = point;
|
2015-12-25 04:21:18 +01:00
|
|
|
return xstrndup (info->buffer, info->lastchar - info->buffer);
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-05 08:46:59 +02:00
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
2016-03-06 17:59:45 +01:00
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_el_bind (void *input, const char *seq, const char *function_name)
|
|
|
|
|
{
|
|
|
|
|
struct input_el *self = input;
|
|
|
|
|
el_set (self->editline, EL_BIND, seq, function_name, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
input_el_bind_meta (void *input, char key, const char *function_name)
|
|
|
|
|
{
|
|
|
|
|
char keyseq[] = { 'M', '-', key, 0 };
|
|
|
|
|
input_el_bind (input, keyseq, function_name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
input_el_bind_control (void *input, char key, const char *function_name)
|
|
|
|
|
{
|
|
|
|
|
char keyseq[] = { '^', key, 0 };
|
|
|
|
|
input_el_bind (input, keyseq, function_name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
input_el__forward (ffi_cif *cif, void *ret, void **args, void *user_data)
|
2016-03-06 17:59:45 +01:00
|
|
|
{
|
|
|
|
|
(void) cif;
|
|
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input_el_fn *data = user_data;
|
2016-03-06 17:59:45 +01:00
|
|
|
*(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
|
2016-03-07 01:08:52 +01:00
|
|
|
input_el_register_fn (void *input,
|
2016-03-06 17:59:45 +01:00
|
|
|
const char *name, const char *help, input_fn callback, void *user_data)
|
|
|
|
|
{
|
|
|
|
|
void *bound_fn = NULL;
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input_el_fn *data = ffi_closure_alloc (sizeof *data, &bound_fn);
|
2016-03-06 17:59:45 +01:00
|
|
|
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,
|
2016-03-07 01:08:52 +01:00
|
|
|
&cif, input_el__forward, data, bound_fn) == FFI_OK);
|
2016-03-06 17:59:45 +01:00
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input_el *self = input;
|
2016-03-06 17:59:45 +01:00
|
|
|
el_wset (self->editline, EL_ADDFN, data->name, data->help, bound_fn);
|
|
|
|
|
LIST_PREPEND (self->fns, data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
2015-05-05 08:46:59 +02:00
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_el_start (void *input, const char *program_name)
|
2015-05-05 08:46:59 +02:00
|
|
|
{
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input_el *self = input;
|
2015-05-05 08:46:59 +02:00
|
|
|
self->editline = el_init (program_name, stdin, stdout, stderr);
|
|
|
|
|
el_set (self->editline, EL_CLIENTDATA, self);
|
|
|
|
|
el_set (self->editline, EL_PROMPT_ESC,
|
2016-03-07 01:08:52 +01:00
|
|
|
input_el__make_prompt, INPUT_START_IGNORE);
|
2015-05-05 08:46:59 +02:00
|
|
|
el_set (self->editline, EL_SIGNAL, false);
|
|
|
|
|
el_set (self->editline, EL_UNBUFFERED, true);
|
|
|
|
|
el_set (self->editline, EL_EDITOR, "emacs");
|
|
|
|
|
|
|
|
|
|
app_editline_init (self);
|
2015-05-08 06:23:38 +02:00
|
|
|
|
2015-05-05 08:46:59 +02:00
|
|
|
self->prompt_shown = 1;
|
|
|
|
|
self->active = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_el_stop (void *input)
|
2015-05-05 08:46:59 +02:00
|
|
|
{
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input_el *self = input;
|
2015-05-05 08:46:59 +02:00
|
|
|
if (self->prompt_shown > 0)
|
2016-03-07 01:08:52 +01:00
|
|
|
input_el__erase (self);
|
2015-05-05 08:46:59 +02:00
|
|
|
|
|
|
|
|
el_end (self->editline);
|
|
|
|
|
self->editline = NULL;
|
|
|
|
|
self->active = false;
|
2015-05-12 07:07:02 +02:00
|
|
|
self->prompt_shown = false;
|
2015-05-05 08:46:59 +02:00
|
|
|
}
|
|
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
static void
|
|
|
|
|
input_el_prepare (void *input, bool enabled)
|
|
|
|
|
{
|
|
|
|
|
struct input_el *self = input;
|
|
|
|
|
el_set (self->editline, EL_PREP_TERM, enabled);
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-05 08:46:59 +02:00
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_el__save_buffer (struct input_el *self, struct input_el_buffer *buffer)
|
2015-05-05 08:46:59 +02:00
|
|
|
{
|
|
|
|
|
const LineInfoW *info = el_wline (self->editline);
|
|
|
|
|
int len = info->lastchar - info->buffer;
|
|
|
|
|
int point = info->cursor - info->buffer;
|
|
|
|
|
|
2024-08-08 09:13:14 +02:00
|
|
|
wchar_t *line = xcalloc (len + 1, sizeof *info->buffer);
|
2015-05-05 08:46:59 +02:00
|
|
|
memcpy (line, info->buffer, sizeof *info->buffer * len);
|
|
|
|
|
el_cursor (self->editline, len - point);
|
|
|
|
|
el_wdeletestr (self->editline, len);
|
|
|
|
|
|
|
|
|
|
buffer->saved_line = line;
|
|
|
|
|
buffer->saved_point = point;
|
|
|
|
|
buffer->saved_len = len;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_el__save (struct input_el *self)
|
|
|
|
|
{
|
|
|
|
|
if (self->current)
|
|
|
|
|
input_el__save_buffer (self, self->current);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
input_el__restore_buffer (struct input_el *self, struct input_el_buffer *buffer)
|
2015-05-05 08:46:59 +02:00
|
|
|
{
|
|
|
|
|
if (buffer->saved_line)
|
|
|
|
|
{
|
|
|
|
|
el_winsertstr (self->editline, buffer->saved_line);
|
|
|
|
|
el_cursor (self->editline,
|
|
|
|
|
-(buffer->saved_len - buffer->saved_point));
|
2020-09-02 19:01:31 +02:00
|
|
|
free (buffer->saved_line);
|
|
|
|
|
buffer->saved_line = NULL;
|
2015-05-05 08:46:59 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_el__restore (struct input_el *self)
|
2015-05-05 08:46:59 +02:00
|
|
|
{
|
|
|
|
|
if (self->current)
|
2016-03-07 01:08:52 +01:00
|
|
|
input_el__restore_buffer (self, self->current);
|
|
|
|
|
}
|
2015-05-05 08:46:59 +02:00
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
2022-08-29 10:29:49 +02:00
|
|
|
// Editline keeping its own history position (look for "eventno" there).
|
|
|
|
|
// This is the only sane way of resetting it.
|
2022-08-27 14:35:07 +02:00
|
|
|
static void
|
2022-08-29 10:29:49 +02:00
|
|
|
input_el__start_over (struct input_el *self)
|
2022-08-27 14:35:07 +02:00
|
|
|
{
|
2022-09-06 16:40:31 +02:00
|
|
|
wchar_t x[] = { L'c' & 31, 0 };
|
2022-08-29 10:29:49 +02:00
|
|
|
el_wpush (self->editline, x);
|
2022-08-29 08:31:11 +02:00
|
|
|
|
2022-08-29 10:29:49 +02:00
|
|
|
int dummy_count = 0;
|
2022-08-27 14:35:07 +02:00
|
|
|
(void) el_wgets (self->editline, &dummy_count);
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
static void
|
|
|
|
|
input_el_buffer_switch (void *input, input_buffer_t input_buffer)
|
|
|
|
|
{
|
|
|
|
|
struct input_el *self = input;
|
|
|
|
|
struct input_el_buffer *buffer = input_buffer;
|
2022-08-27 14:35:07 +02:00
|
|
|
if (!self->active)
|
|
|
|
|
return;
|
2016-03-07 01:08:52 +01:00
|
|
|
|
|
|
|
|
if (self->current)
|
|
|
|
|
input_el__save_buffer (self, self->current);
|
|
|
|
|
|
2015-05-05 08:46:59 +02:00
|
|
|
self->current = buffer;
|
2022-08-27 14:35:07 +02:00
|
|
|
el_wset (self->editline, EL_HIST, history, buffer->history);
|
2022-10-05 00:16:57 +02:00
|
|
|
// We only know how to reset the history position to be at the end.
|
2022-08-29 10:29:49 +02:00
|
|
|
input_el__start_over (self);
|
2022-08-27 14:35:07 +02:00
|
|
|
input_el__restore_buffer (self, buffer);
|
2015-05-05 08:46:59 +02:00
|
|
|
}
|
|
|
|
|
|
2022-09-30 17:16:16 +02:00
|
|
|
static struct strv
|
2022-10-05 00:16:57 +02:00
|
|
|
input_el_buffer_get_history (void *input, input_buffer_t input_buffer)
|
2022-09-30 17:16:16 +02:00
|
|
|
{
|
2022-10-05 00:16:57 +02:00
|
|
|
(void) input;
|
2022-09-30 17:16:16 +02:00
|
|
|
struct input_el_buffer *buffer = input_buffer;
|
|
|
|
|
struct strv v = strv_make ();
|
|
|
|
|
HistEventW ev;
|
|
|
|
|
if (history_w (buffer->history, &ev, H_LAST) < 0)
|
|
|
|
|
return v;
|
|
|
|
|
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
size_t len = wcstombs (NULL, ev.str, 0);
|
|
|
|
|
if (len++ == (size_t) -1)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
char *mb = xmalloc (len);
|
|
|
|
|
mb[wcstombs (mb, ev.str, len)] = 0;
|
|
|
|
|
strv_append_owned (&v, mb);
|
|
|
|
|
}
|
|
|
|
|
while (history_w (buffer->history, &ev, H_PREV) >= 0);
|
|
|
|
|
return v;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-05 00:16:57 +02:00
|
|
|
static void
|
|
|
|
|
input_el_buffer_add_history (void *input, input_buffer_t input_buffer,
|
|
|
|
|
const char *line)
|
|
|
|
|
{
|
|
|
|
|
(void) input;
|
|
|
|
|
struct input_el_buffer *buffer = input_buffer;
|
|
|
|
|
|
|
|
|
|
// When currently iterating history, this makes editline's internal
|
|
|
|
|
// history pointer wrongly point to a newer entry.
|
|
|
|
|
size_t len = mbstowcs (NULL, line, 0);
|
|
|
|
|
if (len++ != (size_t) -1)
|
|
|
|
|
{
|
|
|
|
|
wchar_t *wc = xcalloc (len, sizeof *wc);
|
|
|
|
|
wc[mbstowcs (wc, line, len)] = 0;
|
|
|
|
|
HistEventW ev;
|
|
|
|
|
(void) history_w (buffer->history, &ev, H_ENTER, wc);
|
|
|
|
|
free (wc);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-05 08:46:59 +02:00
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_el_buffer_destroy (void *input, input_buffer_t input_buffer)
|
2015-05-05 08:46:59 +02:00
|
|
|
{
|
2022-08-29 14:04:50 +02:00
|
|
|
struct input_el *self = input;
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input_el_buffer *buffer = input_buffer;
|
2022-08-29 14:04:50 +02:00
|
|
|
if (self->active && self->current == buffer)
|
|
|
|
|
{
|
|
|
|
|
el_wset (self->editline, EL_HIST, history, NULL);
|
|
|
|
|
self->current = NULL;
|
|
|
|
|
}
|
2016-03-07 01:08:52 +01:00
|
|
|
|
|
|
|
|
history_wend (buffer->history);
|
|
|
|
|
free (buffer->saved_line);
|
|
|
|
|
free (buffer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static input_buffer_t
|
|
|
|
|
input_el_buffer_new (void *input)
|
|
|
|
|
{
|
|
|
|
|
(void) input;
|
|
|
|
|
struct input_el_buffer *self = xcalloc (1, sizeof *self);
|
|
|
|
|
self->history = history_winit ();
|
|
|
|
|
|
|
|
|
|
HistEventW ev;
|
|
|
|
|
history_w (self->history, &ev, H_SETSIZE, HISTORY_LIMIT);
|
|
|
|
|
return self;
|
2015-05-05 08:46:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_el_hide (void *input)
|
2015-05-05 08:46:59 +02:00
|
|
|
{
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input_el *self = input;
|
2015-05-05 08:46:59 +02:00
|
|
|
if (!self->active || self->prompt_shown-- < 1)
|
|
|
|
|
return;
|
|
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
input_el__save (self);
|
|
|
|
|
input_el__erase (self);
|
2015-05-05 08:46:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_el_show (void *input)
|
2015-05-05 08:46:59 +02:00
|
|
|
{
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input_el *self = input;
|
2015-05-05 08:46:59 +02:00
|
|
|
if (!self->active || ++self->prompt_shown < 1)
|
|
|
|
|
return;
|
|
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
input_el__restore (self);
|
2015-05-05 08:46:59 +02:00
|
|
|
el_set (self->editline,
|
2016-03-07 01:08:52 +01:00
|
|
|
EL_PROMPT_ESC, input_el__make_prompt, INPUT_START_IGNORE);
|
|
|
|
|
input_el__redisplay (self);
|
2015-05-05 08:46:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
|
|
static void
|
2016-03-07 01:08:52 +01:00
|
|
|
input_el_on_tty_resized (void *input)
|
|
|
|
|
{
|
|
|
|
|
struct input_el *self = input;
|
|
|
|
|
el_resize (self->editline);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
input_el_on_tty_readable (void *input)
|
2015-05-05 08:46:59 +02:00
|
|
|
{
|
|
|
|
|
// We bind the return key to process it how we need to
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input_el *self = input;
|
2015-05-05 08:46:59 +02:00
|
|
|
|
|
|
|
|
// el_gets() with EL_UNBUFFERED doesn't work with UTF-8,
|
|
|
|
|
// we must use the wide-character interface
|
|
|
|
|
int count = 0;
|
|
|
|
|
const wchar_t *buf = el_wgets (self->editline, &count);
|
|
|
|
|
if (!buf || count-- <= 0)
|
|
|
|
|
return;
|
|
|
|
|
|
2022-08-27 14:35:07 +02:00
|
|
|
if (count == 0 && buf[0] == ('D' - 0x40) /* hardcoded VEOF in el_wgets() */)
|
2015-05-05 20:18:41 +02:00
|
|
|
{
|
|
|
|
|
el_deletestr (self->editline, 1);
|
2016-03-07 01:08:52 +01:00
|
|
|
input_el__redisplay (self);
|
|
|
|
|
input_el_ding (self);
|
2015-05-05 20:18:41 +02:00
|
|
|
}
|
2015-05-05 08:46:59 +02:00
|
|
|
}
|
|
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
input_el_destroy (void *input)
|
|
|
|
|
{
|
|
|
|
|
struct input_el *self = input;
|
|
|
|
|
LIST_FOR_EACH (struct input_el_fn, iter, self->fns)
|
|
|
|
|
{
|
|
|
|
|
free (iter->name);
|
|
|
|
|
free (iter->help);
|
|
|
|
|
ffi_closure_free (iter);
|
|
|
|
|
}
|
|
|
|
|
free (self->prompt);
|
|
|
|
|
free (self);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define XX(a) .a = input_el_ ## a,
|
|
|
|
|
static struct input_vtable input_el_vtable = { INPUT_VTABLE (XX) };
|
|
|
|
|
#undef XX
|
|
|
|
|
|
|
|
|
|
static struct input *
|
|
|
|
|
input_el_new (void)
|
|
|
|
|
{
|
|
|
|
|
struct input_el *self = xcalloc (1, sizeof *self);
|
|
|
|
|
self->super.vtable = &input_el_vtable;
|
|
|
|
|
return &self->super;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#define input_new input_el_new
|
2015-05-05 08:46:59 +02:00
|
|
|
#endif // HAVE_EDITLINE
|
|
|
|
|
|
2015-04-12 04:52:39 +02:00
|
|
|
// --- Application data --------------------------------------------------------
|
|
|
|
|
|
2020-10-02 07:09:58 +02:00
|
|
|
// All text stored in our data structures is encoded in UTF-8. Or at least
|
|
|
|
|
// should be--our only ways of retrieving strings are: via the command line
|
|
|
|
|
// (converted from locale, no room for errors), via the configuration file
|
|
|
|
|
// (restrictive ASCII grammar for bare words and an internal check for strings),
|
|
|
|
|
// and via plugins (meticulously validated).
|
|
|
|
|
//
|
|
|
|
|
// The only exception is IRC identifiers.
|
2015-04-13 00:06:08 +02:00
|
|
|
|
2022-08-25 12:28:17 +02:00
|
|
|
// ~~~ Scripting support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2015-04-21 00:29:07 +02:00
|
|
|
|
2015-11-18 23:03:21 +01:00
|
|
|
// We need a few reference countable objects with support for both strong
|
|
|
|
|
// and weak references (mainly used for scripted plugins).
|
|
|
|
|
//
|
|
|
|
|
// Beware that if you don't own the object, you will most probably want
|
|
|
|
|
// to keep the weak reference link so that you can get rid of it later.
|
|
|
|
|
// Also note that you have to make sure the user_data don't leak resources.
|
|
|
|
|
//
|
|
|
|
|
// Having a callback is more versatile than just nulling out a pointer.
|
2015-04-19 02:12:59 +02:00
|
|
|
|
2015-04-21 00:04:34 +02:00
|
|
|
/// Callback just before a reference counted object is destroyed
|
|
|
|
|
typedef void (*destroy_cb_fn) (void *object, void *user_data);
|
|
|
|
|
|
2015-11-18 23:03:21 +01:00
|
|
|
struct weak_ref_link
|
|
|
|
|
{
|
|
|
|
|
LIST_HEADER (struct weak_ref_link)
|
|
|
|
|
|
|
|
|
|
destroy_cb_fn on_destroy; ///< Called when object is destroyed
|
|
|
|
|
void *user_data; ///< User data
|
|
|
|
|
};
|
|
|
|
|
|
2015-11-21 15:15:49 +01:00
|
|
|
static struct weak_ref_link *
|
|
|
|
|
weak_ref (struct weak_ref_link **list, destroy_cb_fn cb, void *user_data)
|
|
|
|
|
{
|
|
|
|
|
struct weak_ref_link *link = xcalloc (1, sizeof *link);
|
|
|
|
|
link->on_destroy = cb;
|
|
|
|
|
link->user_data = user_data;
|
|
|
|
|
LIST_PREPEND (*list, link);
|
|
|
|
|
return link;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
weak_unref (struct weak_ref_link **list, struct weak_ref_link **link)
|
|
|
|
|
{
|
|
|
|
|
if (*link)
|
|
|
|
|
LIST_UNLINK (*list, *link);
|
|
|
|
|
free (*link);
|
|
|
|
|
*link = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-21 00:29:07 +02:00
|
|
|
#define REF_COUNTABLE_HEADER \
|
|
|
|
|
size_t ref_count; /**< Reference count */ \
|
2015-11-18 23:03:21 +01:00
|
|
|
struct weak_ref_link *weak_refs; /**< To remove any weak references */
|
2015-04-21 00:29:07 +02:00
|
|
|
|
|
|
|
|
#define REF_COUNTABLE_METHODS(name) \
|
|
|
|
|
static struct name * \
|
|
|
|
|
name ## _ref (struct name *self) \
|
|
|
|
|
{ \
|
|
|
|
|
self->ref_count++; \
|
|
|
|
|
return self; \
|
|
|
|
|
} \
|
|
|
|
|
\
|
|
|
|
|
static void \
|
|
|
|
|
name ## _unref (struct name *self) \
|
|
|
|
|
{ \
|
|
|
|
|
if (--self->ref_count) \
|
|
|
|
|
return; \
|
2015-11-18 23:03:21 +01:00
|
|
|
LIST_FOR_EACH (struct weak_ref_link, iter, self->weak_refs) \
|
|
|
|
|
{ \
|
|
|
|
|
iter->on_destroy (self, iter->user_data); \
|
|
|
|
|
free (iter); \
|
|
|
|
|
} \
|
2015-04-21 00:29:07 +02:00
|
|
|
name ## _destroy (self); \
|
2015-11-18 23:03:21 +01:00
|
|
|
} \
|
|
|
|
|
\
|
|
|
|
|
static struct weak_ref_link * \
|
|
|
|
|
name ## _weak_ref (struct name *self, destroy_cb_fn cb, void *user_data) \
|
2016-10-27 11:48:48 +02:00
|
|
|
{ return weak_ref (&self->weak_refs, cb, user_data); } \
|
2015-11-18 23:03:21 +01:00
|
|
|
\
|
|
|
|
|
static void \
|
|
|
|
|
name ## _weak_unref (struct name *self, struct weak_ref_link **link) \
|
2015-11-21 15:15:49 +01:00
|
|
|
{ weak_unref (&self->weak_refs, link); }
|
2015-04-21 00:29:07 +02:00
|
|
|
|
2015-04-12 04:52:39 +02:00
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
2016-10-27 17:03:53 +02:00
|
|
|
// Simple introspection framework to simplify exporting stuff to Lua, since
|
|
|
|
|
// there is a lot of it. While not fully automated, at least we have control
|
|
|
|
|
// over which fields are exported.
|
|
|
|
|
|
|
|
|
|
enum ispect_type
|
|
|
|
|
{
|
2016-10-27 19:10:35 +02:00
|
|
|
ISPECT_BOOL, ISPECT_INT, ISPECT_UINT, ISPECT_SIZE, ISPECT_STRING,
|
|
|
|
|
|
|
|
|
|
ISPECT_STR, ///< "struct str"
|
|
|
|
|
ISPECT_STR_MAP, ///< "struct str_map"
|
2016-10-28 00:07:49 +02:00
|
|
|
ISPECT_REF ///< Weakly referenced object
|
2016-10-27 17:03:53 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct ispect_field
|
|
|
|
|
{
|
|
|
|
|
const char *name; ///< Name of the field
|
|
|
|
|
size_t offset; ///< Offset in the structure
|
|
|
|
|
enum ispect_type type; ///< Type of the field
|
2016-10-28 00:07:49 +02:00
|
|
|
|
|
|
|
|
enum ispect_type subtype; ///< STR_MAP subtype
|
|
|
|
|
struct ispect_field *fields; ///< REF target fields
|
|
|
|
|
bool is_list; ///< REF target is a list
|
2016-10-27 17:03:53 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define ISPECT(object, field, type) \
|
2016-10-28 00:07:49 +02:00
|
|
|
{ #field, offsetof (struct object, field), ISPECT_##type, 0, NULL, false },
|
|
|
|
|
#define ISPECT_REF(object, field, is_list, ref_type) \
|
|
|
|
|
{ #field, offsetof (struct object, field), ISPECT_REF, 0, \
|
|
|
|
|
g_##ref_type##_ispect, is_list },
|
|
|
|
|
#define ISPECT_MAP(object, field, subtype) \
|
|
|
|
|
{ #field, offsetof (struct object, field), ISPECT_STR_MAP, \
|
|
|
|
|
ISPECT_##subtype, NULL, false },
|
|
|
|
|
#define ISPECT_MAP_REF(object, field, is_list, ref_type) \
|
|
|
|
|
{ #field, offsetof (struct object, field), ISPECT_STR_MAP, \
|
|
|
|
|
ISPECT_REF, g_##ref_type##_ispect, is_list },
|
2016-10-27 17:03:53 +02:00
|
|
|
|
2022-08-25 12:28:17 +02:00
|
|
|
// ~~~ Chat ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2016-10-27 17:03:53 +02:00
|
|
|
|
2015-04-19 02:12:59 +02:00
|
|
|
struct user_channel
|
2015-04-18 22:09:05 +02:00
|
|
|
{
|
2015-04-19 02:12:59 +02:00
|
|
|
LIST_HEADER (struct user_channel)
|
2015-04-18 22:09:05 +02:00
|
|
|
|
2015-04-19 02:12:59 +02:00
|
|
|
struct channel *channel; ///< Reference to channel
|
2015-04-18 22:09:05 +02:00
|
|
|
};
|
|
|
|
|
|
2015-04-19 02:12:59 +02:00
|
|
|
static struct user_channel *
|
2020-10-04 08:32:15 +02:00
|
|
|
user_channel_new (struct channel *channel)
|
2015-04-18 22:09:05 +02:00
|
|
|
{
|
2015-04-19 02:12:59 +02:00
|
|
|
struct user_channel *self = xcalloc (1, sizeof *self);
|
2020-10-04 08:32:15 +02:00
|
|
|
self->channel = channel;
|
2015-04-19 02:12:59 +02:00
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2015-04-21 00:29:07 +02:00
|
|
|
user_channel_destroy (struct user_channel *self)
|
2015-04-19 02:12:59 +02:00
|
|
|
{
|
2015-04-25 00:36:02 +02:00
|
|
|
// The "channel" reference is weak and this object should get
|
|
|
|
|
// destroyed whenever the user stops being in the channel.
|
2015-04-18 22:09:05 +02:00
|
|
|
free (self);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
2015-04-19 02:12:59 +02:00
|
|
|
// We keep references to user information in channels and buffers,
|
2015-04-21 00:04:34 +02:00
|
|
|
// and weak references in the name lookup table.
|
2015-04-19 02:12:59 +02:00
|
|
|
|
|
|
|
|
struct user
|
|
|
|
|
{
|
2015-04-21 00:29:07 +02:00
|
|
|
REF_COUNTABLE_HEADER
|
2015-04-21 00:04:34 +02:00
|
|
|
|
2015-04-19 02:12:59 +02:00
|
|
|
char *nickname; ///< Literal nickname
|
|
|
|
|
bool away; ///< User is away
|
|
|
|
|
|
2020-10-16 16:45:40 +02:00
|
|
|
struct user_channel *channels; ///< Channels the user is on (with us)
|
2015-04-19 02:12:59 +02:00
|
|
|
};
|
|
|
|
|
|
2016-10-28 00:07:49 +02:00
|
|
|
static struct ispect_field g_user_ispect[] =
|
2016-10-27 17:03:53 +02:00
|
|
|
{
|
|
|
|
|
ISPECT( user, nickname, STRING )
|
|
|
|
|
ISPECT( user, away, BOOL )
|
|
|
|
|
{}
|
|
|
|
|
};
|
|
|
|
|
|
2015-04-19 02:12:59 +02:00
|
|
|
static struct user *
|
2020-10-04 08:32:15 +02:00
|
|
|
user_new (char *nickname)
|
2015-04-19 02:12:59 +02:00
|
|
|
{
|
|
|
|
|
struct user *self = xcalloc (1, sizeof *self);
|
|
|
|
|
self->ref_count = 1;
|
2020-10-04 08:32:15 +02:00
|
|
|
self->nickname = nickname;
|
2015-04-19 02:12:59 +02:00
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
user_destroy (struct user *self)
|
|
|
|
|
{
|
|
|
|
|
free (self->nickname);
|
|
|
|
|
LIST_FOR_EACH (struct user_channel, iter, self->channels)
|
|
|
|
|
user_channel_destroy (iter);
|
|
|
|
|
free (self);
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-21 00:29:07 +02:00
|
|
|
REF_COUNTABLE_METHODS (user)
|
2015-04-19 02:12:59 +02:00
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
|
|
struct channel_user
|
|
|
|
|
{
|
|
|
|
|
LIST_HEADER (struct channel_user)
|
|
|
|
|
|
|
|
|
|
struct user *user; ///< Reference to user
|
2020-10-04 08:22:20 +02:00
|
|
|
char *prefixes; ///< Ordered @+... characters
|
2015-04-19 02:12:59 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static struct channel_user *
|
2020-10-04 08:22:20 +02:00
|
|
|
channel_user_new (struct user *user, const char *prefixes)
|
2015-04-19 02:12:59 +02:00
|
|
|
{
|
|
|
|
|
struct channel_user *self = xcalloc (1, sizeof *self);
|
2020-10-04 08:22:20 +02:00
|
|
|
self->user = user;
|
|
|
|
|
self->prefixes = xstrdup (prefixes);
|
2015-04-19 02:12:59 +02:00
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2015-04-21 00:29:07 +02:00
|
|
|
channel_user_destroy (struct channel_user *self)
|
2015-04-19 02:12:59 +02:00
|
|
|
{
|
|
|
|
|
user_unref (self->user);
|
2020-10-04 08:22:20 +02:00
|
|
|
free (self->prefixes);
|
2015-04-19 02:12:59 +02:00
|
|
|
free (self);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
2015-04-25 00:36:02 +02:00
|
|
|
// We keep references to channels in their buffers,
|
|
|
|
|
// and weak references in their users and the name lookup table.
|
|
|
|
|
|
2015-04-19 02:12:59 +02:00
|
|
|
struct channel
|
|
|
|
|
{
|
2015-04-21 00:29:07 +02:00
|
|
|
REF_COUNTABLE_HEADER
|
2015-04-21 00:04:34 +02:00
|
|
|
|
2020-10-16 16:45:40 +02:00
|
|
|
struct server *s; ///< Server
|
|
|
|
|
|
2015-04-19 02:12:59 +02:00
|
|
|
char *name; ///< Channel name
|
|
|
|
|
char *topic; ///< Channel topic
|
|
|
|
|
|
2015-06-04 22:19:32 +02:00
|
|
|
// XXX: write something like an ordered set of characters object?
|
|
|
|
|
struct str no_param_modes; ///< No parameter channel modes
|
|
|
|
|
struct str_map param_modes; ///< Parametrized channel modes
|
|
|
|
|
|
2015-04-19 02:12:59 +02:00
|
|
|
struct channel_user *users; ///< Channel users
|
2017-01-23 23:50:27 +01:00
|
|
|
struct strv names_buf; ///< Buffer for RPL_NAMREPLY
|
2016-10-23 16:29:55 +02:00
|
|
|
size_t users_len; ///< User count
|
2015-07-12 00:30:10 +02:00
|
|
|
|
|
|
|
|
bool left_manually; ///< Don't rejoin on reconnect
|
2020-10-16 16:45:40 +02:00
|
|
|
bool show_names_after_who; ///< RPL_ENDOFWHO delays RPL_ENDOFNAMES
|
2015-04-19 02:12:59 +02:00
|
|
|
};
|
|
|
|
|
|
2016-10-28 00:07:49 +02:00
|
|
|
static struct ispect_field g_channel_ispect[] =
|
2016-10-27 17:03:53 +02:00
|
|
|
{
|
2016-10-28 00:07:49 +02:00
|
|
|
ISPECT( channel, name, STRING )
|
|
|
|
|
ISPECT( channel, topic, STRING )
|
|
|
|
|
ISPECT( channel, no_param_modes, STR )
|
|
|
|
|
ISPECT_MAP( channel, param_modes, STRING )
|
|
|
|
|
ISPECT( channel, users_len, SIZE )
|
|
|
|
|
ISPECT( channel, left_manually, BOOL )
|
2016-10-27 17:03:53 +02:00
|
|
|
{}
|
|
|
|
|
};
|
|
|
|
|
|
2015-04-19 02:12:59 +02:00
|
|
|
static struct channel *
|
2020-10-16 16:45:40 +02:00
|
|
|
channel_new (struct server *s, char *name)
|
2015-04-19 02:12:59 +02:00
|
|
|
{
|
|
|
|
|
struct channel *self = xcalloc (1, sizeof *self);
|
|
|
|
|
self->ref_count = 1;
|
2020-10-16 16:45:40 +02:00
|
|
|
self->s = s;
|
2020-10-04 08:32:15 +02:00
|
|
|
self->name = name;
|
2017-06-22 22:39:39 +02:00
|
|
|
self->no_param_modes = str_make ();
|
|
|
|
|
self->param_modes = str_map_make (free);
|
|
|
|
|
self->names_buf = strv_make ();
|
2015-04-19 02:12:59 +02:00
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2015-04-21 00:04:34 +02:00
|
|
|
channel_destroy (struct channel *self)
|
2015-04-19 02:12:59 +02:00
|
|
|
{
|
|
|
|
|
free (self->name);
|
|
|
|
|
free (self->topic);
|
2015-06-04 22:19:32 +02:00
|
|
|
str_free (&self->no_param_modes);
|
|
|
|
|
str_map_free (&self->param_modes);
|
2015-04-25 00:36:02 +02:00
|
|
|
// Owner has to make sure we have no users by now
|
2016-10-23 16:29:55 +02:00
|
|
|
hard_assert (!self->users && !self->users_len);
|
2017-01-23 23:50:27 +01:00
|
|
|
strv_free (&self->names_buf);
|
2015-04-19 02:12:59 +02:00
|
|
|
free (self);
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-21 00:29:07 +02:00
|
|
|
REF_COUNTABLE_METHODS (channel)
|
2015-04-19 02:12:59 +02:00
|
|
|
|
2022-09-10 15:55:13 +02:00
|
|
|
// ~~~ Attribute utilities ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
enum
|
|
|
|
|
{
|
|
|
|
|
TEXT_BOLD = 1 << 0,
|
|
|
|
|
TEXT_ITALIC = 1 << 1,
|
|
|
|
|
TEXT_UNDERLINE = 1 << 2,
|
|
|
|
|
TEXT_INVERSE = 1 << 3,
|
|
|
|
|
TEXT_BLINK = 1 << 4,
|
|
|
|
|
TEXT_CROSSED_OUT = 1 << 5,
|
|
|
|
|
TEXT_MONOSPACE = 1 << 6
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Similar to code in liberty-tui.c.
|
|
|
|
|
struct attrs
|
|
|
|
|
{
|
|
|
|
|
short fg; ///< Foreground (256-colour cube or -1)
|
|
|
|
|
short bg; ///< Background (256-colour cube or -1)
|
|
|
|
|
unsigned attrs; ///< TEXT_* mask
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// Decode attributes in the value using a subset of the git config format,
|
|
|
|
|
/// ignoring all errors since it doesn't affect functionality
|
|
|
|
|
static struct attrs
|
|
|
|
|
attrs_decode (const char *value)
|
|
|
|
|
{
|
|
|
|
|
struct strv v = strv_make ();
|
|
|
|
|
cstr_split (value, " ", true, &v);
|
|
|
|
|
|
|
|
|
|
int colors = 0;
|
|
|
|
|
struct attrs attrs = { -1, -1, 0 };
|
|
|
|
|
for (char **it = v.vector; *it; it++)
|
|
|
|
|
{
|
|
|
|
|
char *end = NULL;
|
|
|
|
|
long n = strtol (*it, &end, 10);
|
|
|
|
|
if (*it != end && !*end && n >= SHRT_MIN && n <= SHRT_MAX)
|
|
|
|
|
{
|
|
|
|
|
if (colors == 0) attrs.fg = n;
|
|
|
|
|
if (colors == 1) attrs.bg = n;
|
|
|
|
|
colors++;
|
|
|
|
|
}
|
|
|
|
|
else if (!strcmp (*it, "bold")) attrs.attrs |= TEXT_BOLD;
|
|
|
|
|
else if (!strcmp (*it, "italic")) attrs.attrs |= TEXT_ITALIC;
|
|
|
|
|
else if (!strcmp (*it, "ul")) attrs.attrs |= TEXT_UNDERLINE;
|
|
|
|
|
else if (!strcmp (*it, "reverse")) attrs.attrs |= TEXT_INVERSE;
|
|
|
|
|
else if (!strcmp (*it, "blink")) attrs.attrs |= TEXT_BLINK;
|
|
|
|
|
else if (!strcmp (*it, "strike")) attrs.attrs |= TEXT_CROSSED_OUT;
|
|
|
|
|
}
|
|
|
|
|
strv_free (&v);
|
|
|
|
|
return attrs;
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-25 12:28:17 +02:00
|
|
|
// ~~~ Buffers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2015-04-19 02:12:59 +02:00
|
|
|
|
2015-06-28 20:48:43 +02:00
|
|
|
enum formatter_item_type
|
|
|
|
|
{
|
2016-01-31 20:07:20 +01:00
|
|
|
FORMATTER_ITEM_END, ///< Sentinel value for arrays
|
2015-06-28 20:48:43 +02:00
|
|
|
FORMATTER_ITEM_TEXT, ///< Text
|
|
|
|
|
FORMATTER_ITEM_ATTR, ///< Formatting attributes
|
2020-10-11 17:36:21 +02:00
|
|
|
FORMATTER_ITEM_FG_COLOR, ///< Foreground colour
|
|
|
|
|
FORMATTER_ITEM_BG_COLOR, ///< Background colour
|
2021-08-29 12:07:31 +02:00
|
|
|
FORMATTER_ITEM_SIMPLE, ///< Toggle IRC formatting
|
2015-06-28 20:48:43 +02:00
|
|
|
FORMATTER_ITEM_IGNORE_ATTR ///< Un/set attribute ignoration
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct formatter_item
|
|
|
|
|
{
|
2016-01-31 20:07:20 +01:00
|
|
|
enum formatter_item_type type : 16; ///< Type of this item
|
2022-08-27 05:22:53 +02:00
|
|
|
int attribute : 16; ///< Attribute ID or a TEXT_* mask
|
2022-09-05 22:34:20 +02:00
|
|
|
int color; ///< Colour ([256 << 16] | 16)
|
2016-01-31 20:07:20 +01:00
|
|
|
char *text; ///< String
|
2015-06-28 20:48:43 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void
|
2016-01-31 20:07:20 +01:00
|
|
|
formatter_item_free (struct formatter_item *self)
|
2015-06-28 20:48:43 +02:00
|
|
|
{
|
|
|
|
|
free (self->text);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
|
|
struct formatter
|
|
|
|
|
{
|
|
|
|
|
struct app_context *ctx; ///< Application context
|
|
|
|
|
struct server *s; ///< Server
|
|
|
|
|
|
2022-09-13 20:23:48 +02:00
|
|
|
bool clean; ///< Assume ATTR_RESET
|
2015-06-28 20:48:43 +02:00
|
|
|
struct formatter_item *items; ///< Items
|
2016-01-31 20:07:20 +01:00
|
|
|
size_t items_len; ///< Items used
|
|
|
|
|
size_t items_alloc; ///< Items allocated
|
2015-06-28 20:48:43 +02:00
|
|
|
};
|
|
|
|
|
|
2017-06-22 22:45:25 +02:00
|
|
|
static struct formatter
|
|
|
|
|
formatter_make (struct app_context *ctx, struct server *s)
|
2015-06-28 20:48:43 +02:00
|
|
|
{
|
2022-09-13 20:23:48 +02:00
|
|
|
struct formatter self = { .ctx = ctx, .s = s, .clean = true };
|
2024-08-08 09:13:14 +02:00
|
|
|
self.items = xcalloc ((self.items_alloc = 16), sizeof *self.items);
|
2017-06-22 22:45:25 +02:00
|
|
|
return self;
|
2015-06-28 20:48:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
formatter_free (struct formatter *self)
|
|
|
|
|
{
|
2016-01-31 20:07:20 +01:00
|
|
|
for (size_t i = 0; i < self->items_len; i++)
|
|
|
|
|
formatter_item_free (&self->items[i]);
|
|
|
|
|
free (self->items);
|
2015-06-28 20:48:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
2015-04-12 04:52:39 +02:00
|
|
|
enum buffer_line_flags
|
|
|
|
|
{
|
2022-09-02 12:31:42 +02:00
|
|
|
BUFFER_LINE_SKIP_FILE = 1 << 0, ///< Don't log this to file
|
|
|
|
|
BUFFER_LINE_UNIMPORTANT = 1 << 1, ///< Joins, parts, similar spam
|
2015-06-28 02:49:28 +02:00
|
|
|
BUFFER_LINE_HIGHLIGHT = 1 << 2, ///< The user was highlighted by this
|
2022-09-02 12:31:42 +02:00
|
|
|
};
|
|
|
|
|
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
// NOTE: This sequence must match up with xC-proto, only one lower.
|
2022-09-02 12:31:42 +02:00
|
|
|
enum buffer_line_rendition
|
|
|
|
|
{
|
|
|
|
|
BUFFER_LINE_BARE, ///< Unadorned
|
|
|
|
|
BUFFER_LINE_INDENT, ///< Just indent the line
|
|
|
|
|
BUFFER_LINE_STATUS, ///< Status message
|
|
|
|
|
BUFFER_LINE_ERROR, ///< Error message
|
|
|
|
|
BUFFER_LINE_JOIN, ///< Join arrow
|
|
|
|
|
BUFFER_LINE_PART, ///< Part arrow
|
2022-09-05 22:53:34 +02:00
|
|
|
BUFFER_LINE_ACTION, ///< Highlighted asterisk
|
2015-04-25 13:41:10 +02:00
|
|
|
};
|
|
|
|
|
|
2015-04-12 04:52:39 +02:00
|
|
|
struct buffer_line
|
|
|
|
|
{
|
|
|
|
|
LIST_HEADER (struct buffer_line)
|
|
|
|
|
|
2022-09-02 12:31:42 +02:00
|
|
|
unsigned flags; ///< Functional flags
|
|
|
|
|
enum buffer_line_rendition r; ///< What the line should look like
|
2015-04-12 04:52:39 +02:00
|
|
|
time_t when; ///< Time of the event
|
2016-01-31 20:07:20 +01:00
|
|
|
struct formatter_item items[]; ///< Line data
|
2015-04-12 04:52:39 +02:00
|
|
|
};
|
|
|
|
|
|
2016-01-31 20:07:20 +01:00
|
|
|
/// Create a new buffer line stealing all data from the provided formatter
|
2015-04-13 00:06:08 +02:00
|
|
|
struct buffer_line *
|
2016-01-17 22:15:48 +01:00
|
|
|
buffer_line_new (struct formatter *f)
|
2015-04-13 00:06:08 +02:00
|
|
|
{
|
2016-01-31 20:07:20 +01:00
|
|
|
// We make space for one more item that gets initialized to all zeros,
|
|
|
|
|
// meaning FORMATTER_ITEM_END (because it's the first value in the enum)
|
|
|
|
|
size_t items_size = f->items_len * sizeof *f->items;
|
|
|
|
|
struct buffer_line *self =
|
|
|
|
|
xcalloc (1, sizeof *self + items_size + sizeof *self->items);
|
|
|
|
|
memcpy (self->items, f->items, items_size);
|
|
|
|
|
|
|
|
|
|
// We've stolen pointers from the formatter, let's destroy it altogether
|
|
|
|
|
free (f->items);
|
|
|
|
|
memset (f, 0, sizeof *f);
|
2015-04-13 00:06:08 +02:00
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
buffer_line_destroy (struct buffer_line *self)
|
|
|
|
|
{
|
2016-01-31 20:07:20 +01:00
|
|
|
for (struct formatter_item *iter = self->items; iter->type; iter++)
|
|
|
|
|
formatter_item_free (iter);
|
2015-04-13 00:06:08 +02:00
|
|
|
free (self);
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-12 04:52:39 +02:00
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
|
|
enum buffer_type
|
|
|
|
|
{
|
|
|
|
|
BUFFER_GLOBAL, ///< Global information
|
|
|
|
|
BUFFER_SERVER, ///< Server-related messages
|
|
|
|
|
BUFFER_CHANNEL, ///< Channels
|
|
|
|
|
BUFFER_PM ///< Private messages (query)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct buffer
|
|
|
|
|
{
|
|
|
|
|
LIST_HEADER (struct buffer)
|
2015-11-18 23:49:09 +01:00
|
|
|
REF_COUNTABLE_HEADER
|
2015-04-12 04:52:39 +02:00
|
|
|
|
|
|
|
|
enum buffer_type type; ///< Type of the buffer
|
|
|
|
|
char *name; ///< The name of the buffer
|
|
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input *input; ///< API for "input_data"
|
|
|
|
|
input_buffer_t input_data; ///< User interface data
|
2015-04-15 02:10:21 +02:00
|
|
|
|
2015-04-13 00:06:08 +02:00
|
|
|
// Buffer contents:
|
2015-04-12 04:52:39 +02:00
|
|
|
|
2015-04-13 00:06:08 +02:00
|
|
|
struct buffer_line *lines; ///< All lines in this buffer
|
|
|
|
|
struct buffer_line *lines_tail; ///< The tail of buffer lines
|
|
|
|
|
unsigned lines_count; ///< How many lines we have
|
2015-04-12 04:52:39 +02:00
|
|
|
|
2016-03-26 03:09:29 +01:00
|
|
|
unsigned new_messages_count; ///< # messages since last left
|
|
|
|
|
unsigned new_unimportant_count; ///< How much of that is unimportant
|
2015-06-21 04:00:20 +02:00
|
|
|
bool highlighted; ///< We've been highlighted
|
2016-10-23 16:53:31 +02:00
|
|
|
bool hide_unimportant; ///< Hide unimportant messages
|
2015-04-13 00:06:08 +02:00
|
|
|
|
2015-07-04 15:24:08 +02:00
|
|
|
FILE *log_file; ///< Log file
|
|
|
|
|
|
2015-04-19 02:12:59 +02:00
|
|
|
// Origin information:
|
2015-04-12 04:52:39 +02:00
|
|
|
|
2015-04-30 00:02:14 +02:00
|
|
|
struct server *server; ///< Reference to server
|
2015-04-19 02:12:59 +02:00
|
|
|
struct channel *channel; ///< Reference to channel
|
2015-04-30 00:02:14 +02:00
|
|
|
struct user *user; ///< Reference to user
|
2015-04-12 04:52:39 +02:00
|
|
|
};
|
|
|
|
|
|
2016-10-28 00:07:49 +02:00
|
|
|
static struct ispect_field g_server_ispect[];
|
|
|
|
|
static struct ispect_field g_buffer_ispect[] =
|
2016-10-27 17:03:53 +02:00
|
|
|
{
|
2016-10-28 00:07:49 +02:00
|
|
|
ISPECT( buffer, name, STRING )
|
|
|
|
|
ISPECT( buffer, new_messages_count, UINT )
|
|
|
|
|
ISPECT( buffer, new_unimportant_count, UINT )
|
|
|
|
|
ISPECT( buffer, highlighted, BOOL )
|
|
|
|
|
ISPECT( buffer, hide_unimportant, BOOL )
|
|
|
|
|
ISPECT_REF( buffer, server, false, server )
|
|
|
|
|
ISPECT_REF( buffer, channel, false, channel )
|
|
|
|
|
ISPECT_REF( buffer, user, false, user )
|
2016-10-27 17:03:53 +02:00
|
|
|
{}
|
|
|
|
|
};
|
|
|
|
|
|
2015-04-12 04:52:39 +02:00
|
|
|
static struct buffer *
|
2020-10-31 23:42:49 +01:00
|
|
|
buffer_new (struct input *input, enum buffer_type type, char *name)
|
2015-04-12 04:52:39 +02:00
|
|
|
{
|
|
|
|
|
struct buffer *self = xcalloc (1, sizeof *self);
|
2015-11-18 23:49:09 +01:00
|
|
|
self->ref_count = 1;
|
2016-03-07 01:08:52 +01:00
|
|
|
self->input = input;
|
|
|
|
|
self->input_data = CALL (input, buffer_new);
|
2020-10-31 23:42:49 +01:00
|
|
|
self->type = type;
|
|
|
|
|
self->name = name;
|
2015-04-12 04:52:39 +02:00
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
buffer_destroy (struct buffer *self)
|
|
|
|
|
{
|
|
|
|
|
free (self->name);
|
2015-05-05 03:23:53 +02:00
|
|
|
if (self->input_data)
|
2016-03-07 01:08:52 +01:00
|
|
|
{
|
|
|
|
|
#ifdef HAVE_READLINE
|
|
|
|
|
// FIXME: can't really free "history" contents from here, as we cannot
|
|
|
|
|
// be sure that the user interface pointer is valid and usable
|
|
|
|
|
input_rl__buffer_destroy_wo_history (self->input_data);
|
|
|
|
|
#else // ! HAVE_READLINE
|
|
|
|
|
CALL_ (self->input, buffer_destroy, self->input_data);
|
|
|
|
|
#endif // ! HAVE_READLINE
|
|
|
|
|
}
|
2015-04-19 02:12:59 +02:00
|
|
|
LIST_FOR_EACH (struct buffer_line, iter, self->lines)
|
|
|
|
|
buffer_line_destroy (iter);
|
2015-07-04 15:24:08 +02:00
|
|
|
if (self->log_file)
|
|
|
|
|
(void) fclose (self->log_file);
|
2015-04-19 02:12:59 +02:00
|
|
|
if (self->user)
|
|
|
|
|
user_unref (self->user);
|
|
|
|
|
if (self->channel)
|
|
|
|
|
channel_unref (self->channel);
|
2015-04-12 04:52:39 +02:00
|
|
|
free (self);
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-18 23:49:09 +01:00
|
|
|
REF_COUNTABLE_METHODS (buffer)
|
|
|
|
|
#define buffer_ref do_not_use_dangerous
|
|
|
|
|
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
// ~~~ Relay ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
struct client
|
|
|
|
|
{
|
|
|
|
|
LIST_HEADER (struct client)
|
|
|
|
|
struct app_context *ctx; ///< Application context
|
|
|
|
|
|
|
|
|
|
// TODO: Convert this all to TLS, and only TLS, with required client cert.
|
|
|
|
|
// That means replacing plumbing functions with the /other/ set from xD.
|
|
|
|
|
|
|
|
|
|
int socket_fd; ///< The TCP socket
|
|
|
|
|
struct str read_buffer; ///< Unprocessed input
|
|
|
|
|
struct str write_buffer; ///< Output yet to be sent out
|
|
|
|
|
|
|
|
|
|
uint32_t event_seq; ///< Outgoing message counter
|
|
|
|
|
bool initialized; ///< Initial sync took place
|
2025-05-09 22:34:25 +02:00
|
|
|
bool closing; ///< We're closing the connection
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
|
|
|
|
|
struct poller_fd socket_event; ///< The socket can be read/written to
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static struct client *
|
|
|
|
|
client_new (void)
|
|
|
|
|
{
|
|
|
|
|
struct client *self = xcalloc (1, sizeof *self);
|
|
|
|
|
self->socket_fd = -1;
|
|
|
|
|
self->read_buffer = str_make ();
|
|
|
|
|
self->write_buffer = str_make ();
|
|
|
|
|
return self;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
client_destroy (struct client *self)
|
|
|
|
|
{
|
|
|
|
|
if (!soft_assert (self->socket_fd == -1))
|
|
|
|
|
xclose (self->socket_fd);
|
|
|
|
|
|
|
|
|
|
str_free (&self->read_buffer);
|
|
|
|
|
str_free (&self->write_buffer);
|
|
|
|
|
free (self);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void client_kill (struct client *c);
|
|
|
|
|
|
2022-08-25 12:28:17 +02:00
|
|
|
// ~~~ Server ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2015-04-12 04:52:39 +02:00
|
|
|
|
2015-07-15 23:34:36 +02:00
|
|
|
// The only real purpose of this is to abstract away TLS
|
2015-07-03 20:32:31 +02:00
|
|
|
struct transport
|
|
|
|
|
{
|
|
|
|
|
/// Initialize the transport
|
2015-12-09 00:53:56 +01:00
|
|
|
bool (*init) (struct server *s, const char *hostname, struct error **e);
|
2015-07-03 20:32:31 +02:00
|
|
|
/// Destroy the user data pointer
|
|
|
|
|
void (*cleanup) (struct server *s);
|
|
|
|
|
|
|
|
|
|
/// The underlying socket may have become readable, update `read_buffer'
|
2016-01-06 23:37:30 +01:00
|
|
|
enum socket_io_result (*try_read) (struct server *s);
|
2015-07-03 20:32:31 +02:00
|
|
|
/// The underlying socket may have become writeable, flush `write_buffer'
|
2016-01-06 23:37:30 +01:00
|
|
|
enum socket_io_result (*try_write) (struct server *s);
|
2015-07-03 20:32:31 +02:00
|
|
|
/// Return event mask to use in the poller
|
|
|
|
|
int (*get_poll_events) (struct server *s);
|
|
|
|
|
|
|
|
|
|
/// Called just before closing the connection from our side
|
|
|
|
|
void (*in_before_shutdown) (struct server *s);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
2015-05-08 17:39:26 +02:00
|
|
|
enum server_state
|
|
|
|
|
{
|
|
|
|
|
IRC_DISCONNECTED, ///< Not connected
|
2015-05-09 22:10:58 +02:00
|
|
|
IRC_CONNECTING, ///< Connecting to the server
|
2015-05-08 17:39:26 +02:00
|
|
|
IRC_CONNECTED, ///< Trying to register
|
2015-07-03 20:32:31 +02:00
|
|
|
IRC_REGISTERED, ///< We can chat now
|
|
|
|
|
IRC_CLOSING, ///< Flushing output before shutdown
|
2025-05-09 22:34:25 +02:00
|
|
|
IRC_HALF_CLOSED ///< Connection shut down from our side
|
2015-05-08 17:39:26 +02:00
|
|
|
};
|
|
|
|
|
|
2015-06-20 22:42:38 +02:00
|
|
|
/// Convert an IRC identifier character to lower-case
|
|
|
|
|
typedef int (*irc_tolower_fn) (int);
|
|
|
|
|
|
|
|
|
|
/// Key conversion function for hashmap lookups
|
|
|
|
|
typedef size_t (*irc_strxfrm_fn) (char *, const char *, size_t);
|
|
|
|
|
|
2015-04-30 00:02:14 +02:00
|
|
|
struct server
|
2015-04-12 04:52:39 +02:00
|
|
|
{
|
2015-11-18 23:49:09 +01:00
|
|
|
REF_COUNTABLE_HEADER
|
2015-04-30 00:02:14 +02:00
|
|
|
struct app_context *ctx; ///< Application context
|
2015-05-09 23:30:04 +02:00
|
|
|
|
2015-05-16 12:33:59 +02:00
|
|
|
char *name; ///< Server identifier
|
|
|
|
|
struct buffer *buffer; ///< The buffer for this server
|
2015-08-17 00:08:19 +02:00
|
|
|
struct config_item *config; ///< Configuration root
|
2015-04-13 00:06:08 +02:00
|
|
|
|
2015-05-16 12:33:59 +02:00
|
|
|
// Connection:
|
|
|
|
|
|
2015-05-08 17:39:26 +02:00
|
|
|
enum server_state state; ///< Connection state
|
2015-05-09 22:10:58 +02:00
|
|
|
struct connector *connector; ///< Connection establisher
|
2015-07-20 23:31:26 +02:00
|
|
|
struct socks_connector *socks_conn; ///< SOCKS connection establisher
|
2015-07-17 21:18:05 +02:00
|
|
|
unsigned reconnect_attempt; ///< Number of reconnect attempt
|
2015-05-16 12:45:39 +02:00
|
|
|
bool manual_disconnect; ///< Don't reconnect after disconnect
|
2015-05-08 17:39:26 +02:00
|
|
|
|
|
|
|
|
int socket; ///< Socket FD of the server
|
2015-04-12 04:52:39 +02:00
|
|
|
struct str read_buffer; ///< Input yet to be processed
|
2015-07-03 20:32:31 +02:00
|
|
|
struct str write_buffer; ///< Outut yet to be be sent out
|
|
|
|
|
struct poller_fd socket_event; ///< We can read from the socket
|
2015-04-12 04:52:39 +02:00
|
|
|
|
2015-07-03 20:32:31 +02:00
|
|
|
struct transport *transport; ///< Transport method
|
|
|
|
|
void *transport_data; ///< Transport data
|
2015-04-13 00:06:08 +02:00
|
|
|
|
2015-06-20 21:10:50 +02:00
|
|
|
// Events:
|
|
|
|
|
|
|
|
|
|
struct poller_timer ping_tmr; ///< We should send a ping
|
|
|
|
|
struct poller_timer timeout_tmr; ///< Connection seems to be dead
|
|
|
|
|
struct poller_timer reconnect_tmr; ///< We should reconnect now
|
2015-07-18 14:12:34 +02:00
|
|
|
struct poller_timer autojoin_tmr; ///< Re/join channels as appropriate
|
2015-04-19 02:12:59 +02:00
|
|
|
|
2015-05-16 12:33:59 +02:00
|
|
|
// IRC:
|
2015-04-30 00:02:14 +02:00
|
|
|
|
2015-06-20 21:10:50 +02:00
|
|
|
// TODO: an output queue to prevent excess floods (this will be needed
|
|
|
|
|
// especially for away status polling)
|
|
|
|
|
|
2015-06-23 22:10:16 +02:00
|
|
|
bool rehashing; ///< Rehashing IRC identifiers
|
|
|
|
|
|
2015-04-19 02:12:59 +02:00
|
|
|
struct str_map irc_users; ///< IRC user data
|
|
|
|
|
struct str_map irc_channels; ///< IRC channel data
|
|
|
|
|
struct str_map irc_buffer_map; ///< Maps IRC identifiers to buffers
|
2015-04-18 22:09:05 +02:00
|
|
|
|
2015-04-20 22:09:56 +02:00
|
|
|
struct user *irc_user; ///< Our own user
|
2015-07-09 02:46:31 +02:00
|
|
|
int nick_counter; ///< Iterates "nicks" when registering
|
2022-09-21 16:32:08 +02:00
|
|
|
struct str irc_user_modes; ///< Our current user modes
|
2015-04-20 22:09:56 +02:00
|
|
|
char *irc_user_host; ///< Our current user@host
|
2015-11-22 23:04:51 +01:00
|
|
|
bool autoaway_active; ///< Autoaway is currently active
|
2015-04-15 16:54:05 +02:00
|
|
|
|
2022-09-07 19:04:23 +02:00
|
|
|
struct strv outstanding_joins; ///< JOINs we expect a response to
|
|
|
|
|
|
2021-05-28 01:25:10 +02:00
|
|
|
struct strv cap_ls_buf; ///< Buffer for IRCv3.2 CAP LS
|
2020-10-16 16:45:40 +02:00
|
|
|
bool cap_echo_message; ///< Whether the server echoes messages
|
|
|
|
|
bool cap_away_notify; ///< Whether we get AWAY notifications
|
2021-05-28 02:11:23 +02:00
|
|
|
bool cap_sasl; ///< Whether SASL is available
|
2015-06-20 21:38:04 +02:00
|
|
|
|
2015-06-01 21:45:04 +02:00
|
|
|
// Server-specific information (from RPL_ISUPPORT):
|
|
|
|
|
|
2015-06-20 22:42:38 +02:00
|
|
|
irc_tolower_fn irc_tolower; ///< Server tolower()
|
|
|
|
|
irc_strxfrm_fn irc_strxfrm; ///< Server strxfrm()
|
2015-06-01 21:45:04 +02:00
|
|
|
|
|
|
|
|
char *irc_chantypes; ///< Channel types (name prefixes)
|
|
|
|
|
char *irc_idchan_prefixes; ///< Prefixes for "safe channels"
|
|
|
|
|
char *irc_statusmsg; ///< Prefixes for channel targets
|
|
|
|
|
|
2021-06-17 12:08:08 +02:00
|
|
|
char irc_extban_prefix; ///< EXTBAN prefix or \0
|
|
|
|
|
char *irc_extban_types; ///< EXTBAN types
|
|
|
|
|
|
2015-06-03 23:17:10 +02:00
|
|
|
char *irc_chanmodes_list; ///< Channel modes for lists
|
|
|
|
|
char *irc_chanmodes_param_always; ///< Channel modes with mandatory param
|
|
|
|
|
char *irc_chanmodes_param_when_set; ///< Channel modes with param when set
|
|
|
|
|
char *irc_chanmodes_param_never; ///< Channel modes without param
|
|
|
|
|
|
2015-06-01 21:45:04 +02:00
|
|
|
char *irc_chanuser_prefixes; ///< Channel user prefixes
|
|
|
|
|
char *irc_chanuser_modes; ///< Channel user modes
|
|
|
|
|
|
2015-06-17 21:19:09 +02:00
|
|
|
unsigned irc_max_modes; ///< Max parametrized modes per command
|
2015-04-30 00:02:14 +02:00
|
|
|
};
|
|
|
|
|
|
2016-10-28 00:07:49 +02:00
|
|
|
static struct ispect_field g_server_ispect[] =
|
2016-10-27 17:03:53 +02:00
|
|
|
{
|
2016-10-28 00:07:49 +02:00
|
|
|
ISPECT( server, name, STRING )
|
|
|
|
|
ISPECT( server, state, INT )
|
|
|
|
|
ISPECT( server, reconnect_attempt, UINT )
|
|
|
|
|
ISPECT( server, manual_disconnect, BOOL )
|
|
|
|
|
ISPECT( server, irc_user_host, STRING )
|
|
|
|
|
ISPECT( server, autoaway_active, BOOL )
|
|
|
|
|
ISPECT( server, cap_echo_message, BOOL )
|
|
|
|
|
ISPECT_REF( server, buffer, false, buffer )
|
2016-10-27 18:46:27 +02:00
|
|
|
|
|
|
|
|
// TODO: either rename the underlying field or fix the plugins
|
2016-10-27 20:58:14 +02:00
|
|
|
{ "user", offsetof (struct server, irc_user),
|
2016-10-28 00:07:49 +02:00
|
|
|
ISPECT_REF, 0, g_user_ispect, false },
|
2022-09-21 16:32:08 +02:00
|
|
|
{ "user_mode", offsetof (struct server, irc_user_modes),
|
2016-10-28 00:07:49 +02:00
|
|
|
ISPECT_STR, 0, NULL, false },
|
2016-10-27 18:46:27 +02:00
|
|
|
|
2016-10-27 17:03:53 +02:00
|
|
|
{}
|
|
|
|
|
};
|
|
|
|
|
|
2015-04-30 00:02:14 +02:00
|
|
|
static void on_irc_timeout (void *user_data);
|
2015-05-09 22:10:58 +02:00
|
|
|
static void on_irc_ping_timeout (void *user_data);
|
2015-07-18 14:12:34 +02:00
|
|
|
static void on_irc_autojoin_timeout (void *user_data);
|
2015-05-09 22:10:58 +02:00
|
|
|
static void irc_initiate_connect (struct server *s);
|
2015-04-30 00:02:14 +02:00
|
|
|
|
2015-06-20 22:42:38 +02:00
|
|
|
static void
|
|
|
|
|
server_init_specifics (struct server *self)
|
|
|
|
|
{
|
|
|
|
|
// Defaults as per the RPL_ISUPPORT drafts, or RFC 1459
|
|
|
|
|
|
|
|
|
|
self->irc_tolower = irc_tolower;
|
|
|
|
|
self->irc_strxfrm = irc_strxfrm;
|
|
|
|
|
|
|
|
|
|
self->irc_chantypes = xstrdup ("#&");
|
|
|
|
|
self->irc_idchan_prefixes = xstrdup ("");
|
|
|
|
|
self->irc_statusmsg = xstrdup ("");
|
|
|
|
|
|
2021-06-17 12:08:08 +02:00
|
|
|
self->irc_extban_prefix = 0;
|
|
|
|
|
self->irc_extban_types = xstrdup ("");
|
|
|
|
|
|
2015-06-20 22:42:38 +02:00
|
|
|
self->irc_chanmodes_list = xstrdup ("b");
|
|
|
|
|
self->irc_chanmodes_param_always = xstrdup ("k");
|
|
|
|
|
self->irc_chanmodes_param_when_set = xstrdup ("l");
|
|
|
|
|
self->irc_chanmodes_param_never = xstrdup ("imnpst");
|
|
|
|
|
|
|
|
|
|
self->irc_chanuser_prefixes = xstrdup ("@+");
|
|
|
|
|
self->irc_chanuser_modes = xstrdup ("ov");
|
|
|
|
|
|
|
|
|
|
self->irc_max_modes = 3;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
server_free_specifics (struct server *self)
|
|
|
|
|
{
|
|
|
|
|
free (self->irc_chantypes);
|
|
|
|
|
free (self->irc_idchan_prefixes);
|
|
|
|
|
free (self->irc_statusmsg);
|
|
|
|
|
|
2021-06-17 12:08:08 +02:00
|
|
|
free (self->irc_extban_types);
|
|
|
|
|
|
2015-06-20 22:42:38 +02:00
|
|
|
free (self->irc_chanmodes_list);
|
|
|
|
|
free (self->irc_chanmodes_param_always);
|
|
|
|
|
free (self->irc_chanmodes_param_when_set);
|
|
|
|
|
free (self->irc_chanmodes_param_never);
|
|
|
|
|
|
|
|
|
|
free (self->irc_chanuser_prefixes);
|
|
|
|
|
free (self->irc_chanuser_modes);
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-18 23:49:09 +01:00
|
|
|
static struct server *
|
|
|
|
|
server_new (struct poller *poller)
|
2015-04-30 00:02:14 +02:00
|
|
|
{
|
2015-11-18 23:49:09 +01:00
|
|
|
struct server *self = xcalloc (1, sizeof *self);
|
|
|
|
|
self->ref_count = 1;
|
2015-05-16 12:33:59 +02:00
|
|
|
|
2015-05-08 17:39:26 +02:00
|
|
|
self->socket = -1;
|
2017-06-22 22:39:39 +02:00
|
|
|
self->read_buffer = str_make ();
|
|
|
|
|
self->write_buffer = str_make ();
|
2015-05-08 17:39:26 +02:00
|
|
|
self->state = IRC_DISCONNECTED;
|
2015-04-30 00:02:14 +02:00
|
|
|
|
2017-06-22 22:39:39 +02:00
|
|
|
self->timeout_tmr = poller_timer_make (poller);
|
2015-06-20 21:10:50 +02:00
|
|
|
self->timeout_tmr.dispatcher = on_irc_timeout;
|
|
|
|
|
self->timeout_tmr.user_data = self;
|
|
|
|
|
|
2017-06-22 22:39:39 +02:00
|
|
|
self->ping_tmr = poller_timer_make (poller);
|
2015-06-20 21:10:50 +02:00
|
|
|
self->ping_tmr.dispatcher = on_irc_ping_timeout;
|
|
|
|
|
self->ping_tmr.user_data = self;
|
|
|
|
|
|
2017-06-22 22:39:39 +02:00
|
|
|
self->reconnect_tmr = poller_timer_make (poller);
|
2015-06-20 21:10:50 +02:00
|
|
|
self->reconnect_tmr.dispatcher = (poller_timer_fn) irc_initiate_connect;
|
|
|
|
|
self->reconnect_tmr.user_data = self;
|
|
|
|
|
|
2017-06-22 22:39:39 +02:00
|
|
|
self->autojoin_tmr = poller_timer_make (poller);
|
2015-07-18 14:12:34 +02:00
|
|
|
self->autojoin_tmr.dispatcher = on_irc_autojoin_timeout;
|
|
|
|
|
self->autojoin_tmr.user_data = self;
|
|
|
|
|
|
2017-06-22 22:39:39 +02:00
|
|
|
self->irc_users = str_map_make (NULL);
|
2015-06-20 21:10:50 +02:00
|
|
|
self->irc_users.key_xfrm = irc_strxfrm;
|
2017-06-22 22:39:39 +02:00
|
|
|
self->irc_channels = str_map_make (NULL);
|
2015-06-20 21:10:50 +02:00
|
|
|
self->irc_channels.key_xfrm = irc_strxfrm;
|
2017-06-22 22:39:39 +02:00
|
|
|
self->irc_buffer_map = str_map_make (NULL);
|
2015-06-20 21:10:50 +02:00
|
|
|
self->irc_buffer_map.key_xfrm = irc_strxfrm;
|
|
|
|
|
|
2022-09-21 16:32:08 +02:00
|
|
|
self->irc_user_modes = str_make ();
|
2015-06-07 04:20:39 +02:00
|
|
|
|
2022-09-07 19:04:23 +02:00
|
|
|
self->outstanding_joins = strv_make ();
|
2021-05-28 01:25:10 +02:00
|
|
|
self->cap_ls_buf = strv_make ();
|
2015-06-20 22:42:38 +02:00
|
|
|
server_init_specifics (self);
|
2015-11-18 23:49:09 +01:00
|
|
|
return self;
|
2015-04-30 00:02:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2015-11-18 23:49:09 +01:00
|
|
|
server_destroy (struct server *self)
|
2015-04-30 00:02:14 +02:00
|
|
|
{
|
2015-06-20 21:10:50 +02:00
|
|
|
free (self->name);
|
|
|
|
|
|
2015-05-09 22:10:58 +02:00
|
|
|
if (self->connector)
|
|
|
|
|
{
|
|
|
|
|
connector_free (self->connector);
|
|
|
|
|
free (self->connector);
|
|
|
|
|
}
|
2015-07-20 23:31:26 +02:00
|
|
|
if (self->socks_conn)
|
|
|
|
|
{
|
|
|
|
|
socks_connector_free (self->socks_conn);
|
|
|
|
|
free (self->socks_conn);
|
|
|
|
|
}
|
2015-07-03 20:32:31 +02:00
|
|
|
|
|
|
|
|
if (self->transport
|
|
|
|
|
&& self->transport->cleanup)
|
|
|
|
|
self->transport->cleanup (self);
|
|
|
|
|
|
2015-05-08 17:39:26 +02:00
|
|
|
if (self->socket != -1)
|
2015-04-30 00:02:14 +02:00
|
|
|
{
|
2015-07-03 20:32:31 +02:00
|
|
|
poller_fd_reset (&self->socket_event);
|
2017-05-06 21:35:44 +02:00
|
|
|
xclose (self->socket);
|
2015-04-30 00:02:14 +02:00
|
|
|
}
|
|
|
|
|
str_free (&self->read_buffer);
|
2015-07-03 20:32:31 +02:00
|
|
|
str_free (&self->write_buffer);
|
2015-04-30 00:02:14 +02:00
|
|
|
|
2015-07-14 06:50:39 +02:00
|
|
|
poller_timer_reset (&self->ping_tmr);
|
|
|
|
|
poller_timer_reset (&self->timeout_tmr);
|
|
|
|
|
poller_timer_reset (&self->reconnect_tmr);
|
2015-07-18 14:12:34 +02:00
|
|
|
poller_timer_reset (&self->autojoin_tmr);
|
2015-07-14 06:50:39 +02:00
|
|
|
|
2015-06-20 21:10:50 +02:00
|
|
|
str_map_free (&self->irc_users);
|
|
|
|
|
str_map_free (&self->irc_channels);
|
|
|
|
|
str_map_free (&self->irc_buffer_map);
|
2015-05-15 20:05:27 +02:00
|
|
|
|
2015-04-30 00:02:14 +02:00
|
|
|
if (self->irc_user)
|
|
|
|
|
user_unref (self->irc_user);
|
2022-09-21 16:32:08 +02:00
|
|
|
str_free (&self->irc_user_modes);
|
2015-04-30 00:02:14 +02:00
|
|
|
free (self->irc_user_host);
|
|
|
|
|
|
2022-09-07 19:04:23 +02:00
|
|
|
strv_free (&self->outstanding_joins);
|
2021-05-28 01:25:10 +02:00
|
|
|
strv_free (&self->cap_ls_buf);
|
2015-06-20 22:42:38 +02:00
|
|
|
server_free_specifics (self);
|
2015-05-16 12:33:59 +02:00
|
|
|
free (self);
|
|
|
|
|
}
|
|
|
|
|
|
2015-11-18 23:49:09 +01:00
|
|
|
REF_COUNTABLE_METHODS (server)
|
|
|
|
|
#define server_ref do_not_use_dangerous
|
|
|
|
|
|
2022-08-25 12:28:17 +02:00
|
|
|
// ~~~ Scripting ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2015-04-30 00:02:14 +02:00
|
|
|
|
2015-11-19 14:23:10 +01:00
|
|
|
struct plugin
|
|
|
|
|
{
|
|
|
|
|
LIST_HEADER (struct plugin)
|
|
|
|
|
|
|
|
|
|
char *name; ///< Name of the plugin
|
|
|
|
|
struct plugin_vtable *vtable; ///< Methods
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct plugin_vtable
|
|
|
|
|
{
|
2020-10-20 02:02:09 +02:00
|
|
|
/// Collect garbage
|
|
|
|
|
void (*gc) (struct plugin *self);
|
2015-11-19 14:23:10 +01:00
|
|
|
/// Unregister and free the plugin including all relevant resources
|
|
|
|
|
void (*free) (struct plugin *self);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
plugin_destroy (struct plugin *self)
|
|
|
|
|
{
|
|
|
|
|
self->vtable->free (self);
|
|
|
|
|
free (self->name);
|
|
|
|
|
free (self);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
2015-11-21 18:40:07 +01:00
|
|
|
// This is a bit ugly since insertion is O(n) and the need to get rid of the
|
|
|
|
|
// specific type because of list macros, however I don't currently posses any
|
|
|
|
|
// strictly better, ordered data structure
|
|
|
|
|
|
|
|
|
|
struct hook
|
|
|
|
|
{
|
|
|
|
|
LIST_HEADER (struct hook)
|
|
|
|
|
int priority; ///< The lesser the sooner
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static struct hook *
|
|
|
|
|
hook_insert (struct hook *list, struct hook *item)
|
|
|
|
|
{
|
|
|
|
|
// Corner cases: list is empty or we precede everything
|
|
|
|
|
if (!list || item->priority < list->priority)
|
|
|
|
|
{
|
|
|
|
|
LIST_PREPEND (list, item);
|
|
|
|
|
return list;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Otherwise fast-forward to the last entry that precedes us
|
|
|
|
|
struct hook *before = list;
|
2015-11-24 02:32:11 +01:00
|
|
|
while (before->next && before->next->priority < item->priority)
|
2015-11-21 18:40:07 +01:00
|
|
|
before = before->next;
|
|
|
|
|
|
|
|
|
|
// And link ourselves in between it and its successor
|
|
|
|
|
if ((item->next = before->next))
|
|
|
|
|
item->next->prev = item;
|
|
|
|
|
before->next = item;
|
|
|
|
|
item->prev = before;
|
|
|
|
|
return list;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
2015-11-19 19:09:05 +01:00
|
|
|
struct input_hook
|
|
|
|
|
{
|
2015-11-21 18:40:07 +01:00
|
|
|
struct hook super; ///< Common hook fields
|
2015-11-19 19:09:05 +01:00
|
|
|
|
|
|
|
|
/// Takes over the ownership of "input", returns either NULL if input
|
|
|
|
|
/// was thrown away, or a possibly modified version of it
|
|
|
|
|
char *(*filter) (struct input_hook *self,
|
|
|
|
|
struct buffer *buffer, char *input);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct irc_hook
|
|
|
|
|
{
|
2015-11-21 18:40:07 +01:00
|
|
|
struct hook super; ///< Common hook fields
|
2015-11-19 19:09:05 +01:00
|
|
|
|
|
|
|
|
/// Takes over the ownership of "message", returns either NULL if message
|
|
|
|
|
/// was thrown away, or a possibly modified version of it
|
|
|
|
|
char *(*filter) (struct irc_hook *self,
|
|
|
|
|
struct server *server, char *message);
|
|
|
|
|
};
|
|
|
|
|
|
2016-10-28 01:01:27 +02:00
|
|
|
struct prompt_hook
|
|
|
|
|
{
|
|
|
|
|
struct hook super; ///< Common hook fields
|
|
|
|
|
|
|
|
|
|
/// Returns what the prompt should look like right now based on other state
|
|
|
|
|
char *(*make) (struct prompt_hook *self);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
2016-01-15 01:44:35 +01:00
|
|
|
struct completion_word
|
|
|
|
|
{
|
|
|
|
|
size_t start; ///< Offset to start of word
|
|
|
|
|
size_t end; ///< Offset to end of word
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct completion
|
|
|
|
|
{
|
|
|
|
|
char *line; ///< The line which is being completed
|
|
|
|
|
|
|
|
|
|
struct completion_word *words; ///< Word locations
|
|
|
|
|
size_t words_len; ///< Number of words
|
|
|
|
|
size_t words_alloc; ///< Number of words allocated
|
|
|
|
|
|
|
|
|
|
size_t location; ///< Which word is being completed
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct completion_hook
|
|
|
|
|
{
|
|
|
|
|
struct hook super; ///< Common hook fields
|
|
|
|
|
|
|
|
|
|
/// Tries to add possible completions of "word" to "output"
|
|
|
|
|
void (*complete) (struct completion_hook *self,
|
2017-01-23 23:50:27 +01:00
|
|
|
struct completion *data, const char *word, struct strv *output);
|
2016-01-15 01:44:35 +01:00
|
|
|
};
|
|
|
|
|
|
2022-08-25 12:28:17 +02:00
|
|
|
// ~~~ Main context ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2016-01-15 01:44:35 +01:00
|
|
|
|
2015-04-30 00:02:14 +02:00
|
|
|
struct app_context
|
|
|
|
|
{
|
2022-09-10 15:55:13 +02:00
|
|
|
/// Default terminal attributes
|
|
|
|
|
struct attrs theme_defaults[ATTR_COUNT];
|
2015-07-05 15:57:53 +02:00
|
|
|
|
2015-04-30 00:02:14 +02:00
|
|
|
// Configuration:
|
|
|
|
|
|
2015-05-02 23:00:34 +02:00
|
|
|
struct config config; ///< Program configuration
|
2022-09-10 15:55:13 +02:00
|
|
|
struct attrs theme[ATTR_COUNT]; ///< Terminal attributes
|
2015-04-30 00:02:14 +02:00
|
|
|
bool isolate_buffers; ///< Isolate global/server buffers
|
2015-07-05 16:28:27 +02:00
|
|
|
bool beep_on_highlight; ///< Beep on highlight
|
2015-07-05 17:02:11 +02:00
|
|
|
bool logging; ///< Logging to file enabled
|
2015-07-26 22:21:08 +02:00
|
|
|
bool show_all_prefixes; ///< Show all prefixes before nicks
|
2016-03-21 00:30:59 +01:00
|
|
|
bool word_wrapping; ///< Enable simple word wrapping
|
2015-04-30 00:02:14 +02:00
|
|
|
|
2015-05-16 12:33:59 +02:00
|
|
|
struct str_map servers; ///< Our servers
|
2015-04-30 00:02:14 +02:00
|
|
|
|
|
|
|
|
// Events:
|
|
|
|
|
|
|
|
|
|
struct poller_fd tty_event; ///< Terminal input event
|
|
|
|
|
struct poller_fd signal_event; ///< Signal FD event
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
struct poller_fd relay_event; ///< New relay connection available
|
2015-04-12 04:52:39 +02:00
|
|
|
|
2015-07-04 15:43:45 +02:00
|
|
|
struct poller_timer flush_timer; ///< Flush all open files (e.g. logs)
|
2015-07-11 06:10:46 +02:00
|
|
|
struct poller_timer date_chg_tmr; ///< Print a date change
|
2015-11-22 23:04:51 +01:00
|
|
|
struct poller_timer autoaway_tmr; ///< Autoaway timer
|
2015-07-04 15:43:45 +02:00
|
|
|
|
2015-04-13 00:06:08 +02:00
|
|
|
struct poller poller; ///< Manages polled descriptors
|
|
|
|
|
bool quitting; ///< User requested quitting
|
|
|
|
|
bool polling; ///< The event loop is running
|
|
|
|
|
|
|
|
|
|
// Buffers:
|
2015-04-12 04:52:39 +02:00
|
|
|
|
|
|
|
|
struct buffer *buffers; ///< All our buffers in order
|
|
|
|
|
struct buffer *buffers_tail; ///< The tail of our buffers
|
|
|
|
|
|
2015-05-16 12:33:59 +02:00
|
|
|
struct buffer *global_buffer; ///< The global buffer
|
|
|
|
|
struct buffer *current_buffer; ///< The current buffer
|
2015-04-27 22:51:40 +02:00
|
|
|
struct buffer *last_buffer; ///< Last used buffer
|
|
|
|
|
|
2015-05-15 20:05:27 +02:00
|
|
|
struct str_map buffers_by_name; ///< Buffers by name
|
2015-04-18 22:09:05 +02:00
|
|
|
|
2016-01-15 03:49:24 +01:00
|
|
|
unsigned backlog_limit; ///< Limit for buffer lines
|
2015-04-12 04:52:39 +02:00
|
|
|
time_t last_displayed_msg_time; ///< Time of last displayed message
|
|
|
|
|
|
2015-04-13 00:06:08 +02:00
|
|
|
// Terminal:
|
2015-04-12 04:52:39 +02:00
|
|
|
|
|
|
|
|
iconv_t term_to_utf8; ///< Terminal encoding to UTF-8
|
|
|
|
|
iconv_t term_from_utf8; ///< UTF-8 to terminal encoding
|
|
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
struct input *input; ///< User interface
|
2015-05-12 03:48:52 +02:00
|
|
|
|
2016-10-23 17:34:52 +02:00
|
|
|
struct poller_idle prompt_event; ///< Deferred prompt refresh
|
2016-01-07 22:12:29 +01:00
|
|
|
struct poller_idle input_event; ///< Pending input event
|
2017-01-23 23:50:27 +01:00
|
|
|
struct strv pending_input; ///< Pending input lines
|
2016-01-07 22:12:29 +01:00
|
|
|
|
2020-10-11 17:36:21 +02:00
|
|
|
int *nick_palette; ///< A 256-colour palette for nicknames
|
2015-08-10 07:51:03 +02:00
|
|
|
size_t nick_palette_len; ///< Number of entries in nick_palette
|
|
|
|
|
|
2021-08-29 12:07:31 +02:00
|
|
|
bool awaiting_formatting_escape; ///< Awaiting an IRC formatting escape
|
2015-11-15 01:03:29 +01:00
|
|
|
bool in_bracketed_paste; ///< User is pasting some content
|
2015-11-15 01:23:32 +01:00
|
|
|
struct str input_buffer; ///< Buffered pasted content
|
2015-11-15 01:03:29 +01:00
|
|
|
|
2022-08-26 03:43:32 +02:00
|
|
|
bool running_pager; ///< Running a pager for buffer history
|
2015-12-25 04:21:18 +01:00
|
|
|
bool running_editor; ///< Running editor for the input
|
|
|
|
|
char *editor_filename; ///< The file being edited by user
|
2015-11-15 15:36:03 +01:00
|
|
|
int terminal_suspended; ///< Terminal suspension level
|
2015-11-19 14:23:10 +01:00
|
|
|
|
2025-05-09 22:34:25 +02:00
|
|
|
// Relay:
|
|
|
|
|
|
|
|
|
|
int relay_fd; ///< Listening socket FD
|
|
|
|
|
struct client *clients; ///< Our relay clients
|
|
|
|
|
|
|
|
|
|
/// A single message buffer to prepare all outcoming messages within
|
|
|
|
|
struct relay_event_message relay_message;
|
|
|
|
|
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
// Plugins:
|
|
|
|
|
|
2015-11-19 14:23:10 +01:00
|
|
|
struct plugin *plugins; ///< Loaded plugins
|
2015-11-21 18:40:07 +01:00
|
|
|
struct hook *input_hooks; ///< Input hooks
|
|
|
|
|
struct hook *irc_hooks; ///< IRC hooks
|
2016-10-28 01:01:27 +02:00
|
|
|
struct hook *prompt_hooks; ///< Prompt hooks
|
2016-01-15 01:44:35 +01:00
|
|
|
struct hook *completion_hooks; ///< Autocomplete hooks
|
2015-04-12 04:52:39 +02:00
|
|
|
}
|
|
|
|
|
*g_ctx;
|
|
|
|
|
|
2016-10-28 00:07:49 +02:00
|
|
|
static struct ispect_field g_ctx_ispect[] =
|
2016-10-27 20:02:25 +02:00
|
|
|
{
|
2016-10-28 00:07:49 +02:00
|
|
|
ISPECT_MAP_REF( app_context, servers, false, server )
|
|
|
|
|
ISPECT_REF( app_context, buffers, true, buffer )
|
|
|
|
|
ISPECT_REF( app_context, global_buffer, false, buffer )
|
|
|
|
|
ISPECT_REF( app_context, current_buffer, false, buffer )
|
2016-10-27 20:02:25 +02:00
|
|
|
{}
|
|
|
|
|
};
|
|
|
|
|
|
2015-08-10 07:51:03 +02:00
|
|
|
static int *
|
|
|
|
|
filter_color_cube_for_acceptable_nick_colors (size_t *len)
|
|
|
|
|
{
|
|
|
|
|
// This is a pure function and we don't use threads, static storage is fine
|
|
|
|
|
static int table[6 * 6 * 6];
|
|
|
|
|
size_t len_counter = 0;
|
2021-06-15 07:11:35 +02:00
|
|
|
for (int x = 0; x < (int) N_ELEMENTS (table); x++)
|
2015-08-10 07:51:03 +02:00
|
|
|
{
|
|
|
|
|
int r = x / 36;
|
|
|
|
|
int g = (x / 6) % 6;
|
|
|
|
|
int b = (x % 6);
|
|
|
|
|
|
2021-06-15 04:35:41 +02:00
|
|
|
// The first step is 95/255, the rest are 40/255,
|
|
|
|
|
// as an approximation we can double the first step
|
|
|
|
|
double linear_R = pow ((r + !!r) / 6., 2.2);
|
|
|
|
|
double linear_G = pow ((g + !!g) / 6., 2.2);
|
|
|
|
|
double linear_B = pow ((b + !!b) / 6., 2.2);
|
|
|
|
|
|
|
|
|
|
// Use the relative luminance of colours within the cube to filter
|
|
|
|
|
// colours that look okay-ish on terminals with both black and white
|
|
|
|
|
// backgrounds (use the test-nick-colors script to calibrate)
|
|
|
|
|
double Y = 0.2126 * linear_R + 0.7152 * linear_G + 0.0722 * linear_B;
|
|
|
|
|
if (Y >= .25 && Y <= .4)
|
2015-08-10 07:51:03 +02:00
|
|
|
table[len_counter++] = 16 + x;
|
|
|
|
|
}
|
|
|
|
|
*len = len_counter;
|
|
|
|
|
return table;
|
|
|
|
|
}
|
|
|
|
|
|
2016-03-26 14:07:09 +01:00
|
|
|
static bool
|
|
|
|
|
app_iconv_open (iconv_t *target, const char *to, const char *from)
|
|
|
|
|
{
|
|
|
|
|
if (ICONV_ACCEPTS_TRANSLIT)
|
|
|
|
|
{
|
|
|
|
|
char *to_real = xstrdup_printf ("%s//TRANSLIT", to);
|
|
|
|
|
*target = iconv_open (to_real, from);
|
|
|
|
|
free (to_real);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
*target = iconv_open (to, from);
|
|
|
|
|
return *target != (iconv_t) -1;
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-12 04:52:39 +02:00
|
|
|
static void
|
|
|
|
|
app_context_init (struct app_context *self)
|
|
|
|
|
{
|
|
|
|
|
memset (self, 0, sizeof *self);
|
|
|
|
|
|
2017-06-22 22:39:39 +02:00
|
|
|
self->config = config_make ();
|
2015-04-19 02:12:59 +02:00
|
|
|
poller_init (&self->poller);
|
|
|
|
|
|
2017-06-22 22:39:39 +02:00
|
|
|
self->servers = str_map_make ((str_map_free_fn) server_unref);
|
2015-06-01 21:45:36 +02:00
|
|
|
self->servers.key_xfrm = tolower_ascii_strxfrm;
|
2015-04-30 00:02:14 +02:00
|
|
|
|
2017-06-22 22:39:39 +02:00
|
|
|
self->buffers_by_name = str_map_make (NULL);
|
2015-06-01 21:45:36 +02:00
|
|
|
self->buffers_by_name.key_xfrm = tolower_ascii_strxfrm;
|
2015-04-13 00:06:08 +02:00
|
|
|
|
2016-01-15 03:49:24 +01:00
|
|
|
// So that we don't lose the logo shortly after startup
|
|
|
|
|
self->backlog_limit = 1000;
|
2015-04-12 04:52:39 +02:00
|
|
|
self->last_displayed_msg_time = time (NULL);
|
|
|
|
|
|
2016-03-26 14:07:09 +01:00
|
|
|
char *native = nl_langinfo (CODESET);
|
|
|
|
|
if (!app_iconv_open (&self->term_from_utf8, native, "UTF-8")
|
2016-04-03 04:05:04 +02:00
|
|
|
|| !app_iconv_open (&self->term_to_utf8, "UTF-8", native))
|
2015-04-12 04:52:39 +02:00
|
|
|
exit_fatal ("creating the UTF-8 conversion object failed: %s",
|
|
|
|
|
strerror (errno));
|
|
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
self->input = input_new ();
|
2016-03-10 00:06:28 +01:00
|
|
|
self->input->user_data = self;
|
2017-06-22 22:39:39 +02:00
|
|
|
self->pending_input = strv_make ();
|
|
|
|
|
self->input_buffer = str_make ();
|
2015-08-10 07:51:03 +02:00
|
|
|
|
|
|
|
|
self->nick_palette =
|
|
|
|
|
filter_color_cube_for_acceptable_nick_colors (&self->nick_palette_len);
|
2025-05-09 22:34:25 +02:00
|
|
|
|
|
|
|
|
self->relay_fd = -1;
|
2015-04-12 04:52:39 +02:00
|
|
|
}
|
|
|
|
|
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
static void
|
|
|
|
|
app_context_relay_stop (struct app_context *self)
|
|
|
|
|
{
|
|
|
|
|
if (self->relay_fd != -1)
|
|
|
|
|
{
|
|
|
|
|
poller_fd_reset (&self->relay_event);
|
|
|
|
|
xclose (self->relay_fd);
|
|
|
|
|
self->relay_fd = -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-12 04:52:39 +02:00
|
|
|
static void
|
|
|
|
|
app_context_free (struct app_context *self)
|
|
|
|
|
{
|
2015-11-19 14:23:10 +01:00
|
|
|
// Plugins can try to use of the other fields when destroyed
|
|
|
|
|
LIST_FOR_EACH (struct plugin, iter, self->plugins)
|
|
|
|
|
plugin_destroy (iter);
|
|
|
|
|
|
2015-05-02 23:00:34 +02:00
|
|
|
config_free (&self->config);
|
2015-04-15 16:54:05 +02:00
|
|
|
|
2015-04-24 23:30:48 +02:00
|
|
|
LIST_FOR_EACH (struct buffer, iter, self->buffers)
|
2015-06-28 23:00:46 +02:00
|
|
|
{
|
|
|
|
|
#ifdef HAVE_READLINE
|
2016-03-07 01:08:52 +01:00
|
|
|
// We can use the user interface here; see buffer_destroy()
|
|
|
|
|
CALL_ (self->input, buffer_destroy, iter->input_data);
|
2015-06-28 23:00:46 +02:00
|
|
|
iter->input_data = NULL;
|
|
|
|
|
#endif // HAVE_READLINE
|
2015-11-18 23:49:09 +01:00
|
|
|
buffer_unref (iter);
|
2015-06-28 23:00:46 +02:00
|
|
|
}
|
2015-04-24 23:30:48 +02:00
|
|
|
str_map_free (&self->buffers_by_name);
|
|
|
|
|
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
app_context_relay_stop (self);
|
|
|
|
|
LIST_FOR_EACH (struct client, c, self->clients)
|
|
|
|
|
client_kill (c);
|
|
|
|
|
relay_event_message_free (&self->relay_message);
|
|
|
|
|
|
2015-05-16 12:33:59 +02:00
|
|
|
str_map_free (&self->servers);
|
2015-04-19 02:12:59 +02:00
|
|
|
poller_free (&self->poller);
|
|
|
|
|
|
2015-04-12 04:52:39 +02:00
|
|
|
iconv_close (self->term_from_utf8);
|
|
|
|
|
iconv_close (self->term_to_utf8);
|
2015-04-13 00:06:08 +02:00
|
|
|
|
2016-03-07 01:08:52 +01:00
|
|
|
CALL (self->input, destroy);
|
2017-01-23 23:50:27 +01:00
|
|
|
strv_free (&self->pending_input);
|
2015-11-15 01:23:32 +01:00
|
|
|
str_free (&self->input_buffer);
|
2015-12-25 04:21:18 +01:00
|
|
|
|
|
|
|
|
free (self->editor_filename);
|
2015-04-12 04:52:39 +02:00
|
|
|
}
|
|
|
|
|
|
2016-10-23 17:34:52 +02:00
|
|
|
static void
|
|
|
|
|
refresh_prompt (struct app_context *ctx)
|
|
|
|
|
{
|
|
|
|
|
// XXX: the need for this conditional could probably be resolved
|
|
|
|
|
// by some clever reordering
|
|
|
|
|
if (ctx->prompt_event.poller)
|
|
|
|
|
poller_idle_set (&ctx->prompt_event);
|
|
|
|
|
}
|
2015-04-15 02:10:21 +02:00
|
|
|
|
2015-05-02 23:00:34 +02:00
|
|
|
// --- Configuration -----------------------------------------------------------
|
|
|
|
|
|
2015-07-11 04:28:34 +02:00
|
|
|
static void
|
2015-08-17 00:08:19 +02:00
|
|
|
on_config_debug_mode_change (struct config_item *item)
|
2015-07-11 04:28:34 +02:00
|
|
|
{
|
|
|
|
|
g_debug_mode = item->value.boolean;
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-26 22:21:08 +02:00
|
|
|
static void
|
2015-08-17 00:08:19 +02:00
|
|
|
on_config_show_all_prefixes_change (struct config_item *item)
|
2015-07-26 22:21:08 +02:00
|
|
|
{
|
|
|
|
|
struct app_context *ctx = item->user_data;
|
|
|
|
|
ctx->show_all_prefixes = item->value.boolean;
|
|
|
|
|
refresh_prompt (ctx);
|
|
|
|
|
}
|
|
|
|
|
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
static void on_config_relay_bind_change (struct config_item *item);
|
2016-01-15 03:49:24 +01:00
|
|
|
static void on_config_backlog_limit_change (struct config_item *item);
|
2022-09-10 15:55:13 +02:00
|
|
|
static void on_config_theme_change (struct config_item *item);
|
2015-08-17 00:08:19 +02:00
|
|
|
static void on_config_logging_change (struct config_item *item);
|
2015-05-02 23:00:34 +02:00
|
|
|
|
2015-07-05 16:28:27 +02:00
|
|
|
#define TRIVIAL_BOOLEAN_ON_CHANGE(name) \
|
|
|
|
|
static void \
|
2015-08-17 00:08:19 +02:00
|
|
|
on_config_ ## name ## _change (struct config_item *item) \
|
2015-07-05 16:28:27 +02:00
|
|
|
{ \
|
|
|
|
|
struct app_context *ctx = item->user_data; \
|
|
|
|
|
ctx->name = item->value.boolean; \
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TRIVIAL_BOOLEAN_ON_CHANGE (isolate_buffers)
|
|
|
|
|
TRIVIAL_BOOLEAN_ON_CHANGE (beep_on_highlight)
|
2016-03-21 00:30:59 +01:00
|
|
|
TRIVIAL_BOOLEAN_ON_CHANGE (word_wrapping)
|
2015-07-05 16:28:27 +02:00
|
|
|
|
2015-07-11 04:28:34 +02:00
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
2015-05-02 23:00:34 +02:00
|
|
|
static bool
|
|
|
|
|
config_validate_nonjunk_string
|
2015-08-17 00:08:19 +02:00
|
|
|
(const struct config_item *item, struct error **e)
|
2015-05-02 23:00:34 +02:00
|
|
|
{
|
|
|
|
|
if (item->type == CONFIG_ITEM_NULL)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
hard_assert (config_item_type_is_string (item->type));
|
|
|
|
|
for (size_t i = 0; i < item->value.string.len; i++)
|
|
|
|
|
{
|
|
|
|
|
// Not even a tabulator
|
|
|
|
|
unsigned char c = item->value.string.str[i];
|
2020-10-02 06:52:11 +02:00
|
|
|
if (iscntrl_ascii (c))
|
2015-05-02 23:00:34 +02:00
|
|
|
{
|
|
|
|
|
error_set (e, "control characters are not allowed");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-14 06:22:58 +02:00
|
|
|
static bool
|
|
|
|
|
config_validate_addresses
|
2015-08-17 00:08:19 +02:00
|
|
|
(const struct config_item *item, struct error **e)
|
2015-05-14 06:22:58 +02:00
|
|
|
{
|
|
|
|
|
if (item->type == CONFIG_ITEM_NULL)
|
|
|
|
|
return true;
|
|
|
|
|
if (!config_validate_nonjunk_string (item, e))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// Comma-separated list of "host[:port]" pairs
|
|
|
|
|
regex_t re;
|
|
|
|
|
int err = regcomp (&re, "^([^/:,]+(:[^/:,]+)?)?"
|
|
|
|
|
"(,([^/:,]+(:[^/:,]+)?)?)*$", REG_EXTENDED | REG_NOSUB);
|
|
|
|
|
hard_assert (!err);
|
|
|
|
|
|
|
|
|
|
bool result = !regexec (&re, item->value.string.str, 0, NULL, 0);
|
|
|
|
|
if (!result)
|
|
|
|
|
error_set (e, "invalid address list string");
|
|
|
|
|
|
|
|
|
|
regfree (&re);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-02 23:00:34 +02:00
|
|
|
static bool
|
|
|
|
|
config_validate_nonnegative
|
2015-08-17 00:08:19 +02:00
|
|
|
(const struct config_item *item, struct error **e)
|
2015-05-02 23:00:34 +02:00
|
|
|
{
|
|
|
|
|
if (item->type == CONFIG_ITEM_NULL)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
hard_assert (item->type == CONFIG_ITEM_INTEGER);
|
|
|
|
|
if (item->value.integer >= 0)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
error_set (e, "must be non-negative");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-08 09:02:18 +02:00
|
|
|
static const struct config_schema g_config_server[] =
|
2015-05-02 23:00:34 +02:00
|
|
|
{
|
2015-07-09 02:46:31 +02:00
|
|
|
{ .name = "nicks",
|
2015-05-02 23:00:34 +02:00
|
|
|
.comment = "IRC nickname",
|
2015-07-09 02:46:31 +02:00
|
|
|
.type = CONFIG_ITEM_STRING_ARRAY,
|
2015-05-02 23:00:34 +02:00
|
|
|
.validate = config_validate_nonjunk_string },
|
|
|
|
|
{ .name = "username",
|
|
|
|
|
.comment = "IRC user name",
|
|
|
|
|
.type = CONFIG_ITEM_STRING,
|
|
|
|
|
.validate = config_validate_nonjunk_string },
|
|
|
|
|
{ .name = "realname",
|
|
|
|
|
.comment = "IRC real name/e-mail",
|
|
|
|
|
.type = CONFIG_ITEM_STRING,
|
|
|
|
|
.validate = config_validate_nonjunk_string },
|
|
|
|
|
|
2015-05-14 06:22:58 +02:00
|
|
|
{ .name = "addresses",
|
|
|
|
|
.comment = "Addresses of the IRC network (e.g. \"irc.net:6667\")",
|
|
|
|
|
.type = CONFIG_ITEM_STRING_ARRAY,
|
|
|
|
|
.validate = config_validate_addresses },
|
2015-05-20 21:18:15 +02:00
|
|
|
{ .name = "password",
|
|
|
|
|
.comment = "Password to connect to the server, if any",
|
|
|
|
|
.type = CONFIG_ITEM_STRING,
|
|
|
|
|
.validate = config_validate_nonjunk_string },
|
2016-02-01 21:57:43 +01:00
|
|
|
// XXX: if we add support for new capabilities, the value stays unchanged
|
|
|
|
|
{ .name = "capabilities",
|
|
|
|
|
.comment = "Capabilities to use if supported by server",
|
|
|
|
|
.type = CONFIG_ITEM_STRING_ARRAY,
|
|
|
|
|
.validate = config_validate_nonjunk_string,
|
2020-10-16 16:45:40 +02:00
|
|
|
.default_ = "\"multi-prefix,invite-notify,server-time,echo-message,"
|
2021-05-30 08:06:38 +02:00
|
|
|
"message-tags,away-notify,cap-notify,chghost\"" },
|
2015-05-02 23:00:34 +02:00
|
|
|
|
2015-07-28 20:31:42 +02:00
|
|
|
{ .name = "tls",
|
2015-07-15 23:34:36 +02:00
|
|
|
.comment = "Whether to use TLS",
|
2015-05-02 23:00:34 +02:00
|
|
|
.type = CONFIG_ITEM_BOOLEAN,
|
|
|
|
|
.default_ = "off" },
|
2015-07-28 20:31:42 +02:00
|
|
|
{ .name = "tls_cert",
|
2015-07-15 23:34:36 +02:00
|
|
|
.comment = "Client TLS certificate (PEM)",
|
2015-05-02 23:00:34 +02:00
|
|
|
.type = CONFIG_ITEM_STRING },
|
2015-07-28 20:31:42 +02:00
|
|
|
{ .name = "tls_verify",
|
2015-05-02 23:00:34 +02:00
|
|
|
.comment = "Whether to verify certificates",
|
|
|
|
|
.type = CONFIG_ITEM_BOOLEAN,
|
|
|
|
|
.default_ = "on" },
|
2015-07-28 20:31:42 +02:00
|
|
|
{ .name = "tls_ca_file",
|
2015-05-02 23:00:34 +02:00
|
|
|
.comment = "OpenSSL CA bundle file",
|
|
|
|
|
.type = CONFIG_ITEM_STRING },
|
2015-07-28 20:31:42 +02:00
|
|
|
{ .name = "tls_ca_path",
|
2015-05-02 23:00:34 +02:00
|
|
|
.comment = "OpenSSL CA bundle path",
|
|
|
|
|
.type = CONFIG_ITEM_STRING },
|
2015-07-28 20:31:42 +02:00
|
|
|
{ .name = "tls_ciphers",
|
2015-07-12 17:15:33 +02:00
|
|
|
.comment = "OpenSSL cipher preference list",
|
|
|
|
|
.type = CONFIG_ITEM_STRING,
|
|
|
|
|
.default_ = "\"DEFAULT:!MEDIUM:!LOW\"" },
|
2015-05-02 23:00:34 +02:00
|
|
|
|
2015-07-11 06:16:53 +02:00
|
|
|
{ .name = "autoconnect",
|
|
|
|
|
.comment = "Connect automatically on startup",
|
|
|
|
|
.type = CONFIG_ITEM_BOOLEAN,
|
|
|
|
|
.default_ = "on" },
|
2015-05-02 23:00:34 +02:00
|
|
|
{ .name = "autojoin",
|
2016-12-30 07:49:10 +01:00
|
|
|
.comment = "Channels to join on start (e.g. \"#abc,#def key,#ghi\")",
|
2015-05-02 23:00:34 +02:00
|
|
|
.type = CONFIG_ITEM_STRING_ARRAY,
|
|
|
|
|
.validate = config_validate_nonjunk_string },
|
2015-07-18 13:23:22 +02:00
|
|
|
{ .name = "command",
|
|
|
|
|
.comment = "Command to execute after a successful connect",
|
|
|
|
|
.type = CONFIG_ITEM_STRING },
|
2015-07-18 14:12:34 +02:00
|
|
|
{ .name = "command_delay",
|
|
|
|
|
.comment = "Delay between executing \"command\" and joining channels",
|
|
|
|
|
.type = CONFIG_ITEM_INTEGER,
|
|
|
|
|
.validate = config_validate_nonnegative,
|
|
|
|
|
.default_ = "0" },
|
2015-05-02 23:00:34 +02:00
|
|
|
{ .name = "reconnect",
|
|
|
|
|
.comment = "Whether to reconnect on error",
|
|
|
|
|
.type = CONFIG_ITEM_BOOLEAN,
|
|
|
|
|
.default_ = "on" },
|
|
|
|
|
{ .name = "reconnect_delay",
|
|
|
|
|
.comment = "Time between reconnecting",
|
|
|
|
|
.type = CONFIG_ITEM_INTEGER,
|
2015-05-16 12:45:39 +02:00
|
|
|
.validate = config_validate_nonnegative,
|
2015-05-02 23:00:34 +02:00
|
|
|
.default_ = "5" },
|
|
|
|
|
|
|
|
|
|
{ .name = "socks_host",
|
|
|
|
|
.comment = "Address of a SOCKS 4a/5 proxy",
|
|
|
|
|
.type = CONFIG_ITEM_STRING,
|
|
|
|
|
.validate = config_validate_nonjunk_string },
|
|
|
|
|
{ .name = "socks_port",
|
|
|
|
|
.comment = "SOCKS port number",
|
|
|
|
|
.type = CONFIG_ITEM_INTEGER,
|
|
|
|
|
.validate = config_validate_nonnegative,
|
|
|
|
|
.default_ = "1080" },
|
|
|
|
|
{ .name = "socks_username",
|
|
|
|
|
.comment = "SOCKS auth. username",
|
|
|
|
|
.type = CONFIG_ITEM_STRING },
|
|
|
|
|
{ .name = "socks_password",
|
|
|
|
|
.comment = "SOCKS auth. password",
|
|
|
|
|
.type = CONFIG_ITEM_STRING },
|
|
|
|
|
{}
|
|
|
|
|
};
|
|
|
|
|
|
2024-08-08 09:02:18 +02:00
|
|
|
static const struct config_schema g_config_general[] =
|
2015-05-02 23:00:34 +02:00
|
|
|
{
|
2022-08-26 04:59:25 +02:00
|
|
|
{ .name = "autosave",
|
|
|
|
|
.comment = "Save configuration automatically after each change",
|
2022-08-26 03:43:32 +02:00
|
|
|
.type = CONFIG_ITEM_BOOLEAN,
|
|
|
|
|
.default_ = "on" },
|
|
|
|
|
{ .name = "debug_mode",
|
|
|
|
|
.comment = "Produce some debugging output",
|
|
|
|
|
.type = CONFIG_ITEM_BOOLEAN,
|
|
|
|
|
.default_ = "off",
|
|
|
|
|
.on_change = on_config_debug_mode_change },
|
|
|
|
|
{ .name = "logging",
|
|
|
|
|
.comment = "Log buffer contents to file",
|
|
|
|
|
.type = CONFIG_ITEM_BOOLEAN,
|
|
|
|
|
.default_ = "off",
|
|
|
|
|
.on_change = on_config_logging_change },
|
|
|
|
|
{ .name = "plugin_autoload",
|
|
|
|
|
.comment = "Plugins to automatically load on start",
|
|
|
|
|
.type = CONFIG_ITEM_STRING_ARRAY,
|
|
|
|
|
.validate = config_validate_nonjunk_string },
|
Start X11 and web frontends for xC
For this, we needed a wire protocol. After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK. It now has two backends, per each of:
- xF, the X11 frontend, is in C, and is meant to be the primary
user interface in the future.
- xP, the web frontend, relies on a protocol proxy written in Go,
and is meant for use on-the-go (no pun intended).
They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-08-08 04:39:20 +02:00
|
|
|
{ .name = "relay_bind",
|
|
|
|
|
.comment = "Address to bind to for a user interface relay point",
|
|
|
|
|
.type = CONFIG_ITEM_STRING,
|
|
|
|
|
.validate = config_validate_nonjunk_string,
|
|
|
|
|
.on_change = on_config_relay_bind_change },
|
2022-08-26 03:43:32 +02:00
|
|
|
|
|
|
|
|
// Buffer history:
|
|
|
|
|
{ .name = "backlog_limit",
|
|
|
|
|
.comment = "Maximum number of lines stored in the backlog",
|
|
|
|
|
.type = CONFIG_ITEM_INTEGER,
|
|
|
|
|
.validate = config_validate_nonnegative,
|
|
|
|
|
.default_ = "1000",
|
|
|
|
|
.on_change = on_config_backlog_limit_change },
|
|
|
|
|
{ .name = "pager",
|
|
|
|
|
.comment = "Shell command to page buffer history (args: name [path])",
|
|
|
|
|
.type = CONFIG_ITEM_STRING,
|
|
|
|
|
.default_ = "`name=$(echo \"$1\" | sed 's/[%?:.]/\\\\&/g'); "
|
|
|
|
|
"prompt='?f%F:'$name'. ?db- page %db?L of %D. .(?eEND:?PB%PB\\%..)'; "
|
|
|
|
|
"LESSSECURE=1 less +Gb -Ps\"$prompt\" \"${2:--R}\"`" },
|
|
|
|
|
{ .name = "pager_strip_formatting",
|
|
|
|
|
.comment = "Strip terminal formatting from pager input",
|
|
|
|
|
.type = CONFIG_ITEM_BOOLEAN,
|
|
|
|
|
.default_ = "off" },
|
|
|
|
|
|
|
|
|
|
// Output adjustments:
|
|
|
|
|
{ .name = "beep_on_highlight",
|
|
|
|
|
.comment = "Ring the bell when highlighted or on a new invisible PM",
|
|
|
|
|
.type = CONFIG_ITEM_BOOLEAN,
|
|
|
|
|
.default_ = "on",
|
|
|
|
|
.on_change = on_config_beep_on_highlight_change },
|
|
|
|
|
{ .name = "date_change_line",
|
|
|
|
|
.comment = "Input to strftime(3) for the date change line",
|
|
|
|
|
.type = CONFIG_ITEM_STRING,
|
|
|
|
|
.default_ = "\"%F\"" },
|
2015-05-02 23:00:34 +02:00
|
|
|
{ .name = "isolate_buffers",
|
|
|
|
|
.comment = "Don't leak messages from the server and global buffers",
|
|
|
|
|
.type = CONFIG_ITEM_BOOLEAN,
|
2015-07-05 16:28:27 +02:00
|
|
|
.default_ = "off",
|
|
|
|
|
.on_change = on_config_isolate_buffers_change },
|
2022-08-26 03:43:32 +02:00
|
|
|
{ .name = "read_marker_char",
|
|
|
|
|
.comment = "The character to use for the read marker line",
|
|
|
|
|
.type = CONFIG_ITEM_STRING,
|
|
|
|
|
.default_ = "\"-\"",
|
|
|
|
|
.validate = config_validate_nonjunk_string },
|
2015-07-26 22:21:08 +02:00
|
|
|
{ .name = "show_all_prefixes",
|
|
|
|
|
.comment = "Show all prefixes in front of nicknames",
|
|
|
|
|
.type = CONFIG_ITEM_BOOLEAN,
|
|
|
|
|
.default_ = "off",
|
|
|
|
|
.on_change = on_config_show_all_prefixes_change },
|
2016-03-21 00:30:59 +01:00
|
|
|
{ .name = "word_wrapping",
|
|
|
|
|
.comment = "Enable simple word wrapping in buffers",
|
|
|
|
|
.type = CONFIG_ITEM_BOOLEAN,
|
|
|
|
|
.default_ = "on",
|
|
|
|
|
.on_change = on_config_word_wrapping_change },
|
2022-08-26 03:43:32 +02:00
|
|
|
|
|
|
|
|
// User input:
|
|
|
|
|
{ .name = "editor",
|
2022-08-14 20:26:38 +02:00
|
|
|
.comment = "VIM: \"vim +%Bgo %F\", Emacs: \"emacs -nw +%L:%C %F\", "
|
|
|
|
|
"nano/micro/kakoune: \"nano/micro/kak +%L:%C %F\"",
|
xC: allow passing the cursor position to editors
Add a configuration option to set a custom editor command,
different from EDITOR or VISUAL--those remain as defaults.
Implement substitutions allowing to convey cursor information
to VIM and Emacs (the latter of which is fairly painful to cater to),
and put usage hints in the configuration option's description.
This should make the editing experience a bit more seamless
for users, even though the position is carried over in one way only.
No sophisticated quoting capabilities were deemed necessary,
it is a lot of code already. The particular syntax is inspired
by .desktop files and systemd.
["/bin/sh", "-c", "vim +$2go \"$1\"", filename, position, line, column]
would be a slightly simpler but cryptic way of implementing this.
2021-10-30 08:34:14 +02:00
|
|
|
.type = CONFIG_ITEM_STRING },
|
2021-10-30 04:18:52 +02:00
|
|
|
{ .name = "process_pasted_text",
|
|
|
|
|
.comment = "Normalize newlines and quote the command prefix in pastes",
|
|
|
|
|
.type = CONFIG_ITEM_BOOLEAN,
|
|
|
|
|
.default_ = "on" },
|
2015-11-19 14:23:10 +01:00
|
|
|
|
2022-08-26 03:43:32 +02:00
|
|
|
// Pan-server configuration:
|
2015-11-22 23:04:51 +01:00
|
|
|
{ .name = "autoaway_message",
|
|
|
|
|
.comment = "Automated away message",
|
|
|
|
|
.type = CONFIG_ITEM_STRING,
|
|
|
|
|
.default_ = "\"I'm not here right now\"" },
|
|
|
|
|
{ .name = "autoaway_delay",
|
|
|
|
|
.comment = "Delay from the last keypress in seconds",
|
|
|
|
|
.type = CONFIG_ITEM_INTEGER,
|
|
|
|
|
.validate = config_validate_nonnegative,
|
|
|
|
|
.default_ = "1800" },
|
2022-08-26 03:43:32 +02:00
|
|
|
{ .name = "reconnect_delay_growing",
|
|
|
|
|
.comment = "Growth factor for the reconnect delay",
|
|
|
|
|
.type = CONFIG_ITEM_INTEGER,
|
|
|
|
|
.validate = config_validate_nonnegative,
|
|
|
|
|
.default_ = "2" },
|
|
|
|
|
{ .name = "reconnect_delay_max",
|
|
|
|
|
.comment = "Maximum reconnect delay in seconds",
|
|
|
|
|
.type = CONFIG_ITEM_INTEGER,
|
|
|
|
|
.validate = config_validate_nonnegative,
|
|
|
|
|
.default_ = "600" },
|
2015-05-02 23:00:34 +02:00
|
|
|
{}
|
|
|
|
|
};
|
|
|
|
|
|
2024-08-08 09:02:18 +02:00
|
|
|
static const struct config_schema g_config_theme[] =
|
2015-05-02 23:00:34 +02:00
|
|
|
{
|
2020-09-20 13:24:02 +02:00
|
|
|
#define XX(x, y, z) { .name = #y, .comment = #z, .type = CONFIG_ITEM_STRING, \
|
2022-09-10 15:55:13 +02:00
|
|
|
.on_change = on_config_theme_change },
|
2015-05-02 23:00:34 +02:00
|
|
|
ATTR_TABLE (XX)
|
|
|
|
|
#undef XX
|
|
|
|
|
{}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
|
|
static void
|
2022-08-26 03:43:32 +02:00
|
|
|
load_config_general (struct config_item *subtree, void *user_data)
|
2015-05-02 23:00:34 +02:00
|
|
|
{
|
2022-08-26 03:43:32 +02:00
|
|
|
config_schema_apply_to_object (g_config_general, subtree, user_data);
|
2015-05-02 23:00:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
2022-09-10 15:55:13 +02:00
|
|
|
load_config_theme (struct config_item *subtree, void *user_data)
|
2015-05-02 23:00:34 +02:00
|
|
|
{
|
2022-09-10 15:55:13 +02:00
|
|
|
config_schema_apply_to_object (g_config_theme, subtree, user_data);
|
2015-05-02 23:00:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
register_config_modules (struct app_context *ctx)
|
|
|
|
|
{
|
|
|
|
|
struct config *config = &ctx->config;
|
2015-07-05 01:11:20 +02:00
|
|
|
// The servers are loaded later when we can create buffers for them
|
2022-09-10 15:55:13 +02:00
|
|
|
config_register_module (config, "servers", NULL, NULL);
|
|
|
|
|
config_register_module (config, "aliases", NULL, NULL);
|
|
|
|
|
config_register_module (config, "plugins", NULL, NULL);
|
|
|
|
|
config_register_module (config, "general", load_config_general, ctx);
|
|
|
|
|
config_register_module (config, "theme", load_config_theme, ctx);
|
2015-05-02 23:00:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
|
|
static const char *
|
2015-08-17 00:08:19 +02:00
|
|
|
get_config_string (struct config_item *root, const char *key)
|
2015-05-02 23:00:34 +02:00
|
|
|
{
|
2015-08-17 00:08:19 +02:00
|
|
|
struct config_item *item = config_item_get (root, key, NULL);
|
2015-05-02 23:00:34 +02:00
|
|
|
hard_assert (item);
|
|
|
|
|
if (item->type == CONFIG_ITEM_NULL)
|
|
|
|
|
return NULL;
|
|
|
|
|
hard_assert (config_item_type_is_string (item->type));
|
|
|
|
|
return item->value.string.str;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
2015-05-16 12:33:59 +02:00
|
|
|
set_config_string
|
2015-08-17 00:08:19 +02:00
|
|
|
(struct config_item *root, const char *key, const char *value)
|
2015-05-02 23:00:34 +02:00
|
|
|
{
|
2015-08-17 00:08:19 +02:00
|
|
|
struct config_item *item = config_item_get (root, key, NULL);
|
2015-05-02 23:00:34 +02:00
|
|
|
hard_assert (item);
|
|
|
|
|
|
2015-08-17 00:08:19 +02:00
|
|
|
struct config_item *new_ = config_item_string_from_cstr (value);
|
2015-05-02 23:00:34 +02:00
|
|
|
struct error *e = NULL;
|
|
|
|
|
if (config_item_set_from (item, new_, &e))
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
config_item_destroy (new_);
|
|
|
|
|
print_error ("couldn't set `%s' in configuration: %s", key, e->message);
|
|
|
|
|
error_free (e);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int64_t
|
2015-08-17 00:08:19 +02:00
|
|
|
get_config_integer (struct config_item *root, const char *key)
|
2015-05-02 23:00:34 +02:00
|
|
|
{
|
2015-08-17 00:08:19 +02:00
|
|
|
struct config_item *item = config_item_get (root, key, NULL);
|
2015-05-02 23:00:34 +02:00
|
|
|
hard_assert (item && item->type == CONFIG_ITEM_INTEGER);
|
|
|
|
|
return item->value.integer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
2015-08-17 00:08:19 +02:00
|
|
|
get_config_boolean (struct config_item *root, const char *key)
|
2015-05-02 23:00:34 +02:00
|
|
|
{
|
2015-08-17 00:08:19 +02:00
|
|
|
struct config_item *item = config_item_get (root, key, NULL);
|
2015-05-02 23:00:34 +02:00
|
|
|
hard_assert (item && item->type == CONFIG_ITEM_BOOLEAN);
|
|
|
|
|
return item->value.boolean;
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-03 19:34:01 +02:00
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
2015-07-10 01:44:02 +02:00
|
|
|
static struct str_map *
|
|
|
|
|
get_servers_config (struct app_context *ctx)
|
|
|
|
|
{
|
|
|
|
|
return &config_item_get (ctx->config.root, "servers", NULL)->value.object;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct str_map *
|
|
|
|
|
get_aliases_config (struct app_context *ctx)
|
|
|
|
|
{
|
|
|
|
|
return &config_item_get (ctx->config.root, "aliases", NULL)->value.object;
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-28 02:03:26 +01:00
|
|
|
static struct str_map *
|
|
|
|
|
get_plugins_config (struct app_context *ctx)
|
|
|
|
|
{
|
|
|
|
|
return &config_item_get (ctx->config.root, "plugins", NULL)->value.object;
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-10 01:44:02 +02:00
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
2015-05-03 19:34:01 +02:00
|
|
|
static void
|
2015-12-10 21:21:21 +01:00
|
|
|
serialize_configuration (struct config_item *root, struct str *output)
|
2015-05-03 19:34:01 +02:00
|
|
|
{
|
|
|
|
|
str_append (output,
|
|
|
|
|
"# " PROGRAM_NAME " " PROGRAM_VERSION " configuration file\n"
|
|
|
|
|
"#\n"
|
|
|
|
|
"# Relative paths are searched for in ${XDG_CONFIG_HOME:-~/.config}\n"
|
|
|
|
|
"# /" PROGRAM_NAME " as well as in $XDG_CONFIG_DIRS/" PROGRAM_NAME "\n"
|
|
|
|
|
"#\n"
|
|
|
|
|
"# Everything is in UTF-8. Any custom comments will be overwritten.\n"
|
|
|
|
|
"\n");
|
|
|
|
|
|
2015-12-10 21:21:21 +01:00
|
|
|
config_item_write (root, true, output);
|
2015-05-03 19:34:01 +02:00
|
|
|
}
|
|
|
|
|
|
2015-05-10 09:55:44 +02:00
|
|
|
// --- Terminal output ---------------------------------------------------------
|
|
|
|
|
|
2020-10-11 17:36:21 +02:00
|
|
|
/// Default colour pair
|
2015-05-10 09:55:44 +02:00
|
|
|
#define COLOR_DEFAULT -1
|
|
|
|
|
|
2020-10-11 17:36:21 +02:00
|
|
|
/// Bright versions of the basic colour set
|
2015-05-10 09:55:44 +02:00
|
|
|
#define COLOR_BRIGHT(x) (COLOR_ ## x + 8)
|
2015-04-12 04:52:39 +02:00
|
|
|
|
2020-10-11 17:36:21 +02:00
|
|
|
/// Builds a colour pair for 256-colour terminals with a 16-colour backup value
|
2015-05-10 23:06:19 +02:00
|
|
|
#define COLOR_256(name, c256) \
|
2015-11-13 09:05:25 +01:00
|
|
|
(((COLOR_ ## name) & 0xFFFF) | (((c256) & 0xFFFF) << 16))
|
2015-05-10 23:06:19 +02:00
|
|
|
|
2015-04-12 04:52:39 +02:00
|
|
|
typedef int (*terminal_printer_fn) (int);
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
putchar_stderr (int c)
|
|
|
|
|
{
|
|
|
|
|
return fputc (c, stderr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static terminal_printer_fn
|
|
|
|
|
get_attribute_printer (FILE *stream)
|
|
|
|
|
{
|
|
|
|
|
if (stream == stdout && g_terminal.stdout_is_tty)
|
|
|
|
|
return putchar;
|
|
|
|
|
if (stream == stderr && g_terminal.stderr_is_tty)
|
|
|
|
|
return putchar_stderr;
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-25 12:28:17 +02:00
|
|
|
// ~~~ Attribute printer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
2015-05-10 23:06:19 +02:00
|
|
|
|
|
|
|
|
// A little tool that tries to make the most of the terminal's capabilities
|
|
|
|
|
// to set up text attributes. It mostly targets just terminal emulators as that
|
|
|
|
|
// is what people are using these days. At least no stupid ncurses limits us
|
2020-10-11 17:36:21 +02:00
|
|
|
// with colour pairs.
|
2015-05-10 23:06:19 +02:00
|
|
|
|
2016-10-29 18:02:03 +02:00
|
|
|
struct attr_printer
|
2015-05-11 19:09:42 +02:00
|
|
|
{
|
2022-09-10 15:55:13 +02:00
|
|
|
struct attrs *attrs; ///< Named attributes
|
2015-08-08 20:29:31 +02:00
|
|
|
FILE *stream; ///< Output stream
|
2015-05-11 19:09:42 +02:00
|
|
|
bool dirty; ///< Attributes are set
|
|
|
|
|
};
|
|
|
|
|
|
2022-09-10 15:55:13 +02:00
|
|
|
#define ATTR_PRINTER_INIT(attrs, stream) { attrs, stream, true }
|
2016-10-29 05:33:22 +02:00
|
|
|
|
2020-10-04 12:04:24 +02:00
|
|
|
static void
|
|
|
|
|
attr_printer_filtered_puts (FILE *stream, const char *attr)
|
|
|
|
|
{
|
|
|
|
|
for (; *attr; attr++)
|
|
|
|
|
{
|
|
|
|
|
// sgr/set_attributes and sgr0/exit_attribute_mode like to enable or
|
|
|
|
|
// disable the ACS with SO/SI (e.g. for TERM=screen), however `less -R`
|
|
|
|
|
// does not skip over these characters and it screws up word wrapping
|
|
|
|
|
if (*attr == 14 /* SO */ || *attr == 15 /* SI */)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// Trivially skip delay sequences intended to be processed by tputs()
|
|
|
|
|
const char *end = NULL;
|
|
|
|
|
if (attr[0] == '$' && attr[1] == '<' && (end = strchr (attr, '>')))
|
|
|
|
|
attr = end;
|
|
|
|
|
else
|
|
|
|
|
fputc (*attr, stream);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-08-08 20:29:31 +02:00
|
|
|
static void
|
2016-10-29 18:02:03 +02:00
|
|
|
attr_printer_tputs (struct attr_printer *self, const char *attr)
|
2015-08-08 20:29:31 +02:00
|
|
|
{
|
|
|
|
|
terminal_printer_fn printer = get_attribute_printer (self->stream);
|
|
|
|
|
if (printer)
|
|
|
|
|
tputs (attr, 1, printer);
|
|
|
|
|
else
|
2020-10-04 12:04:24 +02:00
|
|
|
// We shouldn't really do this but we need it to output formatting
|
2022-08-29 08:21:14 +02:00
|
|
|
// to the pager--it should be SGR-only
|
2020-10-04 12:04:24 +02:00
|
|
|
attr_printer_filtered_puts (self->stream, attr);
|
2015-08-08 20:29:31 +02:00
|
|
|
}
|
|
|
|
|
|
2015-05-11 19:09:42 +02:00
|
|
|
static void
|
2016-10-29 18:02:03 +02:00
|
|
|
attr_printer_reset (struct attr_printer *self)
|
2015-05-11 19:09:42 +02:00
|
|
|
{
|
|
|
|
|
if (self->dirty)
|
2022-09-10 15:55:13 +02:00
|
|
|
attr_printer_tputs (self, exit_attribute_mode);
|
2015-05-11 19:09:42 +02:00
|
|
|
|
|
|
|
|
self->dirty = false;
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-12 02:43:53 +02:00
|
|
|
// NOTE: commonly terminals have:
|
2020-10-11 17:36:21 +02:00
|
|
|
// 8 colours (worst, bright fg often with BOLD, bg sometimes with BLINK)
|
|
|
|
|
// 16 colours (okayish, we have the full basic range guaranteed)
|
|
|
|
|
// 88 colours (the same plus a 4^3 RGB cube and a few shades of grey)
|
|
|
|
|
// 256 colours (best, like above but with a larger cube and more grey)
|
2015-05-12 02:43:53 +02:00
|
|
|
|
2020-10-11 17:36:21 +02:00
|
|
|
/// Interpolate from the 256-colour palette to the 88-colour one
|
2015-05-12 02:43:53 +02:00
|
|
|
static int
|
2016-10-29 18:02:03 +02:00
|
|
|
attr_printer_256_to_88 (int color)
|
2015-05-12 02:43:53 +02:00
|
|
|
{
|
|
|
|
|
// These colours are the same everywhere
|
|
|
|
|
if (color < 16)
|
|
|
|
|
return color;
|
|
|
|
|
|
2020-10-11 17:36:21 +02:00
|
|
|
// 24 -> 8 extra shades of grey
|
2015-05-12 02:43:53 +02:00
|
|
|
if (color >= 232)
|
|
|
|
|
return 80 + (color - 232) / 3;
|
|
|
|
|
|
|
|
|
|
// 6 * 6 * 6 cube -> 4 * 4 * 4 cube
|
|
|
|
|
int x[6] = { 0, 1, 1, 2, 2, 3 };
|
|
|
|
|
int index = color - 16;
|
|
|
|
|
return 16 +
|
|
|
|
|
( x[ index / 36 ] << 8
|
|
|
|
|
| x[(index / 6) % 6 ] << 4
|
|
|
|
|
| x[(index % 6) ] );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
2016-10-29 18:02:03 +02:00
|
|
|
attr_printer_decode_color (int color, bool *is_bright)
|
2015-05-12 02:43:53 +02:00
|
|
|
{
|
|
|
|
|
int16_t c16 = color; hard_assert (c16 < 16);
|
|
|
|
|
int16_t c256 = color >> 16; hard_assert (c256 < 256);
|
|
|
|
|
|
|
|
|
|
*is_bright = false;
|
|
|
|
|
switch (max_colors)
|
|
|
|
|
{
|
|
|
|
|
case 8:
|
|
|
|
|
if (c16 >= 8)
|
|
|
|
|
{
|
|
|
|
|
c16 -= 8;
|
|
|
|
|
*is_bright = true;
|
|
|
|
|
}
|
2018-01-07 05:45:34 +01:00
|
|
|
// Fall-through
|
2015-05-12 02:43:53 +02:00
|
|
|
case 16:
|
|
|
|
|
return c16;
|
|
|
|
|
|
|
|
|
|
case 88:
|
2016-10-29 18:02:03 +02:00
|
|
|
return c256 <= 0 ? c16 : attr_printer_256_to_88 (c256);
|
2015-05-12 02:43:53 +02:00
|
|
|
case 256:
|
|
|
|
|
return c256 <= 0 ? c16 : c256;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
// Unsupported palette
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-05-11 19:09:42 +02:00
|
|
|
|
|
|
|
|
static void
|
2016-10-29 18:02:03 +02:00
|
|
|
attr_printer_apply (struct attr_printer *self,
|
|
|
|
|
int text_attrs, int wanted_fg, int wanted_bg)
|
2015-05-11 19:09:42 +02:00
|
|
|
{
|
2015-05-12 02:43:53 +02:00
|
|
|
bool fg_is_bright;
|
2016-10-29 18:02:03 +02:00
|
|
|
int fg = attr_printer_decode_color (wanted_fg, &fg_is_bright);
|
2015-05-12 02:43:53 +02:00
|
|
|
bool bg_is_bright;
|
2016-10-29 18:02:03 +02:00
|
|
|
int bg = attr_printer_decode_color (wanted_bg, &bg_is_bright);
|
2015-05-11 19:09:42 +02:00
|
|
|
|
2016-10-29 18:02:03 +02:00
|
|
|
bool have_inverse = !!(text_attrs & TEXT_INVERSE);
|
2015-08-10 22:42:53 +02:00
|
|
|
if (have_inverse)
|
2015-05-11 19:09:42 +02:00
|
|
|
{
|
2015-05-12 02:43:53 +02:00
|
|
|
bool tmp = fg_is_bright;
|
|
|
|
|
fg_is_bright = bg_is_bright;
|
|
|
|
|
bg_is_bright = tmp;
|
2015-05-11 19:09:42 +02:00
|
|
|
}
|
|
|
|
|
|
2015-08-10 22:42:53 +02:00
|
|
|
// In 8 colour mode, some terminals don't support bright backgrounds.
|
|
|
|
|
// However, we can make use of the fact that the brightness change caused
|
|
|
|
|
// by the bold attribute is retained when inverting the colours.
|
|
|
|
|
// This has the downside of making the text bold when it's not supposed
|
|
|
|
|
// to be, and we still can't make both colours bright, so it's more of
|
|
|
|
|
// an interesting hack rather than anything else.
|
|
|
|
|
if (!fg_is_bright && bg_is_bright && have_inverse)
|
2016-10-29 18:02:03 +02:00
|
|
|
text_attrs |= TEXT_BOLD;
|
2015-08-10 22:42:53 +02:00
|
|
|
else if (!fg_is_bright && bg_is_bright
|
|
|
|
|
&& !have_inverse && fg >= 0 && bg >= 0)
|
|
|
|
|
{
|
|
|
|
|
// As long as none of the colours is the default, we can swap them
|
|
|
|
|
int tmp = fg; fg = bg; bg = tmp;
|
2016-10-29 18:02:03 +02:00
|
|
|
text_attrs |= TEXT_BOLD | TEXT_INVERSE;
|
2015-08-10 22:42:53 +02:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2020-09-16 07:42:05 +02:00
|
|
|
// This often works, however...
|
2016-10-29 18:02:03 +02:00
|
|
|
if (fg_is_bright) text_attrs |= TEXT_BOLD;
|
2020-09-16 07:42:05 +02:00
|
|
|
// this turns out to be annoying if implemented "correctly"
|
2016-10-29 18:02:03 +02:00
|
|
|
if (bg_is_bright) text_attrs |= TEXT_BLINK;
|
2015-08-10 22:42:53 +02:00
|
|
|
}
|
2015-05-11 19:09:42 +02:00
|
|
|
|
2016-10-29 18:02:03 +02:00
|
|
|
attr_printer_reset (self);
|
2015-05-11 19:09:42 +02:00
|
|
|
|
2021-08-29 12:06:53 +02:00
|
|
|
// TEXT_MONOSPACE is unimplemented, for obvious reasons
|
2016-10-29 18:02:03 +02:00
|
|
|
if (text_attrs)
|
|
|
|
|
attr_printer_tputs (self, tparm (set_attributes,
|
2015-08-08 20:29:31 +02:00
|
|
|
0, // standout
|
2016-10-29 18:02:03 +02:00
|
|
|
text_attrs & TEXT_UNDERLINE,
|
|
|
|
|
text_attrs & TEXT_INVERSE,
|
|
|
|
|
text_attrs & TEXT_BLINK,
|
2015-08-08 20:29:31 +02:00
|
|
|
0, // dim
|
2016-10-29 18:02:03 +02:00
|
|
|
text_attrs & TEXT_BOLD,
|
2015-08-08 20:29:31 +02:00
|
|
|
0, // blank
|
|
|
|
|
0, // protect
|
|
|
|
|
0)); // acs
|
2020-10-11 17:58:35 +02:00
|
|
|
if ((text_attrs & TEXT_ITALIC) && enter_italics_mode)
|
2016-10-29 18:02:03 +02:00
|
|
|
attr_printer_tputs (self, enter_italics_mode);
|
2015-05-11 19:09:42 +02:00
|
|
|
|
2020-10-11 17:58:35 +02:00
|
|
|
char *smxx = NULL;
|
|
|
|
|
if ((text_attrs & TEXT_CROSSED_OUT)
|
|
|
|
|
&& (smxx = tigetstr ("smxx")) && smxx != (char *) -1)
|
|
|
|
|
attr_printer_tputs (self, smxx);
|
|
|
|
|
|
2015-05-12 02:43:53 +02:00
|
|
|
if (fg >= 0)
|
2016-10-29 18:02:03 +02:00
|
|
|
attr_printer_tputs (self, g_terminal.color_set_fg[fg]);
|
2015-05-12 02:43:53 +02:00
|
|
|
if (bg >= 0)
|
2016-10-29 18:02:03 +02:00
|
|
|
attr_printer_tputs (self, g_terminal.color_set_bg[bg]);
|
2015-05-12 02:43:53 +02:00
|
|
|
|
2015-05-11 19:09:42 +02:00
|
|
|
self->dirty = true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-10 15:55:13 +02:00
|
|
|
static void
|
|
|
|
|
attr_printer_apply_named (struct attr_printer *self, int attribute)
|
|
|
|
|
{
|
|
|
|
|
attr_printer_reset (self);
|
|
|
|
|
if (attribute == ATTR_RESET)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// See the COLOR_256 macro or attr_printer_decode_color().
|
|
|
|
|
struct attrs *a = &self->attrs[attribute];
|
|
|
|
|
attr_printer_apply (self, a->attrs,
|
|
|
|
|
a->fg < 16 ? a->fg : (a->fg << 16 | (-1 & 0xFFFF)),
|
|
|
|
|
a->bg < 16 ? a->bg : (a->bg << 16 | (-1 & 0xFFFF)));
|
|
|
|
|
self->dirty = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ~~~ Logging redirect ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
vprint_attributed (struct app_context *ctx,
|
|
|
|
|
FILE *stream, intptr_t attribute, const char *fmt, va_list ap)
|
|
|
|
|
{
|
|
|
|
|
terminal_printer_fn printer = get_attribute_printer (stream);
|
|
|
|
|
if (!attribute)
|
|
|
|
|
printer = NULL;
|
|
|
|
|
|
|
|
|
|
struct attr_printer state = ATTR_PRINTER_INIT (ctx->theme, stream);
|
|
|
|
|
if (printer)
|
|
|
|
|
attr_printer_apply_named (&state, attribute);
|
|
|
|
|
|
|
|
|
|
vfprintf (stream, fmt, ap);
|
|
|
|
|
|
|
|
|
|
if (printer)
|
|
|
|
|
attr_printer_reset (&state);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
print_attributed (struct app_context *ctx,
|
|
|
|
|
FILE *stream, intptr_t attribute, const char *fmt, ...)
|
|
|
|
|
{
|
|
|
|
|
va_list ap;
|
|
|
|
|
va_start (ap, fmt);
|
|
|
|
|
vprint_attributed (ctx, stream, attribute, fmt, ap);
|
|
|
|
|
va_end (ap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
log_message_attributed (void *user_data, const char *quote, const char *fmt,
|
|
|
|
|
va_list ap)
|
|
|
|
|
{
|
|
|
|
|
FILE *stream = stderr;
|
|
|
|
|
struct app_context *ctx = g_ctx;
|
|
|
|
|
|
|
|
|
|
CALL (ctx->input, hide);
|
|
|
|
|
|
|
|
|
|
print_attributed (ctx, stream, (intptr_t) user_data, "%s", quote);
|
|
|
|
|
vprint_attributed (ctx, stream, (intptr_t) user_data, fmt, ap);
|
|
|
|
|
fputs ("\n", stream);
|
|
|
|
|
|
|
|
|
|
CALL (ctx->input, show);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ~~~ Theme ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
|
attr_by_name (const char *name)
|
|
|
|
|
{
|
|
|
|
|
static const char *table[ATTR_COUNT] =
|
|
|
|
|
{
|
|
|
|
|
NULL,
|
|
|
|
|
#define XX(x, y, z) [ATTR_ ## x] = #y,
|
|
|
|
|
ATTR_TABLE (XX)
|
|
|
|
|
#undef XX
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (size_t i = 1; i < N_ELEMENTS (table); i++)
|
|
|
|
|
if (!strcmp (name, table[i]))
|
|
|
|
|
return i;
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
on_config_theme_change (struct config_item *item)
|
|
|
|
|
{
|
|
|
|
|
struct app_context *ctx = item->user_data;
|
|
|
|
|
ssize_t id = attr_by_name (item->schema->name);
|
|
|
|
|
if (id != -1)
|
|
|
|
|
{
|
|
|
|
|
// TODO: There should be a validator.
|
|
|
|
|
ctx->theme[id] = item->type == CONFIG_ITEM_NULL
|
|
|
|
|
? ctx->theme_defaults[id]
|
|
|
|
|
: attrs_decode (item->value.string.str);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
init_colors (struct app_context *ctx)
|
|
|
|
|
{
|
|
|
|
|
bool have_ti = init_terminal ();
|
|
|
|
|
|
|
|
|
|
#define INIT_ATTR(id, ...) ctx->theme[ATTR_ ## id] = \
|
|
|
|
|
ctx->theme_defaults[ATTR_ ## id] = (struct attrs) { __VA_ARGS__ }
|
|
|
|
|
|
|
|
|
|
INIT_ATTR (PROMPT, -1, -1, TEXT_BOLD);
|
2022-09-11 22:16:18 +02:00
|
|
|
INIT_ATTR (RESET, -1, -1, 0);
|
2022-09-10 15:55:13 +02:00
|
|
|
INIT_ATTR (DATE_CHANGE, -1, -1, TEXT_BOLD);
|
2022-09-11 22:16:18 +02:00
|
|
|
INIT_ATTR (READ_MARKER, COLOR_MAGENTA, -1, 0);
|
|
|
|
|
INIT_ATTR (WARNING, COLOR_YELLOW, -1, 0);
|
|
|
|
|
INIT_ATTR (ERROR, COLOR_RED, -1, 0);
|
2022-09-10 15:55:13 +02:00
|
|
|
|
2022-09-11 22:16:18 +02:00
|
|
|
INIT_ATTR (EXTERNAL, COLOR_WHITE, -1, 0);
|
|
|
|
|
INIT_ATTR (TIMESTAMP, COLOR_WHITE, -1, 0);
|
2022-09-10 15:55:13 +02:00
|
|
|
INIT_ATTR (HIGHLIGHT, COLOR_BRIGHT (YELLOW), COLOR_MAGENTA, TEXT_BOLD);
|
2022-09-11 22:16:18 +02:00
|
|
|
INIT_ATTR (ACTION, COLOR_RED, -1, 0);
|
|
|
|
|
INIT_ATTR (USERHOST, COLOR_CYAN, -1, 0);
|
|
|
|
|
INIT_ATTR (JOIN, COLOR_GREEN, -1, 0);
|
|
|
|
|
INIT_ATTR (PART, COLOR_RED, -1, 0);
|
2022-09-10 15:55:13 +02:00
|
|
|
|
|
|
|
|
#undef INIT_ATTR
|
|
|
|
|
|
|
|
|
|
// This prevents formatters from obtaining an attribute printer function
|
|
|
|
|
if (!have_ti)
|
|
|
|
|
{
|
|
|
|
|
g_terminal.stdout_is_tty = false;
|
|
|
|
|
g_terminal.stderr_is_tty = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g_log_message_real = log_message_attributed;
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-06 01:54:02 +02:00
|
|
|
// --- Helpers -----------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
irc_server_strcmp (struct server *s, const char *a, const char *b)
|
|
|
|
|
{
|
|
|
|
|
int x;
|
|
|
|
|
while (*a || *b)
|
|
|
|
|
if ((x = s->irc_tolower (*a++) - s->irc_tolower (*b++)))
|
|
|
|
|
return x;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
irc_server_strncmp (struct server *s, const char *a, const char *b, size_t n)
|
|
|
|
|
{
|
|
|
|
|
int x;
|
|
|
|
|
while (n-- && (*a || *b))
|
|
|
|
|
if ((x = s->irc_tolower (*a++) - s->irc_tolower (*b++)))
|
|
|
|
|
return x;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static char *
|
|
|
|
|
irc_cut_nickname (const char *prefix)
|
|
|
|
|
{
|
2015-07-11 17:54:38 +02:00
|
|
|
return cstr_cut_until (prefix, "!@");
|
2015-07-06 01:54:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const char *
|
|
|
|
|
irc_find_userhost (const char *prefix)
|
|
|
|
|
{
|
|
|
|
|
const char *p = strchr (prefix, '!');
|
|
|
|
|
return p ? p + 1 : NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
irc_is_this_us (struct server *s, const char *prefix)
|
|
|
|
|
{
|
|
|
|
|
// This shouldn't be called before successfully registering.
|
|
|
|
|
// Better safe than sorry, though.
|
|
|
|
|
if (!s->irc_user)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
char *nick = irc_cut_nickname (prefix);
|
|
|
|
|
bool result = !irc_server_strcmp (s, nick, s->irc_user->nickname);
|
|
|
|
|
free (nick);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
irc_is_channel (struct server *s, const char *ident)
|
|
|
|
|
{
|
|
|
|
|
return *ident
|
|
|
|
|
&& (!!strchr (s->irc_chantypes, *ident) ||
|
|
|
|
|
!!strchr (s->irc_idchan_prefixes, *ident));
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-24 01:39:49 +02:00
|
|
|
// Message targets can be prefixed by a character filtering their targets
|
|
|
|
|
static const char *
|
|
|
|
|
irc_skip_statusmsg (struct server *s, const char *target)
|
|
|
|
|
{
|
|
|
|
|
return target + (*target && strchr (s->irc_statusmsg, *target));
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-17 12:08:08 +02:00
|
|
|
static bool
|
|
|
|
|
irc_is_extban (struct server *s, const char *target)
|
|
|
|
|
{
|
|
|
|
|
// Some servers have a prefix, and some support negation
|
|
|
|
|
if (s->irc_extban_prefix && *target++ != s->irc_extban_prefix)
|
|
|
|
|
return false;
|
|
|
|
|
if (*target == '~')
|
|
|
|
|
target++;
|
|
|
|
|
|
|
|
|
|
// XXX: we don't know if it's supposed to have an argument, or not
|
|
|
|
|
return *target && strchr (s->irc_extban_types, *target++)
|
|
|
|
|
&& strchr (":\0", *target);
|
|
|
|
|
}
|
|
|
|
|
|
2015-07-06 01:54:02 +02:00
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
2020-10-12 23:32:58 +02:00
|
|
|
// As of 2020, everything should be in UTF-8. And if it's not, we'll decode it
|
2015-07-06 01:54:02 +02:00
|
|
|
// as ISO Latin 1. This function should not be called on the whole message.
|
|
|
|
|
static char *
|
2016-04-03 04:05:04 +02:00
|
|
|
irc_to_utf8 (const char *text)
|
2015-07-06 01:54:02 +02:00
|
|
|
{
|
|
|
|
|
if (!text)
|
|
|
|
|
return NULL;
|
2020-10-21 05:39:05 +02:00
|
|
|
|
|
|
|
|
// XXX: the validation may be unnecessarily harsh, could do with a lenient
|
|
|
|
|
// first pass, then replace any errors with the replacement character
|
2015-07-06 01:54:02 +02:00
|
|
|
size_t len = strlen (text) + 1;
|
|
|
|
|
if (utf8_validate (text, len))
|
|
|
|
|
return xstrdup (text);
|
2016-04-03 04:05:04 +02:00
|
|
|
|
|
|
|
|
// Windows 1252 redefines several silly C1 control characters as glyphs
|
2016-10-28 18:16:21 +02:00
|
|
|
static const char c1[32][4] =
|
2016-04-03 04:05:04 +02:00
|
|
|
{
|
|
|
|
|
"\xe2\x82\xac", "\xc2\x81", "\xe2\x80\x9a", "\xc6\x92",
|
|
|
|
|
"\xe2\x80\x9e", "\xe2\x80\xa6", "\xe2\x80\xa0", "\xe2\x80\xa1",
|
|
|
|
|
"\xcb\x86", "\xe2\x80\xb0", "\xc5\xa0", "\xe2\x80\xb9",
|
|
|
|
|
"\xc5\x92", "\xc2\x8d", "\xc5\xbd", "\xc2\x8f",
|
|
|
|
|
"\xc2\x90", "\xe2\x80\x98", "\xe2\x80\x99", "\xe2\x80\x9c",
|
|
|
|
|
"\xe2\x80\x9d", "\xe2\x80\xa2", "\xe2\x80\x93", "\xe2\x80\x94",
|
|
|
|
|
"\xcb\x9c", "\xe2\x84\xa2", "\xc5\xa1", "\xe2\x80\xba",
|
|
|
|
|
"\xc5\x93", "\xc2\x9d", "\xc5\xbe", "\xc5\xb8",
|
|
|
|
|
};
|
|
|
|
|
|
2017-06-22 22:39:39 +02:00
|
|
|
struct str s = str_make ();
|
2016-04-03 04:05:04 +02:00
|
|
|
for (const char *p = text; *p; p++)
|
|
|
|
|
{
|
|
|
|
|
int c = *(unsigned char *) p;
|
|
|
|
|
if (c < 0x80)
|
|
|
|
|
str_append_c (&s, c);
|
|
|
|
|
else if (c < 0xA0)
|
|
|
|
|
str_append (&s, c1[c & 0x1f]);
|
|
|
|
|
else
|
|
|
|
|
str_append_data (&s,
|
|
|
|
|
(char[]) {0xc0 | (c >> 6), 0x80 | (c & 0x3f)}, 2);
|
|
|
|
|
}
|
|
|
|
|
return str_steal (&s);
|
2015-07-06 01:54:02 +02:00
|
|
|
}
|
|
|
|
|
|
2015-04-26 18:23:43 +02:00
|
|
|
// --- Output formatter --------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
// This complicated piece of code makes attributed text formatting simple.
|
|
|
|
|
// We use a printf-inspired syntax to push attributes and text to the object,
|
|
|
|
|
// then flush it either to a terminal, or a log file with formatting stripped.
|
|
|
|
|
//
|
|
|
|
|
// Format strings use a #-quoted notation, to differentiate from printf:
|
2015-06-28 02:49:28 +02:00
|
|
|
// #s inserts a string (expected to be in UTF-8)
|
|
|
|
|
// #d inserts a signed integer
|
2015-12-25 21:22:59 +01:00
|
|
|
// #l inserts a locale-encoded string
|
2015-06-28 02:49:28 +02:00
|
|
|
//
|
2022-09-13 20:23:48 +02:00
|
|
|
// #S inserts a string from the server in an unknown encoding
|
2021-08-29 12:07:31 +02:00
|
|
|
// #m inserts an IRC-formatted string (auto-resets at boundaries)
|
2015-06-28 02:49:28 +02:00
|
|
|
// #n cuts the nickname from a string and automatically colours it
|
2015-06-29 08:54:00 +02:00
|
|
|
// #N is like #n but also appends userhost, if present
|
2015-04-26 18:23:43 +02:00
|
|
|
//
|
|
|
|
|
// #a inserts named attributes (auto-resets)
|
|
|
|
|
// #r resets terminal attributes
|
2020-10-11 17:36:21 +02:00
|
|
|
// #c sets foreground colour
|
|
|
|
|
// #C sets background colour
|
2015-06-28 02:49:28 +02:00
|
|
|
//
|
|
|
|
|
// Modifiers:
|
|
|
|
|
// & free() the string argument after using it
|
2015-04-26 18:23:43 +02:00
|
|
|
|
2015-05-10 23:06:19 +02:00
|
|
|
static void
|
|
|
|
|
formatter_add_item (struct formatter *self, struct formatter_item template_)
|
2015-04-26 18:23:43 +02:00
|
|
|
{
|
2022-09-13 20:23:48 +02:00
|
|
|
// Auto-resetting tends to create unnecessary items,
|
|
|
|
|
// which also end up being relayed to frontends, so filter them out.
|
|
|
|
|
bool reset =
|
|
|
|
|
template_.type == FORMATTER_ITEM_ATTR &&
|
|
|
|
|
template_.attribute == ATTR_RESET;
|
|
|
|
|
if (self->clean && reset)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
self->clean = reset ||
|
|
|
|
|
(self->clean && template_.type == FORMATTER_ITEM_TEXT);
|
|
|
|
|
|
2015-05-10 23:06:19 +02:00
|
|
|
if (template_.text)
|
|
|
|
|
template_.text = xstrdup (template_.text);
|
|
|
|
|
|
2016-01-31 20:07:20 +01:00
|
|
|
if (self->items_len == self->items_alloc)
|
|
|
|
|
self->items = xreallocarray
|
|
|
|
|
(self->items, sizeof *self->items, (self->items_alloc <<= 1));
|
|
|
|
|
self->items[self->items_len++] = template_;
|
2015-04-26 18:23:43 +02:00
|
|
|
}
|
|
|
|
|
|
2015-05-10 23:06:19 +02:00
|
|
|
#define FORMATTER_ADD_ITEM(self, type_, ...) formatter_add_item ((self), \
|
|
|
|
|
(struct formatter_item) { .type = FORMATTER_ITEM_ ## type_, __VA_ARGS__ })
|
2015-04-26 18:23:43 +02:00
|
|
|
|
2015-05-10 23:06:19 +02:00
|
|
|
#define FORMATTER_ADD_RESET(self) \
|
|
|
|
|
FORMATTER_ADD_ITEM ((self), ATTR, .attribute = ATTR_RESET)
|
|
|
|
|
#define FORMATTER_ADD_TEXT(self, text_) \
|
|
|
|
|
FORMATTER_ADD_ITEM ((self), TEXT, .text = (text_))
|
2015-04-26 18:23:43 +02:00
|
|
|
|
2015-05-10 05:28:12 +02:00
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
|
|
enum
|
|
|
|
|
{
|
|
|
|
|
MIRC_WHITE, MIRC_BLACK, MIRC_BLUE, MIRC_GREEN,
|
|
|
|
|
MIRC_L_RED, MIRC_RED, MIRC_PURPLE, MIRC_ORANGE,
|
|
|
|
|
MIRC_YELLOW, MIRC_L_GREEN, MIRC_CYAN, MIRC_L_CYAN,
|
|
|
|
|
MIRC_L_BLUE, MIRC_L_PURPLE, MIRC_GRAY, MIRC_L_GRAY,
|
|
|
|
|
};
|
|
|
|
|
|
2020-10-11 17:36:21 +02:00
|
|
|
// We use estimates from the 16 colour terminal palette, or the 256 colour cube,
|
2015-05-10 05:28:12 +02:00
|
|
|
// which is not always available. The mIRC orange colour is only in the cube.
|
|
|
|
|
|
2015-05-10 23:06:19 +02:00
|
|
|
static const int g_mirc_to_terminal[] =
|
2015-05-10 05:28:12 +02:00
|
|
|
{
|
2015-05-10 23:06:19 +02:00
|
|
|
[MIRC_WHITE] = COLOR_256 (BRIGHT (WHITE), 231),
|
|
|
|
|
[MIRC_BLACK] = COLOR_256 (BLACK, 16),
|
|
|
|
|
[MIRC_BLUE] = COLOR_256 (BLUE, 19),
|
|
|
|
|
[MIRC_GREEN] = COLOR_256 (GREEN, 34),
|
|
|
|
|
[MIRC_L_RED] = COLOR_256 (BRIGHT (RED), 196),
|
|
|
|
|
[MIRC_RED] = COLOR_256 (RED, 124),
|
|
|
|
|
[MIRC_PURPLE] = COLOR_256 (MAGENTA, 127),
|
|
|
|
|
[MIRC_ORANGE] = COLOR_256 (BRIGHT (YELLOW), 214),
|
|
|
|
|
[MIRC_YELLOW] = COLOR_256 (BRIGHT (YELLOW), 226),
|
|
|
|
|
[MIRC_L_GREEN] = COLOR_256 (BRIGHT (GREEN), 46),
|
|
|
|
|
[MIRC_CYAN] = COLOR_256 (CYAN, 37),
|
|
|
|
|
[MIRC_L_CYAN] = COLOR_256 (BRIGHT (CYAN), 51),
|
|
|
|
|
[MIRC_L_BLUE] = COLOR_256 (BRIGHT (BLUE), 21),
|
|
|
|
|
[MIRC_L_PURPLE] = COLOR_256 (BRIGHT (MAGENTA),201),
|
|
|
|
|
[MIRC_GRAY] = COLOR_256 (BRIGHT (BLACK), 244),
|
|
|
|
|
[MIRC_L_GRAY] = COLOR_256 (WHITE, 252),
|
2015-05-10 05:28:12 +02:00
|
|
|
};
|
|
|
|
|
|
2020-10-11 17:41:57 +02:00
|
|
|
// https://modern.ircdocs.horse/formatting.html
|
|
|
|
|
// http://anti.teamidiot.de/static/nei/*/extended_mirc_color_proposal.html
|
2021-08-28 17:51:58 +02:00
|
|
|
static const int16_t g_extra_to_256[100 - 16] =
|
2020-10-11 17:41:57 +02:00
|
|
|
{
|
|
|
|
|
52, 94, 100, 58, 22, 29, 23, 24, 17, 54, 53, 89,
|
|
|
|
|
88, 130, 142, 64, 28, 35, 30, 25, 18, 91, 90, 125,
|
|
|
|
|
124, 166, 184, 106, 34, 49, 37, 33, 19, 129, 127, 161,
|
|
|
|
|
196, 208, 226, 154, 46, 86 , 51, 75, 21, 171, 201, 198,
|
|
|
|
|
203, 215, 227, 191, 83, 122, 87, 111, 63, 177, 207, 205,
|
|
|
|
|
217, 223, 229, 193, 157, 158, 159, 153, 147, 183, 219, 212,
|
|
|
|
|
16, 233, 235, 237, 239, 241, 244, 247, 250, 254, 231, -1
|
|
|
|
|
};
|
|
|
|
|
|
2015-05-10 09:55:44 +02:00
|
|
|
static const char *
|
2021-08-28 17:51:58 +02:00
|
|
|
irc_parse_mirc_color (const char *s, uint8_t *fg, uint8_t *bg)
|
2015-05-10 09:55:44 +02:00
|
|
|
{
|
|
|
|
|
if (!isdigit_ascii (*s))
|
2016-03-08 01:59:51 +01:00
|
|
|
{
|
2021-08-28 17:51:58 +02:00
|
|
|
*fg = *bg = 99;
|
2015-05-10 09:55:44 +02:00
|
|
|
return s;
|
2016-03-08 01:59:51 +01:00
|
|
|
}
|
2015-05-10 09:55:44 +02:00
|
|
|
|
2021-08-28 17:51:58 +02:00
|
|
|
*fg = *s++ - '0';
|
2015-05-10 09:55:44 +02:00
|
|
|
if (isdigit_ascii (*s))
|
2021-08-28 17:51:58 +02:00
|
|
|
*fg = *fg * 10 + (*s++ - '0');
|
2015-05-10 09:55:44 +02:00
|
|
|
|
|
|
|
|
if (*s != ',' || !isdigit_ascii (s[1]))
|
|
|
|
|
return s;
|
2015-05-12 02:48:12 +02:00
|
|
|
s++;
|
2015-05-10 09:55:44 +02:00
|
|
|
|
2021-08-28 17:51:58 +02:00
|
|
|
*bg = *s++ - '0';
|
2015-05-12 02:48:12 +02:00
|
|
|
if (isdigit_ascii (*s))
|
2021-08-28 17:51:58 +02:00
|
|
|
*bg = *bg * 10 + (*s++ - '0');
|
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-29 12:06:53 +02:00
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
|
|
struct irc_char_attrs
|
|
|
|
|
{
|
|
|
|
|
uint8_t fg, bg; ///< {Fore,back}ground colour or 99
|
|
|
|
|
uint8_t attributes; ///< TEXT_* flags, except TEXT_BLINK
|
|
|
|
|
uint8_t starts_at_boundary; ///< Possible to split here?
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
irc_serialize_char_attrs (const struct irc_char_attrs *attrs, struct str *out)
|
|
|
|
|
{
|
|
|
|
|
soft_assert (attrs->fg < 100 && attrs->bg < 100);
|
|
|
|
|
|
|
|
|
|
if (attrs->fg != 99 || attrs->bg != 99)
|
|
|
|
|
{
|
|
|
|
|
str_append_printf (out, "\x03%u", attrs->fg);
|
|
|
|
|
if (attrs->bg != 99)
|
|
|
|
|
str_append_printf (out, ",%02u", attrs->bg);
|
|
|
|
|
}
|
|
|
|
|
if (attrs->attributes & TEXT_BOLD) str_append_c (out, '\x02');
|
|
|
|
|
if (attrs->attributes & TEXT_ITALIC) str_append_c (out, '\x1d');
|
|
|
|
|
if (attrs->attributes & TEXT_UNDERLINE) str_append_c (out, '\x1f');
|
|
|
|
|
if (attrs->attributes & TEXT_INVERSE) str_append_c (out, '\x16');
|
|
|
|
|
if (attrs->attributes & TEXT_CROSSED_OUT) str_append_c (out, '\x1e');
|
|
|
|
|
if (attrs->attributes & TEXT_MONOSPACE) str_append_c (out, '\x11');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
irc_parse_attribute (char c)
|
|
|
|
|
{
|
|
|
|
|
switch (c)
|
|
|
|
|
{
|
|
|
|
|
case '\x02' /* ^B */: return TEXT_BOLD;
|
|
|
|
|
case '\x11' /* ^Q */: return TEXT_MONOSPACE;
|
|
|
|
|
case '\x16' /* ^V */: return TEXT_INVERSE;
|
|
|
|
|
case '\x1d' /* ^] */: return TEXT_ITALIC;
|
|
|
|
|
case '\x1e' /* ^^ */: return TEXT_CROSSED_OUT;
|
|
|
|
|
case '\x1f' /* ^_ */: return TEXT_UNDERLINE;
|
|
|
|
|
case '\x0f' /* ^O */: return -1;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The text needs to be NUL-terminated, and a valid UTF-8 string
|
|
|
|
|
static struct irc_char_attrs *
|
|
|
|
|
irc_analyze_text (const char *text, size_t len)
|
|
|
|
|
{
|
|
|
|
|
struct irc_char_attrs *attrs = xcalloc (len, sizeof *attrs),
|
|
|
|
|
blank = { .fg = 99, .bg = 99, .starts_at_boundary = true },
|
|
|
|
|
next = blank, cur = next;
|
|
|
|
|
|
|
|
|
|
for (size_t i = 0; i != len; cur = next)
|
|
|
|
|
{
|
|
|
|
|
const char *start = text;
|
|
|
|
|
hard_assert (utf8_decode (&text, len - i) >= 0);
|
|
|
|
|
|
|
|
|
|
int attribute = irc_parse_attribute (*start);
|
|
|
|
|
if (*start == '\x03')
|
|
|
|
|
text = irc_parse_mirc_color (text, &next.fg, &next.bg);
|
|
|
|
|
else if (attribute > 0)
|
|
|
|
|
next.attributes ^= attribute;
|
|
|
|
|
else if (attribute < 0)
|
|
|
|
|
next = blank;
|
|
|
|
|
|
|
|
|
|
while (start++ != text)
|
|
|
|
|
{
|
|
|
|
|
attrs[i++] = cur;
|
|
|
|
|
cur.starts_at_boundary = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return attrs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
2021-08-28 17:51:58 +02:00
|
|
|
static const char *
|
|
|
|
|
formatter_parse_mirc_color (struct formatter *self, const char *s)
|
|
|
|
|
{
|
|
|
|
|
uint8_t fg = 255, bg = 255;
|
|
|
|
|
s = irc_parse_mirc_color (s, &fg, &bg);
|
|
|
|
|
|
|
|
|
|
if (fg < 16)
|
|
|
|
|
FORMATTER_ADD_ITEM (self, FG_COLOR, .color = g_mirc_to_terminal[fg]);
|
|
|
|
|
else if (fg < 100)
|
|
|
|
|
FORMATTER_ADD_ITEM (self, FG_COLOR,
|
|
|
|
|
.color = COLOR_256 (DEFAULT, g_extra_to_256[fg - 16]));
|
|
|
|
|
|
2020-10-11 17:41:57 +02:00
|
|
|
if (bg < 16)
|
2015-05-10 23:06:19 +02:00
|
|
|
FORMATTER_ADD_ITEM (self, BG_COLOR, .color = g_mirc_to_terminal[bg]);
|
2021-08-28 17:51:58 +02:00
|
|
|
else if (bg < 100)
|
2020-10-11 17:41:57 +02:00
|
|
|
FORMATTER_ADD_ITEM (self, BG_COLOR,
|
2021-08-28 17:51:58 +02:00
|
|
|
.color = COLOR_256 (DEFAULT, g_extra_to_256[bg - 16]));
|
2015-05-10 09:55:44 +02:00
|
|
|
|
|
|
|
|
return s;
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-10 05:28:12 +02:00
|
|
|
static void
|
2021-08-29 12:07:31 +02:00
|
|
|
formatter_parse_message (struct formatter *self, const char *s)
|
2015-05-10 05:28:12 +02:00
|
|
|
{
|
2015-05-10 23:06:19 +02:00
|
|
|
FORMATTER_ADD_RESET (self);
|
2015-05-10 05:28:12 +02:00
|
|
|
|
2017-06-22 22:39:39 +02:00
|
|
|
struct str buf = str_make ();
|
2015-05-10 23:11:43 +02:00
|
|
|
unsigned char c;
|
2015-05-10 05:28:12 +02:00
|
|
|
while ((c = *s++))
|
|
|
|
|
{
|
2015-05-10 09:55:44 +02:00
|
|
|
if (buf.len && c < 0x20)
|
|
|
|
|
{
|
2015-05-10 23:06:19 +02:00
|
|
|
FORMATTER_ADD_TEXT (self, buf.str);
|
2015-05-10 09:55:44 +02:00
|
|
|
str_reset (&buf);
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-29 12:06:53 +02:00
|
|
|
int attribute = irc_parse_attribute (c);
|
|
|
|
|
if (c == '\x03')
|
2015-05-10 09:55:44 +02:00
|
|
|
s = formatter_parse_mirc_color (self, s);
|
2021-08-29 12:06:53 +02:00
|
|
|
else if (attribute > 0)
|
|
|
|
|
FORMATTER_ADD_ITEM (self, SIMPLE, .attribute = attribute);
|
|
|
|
|
else if (attribute < 0)
|
2015-05-10 23:06:19 +02:00
|
|
|
FORMATTER_ADD_RESET (self);
|
2021-08-29 12:06:53 +02:00
|
|
|
else
|
2015-05-10 09:55:44 +02:00
|
|
|
str_append_c (&buf, c);
|
2015-05-10 05:28:12 +02:00
|
|
|
}
|
|
|
|
|
|
2015-05-10 09:55:44 +02:00
|
|
|
if (buf.len)
|
2015-05-10 23:06:19 +02:00
|
|
|
FORMATTER_ADD_TEXT (self, buf.str);
|
2015-05-10 05:28:12 +02:00
|
|
|
|
2015-05-10 09:55:44 +02:00
|
|
|
str_free (&buf);
|
2015-05-10 23:06:19 +02:00
|
|
|
FORMATTER_ADD_RESET (self);
|
2015-05-10 05:28:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
2015-06-28 02:49:28 +02:00
|
|
|
static void
|
2016-10-23 13:38:46 +02:00
|
|
|
formatter_parse_nick (struct formatter *self, const char *s)
|
2015-06-28 02:49:28 +02:00
|
|
|
{
|
2015-09-24 02:03:10 +02:00
|
|
|
// For outgoing messages; maybe we should add a special #t for them
|
|
|
|
|
// which would also make us not cut off the userhost part, ever
|
|
|
|
|
if (irc_is_channel (self->s, irc_skip_statusmsg (self->s, s)))
|
|
|
|
|
{
|
2016-04-03 04:05:04 +02:00
|
|
|
char *tmp = irc_to_utf8 (s);
|
2015-09-24 02:03:10 +02:00
|
|
|
FORMATTER_ADD_TEXT (self, tmp);
|
|
|
|
|
free (tmp);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-28 02:49:28 +02:00
|
|
|
char *nick = irc_cut_nickname (s);
|
2015-07-27 01:18:32 +02:00
|
|
|
int color = siphash_wrapper (nick, strlen (nick)) % 7;
|
|
|
|
|
|
|
|
|
|
// Never use the black colour, could become transparent on black terminals;
|
|
|
|
|
// white is similarly excluded from the range
|
|
|
|
|
if (color == COLOR_BLACK)
|
2015-08-11 21:22:44 +02:00
|
|
|
color = (uint16_t) -1;
|
2015-07-27 01:18:32 +02:00
|
|
|
|
2020-10-11 17:36:21 +02:00
|
|
|
// Use a colour from the 256-colour cube if available
|
2015-08-10 07:51:03 +02:00
|
|
|
color |= self->ctx->nick_palette[siphash_wrapper (nick,
|
|
|
|
|
strlen (nick)) % self->ctx->nick_palette_len] << 16;
|
2015-06-28 02:49:28 +02:00
|
|
|
|
2020-10-11 17:36:21 +02:00
|
|
|
// We always use the default colour for ourselves
|
2015-06-28 02:49:28 +02:00
|
|
|
if (self->s && irc_is_this_us (self->s, nick))
|
|
|
|
|
color = -1;
|
|
|
|
|
|
|
|
|
|
FORMATTER_ADD_ITEM (self, FG_COLOR, .color = color);
|
|
|
|
|
|
2016-04-03 04:05:04 +02:00
|
|
|
char *x = irc_to_utf8 (nick);
|
2015-06-28 02:49:28 +02:00
|
|
|
free (nick);
|
|
|
|
|
FORMATTER_ADD_TEXT (self, x);
|
|
|
|
|
free (x);
|
|
|
|
|
|
2020-10-11 17:36:21 +02:00
|
|
|
// Need to reset the colour afterwards
|
2015-06-28 02:49:28 +02:00
|
|
|
FORMATTER_ADD_ITEM (self, FG_COLOR, .color = -1);
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-29 08:54:00 +02:00
|
|
|
static void
|
2016-10-23 13:38:46 +02:00
|
|
|
formatter_parse_nick_full (struct formatter *self, const char *s)
|
2015-06-29 08:54:00 +02:00
|
|
|
{
|
|
|
|
|
formatter_parse_nick (self, s);
|
|
|
|
|
|
|
|
|
|
const char *userhost;
|
|
|
|
|
if (!(userhost = irc_find_userhost (s)))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
FORMATTER_ADD_TEXT (self, " (");
|
|
|
|
|
FORMATTER_ADD_ITEM (self, ATTR, .attribute = ATTR_USERHOST);
|
|
|
|
|
|
2016-04-03 04:05:04 +02:00
|
|
|
char *x = irc_to_utf8 (userhost);
|
2015-06-29 08:54:00 +02:00
|
|
|
FORMATTER_ADD_TEXT (self, x);
|
|
|
|
|
free (x);
|
|
|
|
|
|
|
|
|
|
FORMATTER_ADD_RESET (self);
|
|
|
|
|
FORMATTER_ADD_TEXT (self, ")");
|
|
|
|
|
}
|
|
|
|
|
|
2015-04-26 18:23:43 +02:00
|
|
|
static const char *
|
|
|
|
|
formatter_parse_field (struct formatter *self,
|
|
|
|
|
const char *field, struct str *buf, va_list *ap)
|
|
|
|
|
{
|
2015-06-28 02:49:28 +02:00
|
|
|
bool free_string = false;
|
|
|
|
|
char *s = NULL;
|
|
|
|
|
char *tmp = NULL;
|
2015-04-26 18:23:43 +02:00
|
|
|
int c;
|
|
|
|
|
|
|
|
|
|
restart:
|
|
|
|
|
switch ((c = *field++))
|
|
|
|
|
{
|
|
|
|
|
// We can push boring text content to the caller's buffer
|
|
|
|
|
// and let it flush the buffer only when it's actually needed
|
|
|
|
|
case 'd':
|
2015-06-28 02:49:28 +02:00
|
|
|
tmp = xstrdup_printf ("%d", va_arg (*ap, int));
|
|
|
|
|
str_append (buf, tmp);
|
|
|
|
|
free (tmp);
|
|
|
|
|
break;
|
|
|
|
|
case 's':
|
|
|
|
|
str_append (buf, (s = va_arg (*ap, char *)));
|
|
|
|
|
break;
|
2015-12-25 21:22:59 +01:00
|
|
|
case 'l':
|
|
|
|
|
if (!(tmp = iconv_xstrdup (self->ctx->term_to_utf8,
|
|
|
|
|
(s = va_arg (*ap, char *)), -1, NULL)))
|
|
|
|
|
print_error ("character conversion failed for: %s", "output");
|
|
|
|
|
else
|
|
|
|
|
str_append (buf, tmp);
|
|
|
|
|
free (tmp);
|
|
|
|
|
break;
|
2015-06-28 02:49:28 +02:00
|
|
|
|
|
|
|
|
case 'S':
|
2016-04-03 04:05:04 +02:00
|
|
|
tmp = irc_to_utf8 ((s = va_arg (*ap, char *)));
|
2015-06-28 02:49:28 +02:00
|
|
|
str_append (buf, tmp);
|
|
|
|
|
free (tmp);
|
2015-04-26 18:23:43 +02:00
|
|
|
break;
|
2015-05-10 09:55:44 +02:00
|
|
|
case 'm':
|
2016-04-03 04:05:04 +02:00
|
|
|
tmp = irc_to_utf8 ((s = va_arg (*ap, char *)));
|
2021-08-29 12:07:31 +02:00
|
|
|
formatter_parse_message (self, tmp);
|
2015-06-28 02:49:28 +02:00
|
|
|
free (tmp);
|
|
|
|
|
break;
|
|
|
|
|
case 'n':
|
|
|
|
|
formatter_parse_nick (self, (s = va_arg (*ap, char *)));
|
2015-05-10 09:55:44 +02:00
|
|
|
break;
|
2015-06-29 08:54:00 +02:00
|
|
|
case 'N':
|
|
|
|
|
formatter_parse_nick_full (self, (s = va_arg (*ap, char *)));
|
|
|
|
|
break;
|
2015-05-10 09:55:44 +02:00
|
|
|
|
2015-04-26 18:23:43 +02:00
|
|
|
case 'a':
|
2015-05-10 23:06:19 +02:00
|
|
|
FORMATTER_ADD_ITEM (self, ATTR, .attribute = va_arg (*ap, int));
|
2015-04-26 18:23:43 +02:00
|
|
|
break;
|
|
|
|
|
case 'c':
|
2015-05-10 23:06:19 +02:00
|
|
|
FORMATTER_ADD_ITEM (self, FG_COLOR, .color = va_arg (*ap, int));
|
2015-04-26 18:23:43 +02:00
|
|