Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
64d38296ad
|
|||
|
8d16434945
|
|||
|
baf5ff0d25
|
|||
|
9472f960fd
|
|||
|
7a21781343
|
|||
|
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
|
+48
-13
@@ -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 ()
|
||||
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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) \
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2.3.0
|
||||
2.4.0
|
||||
|
||||
+22
-12
@@ -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
@@ -70,7 +70,7 @@ body {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.toolbar, .title div {
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-right: -.3em;
|
||||
|
||||
+20
-15
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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