16 Commits

Author SHA1 Message Date
ef0cbe9a59 Rename the project
It is about to see some extensions, obsoleting the number three.
2022-08-07 10:40:42 +02:00
2d8808d795 utm-filter.lua: mention the passing of fbclid 2022-07-18 17:59:28 +02:00
60d52ad479 xC, xD: add basic WALLOPS support 2022-02-04 22:48:54 +01:00
b358f53ec3 Bump version, update NEWS 2021-12-21 05:58:34 +01:00
2eb315f5c4 utm-filter.lua: add Facebook to the filter 2021-12-20 14:36:41 +01:00
851c2ee548 CMakeLists.txt: fix macOS build 2021-11-02 15:34:51 +01:00
f9848ed627 Update README 2021-10-31 05:16:57 +01:00
686a39df38 CMakeLists.txt: slightly modernize 2021-10-31 04:30:04 +01:00
9cea3fca91 Update NEWS 2021-10-30 14:25:13 +02:00
5165f76b7c xC: quote text coming from a bracketed paste
Not having this has caused me much annoyance over the years.
2021-10-30 09:27:32 +02:00
92ac13f3c6 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 09:02:35 +02:00
df4ca74580 xC: make libedit autocomplete less miserable
Omitting even this hack was a huge hit to overall usability.
2021-10-30 08:29:16 +02:00
9e297244a4 Update .gitignore 2021-10-30 03:37:22 +02:00
d32ba133c0 Add clang-format configuration, clean up 2021-10-30 02:55:19 +02:00
ce3976e1ec xC: normalize ^J behaviour to follow Readline
For some reason Editline inserts it verbatim,
but in a more broken manner than it has with ^V^J.
2021-10-28 08:49:01 +02:00
e5ed89646b xC: fix newer libedit (2021-08-29) 2021-10-28 08:23:52 +02:00
12 changed files with 351 additions and 105 deletions

32
.clang-format Normal file
View File

@@ -0,0 +1,32 @@
# clang-format is fairly limited, and these rules are approximate:
# - array initializers can get terribly mangled with clang-format 12.0,
# - sometimes it still aligns with space characters,
# - struct name NL { NL ... NL } NL name; is unachievable.
BasedOnStyle: GNU
ColumnLimit: 80
IndentWidth: 4
TabWidth: 4
UseTab: ForContinuationAndIndentation
BreakBeforeBraces: Allman
SpaceAfterCStyleCast: true
AlignAfterOpenBracket: DontAlign
AlignOperands: DontAlign
AlignConsecutiveMacros: Consecutive
AllowAllArgumentsOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
IndentGotoLabels: false
# IncludeCategories has some potential, but it may also break the build.
# Note that the documentation says the value should be "Never".
SortIncludes: false
# This is a compromise, it generally works out aesthetically better.
BinPackArguments: false
# Unfortunately, this can't be told to align to column 40 or so.
SpacesBeforeTrailingComments: 2
# liberty-specific macro body wrappers.
MacroBlockBegin: "BLOCK_START"
MacroBlockEnd: "BLOCK_END"
ForEachMacros: ["LIST_FOR_EACH"]

10
.gitignore vendored
View File

@@ -3,7 +3,9 @@
# Qt Creator files # Qt Creator files
/CMakeLists.txt.user* /CMakeLists.txt.user*
/uirc3.config /xK.config
/uirc3.files /xK.files
/uirc3.creator* /xK.creator*
/uirc3.includes /xK.includes
/xK.cflags
/xK.cxxflags

View File

@@ -1,15 +1,19 @@
cmake_minimum_required (VERSION 3.0) # Ubuntu 18.04 LTS and OpenBSD 6.4
project (uirc3 VERSION 1.4.0 LANGUAGES C) cmake_minimum_required (VERSION 3.10)
project (xK VERSION 1.5.0 DESCRIPTION "IRC client, daemon and bot" LANGUAGES C)
# Options # Options
option (WANT_READLINE "Use GNU Readline for the UI (better)" ON) option (WANT_READLINE "Use GNU Readline for the UI (better)" ON)
option (WANT_LIBEDIT "Use BSD libedit for the UI" OFF) option (WANT_LIBEDIT "Use BSD libedit for the UI" OFF)
# Moar warnings # Moar warnings
set (CMAKE_C_STANDARD 99)
set (CMAKE_C_STANDARD_REQUIRED ON)
set (CMAKE_C_EXTENSIONS OFF)
if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC) if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
# -Wunused-function is pretty annoying here, as everything is static # -Wunused-function is pretty annoying here, as everything is static
set (wdisabled "-Wno-unused-function") set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-unused-function")
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -Wall -Wextra ${wdisabled}")
endif () endif ()
# Version # Version
@@ -57,6 +61,8 @@ if ("${CMAKE_SYSTEM_NAME}" MATCHES "BSD")
# Need this for SIGWINCH in FreeBSD and OpenBSD respectively; # Need this for SIGWINCH in FreeBSD and OpenBSD respectively;
# our POSIX version macros make it undefined # our POSIX version macros make it undefined
add_definitions (-D__BSD_VISIBLE=1 -D_BSD_SOURCE=1) add_definitions (-D__BSD_VISIBLE=1 -D_BSD_SOURCE=1)
elseif (APPLE)
add_definitions (-D_DARWIN_C_SOURCE)
endif () endif ()
# -lrt is only for glibc < 2.17 # -lrt is only for glibc < 2.17
@@ -112,10 +118,16 @@ endif ()
if ((WANT_READLINE AND WANT_LIBEDIT) OR (NOT WANT_READLINE AND NOT WANT_LIBEDIT)) if ((WANT_READLINE AND WANT_LIBEDIT) OR (NOT WANT_READLINE AND NOT WANT_LIBEDIT))
message (SEND_ERROR "You have to choose either GNU Readline or libedit") message (SEND_ERROR "You have to choose either GNU Readline or libedit")
elseif (WANT_READLINE) elseif (WANT_READLINE)
pkg_check_modules (readline readline)
# OpenBSD's default readline is too old # OpenBSD's default readline is too old
if ("${CMAKE_SYSTEM_NAME}" MATCHES "OpenBSD") if ("${CMAKE_SYSTEM_NAME}" MATCHES "OpenBSD")
include_directories (${OPENBSD_LOCALBASE}/include/ereadline) include_directories (${OPENBSD_LOCALBASE}/include/ereadline)
list (APPEND xC_libraries ereadline) list (APPEND xC_libraries ereadline)
elseif (readline_FOUND)
list (APPEND xC_libraries ${readline_LIBRARIES})
include_directories (${readline_INCLUDE_DIRS})
link_directories (${readline_LIBRARY_DIRS})
else () else ()
list (APPEND xC_libraries readline) list (APPEND xC_libraries readline)
endif () endif ()
@@ -217,7 +229,6 @@ foreach (page ${project_MAN_PAGES})
endforeach () endforeach ()
# CPack # CPack
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Unreasonable IRC client, daemon and bot")
set (CPACK_PACKAGE_VERSION "${project_version_safe}") set (CPACK_PACKAGE_VERSION "${project_version_safe}")
set (CPACK_PACKAGE_VENDOR "Premysl Eric Janouch") set (CPACK_PACKAGE_VENDOR "Premysl Eric Janouch")
set (CPACK_PACKAGE_CONTACT "Přemysl Eric Janouch <p@janouch.name>") set (CPACK_PACKAGE_CONTACT "Přemysl Eric Janouch <p@janouch.name>")

22
NEWS
View File

@@ -1,3 +1,25 @@
Unreleased
* xC: made it show WALLOPS messages, as PRIVMSG for the server buffer
* xD: implemented WALLOPS, choosing to make it target even non-operators
1.5.0 (2021-12-21) "The Show Must Go On"
* xC: made it possible to pass the cursor position to external editors,
in particular VIM and Emacs
* xC: started quoting text coming from bracketed pastes,
to minimize the risk of trying to execute filesystem paths as commands
* xC: fixed to work with post-2021-08-29 editline
* xC: extended editline's autocomplete to show all options
* utm-filter.lua: added Facebook's tracking parameter to the filter
1.4.0 (2021-10-06) "Call Me Scruffy Scruffington" 1.4.0 (2021-10-06) "Call Me Scruffy Scruffington"
* xC: made message autosplitting respect text formatting * xC: made message autosplitting respect text formatting

View File

@@ -1,32 +1,31 @@
uirc3 xK
===== ==
:compact-option:
The unreasonable IRC trinity. This project consists of an IRC client, daemon, 'xK' (chat kit) is an IRC software suite consisting of a terminal client,
and bot. It's all you're ever going to need for chatting, as long as you can daemon, and bot. It's all you're ever going to need for chatting,
make do with minimalist software. so long as you can make do with slightly minimalist software.
All of them have these potentially interesting properties: They come with these potentially interesting properties:
- IPv6 support - supporting IRCv3, SOCKS, IPv6, TLS (including client certificates)
- TLS support, including client certificates - lean on dependencies
- lean on dependencies (with the exception of 'xC')
- compact and arguably easy to hack on - compact and arguably easy to hack on
- very permissive license - maximally permissive license
xC xC
-- --
The IRC client. It is largely defined by being built on top of GNU Readline The IRC client, and the core of 'xK'. It is largely defined by being built
that has been hacked to death. Its interface should feel somewhat familiar for on top of GNU Readline that has been hacked to death. Its interface should feel
weechat or irssi users. somewhat familiar for weechat or irssi users.
image::xC.png[align="center"] image::xC.png[align="center"]
This is the largest application within the project. It has most of the stuff It has most of the stuff you'd expect of an IRC client, such as being
you'd expect of an IRC client, such as being able to set up multiple servers, multiserver, a powerful configuration system, integrated help, text formatting,
a powerful configuration system, integrated help, text formatting, CTCP queries, automatic splitting of overlong messages, multiline editing, bracketed paste
automatic splitting of overlong messages, autocomplete, logging to file, support, decent word wrapping, autocomplete, logging, CTCP queries, auto-away,
auto-away, command aliases and basic support for Lua scripting. command aliases, and basic support for Lua scripting. As a unique bonus,
you can launch a full text editor from within.
xD xD
-- --
@@ -37,10 +36,8 @@ do it just fine.
Notable features: Notable features:
- TLS autodetection (why doesn't everyone have this?), using secure defaults - TLS autodetection (I'm still wondering why everyone doesn't have this)
- IRCop authentication via TLS client certificates - IRCop authentication via TLS client certificates
- epoll/kqueue support; this means that it should be able to handle quite
a number of concurrent user connections
- partial IRCv3 support - partial IRCv3 support
Not supported: Not supported:
@@ -58,16 +55,14 @@ and development continues over there.
xB xB
-- --
The IRC bot. It builds upon the concept of my other VitaminA IRC bot. The main The IRC bot. While originally intended to be a simple rewrite of my old GNU AWK
characteristic of these two bots is that they run plugins as coprocesses, which bot in C, it fairly quickly became a playground, and it eventually got me into
allows for enhanced reliability and programming language freedom. writing the rest of this package.
While originally intended to be a simple rewrite of the original AWK bot in C, Its main characteristic is that it runs plugins as coprocesses, allowing for
it fairly quickly became a playground, and it eventually got me into writing enhanced reliability and programming language freedom. Moreover, it recovers
the rest of the package. from any crashes, and offers native SOCKS support (even though socksify can add
that easily to any program).
It survives crashes, server disconnects and timeouts, and also has native SOCKS
support (even though socksify can add that easily to any program).
Packages Packages
-------- --------
@@ -84,10 +79,10 @@ Additionally for 'xC': curses, libffi, lua >= 5.3 (optional),
Avoid libedit if you can, in general it works but at the moment history is Avoid libedit if you can, in general it works but at the moment history is
acting up and I have no clue about fixing it. acting up and I have no clue about fixing it.
$ git clone --recursive https://git.janouch.name/p/uirc3.git $ git clone --recursive https://git.janouch.name/p/xK.git
$ mkdir uirc3/build $ mkdir xK/build
$ cd uirc3/build $ cd xK/build
$ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug \ $ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DWANT_READLINE=ON -DWANT_LIBEDIT=OFF -DWANT_LUA=ON -DWANT_READLINE=ON -DWANT_LIBEDIT=OFF -DWANT_LUA=ON
$ make $ make
@@ -95,10 +90,10 @@ To install the application, you can do either the usual:
# make install # make install
Or you can try telling CMake to make a package for you. For Debian it is: Or you can try telling CMake to make a package for you:
$ cpack -G DEB $ cpack -G DEB # also supported: RPM, FreeBSD
# dpkg -i uirc3-*.deb # dpkg -i xK-*.deb
Usage Usage
----- -----
@@ -184,7 +179,7 @@ configurations accordingly, but I consider it rather messy and unnecessary.
Contributing and Support Contributing and Support
------------------------ ------------------------
Use https://git.janouch.name/p/uirc3 to report any bugs, request features, Use https://git.janouch.name/p/xK to report any bugs, request features,
or submit pull requests. `git send-email` is tolerated. If you want to discuss or submit pull requests. `git send-email` is tolerated. If you want to discuss
the project, feel free to join me at ircs://irc.janouch.name, channel #dev. the project, feel free to join me at ircs://irc.janouch.name, channel #dev.

View File

@@ -22,11 +22,11 @@
#define LIBERTY_WANT_PROTO_IRC #define LIBERTY_WANT_PROTO_IRC
#ifdef WANT_SYSLOG_LOGGING #ifdef WANT_SYSLOG_LOGGING
#define print_fatal_data ((void *) LOG_ERR) #define print_fatal_data ((void *) LOG_ERR)
#define print_error_data ((void *) LOG_ERR) #define print_error_data ((void *) LOG_ERR)
#define print_warning_data ((void *) LOG_WARNING) #define print_warning_data ((void *) LOG_WARNING)
#define print_status_data ((void *) LOG_INFO) #define print_status_data ((void *) LOG_INFO)
#define print_debug_data ((void *) LOG_DEBUG) #define print_debug_data ((void *) LOG_DEBUG)
#endif // WANT_SYSLOG_LOGGING #endif // WANT_SYSLOG_LOGGING
#include "liberty/liberty.c" #include "liberty/liberty.c"

View File

@@ -1,7 +1,7 @@
-- --
-- utm-filter.lua: filter out Google Analytics bullshit from URLs -- utm-filter.lua: filter out Google Analytics bullshit etc. from URLs
-- --
-- Copyright (c) 2015, Přemysl Eric Janouch <p@janouch.name> -- Copyright (c) 2015 - 2021, Přemysl Eric Janouch <p@janouch.name>
-- --
-- Permission to use, copy, modify, and/or distribute this software for any -- Permission to use, copy, modify, and/or distribute this software for any
-- purpose with or without fee is hereby granted. -- purpose with or without fee is hereby granted.
@@ -19,6 +19,10 @@
local banned = { local banned = {
gclid = 1, gclid = 1,
-- Alas, Facebook no longer uses this parameter, see:
-- https://news.ycombinator.com/item?id=32117489
fbclid = 1,
utm_source = 1, utm_source = 1,
utm_medium = 1, utm_medium = 1,
utm_term = 1, utm_term = 1,

View File

@@ -1,8 +1,8 @@
xB(1) xB(1)
===== =====
:doctype: manpage :doctype: manpage
:manmanual: uirc3 Manual :manmanual: xK Manual
:mansource: uirc3 {release-version} :mansource: xK {release-version}
Name Name
---- ----
@@ -100,5 +100,5 @@ _/usr/share/xB/plugins/_::
Reporting bugs Reporting bugs
-------------- --------------
Use https://git.janouch.name/p/uirc3 to report bugs, request features, Use https://git.janouch.name/p/xK to report bugs, request features,
or submit pull requests. or submit pull requests.

View File

@@ -1,8 +1,8 @@
xC(1) xC(1)
===== =====
:doctype: manpage :doctype: manpage
:manmanual: uirc3 Manual :manmanual: xK Manual
:mansource: uirc3 {release-version} :mansource: xK {release-version}
Name Name
---- ----
@@ -119,7 +119,7 @@ to work but exhibits bugs that are not our fault.
Reporting bugs Reporting bugs
-------------- --------------
Use https://git.janouch.name/p/uirc3 to report bugs, request features, Use https://git.janouch.name/p/xK to report bugs, request features,
or submit pull requests. or submit pull requests.
See also See also

224
xC.c
View File

@@ -238,8 +238,8 @@ struct input_vtable
/// Bind Alt+key to the given named function /// Bind Alt+key to the given named function
void (*bind_meta) (void *input, char key, const char *fn); void (*bind_meta) (void *input, char key, const char *fn);
/// Get the current line input /// Get the current line input and position within
char *(*get_line) (void *input); char *(*get_line) (void *input, int *position);
/// Clear the current line input /// Clear the current line input
void (*clear_line) (void *input); void (*clear_line) (void *input);
/// Insert text at current position /// Insert text at current position
@@ -361,9 +361,10 @@ input_rl_insert (void *input, const char *s)
} }
static char * static char *
input_rl_get_line (void *input) input_rl_get_line (void *input, int *position)
{ {
(void) input; (void) input;
if (position) *position = rl_point;
return rl_copy_text (0, rl_end); return rl_copy_text (0, rl_end);
} }
@@ -771,24 +772,13 @@ struct input_el
static void app_editline_init (struct input_el *self); static void app_editline_init (struct input_el *self);
static int
input_el__get_termios (int character, int fallback)
{
if (!g_terminal.initialized)
return fallback;
cc_t value = g_terminal.termios.c_cc[character];
if (value == _POSIX_VDISABLE)
return fallback;
return value;
}
static void static void
input_el__redisplay (void *input) input_el__redisplay (void *input)
{ {
// See rl_redisplay() // See rl_redisplay(), however NetBSD editline's map.c v1.54 breaks VREPRINT
// so we bind redisplay somewhere else in app_editline_init()
struct input_el *self = input; struct input_el *self = input;
char x[] = { input_el__get_termios (VREPRINT, 'R' - 0x40), 0 }; char x[] = { 'q' & 31, 0 };
el_push (self->editline, x); el_push (self->editline, x);
// We have to do this or it gets stuck and nothing is done // We have to do this or it gets stuck and nothing is done
@@ -871,10 +861,12 @@ input_el_insert (void *input, const char *s)
} }
static char * static char *
input_el_get_line (void *input) input_el_get_line (void *input, int *position)
{ {
struct input_el *self = input; struct input_el *self = input;
const LineInfo *info = el_line (self->editline); const LineInfo *info = el_line (self->editline);
int point = info->cursor - info->buffer;
if (position) *position = point;
return xstrndup (info->buffer, info->lastchar - info->buffer); return xstrndup (info->buffer, info->lastchar - info->buffer);
} }
@@ -2450,6 +2442,13 @@ static struct config_schema g_config_behaviour[] =
.type = CONFIG_ITEM_BOOLEAN, .type = CONFIG_ITEM_BOOLEAN,
.default_ = "on", .default_ = "on",
.on_change = on_config_word_wrapping_change }, .on_change = on_config_word_wrapping_change },
{ .name = "editor_command",
.comment = "VIM: \"vim +%Bgo %F\", Emacs: \"emacs -nw +%L:%C %F\"",
.type = CONFIG_ITEM_STRING },
{ .name = "process_pasted_text",
.comment = "Normalize newlines and quote the command prefix in pastes",
.type = CONFIG_ITEM_BOOLEAN,
.default_ = "on" },
{ .name = "date_change_line", { .name = "date_change_line",
.comment = "Input to strftime(3) for the date change line", .comment = "Input to strftime(3) for the date change line",
.type = CONFIG_ITEM_STRING, .type = CONFIG_ITEM_STRING,
@@ -6905,7 +6904,7 @@ irc_handle_join (struct server *s, const struct irc_message *msg)
buffer_add (s->ctx, buffer); buffer_add (s->ctx, buffer);
char *input = CALL (s->ctx->input, get_line); char *input = CALL_ (s->ctx->input, get_line, NULL);
if (!*input) if (!*input)
buffer_activate (s->ctx, buffer); buffer_activate (s->ctx, buffer);
else else
@@ -7489,6 +7488,16 @@ irc_handle_topic (struct server *s, const struct irc_message *msg)
} }
} }
static void
irc_handle_wallops (struct server *s, const struct irc_message *msg)
{
if (!msg->prefix || msg->params.len < 1)
return;
const char *message = msg->params.vector[0];
log_server (s, s->buffer, 0, "<#n> #m", msg->prefix, message);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static struct irc_handler g_irc_handlers[] = static struct irc_handler g_irc_handlers[] =
@@ -7512,6 +7521,7 @@ static struct irc_handler g_irc_handlers[] =
{ "QUIT", irc_handle_quit }, { "QUIT", irc_handle_quit },
{ "TAGMSG", irc_handle_tagmsg }, { "TAGMSG", irc_handle_tagmsg },
{ "TOPIC", irc_handle_topic }, { "TOPIC", irc_handle_topic },
{ "WALLOPS", irc_handle_wallops },
}; };
static bool static bool
@@ -12629,8 +12639,6 @@ process_input (struct app_context *ctx, char *user_input)
else else
{ {
struct strv lines = strv_make (); struct strv lines = strv_make ();
// XXX: this interprets commands in pasted text
cstr_split (input, "\r\n", false, &lines); cstr_split (input, "\r\n", false, &lines);
for (size_t i = 0; i < lines.len; i++) for (size_t i = 0; i < lines.len; i++)
(void) process_input_utf8 (ctx, (void) process_input_utf8 (ctx,
@@ -13163,7 +13171,7 @@ dump_input_to_file (struct app_context *ctx, char *template, struct error **e)
if (fd < 0) if (fd < 0)
return error_set (e, "%s", strerror (errno)); return error_set (e, "%s", strerror (errno));
char *input = CALL (ctx->input, get_line); char *input = CALL_ (ctx->input, get_line, NULL);
bool success = xwrite (fd, input, strlen (input), e); bool success = xwrite (fd, input, strlen (input), e);
free (input); free (input);
@@ -13191,6 +13199,103 @@ try_dump_input_to_file (struct app_context *ctx)
return NULL; return NULL;
} }
static struct strv
build_editor_command (struct app_context *ctx, const char *filename)
{
struct strv argv = strv_make ();
const char *editor = get_config_string
(ctx->config.root, "behaviour.editor_command");
if (!editor)
{
const char *command;
if (!(command = getenv ("VISUAL"))
&& !(command = getenv ("EDITOR")))
command = "vi";
strv_append (&argv, command);
strv_append (&argv, filename);
return argv;
}
int cursor = 0;
char *input = CALL_ (ctx->input, get_line, &cursor);
hard_assert (cursor >= 0);
mbstate_t ps;
memset (&ps, 0, sizeof ps);
wchar_t wch;
size_t len, processed = 0, line_one_based = 1, column = 0;
while (processed < (size_t) cursor
&& (len = mbrtowc (&wch, input + processed, cursor - processed, &ps))
&& len != (size_t) -2 && len != (size_t) -1)
{
// Both VIM and Emacs use the caret notation with columns.
// Consciously leaving tabs broken, they're too difficult to handle.
int width = wcwidth (wch);
if (width < 0)
width = 2;
processed += len;
if (wch == '\n')
{
line_one_based++;
column = 0;
}
else
column += width;
}
free (input);
// Trivially split the command on spaces and substitute our values
struct str argument = str_make ();
for (; *editor; editor++)
{
if (*editor == ' ')
{
if (argument.len)
{
strv_append_owned (&argv, str_steal (&argument));
argument = str_make ();
}
continue;
}
if (*editor != '%' || !editor[1])
{
str_append_c (&argument, *editor);
continue;
}
// None of them are zero-length, thus words don't get lost
switch (*++editor)
{
case 'F':
str_append (&argument, filename);
break;
case 'L':
str_append_printf (&argument, "%zu", line_one_based);
break;
case 'C':
str_append_printf (&argument, "%zu", column + 1);
break;
case 'B':
str_append_printf (&argument, "%d", cursor + 1);
break;
case '%':
case ' ':
str_append_c (&argument, *editor);
break;
default:
print_warning ("unknown substitution variable");
}
}
if (argument.len)
strv_append_owned (&argv, str_steal (&argument));
else
str_free (&argument);
return argv;
}
static bool static bool
on_edit_input (int count, int key, void *user_data) on_edit_input (int count, int key, void *user_data)
{ {
@@ -13202,16 +13307,15 @@ on_edit_input (int count, int key, void *user_data)
if (!(filename = try_dump_input_to_file (ctx))) if (!(filename = try_dump_input_to_file (ctx)))
return false; return false;
const char *command; struct strv argv = build_editor_command (ctx, filename);
if (!(command = getenv ("VISUAL")) if (!argv.len)
&& !(command = getenv ("EDITOR"))) strv_append (&argv, "true");
command = "vi";
hard_assert (!ctx->running_editor); hard_assert (!ctx->running_editor);
switch (spawn_helper_child (ctx)) switch (spawn_helper_child (ctx))
{ {
case 0: case 0:
execlp (command, command, filename, NULL); execvp (argv.vector[0], argv.vector);
print_error ("%s: %s", print_error ("%s: %s",
"Failed to launch editor", strerror (errno)); "Failed to launch editor", strerror (errno));
_exit (EXIT_FAILURE); _exit (EXIT_FAILURE);
@@ -13224,6 +13328,7 @@ on_edit_input (int count, int key, void *user_data)
ctx->running_editor = true; ctx->running_editor = true;
ctx->editor_filename = filename; ctx->editor_filename = filename;
} }
strv_free (&argv);
return true; return true;
} }
@@ -13709,19 +13814,33 @@ on_editline_complete (EditLine *editline, int key)
// Insert the best match instead // Insert the best match instead
el_insertstr (editline, completions[0]); el_insertstr (editline, completions[0]);
// I'm not sure if Readline's menu-complete can at all be implemented
// with Editline--we have no way of detecting what the last executed handler
// was. Employ the formatter's wrapping feature to spew all options.
bool only_match = !completions[1]; bool only_match = !completions[1];
if (!only_match)
{
CALL (ctx->input, hide);
redraw_screen (ctx);
struct formatter f = formatter_make (ctx, NULL);
for (char **p = completions; *++p; )
formatter_add (&f, " #l", *p);
formatter_add (&f, "\n");
formatter_flush (&f, stdout, 0);
formatter_free (&f);
CALL (ctx->input, show);
}
for (char **p = completions; *p; p++) for (char **p = completions; *p; p++)
free (*p); free (*p);
free (completions); free (completions);
// I'm not sure if Readline's menu-complete can at all be implemented
// with Editline. Spamming the terminal with possible completions
// probably isn't what the user wants and we have no way of detecting
// what the last executed handler was.
if (!only_match) if (!only_match)
return CC_REFRESH_BEEP; return CC_REFRESH_BEEP;
// But if there actually is just one match, finish the word // If there actually is just one match, finish the word
el_insertstr (editline, " "); el_insertstr (editline, " ");
return CC_REFRESH; return CC_REFRESH;
} }
@@ -13782,8 +13901,11 @@ app_editline_init (struct input_el *self)
CALL_ (input, bind_control, 'w', "ed-delete-prev-word"); CALL_ (input, bind_control, 'w', "ed-delete-prev-word");
// Just what are you doing? // Just what are you doing?
CALL_ (input, bind_control, 'u', "vi-kill-line-prev"); CALL_ (input, bind_control, 'u', "vi-kill-line-prev");
// See input_el__redisplay(), functionally important
CALL_ (input, bind_control, 'q', "ed-redisplay");
// We need to hide the prompt and input first // We need to hide the prompt and input first
CALL_ (input, bind, "\r", "send-line");
CALL_ (input, bind, "\n", "send-line"); CALL_ (input, bind, "\n", "send-line");
CALL_ (input, bind_control, 'i', "complete"); CALL_ (input, bind_control, 'i', "complete");
@@ -14125,6 +14247,40 @@ done:
#define BRACKETED_PASTE_LIMIT 102400 ///< How much text can be pasted #define BRACKETED_PASTE_LIMIT 102400 ///< How much text can be pasted
static bool
insert_paste (struct app_context *ctx, char *paste, size_t len)
{
if (!get_config_boolean (ctx->config.root, "behaviour.process_pasted_text"))
return CALL_ (ctx->input, insert, paste);
// Without ICRNL, which Editline keeps but Readline doesn't,
// the terminal sends newlines as carriage returns (seen on urxvt)
for (size_t i = 0; i < len; i++)
if (paste[i] == '\r')
paste[i] = '\n';
int position = 0;
char *input = CALL_ (ctx->input, get_line, &position);
bool quote_first_slash = !position || strchr ("\r\n", input[position - 1]);
free (input);
// Executing commands by accident is much more common than pasting them
// intentionally, although the latter may also have security consequences
struct str processed = str_make ();
str_reserve (&processed, len);
for (size_t i = 0; i < len; i++)
{
if (paste[i] == '/'
&& ((!i && quote_first_slash) || (i && paste[i - 1] == '\n')))
str_append_c (&processed, paste[i]);
str_append_c (&processed, paste[i]);
}
bool success = CALL_ (ctx->input, insert, processed.str);
str_free (&processed);
return success;
}
static void static void
process_bracketed_paste (const struct pollfd *fd, struct app_context *ctx) process_bracketed_paste (const struct pollfd *fd, struct app_context *ctx)
{ {
@@ -14149,7 +14305,7 @@ process_bracketed_paste (const struct pollfd *fd, struct app_context *ctx)
(int) (text_len = BRACKETED_PASTE_LIMIT)); (int) (text_len = BRACKETED_PASTE_LIMIT));
buf->str[text_len] = '\0'; buf->str[text_len] = '\0';
if (CALL_ (ctx->input, insert, buf->str)) if (insert_paste (ctx, buf->str, text_len))
goto done; goto done;
error: error:

View File

@@ -1,8 +1,8 @@
xD(1) xD(1)
===== =====
:doctype: manpage :doctype: manpage
:manmanual: uirc3 Manual :manmanual: xK Manual
:mansource: uirc3 {release-version} :mansource: xK {release-version}
Name Name
---- ----
@@ -49,5 +49,5 @@ _/etc/xdg/xD/xD.conf_::
Reporting bugs Reporting bugs
-------------- --------------
Use https://git.janouch.name/p/uirc3 to report bugs, request features, Use https://git.janouch.name/p/xK to report bugs, request features,
or submit pull requests. or submit pull requests.

24
xD.c
View File

@@ -2932,6 +2932,29 @@ irc_handle_links (const struct irc_message *msg, struct client *c)
irc_send_reply (c, IRC_RPL_ENDOFLINKS, mask); irc_send_reply (c, IRC_RPL_ENDOFLINKS, mask);
} }
static void
irc_handle_wallops (const struct irc_message *msg, struct client *c)
{
if (msg->params.len < 1)
RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command);
if (!(c->mode & IRC_USER_MODE_OPERATOR))
RETURN_WITH_REPLY (c, IRC_ERR_NOPRIVILEGES);
const char *message = msg->params.vector[0];
// Our interpretation: anonymize the sender,
// and target all users who want to receive these messages
struct str_map_iter iter = str_map_iter_make (&c->ctx->users);
struct client *target;
while ((target = str_map_iter_next (&iter)))
{
if (target != c && !(target->mode & IRC_USER_MODE_RX_WALLOPS))
continue;
client_send (target, ":%s WALLOPS :%s", c->ctx->server_name, message);
}
}
static void static void
irc_handle_kill (const struct irc_message *msg, struct client *c) irc_handle_kill (const struct irc_message *msg, struct client *c)
{ {
@@ -2994,6 +3017,7 @@ irc_register_handlers (struct server_context *ctx)
{ "ADMIN", true, irc_handle_admin, 0, 0 }, { "ADMIN", true, irc_handle_admin, 0, 0 },
{ "STATS", true, irc_handle_stats, 0, 0 }, { "STATS", true, irc_handle_stats, 0, 0 },
{ "LINKS", true, irc_handle_links, 0, 0 }, { "LINKS", true, irc_handle_links, 0, 0 },
{ "WALLOPS", true, irc_handle_wallops, 0, 0 },
{ "MODE", true, irc_handle_mode, 0, 0 }, { "MODE", true, irc_handle_mode, 0, 0 },
{ "PRIVMSG", true, irc_handle_privmsg, 0, 0 }, { "PRIVMSG", true, irc_handle_privmsg, 0, 0 },