Compare commits
33 Commits
v1.3.0
...
83764d1e1b
| Author | SHA1 | Date | |
|---|---|---|---|
|
83764d1e1b
|
|||
|
a717782480
|
|||
|
c50c959f4d
|
|||
|
0dd7536b5a
|
|||
|
0750096827
|
|||
|
49d9980662
|
|||
|
2f7fbcdc5d
|
|||
|
ef0cbe9a59
|
|||
|
2d8808d795
|
|||
|
60d52ad479
|
|||
|
b358f53ec3
|
|||
|
2eb315f5c4
|
|||
|
851c2ee548
|
|||
|
f9848ed627
|
|||
|
686a39df38
|
|||
|
9cea3fca91
|
|||
|
5165f76b7c
|
|||
|
92ac13f3c6
|
|||
|
df4ca74580
|
|||
|
9e297244a4
|
|||
|
d32ba133c0
|
|||
|
ce3976e1ec
|
|||
|
e5ed89646b
|
|||
|
5e728f6d31
|
|||
|
766f68e070
|
|||
|
3dc5242d43
|
|||
|
fd9d5db1d2
|
|||
|
cb480b4c71
|
|||
|
59cc423694
|
|||
|
9323089d66
|
|||
|
de7df1f60d
|
|||
|
b082e82b62
|
|||
|
b8dbc70a9c
|
32
.clang-format
Normal file
32
.clang-format
Normal 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
10
.gitignore
vendored
@@ -3,7 +3,9 @@
|
||||
|
||||
# Qt Creator files
|
||||
/CMakeLists.txt.user*
|
||||
/uirc3.config
|
||||
/uirc3.files
|
||||
/uirc3.creator*
|
||||
/uirc3.includes
|
||||
/xK.config
|
||||
/xK.files
|
||||
/xK.creator*
|
||||
/xK.includes
|
||||
/xK.cflags
|
||||
/xK.cxxflags
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
cmake_minimum_required (VERSION 3.0)
|
||||
project (uirc3 VERSION 1.3.0 LANGUAGES C)
|
||||
# Ubuntu 18.04 LTS and OpenBSD 6.4
|
||||
cmake_minimum_required (VERSION 3.10)
|
||||
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)
|
||||
option (WANT_LIBEDIT "Use BSD libedit for the UI" OFF)
|
||||
|
||||
# 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)
|
||||
# -Wunused-function is pretty annoying here, as everything is static
|
||||
set (wdisabled "-Wno-unused-function")
|
||||
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -Wall -Wextra ${wdisabled}")
|
||||
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-unused-function")
|
||||
endif ()
|
||||
|
||||
# Version
|
||||
@@ -57,6 +61,8 @@ if ("${CMAKE_SYSTEM_NAME}" MATCHES "BSD")
|
||||
# Need this for SIGWINCH in FreeBSD and OpenBSD respectively;
|
||||
# our POSIX version macros make it undefined
|
||||
add_definitions (-D__BSD_VISIBLE=1 -D_BSD_SOURCE=1)
|
||||
elseif (APPLE)
|
||||
add_definitions (-D_DARWIN_C_SOURCE)
|
||||
endif ()
|
||||
|
||||
# -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))
|
||||
message (SEND_ERROR "You have to choose either GNU Readline or libedit")
|
||||
elseif (WANT_READLINE)
|
||||
pkg_check_modules (readline readline)
|
||||
|
||||
# OpenBSD's default readline is too old
|
||||
if ("${CMAKE_SYSTEM_NAME}" MATCHES "OpenBSD")
|
||||
include_directories (${OPENBSD_LOCALBASE}/include/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 ()
|
||||
list (APPEND xC_libraries readline)
|
||||
endif ()
|
||||
@@ -184,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/
|
||||
@@ -192,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}")
|
||||
add_custom_command (OUTPUT ${page_output}
|
||||
COMMAND ${ASCIIDOCTOR_EXECUTABLE} -b manpage
|
||||
-a release-version=${project_version}
|
||||
"${PROJECT_SOURCE_DIR}/${page}.adoc"
|
||||
-o "${page_output}"
|
||||
DEPENDS ${page}.adoc
|
||||
COMMENT "Generating man page for ${page}" VERBATIM)
|
||||
if (ASCIIDOCTOR_EXECUTABLE)
|
||||
add_custom_command (OUTPUT ${page_output}
|
||||
COMMAND ${ASCIIDOCTOR_EXECUTABLE} -b manpage
|
||||
-a release-version=${project_version}
|
||||
-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})
|
||||
@@ -217,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.
|
||||
|
||||
36
NEWS
36
NEWS
@@ -1,3 +1,39 @@
|
||||
Unreleased
|
||||
|
||||
* xC: improved backlog helper integration capabilities
|
||||
|
||||
* 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"
|
||||
|
||||
* xC: made message autosplitting respect text formatting
|
||||
|
||||
* xC: fixed displaying IRC colours above 16
|
||||
|
||||
* xC: offer IRCnet as an IRC network to connect to,
|
||||
rather than the lunatic new Freenode
|
||||
|
||||
* xD: started bumping the soft limit on file descriptors to the hard one
|
||||
|
||||
|
||||
1.3.0 (2021-08-07) "New World Order"
|
||||
|
||||
* xC: made nick autocompletion offer recent speakers first
|
||||
|
||||
90
README.adoc
90
README.adoc
@@ -1,32 +1,31 @@
|
||||
uirc3
|
||||
=====
|
||||
:compact-option:
|
||||
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.
|
||||
|
||||
All of them have these potentially interesting properties:
|
||||
They come with these potentially interesting properties:
|
||||
|
||||
- IPv6 support
|
||||
- TLS support, including client certificates
|
||||
- lean on dependencies (with the exception of 'xC')
|
||||
- supporting IRCv3, SOCKS, IPv6, TLS (including client certificates)
|
||||
- lean on dependencies
|
||||
- compact and arguably easy to hack on
|
||||
- very permissive license
|
||||
- maximally permissive license
|
||||
|
||||
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 being built
|
||||
on top of GNU Readline that has been hacked to death. Its interface should feel
|
||||
somewhat familiar for weechat or irssi users.
|
||||
|
||||
image::xC.png[align="center"]
|
||||
|
||||
This is the largest application within the project. It has most of the stuff
|
||||
you'd expect of an IRC client, such as being able to set up multiple servers,
|
||||
a powerful configuration system, integrated help, text formatting, CTCP queries,
|
||||
automatic splitting of overlong messages, autocomplete, logging to file,
|
||||
auto-away, command aliases and basic support for Lua scripting.
|
||||
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.
|
||||
|
||||
xD
|
||||
--
|
||||
@@ -37,10 +36,8 @@ do it just fine.
|
||||
|
||||
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
|
||||
- epoll/kqueue support; this means that it should be able to handle quite
|
||||
a number of concurrent user connections
|
||||
- partial IRCv3 support
|
||||
|
||||
Not supported:
|
||||
@@ -58,16 +55,14 @@ and development continues over there.
|
||||
|
||||
xB
|
||||
--
|
||||
The IRC bot. It builds upon the concept of my other VitaminA IRC bot. The main
|
||||
characteristic of these two bots is that they run plugins as coprocesses, which
|
||||
allows for enhanced reliability and programming language freedom.
|
||||
The IRC bot. While originally intended to be a simple rewrite of my old GNU AWK
|
||||
bot in C, it fairly quickly became a playground, and it eventually got me into
|
||||
writing the rest of this package.
|
||||
|
||||
While originally intended to be a simple rewrite of the original AWK bot in C,
|
||||
it fairly quickly became a playground, and it eventually got me into writing
|
||||
the rest of the package.
|
||||
|
||||
It survives crashes, server disconnects and timeouts, and also has native SOCKS
|
||||
support (even though socksify can add that easily to any program).
|
||||
Its main characteristic is that it runs plugins as coprocesses, allowing for
|
||||
enhanced reliability and programming language freedom. Moreover, it recovers
|
||||
from any crashes, and offers native SOCKS support (even though socksify can add
|
||||
that easily to any program).
|
||||
|
||||
Packages
|
||||
--------
|
||||
@@ -76,7 +71,8 @@ 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
|
||||
@@ -84,10 +80,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
|
||||
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
|
||||
$ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug \
|
||||
$ 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
|
||||
|
||||
@@ -95,10 +91,10 @@ To install the application, you can do either the usual:
|
||||
|
||||
# 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
|
||||
# dpkg -i uirc3-*.deb
|
||||
$ cpack -G DEB # also supported: RPM, FreeBSD
|
||||
# dpkg -i xK-*.deb
|
||||
|
||||
Usage
|
||||
-----
|
||||
@@ -156,14 +152,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.
|
||||
|
||||
Assuming that your build supports Lua plugins, and that you have a decent,
|
||||
properly set-up terminal emulator, it suffices to run:
|
||||
|
||||
/set behaviour.backlog_helper = Press Tab here and change +Gb to +Gb1d
|
||||
/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\\%..'"
|
||||
/set attributes.userhost = "\x1b[38;5;109m"
|
||||
/set attributes.join = "\x1b[38;5;108m"
|
||||
/set attributes.part = "\x1b[38;5;138m"
|
||||
@@ -184,7 +184,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.
|
||||
|
||||
|
||||
24
common.c
24
common.c
@@ -22,11 +22,11 @@
|
||||
#define LIBERTY_WANT_PROTO_IRC
|
||||
|
||||
#ifdef WANT_SYSLOG_LOGGING
|
||||
#define print_fatal_data ((void *) LOG_ERR)
|
||||
#define print_error_data ((void *) LOG_ERR)
|
||||
#define print_warning_data ((void *) LOG_WARNING)
|
||||
#define print_status_data ((void *) LOG_INFO)
|
||||
#define print_debug_data ((void *) LOG_DEBUG)
|
||||
#define print_fatal_data ((void *) LOG_ERR)
|
||||
#define print_error_data ((void *) LOG_ERR)
|
||||
#define print_warning_data ((void *) LOG_WARNING)
|
||||
#define print_status_data ((void *) LOG_INFO)
|
||||
#define print_debug_data ((void *) LOG_DEBUG)
|
||||
#endif // WANT_SYSLOG_LOGGING
|
||||
|
||||
#include "liberty/liberty.c"
|
||||
@@ -87,15 +87,15 @@ static time_t
|
||||
unixtime_msec (long *msec)
|
||||
{
|
||||
#ifdef _POSIX_TIMERS
|
||||
struct timespec tp;
|
||||
hard_assert (clock_gettime (CLOCK_REALTIME, &tp) != -1);
|
||||
*msec = tp.tv_nsec / 1000000;
|
||||
struct timespec tp;
|
||||
hard_assert (clock_gettime (CLOCK_REALTIME, &tp) != -1);
|
||||
*msec = tp.tv_nsec / 1000000;
|
||||
#else // ! _POSIX_TIMERS
|
||||
struct timeval tp;
|
||||
hard_assert (gettimeofday (&tp, NULL) != -1);
|
||||
*msec = tp.tv_usec / 1000;
|
||||
struct timeval tp;
|
||||
hard_assert (gettimeofday (&tp, NULL) != -1);
|
||||
*msec = tp.tv_usec / 1000;
|
||||
#endif // ! _POSIX_TIMERS
|
||||
return tp.tv_sec;
|
||||
return tp.tv_sec;
|
||||
}
|
||||
|
||||
// --- Logging -----------------------------------------------------------------
|
||||
|
||||
2
liberty
2
liberty
Submodule liberty updated: 9639777814...f545be725d
@@ -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
|
||||
-- purpose with or without fee is hereby granted.
|
||||
@@ -19,6 +19,10 @@
|
||||
local banned = {
|
||||
gclid = 1,
|
||||
|
||||
-- Alas, Facebook no longer uses this parameter, see:
|
||||
-- https://news.ycombinator.com/item?id=32117489
|
||||
fbclid = 1,
|
||||
|
||||
utm_source = 1,
|
||||
utm_medium = 1,
|
||||
utm_term = 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.
|
||||
|
||||
10
xC.adoc
10
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.
|
||||
|
||||
@@ -119,7 +119,7 @@ to work but exhibits bugs that are not our fault.
|
||||
|
||||
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
|
||||
|
||||
512
xC.c
512
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.
|
||||
@@ -238,8 +238,8 @@ struct input_vtable
|
||||
/// Bind Alt+key to the given named function
|
||||
void (*bind_meta) (void *input, char key, const char *fn);
|
||||
|
||||
/// Get the current line input
|
||||
char *(*get_line) (void *input);
|
||||
/// Get the current line input and position within
|
||||
char *(*get_line) (void *input, int *position);
|
||||
/// Clear the current line input
|
||||
void (*clear_line) (void *input);
|
||||
/// Insert text at current position
|
||||
@@ -361,9 +361,10 @@ input_rl_insert (void *input, const char *s)
|
||||
}
|
||||
|
||||
static char *
|
||||
input_rl_get_line (void *input)
|
||||
input_rl_get_line (void *input, int *position)
|
||||
{
|
||||
(void) input;
|
||||
if (position) *position = rl_point;
|
||||
return rl_copy_text (0, rl_end);
|
||||
}
|
||||
|
||||
@@ -771,24 +772,13 @@ struct input_el
|
||||
|
||||
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
|
||||
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;
|
||||
char x[] = { input_el__get_termios (VREPRINT, 'R' - 0x40), 0 };
|
||||
char x[] = { 'q' & 31, 0 };
|
||||
el_push (self->editline, x);
|
||||
|
||||
// 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 *
|
||||
input_el_get_line (void *input)
|
||||
input_el_get_line (void *input, int *position)
|
||||
{
|
||||
struct input_el *self = input;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1448,7 +1440,7 @@ enum formatter_item_type
|
||||
FORMATTER_ITEM_ATTR, ///< Formatting attributes
|
||||
FORMATTER_ITEM_FG_COLOR, ///< Foreground colour
|
||||
FORMATTER_ITEM_BG_COLOR, ///< Background colour
|
||||
FORMATTER_ITEM_SIMPLE, ///< Toggle mIRC formatting
|
||||
FORMATTER_ITEM_SIMPLE, ///< Toggle IRC formatting
|
||||
FORMATTER_ITEM_IGNORE_ATTR ///< Un/set attribute ignoration
|
||||
};
|
||||
|
||||
@@ -2089,7 +2081,7 @@ struct app_context
|
||||
int *nick_palette; ///< A 256-colour palette for nicknames
|
||||
size_t nick_palette_len; ///< Number of entries in nick_palette
|
||||
|
||||
bool awaiting_mirc_escape; ///< Awaiting a mIRC attribute escape
|
||||
bool awaiting_formatting_escape; ///< Awaiting an IRC formatting escape
|
||||
bool in_bracketed_paste; ///< User is pasting some content
|
||||
struct str input_buffer; ///< Buffered pasted content
|
||||
|
||||
@@ -2450,6 +2442,14 @@ 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\", "
|
||||
"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,
|
||||
@@ -2481,9 +2481,11 @@ static struct config_schema g_config_behaviour[] =
|
||||
.default_ = "1000",
|
||||
.on_change = on_config_backlog_limit_change },
|
||||
{ .name = "backlog_helper",
|
||||
.comment = "Shell command to display a buffer's history",
|
||||
.comment = "Shell command to page buffer history (args: name [path])",
|
||||
.type = CONFIG_ITEM_STRING,
|
||||
.default_ = "\"LESSSECURE=1 less -M -R +Gb\"" },
|
||||
.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 = "backlog_helper_strip_formatting",
|
||||
.comment = "Strip formatting from backlog helper input",
|
||||
.type = CONFIG_ITEM_BOOLEAN,
|
||||
@@ -2797,7 +2799,8 @@ enum
|
||||
TEXT_UNDERLINE = 1 << 2,
|
||||
TEXT_INVERSE = 1 << 3,
|
||||
TEXT_BLINK = 1 << 4,
|
||||
TEXT_CROSSED_OUT = 1 << 5
|
||||
TEXT_CROSSED_OUT = 1 << 5,
|
||||
TEXT_MONOSPACE = 1 << 6
|
||||
};
|
||||
|
||||
struct attr_printer
|
||||
@@ -2960,6 +2963,7 @@ attr_printer_apply (struct attr_printer *self,
|
||||
|
||||
attr_printer_reset (self);
|
||||
|
||||
// TEXT_MONOSPACE is unimplemented, for obvious reasons
|
||||
if (text_attrs)
|
||||
attr_printer_tputs (self, tparm (set_attributes,
|
||||
0, // standout
|
||||
@@ -3121,7 +3125,7 @@ irc_to_utf8 (const char *text)
|
||||
// #l inserts a locale-encoded string
|
||||
//
|
||||
// #S inserts a string from the server with unknown encoding
|
||||
// #m inserts a mIRC-formatted string (auto-resets at boundaries)
|
||||
// #m inserts an IRC-formatted string (auto-resets at boundaries)
|
||||
// #n cuts the nickname from a string and automatically colours it
|
||||
// #N is like #n but also appends userhost, if present
|
||||
//
|
||||
@@ -3152,8 +3156,6 @@ formatter_add_item (struct formatter *self, struct formatter_item template_)
|
||||
FORMATTER_ADD_ITEM ((self), ATTR, .attribute = ATTR_RESET)
|
||||
#define FORMATTER_ADD_TEXT(self, text_) \
|
||||
FORMATTER_ADD_ITEM ((self), TEXT, .text = (text_))
|
||||
#define FORMATTER_ADD_SIMPLE(self, attribute_) \
|
||||
FORMATTER_ADD_ITEM ((self), SIMPLE, .attribute = TEXT_ ## attribute_)
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
@@ -3190,7 +3192,7 @@ static const int g_mirc_to_terminal[] =
|
||||
|
||||
// https://modern.ircdocs.horse/formatting.html
|
||||
// http://anti.teamidiot.de/static/nei/*/extended_mirc_color_proposal.html
|
||||
static const char g_extra_to_256[100 - 16] =
|
||||
static const int16_t g_extra_to_256[100 - 16] =
|
||||
{
|
||||
52, 94, 100, 58, 22, 29, 23, 24, 17, 54, 53, 89,
|
||||
88, 130, 142, 64, 28, 35, 30, 25, 18, 91, 90, 125,
|
||||
@@ -3202,42 +3204,127 @@ static const char g_extra_to_256[100 - 16] =
|
||||
};
|
||||
|
||||
static const char *
|
||||
formatter_parse_mirc_color (struct formatter *self, const char *s)
|
||||
irc_parse_mirc_color (const char *s, uint8_t *fg, uint8_t *bg)
|
||||
{
|
||||
if (!isdigit_ascii (*s))
|
||||
{
|
||||
FORMATTER_ADD_ITEM (self, FG_COLOR, .color = -1);
|
||||
FORMATTER_ADD_ITEM (self, BG_COLOR, .color = -1);
|
||||
*fg = *bg = 99;
|
||||
return s;
|
||||
}
|
||||
|
||||
int fg = *s++ - '0';
|
||||
*fg = *s++ - '0';
|
||||
if (isdigit_ascii (*s))
|
||||
fg = fg * 10 + (*s++ - '0');
|
||||
if (fg < 16)
|
||||
FORMATTER_ADD_ITEM (self, FG_COLOR, .color = g_mirc_to_terminal[fg]);
|
||||
else
|
||||
FORMATTER_ADD_ITEM (self, FG_COLOR,
|
||||
.color = COLOR_256 (DEFAULT, g_extra_to_256[fg]));
|
||||
*fg = *fg * 10 + (*s++ - '0');
|
||||
|
||||
if (*s != ',' || !isdigit_ascii (s[1]))
|
||||
return s;
|
||||
s++;
|
||||
|
||||
int bg = *s++ - '0';
|
||||
*bg = *s++ - '0';
|
||||
if (isdigit_ascii (*s))
|
||||
bg = bg * 10 + (*s++ - '0');
|
||||
*bg = *bg * 10 + (*s++ - '0');
|
||||
return s;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
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]));
|
||||
|
||||
if (bg < 16)
|
||||
FORMATTER_ADD_ITEM (self, BG_COLOR, .color = g_mirc_to_terminal[bg]);
|
||||
else
|
||||
else if (bg < 100)
|
||||
FORMATTER_ADD_ITEM (self, BG_COLOR,
|
||||
.color = COLOR_256 (DEFAULT, g_extra_to_256[bg]));
|
||||
.color = COLOR_256 (DEFAULT, g_extra_to_256[bg - 16]));
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
static void
|
||||
formatter_parse_mirc (struct formatter *self, const char *s)
|
||||
formatter_parse_message (struct formatter *self, const char *s)
|
||||
{
|
||||
FORMATTER_ADD_RESET (self);
|
||||
|
||||
@@ -3251,24 +3338,15 @@ formatter_parse_mirc (struct formatter *self, const char *s)
|
||||
str_reset (&buf);
|
||||
}
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case '\x02': FORMATTER_ADD_SIMPLE (self, BOLD); break;
|
||||
case '\x11': /* monospace, N/A */ break;
|
||||
case '\x1d': FORMATTER_ADD_SIMPLE (self, ITALIC); break;
|
||||
case '\x1e': FORMATTER_ADD_SIMPLE (self, CROSSED_OUT); break;
|
||||
case '\x1f': FORMATTER_ADD_SIMPLE (self, UNDERLINE); break;
|
||||
case '\x16': FORMATTER_ADD_SIMPLE (self, INVERSE); break;
|
||||
|
||||
case '\x03':
|
||||
int attribute = irc_parse_attribute (c);
|
||||
if (c == '\x03')
|
||||
s = formatter_parse_mirc_color (self, s);
|
||||
break;
|
||||
case '\x0f':
|
||||
else if (attribute > 0)
|
||||
FORMATTER_ADD_ITEM (self, SIMPLE, .attribute = attribute);
|
||||
else if (attribute < 0)
|
||||
FORMATTER_ADD_RESET (self);
|
||||
break;
|
||||
default:
|
||||
else
|
||||
str_append_c (&buf, c);
|
||||
}
|
||||
}
|
||||
|
||||
if (buf.len)
|
||||
@@ -3378,7 +3456,7 @@ restart:
|
||||
break;
|
||||
case 'm':
|
||||
tmp = irc_to_utf8 ((s = va_arg (*ap, char *)));
|
||||
formatter_parse_mirc (self, tmp);
|
||||
formatter_parse_message (self, tmp);
|
||||
free (tmp);
|
||||
break;
|
||||
case 'n':
|
||||
@@ -4086,6 +4164,9 @@ log_full (struct app_context *ctx, struct server *s, struct buffer *buffer,
|
||||
log_server_status ((s), (s)->buffer, "Notice -> #n: #m", (target), (text))
|
||||
#define log_outcoming_orphan_privmsg(s, target, text) \
|
||||
log_server_status ((s), (s)->buffer, "MSG(#n): #m", (target), (text))
|
||||
#define log_outcoming_orphan_action(s, target, text) \
|
||||
log_server_status ((s), (s)->buffer, "MSG(#n): #a*#r #m", (target), \
|
||||
ATTR_ACTION, (text))
|
||||
|
||||
#define log_ctcp_query(s, target, tag) \
|
||||
log_server_status ((s), (s)->buffer, "CTCP query to #S: #S", target, tag)
|
||||
@@ -6210,7 +6291,7 @@ irc_is_highlight (struct server *s, const char *message)
|
||||
// Strip formatting from the message so that it doesn't interfere
|
||||
// with nickname detection (colour sequences in particular)
|
||||
struct formatter f = formatter_make (s->ctx, NULL);
|
||||
formatter_parse_mirc (&f, message);
|
||||
formatter_parse_message (&f, message);
|
||||
|
||||
struct str stripped = str_make ();
|
||||
for (size_t i = 0; i < f.items_len; i++)
|
||||
@@ -6511,8 +6592,9 @@ irc_handle_sent_privmsg_text (struct server *s,
|
||||
prefixes, s->irc_user->nickname, text->str);
|
||||
free (prefixes);
|
||||
}
|
||||
else if (is_action)
|
||||
log_outcoming_orphan_action (s, target, text->str);
|
||||
else
|
||||
// TODO: also handle actions here
|
||||
log_outcoming_orphan_privmsg (s, target, text->str);
|
||||
}
|
||||
|
||||
@@ -6825,7 +6907,7 @@ irc_handle_join (struct server *s, const struct irc_message *msg)
|
||||
|
||||
buffer_add (s->ctx, buffer);
|
||||
|
||||
char *input = CALL (s->ctx->input, get_line);
|
||||
char *input = CALL_ (s->ctx->input, get_line, NULL);
|
||||
if (!*input)
|
||||
buffer_activate (s->ctx, buffer);
|
||||
else
|
||||
@@ -7409,6 +7491,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[] =
|
||||
@@ -7432,6 +7524,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
|
||||
@@ -8227,12 +8320,12 @@ irc_process_message (const struct irc_message *msg, struct server *s)
|
||||
|
||||
// --- Message autosplitting magic ---------------------------------------------
|
||||
|
||||
// This is the most basic acceptable algorithm; something like ICU with proper
|
||||
// This is a rather basic algorithm; something like ICU with proper
|
||||
// locale specification would be needed to make it work better.
|
||||
|
||||
static size_t
|
||||
wrap_text_for_single_line (const char *text, size_t text_len,
|
||||
size_t line_len, struct str *output)
|
||||
wrap_text_for_single_line (const char *text, struct irc_char_attrs *attrs,
|
||||
size_t text_len, size_t target_len, struct str *output)
|
||||
{
|
||||
size_t eaten = 0;
|
||||
|
||||
@@ -8240,7 +8333,7 @@ wrap_text_for_single_line (const char *text, size_t text_len,
|
||||
const char *word_start;
|
||||
const char *word_end = text + strcspn (text, " ");
|
||||
size_t word_len = word_end - text;
|
||||
while (line_len && word_len <= line_len)
|
||||
while (target_len && word_len <= target_len)
|
||||
{
|
||||
if (word_len)
|
||||
{
|
||||
@@ -8248,7 +8341,7 @@ wrap_text_for_single_line (const char *text, size_t text_len,
|
||||
|
||||
text += word_len;
|
||||
eaten += word_len;
|
||||
line_len -= word_len;
|
||||
target_len -= word_len;
|
||||
}
|
||||
|
||||
// Find the next word's end
|
||||
@@ -8262,53 +8355,60 @@ wrap_text_for_single_line (const char *text, size_t text_len,
|
||||
return eaten + (word_start - text);
|
||||
|
||||
// And if that doesn't help, cut the longest valid block of characters
|
||||
for (const char *p = text; (size_t) (p - text) <= line_len; )
|
||||
{
|
||||
eaten = p - text;
|
||||
hard_assert (utf8_decode (&p, text_len - eaten) >= 0);
|
||||
}
|
||||
for (size_t i = 1; i <= text_len && i <= target_len; i++)
|
||||
if (i == text_len || attrs[i].starts_at_boundary)
|
||||
eaten = i;
|
||||
|
||||
str_append_data (output, text, eaten);
|
||||
return eaten;
|
||||
}
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
// In practice, this should never fail at all, although it's not guaranteed
|
||||
static bool
|
||||
wrap_message (const char *message,
|
||||
int line_max, struct strv *output, struct error **e)
|
||||
{
|
||||
size_t message_left = strlen (message), i = 0;
|
||||
struct irc_char_attrs *attrs = irc_analyze_text (message, message_left);
|
||||
struct str m = str_make ();
|
||||
if (line_max <= 0)
|
||||
goto error;
|
||||
|
||||
int message_left = strlen (message);
|
||||
while (message_left > line_max)
|
||||
while (m.len + message_left > (size_t) line_max)
|
||||
{
|
||||
struct str m = str_make ();
|
||||
|
||||
size_t eaten = wrap_text_for_single_line
|
||||
(message, message_left, line_max, &m);
|
||||
(message + i, attrs + i, message_left, line_max - m.len, &m);
|
||||
if (!eaten)
|
||||
{
|
||||
str_free (&m);
|
||||
goto error;
|
||||
}
|
||||
|
||||
strv_append_owned (output, str_steal (&m));
|
||||
message += eaten;
|
||||
message_left -= eaten;
|
||||
m = str_make ();
|
||||
|
||||
i += eaten;
|
||||
if (!(message_left -= eaten))
|
||||
break;
|
||||
|
||||
irc_serialize_char_attrs (attrs + i, &m);
|
||||
if (m.len >= (size_t) line_max)
|
||||
{
|
||||
print_debug ("formatting continuation too long");
|
||||
str_reset (&m);
|
||||
}
|
||||
}
|
||||
|
||||
if (message_left)
|
||||
strv_append (output, message);
|
||||
strv_append_owned (output,
|
||||
xstrdup_printf ("%s%s", m.str, message + i));
|
||||
|
||||
free (attrs);
|
||||
str_free (&m);
|
||||
return true;
|
||||
|
||||
error:
|
||||
// Well, that's just weird
|
||||
error_set (e,
|
||||
free (attrs);
|
||||
str_free (&m);
|
||||
return error_set (e,
|
||||
"Message splitting was unsuccessful as there was "
|
||||
"too little room for UTF-8 characters");
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Automatically splits messages that arrive at other clients with our prefix
|
||||
@@ -12542,8 +12642,6 @@ process_input (struct app_context *ctx, char *user_input)
|
||||
else
|
||||
{
|
||||
struct strv lines = strv_make ();
|
||||
|
||||
// XXX: this interprets commands in pasted text
|
||||
cstr_split (input, "\r\n", false, &lines);
|
||||
for (size_t i = 0; i < lines.len; i++)
|
||||
(void) process_input_utf8 (ctx,
|
||||
@@ -13076,7 +13174,7 @@ dump_input_to_file (struct app_context *ctx, char *template, struct error **e)
|
||||
if (fd < 0)
|
||||
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);
|
||||
free (input);
|
||||
|
||||
@@ -13104,6 +13202,110 @@ try_dump_input_to_file (struct app_context *ctx)
|
||||
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";
|
||||
|
||||
// Although most visual editors support a "+LINE" argument (every
|
||||
// editor mentioned in the default value of behaviour.editor_command,
|
||||
// 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;
|
||||
}
|
||||
|
||||
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
|
||||
on_edit_input (int count, int key, void *user_data)
|
||||
{
|
||||
@@ -13115,16 +13317,15 @@ on_edit_input (int count, int key, void *user_data)
|
||||
if (!(filename = try_dump_input_to_file (ctx)))
|
||||
return false;
|
||||
|
||||
const char *command;
|
||||
if (!(command = getenv ("VISUAL"))
|
||||
&& !(command = getenv ("EDITOR")))
|
||||
command = "vi";
|
||||
struct strv argv = build_editor_command (ctx, filename);
|
||||
if (!argv.len)
|
||||
strv_append (&argv, "true");
|
||||
|
||||
hard_assert (!ctx->running_editor);
|
||||
switch (spawn_helper_child (ctx))
|
||||
{
|
||||
case 0:
|
||||
execlp (command, command, filename, NULL);
|
||||
execvp (argv.vector[0], argv.vector);
|
||||
print_error ("%s: %s",
|
||||
"Failed to launch editor", strerror (errno));
|
||||
_exit (EXIT_FAILURE);
|
||||
@@ -13137,6 +13338,7 @@ on_edit_input (int count, int key, void *user_data)
|
||||
ctx->running_editor = true;
|
||||
ctx->editor_filename = filename;
|
||||
}
|
||||
strv_free (&argv);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -13174,15 +13376,19 @@ input_editor_cleanup (struct app_context *ctx)
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
static void
|
||||
launch_backlog_helper (struct app_context *ctx, int backlog_fd)
|
||||
launch_backlog_helper (struct app_context *ctx, int backlog_fd,
|
||||
const char *name, const char *path)
|
||||
{
|
||||
hard_assert (!ctx->running_backlog_helper);
|
||||
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);
|
||||
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, "behaviour.backlog_helper"),
|
||||
PROGRAM_NAME, localized_name, path, NULL);
|
||||
print_error ("%s: %s",
|
||||
"Failed to launch backlog helper", strerror (errno));
|
||||
_exit (EXIT_FAILURE);
|
||||
@@ -13227,7 +13433,7 @@ display_backlog (struct app_context *ctx, int flush_opts)
|
||||
|
||||
rewind (backlog);
|
||||
set_cloexec (fileno (backlog));
|
||||
launch_backlog_helper (ctx, fileno (backlog));
|
||||
launch_backlog_helper (ctx, fileno (backlog), buffer->name, NULL);
|
||||
fclose (backlog);
|
||||
return true;
|
||||
}
|
||||
@@ -13255,24 +13461,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_backlog_helper (ctx, fileno (full_log), buffer->name, path);
|
||||
fclose (full_log);
|
||||
free (path);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -13417,7 +13624,7 @@ on_insert_attribute (int count, int key, void *user_data)
|
||||
(void) key;
|
||||
|
||||
struct app_context *ctx = user_data;
|
||||
ctx->awaiting_mirc_escape = true;
|
||||
ctx->awaiting_formatting_escape = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -13451,7 +13658,7 @@ input_add_functions (void *user_data)
|
||||
XX ("toggle-unimportant", "Toggle junk msgs", on_toggle_unimportant)
|
||||
XX ("edit-input", "Edit input", on_edit_input)
|
||||
XX ("redraw-screen", "Redraw screen", on_redraw_screen)
|
||||
XX ("insert-attribute", "mIRC formatting", on_insert_attribute)
|
||||
XX ("insert-attribute", "IRC formatting", on_insert_attribute)
|
||||
XX ("start-paste-mode", "Bracketed paste", on_start_paste_mode)
|
||||
#undef XX
|
||||
}
|
||||
@@ -13622,19 +13829,33 @@ on_editline_complete (EditLine *editline, int key)
|
||||
|
||||
// Insert the best match instead
|
||||
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];
|
||||
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++)
|
||||
free (*p);
|
||||
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)
|
||||
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, " ");
|
||||
return CC_REFRESH;
|
||||
}
|
||||
@@ -13695,8 +13916,11 @@ 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");
|
||||
|
||||
// We need to hide the prompt and input first
|
||||
CALL_ (input, bind, "\r", "send-line");
|
||||
CALL_ (input, bind, "\n", "send-line");
|
||||
|
||||
CALL_ (input, bind_control, 'i', "complete");
|
||||
@@ -13722,9 +13946,9 @@ static const char *g_first_time_help[] =
|
||||
"F5/Ctrl-P\x02 or \x02" "F6/Ctrl-N\x02.",
|
||||
"",
|
||||
"Finally, adding a network is as simple as:",
|
||||
" - \x02/server add freenode\x02",
|
||||
" - \x02/set servers.freenode.addresses = \"chat.freenode.net\"\x02",
|
||||
" - \x02/connect freenode\x02",
|
||||
" - \x02/server add IRCnet\x02",
|
||||
" - \x02/set servers.IRCnet.addresses = \"open.ircnet.net\"\x02",
|
||||
" - \x02/connect IRCnet\x02",
|
||||
"",
|
||||
"That should be enough to get you started. Have fun!",
|
||||
""
|
||||
@@ -13981,7 +14205,7 @@ on_signal_pipe_readable (const struct pollfd *fd, struct app_context *ctx)
|
||||
}
|
||||
|
||||
static void
|
||||
process_mirc_escape (const struct pollfd *fd, struct app_context *ctx)
|
||||
process_formatting_escape (const struct pollfd *fd, struct app_context *ctx)
|
||||
{
|
||||
// There's no other way with libedit, as both el_getc() in a function
|
||||
// handler and CC_ARGHACK would block execution
|
||||
@@ -14033,11 +14257,45 @@ error:
|
||||
CALL (ctx->input, ding);
|
||||
done:
|
||||
str_reset (buf);
|
||||
ctx->awaiting_mirc_escape = false;
|
||||
ctx->awaiting_formatting_escape = false;
|
||||
}
|
||||
|
||||
#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
|
||||
process_bracketed_paste (const struct pollfd *fd, struct app_context *ctx)
|
||||
{
|
||||
@@ -14062,7 +14320,7 @@ process_bracketed_paste (const struct pollfd *fd, struct app_context *ctx)
|
||||
(int) (text_len = BRACKETED_PASTE_LIMIT));
|
||||
|
||||
buf->str[text_len] = '\0';
|
||||
if (CALL_ (ctx->input, insert, buf->str))
|
||||
if (insert_paste (ctx, buf->str, text_len))
|
||||
goto done;
|
||||
|
||||
error:
|
||||
@@ -14129,8 +14387,8 @@ on_tty_readable (const struct pollfd *fd, struct app_context *ctx)
|
||||
if (fd->revents & ~(POLLIN | POLLHUP | POLLERR))
|
||||
print_debug ("fd %d: unexpected revents: %d", fd->fd, fd->revents);
|
||||
|
||||
if (ctx->awaiting_mirc_escape)
|
||||
process_mirc_escape (fd, ctx);
|
||||
if (ctx->awaiting_formatting_escape)
|
||||
process_formatting_escape (fd, ctx);
|
||||
else if (ctx->in_bracketed_paste)
|
||||
process_bracketed_paste (fd, ctx);
|
||||
else if (!ctx->quitting)
|
||||
@@ -14303,9 +14561,11 @@ test_aliases (void)
|
||||
static void
|
||||
test_wrapping (void)
|
||||
{
|
||||
static const char *message = " foo bar foobar fóóbárbáz";
|
||||
static const char *split[] =
|
||||
{ " foo", "bar", "foob", "ar", "fó", "ób", "árb", "áz" };
|
||||
static const char *message = " foo bar foobar fóóbárbáz\002 a\0031 b";
|
||||
// XXX: formatting continuation order is implementation-dependent here
|
||||
// (irc_serialize_char_attrs() makes a choice in serialization)
|
||||
static const char *split[] = { " foo", "bar", "foob", "ar",
|
||||
"fó", "ób", "árb", "áz\x02", "\002a\0031", "\0031\002b" };
|
||||
|
||||
struct strv v = strv_make ();
|
||||
hard_assert (wrap_message (message, 4, &v, NULL));
|
||||
|
||||
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.
|
||||
|
||||
44
xD.c
44
xD.c
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* xD.c: an IRC daemon
|
||||
*
|
||||
* Copyright (c) 2014 - 2020, 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.
|
||||
@@ -22,7 +22,9 @@
|
||||
#define WANT_SYSLOG_LOGGING
|
||||
#include "common.c"
|
||||
#include "xD-replies.c"
|
||||
|
||||
#include <nl_types.h>
|
||||
#include <sys/resource.h>
|
||||
|
||||
enum { PIPE_READ, PIPE_WRITE };
|
||||
|
||||
@@ -2930,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)
|
||||
{
|
||||
@@ -2992,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 },
|
||||
@@ -3984,6 +4010,21 @@ daemonize (struct server_context *ctx)
|
||||
poller_post_fork (&ctx->poller);
|
||||
}
|
||||
|
||||
static void
|
||||
setup_limits (void)
|
||||
{
|
||||
struct rlimit limit;
|
||||
if (getrlimit (RLIMIT_NOFILE, &limit))
|
||||
{
|
||||
print_warning ("%s: %s", "getrlimit", strerror (errno));
|
||||
return;
|
||||
}
|
||||
|
||||
limit.rlim_cur = limit.rlim_max;
|
||||
if (setrlimit (RLIMIT_NOFILE, &limit))
|
||||
print_warning ("%s: %s", "setrlimit", strerror (errno));
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
@@ -4030,6 +4071,7 @@ main (int argc, char *argv[])
|
||||
|
||||
print_status (PROGRAM_NAME " " PROGRAM_VERSION " starting");
|
||||
setup_signal_handlers ();
|
||||
setup_limits ();
|
||||
init_openssl ();
|
||||
|
||||
struct server_context ctx;
|
||||
|
||||
Reference in New Issue
Block a user