Compare commits
24 Commits
v1.5.0
...
91db8e6e54
| Author | SHA1 | Date | |
|---|---|---|---|
|
91db8e6e54
|
|||
|
dbe95fa298
|
|||
|
9d5e57a501
|
|||
|
4ed6693f57
|
|||
|
bea8d13227
|
|||
|
ecebeace0e
|
|||
|
ca33adeeee
|
|||
|
b31e079256
|
|||
|
57597bf8a2
|
|||
|
c0996fcbe7
|
|||
|
03d8ea4c5a
|
|||
|
dc002a2db4
|
|||
|
a32916ffcf
|
|||
|
f7be510d26
|
|||
|
83764d1e1b
|
|||
|
a717782480
|
|||
|
c50c959f4d
|
|||
|
0dd7536b5a
|
|||
|
0750096827
|
|||
|
49d9980662
|
|||
|
2f7fbcdc5d
|
|||
|
ef0cbe9a59
|
|||
|
2d8808d795
|
|||
|
60d52ad479
|
12
.gitignore
vendored
12
.gitignore
vendored
@@ -3,9 +3,9 @@
|
||||
|
||||
# Qt Creator files
|
||||
/CMakeLists.txt.user*
|
||||
/uirc3.config
|
||||
/uirc3.files
|
||||
/uirc3.creator*
|
||||
/uirc3.includes
|
||||
/uirc3.cflags
|
||||
/uirc3.cxxflags
|
||||
/xK.config
|
||||
/xK.files
|
||||
/xK.creator*
|
||||
/xK.includes
|
||||
/xK.cflags
|
||||
/xK.cxxflags
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Ubuntu 18.04 LTS and OpenBSD 6.4
|
||||
cmake_minimum_required (VERSION 3.10)
|
||||
project (uirc3 VERSION 1.5.0 LANGUAGES C)
|
||||
project (xK VERSION 1.5.0 DESCRIPTION "IRC client, daemon and bot" LANGUAGES C)
|
||||
|
||||
# Options
|
||||
option (WANT_READLINE "Use GNU Readline for the UI (better)" ON)
|
||||
@@ -148,7 +148,7 @@ include_directories (${PROJECT_SOURCE_DIR} ${PROJECT_BINARY_DIR})
|
||||
|
||||
# Generate IRC replies--we need a custom target because of the multiple outputs
|
||||
add_custom_command (OUTPUT xD-replies.c xD.msg
|
||||
COMMAND ${PROJECT_SOURCE_DIR}/xD-gen-replies.sh
|
||||
COMMAND env LC_ALL=C awk -f ${PROJECT_SOURCE_DIR}/xD-gen-replies.awk
|
||||
> xD-replies.c < ${PROJECT_SOURCE_DIR}/xD-replies
|
||||
DEPENDS ${PROJECT_SOURCE_DIR}/xD-replies
|
||||
COMMENT "Generating files from the list of server numerics")
|
||||
@@ -196,7 +196,7 @@ add_custom_target (clang-tidy
|
||||
# Installation
|
||||
install (TARGETS xB xC xD DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||
# XXX: our defaults for XDG_DATA_DIRS expect /usr/local/shore or /usr/share
|
||||
# XXX: our defaults for XDG_DATA_DIRS expect /usr/local/share or /usr/share
|
||||
install (DIRECTORY plugins/xB/
|
||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/xB/plugins USE_SOURCE_PERMISSIONS)
|
||||
install (DIRECTORY plugins/xC/
|
||||
@@ -204,20 +204,31 @@ install (DIRECTORY plugins/xC/
|
||||
|
||||
# Generate documentation from text markup
|
||||
find_program (ASCIIDOCTOR_EXECUTABLE asciidoctor)
|
||||
if (NOT ASCIIDOCTOR_EXECUTABLE)
|
||||
message (FATAL_ERROR "asciidoctor not found")
|
||||
find_program (A2X_EXECUTABLE a2x)
|
||||
if (NOT ASCIIDOCTOR_EXECUTABLE AND NOT A2X_EXECUTABLE)
|
||||
message (FATAL_ERROR "Neither asciidoctor nor a2x were found")
|
||||
endif ()
|
||||
|
||||
foreach (page xB xC xD)
|
||||
set (page_output "${PROJECT_BINARY_DIR}/${page}.1")
|
||||
list (APPEND project_MAN_PAGES "${page_output}")
|
||||
if (ASCIIDOCTOR_EXECUTABLE)
|
||||
add_custom_command (OUTPUT ${page_output}
|
||||
COMMAND ${ASCIIDOCTOR_EXECUTABLE} -b manpage
|
||||
-a release-version=${project_version}
|
||||
"${PROJECT_SOURCE_DIR}/${page}.adoc"
|
||||
-o "${page_output}"
|
||||
"${PROJECT_SOURCE_DIR}/${page}.adoc"
|
||||
DEPENDS ${page}.adoc
|
||||
COMMENT "Generating man page for ${page}" VERBATIM)
|
||||
elseif (A2X_EXECUTABLE)
|
||||
add_custom_command (OUTPUT ${page_output}
|
||||
COMMAND ${A2X_EXECUTABLE} --doctype manpage --format manpage
|
||||
-a release-version=${project_version}
|
||||
-D "${PROJECT_BINARY_DIR}"
|
||||
"${PROJECT_SOURCE_DIR}/${page}.adoc"
|
||||
DEPENDS ${page}.adoc
|
||||
COMMENT "Generating man page for ${page}" VERBATIM)
|
||||
endif ()
|
||||
endforeach ()
|
||||
|
||||
add_custom_target (docs ALL DEPENDS ${project_MAN_PAGES})
|
||||
@@ -229,7 +240,6 @@ foreach (page ${project_MAN_PAGES})
|
||||
endforeach ()
|
||||
|
||||
# CPack
|
||||
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Unreasonable IRC client, daemon and bot")
|
||||
set (CPACK_PACKAGE_VERSION "${project_version_safe}")
|
||||
set (CPACK_PACKAGE_VENDOR "Premysl Eric Janouch")
|
||||
set (CPACK_PACKAGE_CONTACT "Přemysl Eric Janouch <p@janouch.name>")
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2014 - 2021, Přemysl Eric Janouch <p@janouch.name>
|
||||
Copyright (c) 2014 - 2022, Přemysl Eric Janouch <p@janouch.name>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted.
|
||||
|
||||
17
NEWS
17
NEWS
@@ -1,3 +1,20 @@
|
||||
Unreleased
|
||||
|
||||
* xC: all behaviour.* configuration options have been renamed to general.*,
|
||||
with the exception of editor_command/editor, backlog_helper/pager,
|
||||
and backlog_helper_strip_formatting/pager_strip_formatting
|
||||
|
||||
* xC: replaced behaviour.save_on_quit with general.autosave
|
||||
|
||||
* xC: improved pager integration capabilities
|
||||
|
||||
* xC: normalized editline's history behaviour, making it a viable frontend
|
||||
|
||||
* 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,
|
||||
|
||||
97
README.adoc
97
README.adoc
@@ -1,53 +1,42 @@
|
||||
uirc3
|
||||
=====
|
||||
xK
|
||||
==
|
||||
|
||||
The unreasonable IRC trinity. This project consists of an IRC client, daemon,
|
||||
and bot. It's all you're ever going to need for chatting, as long as you can
|
||||
make do with minimalist software.
|
||||
'xK' (chat kit) is an IRC software suite consisting of a terminal client,
|
||||
daemon, and bot. It's all you're ever going to need for chatting,
|
||||
so long as you can make do with slightly minimalist software.
|
||||
|
||||
They have these potentially interesting properties:
|
||||
|
||||
- supporting IRCv3, SOCKS, IPv6, TLS (including client certificates)
|
||||
- lean on dependencies
|
||||
- compact and arguably easy to hack on
|
||||
- maximally permissive license
|
||||
They're all lean on dependencies, and offer a maximally permissive licence.
|
||||
|
||||
xC
|
||||
--
|
||||
The IRC client. It is largely defined by being built on top of GNU Readline
|
||||
that has been hacked to death. Its interface should feel somewhat familiar for
|
||||
weechat or irssi users.
|
||||
The IRC client, and the core of 'xK'. It is largely defined by building on top
|
||||
of GNU Readline or BSD Editline that have been hacked to death. Its interface
|
||||
should feel somewhat familiar for weechat or irssi users.
|
||||
|
||||
image::xC.png[align="center"]
|
||||
|
||||
This is the core of the project. It has most of the stuff you'd expect of
|
||||
an IRC client, such as being multiserver, a powerful configuration system,
|
||||
integrated help, text formatting, automatic splitting of overlong messages,
|
||||
multiline editing, bracketed paste support, decent word wrapping, autocomplete,
|
||||
logging, CTCP queries, auto-away, command aliases, and basic support for Lua
|
||||
scripting. As a unique bonus, you can launch a full text editor from within.
|
||||
It has most features you'd expect of an IRC client, such as being multiserver,
|
||||
a powerful configuration system, integrated help, text formatting, automatic
|
||||
message splitting, multiline editing, bracketed paste support, word wrapping
|
||||
that doesn't break links, autocomplete, logging, CTCP queries, auto-away,
|
||||
command aliases, SOCKS proxying, SASL EXTERNAL authentication using TLS client
|
||||
certificates, or basic support for Lua scripting. As a unique bonus, you can
|
||||
launch a full text editor from within.
|
||||
|
||||
xD
|
||||
--
|
||||
The IRC daemon. It is designed to be used as a regular user application rather
|
||||
than a system-wide daemon. If all you want is a decent, minimal IRCd for
|
||||
testing purposes or a small network of respectful users (or bots), this one will
|
||||
do it just fine.
|
||||
The IRC daemon. It is designed for use as a regular user application rather
|
||||
than a system-wide daemon, and follows the XDG Base Directory Specification.
|
||||
If all you want is a decent, minimal IRCd for testing purposes or a small
|
||||
network of respectful users (or bots), this one will do it just fine.
|
||||
|
||||
Notable features:
|
||||
It autodetects TLS on incoming connections (I'm still wondering why everyone
|
||||
doesn't have this), authenticates operators via TLS client certificate
|
||||
fingerprints, and supports a number of IRCv3 capabilities.
|
||||
|
||||
- TLS autodetection (I'm still wondering why everyone doesn't have this)
|
||||
- IRCop authentication via TLS client certificates
|
||||
- partial IRCv3 support
|
||||
|
||||
Not supported:
|
||||
|
||||
- server linking (which also means no services); I consider existing protocols
|
||||
for this purpose ugly and tricky to implement correctly; I've also found no
|
||||
use for this feature yet
|
||||
- online changes to configuration; the configuration system from 'xC' could
|
||||
be used to implement this feature if needed
|
||||
- limits of almost any kind, just connections and mode `+l`
|
||||
What it notably doesn't support is online changes to configuration, any limits
|
||||
besides the total number of connections and mode `+l`, or server linking
|
||||
(which also means no services).
|
||||
|
||||
This program has been
|
||||
https://git.janouch.name/p/haven/src/branch/master/hid[ported to Go],
|
||||
@@ -71,17 +60,15 @@ a package with the latest development version from Archlinux's AUR.
|
||||
|
||||
Building
|
||||
--------
|
||||
Build dependencies: CMake, pkg-config, asciidoctor, awk, liberty (included) +
|
||||
Build dependencies: CMake, pkg-config, asciidoctor or asciidoc, awk,
|
||||
liberty (included) +
|
||||
Runtime dependencies: openssl +
|
||||
Additionally for 'xC': curses, libffi, lua >= 5.3 (optional),
|
||||
readline >= 6.0 or libedit >= 2013-07-12
|
||||
|
||||
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.
|
||||
|
||||
$ git clone --recursive https://git.janouch.name/p/uirc3.git
|
||||
$ mkdir uirc3/build
|
||||
$ cd uirc3/build
|
||||
$ git clone --recursive https://git.janouch.name/p/xK.git
|
||||
$ mkdir xK/build
|
||||
$ cd xK/build
|
||||
$ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DWANT_READLINE=ON -DWANT_LIBEDIT=OFF -DWANT_LUA=ON
|
||||
$ make
|
||||
@@ -93,7 +80,7 @@ To install the application, you can do either the usual:
|
||||
Or you can try telling CMake to make a package for you:
|
||||
|
||||
$ cpack -G DEB # also supported: RPM, FreeBSD
|
||||
# dpkg -i uirc3-*.deb
|
||||
# dpkg -i xK-*.deb
|
||||
|
||||
Usage
|
||||
-----
|
||||
@@ -151,14 +138,18 @@ Beware that you can easily break the program if you're not careful.
|
||||
|
||||
How do I make xC look like the screenshot?
|
||||
------------------------------------------
|
||||
First of all, you must build it with Lua support. With the defaults, 'xC'
|
||||
doesn't look too fancy because I don't want to depend on Lua or 256-colour
|
||||
terminals. In addition to that, I appear to be one of the few people who use
|
||||
black on white terminals.
|
||||
With the defaults, 'xC' doesn't look too fancy because I don't want to have
|
||||
a hard dependency on either Lua for the bundled script that provides an easily
|
||||
adjustable enhanced prompt, or on 256-colour terminals. Moreover, it's nearly
|
||||
impossible to come up with a colour theme that would work well with both
|
||||
black-on-white and white-on-black terminals, or anything wild in between.
|
||||
|
||||
/set behaviour.date_change_line = "%a %e %b %Y"
|
||||
/set behaviour.plugin_autoload += "fancy-prompt.lua"
|
||||
/set behaviour.backlog_helper = "LESSSECURE=1 less -R +Gb1d -Ps'Backlog ?ltlines %lt-%lb?L/%L. .?e(END):?pB%pB\\%..'"
|
||||
Assuming that your build supports Lua plugins, and that you have a decent,
|
||||
properly set-up terminal emulator, it suffices to run:
|
||||
|
||||
/set general.pager = Press Tab here and change +Gb to +Gb1d
|
||||
/set general.date_change_line = "%a %e %b %Y"
|
||||
/set general.plugin_autoload += "fancy-prompt.lua"
|
||||
/set attributes.userhost = "\x1b[38;5;109m"
|
||||
/set attributes.join = "\x1b[38;5;108m"
|
||||
/set attributes.part = "\x1b[38;5;138m"
|
||||
@@ -179,7 +170,7 @@ configurations accordingly, but I consider it rather messy and unnecessary.
|
||||
|
||||
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
|
||||
the project, feel free to join me at ircs://irc.janouch.name, channel #dev.
|
||||
|
||||
|
||||
2
liberty
2
liberty
Submodule liberty updated: 1b9d89cab3...f545be725d
@@ -1,7 +1,7 @@
|
||||
--
|
||||
-- fancy-prompt.lua: the fancy multiline prompt you probably want
|
||||
--
|
||||
-- Copyright (c) 2016, Přemysl Eric Janouch <p@janouch.name>
|
||||
-- Copyright (c) 2016 - 2022, Přemysl Eric Janouch <p@janouch.name>
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software for any
|
||||
-- purpose with or without fee is hereby granted.
|
||||
@@ -40,7 +40,7 @@ xC.hook_prompt (function (hook)
|
||||
if buffer == current then
|
||||
current_n = i
|
||||
elseif buffer.new_messages_count ~= buffer.new_unimportant_count then
|
||||
if active ~= "" then active = active .. "," end
|
||||
active = active .. ","
|
||||
if buffer.highlighted then
|
||||
active = active .. "!"
|
||||
bg_color = "224"
|
||||
@@ -48,7 +48,6 @@ xC.hook_prompt (function (hook)
|
||||
active = active .. i
|
||||
end
|
||||
end
|
||||
if active ~= "" then active = "(" .. active .. ")" end
|
||||
local x = current_n .. ":" .. current.name
|
||||
if chan and chan.users_len ~= 0 then
|
||||
local params = ""
|
||||
@@ -56,25 +55,34 @@ xC.hook_prompt (function (hook)
|
||||
params = params .. " +" .. mode .. " " .. param
|
||||
end
|
||||
local modes = chan.no_param_modes .. params:sub (3)
|
||||
if modes ~= "" then x = x .. "(+" .. modes .. ")" end
|
||||
if modes ~= "" then
|
||||
x = x .. "(+" .. modes .. ")"
|
||||
end
|
||||
x = x .. "{" .. chan.users_len .. "}"
|
||||
end
|
||||
if current.hide_unimportant then x = x .. "<H>" end
|
||||
|
||||
local lines, cols = xC.get_screen_size ()
|
||||
x = x .. " " .. active .. string.rep (" ", cols)
|
||||
if current.hide_unimportant then
|
||||
x = x .. "<H>"
|
||||
end
|
||||
if active ~= "" then
|
||||
x = x .. " (" .. active:sub (2) .. ")"
|
||||
end
|
||||
|
||||
-- Readline 7.0.003 seems to be broken and completely corrupts the prompt.
|
||||
-- However 8.0.004 seems to be fine with these, as is libedit 20191231-3.1.
|
||||
--x = x:gsub("[\128-\255]", "?")
|
||||
|
||||
-- Cut off extra characters and apply formatting, including the hack.
|
||||
-- FIXME: this doesn't count with full-width or zero-width characters.
|
||||
-- We might want to export wcwidth() above term_from_utf8 somehow.
|
||||
local overflow = utf8.offset (x, cols - 1)
|
||||
if overflow then x = x:sub (1, overflow) end
|
||||
-- Align to the terminal's width and apply formatting, including the hack.
|
||||
local lines, cols = xC.get_screen_size ()
|
||||
local trailing, width = " ", xC.measure (x)
|
||||
while cols > 0 and width >= cols do
|
||||
x = x:sub (1, utf8.offset (x, -1) - 1)
|
||||
trailing, width = ">", xC.measure (x)
|
||||
end
|
||||
|
||||
x = "\x01\x1b[0;4;1;38;5;16m\x1b[48;5;" .. bg_color .. "m\x02" ..
|
||||
x .. "\x01\x1b[0;4;1;7;38;5;" .. bg_color .. "m\x02 \x01\x1b[0;1m\x02"
|
||||
x .. string.rep (" ", cols - width - 1) ..
|
||||
"\x01\x1b[0;4;1;7;38;5;" .. bg_color .. "m\x02" ..
|
||||
trailing .. "\x01\x1b[0;1m\x02"
|
||||
|
||||
local user_prefix = function (chan, user)
|
||||
for i, chan_user in ipairs (chan.users) do
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
-- A list of useless URL parameters that don't affect page function
|
||||
local banned = {
|
||||
gclid = 1,
|
||||
|
||||
-- Alas, Facebook no longer uses this parameter, see:
|
||||
-- https://news.ycombinator.com/item?id=32117489
|
||||
fbclid = 1,
|
||||
|
||||
utm_source = 1,
|
||||
|
||||
12
xB.adoc
12
xB.adoc
@@ -1,8 +1,8 @@
|
||||
xB(1)
|
||||
=====
|
||||
:doctype: manpage
|
||||
:manmanual: uirc3 Manual
|
||||
:mansource: uirc3 {release-version}
|
||||
:manmanual: xK Manual
|
||||
:mansource: xK {release-version}
|
||||
|
||||
Name
|
||||
----
|
||||
@@ -60,14 +60,14 @@ using the IRC protocol. (Caveat: the standard C library doesn't automatically
|
||||
flush FILE streams for pipes on newlines.) A special *XB* command is introduced
|
||||
for RPC, with the following subcommands:
|
||||
|
||||
*XB get_config* _key_::
|
||||
*XB get_config* __key__::
|
||||
Request the value of the given configuration option. If no such option
|
||||
exists, the value will be empty. The response will be delivered in
|
||||
the following format:
|
||||
+
|
||||
```
|
||||
....
|
||||
XB :value
|
||||
```
|
||||
....
|
||||
+
|
||||
This is particularly useful for retrieving the *prefix* string.
|
||||
|
||||
@@ -100,5 +100,5 @@ _/usr/share/xB/plugins/_::
|
||||
|
||||
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.
|
||||
|
||||
19
xC.adoc
19
xC.adoc
@@ -1,8 +1,8 @@
|
||||
xC(1)
|
||||
=====
|
||||
:doctype: manpage
|
||||
:manmanual: uirc3 Manual
|
||||
:mansource: uirc3 {release-version}
|
||||
:manmanual: xK Manual
|
||||
:mansource: xK {release-version}
|
||||
|
||||
Name
|
||||
----
|
||||
@@ -25,9 +25,9 @@ Options
|
||||
other formatting marks to ANSI codes retrieved from the *terminfo*(5)
|
||||
database:
|
||||
+
|
||||
```
|
||||
....
|
||||
printf '\x02bold\x02\n' | xC -f
|
||||
```
|
||||
....
|
||||
+
|
||||
This feature may be used to preview server MOTD files.
|
||||
|
||||
@@ -62,10 +62,10 @@ their respective function names:
|
||||
*M-a*: *goto-activity*::
|
||||
Go to the first following buffer with unseen activity.
|
||||
*PageUp*: *display-backlog*::
|
||||
Show the in-memory backlog for this buffer in the backlog helper,
|
||||
Show the in-memory backlog for this buffer using *general.pager*,
|
||||
which is almost certainly the *less*(1) program.
|
||||
*M-h*: *display-full-log*::
|
||||
Show the log file for this buffer in the backlog helper.
|
||||
Show the log file for this buffer using *general.pager*.
|
||||
*M-H*: *toggle-unimportant*::
|
||||
Hide all join, part and quit messages, as well as all channel mode changes
|
||||
that only relate to user channel modes. Intended to reduce noise in
|
||||
@@ -105,7 +105,7 @@ _~/.config/xC/xC.conf_::
|
||||
as the */set* command, to make changes in it.
|
||||
|
||||
_~/.local/share/xC/logs/_::
|
||||
When enabled by *behaviour.logging*, log files are stored here.
|
||||
When enabled by *general.logging*, log files are stored here.
|
||||
|
||||
_~/.local/share/xC/plugins/_::
|
||||
_/usr/local/share/xC/plugins/_::
|
||||
@@ -114,12 +114,11 @@ _/usr/share/xC/plugins/_::
|
||||
|
||||
Bugs
|
||||
----
|
||||
The editline (libedit) frontend is more of a proof of concept that mostly seems
|
||||
to work but exhibits bugs that are not our fault.
|
||||
The editline (libedit) frontend may exhibit some unexpected behaviour.
|
||||
|
||||
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.
|
||||
|
||||
See also
|
||||
|
||||
376
xC.c
376
xC.c
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* xC.c: a terminal-based IRC client
|
||||
*
|
||||
* Copyright (c) 2015 - 2021, Přemysl Eric Janouch <p@janouch.name>
|
||||
* Copyright (c) 2015 - 2022, Přemysl Eric Janouch <p@janouch.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted.
|
||||
@@ -259,7 +259,7 @@ struct input_vtable
|
||||
XX (get_line) XX (clear_line) XX (insert) \
|
||||
XX (on_tty_resized) XX (on_tty_readable)
|
||||
|
||||
// --- GNU Readline ------------------------------------------------------------
|
||||
// ~~~ GNU Readline ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
#ifdef HAVE_READLINE
|
||||
|
||||
@@ -728,7 +728,7 @@ input_rl_new (void)
|
||||
#define input_new input_rl_new
|
||||
#endif // HAVE_READLINE
|
||||
|
||||
// --- BSD Editline ------------------------------------------------------------
|
||||
// ~~~ BSD Editline ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
#ifdef HAVE_EDITLINE
|
||||
|
||||
@@ -778,12 +778,12 @@ input_el__redisplay (void *input)
|
||||
// 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;
|
||||
char x[] = { 'q' & 31, 0 };
|
||||
el_push (self->editline, x);
|
||||
wchar_t x[] = { L'q' & 31, 0 };
|
||||
el_wpush (self->editline, x);
|
||||
|
||||
// We have to do this or it gets stuck and nothing is done
|
||||
int count = 0;
|
||||
(void) el_wgets (self->editline, &count);
|
||||
int dummy_count = 0;
|
||||
(void) el_wgets (self->editline, &dummy_count);
|
||||
}
|
||||
|
||||
static char *
|
||||
@@ -1026,18 +1026,33 @@ input_el__restore (struct input_el *self)
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
// Editline keeping its own history position (look for "eventno" there).
|
||||
// This is the only sane way of resetting it.
|
||||
static void
|
||||
input_el__start_over (struct input_el *self)
|
||||
{
|
||||
wchar_t x[] = { L'g' & 31, 0 };
|
||||
el_wpush (self->editline, x);
|
||||
|
||||
int dummy_count = 0;
|
||||
(void) el_wgets (self->editline, &dummy_count);
|
||||
}
|
||||
|
||||
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;
|
||||
if (!self->active)
|
||||
return;
|
||||
|
||||
if (self->current)
|
||||
input_el__save_buffer (self, self->current);
|
||||
|
||||
input_el__restore_buffer (self, buffer);
|
||||
el_wset (self->editline, EL_HIST, history, buffer->history);
|
||||
self->current = buffer;
|
||||
el_wset (self->editline, EL_HIST, history, buffer->history);
|
||||
input_el__start_over (self);
|
||||
input_el__restore_buffer (self, buffer);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -1111,7 +1126,7 @@ input_el_on_tty_readable (void *input)
|
||||
if (!buf || count-- <= 0)
|
||||
return;
|
||||
|
||||
if (count == 0 && buf[0] == ('D' - 0x40) /* hardcoded VEOF in editline */)
|
||||
if (count == 0 && buf[0] == ('D' - 0x40) /* hardcoded VEOF in el_wgets() */)
|
||||
{
|
||||
el_deletestr (self->editline, 1);
|
||||
input_el__redisplay (self);
|
||||
@@ -1160,7 +1175,7 @@ input_el_new (void)
|
||||
//
|
||||
// The only exception is IRC identifiers.
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
// ~~~ Scripting support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
// We need a few reference countable objects with support for both strong
|
||||
// and weak references (mainly used for scripted plugins).
|
||||
@@ -1272,7 +1287,7 @@ struct ispect_field
|
||||
{ #field, offsetof (struct object, field), ISPECT_STR_MAP, \
|
||||
ISPECT_REF, g_##ref_type##_ispect, is_list },
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
// ~~~ Chat ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
struct user_channel
|
||||
{
|
||||
@@ -1431,7 +1446,7 @@ channel_destroy (struct channel *self)
|
||||
|
||||
REF_COUNTABLE_METHODS (channel)
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
// ~~~ Buffers ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
enum formatter_item_type
|
||||
{
|
||||
@@ -1444,10 +1459,21 @@ enum formatter_item_type
|
||||
FORMATTER_ITEM_IGNORE_ATTR ///< Un/set attribute ignoration
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
struct formatter_item
|
||||
{
|
||||
enum formatter_item_type type : 16; ///< Type of this item
|
||||
int attribute : 16; ///< Attribute ID
|
||||
int attribute : 16; ///< Attribute ID or a TEXT_* mask
|
||||
int color; ///< Colour
|
||||
char *text; ///< String
|
||||
};
|
||||
@@ -1627,7 +1653,7 @@ buffer_destroy (struct buffer *self)
|
||||
REF_COUNTABLE_METHODS (buffer)
|
||||
#define buffer_ref do_not_use_dangerous
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
// ~~~ Server ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
// The only real purpose of this is to abstract away TLS
|
||||
struct transport
|
||||
@@ -1903,7 +1929,7 @@ server_destroy (struct server *self)
|
||||
REF_COUNTABLE_METHODS (server)
|
||||
#define server_ref do_not_use_dangerous
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
// ~~~ Scripting ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
struct plugin
|
||||
{
|
||||
@@ -2022,7 +2048,7 @@ struct completion_hook
|
||||
struct completion *data, const char *word, struct strv *output);
|
||||
};
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
// ~~~ Main context ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
struct app_context
|
||||
{
|
||||
@@ -2085,7 +2111,7 @@ struct app_context
|
||||
bool in_bracketed_paste; ///< User is pasting some content
|
||||
struct str input_buffer; ///< Buffered pasted content
|
||||
|
||||
bool running_backlog_helper; ///< Running a backlog helper
|
||||
bool running_pager; ///< Running a pager for buffer history
|
||||
bool running_editor; ///< Running editor for the input
|
||||
char *editor_filename; ///< The file being edited by user
|
||||
int terminal_suspended; ///< Terminal suspension level
|
||||
@@ -2420,18 +2446,65 @@ static struct config_schema g_config_server[] =
|
||||
{}
|
||||
};
|
||||
|
||||
static struct config_schema g_config_behaviour[] =
|
||||
static struct config_schema g_config_general[] =
|
||||
{
|
||||
{ .name = "autosave",
|
||||
.comment = "Save configuration automatically after each change",
|
||||
.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 },
|
||||
|
||||
// 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\"" },
|
||||
{ .name = "isolate_buffers",
|
||||
.comment = "Don't leak messages from the server and global buffers",
|
||||
.type = CONFIG_ITEM_BOOLEAN,
|
||||
.default_ = "off",
|
||||
.on_change = on_config_isolate_buffers_change },
|
||||
{ .name = "beep_on_highlight",
|
||||
.comment = "Beep when highlighted or on a new invisible PM",
|
||||
.type = CONFIG_ITEM_BOOLEAN,
|
||||
.default_ = "on",
|
||||
.on_change = on_config_beep_on_highlight_change },
|
||||
{ .name = "read_marker_char",
|
||||
.comment = "The character to use for the read marker line",
|
||||
.type = CONFIG_ITEM_STRING,
|
||||
.default_ = "\"-\"",
|
||||
.validate = config_validate_nonjunk_string },
|
||||
{ .name = "show_all_prefixes",
|
||||
.comment = "Show all prefixes in front of nicknames",
|
||||
.type = CONFIG_ITEM_BOOLEAN,
|
||||
@@ -2442,63 +2515,18 @@ static struct config_schema g_config_behaviour[] =
|
||||
.type = CONFIG_ITEM_BOOLEAN,
|
||||
.default_ = "on",
|
||||
.on_change = on_config_word_wrapping_change },
|
||||
{ .name = "editor_command",
|
||||
.comment = "VIM: \"vim +%Bgo %F\", Emacs: \"emacs -nw +%L:%C %F\"",
|
||||
|
||||
// User input:
|
||||
{ .name = "editor",
|
||||
.comment = "VIM: \"vim +%Bgo %F\", Emacs: \"emacs -nw +%L:%C %F\", "
|
||||
"nano/micro/kakoune: \"nano/micro/kak +%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",
|
||||
.comment = "Input to strftime(3) for the date change line",
|
||||
.type = CONFIG_ITEM_STRING,
|
||||
.default_ = "\"%F\"" },
|
||||
{ .name = "read_marker_char",
|
||||
.comment = "The character to use for the read marker line",
|
||||
.type = CONFIG_ITEM_STRING,
|
||||
.default_ = "\"-\"",
|
||||
.validate = config_validate_nonjunk_string },
|
||||
{ .name = "logging",
|
||||
.comment = "Log buffer contents to file",
|
||||
.type = CONFIG_ITEM_BOOLEAN,
|
||||
.default_ = "off",
|
||||
.on_change = on_config_logging_change },
|
||||
{ .name = "save_on_quit",
|
||||
.comment = "Save configuration before quitting",
|
||||
.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 = "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 = "backlog_helper",
|
||||
.comment = "Shell command to display a buffer's history",
|
||||
.type = CONFIG_ITEM_STRING,
|
||||
.default_ = "\"LESSSECURE=1 less -M -R +Gb\"" },
|
||||
{ .name = "backlog_helper_strip_formatting",
|
||||
.comment = "Strip formatting from backlog helper input",
|
||||
.type = CONFIG_ITEM_BOOLEAN,
|
||||
.default_ = "off" },
|
||||
|
||||
{ .name = "reconnect_delay_growing",
|
||||
.comment = "Growing factor for 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" },
|
||||
|
||||
// Pan-server configuration:
|
||||
{ .name = "autoaway_message",
|
||||
.comment = "Automated away message",
|
||||
.type = CONFIG_ITEM_STRING,
|
||||
@@ -2508,11 +2536,16 @@ static struct config_schema g_config_behaviour[] =
|
||||
.type = CONFIG_ITEM_INTEGER,
|
||||
.validate = config_validate_nonnegative,
|
||||
.default_ = "1800" },
|
||||
|
||||
{ .name = "plugin_autoload",
|
||||
.comment = "Plugins to automatically load on start",
|
||||
.type = CONFIG_ITEM_STRING_ARRAY,
|
||||
.validate = config_validate_nonjunk_string },
|
||||
{ .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" },
|
||||
{}
|
||||
};
|
||||
|
||||
@@ -2528,9 +2561,9 @@ static struct config_schema g_config_attributes[] =
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static void
|
||||
load_config_behaviour (struct config_item *subtree, void *user_data)
|
||||
load_config_general (struct config_item *subtree, void *user_data)
|
||||
{
|
||||
config_schema_apply_to_object (g_config_behaviour, subtree, user_data);
|
||||
config_schema_apply_to_object (g_config_general, subtree, user_data);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -2547,7 +2580,7 @@ register_config_modules (struct app_context *ctx)
|
||||
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, "behaviour", load_config_behaviour, ctx);
|
||||
config_register_module (config, "general", load_config_general, ctx);
|
||||
config_register_module (config, "attributes", load_config_attributes, ctx);
|
||||
}
|
||||
|
||||
@@ -2782,24 +2815,13 @@ init_colors (struct app_context *ctx)
|
||||
(config_item_get (ctx->config.root, "attributes", NULL));
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
// ~~~ Attribute printer ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
// 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
|
||||
// with colour pairs.
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
struct attr_printer
|
||||
{
|
||||
char **attrs; ///< Named attributes
|
||||
@@ -2837,7 +2859,7 @@ attr_printer_tputs (struct attr_printer *self, const char *attr)
|
||||
tputs (attr, 1, printer);
|
||||
else
|
||||
// We shouldn't really do this but we need it to output formatting
|
||||
// to the backlog helper--it should be SGR-only
|
||||
// to the pager--it should be SGR-only
|
||||
attr_printer_filtered_puts (self->stream, attr);
|
||||
}
|
||||
|
||||
@@ -3773,7 +3795,7 @@ explode_text (struct exploder *self, const char *text)
|
||||
if (!strchr ("\a\b\x0e\x0f\x1b" /* BEL BS SO SI ESC */, *p))
|
||||
str_append_c (&filtered, *p);
|
||||
|
||||
size_t term_len = 0;
|
||||
size_t term_len = 0, processed = 0, len;
|
||||
char *term = iconv_xstrdup (self->ctx->term_from_utf8,
|
||||
filtered.str, filtered.len + 1, &term_len);
|
||||
str_free (&filtered);
|
||||
@@ -3782,11 +3804,10 @@ explode_text (struct exploder *self, const char *text)
|
||||
memset (&ps, 0, sizeof ps);
|
||||
|
||||
wchar_t wch;
|
||||
size_t len, processed = 0;
|
||||
while ((len = mbrtowc (&wch, term + processed, term_len - processed, &ps)))
|
||||
{
|
||||
hard_assert (len != (size_t) -2 && len != (size_t) -1);
|
||||
processed += len;
|
||||
hard_assert ((processed += len) <= term_len);
|
||||
|
||||
struct line_char *c = line_char_new (wch);
|
||||
c->attrs = self->attrs;
|
||||
@@ -3921,7 +3942,7 @@ buffer_update_time (struct app_context *ctx, time_t now, FILE *stream,
|
||||
|
||||
char buf[64] = "";
|
||||
const char *format =
|
||||
get_config_string (ctx->config.root, "behaviour.date_change_line");
|
||||
get_config_string (ctx->config.root, "general.date_change_line");
|
||||
if (!strftime (buf, sizeof buf, format, ¤t))
|
||||
{
|
||||
print_error ("%s: %s", "strftime", strerror (errno));
|
||||
@@ -4298,8 +4319,8 @@ buffer_print_read_marker (struct app_context *ctx, FILE *stream, int flush_opts)
|
||||
{
|
||||
struct formatter f = formatter_make (ctx, NULL);
|
||||
const int timestamp_width = 8; // hardcoded to %T right now, simple
|
||||
const char *marker_char = get_config_string (ctx->config.root,
|
||||
"behaviour.read_marker_char");
|
||||
const char *marker_char =
|
||||
get_config_string (ctx->config.root, "general.read_marker_char");
|
||||
|
||||
// We could turn this off on FLUSH_OPT_NOWRAP, however our default pager
|
||||
// wraps lines for us even if we don't do it ourselves, and thus there's
|
||||
@@ -4909,8 +4930,8 @@ static int64_t
|
||||
irc_get_reconnect_delay (struct server *s)
|
||||
{
|
||||
int64_t delay = get_config_integer (s->config, "reconnect_delay");
|
||||
int64_t delay_factor = get_config_integer (s->ctx->config.root,
|
||||
"behaviour.reconnect_delay_growing");
|
||||
int64_t delay_factor = get_config_integer
|
||||
(s->ctx->config.root, "general.reconnect_delay_growing");
|
||||
for (unsigned i = 0; i < s->reconnect_attempt; i++)
|
||||
{
|
||||
if (delay_factor && delay > INT64_MAX / delay_factor)
|
||||
@@ -4918,8 +4939,8 @@ irc_get_reconnect_delay (struct server *s)
|
||||
delay *= delay_factor;
|
||||
}
|
||||
|
||||
int64_t delay_max = get_config_integer (s->ctx->config.root,
|
||||
"behaviour.reconnect_delay_max");
|
||||
int64_t delay_max =
|
||||
get_config_integer (s->ctx->config.root, "general.reconnect_delay_max");
|
||||
return MIN (delay, delay_max);
|
||||
}
|
||||
|
||||
@@ -7488,6 +7509,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[] =
|
||||
@@ -7511,6 +7542,7 @@ static struct irc_handler g_irc_handlers[] =
|
||||
{ "QUIT", irc_handle_quit },
|
||||
{ "TAGMSG", irc_handle_tagmsg },
|
||||
{ "TOPIC", irc_handle_topic },
|
||||
{ "WALLOPS", irc_handle_wallops },
|
||||
};
|
||||
|
||||
static bool
|
||||
@@ -10513,6 +10545,33 @@ lua_plugin_get_screen_size (lua_State *L)
|
||||
return 2;
|
||||
}
|
||||
|
||||
static int
|
||||
lua_plugin_measure (lua_State *L)
|
||||
{
|
||||
struct lua_plugin *plugin = lua_touserdata (L, lua_upvalueindex (1));
|
||||
const char *line = lua_plugin_check_utf8 (L, 1);
|
||||
|
||||
size_t term_len = 0, processed = 0, width = 0, len;
|
||||
char *term = iconv_xstrdup (plugin->ctx->term_from_utf8,
|
||||
(char *) line, strlen (line) + 1, &term_len);
|
||||
|
||||
mbstate_t ps;
|
||||
memset (&ps, 0, sizeof ps);
|
||||
|
||||
wchar_t wch;
|
||||
while ((len = mbrtowc (&wch, term + processed, term_len - processed, &ps)))
|
||||
{
|
||||
hard_assert (len != (size_t) -2 && len != (size_t) -1);
|
||||
hard_assert ((processed += len) <= term_len);
|
||||
|
||||
int wch_width = wcwidth (wch);
|
||||
width += MAX (0, wch_width);
|
||||
}
|
||||
free (term);
|
||||
lua_pushinteger (L, width);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
lua_ctx_gc (lua_State *L)
|
||||
{
|
||||
@@ -10533,6 +10592,7 @@ static luaL_Reg lua_plugin_library[] =
|
||||
// And these are methods:
|
||||
|
||||
{ "get_screen_size", lua_plugin_get_screen_size },
|
||||
{ "measure", lua_plugin_measure },
|
||||
{ "__gc", lua_ctx_gc },
|
||||
{ NULL, NULL },
|
||||
};
|
||||
@@ -10951,8 +11011,8 @@ plugin_unload (struct app_context *ctx, const char *name)
|
||||
static void
|
||||
load_plugins (struct app_context *ctx)
|
||||
{
|
||||
const char *plugins = get_config_string
|
||||
(ctx->config.root, "behaviour.plugin_autoload");
|
||||
const char *plugins =
|
||||
get_config_string (ctx->config.root, "general.plugin_autoload");
|
||||
if (plugins)
|
||||
{
|
||||
struct strv v = strv_make ();
|
||||
@@ -11281,7 +11341,7 @@ handle_command_set_modify
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
static bool
|
||||
handle_command_set_assign_item (struct app_context *ctx,
|
||||
char *key, struct config_item *new_, bool add, bool remove)
|
||||
{
|
||||
@@ -11304,20 +11364,22 @@ handle_command_set_assign_item (struct app_context *ctx,
|
||||
log_global_error (ctx,
|
||||
"Failed to set option \"#s\": #s", key, e->message);
|
||||
error_free (e);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
struct strv tmp = strv_make ();
|
||||
dump_matching_options (ctx->config.root, key, &tmp);
|
||||
log_global_status (ctx, "Option changed: #s", tmp.vector[0]);
|
||||
strv_free (&tmp);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
handle_command_set_assign
|
||||
(struct app_context *ctx, struct strv *all, char *arguments)
|
||||
{
|
||||
hard_assert (all->len > 0);
|
||||
|
||||
char *op = cut_word (&arguments);
|
||||
bool add = false;
|
||||
bool remove = false;
|
||||
@@ -11345,13 +11407,19 @@ handle_command_set_assign
|
||||
config_item_destroy (new_);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
for (size_t i = 0; i < all->len; i++)
|
||||
{
|
||||
char *key = cstr_cut_until (all->vector[i], " ");
|
||||
handle_command_set_assign_item (ctx, key, new_, add, remove);
|
||||
if (handle_command_set_assign_item (ctx, key, new_, add, remove))
|
||||
changed = true;
|
||||
free (key);
|
||||
}
|
||||
config_item_destroy (new_);
|
||||
|
||||
if (changed && get_config_boolean (ctx->config.root, "general.autosave"))
|
||||
save_configuration (ctx);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -13081,7 +13149,7 @@ toggle_bracketed_paste (bool enable)
|
||||
static void
|
||||
suspend_terminal (struct app_context *ctx)
|
||||
{
|
||||
// Terminal can get suspended by both backlog helper and SIGTSTP handling
|
||||
// Terminal can get suspended by both the pager and SIGTSTP handling
|
||||
if (ctx->terminal_suspended++ > 0)
|
||||
return;
|
||||
|
||||
@@ -13192,8 +13260,7 @@ 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");
|
||||
const char *editor = get_config_string (ctx->config.root, "general.editor");
|
||||
if (!editor)
|
||||
{
|
||||
const char *command;
|
||||
@@ -13201,6 +13268,13 @@ build_editor_command (struct app_context *ctx, const char *filename)
|
||||
&& !(command = getenv ("EDITOR")))
|
||||
command = "vi";
|
||||
|
||||
// Although most visual editors support a "+LINE" argument
|
||||
// (every editor mentioned in the default value of general.editor,
|
||||
// plus vi, mcedit, vis, ...), it isn't particularly useful by itself.
|
||||
// We need to be able to specify the column number.
|
||||
//
|
||||
// Seeing as less popular software may try to process this as a filename
|
||||
// and fail, do not bother with this "undocumented standard feature".
|
||||
strv_append (&argv, command);
|
||||
strv_append (&argv, filename);
|
||||
return argv;
|
||||
@@ -13355,24 +13429,27 @@ input_editor_cleanup (struct app_context *ctx)
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static void
|
||||
launch_backlog_helper (struct app_context *ctx, int backlog_fd)
|
||||
launch_pager (struct app_context *ctx,
|
||||
int fd, const char *name, const char *path)
|
||||
{
|
||||
hard_assert (!ctx->running_backlog_helper);
|
||||
hard_assert (!ctx->running_pager);
|
||||
switch (spawn_helper_child (ctx))
|
||||
{
|
||||
case 0:
|
||||
dup2 (backlog_fd, STDIN_FILENO);
|
||||
execl ("/bin/sh", "/bin/sh", "-c", get_config_string
|
||||
(ctx->config.root, "behaviour.backlog_helper"), NULL);
|
||||
print_error ("%s: %s",
|
||||
"Failed to launch backlog helper", strerror (errno));
|
||||
dup2 (fd, STDIN_FILENO);
|
||||
char *localized_name =
|
||||
iconv_xstrdup (ctx->term_from_utf8, (char *) name, -1, NULL);
|
||||
execl ("/bin/sh", "/bin/sh", "-c",
|
||||
get_config_string (ctx->config.root, "general.pager"),
|
||||
PROGRAM_NAME, localized_name, path, NULL);
|
||||
print_error ("%s: %s", "Failed to launch pager", strerror (errno));
|
||||
_exit (EXIT_FAILURE);
|
||||
case -1:
|
||||
log_global_error (ctx, "#s: #l",
|
||||
"Failed to launch backlog helper", strerror (errno));
|
||||
"Failed to launch pager", strerror (errno));
|
||||
break;
|
||||
default:
|
||||
ctx->running_backlog_helper = true;
|
||||
ctx->running_pager = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13388,7 +13465,7 @@ display_backlog (struct app_context *ctx, int flush_opts)
|
||||
}
|
||||
|
||||
if (!get_config_boolean (ctx->config.root,
|
||||
"behaviour.backlog_helper_strip_formatting"))
|
||||
"general.pager_strip_formatting"))
|
||||
flush_opts |= FLUSH_OPT_RAW;
|
||||
|
||||
struct buffer *buffer = ctx->current_buffer;
|
||||
@@ -13408,7 +13485,7 @@ display_backlog (struct app_context *ctx, int flush_opts)
|
||||
|
||||
rewind (backlog);
|
||||
set_cloexec (fileno (backlog));
|
||||
launch_backlog_helper (ctx, fileno (backlog));
|
||||
launch_pager (ctx, fileno (backlog), buffer->name, NULL);
|
||||
fclose (backlog);
|
||||
return true;
|
||||
}
|
||||
@@ -13436,24 +13513,25 @@ on_display_full_log (int count, int key, void *user_data)
|
||||
(void) key;
|
||||
struct app_context *ctx = user_data;
|
||||
|
||||
char *path = buffer_get_log_path (ctx->current_buffer);
|
||||
struct buffer *buffer = ctx->current_buffer;
|
||||
char *path = buffer_get_log_path (buffer);
|
||||
FILE *full_log = fopen (path, "rb");
|
||||
free (path);
|
||||
|
||||
if (!full_log)
|
||||
{
|
||||
log_global_error (ctx, "Failed to open log file for #s: #l",
|
||||
ctx->current_buffer->name, strerror (errno));
|
||||
free (path);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ctx->current_buffer->log_file)
|
||||
if (buffer->log_file)
|
||||
// The regular flush will log any error eventually
|
||||
(void) fflush (ctx->current_buffer->log_file);
|
||||
(void) fflush (buffer->log_file);
|
||||
|
||||
set_cloexec (fileno (full_log));
|
||||
launch_backlog_helper (ctx, fileno (full_log));
|
||||
launch_pager (ctx, fileno (full_log), buffer->name, path);
|
||||
fclose (full_log);
|
||||
free (path);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -13843,18 +13921,13 @@ on_editline_return (EditLine *editline, int key)
|
||||
|
||||
const LineInfoW *info = el_wline (editline);
|
||||
int len = info->lastchar - info->buffer;
|
||||
int point = info->cursor - info->buffer;
|
||||
|
||||
wchar_t *line = calloc (sizeof *info->buffer, len + 1);
|
||||
memcpy (line, info->buffer, sizeof *info->buffer * len);
|
||||
|
||||
// XXX: Editline seems to remember its position in history,
|
||||
// so it's not going to work as you'd expect it to
|
||||
if (*line)
|
||||
{
|
||||
HistEventW ev;
|
||||
history_w (self->current->history, &ev, H_ENTER, line);
|
||||
print_debug ("history: %d %ls", ev.num, ev.str);
|
||||
}
|
||||
free (line);
|
||||
|
||||
@@ -13864,8 +13937,8 @@ on_editline_return (EditLine *editline, int key)
|
||||
xstrndup (info_mb->buffer, info_mb->lastchar - info_mb->buffer));
|
||||
poller_idle_set (&ctx->input_event);
|
||||
|
||||
el_cursor (editline, len - point);
|
||||
el_wdeletestr (editline, len);
|
||||
// We must invoke ch_reset(), which isn't done for us with EL_UNBUFFERED.
|
||||
input_el__start_over (self);
|
||||
return CC_REFRESH;
|
||||
}
|
||||
|
||||
@@ -13890,8 +13963,12 @@ app_editline_init (struct input_el *self)
|
||||
CALL_ (input, bind_control, 'w', "ed-delete-prev-word");
|
||||
// Just what are you doing?
|
||||
CALL_ (input, bind_control, 'u', "vi-kill-line-prev");
|
||||
|
||||
// See input_el__redisplay(), functionally important
|
||||
CALL_ (input, bind_control, 'q', "ed-redisplay");
|
||||
// This is what buffered el_wgets() does, functionally important;
|
||||
// perhaps it could be bound somewhere more appropriate
|
||||
CALL_ (input, bind_control, 'g', "ed-start-over");
|
||||
|
||||
// We need to hide the prompt and input first
|
||||
CALL_ (input, bind, "\r", "send-line");
|
||||
@@ -13914,7 +13991,7 @@ static const char *g_first_time_help[] =
|
||||
"",
|
||||
"To get a list of all commands, type \x02/help\x02. To obtain",
|
||||
"more information on a command or option, simply add it as",
|
||||
"a parameter, e.g. \x02/help set\x02 or \x02/help behaviour.logging\x02.",
|
||||
"a parameter, e.g. \x02/help set\x02 or \x02/help general.logging\x02.",
|
||||
"",
|
||||
"To switch between buffers, press \x02"
|
||||
"F5/Ctrl-P\x02 or \x02" "F6/Ctrl-N\x02.",
|
||||
@@ -14078,8 +14155,8 @@ setup_signal_handlers (void)
|
||||
|
||||
signal (SIGPIPE, SIG_IGN);
|
||||
|
||||
// So that we can write to the terminal while we're running a backlog
|
||||
// helper. This is also inherited by the child so that it doesn't stop
|
||||
// So that we can write to the terminal while we're running a pager.
|
||||
// This is also inherited by the child so that it doesn't stop
|
||||
// when it calls tcsetpgrp().
|
||||
signal (SIGTTOU, SIG_IGN);
|
||||
|
||||
@@ -14121,8 +14198,8 @@ try_reap_child (struct app_context *ctx)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ctx->running_backlog_helper)
|
||||
ctx->running_backlog_helper = false;
|
||||
if (ctx->running_pager)
|
||||
ctx->running_pager = false;
|
||||
else if (!ctx->running_editor)
|
||||
{
|
||||
log_global_debug (ctx, "An unknown child has died");
|
||||
@@ -14239,7 +14316,7 @@ done:
|
||||
static bool
|
||||
insert_paste (struct app_context *ctx, char *paste, size_t len)
|
||||
{
|
||||
if (!get_config_boolean (ctx->config.root, "behaviour.process_pasted_text"))
|
||||
if (!get_config_boolean (ctx->config.root, "general.process_pasted_text"))
|
||||
return CALL_ (ctx->input, insert, paste);
|
||||
|
||||
// Without ICRNL, which Editline keeps but Readline doesn't,
|
||||
@@ -14326,7 +14403,7 @@ reset_autoaway (struct app_context *ctx)
|
||||
|
||||
// And potentially start a new auto-away timer
|
||||
int64_t delay = get_config_integer
|
||||
(ctx->config.root, "behaviour.autoaway_delay");
|
||||
(ctx->config.root, "general.autoaway_delay");
|
||||
if (delay)
|
||||
poller_timer_set (&ctx->autoaway_tmr, delay * 1000);
|
||||
}
|
||||
@@ -14336,7 +14413,7 @@ on_autoaway_timer (struct app_context *ctx)
|
||||
{
|
||||
// An empty message would unset any away status, so let's ignore that
|
||||
const char *message = get_config_string
|
||||
(ctx->config.root, "behaviour.autoaway_message");
|
||||
(ctx->config.root, "general.autoaway_message");
|
||||
if (!message || !*message)
|
||||
return;
|
||||
|
||||
@@ -14693,9 +14770,6 @@ main (int argc, char *argv[])
|
||||
|
||||
CALL (ctx.input, stop);
|
||||
|
||||
if (get_config_boolean (ctx.config.root, "behaviour.save_on_quit"))
|
||||
save_configuration (&ctx);
|
||||
|
||||
app_context_free (&ctx);
|
||||
toggle_bracketed_paste (false);
|
||||
free_terminal ();
|
||||
|
||||
29
xD-gen-replies.awk
Executable file
29
xD-gen-replies.awk
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/awk -f
|
||||
BEGIN {
|
||||
# The message catalog is a by-product
|
||||
msg = "xD.msg"
|
||||
print "$quote \"" > msg;
|
||||
print "$set 1" > msg;
|
||||
}
|
||||
|
||||
/^[0-9]+ *IRC_(ERR|RPL)_[A-Z]+ *".*"$/ {
|
||||
match($0, /".*"/);
|
||||
ids[$1] = $2;
|
||||
texts[$2] = substr($0, RSTART, RLENGTH);
|
||||
print $1 " " texts[$2] > msg
|
||||
}
|
||||
|
||||
END {
|
||||
printf("enum\n{")
|
||||
for (i in ids) {
|
||||
if (seen_first)
|
||||
printf(",")
|
||||
seen_first = 1
|
||||
printf("\n\t%s = %s", ids[i], i)
|
||||
}
|
||||
print "\n};\n"
|
||||
print "static const char *g_default_replies[] =\n{"
|
||||
for (i in ids)
|
||||
print "\t[" ids[i] "] = " texts[ids[i]] ","
|
||||
print "};"
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
#!/bin/sh
|
||||
LC_ALL=C exec awk '
|
||||
BEGIN {
|
||||
# The message catalog is a by-product
|
||||
msg = "xD.msg"
|
||||
print "$quote \"" > msg;
|
||||
print "$set 1" > msg;
|
||||
}
|
||||
/^[0-9]+ *IRC_(ERR|RPL)_[A-Z]+ *".*"$/ {
|
||||
match($0, /".*"/);
|
||||
ids[$1] = $2;
|
||||
texts[$2] = substr($0, RSTART, RLENGTH);
|
||||
print $1 " " texts[$2] > msg
|
||||
}
|
||||
END {
|
||||
printf("enum\n{")
|
||||
for (i in ids) {
|
||||
if (seen_first)
|
||||
printf(",")
|
||||
seen_first = 1
|
||||
printf("\n\t%s = %s", ids[i], i)
|
||||
}
|
||||
print "\n};\n"
|
||||
print "static const char *g_default_replies[] =\n{"
|
||||
for (i in ids)
|
||||
print "\t[" ids[i] "] = " texts[ids[i]] ","
|
||||
print "};"
|
||||
}'
|
||||
6
xD.adoc
6
xD.adoc
@@ -1,8 +1,8 @@
|
||||
xD(1)
|
||||
=====
|
||||
:doctype: manpage
|
||||
:manmanual: uirc3 Manual
|
||||
:mansource: uirc3 {release-version}
|
||||
:manmanual: xK Manual
|
||||
:mansource: xK {release-version}
|
||||
|
||||
Name
|
||||
----
|
||||
@@ -49,5 +49,5 @@ _/etc/xdg/xD/xD.conf_::
|
||||
|
||||
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.
|
||||
|
||||
26
xD.c
26
xD.c
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* xD.c: an IRC daemon
|
||||
*
|
||||
* Copyright (c) 2014 - 2021, Přemysl Eric Janouch <p@janouch.name>
|
||||
* Copyright (c) 2014 - 2022, Přemysl Eric Janouch <p@janouch.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted.
|
||||
@@ -2932,6 +2932,29 @@ irc_handle_links (const struct irc_message *msg, struct client *c)
|
||||
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
|
||||
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 },
|
||||
{ "STATS", true, irc_handle_stats, 0, 0 },
|
||||
{ "LINKS", true, irc_handle_links, 0, 0 },
|
||||
{ "WALLOPS", true, irc_handle_wallops, 0, 0 },
|
||||
|
||||
{ "MODE", true, irc_handle_mode, 0, 0 },
|
||||
{ "PRIVMSG", true, irc_handle_privmsg, 0, 0 },
|
||||
|
||||
Reference in New Issue
Block a user