54 Commits

Author SHA1 Message Date
p 64d38296ad xC: improve buffer rename handling
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
Differences in case only weren't handled well.
2026-09-14 20:16:05 +02:00
p 8d16434945 Bump liberty 2026-09-14 20:16:05 +02:00
p baf5ff0d25 xA: bump Fyne
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
2.8.0 failed build on a 404, so trying to simply download 2.8.1.
2026-09-11 19:57:31 +02:00
p 9472f960fd Bump liberty
Alpine 3.24 Scripts failed
Arch Linux AUR Success
OpenBSD 7.8 Success
2026-09-11 19:36:54 +02:00
p 7a21781343 xF: make the open action skip hidden lines 2026-09-11 19:36:54 +02:00
p b0e9d60ff7 Bump version, update NEWS
Arch Linux AUR Success
OpenBSD 7.8 Success
Alpine 3.24 Success
2026-09-04 15:26:50 +02:00
p 95076ff6ab Bump liberty
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
2026-09-04 15:00:26 +02:00
p f26ab854c9 xF: tiniest AppKit steps
It's not quite possible to port the editor, so not finishing this.
2026-09-02 13:01:53 +02:00
p 6e84d0c48b Bump liberty
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
2026-08-31 12:12:39 +02:00
p 1b0e46d33a xF: optionally colour manage X11
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
Bump liberty.
2026-08-29 07:16:03 +02:00
p ae6519687a xF: state true shortcut origin 2026-08-29 05:10:44 +02:00
p 0caab02933 xF: cleanup
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
2026-08-11 11:03:00 +02:00
p 074b378f03 xC/fancy-prompt.lua: remove old Readline hacks
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
2026-08-11 10:25:59 +02:00
p 6a5533f783 Update NEWS
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
2026-08-11 07:53:09 +02:00
p bb48e185d0 xF: support pasting from clipboard
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
Bump liberty.  Most of the change happens in liberty-xui.
2026-08-11 05:33:17 +02:00
p 83128dc7bb xF: bind C-t to transpose characters
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
Bump liberty.
2026-08-10 13:10:47 +02:00
p 780e9c35bf xF: improve layouting
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
2026-08-10 10:42:30 +02:00
p 3aaf9de5f2 xF: improve the X11 IM experience
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
Bump liberty.  We're not yet achieving what we want to.
2026-08-10 10:13:23 +02:00
p a1c7d2c983 xC/xF: make it possible to insert newlines directly
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
That should do for us to stop claiming that the interface needs polish.
2026-08-10 08:22:33 +02:00
p 63d54df66a xC: add a basic C-t binding 2026-08-10 08:02:29 +02:00
p 959fb15828 xF: play highlight sounds through libcanberra
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
2026-08-10 07:30:38 +02:00
p 36037cb3e5 Update documentation
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
2026-08-10 06:28:25 +02:00
p 800a4e354c xF: push huge logs directly to external editors
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
At around 10 megabytes, processing starts taking quite long.

It might be a good idea to remove the log view altogether now;
it's only there because we copied other frontends.
2026-08-10 04:41:40 +02:00
p 9f1ef95f46 xF: menu autocomplete
I'm not sure if this is simpler or more complex than listing candidates.

xC already has its old libedit hack, so I suppose I'm not going to
bother with it.
2026-08-10 04:36:27 +02:00
p 064102b786 xF: simplify action code
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
2026-08-09 15:20:50 +02:00
p 621f76c797 xC: enable relay binding on multiple addresses
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
2026-08-09 15:04:09 +02:00
p 2d1df86a46 xC + frontends: merge M-! into M-a + reversed M-A
Both WeeChat and irssi merge these two by prioritising highlights
when you press M-a.
2026-08-09 14:58:11 +02:00
p 6ba94ba664 xF: don't call into Xlib when running as a TUI 2026-08-09 14:58:07 +02:00
p f1170bc161 xF: maintain an index of buffer by name
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
Imperceptibly improves performance, shortens source code.
2026-08-08 15:43:01 +02:00
p dbb47ec2ee Adjust key bindings
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
Now they should actually be more friendly to WeeChat users.
2026-08-08 14:13:10 +02:00
p c413f6d4b2 xF: add a binding to open the last link
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
Better than nothing.
2026-08-08 08:58:22 +02:00
p 8ce60cdd2f xF: add bindings to scroll to the top and bottom 2026-08-08 08:22:53 +02:00
p d9af9c1057 xA: mildly improve the experience
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
Fyne got a bit faster at layouting, but showing the log still kills it.
2026-08-08 06:56:07 +02:00
p e816ddb32a xF: adjust layouting to be more like nncmpp
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
2026-08-08 06:08:20 +02:00
p 57e2163bac xF: simplify variable naming 2026-08-08 06:08:20 +02:00
p 60e40f4fe2 Bump liberty
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
2026-08-08 04:16:55 +02:00
p 7c76e60656 xF: eliminate two compiler warnings 2026-08-08 03:20:47 +02:00
p 6fe5f7de4c Update NEWS 2026-08-08 03:02:18 +02:00
p 8e2e7be56f xF: add basic configuration
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
2026-08-08 01:47:21 +02:00
p 3275cd1fa2 xF: reuse a function 2026-08-08 01:47:21 +02:00
p 087091577c xF: deduplicate transcript logic
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
After the last commit it got a bit ridiculous.
2026-08-07 15:17:37 +02:00
p f6a41d7c51 xF: add date change marker
Also rename and reorganise attributes to match xC better.
2026-08-07 15:16:00 +02:00
p 7538dfad2d xF: make highlights request attention from the WM
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
2026-08-07 12:02:26 +02:00
p 2d8867f6fa Clean up the build around xF
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
And make sure it gets built at all, if possible.

It's actually pretty good now, reaching feature parity with xP and xC.
2026-08-07 11:12:55 +02:00
p 4f5d4cb64b xF: make sure the caret is visible in X11 editor
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
2026-08-07 10:30:58 +02:00
p 1704f90934 xF: close buffers on middle click 2026-08-07 09:05:06 +02:00
p 41be32692c xF: enable running external editors on input
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
And also on the transcript.

This is an easy way of resolving text selection,
and the mediocre input editor.
2026-08-07 08:33:23 +02:00
p 65a98dfd7c xF: make xC nick colors just work
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
Colour mapping still suffers, just not as heavily.
2026-08-07 01:26:17 +02:00
p ce848bff0f xF: add word wrapping where it matters the most
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
Plus some necessary cleanup.

This is a bit simplistic in that it is very inefficient.
It runs fast enough, though.
2026-08-06 01:44:31 +02:00
p 6859e9abc9 xF: improve caret visibility 2026-08-06 01:41:17 +02:00
p 82f19cc144 Update README.adoc
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
2026-08-03 02:41:09 +02:00
p e4ce659174 xF: add link detection and opening 2026-08-03 02:41:08 +02:00
p 8f3df19786 xF: add a scrollbar to the buffer list
And clean this area up.
2026-08-03 02:41:08 +02:00
p f8ecf7d0f7 xP: fix channel title layout
Alpine 3.24 Success
Arch Linux AUR Success
OpenBSD 7.8 Success
2026-08-02 19:39:44 +02:00
23 changed files with 2464 additions and 932 deletions
+48 -13
View File
@@ -8,9 +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 (WITH_XF "Build the xF frontend" OFF)
# Moar warnings
set (CMAKE_C_STANDARD 99)
set (CMAKE_C_STANDARD_REQUIRED ON)
@@ -138,8 +135,45 @@ else ()
message (SEND_ERROR "Curses not found")
endif ()
# Dependencies for xF
find_package (Unistring)
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_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)
@@ -171,8 +205,8 @@ add_custom_command (OUTPUT xC-proto.c
add_custom_target (xC-proto DEPENDS ${PROJECT_BINARY_DIR}/xC-proto.c)
# Build
set (project_binaries xB xC xD)
foreach (name xB xC xD)
list (APPEND project_binaries ${name})
add_executable (${name} ${name}.c ${project_config})
target_link_libraries (${name} PRIVATE ${project_libraries})
add_threads (${name})
@@ -186,14 +220,6 @@ target_include_directories (xC PRIVATE ${Termo_INCLUDE_DIRS})
if (WITH_XF)
list (APPEND project_binaries xF)
find_package (Unistring REQUIRED)
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 ()
add_executable (xF xF.c ${project_config})
add_dependencies (xF xC-proto)
target_include_directories (xF PRIVATE
@@ -202,11 +228,20 @@ if (WITH_XF)
${Unistring_LIBRARIES} ${Termo_LIBRARIES}
${ncursesw_LIBRARIES} ${project_libraries})
if (WITH_X11)
target_compile_definitions (xF PRIVATE 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 ()
+25 -2
View File
@@ -1,3 +1,26 @@
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
@@ -279,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
@@ -382,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
+22 -12
View File
@@ -11,9 +11,7 @@ They're all lean on dependencies, and offer a maximally permissive licence.
xC
--
The IRC client, and the core of 'xK'. Its interface should feel somewhat
familiar for weechat or irssi users. Currently the terminal interface
experience deserves some polishing, because we've just got rid of
Readline/Editline dependencies, which kept breaking in exciting ways.
familiar for WeeChat or irssi users.
image::xC.webp[align="center"]
@@ -34,10 +32,18 @@ including link:xC.adoc#_key_bindings[keyboard shortcuts].
image::xP.webp[align="center"]
xA, xF, xT, xW, xM
------------------
Fyne, terminal/X11, Qt Widgets, Win32, Cocoa frontends for 'xC'.
Using them is not recommended.
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
--------------
Experimental Fyne, Qt Widgets, Win32, Cocoa frontends for 'xC'.
xD
--
@@ -89,15 +95,15 @@ Building
Build-only dependencies: CMake, pkg-config, awk, liberty (included),
asciidoctor or asciidoc (recommended but optional), rsvg-convert (for 'xF') +
Common runtime dependencies: openssl +
Additionally for 'xC': curses, lua >= 5.3 (optional), termo (included) +
Additionally for 'xC': any curses, lua >= 5.3 (optional), termo (included) +
Additionally for 'xF': ncursesw, libunistring, termo (included),
x11 + xft + libpng (X11)
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 \
-DWITH_LUA=ON -DWITH_XF=OFF
-DWITH_LUA=ON -DWITH_XF=ON
$ make
To install the application, you can do either the usual:
@@ -118,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.
@@ -132,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,
+30
View File
@@ -31,6 +31,7 @@
#include "liberty/liberty.c"
#include <arpa/inet.h>
#include <math.h>
#include <netinet/tcp.h>
static void
@@ -108,6 +109,35 @@ 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) \
+3
View File
@@ -7,6 +7,9 @@
#define PROJECT_DATADIR "${CMAKE_INSTALL_FULL_DATADIR}"
#cmakedefine HAVE_LUA
#cmakedefine HAVE_X11
#cmakedefine HAVE_LCMS2
#cmakedefine HAVE_CANBERRA
#cmakedefine01 ICONV_ACCEPTS_TRANSLIT
+1 -1
Submodule liberty updated: 12063aecde...c6a8d65bbc
+5 -25
View File
@@ -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 -1
View File
@@ -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;
+2 -3
View File
@@ -3,19 +3,18 @@ module janouch.name/xK/xA
go 1.26
require (
fyne.io/fyne/v2 v2.8.0
fyne.io/fyne/v2 v2.8.1
github.com/ebitengine/oto/v3 v3.4.0
)
require (
fyne.io/systray v1.12.2 // indirect
fyne.io/systray v1.12.3-0.20260810170012-af4e8e793ec4 // 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.10.1 // indirect
github.com/fredbi/uri v1.1.1 // 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
+4 -12
View File
@@ -1,7 +1,7 @@
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=
fyne.io/fyne/v2 v2.8.1 h1:EztGuE2W3Qhd0cWVmU+h5rkzNezUD1To6UqsoLQYUIM=
fyne.io/fyne/v2 v2.8.1/go.mod h1:kpeuFrClm0fiAgJYr2soTfwKMT5rzNcSKzmgGjxvHOY=
fyne.io/systray v1.12.3-0.20260810170012-af4e8e793ec4 h1:149/+Wa5EsLLXfyj2pdTmvnQf2VIlgCIwSjcCTHYhIo=
fyne.io/systray v1.12.3-0.20260810170012-af4e8e793ec4/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/FyshOS/fancyfs v0.0.1 h1:kgvm7VvwOMLkYTqSflplp62SlMVWQ2uAoHw9CXwXHYg=
@@ -16,10 +16,6 @@ github.com/ebitengine/oto/v3 v3.4.0 h1:br0PgASsEWaoWn38b2Goe7m1GKFYfNgnsjSd5Gg+/
github.com/ebitengine/oto/v3 v3.4.0/go.mod h1:IOleLVD0m+CMak3mRVwsYY8vTctQgOM0iiL6S7Ar7eI=
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.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=
@@ -42,8 +38,6 @@ github.com/go-text/typesetting-utils v0.0.0-20260223113751-2d88ac90dae3 h1:drBZz
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=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
github.com/hack-pad/go-indexeddb v0.3.2 h1:DTqeJJYc1usa45Q5r52t01KhvlSN02+Oq+tQbSBI91A=
github.com/hack-pad/go-indexeddb v0.3.2/go.mod h1:QvfTevpDVlkfomY498LhstjwbPW6QC4VC/lxYb0Kom0=
github.com/hack-pad/safejs v0.1.1 h1:d5qPO0iQ7h2oVtpzGnLExE+Wn9AtytxIfltcS2b9KD8=
@@ -62,8 +56,6 @@ github.com/nicksnyder/go-i18n/v2 v2.6.1 h1:JDEJraFsQE17Dut9HFDHzCoAWGEQJom5s0TRd
github.com/nicksnyder/go-i18n/v2 v2.6.1/go.mod h1:Vee0/9RD3Quc/NmwEjzzD7VTZ+Ir7QbXocrkhOzmUKA=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rymdport/portal v0.4.2 h1:7jKRSemwlTyVHHrTGgQg7gmNPJs88xkbKcIL3NlcmSU=
+80 -53
View File
@@ -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)
}
}
@@ -1138,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()
@@ -1304,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
@@ -1427,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 {
@@ -1443,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,
+6 -6
View File
@@ -47,19 +47,19 @@ This is a list of all local specialties and their respective function names:
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.
+140 -120
View File
@@ -56,7 +56,6 @@ enum
#include "xD-replies.c"
#include "xC-proto.c"
#include <math.h>
#include <langinfo.h>
#include <locale.h>
#include <pwd.h>
@@ -315,7 +314,7 @@ input__redisplay (struct input *self)
// Also preferrably avoid using input__str_tputs(),
// though this requires continuous transcoding to the locale encoding.
struct str prompt = str_make ();
bool passthrough = false;
bool passthrough = false, clear_line = false;
struct input_pos pos = { .y = 0, .x = 0 };
const char *p = self->prompt;
@@ -328,11 +327,17 @@ input__redisplay (struct input *self)
passthrough = true;
else if (ch == INPUT_END_IGNORE)
passthrough = false;
else if (passthrough)
str_append_data (&prompt, p, ch_len);
else
{
if (passthrough)
;
else if (ch == '\n')
// Let any passthrough sequences reset formatting first,
// so that this newline doesn't cause an unnecessary BCE on scroll.
if (clear_line)
str_append_c (&prompt, '\n');
clear_line = false;
if (ch == '\n')
{
pos.y++;
pos.x = 0;
@@ -341,11 +346,13 @@ input__redisplay (struct input *self)
else
// FIXME: Either convert from Unicode to wchar_t here,
// or implement the algorithm ourselves.
input__adjust (&pos, wcwidth (ch));
clear_line = input__adjust (&pos, wcwidth (ch));
str_append_data (&prompt, p, ch_len);
}
p += ch_len;
}
if (clear_line)
str_append_c (&prompt, '\n');
if (self->y)
putp (tparm (parm_up_cursor, self->y, 0, 0, 0, 0, 0, 0, 0, 0));
@@ -816,6 +823,23 @@ INPUT_FN (kill_line)
return true;
}
INPUT_FN (transpose_chars)
// TODO(p): Better Unicode processing.
if (buffer->point < 1 || buffer->len < 2)
return false;
if (buffer->point == (int) buffer->len)
buffer->point--;
uint32_t cp = buffer->line[buffer->point];
int width = buffer->w [buffer->point];
buffer->line[buffer->point] = buffer->line[buffer->point - 1];
buffer->w [buffer->point] = buffer->w [buffer->point - 1];
buffer->line[buffer->point - 1] = cp;
buffer->w [buffer->point - 1] = width;
buffer->point++;
return true;
}
INPUT_FN (upcase_word)
// TODO(p): Better Unicode processing.
int i = buffer->point;
@@ -919,10 +943,10 @@ g_input_actions[] =
(const char *[]) { "C-f", "Right", NULL } },
{ "backward-word", "Go back a word",
input_backward_word,
(const char *[]) { "M-b", NULL } },
(const char *[]) { "M-b", "M-Left", NULL } },
{ "forward-word", "Go forward a word",
input_forward_word,
(const char *[]) { "M-f", NULL } },
(const char *[]) { "M-f", "M-Right", NULL } },
{ "backward-delete-char", "Delete last character",
input_backward_delete_char,
@@ -939,6 +963,9 @@ g_input_actions[] =
{ "kill-line", "Delete everything up to EOL",
input_kill_line,
(const char *[]) { "C-k", NULL } },
{ "transpose-chars", "Transpose characters before and at the cursor",
input_transpose_chars,
(const char *[]) { "C-t", NULL } },
{ "upcase-word", "Convert word to uppercase",
input_upcase_word,
@@ -2062,7 +2089,7 @@ struct app_context
struct poller_fd tty_event; ///< Terminal input event
struct poller_fd signal_event; ///< Signal FD event
struct poller_fd relay_event; ///< New relay connection available
struct poller_fd *relay_events; ///< New relay connections available
struct poller_timer flush_timer; ///< Flush all open files (e.g. logs)
struct poller_timer date_chg_tmr; ///< Print a date change
@@ -2109,7 +2136,8 @@ struct app_context
// Relay:
int relay_fd; ///< Listening socket FD
int *relay_fds; ///< Listening socket FDs
size_t relay_len; ///< Number of listening sockets
struct client *clients; ///< Our relay clients
struct client *clients_tail; ///< End of the clients list
unsigned relay_resume_timeout; ///< Detached session TTL in seconds
@@ -2137,35 +2165,6 @@ static struct ispect_field g_ctx_ispect[] =
{}
};
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;
}
static bool
app_iconv_open (iconv_t *target, const char *to, const char *from)
{
@@ -2211,19 +2210,21 @@ app_context_init (struct app_context *self)
self->nick_palette =
filter_color_cube_for_acceptable_nick_colors (&self->nick_palette_len);
self->relay_fd = -1;
}
static void
app_context_relay_stop (struct app_context *self)
{
if (self->relay_fd != -1)
for (size_t i = 0; i < self->relay_len; i++)
{
poller_fd_reset (&self->relay_event);
xclose (self->relay_fd);
self->relay_fd = -1;
poller_fd_reset (&self->relay_events[i]);
xclose (self->relay_fds[i]);
}
free (self->relay_events);
free (self->relay_fds);
self->relay_events = NULL;
self->relay_fds = NULL;
self->relay_len = 0;
}
static void
@@ -2488,8 +2489,8 @@ static const struct config_schema g_config_general[] =
// Relay:
{ .name = "relay_bind",
.comment = "Address to bind to for a user interface relay point",
.type = CONFIG_ITEM_STRING,
.comment = "Addresses to bind to for user interface relay points",
.type = CONFIG_ITEM_STRING_ARRAY,
.validate = config_validate_nonjunk_string,
.on_change = on_config_relay_bind_change },
{ .name = "relay_resume_log_limit",
@@ -5078,11 +5079,11 @@ static void
buffer_rename (struct app_context *ctx,
struct buffer *buffer, const char *new_name)
{
struct buffer *collision = str_map_find (&ctx->buffers_by_name, new_name);
if (collision == buffer)
if (!strcmp (buffer->name, new_name))
return;
hard_assert (!collision);
struct buffer *collision = str_map_find (&ctx->buffers_by_name, new_name);
hard_assert (!collision || collision == buffer);
relay_prepare_buffer_rename (ctx, buffer, new_name);
relay_broadcast (ctx);
@@ -5135,23 +5136,20 @@ buffer_at_index (struct app_context *ctx, int n)
}
static struct buffer *
buffer_next (struct app_context *ctx, int count)
buffer_after (struct app_context *ctx, struct buffer *iter, bool backwards)
{
struct buffer *new_buffer = ctx->current_buffer;
while (count-- > 0)
if (!(new_buffer = new_buffer->next))
new_buffer = ctx->buffers;
return new_buffer;
return backwards
? (iter->prev ? iter->prev : ctx->buffers_tail)
: (iter->next ? iter->next : ctx->buffers);
}
static struct buffer *
buffer_previous (struct app_context *ctx, int count)
buffer_step (struct app_context *ctx, int count, bool backwards)
{
struct buffer *new_buffer = ctx->current_buffer;
struct buffer *iter = ctx->current_buffer;
while (count-- > 0)
if (!(new_buffer = new_buffer->prev))
new_buffer = ctx->buffers_tail;
return new_buffer;
iter = buffer_after (ctx, iter, backwards);
return iter;
}
static bool
@@ -5207,7 +5205,7 @@ buffer_remove_safe (struct app_context *ctx, struct buffer *buffer)
if (buffer == ctx->current_buffer)
buffer_activate (ctx, ctx->last_buffer
? ctx->last_buffer
: buffer_next (ctx, 1));
: buffer_step (ctx, 1, false));
buffer_remove (ctx, buffer);
}
@@ -5224,7 +5222,8 @@ init_global_buffer (struct app_context *ctx)
// --- Users, channels ---------------------------------------------------------
static char *
irc_make_buffer_name (struct server *s, const char *target)
irc_make_buffer_name (struct server *s, const char *target,
struct buffer *ignore)
{
if (!target)
return xstrdup (s->name);
@@ -5234,7 +5233,7 @@ irc_make_buffer_name (struct server *s, const char *target)
free (target_utf8);
struct buffer *conflict = buffer_by_name (s->ctx, name);
if (!conflict)
if (!conflict || conflict == ignore)
return name;
hard_assert (conflict->server == s);
@@ -5289,7 +5288,7 @@ irc_get_or_make_user_buffer (struct server *s, const char *nickname)
// Open a new buffer for the user
buffer = buffer_new (s->ctx->input,
BUFFER_PM, irc_make_buffer_name (s, nickname));
BUFFER_PM, irc_make_buffer_name (s, nickname, NULL));
buffer->server = s;
buffer->user = user;
str_map_set (&s->irc_buffer_map, user->nickname, buffer);
@@ -7560,7 +7559,7 @@ irc_handle_join (struct server *s, const struct irc_message *msg)
return;
buffer = buffer_new (s->ctx->input,
BUFFER_CHANNEL, irc_make_buffer_name (s, channel_name));
BUFFER_CHANNEL, irc_make_buffer_name (s, channel_name, NULL));
buffer->server = s;
buffer->channel = channel =
irc_make_channel (s, xstrdup (channel_name));
@@ -7718,7 +7717,7 @@ irc_handle_nick (struct server *s, const struct irc_message *msg)
bool lexicographically_different =
!!irc_server_strcmp (s, user->nickname, new_nickname);
// What the fuck, someone renamed themselves to ourselves
// Someone has renamed themselves to ourselves, a true fuck-up.
// TODO: probably log a message and force a reconnect
if (lexicographically_different
&& !irc_server_strcmp (s, new_nickname, s->irc_user->nickname))
@@ -7776,7 +7775,7 @@ irc_handle_nick (struct server *s, const struct irc_message *msg)
str_map_set (&s->irc_buffer_map, user->nickname, NULL);
str_map_set (&s->irc_buffer_map, new_nickname, pm_buffer);
char *x = irc_make_buffer_name (s, new_nickname);
char *x = irc_make_buffer_name (s, new_nickname, pm_buffer);
buffer_rename (s->ctx, pm_buffer, x);
free (x);
}
@@ -9324,11 +9323,14 @@ validate_server_name (const char *name)
}
static const char *
check_server_name_for_addition (struct app_context *ctx, const char *name)
check_server_name_for_addition (struct app_context *ctx, const char *name,
struct server *ignore)
{
if (!strcasecmp_ascii (name, ctx->global_buffer->name))
return "name collides with the global buffer";
if (str_map_find (&ctx->servers, name))
struct server *collision = str_map_find (&ctx->servers, name);
if (collision && collision != ignore)
return "server already exists";
if (!validate_server_name (name))
return "invalid server name";
@@ -9352,7 +9354,7 @@ server_add (struct app_context *ctx,
// Add a buffer and activate it
struct buffer *buffer = s->buffer = buffer_new (ctx->input,
BUFFER_SERVER, irc_make_buffer_name (s, NULL));
BUFFER_SERVER, irc_make_buffer_name (s, NULL, NULL));
buffer->server = s;
buffer_add (ctx, buffer);
@@ -9422,7 +9424,8 @@ server_remove (struct app_context *ctx, struct server *s)
static void
server_rename (struct app_context *ctx, struct server *s, const char *new_name)
{
hard_assert (!str_map_find (&ctx->servers, new_name));
struct server *collision = str_map_find (&ctx->servers, new_name);
hard_assert (!collision || collision == s);
relay_prepare_server_rename (ctx, s, new_name);
relay_broadcast (ctx);
@@ -9444,10 +9447,10 @@ server_rename (struct app_context *ctx, struct server *s, const char *new_name)
switch (buffer->type)
{
case BUFFER_PM:
x = irc_make_buffer_name (s, buffer->user->nickname);
x = irc_make_buffer_name (s, buffer->user->nickname, buffer);
break;
case BUFFER_CHANNEL:
x = irc_make_buffer_name (s, buffer->channel->name);
x = irc_make_buffer_name (s, buffer->channel->name, buffer);
break;
default:
hard_assert (!"unexpected type of server-related buffer");
@@ -12208,7 +12211,7 @@ handle_command_relay (struct handler_args *a)
LIST_FOR_EACH (struct client, c, a->ctx->clients)
len++;
if (a->ctx->relay_fd == -1)
if (!a->ctx->relay_len)
log_global_status (a->ctx, "The relay is not enabled");
else
log_global_status (a->ctx, "The relay has #d clients", len);
@@ -12754,7 +12757,7 @@ handle_server_add (struct handler_args *a)
struct app_context *ctx = a->ctx;
char *name = cut_word (&a->arguments);
const char *err;
if ((err = check_server_name_for_addition (ctx, name)))
if ((err = check_server_name_for_addition (ctx, name, NULL)))
log_global_error (ctx, "Cannot create server `#s': #s", name, err);
else
{
@@ -12800,7 +12803,7 @@ handle_server_rename (struct handler_args *a)
if (!(s = str_map_find (&ctx->servers, old_name)))
log_global_error (ctx, "/#s: #s: #s",
"server", "no such server", old_name);
else if ((err = check_server_name_for_addition (ctx, new_name)))
else if ((err = check_server_name_for_addition (ctx, new_name, s)))
log_global_error (ctx,
"Cannot rename server to `#s': #s", new_name, err);
else
@@ -14313,7 +14316,7 @@ static bool
on_previous_buffer (int key, void *user_data)
{
(void) key;
buffer_activate (user_data, buffer_previous (user_data, 1));
buffer_activate (user_data, buffer_step (user_data, 1, true));
return true;
}
@@ -14321,7 +14324,7 @@ static bool
on_next_buffer (int key, void *user_data)
{
(void) key;
buffer_activate (user_data, buffer_next (user_data, 1));
buffer_activate (user_data, buffer_step (user_data, 1, false));
return true;
}
@@ -14338,41 +14341,36 @@ on_switch_buffer (int key, void *user_data)
}
static bool
on_goto_highlight (int key, void *user_data)
goto_activity (struct app_context *ctx, bool backwards)
{
(void) key;
struct app_context *ctx = user_data;
struct buffer *iter = ctx->current_buffer;;
do
{
if (!(iter = iter->next))
iter = ctx->buffers;
if (iter == ctx->current_buffer)
return false;
}
while (!iter->highlighted);
buffer_activate (ctx, iter);
return true;
struct buffer *iter = ctx->current_buffer, *stop = iter;
while ((iter = buffer_after (ctx, iter, backwards)) != stop)
if (iter->highlighted)
{
buffer_activate (ctx, iter);
return true;
}
while ((iter = buffer_after (ctx, iter, backwards)) != stop)
if (iter->new_messages_count != iter->new_unimportant_count)
{
buffer_activate (ctx, iter);
return true;
}
return false;
}
static bool
on_goto_activity (int key, void *user_data)
{
(void) key;
return goto_activity (user_data, false);
}
struct app_context *ctx = user_data;
struct buffer *iter = ctx->current_buffer;
do
{
if (!(iter = iter->next))
iter = ctx->buffers;
if (iter == ctx->current_buffer)
return false;
}
while (iter->new_messages_count == iter->new_unimportant_count);
buffer_activate (ctx, iter);
return true;
static bool
on_goto_backtivity (int key, void *user_data)
{
(void) key;
return goto_activity (user_data, true);
}
static bool
@@ -14457,6 +14455,15 @@ on_insert_attribute (int key, void *user_data)
return true;
}
static bool
on_insert_newline (int key, void *user_data)
{
(void) key;
struct app_context *ctx = user_data;
return input_insert (ctx->input, "\n");
}
#define BRACKETED_PASTE_LIMIT 102400 ///< How much text can be pasted
static bool
@@ -14640,8 +14647,8 @@ app_input_init (struct input *self)
XX ("next-buffer", "Next buffer", on_next_buffer)
XX ("goto-buffer", "Go to buffer", on_goto_buffer)
XX ("switch-buffer", "Switch buffer", on_switch_buffer)
XX ("goto-highlight", "Go to highlight", on_goto_highlight)
XX ("goto-activity", "Go to activity", on_goto_activity)
XX ("goto-backtivity", "Go to activity", on_goto_backtivity)
XX ("move-buffer-left", "Move buffer left", on_move_buffer_left)
XX ("move-buffer-right", "Move buffer right", on_move_buffer_right)
XX ("display-backlog", "Show backlog", on_display_backlog)
@@ -14651,6 +14658,7 @@ app_input_init (struct input *self)
XX ("edit-input", "Edit input", on_edit_input)
XX ("redraw-screen", "Redraw screen", on_redraw_screen)
XX ("insert-attribute", "IRC formatting", on_insert_attribute)
XX ("insert-newline", "Insert newline", on_insert_newline)
XX ("start-paste-mode", "Bracketed paste", on_start_paste_mode)
XX ("accept-line", "Send line", on_input_return)
XX ("complete", "Complete word", on_input_complete)
@@ -14658,6 +14666,8 @@ app_input_init (struct input *self)
input_bind (self, "C-p", "previous-buffer");
input_bind (self, "C-n", "next-buffer");
input_bind (self, "M-Up", "previous-buffer");
input_bind (self, "M-Down", "next-buffer");
input_bind (self, "F5", "previous-buffer");
input_bind (self, "F6", "next-buffer");
@@ -14665,9 +14675,11 @@ app_input_init (struct input *self)
input_bind (self, (char[]) { 'M', '-', '0' + i }, "goto-buffer");
input_bind (self, "M-Tab", "switch-buffer");
input_bind (self, "M-!", "goto-highlight");
input_bind (self, "M-/", "switch-buffer");
input_bind (self, "M-a", "goto-activity");
input_bind (self, "M-A", "goto-backtivity");
input_bind (self, "M-m", "insert-attribute");
input_bind (self, "M-Enter", "insert-newline");
input_bind (self, "M-h", "display-full-log");
input_bind (self, "M-H", "toggle-unimportant");
input_bind (self, "M-e", "edit-input");
@@ -14802,7 +14814,7 @@ load_servers (struct app_context *ctx)
if (subtree->type != CONFIG_ITEM_OBJECT)
log_global_error (ctx, "Error in configuration: "
"ignoring server `#s' as it's not an object", name);
else if ((err = check_server_name_for_addition (ctx, name)))
else if ((err = check_server_name_for_addition (ctx, name, NULL)))
log_global_error (ctx, "Cannot load server `#s': #s", name, err);
else
server_add (ctx, name, subtree);
@@ -15729,12 +15741,12 @@ relay_start (struct app_context *ctx, char *address, struct error **e)
set_blocking (fd, false);
struct poller_fd *event = &ctx->relay_event;
struct poller_fd *event = &ctx->relay_events[ctx->relay_len];
*event = poller_fd_make (&ctx->poller, fd);
event->dispatcher = (poller_fd_fn) on_relay_client_available;
event->user_data = ctx;
ctx->relay_fd = fd;
ctx->relay_fds[ctx->relay_len++] = fd;
poller_fd_set (event, POLLIN);
return true;
}
@@ -15743,21 +15755,29 @@ static void
on_config_relay_bind_change (struct config_item *item)
{
struct app_context *ctx = item->user_data;
char *value = item->value.string.str;
app_context_relay_stop (ctx);
if (!value)
if (item->type == CONFIG_ITEM_NULL)
return;
// XXX: This should perhaps be reencoded as the locale encoding.
char *address = xstrdup (value);
struct strv addresses = strv_make ();
cstr_split (item->value.string.str, ",", true, &addresses);
struct error *e = NULL;
if (!relay_start (ctx, address, &e))
ctx->relay_fds = xcalloc (addresses.len, sizeof *ctx->relay_fds);
ctx->relay_events = xcalloc (addresses.len, sizeof *ctx->relay_events);
for (size_t i = 0; i < addresses.len; i++)
{
log_global_error (ctx, "#s: #l", item->schema->name, e->message);
error_free (e);
// XXX: This should perhaps be reencoded as the locale encoding.
char *address = xstrdup (addresses.vector[i]);
struct error *e = NULL;
if (!relay_start (ctx, address, &e))
{
log_global_error (ctx, "#s: address `#l': #l",
item->schema->name, addresses.vector[i], e->message);
error_free (e);
}
free (address);
}
free (address);
strv_free (&addresses);
}
static void
+27 -3
View File
@@ -10,14 +10,16 @@ xF - Terminal/X11 frontend for xC
Synopsis
--------
*xF* [_OPTION_]... RELAY-ADDRESS
*xF* [_OPTION_]... [HOST:PORT]
Description
-----------
*xF* is an example frontend for the relay interface of the *xC* IRC client.
*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*::
@@ -29,6 +31,28 @@ Options
*-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,
@@ -36,4 +60,4 @@ or submit pull requests.
See also
--------
*xC*(1)
*xC*(1), *xdg-open*(1)
+1974 -619
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1 +1 @@
2.3.0
2.4.0
+22 -12
View File
@@ -1210,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 {
@@ -1239,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() {
@@ -1314,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",
+1 -1
View File
@@ -70,7 +70,7 @@ body {
visibility: hidden;
}
.toolbar, .title div {
.toolbar {
display: flex;
align-items: baseline;
margin-right: -.3em;
+20 -15
View File
@@ -1181,7 +1181,7 @@ let Main = {
return m('.xP', {}, [
overlay,
m('.title', {}, [
m('div', [
m('.toolbar', [
// Midline Horizontal Ellipsis, No-Break Space
m('span', {class: rpc.busy ? undefined : 'invisible'},
`\u22EF\u00A0`),
@@ -1234,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()
@@ -1247,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
+30 -18
View File
@@ -19,6 +19,7 @@
#include "xC-proto.cpp"
#include "config.h"
#include <algorithm>
#include <cstdint>
#include <functional>
#include <map>
@@ -1622,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()
{
@@ -1641,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
View File
@@ -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
+19 -12
View File
@@ -1615,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)
{
@@ -1645,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)
+1 -1
View File
@@ -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