Compare commits
80 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
b0e9d60ff7
|
|||
|
95076ff6ab
|
|||
|
f26ab854c9
|
|||
|
6e84d0c48b
|
|||
|
1b0e46d33a
|
|||
|
ae6519687a
|
|||
|
0caab02933
|
|||
|
074b378f03
|
|||
|
6a5533f783
|
|||
|
bb48e185d0
|
|||
|
83128dc7bb
|
|||
|
780e9c35bf
|
|||
|
3aaf9de5f2
|
|||
|
a1c7d2c983
|
|||
|
63d54df66a
|
|||
|
959fb15828
|
|||
|
36037cb3e5
|
|||
|
800a4e354c
|
|||
|
9f1ef95f46
|
|||
|
064102b786
|
|||
|
621f76c797
|
|||
|
2d1df86a46
|
|||
|
6ba94ba664
|
|||
|
f1170bc161
|
|||
|
dbb47ec2ee
|
|||
|
c413f6d4b2
|
|||
|
8ce60cdd2f
|
|||
|
d9af9c1057
|
|||
|
e816ddb32a
|
|||
|
57e2163bac
|
|||
|
60e40f4fe2
|
|||
|
7c76e60656
|
|||
|
6fe5f7de4c
|
|||
|
8e2e7be56f
|
|||
|
3275cd1fa2
|
|||
|
087091577c
|
|||
|
f6a41d7c51
|
|||
|
7538dfad2d
|
|||
|
2d8867f6fa
|
|||
|
4f5d4cb64b
|
|||
|
1704f90934
|
|||
|
41be32692c
|
|||
|
65a98dfd7c
|
|||
|
ce848bff0f
|
|||
|
6859e9abc9
|
|||
|
82f19cc144
|
|||
|
e4ce659174
|
|||
|
8f3df19786
|
|||
|
f8ecf7d0f7
|
|||
|
5b8335654f
|
|||
|
2a0ce32bdd
|
|||
|
13e1b220ff
|
|||
|
8827d1d473
|
|||
|
70fa559752
|
|||
|
bcfbd40003
|
|||
|
38188632e0
|
|||
|
682f3de097
|
|||
|
5295733629
|
|||
|
49e76b49de
|
|||
|
60a14b6683
|
|||
|
d2a3719b78
|
|||
|
d453b64d3f
|
|||
|
09c3980c50
|
|||
|
ed6ad5981a
|
|||
|
6c749be5f7
|
|||
|
8f21138f26
|
|||
|
542f2bca6b
|
|||
|
b9097f40f6
|
|||
|
4102e3c02c
|
|||
|
474a5973a3
|
|||
|
a65389ec62
|
|||
|
0c256943fd
|
|||
|
da2c8de56f
|
|||
|
eb02e8eee6
|
|||
|
24c8108f35
|
|||
|
67d044a426
|
|||
|
e52501a1df
|
|||
|
f57e8c59d6
|
|||
|
5ba2c825a1
|
|||
|
d11bd2c54e
|
@@ -1,3 +1,6 @@
|
||||
[submodule "liberty"]
|
||||
path = liberty
|
||||
url = https://git.janouch.name/p/liberty.git
|
||||
[submodule "termo"]
|
||||
path = termo
|
||||
url = https://git.janouch.name/p/termo.git
|
||||
|
||||
+113
-42
@@ -8,11 +8,6 @@ string (STRIP "${project_version}" project_version)
|
||||
project (xK VERSION "${project_version}"
|
||||
DESCRIPTION "IRC daemon, bot, TUI client and its web frontend" LANGUAGES C)
|
||||
|
||||
# Options
|
||||
option (WANT_READLINE "Use GNU Readline for the UI (better)" ON)
|
||||
option (WANT_LIBEDIT "Use BSD libedit for the UI" OFF)
|
||||
option (WANT_XF "Build xF" OFF)
|
||||
|
||||
# Moar warnings
|
||||
set (CMAKE_C_STANDARD 99)
|
||||
set (CMAKE_C_STANDARD_REQUIRED ON)
|
||||
@@ -72,6 +67,8 @@ endif ()
|
||||
# -lrt is only for glibc < 2.17
|
||||
# -liconv may or may not be a part of libc
|
||||
# -lm may or may not be a part of libc
|
||||
find_path (iconv_INCLUDE_DIRS iconv.h)
|
||||
include_directories (${iconv_INCLUDE_DIRS})
|
||||
foreach (extra iconv rt m)
|
||||
find_library (extra_lib_${extra} ${extra})
|
||||
if (extra_lib_${extra})
|
||||
@@ -88,13 +85,32 @@ CHECK_C_SOURCE_RUNS ("#include <iconv.h>
|
||||
== (iconv_t) -1; }" ICONV_ACCEPTS_TRANSLIT)
|
||||
|
||||
# Dependencies for xC
|
||||
pkg_check_modules (libffi REQUIRED libffi)
|
||||
list (APPEND xC_libraries ${libffi_LIBRARIES})
|
||||
include_directories (${libffi_INCLUDE_DIRS})
|
||||
link_directories (${libffi_LIBRARY_DIRS})
|
||||
find_package (Termo QUIET NO_MODULE)
|
||||
option (USE_SYSTEM_TERMO
|
||||
"Don't compile our own termo library, use the system one" "${Termo_FOUND}")
|
||||
if (USE_SYSTEM_TERMO)
|
||||
if (NOT Termo_FOUND)
|
||||
message (FATAL_ERROR "System termo library not found")
|
||||
endif ()
|
||||
else ()
|
||||
# We don't want the library to install, but EXCLUDE_FROM_ALL ignores tests
|
||||
add_subdirectory (termo EXCLUDE_FROM_ALL)
|
||||
file (WRITE ${PROJECT_BINARY_DIR}/CTestCustom.cmake
|
||||
"execute_process (COMMAND ${CMAKE_COMMAND} --build termo)")
|
||||
|
||||
# We don't have many good choices; this is a relatively clean approach
|
||||
# (other possibilities: setting a variable in the parent scope, using
|
||||
# a cache variable, writing a special config file with build paths in it
|
||||
# and including it here, or setting a custom property on the targets)
|
||||
get_directory_property (Termo_INCLUDE_DIRS
|
||||
DIRECTORY termo INCLUDE_DIRECTORIES)
|
||||
set (Termo_LIBRARIES termo-static)
|
||||
endif ()
|
||||
list (APPEND xC_libraries ${Termo_LIBRARIES})
|
||||
|
||||
# XXX: other Lua versions may be acceptable, don't know yet
|
||||
pkg_search_module (lua lua53 lua5.3 lua-5.3 lua54 lua5.4 lua-5.4 lua>=5.3)
|
||||
pkg_search_module (lua
|
||||
lua53 lua5.3 lua-5.3 lua54 lua5.4 lua-5.4 lua55 lua5.5 lua-5.5 lua>=5.3)
|
||||
option (WITH_LUA "Enable support for Lua plugins" ${lua_FOUND})
|
||||
|
||||
if (WITH_LUA)
|
||||
@@ -119,32 +135,45 @@ else ()
|
||||
message (SEND_ERROR "Curses not found")
|
||||
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)
|
||||
# Dependencies for xF
|
||||
find_package (Unistring)
|
||||
|
||||
# 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 ()
|
||||
elseif (WANT_LIBEDIT)
|
||||
pkg_check_modules (libedit REQUIRED libedit)
|
||||
list (APPEND xC_libraries ${libedit_LIBRARIES})
|
||||
include_directories (${libedit_INCLUDE_DIRS})
|
||||
set (xF_SATISFIED FALSE)
|
||||
if (ncursesw_FOUND AND Unistring_FOUND)
|
||||
set (xF_SATISFIED TRUE)
|
||||
endif ()
|
||||
option (WITH_XF "Build the xF frontend" "${xF_SATISFIED}")
|
||||
if (WITH_XF AND NOT xF_SATISFIED)
|
||||
message (FATAL_ERROR "Some xF dependencies were not found")
|
||||
endif ()
|
||||
|
||||
pkg_check_modules (x11 x11 xrender xft fontconfig libpng)
|
||||
option (WITH_X11 "Build xF with X11 support" "${x11_FOUND}")
|
||||
if (WITH_X11 AND NOT x11_FOUND)
|
||||
message (FATAL_ERROR "Some X11 libraries were not found")
|
||||
endif ()
|
||||
|
||||
pkg_check_modules (lcms2 lcms2)
|
||||
option (WITH_LCMS2 "Build xF with X11 colour management" "${lcms2_FOUND}")
|
||||
|
||||
pkg_check_modules (canberra libcanberra)
|
||||
set (canberra_SATISFIED FALSE)
|
||||
if (WITH_X11 AND canberra_FOUND)
|
||||
set (canberra_SATISFIED TRUE)
|
||||
endif ()
|
||||
option (WITH_CANBERRA "Build xF with libcanberra sound support"
|
||||
"${canberra_SATISFIED}")
|
||||
if (WITH_CANBERRA AND NOT WITH_X11)
|
||||
message (FATAL_ERROR "libcanberra support requires X11 support")
|
||||
elseif (WITH_CANBERRA AND NOT canberra_FOUND)
|
||||
message (FATAL_ERROR "libcanberra not found")
|
||||
endif ()
|
||||
|
||||
# Generate a configuration file
|
||||
set (HAVE_READLINE "${WANT_READLINE}")
|
||||
set (HAVE_EDITLINE "${WANT_LIBEDIT}")
|
||||
set (HAVE_LUA "${WITH_LUA}")
|
||||
set (HAVE_LUA "${WITH_LUA}")
|
||||
set (HAVE_X11 "${WITH_X11}")
|
||||
set (HAVE_LCMS2 "${WITH_LCMS2}")
|
||||
set (HAVE_CANBERRA "${WITH_CANBERRA}")
|
||||
|
||||
include (GNUInstallDirs)
|
||||
set (project_config ${PROJECT_BINARY_DIR}/config.h)
|
||||
@@ -177,23 +206,42 @@ add_custom_target (xC-proto DEPENDS ${PROJECT_BINARY_DIR}/xC-proto.c)
|
||||
|
||||
# Build
|
||||
foreach (name xB xC xD)
|
||||
list (APPEND project_binaries ${name})
|
||||
add_executable (${name} ${name}.c ${project_config})
|
||||
target_link_libraries (${name} ${project_libraries})
|
||||
target_link_libraries (${name} PRIVATE ${project_libraries})
|
||||
add_threads (${name})
|
||||
endforeach ()
|
||||
|
||||
add_dependencies (xD replies)
|
||||
add_dependencies (xC replies xC-proto)
|
||||
target_link_libraries (xC ${xC_libraries})
|
||||
target_link_libraries (xC PRIVATE ${xC_libraries})
|
||||
target_include_directories (xC PRIVATE ${Termo_INCLUDE_DIRS})
|
||||
|
||||
if (WANT_XF)
|
||||
pkg_check_modules (x11 REQUIRED x11 xrender xft fontconfig)
|
||||
include_directories (${x11_INCLUDE_DIRS})
|
||||
link_directories (${x11_LIBRARY_DIRS})
|
||||
if (WITH_XF)
|
||||
list (APPEND project_binaries xF)
|
||||
|
||||
add_executable (xF xF.c ${project_config})
|
||||
add_dependencies (xF xC-proto)
|
||||
target_link_libraries (xF ${x11_LIBRARIES} ${project_libraries})
|
||||
target_include_directories (xF PRIVATE
|
||||
${Unistring_INCLUDE_DIRS} ${Termo_INCLUDE_DIRS})
|
||||
target_link_libraries (xF PRIVATE
|
||||
${Unistring_LIBRARIES} ${Termo_LIBRARIES}
|
||||
${ncursesw_LIBRARIES} ${project_libraries})
|
||||
if (WITH_X11)
|
||||
target_include_directories (xF PRIVATE ${x11_INCLUDE_DIRS})
|
||||
target_link_directories (xF PRIVATE ${x11_LIBRARY_DIRS})
|
||||
target_link_libraries (xF PRIVATE ${x11_LIBRARIES})
|
||||
endif ()
|
||||
if (WITH_LCMS2)
|
||||
target_include_directories (xF PRIVATE ${lcms2_INCLUDE_DIRS})
|
||||
target_link_directories (xF PRIVATE ${lcms2_LIBRARY_DIRS})
|
||||
target_link_libraries (xF PRIVATE ${lcms2_LIBRARIES})
|
||||
endif ()
|
||||
if (WITH_CANBERRA)
|
||||
target_include_directories (xF PRIVATE ${canberra_INCLUDE_DIRS})
|
||||
target_link_directories (xF PRIVATE ${canberra_LIBRARY_DIRS})
|
||||
target_link_libraries (xF PRIVATE ${canberra_LIBRARIES})
|
||||
endif ()
|
||||
add_threads (xF)
|
||||
endif ()
|
||||
|
||||
@@ -202,7 +250,10 @@ include (CTest)
|
||||
if (BUILD_TESTING)
|
||||
add_executable (test-xC $<TARGET_PROPERTY:xC,SOURCES>)
|
||||
set_target_properties (test-xC PROPERTIES COMPILE_DEFINITIONS TESTING)
|
||||
target_link_libraries (test-xC $<TARGET_PROPERTY:xC,LINK_LIBRARIES>)
|
||||
target_include_directories (test-xC
|
||||
PUBLIC $<TARGET_PROPERTY:xC,INCLUDE_DIRECTORIES>)
|
||||
target_link_libraries (test-xC
|
||||
PUBLIC $<TARGET_PROPERTY:xC,LINK_LIBRARIES>)
|
||||
add_threads (test-xC)
|
||||
add_dependencies (test-xC replies)
|
||||
|
||||
@@ -231,13 +282,33 @@ add_custom_target (clang-tidy
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR})
|
||||
|
||||
# Installation
|
||||
install (TARGETS xB xC xD DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
install (TARGETS ${project_binaries} DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||
install (DIRECTORY plugins/xB/
|
||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/xB/plugins USE_SOURCE_PERMISSIONS)
|
||||
install (DIRECTORY plugins/xC/
|
||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/xC/plugins)
|
||||
|
||||
# Rasterize and install SVG icons
|
||||
if (WITH_XF AND WITH_X11)
|
||||
include (IconUtils)
|
||||
|
||||
set (icon_base ${PROJECT_BINARY_DIR}/icons)
|
||||
set (icon_png_list)
|
||||
foreach (icon_size 16 32 48)
|
||||
icon_to_png (xF ${PROJECT_SOURCE_DIR}/xF.svg
|
||||
${icon_size} ${icon_base} icon_png)
|
||||
list (APPEND icon_png_list ${icon_png})
|
||||
endforeach ()
|
||||
|
||||
add_custom_target (icons ALL DEPENDS ${icon_png_list})
|
||||
|
||||
install (FILES xF.svg
|
||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps)
|
||||
install (DIRECTORY ${icon_base}
|
||||
DESTINATION ${CMAKE_INSTALL_DATADIR})
|
||||
endif ()
|
||||
|
||||
# Generate documentation from text markup
|
||||
find_program (ASCIIDOCTOR_EXECUTABLE asciidoctor)
|
||||
find_program (A2X_EXECUTABLE a2x)
|
||||
@@ -246,7 +317,7 @@ if (NOT ASCIIDOCTOR_EXECUTABLE AND NOT A2X_EXECUTABLE)
|
||||
"falling back to a substandard manual page generator")
|
||||
endif ()
|
||||
|
||||
foreach (page xB xC xD)
|
||||
foreach (page ${project_binaries})
|
||||
set (page_output "${PROJECT_BINARY_DIR}/${page}.1")
|
||||
list (APPEND project_MAN_PAGES "${page_output}")
|
||||
if (ASCIIDOCTOR_EXECUTABLE)
|
||||
|
||||
@@ -1,3 +1,52 @@
|
||||
2.4.0 (2026-09-04) "Complete"
|
||||
|
||||
* xC: general.relay_bind is now a string array
|
||||
|
||||
* xC: after the last release, added back a C-t binding to transpose characters,
|
||||
and M-Enter to insert newlines
|
||||
|
||||
* xC: removed Readline hacks from the fancy-prompt.lua plugin
|
||||
|
||||
* xC + all frontends: adjusted key bindings, most notably:
|
||||
- changed M-a to prioritize highlights when looking for activity;
|
||||
- added M-A to look for buffers in reverse order;
|
||||
- removed the old M-!;
|
||||
- replaced M-PageUp/M-PageDown with M-Up/M-Down;
|
||||
- added M-/ to supplement M-TAB.
|
||||
|
||||
* xF: reached rough feature parity with xC and xP
|
||||
|
||||
* xF: X11: now optionally colour-managed using lcms2, improved IME integration
|
||||
|
||||
* xP: fixed channel title layout for narrow browser windows
|
||||
|
||||
|
||||
2.3.0 (2026-07-31) "Clean"
|
||||
|
||||
* xC: nuked broken Readline and editline support, added a custom interface
|
||||
|
||||
* xC: added a /bindings command
|
||||
|
||||
* xC: relay sessions can now be resumed after a dropped connection (adjustable
|
||||
with general.relay_resume_timeout and general.relay_resume_log_limit);
|
||||
dead links are detected with periodic pings
|
||||
|
||||
* xC: server TLS certificate hostnames are now properly verified
|
||||
|
||||
* xC: added support for Lua 5.5
|
||||
|
||||
* xP: added a button to reconnect and resume the session if possible;
|
||||
page reloads start a new session as before
|
||||
|
||||
* prime.lua: prevented a DoS opportunity on excessively large numbers
|
||||
|
||||
* Added a terminal/X11 frontend for xC called xF
|
||||
|
||||
* Bumped relay protocol version
|
||||
|
||||
* Various bugfixes
|
||||
|
||||
|
||||
2.2.0 (2026-01-10) "Just Doing the Needful"
|
||||
|
||||
* xC: added more characters as nickname delimiters,
|
||||
@@ -253,7 +302,7 @@
|
||||
|
||||
* xC: don't show remembered channel modes on parted channels
|
||||
|
||||
* xC: fix highlight detection in colored text
|
||||
* xC: fix highlight detection in coloured text
|
||||
|
||||
* xC: fix CTCP handling for the real world and don't decode X-QUOTEs
|
||||
|
||||
@@ -356,7 +405,7 @@
|
||||
|
||||
* xC: nicknames in /names are now ordered
|
||||
|
||||
* xC: nicknames now use the 256-color terminal palette if available
|
||||
* xC: nicknames now use the 256-colour terminal palette if available
|
||||
|
||||
* xC: now we skip entries in the "addresses" list that can't be resolved
|
||||
to an address, along with displaying a more helpful message
|
||||
|
||||
+25
-36
@@ -10,9 +10,8 @@ They're all lean on dependencies, and offer a maximally permissive licence.
|
||||
|
||||
xC
|
||||
--
|
||||
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.
|
||||
The IRC client, and the core of 'xK'. Its interface should feel somewhat
|
||||
familiar for WeeChat or irssi users.
|
||||
|
||||
image::xC.webp[align="center"]
|
||||
|
||||
@@ -33,10 +32,18 @@ including link:xC.adoc#_key_bindings[keyboard shortcuts].
|
||||
|
||||
image::xP.webp[align="center"]
|
||||
|
||||
xF
|
||||
--
|
||||
The hybrid terminal/X11 frontend for 'xC'. It has simplified word wrapping
|
||||
and limited colour support. Furthermore, X11 has simplified text shaping
|
||||
and doesn't support text selection (though in 'xC' tradition, it will happily
|
||||
launch an embedded editor on both the input and the log).
|
||||
|
||||
In general, 'xF' mimics 'xP' and adds back a bunch of lost 'xC' UI features.
|
||||
|
||||
xA, xT, xW, xM
|
||||
--------------
|
||||
Fyne, Qt Widgets, Win32, Cocoa frontends for 'xC'.
|
||||
Using them is not recommended.
|
||||
Experimental Fyne, Qt Widgets, Win32, Cocoa frontends for 'xC'.
|
||||
|
||||
xD
|
||||
--
|
||||
@@ -86,16 +93,17 @@ or as a https://git.janouch.name/p/nixexprs[Nix derivation].
|
||||
Building
|
||||
--------
|
||||
Build-only dependencies: CMake, pkg-config, awk, liberty (included),
|
||||
asciidoctor or asciidoc (recommended but optional) +
|
||||
asciidoctor or asciidoc (recommended but optional), rsvg-convert (for 'xF') +
|
||||
Common runtime dependencies: openssl +
|
||||
Additionally for 'xC': curses, libffi, readline >= 6.0 or libedit >= 2013-07-12,
|
||||
lua >= 5.3 (optional) +
|
||||
Additionally for 'xC': any curses, lua >= 5.3 (optional), termo (included) +
|
||||
Additionally for 'xF': ncursesw, libunistring, termo (included),
|
||||
x11 + xft + libpng (X11), lcms2 (X11, optional), libcanberra (X11, optional)
|
||||
|
||||
$ 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 -DWITH_LUA=ON
|
||||
-DWITH_LUA=ON -DWITH_XF=ON
|
||||
$ make
|
||||
|
||||
To install the application, you can do either the usual:
|
||||
@@ -116,12 +124,14 @@ For the rest you might want to generate a configuration file:
|
||||
|
||||
$ xB --write-default-config
|
||||
$ xD --write-default-config
|
||||
$ xF --write-default-config
|
||||
|
||||
After making any necessary edits to the file (there are comments to aid you in
|
||||
doing that), simply run the appropriate program with no arguments:
|
||||
|
||||
$ xB
|
||||
$ xD
|
||||
$ xF
|
||||
|
||||
'xB' stays running in the foreground, therefore I recommend launching it inside
|
||||
a Screen or tmux session.
|
||||
@@ -130,12 +140,14 @@ a Screen or tmux session.
|
||||
file or something like `killall` if you want to terminate it. You can run it
|
||||
as a `forking` type systemd user service.
|
||||
|
||||
xP
|
||||
~~
|
||||
Frontends
|
||||
~~~~~~~~~
|
||||
The precondition for running 'xC' frontends is enabling its relay interface:
|
||||
|
||||
/set general.relay_bind = "127.0.0.1:9000"
|
||||
|
||||
xP
|
||||
~~
|
||||
To build the web server, install the Go compiler, and run `make`
|
||||
from the _xP_ directory. Then start the resulting binary, and navigate to
|
||||
the adress you give it as its first argument--in the following example,
|
||||
@@ -156,8 +168,8 @@ running `make generate` first.
|
||||
xT
|
||||
~~
|
||||
The Qt Widgets frontend is a separate CMake subproject. It generally supports
|
||||
all desktop operating systems. To avoid having to specify the relay address
|
||||
each time you run it, pass it on the command line.
|
||||
all desktop operating systems, Android is technically usable. To avoid having
|
||||
to specify the relay address each time you run it, pass it on the command line.
|
||||
|
||||
xW
|
||||
~~
|
||||
@@ -191,26 +203,6 @@ in the required form, use:
|
||||
|
||||
$ openssl x509 -in public.pem -outform DER | sha256sum
|
||||
|
||||
Custom Key Bindings in xC
|
||||
-------------------------
|
||||
The default and preferred frontend used in 'xC' is GNU Readline. This means
|
||||
that you can change your bindings by editing '~/.inputrc'. For example:
|
||||
|
||||
....
|
||||
# Preload with system-wide settings
|
||||
$include /etc/inputrc
|
||||
|
||||
# Make M-left and M-right reorder buffers
|
||||
$if xC
|
||||
"\e\e[C": move-buffer-right
|
||||
"\e\e[D": move-buffer-left
|
||||
$endif
|
||||
....
|
||||
|
||||
Consult the source code and the GNU Readline manual for a list of available
|
||||
functions. Also refer to the latter for the exact syntax of this file.
|
||||
Beware that you can easily break the program if you're not careful.
|
||||
|
||||
How do I make xC look like the screenshot?
|
||||
------------------------------------------
|
||||
With the defaults, 'xC' doesn't look too fancy because I don't want to have
|
||||
@@ -255,6 +247,3 @@ License
|
||||
-------
|
||||
This software is released under the terms of the 0BSD license, the text of which
|
||||
is included within the package along with the list of authors.
|
||||
|
||||
Note that 'xC' becomes GPL-licensed when you link it against GNU Readline,
|
||||
but that is not a concern of this source package. The licenses are compatible.
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
|
||||
#include "liberty/liberty.c"
|
||||
#include <arpa/inet.h>
|
||||
#include <math.h>
|
||||
#include <netinet/tcp.h>
|
||||
|
||||
static void
|
||||
@@ -108,8 +109,42 @@ tokenize_host_port (char *address, const char **port)
|
||||
return address;
|
||||
}
|
||||
|
||||
static int *
|
||||
filter_color_cube_for_acceptable_nick_colors (size_t *len)
|
||||
{
|
||||
// This is a pure function and we don't use threads, static storage is fine
|
||||
static int table[6 * 6 * 6];
|
||||
size_t len_counter = 0;
|
||||
for (int x = 0; x < (int) N_ELEMENTS (table); x++)
|
||||
{
|
||||
int r = x / 36;
|
||||
int g = (x / 6) % 6;
|
||||
int b = (x % 6);
|
||||
|
||||
// The first step is 95/255, the rest are 40/255,
|
||||
// as an approximation we can double the first step
|
||||
double linear_R = pow ((r + !!r) / 6., 2.2);
|
||||
double linear_G = pow ((g + !!g) / 6., 2.2);
|
||||
double linear_B = pow ((b + !!b) / 6., 2.2);
|
||||
|
||||
// Use the relative luminance of colours within the cube to filter
|
||||
// colours that look okay-ish on terminals with both black and white
|
||||
// backgrounds (use the test-nick-colors script to calibrate)
|
||||
double Y = 0.2126 * linear_R + 0.7152 * linear_G + 0.0722 * linear_B;
|
||||
if (Y >= .25 && Y <= .4)
|
||||
table[len_counter++] = 16 + x;
|
||||
}
|
||||
*len = len_counter;
|
||||
return table;
|
||||
}
|
||||
|
||||
// --- To be moved to liberty --------------------------------------------------
|
||||
|
||||
#define LIST_FOR_EACH_REVERSED(type, iter, tail) \
|
||||
for (type *iter = (tail), *prev; \
|
||||
(iter && (prev = iter->prev)) || iter; \
|
||||
iter = prev)
|
||||
|
||||
// FIXME: in xssl_get_error() we rely on error reasons never being NULL (i.e.,
|
||||
// all loaded), which isn't very robust.
|
||||
// TODO: check all places where this is used and see if we couldn't gain better
|
||||
@@ -134,6 +169,31 @@ xerr_describe_error (void)
|
||||
return reason;
|
||||
}
|
||||
|
||||
static bool
|
||||
str_append_c_utf8 (struct str *s, uint32_t ucs4)
|
||||
{
|
||||
if (ucs4 < 0x80)
|
||||
str_append_c (s, ucs4);
|
||||
else if (ucs4 < 0x7FF)
|
||||
str_append_data (s, (char[]) {
|
||||
0xc0 | (ucs4 >> 6),
|
||||
0x80 | (ucs4 & 0x3f) }, 2);
|
||||
else if (ucs4 < 0xFFFF)
|
||||
str_append_data (s, (char[]) {
|
||||
0xe0 | (ucs4 >> 12),
|
||||
0x80 | ((ucs4 >> 6) & 0x3f),
|
||||
0x80 | (ucs4 & 0x3f) }, 3);
|
||||
else if (ucs4 < 0x10FFFF)
|
||||
str_append_data (s, (char[]) {
|
||||
0xf0 | (ucs4 >> 18),
|
||||
0x80 | ((ucs4 >> 12) & 0x3f),
|
||||
0x80 | ((ucs4 >> 6) & 0x3f),
|
||||
0x80 | (ucs4 & 0x3f) }, 4);
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct str
|
||||
str_from_cstr (const char *cstr)
|
||||
{
|
||||
|
||||
+3
-2
@@ -6,9 +6,10 @@
|
||||
// We use the XDG Base Directory Specification, but may be installed anywhere.
|
||||
#define PROJECT_DATADIR "${CMAKE_INSTALL_FULL_DATADIR}"
|
||||
|
||||
#cmakedefine HAVE_READLINE
|
||||
#cmakedefine HAVE_EDITLINE
|
||||
#cmakedefine HAVE_LUA
|
||||
#cmakedefine HAVE_X11
|
||||
#cmakedefine HAVE_LCMS2
|
||||
#cmakedefine HAVE_CANBERRA
|
||||
|
||||
#cmakedefine01 ICONV_ACCEPTS_TRANSLIT
|
||||
|
||||
|
||||
+1
-1
Submodule liberty updated: 31ae400852...022cce71fa
@@ -14,19 +14,7 @@
|
||||
-- OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
-- CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
--
|
||||
-- Beware that it is a hack and only goes about 90% of the way, which is why
|
||||
-- this functionality is only available as a plugin in the first place
|
||||
-- (well, and also for customizability).
|
||||
--
|
||||
-- The biggest problem is that the way we work with Readline is incompatible
|
||||
-- with multiline prompts, and normal newlines just don't work. This is being
|
||||
-- circumvented by using an overflowing single-line prompt with a specially
|
||||
-- crafted character in the rightmost column that prevents the bar's background
|
||||
-- from spilling all over the last line.
|
||||
--
|
||||
-- There is also a problem with C-r search rendering not clearing out the
|
||||
-- background but to really fix that mode, we'd have to fully reimplement it
|
||||
-- since its alternative prompt very often gets overriden by accident anyway.
|
||||
-- This might go directly to xC, though it's more customizable as a plugin.
|
||||
|
||||
xC.hook_prompt (function (hook)
|
||||
local current = xC.current_buffer
|
||||
@@ -67,11 +55,7 @@ xC.hook_prompt (function (hook)
|
||||
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]", "?")
|
||||
|
||||
-- Align to the terminal's width and apply formatting, including the hack.
|
||||
-- Align to the terminal's width and apply formatting.
|
||||
local lines, cols = xC.get_screen_size ()
|
||||
local trailing, width = " ", xC.measure (x)
|
||||
while cols > 0 and width >= cols do
|
||||
@@ -79,10 +63,10 @@ xC.hook_prompt (function (hook)
|
||||
trailing, width = ">", xC.measure (x)
|
||||
end
|
||||
|
||||
-- xC automatically smartly wraps the line.
|
||||
x = "\x01\x1b[0;4;1;38;5;16m\x1b[48;5;" .. bg_color .. "m\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"
|
||||
x .. string.rep (" ", cols - width - 1) .. trailing ..
|
||||
"\x01\x1b[0;1m\x02"
|
||||
|
||||
local user_prefix = function (chan, user)
|
||||
for i, chan_user in ipairs (chan.users) do
|
||||
@@ -104,10 +88,6 @@ xC.hook_prompt (function (hook)
|
||||
if modes ~= "" then x = x .. "(" .. modes .. ")" end
|
||||
end
|
||||
x = x .. "] "
|
||||
else
|
||||
-- There needs to be at least one character so that the cursor
|
||||
-- doesn't get damaged by our hack in that last column
|
||||
x = x .. "> "
|
||||
end
|
||||
return x
|
||||
end)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
--
|
||||
-- prime.lua: highlight prime numbers in messages
|
||||
--
|
||||
-- Copyright (c) 2020, Přemysl Eric Janouch <p@janouch.name>
|
||||
-- Copyright (c) 2020, 2026 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.
|
||||
@@ -34,6 +34,7 @@ xC.setup_config {
|
||||
-- The prime test is actually very fast, so there is no DoS concern
|
||||
local do_intercolour = function (text)
|
||||
return tostring (text:gsub ("%f[%w_]%d+", function (n)
|
||||
if tonumber (n) > math.maxinteger then return nil end
|
||||
if tonumber (n) < smallest then return nil end
|
||||
for i = 2, n ^ (1 / 2) do if (n % i) == 0 then return nil end end
|
||||
return highlight .. n .. highlight
|
||||
|
||||
Submodule
+1
Submodule termo added at a8f3f6eca3
+1
-1
@@ -9,7 +9,7 @@ export example=$(
|
||||
#define N_ELEMENTS(a) (sizeof (a) / sizeof ((a)[0]))
|
||||
|
||||
$(perl -0777 -ne 'print $& if /^.*?\nfilter_color(?s:.*?)^}$/m' \
|
||||
"$(dirname "$0")"/xC.c)
|
||||
"$(dirname "$0")"/common.c)
|
||||
|
||||
void main () {
|
||||
size_t len = 0;
|
||||
|
||||
@@ -33,6 +33,8 @@ function expect (string)
|
||||
xC:eof {function (p) error "xC exited prematurely" end})
|
||||
end
|
||||
|
||||
expect "]"
|
||||
|
||||
-- Connect to the daemon
|
||||
send "/server add localhost\n"
|
||||
expect "]"
|
||||
|
||||
@@ -18,6 +18,10 @@ FyneApp.toml: ../xK-version
|
||||
Version = '$$(cat ../xK-version)'\n\
|
||||
Build = 1\n\
|
||||
\n\
|
||||
[Migrations]\n\
|
||||
# This is temporary, for Fyne 2.8\n\
|
||||
fyneDo = true\n\
|
||||
\n\
|
||||
[LinuxAndBSD]\n\
|
||||
GenericName = 'IRC Client'\n\
|
||||
Categories = ['Network', 'Chat', 'IRCClient']\n" > $@
|
||||
|
||||
@@ -1,33 +1,36 @@
|
||||
module janouch.name/xK/xA
|
||||
|
||||
go 1.24.0
|
||||
go 1.26
|
||||
|
||||
require (
|
||||
fyne.io/fyne/v2 v2.7.2
|
||||
fyne.io/fyne/v2 v2.8.0
|
||||
github.com/ebitengine/oto/v3 v3.4.0
|
||||
)
|
||||
|
||||
require (
|
||||
fyne.io/systray v1.12.0 // indirect
|
||||
fyne.io/systray v1.12.2 // indirect
|
||||
github.com/BurntSushi/toml v1.6.0 // indirect
|
||||
github.com/FyshOS/fancyfs v0.0.1 // indirect
|
||||
github.com/anthonynsimon/bild v0.16.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/ebitengine/purego v0.9.1 // indirect
|
||||
github.com/ebitengine/purego v0.10.1 // indirect
|
||||
github.com/fredbi/uri v1.1.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fyne-io/gl-js v0.2.0 // indirect
|
||||
github.com/fyne-io/glfw-js v0.3.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.10.1 // indirect
|
||||
github.com/fyne-io/gl-js v0.2.1-0.20260315212741-029c47fd27e8 // indirect
|
||||
github.com/fyne-io/glfw-js v0.4.0 // indirect
|
||||
github.com/fyne-io/image v0.1.1 // indirect
|
||||
github.com/fyne-io/oksvg v0.2.0 // indirect
|
||||
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728 // indirect
|
||||
github.com/go-text/render v0.2.0 // indirect
|
||||
github.com/go-text/typesetting v0.3.2 // indirect
|
||||
github.com/go-gl/gl v0.0.0-20260331235117-4566fea9a276 // indirect
|
||||
github.com/go-gl/glfw/v3.4/glfw v0.1.0-pre.1.0.20260707082822-2a407d02d01a // indirect
|
||||
github.com/go-text/render v0.2.1 // indirect
|
||||
github.com/go-text/typesetting v0.3.4 // indirect
|
||||
github.com/godbus/dbus/v5 v5.2.2 // indirect
|
||||
github.com/hack-pad/go-indexeddb v0.3.2 // indirect
|
||||
github.com/hack-pad/safejs v0.1.1 // indirect
|
||||
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade // indirect
|
||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.24 // indirect
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
@@ -35,10 +38,10 @@ require (
|
||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
|
||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
github.com/yuin/goldmark v1.7.16 // indirect
|
||||
golang.org/x/image v0.34.0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
github.com/yuin/goldmark v1.8.4 // indirect
|
||||
golang.org/x/image v0.44.0 // indirect
|
||||
golang.org/x/net v0.57.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
golang.org/x/text v0.40.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
@@ -1,40 +1,45 @@
|
||||
fyne.io/fyne/v2 v2.7.2 h1:XiNpWkn0PzX43ZCjbb0QYGg1RCxVbugwfVgikWZBCMw=
|
||||
fyne.io/fyne/v2 v2.7.2/go.mod h1:PXbqY3mQmJV3J1NRUR2VbVgUUx3vgvhuFJxyjRK/4Ug=
|
||||
fyne.io/systray v1.12.0 h1:CA1Kk0e2zwFlxtc02L3QFSiIbxJ/P0n582YrZHT7aTM=
|
||||
fyne.io/systray v1.12.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
|
||||
fyne.io/fyne/v2 v2.8.0 h1:KNUdIk1eKsXSPy/wU6MdiR1hppAPvyzbjPbtJ8h6EUQ=
|
||||
fyne.io/fyne/v2 v2.8.0/go.mod h1:tLJK7CVtUBOnMiSDR+J88t/quiGuEhwGs09tIVM1RXg=
|
||||
fyne.io/systray v1.12.2 h1:Y8DZxgLHsVQt6rY9Zrkkg+j67S7vv/1F2viOWKPpVeA=
|
||||
fyne.io/systray v1.12.2/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
|
||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/FyshOS/fancyfs v0.0.1 h1:kgvm7VvwOMLkYTqSflplp62SlMVWQ2uAoHw9CXwXHYg=
|
||||
github.com/FyshOS/fancyfs v0.0.1/go.mod h1:S5SHVz/5R72iCXOxCqdcyTPSlg3JxNd0gaHyGBSrY8A=
|
||||
github.com/anthonynsimon/bild v0.16.1 h1:ECqtLkQ15kqfHdRtzUfNvQniJtHNpzdVU/7feMYAm0o=
|
||||
github.com/anthonynsimon/bild v0.16.1/go.mod h1:hYAxurnswTQ9dexoiK922MepdXLC1lBzG35n6ypk//g=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/ebitengine/oto/v3 v3.4.0 h1:br0PgASsEWaoWn38b2Goe7m1GKFYfNgnsjSd5Gg+/bQ=
|
||||
github.com/ebitengine/oto/v3 v3.4.0/go.mod h1:IOleLVD0m+CMak3mRVwsYY8vTctQgOM0iiL6S7Ar7eI=
|
||||
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
||||
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/ebitengine/purego v0.10.1 h1:dewVBCBT2GaMu1SrNTYxQhgQBethzfhiwvZiLGP/qyY=
|
||||
github.com/ebitengine/purego v0.10.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
||||
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
||||
github.com/fredbi/uri v1.1.1 h1:xZHJC08GZNIUhbP5ImTHnt5Ya0T8FI2VAwI/37kh2Ko=
|
||||
github.com/fredbi/uri v1.1.1/go.mod h1:4+DZQ5zBjEwQCDmXW5JdIjz0PUA+yJbvtBv+u+adr5o=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fyne-io/gl-js v0.2.0 h1:+EXMLVEa18EfkXBVKhifYB6OGs3HwKO3lUElA0LlAjs=
|
||||
github.com/fyne-io/gl-js v0.2.0/go.mod h1:ZcepK8vmOYLu96JoxbCKJy2ybr+g1pTnaBDdl7c3ajI=
|
||||
github.com/fyne-io/glfw-js v0.3.0 h1:d8k2+Y7l+zy2pc7wlGRyPfTgZoqDf3AI4G+2zOWhWUk=
|
||||
github.com/fyne-io/glfw-js v0.3.0/go.mod h1:Ri6te7rdZtBgBpxLW19uBpp3Dl6K9K/bRaYdJ22G8Jk=
|
||||
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||
github.com/fyne-io/gl-js v0.2.1-0.20260315212741-029c47fd27e8 h1:0kdPD/GEntpWmZEK5Zu/xE6Tr37jYCVDf9QP8lA/QK8=
|
||||
github.com/fyne-io/gl-js v0.2.1-0.20260315212741-029c47fd27e8/go.mod h1:ZcepK8vmOYLu96JoxbCKJy2ybr+g1pTnaBDdl7c3ajI=
|
||||
github.com/fyne-io/glfw-js v0.4.0 h1:I9hREBeFyI10cNIqbMKYb1PRidyPDgwob8o2la9SfQo=
|
||||
github.com/fyne-io/glfw-js v0.4.0/go.mod h1:SDchsFZh4n7nVuBoiowOhOgIBdz+qUQVeC1w9fe2yVU=
|
||||
github.com/fyne-io/image v0.1.1 h1:WH0z4H7qfvNUw5l4p3bC1q70sa5+YWVt6HCj7y4VNyA=
|
||||
github.com/fyne-io/image v0.1.1/go.mod h1:xrfYBh6yspc+KjkgdZU/ifUC9sPA5Iv7WYUBzQKK7JM=
|
||||
github.com/fyne-io/oksvg v0.2.0 h1:mxcGU2dx6nwjJsSA9PCYZDuoAcsZ/OuJlvg/Q9Njfo8=
|
||||
github.com/fyne-io/oksvg v0.2.0/go.mod h1:dJ9oEkPiWhnTFNCmRgEze+YNprJF7YRbpjgpWS4kzoI=
|
||||
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA=
|
||||
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728 h1:RkGhqHxEVAvPM0/R+8g7XRwQnHatO0KAuVcwHo8q9W8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728/go.mod h1:SyRD8YfuKk+ZXlDqYiqe1qMSqjNgtHzBTG810KUagMc=
|
||||
github.com/go-text/render v0.2.0 h1:LBYoTmp5jYiJ4NPqDc2pz17MLmA3wHw1dZSVGcOdeAc=
|
||||
github.com/go-text/render v0.2.0/go.mod h1:CkiqfukRGKJA5vZZISkjSYrcdtgKQWRa2HIzvwNN5SU=
|
||||
github.com/go-text/typesetting v0.3.2 h1:OUOFxp9Rx5PiO0/rh2IY+5gmyXjXsVG8+LfEyk9NMcE=
|
||||
github.com/go-text/typesetting v0.3.2/go.mod h1:vIRUT25mLQaSh4C8H/lIsKppQz/Gdb8Pu/tNwpi52ts=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20250618110550-c820a94c77b8 h1:4KCscI9qYWMGTuz6BpJtbUSRzcBrUSSE0ENMJbNSrFs=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20250618110550-c820a94c77b8/go.mod h1:3/62I4La/HBRX9TcTpBj4eipLiwzf+vhI+7whTc9V7o=
|
||||
github.com/go-gl/gl v0.0.0-20260331235117-4566fea9a276 h1:IO5P06Pcj9K04d+l4nrf3c2U56+dAotIFG6u4P1wAHI=
|
||||
github.com/go-gl/gl v0.0.0-20260331235117-4566fea9a276/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
|
||||
github.com/go-gl/glfw/v3.4/glfw v0.1.0-pre.1.0.20260707082822-2a407d02d01a h1:HWK0MBggT/T6YH7VffE10xBIhqeTq8JzIUPJXrRy87g=
|
||||
github.com/go-gl/glfw/v3.4/glfw v0.1.0-pre.1.0.20260707082822-2a407d02d01a/go.mod h1:T5Dn0JwIJOX1euPZ/iT4tq6nFYtmukjcYa7937HuYK8=
|
||||
github.com/go-text/render v0.2.1 h1:qwHhxqGUjjg4L0XyJWj7M7bpY75NZM+kBpv2Yfw5mcg=
|
||||
github.com/go-text/render v0.2.1/go.mod h1:HCCAq8MUlm/WRcXshBb4K/n+IkjeXQ1c2Ba+yICSm0A=
|
||||
github.com/go-text/typesetting v0.3.4 h1:YYurUOtEb9kGSOz4uE3k4OpBGsp1dDL8+fjCeaFamAU=
|
||||
github.com/go-text/typesetting v0.3.4/go.mod h1:4qZCQphq4KSgGTAeI0uMEkVbROgfah8BuyF5LRYr7XY=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20260223113751-2d88ac90dae3 h1:drBZzMgdYPbmyXqOto4YhhJGrFIQCX94FpR4MzTCsos=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20260223113751-2d88ac90dae3/go.mod h1:3/62I4La/HBRX9TcTpBj4eipLiwzf+vhI+7whTc9V7o=
|
||||
github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
|
||||
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
|
||||
@@ -49,6 +54,8 @@ github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe9
|
||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU=
|
||||
github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.1 h1:JDEJraFsQE17Dut9HFDHzCoAWGEQJom5s0TRd17NIEQ=
|
||||
@@ -67,18 +74,18 @@ github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqd
|
||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
|
||||
github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark v1.8.4 h1:oat/nd3U6NeQqFEL3xpEJq7d7c86NI+DbSNGAs4xnjA=
|
||||
github.com/yuin/goldmark v1.8.4/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8=
|
||||
golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU=
|
||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
golang.org/x/image v0.44.0 h1:+tDekMZED9+LrtB3G5xzRggpVh9CARjZqROla3R3R+I=
|
||||
golang.org/x/image v0.44.0/go.mod h1:V8K3KE9KKKE+pLpQDOeN18w9oacNSvy1tDOirTu4xtY=
|
||||
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
|
||||
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
||||
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -393,10 +393,10 @@ func bufferScrollToBottom() {
|
||||
func bufferPushLine(b *buffer, line bufferLine) {
|
||||
b.lines = append(b.lines, line)
|
||||
|
||||
// Fyne's text layouting is extremely slow.
|
||||
// This also noticeably improves performance.
|
||||
// The limit could be made configurable,
|
||||
// and we could use a ring buffer approach to storing the lines.
|
||||
if len(b.lines) > 100 {
|
||||
if len(b.lines) > 1000 {
|
||||
b.lines = slices.Delete(b.lines, 0, 1)
|
||||
}
|
||||
}
|
||||
@@ -529,6 +529,10 @@ func bufferToggleLog() {
|
||||
relaySend(RelayCommandData{Variant: &RelayCommandDataBufferLog{
|
||||
BufferName: name,
|
||||
}}, func(err string, response *RelayResponseData) {
|
||||
if response == nil {
|
||||
showErrorMessage(err)
|
||||
return
|
||||
}
|
||||
if bufferCurrent == name {
|
||||
bufferToggleLogFinish(
|
||||
err, response.Variant.(*RelayResponseDataBufferLog))
|
||||
@@ -545,6 +549,7 @@ func convertItemFormatting(
|
||||
switch data := item.Variant.(type) {
|
||||
case *RelayItemDataReset:
|
||||
*cf = defaultBufferLineItem()
|
||||
*inverse = false
|
||||
case *RelayItemDataFlipBold:
|
||||
cf.format.Bold = !cf.format.Bold
|
||||
case *RelayItemDataFlipItalic:
|
||||
@@ -552,7 +557,7 @@ func convertItemFormatting(
|
||||
case *RelayItemDataFlipUnderline:
|
||||
cf.format.Underline = !cf.format.Underline
|
||||
case *RelayItemDataFlipCrossedOut:
|
||||
// https://github.com/fyne-io/fyne/issues/1084
|
||||
cf.format.Strikethrough = !cf.format.Strikethrough
|
||||
case *RelayItemDataFlipInverse:
|
||||
*inverse = !*inverse
|
||||
case *RelayItemDataFlipMonospace:
|
||||
@@ -1049,6 +1054,11 @@ func relayProcessMessage(m *RelayEventMessage) {
|
||||
case *RelayEventDataServerRename:
|
||||
servers[data.New] = servers[data.ServerName]
|
||||
delete(servers, data.ServerName)
|
||||
for i := range buffers {
|
||||
if buffers[i].serverName == data.ServerName {
|
||||
buffers[i].serverName = data.New
|
||||
}
|
||||
}
|
||||
case *RelayEventDataServerRemove:
|
||||
delete(servers, data.ServerName)
|
||||
}
|
||||
@@ -1097,10 +1107,11 @@ func relayResetState() {
|
||||
}
|
||||
|
||||
func relayRun() {
|
||||
fyne.CurrentApp().Preferences().SetString(preferenceAddress, backendAddress)
|
||||
backendLock.Lock()
|
||||
|
||||
fyne.DoAndWait(func() {
|
||||
fyne.CurrentApp().Preferences().
|
||||
SetString(preferenceAddress, backendAddress)
|
||||
relayResetState()
|
||||
})
|
||||
|
||||
@@ -1127,17 +1138,10 @@ func relayRun() {
|
||||
}, nil)
|
||||
|
||||
relayMessages := relayMakeReceiver(backendContext, backendConn)
|
||||
Loop:
|
||||
for {
|
||||
select {
|
||||
case m, ok := <-relayMessages:
|
||||
if !ok {
|
||||
break Loop
|
||||
}
|
||||
fyne.DoAndWait(func() {
|
||||
relayProcessMessage(&m)
|
||||
})
|
||||
}
|
||||
for m := range relayMessages {
|
||||
fyne.DoAndWait(func() {
|
||||
relayProcessMessage(&m)
|
||||
})
|
||||
}
|
||||
fyne.DoAndWait(func() {
|
||||
wConnect.Show()
|
||||
@@ -1215,6 +1219,10 @@ func inputComplete() bool {
|
||||
Text: state.input,
|
||||
Position: uint32(len(state.input)),
|
||||
}}, func(err string, response *RelayResponseData) {
|
||||
if response == nil {
|
||||
showErrorMessage(err)
|
||||
return
|
||||
}
|
||||
if stamp := inputGetStamp(); state == stamp {
|
||||
inputCompleteFinish(state,
|
||||
err, response.Variant.(*RelayResponseDataBufferComplete))
|
||||
@@ -1289,11 +1297,11 @@ func (e *inputEntry) KeyDown(key *fyne.KeyEvent) {
|
||||
}
|
||||
|
||||
switch {
|
||||
case modifiedKey.Modifier == fyne.KeyModifierControl &&
|
||||
case modifiedKey.Modifier == fyne.KeyModifierAlt &&
|
||||
modifiedKey.KeyName == fyne.KeyP:
|
||||
inputUp()
|
||||
return
|
||||
case modifiedKey.Modifier == fyne.KeyModifierControl &&
|
||||
case modifiedKey.Modifier == fyne.KeyModifierAlt &&
|
||||
modifiedKey.KeyName == fyne.KeyN:
|
||||
inputDown()
|
||||
return
|
||||
@@ -1412,7 +1420,7 @@ func (l *customLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) {
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
// rotatedBuffers returns buffer indexes starting with the current buffer.
|
||||
// rotatedBuffers returns buffer indexes starting after the current buffer.
|
||||
func rotatedBuffers() []int {
|
||||
r, start := make([]int, len(buffers)), 0
|
||||
for i := range buffers {
|
||||
@@ -1428,57 +1436,91 @@ func rotatedBuffers() []int {
|
||||
return r
|
||||
}
|
||||
|
||||
func actionBufferPrevious() {
|
||||
if r := rotatedBuffers(); len(r) <= 0 {
|
||||
} else if i := r[len(r)-1]; i == 0 {
|
||||
bufferActivate(buffers[len(buffers)-1].bufferName)
|
||||
} else {
|
||||
bufferActivate(buffers[i-1].bufferName)
|
||||
}
|
||||
}
|
||||
|
||||
func actionBufferNext() {
|
||||
if r := rotatedBuffers(); len(r) <= 0 {
|
||||
} else {
|
||||
bufferActivate(buffers[r[0]].bufferName)
|
||||
}
|
||||
}
|
||||
|
||||
func actionBufferLast() {
|
||||
if bufferLast != "" {
|
||||
bufferActivate(bufferLast)
|
||||
}
|
||||
}
|
||||
|
||||
func actionBufferActivity(backwards bool) {
|
||||
r := rotatedBuffers()
|
||||
if len(r) == 0 {
|
||||
return
|
||||
}
|
||||
r = r[:len(r)-1]
|
||||
if backwards {
|
||||
slices.Reverse(r)
|
||||
}
|
||||
for _, i := range r {
|
||||
if buffers[i].highlighted {
|
||||
bufferActivate(buffers[i].bufferName)
|
||||
return
|
||||
}
|
||||
}
|
||||
for _, i := range r {
|
||||
if buffers[i].newMessages != 0 {
|
||||
bufferActivate(buffers[i].bufferName)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var shortcuts = map[desktop.CustomShortcut]func(){
|
||||
{
|
||||
KeyName: fyne.KeyPageUp,
|
||||
Modifier: fyne.KeyModifierControl,
|
||||
}: func() {
|
||||
if r := rotatedBuffers(); len(r) <= 0 {
|
||||
} else if i := r[len(r)-1]; i == 0 {
|
||||
bufferActivate(buffers[len(buffers)-1].bufferName)
|
||||
} else {
|
||||
bufferActivate(buffers[i-1].bufferName)
|
||||
}
|
||||
},
|
||||
}: actionBufferPrevious,
|
||||
{
|
||||
KeyName: fyne.KeyUp,
|
||||
Modifier: fyne.KeyModifierAlt,
|
||||
}: actionBufferPrevious,
|
||||
{
|
||||
KeyName: fyne.KeyF5,
|
||||
Modifier: 0,
|
||||
}: actionBufferPrevious,
|
||||
{
|
||||
KeyName: fyne.KeyPageDown,
|
||||
Modifier: fyne.KeyModifierControl,
|
||||
}: func() {
|
||||
if r := rotatedBuffers(); len(r) <= 0 {
|
||||
} else {
|
||||
bufferActivate(buffers[r[0]].bufferName)
|
||||
}
|
||||
},
|
||||
}: actionBufferNext,
|
||||
{
|
||||
KeyName: fyne.KeyDown,
|
||||
Modifier: fyne.KeyModifierAlt,
|
||||
}: actionBufferNext,
|
||||
{
|
||||
KeyName: fyne.KeyF6,
|
||||
Modifier: 0,
|
||||
}: actionBufferNext,
|
||||
{
|
||||
KeyName: fyne.KeyTab,
|
||||
Modifier: fyne.KeyModifierAlt,
|
||||
}: func() {
|
||||
if bufferLast != "" {
|
||||
bufferActivate(bufferLast)
|
||||
}
|
||||
},
|
||||
{
|
||||
// XXX: This makes an assumption on the keyboard layout (we want '!').
|
||||
KeyName: fyne.Key1,
|
||||
Modifier: fyne.KeyModifierAlt | fyne.KeyModifierShift,
|
||||
}: func() {
|
||||
for _, i := range rotatedBuffers() {
|
||||
if buffers[i].highlighted {
|
||||
bufferActivate(buffers[i].bufferName)
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
}: actionBufferLast,
|
||||
{
|
||||
KeyName: fyne.KeyA,
|
||||
Modifier: fyne.KeyModifierAlt,
|
||||
}: func() {
|
||||
for _, i := range rotatedBuffers() {
|
||||
if buffers[i].newMessages != 0 {
|
||||
bufferActivate(buffers[i].bufferName)
|
||||
break
|
||||
}
|
||||
}
|
||||
actionBufferActivity(false)
|
||||
},
|
||||
{
|
||||
KeyName: fyne.KeyA,
|
||||
Modifier: fyne.KeyModifierAlt | fyne.KeyModifierShift,
|
||||
}: func() {
|
||||
actionBufferActivity(true)
|
||||
},
|
||||
{
|
||||
KeyName: fyne.KeyH,
|
||||
|
||||
@@ -630,7 +630,7 @@ recovery_handler (int signum, siginfo_t *info, void *context)
|
||||
const char *signal_name = NULL, *reason = NULL;
|
||||
translate_signal_info (signum, &signal_name, info->si_code, &reason);
|
||||
|
||||
char buf[128], numbuf[8];
|
||||
char buf[128], numbuf[16];
|
||||
if (!signal_name)
|
||||
{
|
||||
snprintf (numbuf, sizeof numbuf, "%d", signum);
|
||||
|
||||
@@ -39,28 +39,27 @@ This feature may be used to preview server MOTD files.
|
||||
|
||||
Key bindings
|
||||
------------
|
||||
Most key bindings are inherited from the frontend in use, which is either GNU
|
||||
Readline or BSD editline. A few of them, however, are special to the IRC client
|
||||
or assume a different function. This is a list of all local overrides and
|
||||
their respective function names:
|
||||
The basic key bindings are inspired by GNU Readline. On top of that, there are
|
||||
some which are special to the IRC client or assume a different function.
|
||||
This is a list of all local specialties and their respective function names:
|
||||
|
||||
*M-p*::
|
||||
Go up in history for this buffer (normally mapped to *C-p*).
|
||||
*M-n*::
|
||||
Go down in history for this buffer (normally mapped to *C-n*).
|
||||
*C-p*, *F5*: *previous-buffer*::
|
||||
*C-p*, *F5*, *M-Up*: *previous-buffer*::
|
||||
Switch to the previous buffer in order.
|
||||
*C-n*, *F6*: *next-buffer*::
|
||||
*C-n*, *F6*, *M-Down*: *next-buffer*::
|
||||
Switch to the next buffer in order.
|
||||
*M-TAB*: *switch-buffer*::
|
||||
*M-/*, *M-TAB*: *switch-buffer*::
|
||||
Switch to the last buffer, i.e., the one you were in before.
|
||||
*M-0*, *M-1*, ..., *M-9*: *goto-buffer*::
|
||||
Go to the N-th buffer (normally sets a repeat counter).
|
||||
Since there is no buffer number zero, *M-0* goes to the tenth one.
|
||||
*M-!*: *goto-highlight*::
|
||||
Go to the first following buffer with an unseen highlight.
|
||||
*M-a*: *goto-activity*::
|
||||
Go to the first following buffer with unseen activity.
|
||||
*M-A*: *goto-backtivity*::
|
||||
Go to the first following/preceding buffer with an unseen highlight,
|
||||
or, if there are none, with unseen activity.
|
||||
*PageUp*: *display-backlog*::
|
||||
Show the in-memory backlog for this buffer using *general.pager*,
|
||||
which is almost certainly the *less*(1) program.
|
||||
@@ -83,13 +82,220 @@ their respective function names:
|
||||
Should there be any issues with the display, this will clear the terminal
|
||||
screen and redraw all information.
|
||||
|
||||
Additionally, *C-w* and *C-u* in editline behave the same as they would in
|
||||
Readline or the "vi" command mode, even though the "emacs" mode is enabled
|
||||
by default.
|
||||
Custom bindings
|
||||
---------------
|
||||
Edit the configuration file manually. For example, to make buffer reordering
|
||||
functions accessible:
|
||||
|
||||
Bindings can be customized in your _.inputrc_ or _.editrc_ file. Both libraries
|
||||
support conditional execution based on the program name. Beware that it is easy
|
||||
to make breaking changes.
|
||||
....
|
||||
bindings = {
|
||||
"M-Left" = "move-buffer-left"
|
||||
"M-Right" = "move-buffer-right"
|
||||
}
|
||||
....
|
||||
|
||||
You may bind any function you see in the */bindings* listing, though beware that
|
||||
you can easily break the program if you're not careful.
|
||||
|
||||
Lua plugin API
|
||||
--------------
|
||||
The API closely reflects the internal structure of the client.
|
||||
For code examples, please consult preinstalled plugins.
|
||||
|
||||
All references to internal objects are weak, and may become invalid.
|
||||
|
||||
Methods named like __:get_PROPERTY__ can also be accessed as __.PROPERTY__.
|
||||
|
||||
As a rule, properties are read-only, and strings are encoded in UTF-8.
|
||||
|
||||
*xC.buffers* ({Buffer, ...})::
|
||||
All buffers in order.
|
||||
*xC.current_buffer* (Buffer)::
|
||||
The active buffer.
|
||||
*xC.global_buffer* (Buffer)::
|
||||
The global buffer.
|
||||
*xC.servers* ({[string = Server, ...]})::
|
||||
Configured servers, indexed by their name.
|
||||
|
||||
*xC.get_screen_size*() -> integer, integer::
|
||||
Return the number of terminal lines and columns.
|
||||
*xC.hook_completion*(_callback_[, _priority_]) -> Hook::
|
||||
Register a generator _callback_ for Tab completions: +
|
||||
Hook, {buffer, line, words, location}, string -> {[string, ...]}?
|
||||
+
|
||||
* *buffer* (Buffer): Where the completion occurs.
|
||||
+
|
||||
* *line* (string): The whole input line.
|
||||
+
|
||||
* *words* ({[string, ...]}): The input line split into words.
|
||||
+
|
||||
* *location* (integer): Which word is being completed, zero-based.
|
||||
|
||||
*xC.hook_input*(_callback_[, _priority_]) -> Hook::
|
||||
Register a filter _callback_ for user-typed input: +
|
||||
Hook, Buffer, string -> string?
|
||||
*xC.hook_irc*(_callback_[, _priority_]) -> Hook::
|
||||
Register a filter _callback_ for incoming raw IRC messages: +
|
||||
Hook, Server, string -> string?
|
||||
*xC.hook_prompt*(_callback_[, _priority_]) -> Hook::
|
||||
Register a generator _callback_ for the TUI input line prompt: +
|
||||
Hook -> string?
|
||||
+
|
||||
So as to not break the display, escape any terminal control sequences
|
||||
by enclosing them between \x01 and \x02.
|
||||
*xC.measure*(_string_) -> integer::
|
||||
Determine the width of a _string_ in terms of terminal cells.
|
||||
*xC.parse*(_message_) -> {tags, prefix, command, params}::
|
||||
Parse a raw IRC _message_ into its components:
|
||||
+
|
||||
* *tags* ({[string = string, ...]})
|
||||
+
|
||||
* *prefix* (string?)
|
||||
+
|
||||
* *command* (string?)
|
||||
+
|
||||
* *params* ({[string, ...]})
|
||||
|
||||
*xC.setup_config*(_options_)::
|
||||
Declare persistent plugin configuration under _plugins.NAME_, with the name
|
||||
derived from the script's basename without any extensions.
|
||||
The _options_ index configuration items by name, as further tables with:
|
||||
+
|
||||
* *type* (string): The configuration type of the value: one of _null_,
|
||||
_object_, _boolean_, _integer_, _string_, __string_array__.
|
||||
+
|
||||
* *comment* (string?): The description of the value.
|
||||
+
|
||||
* *default* (string?): The default value, expressed in terms of
|
||||
the configuration language. Items with a default may not become _null_.
|
||||
+
|
||||
* *on_change* (function?): Called when the value is initialized,
|
||||
first read, or later changed.
|
||||
+
|
||||
* *validate* (function?): Called to validate values after they pass a type
|
||||
check, returning _true_ if a change should be allowed.
|
||||
|
||||
Async Library
|
||||
~~~~~~~~~~~~~
|
||||
Within this concurency model, use Lua's *coroutine.yield* like *await*.
|
||||
|
||||
*xC.async.dial*(_host_, _service_) -> WaitChannel::
|
||||
Connect to the server at _host_, port _service_. Make sure
|
||||
to set the appropriate callbacks on the returned Connection object.
|
||||
*xC.async.go*(_fn_, ...) -> Task::
|
||||
Run _fn_ as a coroutine. These coroutines may only yield WaitChannels.
|
||||
When yielding multiple wait channel values, the operation will return
|
||||
the index of the completed channel, followed by all its return values packed
|
||||
into a single table.
|
||||
*xC.async.timer_ms*(_milliseconds_) -> WaitChannel::
|
||||
Resume the calling task after the given number of _milliseconds_.
|
||||
|
||||
Buffer
|
||||
~~~~~~
|
||||
*.channel* (Channel?)::
|
||||
The associated channel, if any.
|
||||
*.hide_unimportant* (boolean)::
|
||||
Whether unimportant messages are hidden in the user interface.
|
||||
*.highlighted* (boolean)::
|
||||
Whether we've been highlighted since last seeing the buffer.
|
||||
*.name* (string)::
|
||||
The full name of the buffer.
|
||||
*.new_messages_count* (integer)::
|
||||
The number of new messages since last seeing the buffer.
|
||||
*.new_unimportant_count* (integer)::
|
||||
How many of those messages are unimportant.
|
||||
*.server* (Server?)::
|
||||
The associated server, if any.
|
||||
*.user* (User?)::
|
||||
The associated user, if any.
|
||||
|
||||
*:execute*(_input_)::
|
||||
Process a line of _input_ within the buffer, as if the user typed it in.
|
||||
*:log*(_message_)::
|
||||
Log a status _message_.
|
||||
|
||||
Channel
|
||||
~~~~~~~
|
||||
*.left_manually* (boolean)::
|
||||
Whether we've left the channel manually, not to rejoin on reconnect.
|
||||
*.name* (string)::
|
||||
The channel's literal name, which may not be in UTF-8.
|
||||
*.no_param_modes* (string)::
|
||||
Active no-parameter channel modes.
|
||||
*.param_modes* ({[string = string, ...]})::
|
||||
Active parametrized channel modes.
|
||||
*.topic* (string)::
|
||||
The channel topic, which may not be in UTF-8.
|
||||
This string includes any in-line formatting.
|
||||
*.users_len* (integer)::
|
||||
The number of users on the channel.
|
||||
|
||||
*:get_users*() -> {[{prefixes = string, user = User}, ...]}::
|
||||
Return all users on the channel, together with their prefixes.
|
||||
|
||||
Connection
|
||||
~~~~~~~~~~
|
||||
*.on_data*(_data_)::
|
||||
Called whenever _data_ is received from the server.
|
||||
*.on_eof*()::
|
||||
Called when the connection is closed by the server.
|
||||
*.on_error*(_error_)::
|
||||
Called when the connection sees an _error_.
|
||||
|
||||
*:close*()::
|
||||
Close the connection.
|
||||
*:send*(_data_)::
|
||||
Send a string to the server.
|
||||
|
||||
Hook
|
||||
~~~~
|
||||
*:unhook*()::
|
||||
Forget about the hook.
|
||||
|
||||
Server
|
||||
~~~~~~
|
||||
*.autoaway_active* (boolean)::
|
||||
Whether we've currently automatically marked ourselves away.
|
||||
*.buffer* (Buffer)::
|
||||
The server's buffer.
|
||||
*.cap_echo_message* (boolean)::
|
||||
Whether the echo-message capability is active.
|
||||
*.irc_user_host* (string)::
|
||||
Our own user's user@host as we know it, used for message splitting.
|
||||
*.manual_disconnect* (boolean)::
|
||||
A flag to avoid reconnecting automatically after a manual disconnect request
|
||||
causes a connection interrupt.
|
||||
*.name* (string)::
|
||||
The name of the server.
|
||||
*.reconnect_attempt* (integer)::
|
||||
Attempt counter while establishing a connection.
|
||||
//*.state* (integer)::
|
||||
// The connection state as an integer constant. Overriden.
|
||||
*.user* (User?)::
|
||||
Our own user, once we're registered.
|
||||
*.user_mode* (string)::
|
||||
Our own user's modes.
|
||||
|
||||
*:get_state*() -> string::
|
||||
The connection state: one of _disconnected_, _connecting_, _connected_,
|
||||
_registered_, _closing_, _half-closed_.
|
||||
*:send*(_message_)::
|
||||
Send an IRC message verbatim, after appending newline characters.
|
||||
|
||||
Task
|
||||
~~~~
|
||||
*:cancel*()::
|
||||
Cancel the task, interrupting any *yield*.
|
||||
|
||||
User
|
||||
~~~~
|
||||
*.away* (boolean)::
|
||||
Whether the user is marked away.
|
||||
*.nickname* (string)::
|
||||
The user's literal nickname, which may not be in UTF-8.
|
||||
|
||||
*:get_channels*() -> {[Channel, ...]}::
|
||||
Return all channels the user shares with us.
|
||||
|
||||
Environment
|
||||
-----------
|
||||
@@ -113,10 +319,6 @@ _/usr/local/share/xC/plugins/_::
|
||||
_/usr/share/xC/plugins/_::
|
||||
Plugins are searched for in these directories, in order.
|
||||
|
||||
Bugs
|
||||
----
|
||||
The editline (libedit) frontend may exhibit some unexpected behaviour.
|
||||
|
||||
Reporting bugs
|
||||
--------------
|
||||
Use https://git.janouch.name/p/xK to report bugs, request features,
|
||||
@@ -124,4 +326,4 @@ or submit pull requests.
|
||||
|
||||
See also
|
||||
--------
|
||||
*less*(1), *readline*(3) or *editline*(7)
|
||||
*less*(1), *xF*(1)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Backwards-compatible protocol version.
|
||||
const VERSION = 2;
|
||||
const VERSION = 3;
|
||||
|
||||
// From the frontend to the relay.
|
||||
// All commands receive either an Event.RESPONSE, or an Event.ERROR.
|
||||
@@ -13,16 +13,20 @@ struct CommandMessage {
|
||||
BUFFER_ACTIVATE,
|
||||
BUFFER_INPUT,
|
||||
BUFFER_TOGGLE_UNIMPORTANT,
|
||||
PING_RESPONSE,
|
||||
PING,
|
||||
PING_RESPONSE,
|
||||
BUFFER_COMPLETE,
|
||||
BUFFER_LOG,
|
||||
} command) {
|
||||
// If the version check succeeds, the client will receive
|
||||
// an initial stream of SERVER_UPDATE, BUFFER_UPDATE, BUFFER_STATS,
|
||||
// BUFFER_LINE, and finally a BUFFER_ACTIVATE message.
|
||||
// When a session is resumed, the RESPONSE is preceded by an event log
|
||||
// replay starting at event_seq.
|
||||
case HELLO:
|
||||
u32 version;
|
||||
// Empty to start a new session, otherwise the session to resume.
|
||||
u8 session_id<>;
|
||||
// The event_seq to continue from;
|
||||
// a new session is started when that point is no longer available.
|
||||
u32 event_seq;
|
||||
case ACTIVE:
|
||||
void;
|
||||
case BUFFER_ACTIVATE:
|
||||
@@ -74,6 +78,12 @@ struct EventMessage {
|
||||
case RESPONSE:
|
||||
u32 command_seq;
|
||||
union ResponseData switch (Command command) {
|
||||
case HELLO:
|
||||
// The session to present on the next connection. If it differs
|
||||
// from the command, the session is new, and a full resync follows:
|
||||
// an initial stream of SERVER_UPDATE, BUFFER_UPDATE, BUFFER_STATS,
|
||||
// BUFFER_LINE, and finally a BUFFER_ACTIVATE message.
|
||||
u8 session_id<>;
|
||||
case BUFFER_COMPLETE:
|
||||
u32 start;
|
||||
string completions<>;
|
||||
|
||||
@@ -887,7 +887,14 @@ static void
|
||||
client_unregister (struct client *c, const char *reason)
|
||||
{
|
||||
if (!c->registered)
|
||||
{
|
||||
if (c->nickname)
|
||||
{
|
||||
str_map_set (&c->ctx->users, c->nickname, NULL);
|
||||
cstr_set (&c->nickname, NULL);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
char *message = xstrdup_printf (":%s!%s@%s QUIT :%s",
|
||||
c->nickname, c->username, c->hostname, reason);
|
||||
@@ -2630,7 +2637,11 @@ irc_try_kick (struct client *c, const char *channel_name, const char *nick,
|
||||
if (!(chan->modes & IRC_CHAN_MODE_QUIET))
|
||||
irc_channel_multicast (chan, message, NULL);
|
||||
else
|
||||
{
|
||||
client_send (c, "%s", message);
|
||||
if (c != client)
|
||||
client_send (client, "%s", message);
|
||||
}
|
||||
free (message);
|
||||
|
||||
channel_remove_user (chan, user);
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
xF(1)
|
||||
=====
|
||||
:doctype: manpage
|
||||
:manmanual: xK Manual
|
||||
:mansource: xK {release-version}
|
||||
|
||||
Name
|
||||
----
|
||||
xF - Terminal/X11 frontend for xC
|
||||
|
||||
Synopsis
|
||||
--------
|
||||
*xF* [_OPTION_]... [HOST:PORT]
|
||||
|
||||
Description
|
||||
-----------
|
||||
*xF* is a frontend for the relay interface of the *xC* IRC client.
|
||||
It normally runs in the terminal, and can also use a graphical interface
|
||||
when built with support for one.
|
||||
|
||||
When an explicit relay address is given, it takes priority over configuration.
|
||||
|
||||
Options
|
||||
-------
|
||||
*-g*, *--gui*::
|
||||
Use the graphical interface even when running from a terminal.
|
||||
|
||||
*-h*, *--help*::
|
||||
Display a help message and exit.
|
||||
|
||||
*-V*, *--version*::
|
||||
Output version information and exit.
|
||||
|
||||
*--write-default-cfg*[**=**__PATH__]::
|
||||
Write a configuration file with defaults, show its path and exit.
|
||||
+
|
||||
The file will be appropriately commented.
|
||||
|
||||
Key bindings
|
||||
------------
|
||||
As in *xC*(1), with a bunch of notable additions:
|
||||
|
||||
*C-q*::
|
||||
Quit the frontend.
|
||||
*C-F5*::
|
||||
Forcefully reconnect to the relay.
|
||||
*C-o*::
|
||||
Open the last link in the transcript using *xdg-open*(1).
|
||||
*M-E*::
|
||||
Run an external editor on transcript contents, enabling, e.g., search.
|
||||
*F1*, *F2*, *M-F1*, *M-F2*::
|
||||
Scroll the buffer list: up, down, top, bottom.
|
||||
*PageUp*, *PageUp*, *M-Home*, *M-End*, *C-Home*, *C-End*::
|
||||
Scroll the transcript: up, down, top, bottom, top, bottom.
|
||||
|
||||
Reporting bugs
|
||||
--------------
|
||||
Use https://git.janouch.name/p/xK to report bugs, request features,
|
||||
or submit pull requests.
|
||||
|
||||
See also
|
||||
--------
|
||||
*xC*(1), *xdg-open*(1)
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2.2.0
|
||||
2.4.0
|
||||
|
||||
+24
-13
@@ -697,7 +697,8 @@ func refreshBuffer(b: Buffer) {
|
||||
// --- Event processing --------------------------------------------------------
|
||||
|
||||
relayRPC.onConnected = {
|
||||
let hello = RelayCommandDataHello(version: UInt32(relayVersion))
|
||||
let hello = RelayCommandDataHello(
|
||||
version: UInt32(relayVersion), sessionId: [], eventSeq: 0)
|
||||
relayRPC.send(data: hello)
|
||||
}
|
||||
|
||||
@@ -1209,7 +1210,7 @@ class WindowDelegate: NSObject, NSWindowDelegate {
|
||||
refreshBufferList()
|
||||
}
|
||||
|
||||
// Buffer indexes rotated to start after the current buffer.
|
||||
// Buffers rotated to start after the current buffer.
|
||||
func rotatedBuffers() -> Array<Buffer> {
|
||||
guard let i = relayBuffers.firstIndex(
|
||||
where: { $0.bufferName == relayBufferCurrent }) else {
|
||||
@@ -1238,22 +1239,32 @@ class WindowDelegate: NSObject, NSWindowDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
@objc func actionGotoHighlight() {
|
||||
for b in rotatedBuffers() {
|
||||
func gotoActivity(backwards: Bool) {
|
||||
var candidates = rotatedBuffers()
|
||||
candidates.popLast()
|
||||
if backwards {
|
||||
candidates.reverse()
|
||||
}
|
||||
for b in candidates {
|
||||
if b.highlighted {
|
||||
bufferActivate(name: b.bufferName)
|
||||
return
|
||||
}
|
||||
}
|
||||
for b in candidates {
|
||||
if b.newMessages != 0 {
|
||||
bufferActivate(name: b.bufferName)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func actionGotoActivity() {
|
||||
for b in rotatedBuffers() {
|
||||
if b.newMessages != 0 {
|
||||
bufferActivate(name: b.bufferName)
|
||||
return
|
||||
}
|
||||
}
|
||||
gotoActivity(backwards: false)
|
||||
}
|
||||
|
||||
@objc func actionGotoBacktivity() {
|
||||
gotoActivity(backwards: true)
|
||||
}
|
||||
|
||||
@objc func actionToggleUnimportant() {
|
||||
@@ -1313,12 +1324,12 @@ pushAccelerator("Switch buffer",
|
||||
|
||||
// TODO(p): Remove .command, and ignore these with the right Option key.
|
||||
bufferMenu.addItem(NSMenuItem.separator())
|
||||
pushAccelerator("Go to highlight",
|
||||
#selector(WindowDelegate.actionGotoHighlight),
|
||||
"!", [.command, .option])
|
||||
pushAccelerator("Go to activity",
|
||||
#selector(WindowDelegate.actionGotoActivity),
|
||||
"a", [.command, .option])
|
||||
pushAccelerator("Go to activity backwards",
|
||||
#selector(WindowDelegate.actionGotoBacktivity),
|
||||
"a", [.shift, .command, .option])
|
||||
|
||||
bufferMenu.addItem(NSMenuItem.separator())
|
||||
pushAccelerator("Toggle unimportant",
|
||||
|
||||
+105
-44
@@ -9,14 +9,40 @@ class RelayRPC extends EventTarget {
|
||||
super()
|
||||
this.url = url
|
||||
this.commandSeq = 0
|
||||
this.promised = {}
|
||||
|
||||
// Session to resume after a lost connection, and our progress in it.
|
||||
this.sessionId = ''
|
||||
this.lastEventSeq = 0
|
||||
this.connecting = false
|
||||
}
|
||||
|
||||
// FIXME: The m.redraws are contamination for properties
|
||||
// that should have an event: this.connecting, this.busy
|
||||
|
||||
// When we can't resume our session, a 'reset' event will ask for the model
|
||||
// to be reinitialized.
|
||||
connect() {
|
||||
// We can't close the connection immediately, as that queues a task.
|
||||
if (this.connecting)
|
||||
return
|
||||
this.connecting = true
|
||||
m.redraw()
|
||||
|
||||
// We can't reopen the socket immediately, as closing queues a task.
|
||||
if (this.ws === undefined)
|
||||
this._connect()
|
||||
else {
|
||||
this.ws.addEventListener('close',
|
||||
() => this._connect(), {once: true})
|
||||
this.ws.close()
|
||||
}
|
||||
}
|
||||
|
||||
_connect() {
|
||||
if (this.ws !== undefined)
|
||||
throw "Already connecting or connected"
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
new Promise((resolve, reject) => {
|
||||
let ws = this.ws = new WebSocket(this.url)
|
||||
ws.onopen = event => {
|
||||
this._initialize()
|
||||
@@ -27,6 +53,20 @@ class RelayRPC extends EventTarget {
|
||||
this.ws = undefined
|
||||
reject()
|
||||
}
|
||||
}).then(() => {
|
||||
return this.send({
|
||||
command: 'Hello',
|
||||
version: Relay.version,
|
||||
sessionId: this.sessionId,
|
||||
// Continue right after the last event we have processed.
|
||||
eventSeq: this.sessionId ? (this.lastEventSeq + 1) >>> 0 : 0,
|
||||
})
|
||||
}).catch(error => {
|
||||
// Failures manifest as a closed connection.
|
||||
console.error(error)
|
||||
}).finally(() => {
|
||||
this.connecting = false
|
||||
m.redraw()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -42,8 +82,6 @@ class RelayRPC extends EventTarget {
|
||||
this.ws.onclose = event => {
|
||||
let message = "Connection closed: " +
|
||||
event.reason + " (" + event.code + ")"
|
||||
for (const seq in this.promised)
|
||||
this.promised[seq].reject(message)
|
||||
|
||||
this.ws = undefined
|
||||
this.dispatchEvent(new CustomEvent('close', {
|
||||
@@ -52,8 +90,6 @@ class RelayRPC extends EventTarget {
|
||||
|
||||
// Now connect() can be called again.
|
||||
}
|
||||
|
||||
this.promised = {}
|
||||
}
|
||||
|
||||
_process(data) {
|
||||
@@ -66,6 +102,8 @@ class RelayRPC extends EventTarget {
|
||||
}
|
||||
|
||||
_processOne(message) {
|
||||
this.lastEventSeq = message.eventSeq
|
||||
|
||||
let e = message.data
|
||||
let p
|
||||
switch (e.event) {
|
||||
@@ -76,6 +114,9 @@ class RelayRPC extends EventTarget {
|
||||
p.reject(e.error)
|
||||
break
|
||||
case Relay.Event.Response:
|
||||
if (e.data.command === Relay.Command.Hello)
|
||||
this._adoptSession(base64Encode(e.data.sessionId), e.commandSeq)
|
||||
|
||||
if ((p = this.promised[e.commandSeq]) === undefined)
|
||||
console.error("Unawaited response")
|
||||
else if (p !== true)
|
||||
@@ -90,15 +131,33 @@ class RelayRPC extends EventTarget {
|
||||
delete this.promised[e.commandSeq]
|
||||
for (const seq in this.promised) {
|
||||
// We don't particularly care about wraparound issues.
|
||||
if (seq >= e.commandSeq)
|
||||
continue
|
||||
|
||||
this.promised[seq].reject("No response")
|
||||
delete this.promised[seq]
|
||||
if (seq < e.commandSeq)
|
||||
this._abandon(seq, "No response")
|
||||
}
|
||||
m.redraw()
|
||||
}
|
||||
|
||||
_adoptSession(sessionId, seq) {
|
||||
if (sessionId === this.sessionId)
|
||||
return
|
||||
|
||||
// Nothing else can be answered from within the new session.
|
||||
for (const pending in this.promised)
|
||||
if (pending != seq)
|
||||
this._abandon(pending, "Session lost")
|
||||
|
||||
this.sessionId = sessionId
|
||||
this.dispatchEvent(new CustomEvent('reset'))
|
||||
}
|
||||
|
||||
// Stop waiting for a reply that is never going to arrive.
|
||||
_abandon(seq, message) {
|
||||
const p = this.promised[seq]
|
||||
if (p !== true)
|
||||
p.reject(message)
|
||||
delete this.promised[seq]
|
||||
}
|
||||
|
||||
get busy() {
|
||||
for (const seq in this.promised)
|
||||
return true
|
||||
@@ -138,6 +197,8 @@ class RelayRPC extends EventTarget {
|
||||
function utf8Encode(s) { return new TextEncoder().encode(s) }
|
||||
function utf8Decode(s) { return new TextDecoder().decode(s) }
|
||||
|
||||
function base64Encode(bytes) { return btoa(String.fromCharCode(...bytes)) }
|
||||
|
||||
function hasShortcutModifiers(event) {
|
||||
return (event.altKey || event.escapePrefix) &&
|
||||
!event.metaKey && !event.ctrlKey
|
||||
@@ -202,6 +263,16 @@ let bufferAutoscroll = true
|
||||
|
||||
let servers = new Map()
|
||||
|
||||
rpc.addEventListener('reset', event => {
|
||||
buffers.clear()
|
||||
bufferLast = undefined
|
||||
bufferCurrent = undefined
|
||||
bufferLog = undefined
|
||||
bufferAutoscroll = true
|
||||
|
||||
servers.clear()
|
||||
})
|
||||
|
||||
let lastActive = undefined
|
||||
|
||||
function notifyActive() {
|
||||
@@ -259,23 +330,7 @@ function bufferToggleLog() {
|
||||
})
|
||||
}
|
||||
|
||||
let connecting = true
|
||||
rpc.connect().then(result => {
|
||||
buffers.clear()
|
||||
bufferLast = undefined
|
||||
bufferCurrent = undefined
|
||||
bufferLog = undefined
|
||||
bufferAutoscroll = true
|
||||
|
||||
servers.clear()
|
||||
|
||||
rpc.send({command: 'Hello', version: Relay.version})
|
||||
connecting = false
|
||||
m.redraw()
|
||||
}).catch(error => {
|
||||
connecting = false
|
||||
m.redraw()
|
||||
})
|
||||
rpc.connect()
|
||||
|
||||
rpc.addEventListener('close', event => {
|
||||
m.redraw()
|
||||
@@ -1115,7 +1170,7 @@ let Input = {
|
||||
let Main = {
|
||||
view: vnode => {
|
||||
let overlay = undefined
|
||||
if (connecting)
|
||||
if (rpc.connecting)
|
||||
overlay = m('.overlay', {}, "Connecting...")
|
||||
else if (rpc.ws === undefined)
|
||||
overlay = m('.overlay', {}, [
|
||||
@@ -1126,11 +1181,12 @@ let Main = {
|
||||
return m('.xP', {}, [
|
||||
overlay,
|
||||
m('.title', {}, [
|
||||
m('span', [
|
||||
m('.toolbar', [
|
||||
// Midline Horizontal Ellipsis, No-Break Space
|
||||
m('span', {class: rpc.busy ? undefined : 'invisible'},
|
||||
`\u22EF\u00A0`),
|
||||
m('b', {}, `xP`),
|
||||
m('button', {onclick: () => rpc.connect()}, '⟳'),
|
||||
]),
|
||||
m(Topic),
|
||||
]),
|
||||
@@ -1178,10 +1234,22 @@ document.addEventListener('keydown', event => {
|
||||
return
|
||||
|
||||
// Rotate names so that the current buffer comes first.
|
||||
let names = [...buffers.keys()]
|
||||
const names = [...buffers.keys()]
|
||||
names.push.apply(names,
|
||||
names.splice(0, names.findIndex(name => name == bufferCurrent)))
|
||||
|
||||
const gotoActivity = backwards => {
|
||||
const candidates = names.slice(1)
|
||||
if (backwards)
|
||||
candidates.reverse()
|
||||
for (const name of candidates)
|
||||
if (buffers.get(name).highlighted)
|
||||
return bufferActivate(name)
|
||||
for (const name of candidates)
|
||||
if (buffers.get(name).newMessages)
|
||||
return bufferActivate(name)
|
||||
}
|
||||
|
||||
switch (event.key) {
|
||||
case 'h':
|
||||
bufferToggleLog()
|
||||
@@ -1191,28 +1259,21 @@ document.addEventListener('keydown', event => {
|
||||
bufferToggleUnimportant(bufferCurrent)
|
||||
break
|
||||
case 'a':
|
||||
for (const name of names.slice(1))
|
||||
if (buffers.get(name).newMessages) {
|
||||
bufferActivate(name)
|
||||
break
|
||||
}
|
||||
gotoActivity(false)
|
||||
break
|
||||
case '!':
|
||||
for (const name of names.slice(1))
|
||||
if (buffers.get(name).highlighted) {
|
||||
bufferActivate(name)
|
||||
break
|
||||
}
|
||||
case 'A':
|
||||
gotoActivity(true)
|
||||
break
|
||||
case 'Tab':
|
||||
case '/':
|
||||
if (bufferLast !== undefined)
|
||||
bufferActivate(bufferLast)
|
||||
break
|
||||
case 'PageUp':
|
||||
case 'ArrowUp':
|
||||
if (names.length > 1)
|
||||
bufferActivate(names.at(-1))
|
||||
break
|
||||
case 'PageDown':
|
||||
case 'ArrowDown':
|
||||
if (names.length > 1)
|
||||
bufferActivate(names.at(+1))
|
||||
break
|
||||
|
||||
@@ -160,7 +160,7 @@ func clientWriteError(ctx context.Context, ws *websocket.Conn, err error) bool {
|
||||
},
|
||||
},
|
||||
}).AppendTo(nil)
|
||||
if ok {
|
||||
if !ok {
|
||||
log.Println("Event serialization failed")
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -910,6 +910,10 @@ func (c *client) nicknameOrStar() string {
|
||||
|
||||
func (c *client) unregister(reason string) {
|
||||
if !c.registered {
|
||||
if c.nickname != "" {
|
||||
delete(users, ircToCanon(c.nickname))
|
||||
c.nickname = ""
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1126,7 +1130,9 @@ func (c *client) sendLUSERS() {
|
||||
|
||||
nChannels := 0
|
||||
for _, ch := range channels {
|
||||
if 0 != ch.modes&ircChanModeSecret {
|
||||
if 0 == ch.modes&ircChanModeSecret {
|
||||
nChannels++
|
||||
} else if _, present := ch.userModes[c]; present {
|
||||
nChannels++
|
||||
}
|
||||
}
|
||||
@@ -1440,7 +1446,7 @@ func ircHandleUSER(msg *message, c *client) {
|
||||
c.realname = realname
|
||||
|
||||
c.mode = 0
|
||||
if m, err := strconv.ParseUint(mode, 10, 32); err != nil {
|
||||
if m, err := strconv.ParseUint(mode, 10, 32); err == nil {
|
||||
if 0 != m&4 {
|
||||
c.mode |= ircUserModeRxWallops
|
||||
}
|
||||
@@ -1494,7 +1500,7 @@ func ircHandleLUSERS(msg *message, c *client) {
|
||||
|
||||
func ircHandleMOTD(msg *message, c *client) {
|
||||
if len(msg.params) > 0 && !ircIsThisMe(msg.params[0]) {
|
||||
c.sendReply(ERR_NOSUCHSERVER, msg.params[1])
|
||||
c.sendReply(ERR_NOSUCHSERVER, msg.params[0])
|
||||
return
|
||||
}
|
||||
c.sendMOTD()
|
||||
@@ -1868,9 +1874,9 @@ func (mp *modeProcessor) step(modeChar byte) bool {
|
||||
case 'b':
|
||||
mp.doList(&mp.ch.banList, RPL_BANLIST, RPL_ENDOFBANLIST)
|
||||
case 'e':
|
||||
mp.doList(&mp.ch.banList, RPL_EXCEPTLIST, RPL_ENDOFEXCEPTLIST)
|
||||
mp.doList(&mp.ch.exceptionList, RPL_EXCEPTLIST, RPL_ENDOFEXCEPTLIST)
|
||||
case 'I':
|
||||
mp.doList(&mp.ch.banList, RPL_INVITELIST, RPL_ENDOFINVITELIST)
|
||||
mp.doList(&mp.ch.inviteList, RPL_INVITELIST, RPL_ENDOFINVITELIST)
|
||||
|
||||
case 'k':
|
||||
mp.doKey()
|
||||
@@ -2373,7 +2379,7 @@ func ircHandleTOPIC(msg *message, c *client) {
|
||||
}
|
||||
|
||||
ch.topic = msg.params[1]
|
||||
ch.topicWho = fmt.Sprintf("%s@%s@%s", c.nickname, c.username, c.hostname)
|
||||
ch.topicWho = fmt.Sprintf("%s!%s@%s", c.nickname, c.username, c.hostname)
|
||||
ch.topicTime = time.Now()
|
||||
|
||||
message := fmt.Sprintf(":%s!%s@%s TOPIC %s :%s",
|
||||
@@ -2397,7 +2403,7 @@ func ircTryPart(c *client, channelName string, reason string) {
|
||||
return
|
||||
}
|
||||
|
||||
message := fmt.Sprintf(":%s@%s@%s PART %s :%s",
|
||||
message := fmt.Sprintf(":%s!%s@%s PART %s :%s",
|
||||
c.nickname, c.username, c.hostname, channelName, reason)
|
||||
if 0 == ch.modes&ircChanModeQuiet {
|
||||
ircChannelMulticast(ch, message, nil)
|
||||
@@ -2454,12 +2460,15 @@ func ircTryKick(c *client, channelName, nick, reason string) {
|
||||
return
|
||||
}
|
||||
|
||||
message := fmt.Sprintf(":%s@%s@%s KICK %s %s :%s",
|
||||
message := fmt.Sprintf(":%s!%s@%s KICK %s %s :%s",
|
||||
c.nickname, c.username, c.hostname, channelName, nick, reason)
|
||||
if 0 == ch.modes&ircChanModeQuiet {
|
||||
ircChannelMulticast(ch, message, nil)
|
||||
} else {
|
||||
c.send(message)
|
||||
if c != client {
|
||||
client.send(message)
|
||||
}
|
||||
}
|
||||
|
||||
delete(ch.userModes, client)
|
||||
@@ -2485,7 +2494,7 @@ func ircHandleKICK(msg *message, c *client) {
|
||||
ircTryKick(c, targetChannels[0], targetUsers[i], reason)
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < len(channels) && i < len(targetUsers); i++ {
|
||||
for i := 0; i < len(targetChannels) && i < len(targetUsers); i++ {
|
||||
ircTryKick(c, targetChannels[i], targetUsers[i], reason)
|
||||
}
|
||||
}
|
||||
@@ -2560,7 +2569,7 @@ func ircTryJoin(c *client, channelName, key string) {
|
||||
}
|
||||
|
||||
_, invitedByChanop := c.invites[ircToCanon(channelName)]
|
||||
if 0 != ch.modes&ircChanModeInviteOnly && c.inMaskList(ch.inviteList) &&
|
||||
if 0 != ch.modes&ircChanModeInviteOnly && !c.inMaskList(ch.inviteList) &&
|
||||
!invitedByChanop {
|
||||
c.sendReply(ERR_INVITEONLYCHAN, channelName)
|
||||
return
|
||||
@@ -3113,6 +3122,7 @@ func processOneEvent() {
|
||||
capVersion: 301,
|
||||
opened: time.Now(),
|
||||
lastActive: time.Now(),
|
||||
invites: map[string]bool{},
|
||||
// TODO: Make this configurable and more fine-grained.
|
||||
antiflood: newFloodDetector(10*time.Second, 20),
|
||||
}
|
||||
|
||||
+38
-4
@@ -48,7 +48,32 @@ foreach (icon xT xT-highlighted)
|
||||
list (APPEND icon_rsrc_list "${icon_png}")
|
||||
endforeach ()
|
||||
|
||||
if (APPLE)
|
||||
function (android_mipmap_icon svg density size output_dir output)
|
||||
set (_mipmap_dir "${output_dir}/res/mipmap-${density}")
|
||||
set (_png "${_mipmap_dir}/ic_launcher.png")
|
||||
set (${output} "${_png}" PARENT_SCOPE)
|
||||
|
||||
find_program (rsvg_convert_EXECUTABLE rsvg-convert REQUIRED)
|
||||
add_custom_command (OUTPUT "${_png}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${_mipmap_dir}"
|
||||
COMMAND ${rsvg_convert_EXECUTABLE} "--output=${_png}"
|
||||
"--width=${size}" "--height=${size}" -- "${svg}"
|
||||
DEPENDS "${svg}"
|
||||
COMMENT "Generating Android ${density} application icon" VERBATIM)
|
||||
endfunction ()
|
||||
|
||||
if (ANDROID)
|
||||
set (android_base "${PROJECT_BINARY_DIR}/android")
|
||||
set (android_icon_list)
|
||||
foreach (density_size
|
||||
ldpi:36 mdpi:48 hdpi:72 xhdpi:96 xxhdpi:144 xxxhdpi:192)
|
||||
string (REPLACE ":" ";" density_size "${density_size}")
|
||||
android_mipmap_icon ("${PROJECT_SOURCE_DIR}/xT.svg"
|
||||
${density_size} "${android_base}" android_icon)
|
||||
list (APPEND android_icon_list "${android_icon}")
|
||||
endforeach ()
|
||||
add_custom_target (android-icons ALL DEPENDS ${android_icon_list})
|
||||
elseif (APPLE)
|
||||
set (MACOSX_BUNDLE_ICON_FILE xT.icns)
|
||||
icon_to_icns ("${PROJECT_SOURCE_DIR}/xT.svg"
|
||||
"${MACOSX_BUNDLE_ICON_FILE}" icon_icns)
|
||||
@@ -79,7 +104,7 @@ else ()
|
||||
endif ()
|
||||
|
||||
# Build the main executable and link it
|
||||
find_program (awk_EXECUTABLE awk ${find_program_REQUIRE})
|
||||
find_program (awk_EXECUTABLE awk REQUIRED)
|
||||
add_custom_command (OUTPUT xC-proto.cpp
|
||||
COMMAND ${CMAKE_COMMAND} -E env LC_ALL=C ${awk_EXECUTABLE}
|
||||
-f ${root}/liberty/tools/lxdrgen.awk
|
||||
@@ -99,8 +124,17 @@ qt_add_executable (xT
|
||||
add_dependencies (xT xC-proto)
|
||||
qt_add_resources (xT "rsrc" PREFIX / FILES "${beep}" ${icon_rsrc_list})
|
||||
target_link_libraries (xT PRIVATE Qt6::Widgets Qt6::Network Qt6::Multimedia)
|
||||
set_target_properties (xT PROPERTIES WIN32_EXECUTABLE ON MACOSX_BUNDLE ON
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER name.janouch.xT)
|
||||
set_target_properties (xT PROPERTIES
|
||||
WIN32_EXECUTABLE ON
|
||||
MACOSX_BUNDLE ON
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER name.janouch.xT
|
||||
QT_ANDROID_APP_NAME xT
|
||||
QT_ANDROID_APP_ICON "@mipmap/ic_launcher"
|
||||
QT_ANDROID_PACKAGE_NAME name.janouch.xT
|
||||
QT_ANDROID_PACKAGE_SOURCE_DIR "${android_base}")
|
||||
if (ANDROID)
|
||||
add_dependencies (xT android-icons)
|
||||
endif ()
|
||||
|
||||
# https://stackoverflow.com/questions/79079161 and resolved in Qt Creator 16.
|
||||
set (QT_QML_GENERATE_QMLLS_INI ON)
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "xC-proto.cpp"
|
||||
#include "config.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
@@ -1120,9 +1121,12 @@ relay_process_message(const Relay::EventMessage &m)
|
||||
{
|
||||
auto &data = dynamic_cast<Relay::EventData_ServerRename &>(*m.data);
|
||||
auto original = QString::fromStdWString(data.server_name);
|
||||
g.servers.insert_or_assign(
|
||||
QString::fromStdWString(data.new_), g.servers.at(original));
|
||||
auto new_ = QString::fromStdWString(data.new_);
|
||||
g.servers.insert_or_assign(new_, g.servers.at(original));
|
||||
g.servers.erase(original);
|
||||
for (auto &b : g.buffers)
|
||||
if (b.server_name == original)
|
||||
b.server_name = new_;
|
||||
break;
|
||||
}
|
||||
case Relay::Event::SERVER_REMOVE:
|
||||
@@ -1619,6 +1623,22 @@ rotated_buffers()
|
||||
return rotated;
|
||||
}
|
||||
|
||||
static void
|
||||
goto_activity_bidirectional(bool backwards)
|
||||
{
|
||||
auto candidates = rotated_buffers();
|
||||
if (!candidates.empty())
|
||||
candidates.pop_back();
|
||||
if (backwards)
|
||||
std::reverse(candidates.begin(), candidates.end());
|
||||
for (auto i : candidates)
|
||||
if (auto &b = g.buffers[i]; b.highlighted)
|
||||
return buffer_activate(b.buffer_name);
|
||||
for (auto i : candidates)
|
||||
if (auto &b = g.buffers[i]; b.new_messages)
|
||||
return buffer_activate(b.buffer_name);
|
||||
}
|
||||
|
||||
static void
|
||||
bind_shortcuts()
|
||||
{
|
||||
@@ -1638,47 +1658,42 @@ bind_shortcuts()
|
||||
if (auto b = buffer_by_name(g.buffer_last))
|
||||
buffer_activate(b->buffer_name);
|
||||
};
|
||||
auto goto_highlight = [] {
|
||||
for (auto i : rotated_buffers())
|
||||
if (g.buffers[i].highlighted) {
|
||||
buffer_activate(g.buffers[i].buffer_name);
|
||||
break;
|
||||
}
|
||||
};
|
||||
auto goto_activity = [] {
|
||||
for (auto i : rotated_buffers())
|
||||
if (g.buffers[i].new_messages) {
|
||||
buffer_activate(g.buffers[i].buffer_name);
|
||||
break;
|
||||
}
|
||||
};
|
||||
auto toggle_unimportant = [] {
|
||||
if (auto b = buffer_by_name(g.buffer_current))
|
||||
buffer_toggle_unimportant(b->buffer_name);
|
||||
};
|
||||
auto goto_activity = [] {
|
||||
goto_activity_bidirectional(false);
|
||||
};
|
||||
auto goto_backtivity = [] {
|
||||
goto_activity_bidirectional(true);
|
||||
};
|
||||
|
||||
new QShortcut(QKeyCombination(Qt::ControlModifier, Qt::Key_Tab),
|
||||
g.wMain, switch_buffer);
|
||||
new QShortcut(QKeyCombination(Qt::AltModifier, Qt::Key_Tab),
|
||||
g.wMain, switch_buffer);
|
||||
new QShortcut(QKeyCombination(Qt::AltModifier, Qt::Key_Slash),
|
||||
g.wMain, switch_buffer);
|
||||
|
||||
new QShortcut(QKeyCombination(Qt::NoModifier, Qt::Key_F5),
|
||||
g.wMain, previous_buffer);
|
||||
new QShortcut(QKeyCombination(Qt::AltModifier, Qt::Key_PageUp),
|
||||
new QShortcut(QKeyCombination(Qt::AltModifier, Qt::Key_Up),
|
||||
g.wMain, previous_buffer);
|
||||
new QShortcut(QKeyCombination(Qt::ControlModifier, Qt::Key_PageUp),
|
||||
g.wMain, previous_buffer);
|
||||
new QShortcut(QKeyCombination(Qt::NoModifier, Qt::Key_F6),
|
||||
g.wMain, next_buffer);
|
||||
new QShortcut(QKeyCombination(Qt::AltModifier, Qt::Key_PageDown),
|
||||
new QShortcut(QKeyCombination(Qt::AltModifier, Qt::Key_Down),
|
||||
g.wMain, next_buffer);
|
||||
new QShortcut(QKeyCombination(Qt::ControlModifier, Qt::Key_PageDown),
|
||||
g.wMain, next_buffer);
|
||||
|
||||
new QShortcut(QKeyCombination(Qt::AltModifier, Qt::Key_A),
|
||||
g.wMain, goto_activity);
|
||||
new QShortcut(QKeyCombination(Qt::AltModifier, Qt::Key_Exclam),
|
||||
g.wMain, goto_highlight);
|
||||
new QShortcut(
|
||||
QKeyCombination(Qt::AltModifier | Qt::ShiftModifier, Qt::Key_A),
|
||||
g.wMain, goto_backtivity);
|
||||
new QShortcut(QKeyCombination(Qt::AltModifier, Qt::Key_H),
|
||||
g.wMain, toggle_unimportant);
|
||||
}
|
||||
|
||||
+2
-2
@@ -7,8 +7,8 @@
|
||||
#define ID_PREVIOUS_BUFFER 11
|
||||
#define ID_NEXT_BUFFER 12
|
||||
#define ID_SWITCH_BUFFER 13
|
||||
#define ID_GOTO_HIGHLIGHT 14
|
||||
#define ID_GOTO_ACTIVITY 15
|
||||
#define ID_GOTO_ACTIVITY 14
|
||||
#define ID_GOTO_BACKTIVITY 15
|
||||
#define ID_TOGGLE_UNIMPORTANT 16
|
||||
#define ID_DISPLAY_FULL_LOG 17
|
||||
|
||||
|
||||
@@ -741,7 +741,7 @@ refresh_buffer(const Buffer &b)
|
||||
// PFM_BORDER is not implemented, at most we can try to construct
|
||||
// an OLE object the width of the screen and see how it clips
|
||||
// (this is a lot of code).
|
||||
size_t i = 0, mark_before = b.lines.size() -
|
||||
int64_t i = 0, mark_before = int64_t(b.lines.size()) -
|
||||
b.new_messages - b.new_unimportant_messages;
|
||||
for (auto line = b.lines.begin(); line != b.lines.end(); ++line) {
|
||||
if (i == mark_before)
|
||||
@@ -1057,6 +1057,9 @@ relay_process_message(const Relay::EventMessage &m)
|
||||
auto &data = dynamic_cast<Relay::EventData_ServerRename &>(*m.data);
|
||||
g.servers.insert_or_assign(data.new_, g.servers.at(data.server_name));
|
||||
g.servers.erase(data.server_name);
|
||||
for (auto &b : g.buffers)
|
||||
if (b.server_name == data.server_name)
|
||||
b.server_name = data.new_;
|
||||
break;
|
||||
}
|
||||
case Relay::Event::SERVER_REMOVE:
|
||||
@@ -1612,6 +1615,21 @@ process_bufferlist_notification(WORD code)
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
goto_activity(std::vector<size_t> candidates, bool backwards)
|
||||
{
|
||||
if (!candidates.empty())
|
||||
candidates.pop_back();
|
||||
if (backwards)
|
||||
std::reverse(candidates.begin(), candidates.end());
|
||||
for (auto i : candidates)
|
||||
if (auto &b = g.buffers[i]; b.highlighted)
|
||||
return buffer_activate(b.buffer_name);
|
||||
for (auto i : candidates)
|
||||
if (auto &b = g.buffers[i]; b.new_messages)
|
||||
return buffer_activate(b.buffer_name);
|
||||
}
|
||||
|
||||
static void
|
||||
process_accelerator(WORD id)
|
||||
{
|
||||
@@ -1642,19 +1660,11 @@ process_accelerator(WORD id)
|
||||
if (!g.buffer_last.empty())
|
||||
buffer_activate(g.buffer_last);
|
||||
return;
|
||||
case ID_GOTO_HIGHLIGHT:
|
||||
for (auto i : rotated)
|
||||
if (g.buffers[i].highlighted) {
|
||||
buffer_activate(g.buffers[i].buffer_name);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
case ID_GOTO_ACTIVITY:
|
||||
for (auto i : rotated)
|
||||
if (g.buffers[i].new_messages) {
|
||||
buffer_activate(g.buffers[i].buffer_name);
|
||||
break;
|
||||
}
|
||||
goto_activity(rotated, false);
|
||||
return;
|
||||
case ID_GOTO_BACKTIVITY:
|
||||
goto_activity(rotated, true);
|
||||
return;
|
||||
case ID_TOGGLE_UNIMPORTANT:
|
||||
if (b)
|
||||
|
||||
@@ -25,8 +25,8 @@ BEGIN
|
||||
VK_TAB, ID_SWITCH_BUFFER, CONTROL, VIRTKEY
|
||||
// These are proper, but llvm-rc won't accept them (GitHub #64002).
|
||||
#ifndef __clang__
|
||||
"!", ID_GOTO_HIGHLIGHT, ALT
|
||||
"a", ID_GOTO_ACTIVITY, ALT
|
||||
"A", ID_GOTO_BACKTIVITY, ALT
|
||||
"H", ID_TOGGLE_UNIMPORTANT, ALT
|
||||
"h", ID_DISPLAY_FULL_LOG, ALT
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user