Compare commits
34 Commits
v2.0.0
...
4cf8c394b9
| Author | SHA1 | Date | |
|---|---|---|---|
|
4cf8c394b9
|
|||
|
e225306419
|
|||
|
858734384b
|
|||
|
278a7f95a9
|
|||
|
a8575ab875
|
|||
|
081525f5be
|
|||
|
6ac2ac5511
|
|||
|
f6483489c2
|
|||
|
ed5ac1815b
|
|||
|
509cb9f4dd
|
|||
|
b3684c4d9f
|
|||
|
918c589c65
|
|||
|
21095a11d6
|
|||
|
a22baa4b55
|
|||
|
b3e545e0bb
|
|||
|
cd76702ab2
|
|||
|
977b073b58
|
|||
|
46be4836df
|
|||
|
05a41b2629
|
|||
|
a62ed5bbac
|
|||
|
9c9776bacd
|
|||
|
086b879ab8
|
|||
|
214c349869
|
|||
|
3d975c9437
|
|||
|
fce8fd40cc
|
|||
|
3597ab9420
|
|||
|
1635a730e8
|
|||
|
a64b1152a1
|
|||
|
a011b57ce2
|
|||
|
b1ee295345
|
|||
|
872f2d7c59
|
|||
|
f15d887dcd
|
|||
|
841bc00c51
|
|||
|
8afe4f8aad
|
@@ -210,6 +210,11 @@ if (BUILD_TESTING)
|
|||||||
add_test (NAME custom-static-analysis
|
add_test (NAME custom-static-analysis
|
||||||
COMMAND ${PROJECT_SOURCE_DIR}/test-static)
|
COMMAND ${PROJECT_SOURCE_DIR}/test-static)
|
||||||
endif ()
|
endif ()
|
||||||
|
option (BUILD_TESTING_WDYE "Build the integration test" OFF)
|
||||||
|
if (BUILD_TESTING_WDYE)
|
||||||
|
add_subdirectory (liberty/tools/wdye)
|
||||||
|
add_test (NAME integration COMMAND wdye "${PROJECT_SOURCE_DIR}/test.lua")
|
||||||
|
endif ()
|
||||||
|
|
||||||
# Various clang-based diagnostics, loads of fake positives and spam
|
# Various clang-based diagnostics, loads of fake positives and spam
|
||||||
file (GLOB clang_tidy_sources *.c)
|
file (GLOB clang_tidy_sources *.c)
|
||||||
|
|||||||
17
NEWS
17
NEWS
@@ -1,3 +1,18 @@
|
|||||||
|
2.1.0 (2024-12-19) "Bunnyrific"
|
||||||
|
|
||||||
|
* xC: fixed a crash when the channel topic had too many formatting items
|
||||||
|
|
||||||
|
* xC: fixed keyboard EOF behaviour with Readline >= 8.0
|
||||||
|
|
||||||
|
* xC: made it possible to stream commands into the binary
|
||||||
|
|
||||||
|
* xM/xW: various bugfixes
|
||||||
|
|
||||||
|
* Added a Fyne frontend for xC called xA
|
||||||
|
|
||||||
|
* Added a Qt Widgets frontend for xC called xT
|
||||||
|
|
||||||
|
|
||||||
2.0.0 (2024-07-28) "Perfect Is the Enemy of Good"
|
2.0.0 (2024-07-28) "Perfect Is the Enemy of Good"
|
||||||
|
|
||||||
* xD: now using SHA-256 for client certificate fingerprints
|
* xD: now using SHA-256 for client certificate fingerprints
|
||||||
@@ -16,7 +31,7 @@
|
|||||||
|
|
||||||
* xC: replaced behaviour.save_on_quit with general.autosave
|
* xC: replaced behaviour.save_on_quit with general.autosave
|
||||||
|
|
||||||
* xC: the server *.command configuration option now supports multiple lines
|
* xC: the servers.*.command configuration option now supports multiple lines
|
||||||
|
|
||||||
* xC: improved pager integration capabilities
|
* xC: improved pager integration capabilities
|
||||||
|
|
||||||
|
|||||||
23
README.adoc
23
README.adoc
@@ -2,9 +2,9 @@ xK
|
|||||||
==
|
==
|
||||||
|
|
||||||
'xK' (chat kit) is an IRC software suite consisting of a daemon, bot, notifier,
|
'xK' (chat kit) is an IRC software suite consisting of a daemon, bot, notifier,
|
||||||
terminal client, and web/Windows/macOS frontends for the client. It's all
|
terminal client, and web/Windows/macOS/Linux/FreeBSD/Android/iOS frontends
|
||||||
you're ever going to need for chatting, so long as you can make do with slightly
|
for the client. It's all you're ever going to need for chatting, so long as
|
||||||
minimalist software.
|
you can make do with slightly minimalist software.
|
||||||
|
|
||||||
They're all lean on dependencies, and offer a maximally permissive licence.
|
They're all lean on dependencies, and offer a maximally permissive licence.
|
||||||
|
|
||||||
@@ -33,6 +33,11 @@ including link:xC.adoc#_key_bindings[keyboard shortcuts].
|
|||||||
|
|
||||||
image::xP.webp[align="center"]
|
image::xP.webp[align="center"]
|
||||||
|
|
||||||
|
xA, xT, xW, xM
|
||||||
|
--------------
|
||||||
|
Fyne, Qt Widgets, Win32, Cocoa frontends for 'xC'.
|
||||||
|
Using them is not recommended.
|
||||||
|
|
||||||
xD
|
xD
|
||||||
--
|
--
|
||||||
The IRC daemon. It is designed for use as a regular user application rather
|
The IRC daemon. It is designed for use as a regular user application rather
|
||||||
@@ -142,6 +147,18 @@ For remote use, it's recommended to put 'xP' behind a reverse proxy, with TLS,
|
|||||||
and some form of HTTP authentication. Pass the external URL of the WebSocket
|
and some form of HTTP authentication. Pass the external URL of the WebSocket
|
||||||
endpoint as the third command line argument in this case.
|
endpoint as the third command line argument in this case.
|
||||||
|
|
||||||
|
xA
|
||||||
|
~~
|
||||||
|
The Fyne frontend supports all of Linux, FreeBSD, Windows, macOS, Android, and
|
||||||
|
iOS natively, albeit somewhat poorly. Only use `fyne` or `fyne-cross` after
|
||||||
|
running `make generate` first.
|
||||||
|
|
||||||
|
xT
|
||||||
|
~~
|
||||||
|
The Qt Widgets frontend is a separate CMake subproject. It generally supports
|
||||||
|
all desktop operating systems. To avoid having to specify the relay address
|
||||||
|
each time you run it, pass it on the command line.
|
||||||
|
|
||||||
xW
|
xW
|
||||||
~~
|
~~
|
||||||
The Win32 frontend is a separate CMake subproject that should be compiled
|
The Win32 frontend is a separate CMake subproject that should be compiled
|
||||||
|
|||||||
2
liberty
2
liberty
Submodule liberty updated: f04cc2c61e...af889b733e
52
test
52
test
@@ -1,52 +0,0 @@
|
|||||||
#!/usr/bin/expect -f
|
|
||||||
# Very basic end-to-end testing for CI
|
|
||||||
set tempdir [exec mktemp -d]
|
|
||||||
set ::env(XDG_CONFIG_HOME) $tempdir
|
|
||||||
|
|
||||||
# Run the daemon to test against
|
|
||||||
system ./xD --write-default-cfg
|
|
||||||
spawn ./xD -d
|
|
||||||
|
|
||||||
# 10 seconds is a bit too much
|
|
||||||
set timeout 5
|
|
||||||
|
|
||||||
spawn ./xC
|
|
||||||
|
|
||||||
# Fuck this Tcl shit, I want the exit code
|
|
||||||
expect_after {
|
|
||||||
eof {
|
|
||||||
puts ""
|
|
||||||
puts "Child exited prematurely"
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Connect to the daemon
|
|
||||||
send "/server add localhost\n"
|
|
||||||
expect "]"
|
|
||||||
send "/set servers.localhost.addresses = \"localhost\"\n"
|
|
||||||
expect "Option changed"
|
|
||||||
send "/disconnect\n"
|
|
||||||
expect "]"
|
|
||||||
send "/connect\n"
|
|
||||||
expect "Welcome to"
|
|
||||||
|
|
||||||
# Try some chatting
|
|
||||||
send "/join #test\n"
|
|
||||||
expect "has joined"
|
|
||||||
send "Hello\n"
|
|
||||||
expect "Hello"
|
|
||||||
|
|
||||||
# Attributes
|
|
||||||
send "\x1bmbBold text! \x1bmc0,5And colors.\n"
|
|
||||||
expect "]"
|
|
||||||
|
|
||||||
# Try basic commands
|
|
||||||
send "/set\n"
|
|
||||||
expect "]"
|
|
||||||
send "/help\n"
|
|
||||||
expect "]"
|
|
||||||
|
|
||||||
# Quit
|
|
||||||
send "/quit\n"
|
|
||||||
expect "Shutting down"
|
|
||||||
72
test.lua
Normal file
72
test.lua
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#!/usr/bin/env wdye
|
||||||
|
-- Very basic end-to-end testing for CI
|
||||||
|
function exec (...)
|
||||||
|
local p = wdye.spawn(...)
|
||||||
|
local out = wdye.expect(p:eof {function (p) return p[0] end})
|
||||||
|
if not out then
|
||||||
|
error "exec() timeout"
|
||||||
|
end
|
||||||
|
|
||||||
|
local status = p:wait()
|
||||||
|
if status ~= 0 then
|
||||||
|
io.write(out, "\n")
|
||||||
|
error("exit status " .. status)
|
||||||
|
end
|
||||||
|
return out:gsub("%s+$", "")
|
||||||
|
end
|
||||||
|
|
||||||
|
local temp = exec {"mktemp", "-d"}
|
||||||
|
local atexit = {}
|
||||||
|
setmetatable(atexit, {__gc = function () exec {"rm", "-rf", "--", temp} end})
|
||||||
|
|
||||||
|
local env = {XDG_CONFIG_HOME=temp, TERM="xterm"}
|
||||||
|
exec {"./xD", "--write-default-cfg", environ=env}
|
||||||
|
|
||||||
|
-- Run the daemon to test against (assuming the default port 6667)
|
||||||
|
local xD = wdye.spawn {"./xD", "-d", environ=env}
|
||||||
|
local xC = wdye.spawn {"./xC", environ=env}
|
||||||
|
|
||||||
|
function send (...) xC:send(...) end
|
||||||
|
function expect (string)
|
||||||
|
wdye.expect(xC:exact {string},
|
||||||
|
wdye.timeout {5, function (p) error "xC timeout" end},
|
||||||
|
xC:eof {function (p) error "xC exited prematurely" end})
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Connect to the daemon
|
||||||
|
send "/server add localhost\n"
|
||||||
|
expect "]"
|
||||||
|
send "/set servers.localhost.addresses = \"localhost\"\n"
|
||||||
|
expect "Option changed"
|
||||||
|
send "/disconnect\n"
|
||||||
|
expect "]"
|
||||||
|
send "/connect\n"
|
||||||
|
expect "Welcome to"
|
||||||
|
|
||||||
|
-- Try some chatting
|
||||||
|
send "/join #test\n"
|
||||||
|
expect "has joined"
|
||||||
|
send "Hello\n"
|
||||||
|
expect "Hello"
|
||||||
|
|
||||||
|
-- Attributes
|
||||||
|
send "\x1bmbBold text! \x1bmc0,5And colors.\n"
|
||||||
|
expect "]"
|
||||||
|
|
||||||
|
-- Try basic commands
|
||||||
|
send "/set\n"
|
||||||
|
expect "]"
|
||||||
|
send "/help\n"
|
||||||
|
expect "]"
|
||||||
|
|
||||||
|
-- Quit
|
||||||
|
send "/quit\n"
|
||||||
|
expect "Shutting down"
|
||||||
|
|
||||||
|
local s1 = xC:wait()
|
||||||
|
assert(s1 == 0, "xC exited abnormally: " .. s1)
|
||||||
|
|
||||||
|
-- Send SIGINT (^C)
|
||||||
|
xD:send "\003"
|
||||||
|
local s2 = xD:wait()
|
||||||
|
assert(s2 == 0, "xD exited abnormally: " .. s2)
|
||||||
5
xA/.gitignore
vendored
Normal file
5
xA/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/xA
|
||||||
|
/proto.go
|
||||||
|
/FyneApp.toml
|
||||||
|
/*.png
|
||||||
|
/beep.raw
|
||||||
36
xA/Makefile
Normal file
36
xA/Makefile
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
.POSIX:
|
||||||
|
.SUFFIXES:
|
||||||
|
.SUFFIXES: .png .svg
|
||||||
|
AWK = env LC_ALL=C awk
|
||||||
|
|
||||||
|
tools = ../liberty/tools
|
||||||
|
generated = FyneApp.toml xA.png xA-highlighted.png beep.raw proto.go
|
||||||
|
outputs = xA $(generated)
|
||||||
|
all: $(outputs)
|
||||||
|
generate: $(generated)
|
||||||
|
|
||||||
|
FyneApp.toml: ../xK-version
|
||||||
|
printf "\
|
||||||
|
[Details]\n\
|
||||||
|
Icon = 'xA.png'\n\
|
||||||
|
Name = 'xA'\n\
|
||||||
|
ID = 'name.janouch.xA'\n\
|
||||||
|
Version = '$$(cat ../xK-version)'\n\
|
||||||
|
Build = 1\n\
|
||||||
|
\n\
|
||||||
|
[LinuxAndBSD]\n\
|
||||||
|
GenericName = 'IRC Client'\n\
|
||||||
|
Categories = ['Network', 'Chat', 'IRCClient']\n" > $@
|
||||||
|
.svg.png:
|
||||||
|
rsvg-convert --output=$@ -- $<
|
||||||
|
beep.raw:
|
||||||
|
sox -Dr 44100 -c 1 -e signed-integer -b 16 -L -n $@ \
|
||||||
|
synth 0.1 0 25 triangle 800 vol 0.5 fade t 0 -0 0.005 pad 0 0.05
|
||||||
|
proto.go: $(tools)/lxdrgen.awk $(tools)/lxdrgen-go.awk ../xC.lxdr
|
||||||
|
$(AWK) -f $(tools)/lxdrgen.awk -f $(tools)/lxdrgen-go.awk \
|
||||||
|
-v PrefixCamel=Relay ../xC.lxdr > $@
|
||||||
|
xA: xA.go ../xK-version $(generated)
|
||||||
|
go build -ldflags "-X 'main.projectVersion=$$(cat ../xK-version)'" -o $@ \
|
||||||
|
-gcflags=all="-N -l"
|
||||||
|
clean:
|
||||||
|
rm -f $(outputs)
|
||||||
46
xA/go.mod
Normal file
46
xA/go.mod
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
module janouch.name/xK/xA
|
||||||
|
|
||||||
|
go 1.23.0
|
||||||
|
|
||||||
|
toolchain go1.24.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
fyne.io/fyne/v2 v2.6.0
|
||||||
|
github.com/ebitengine/oto/v3 v3.3.3
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
fyne.io/systray v1.11.0 // indirect
|
||||||
|
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/ebitengine/purego v0.8.2 // indirect
|
||||||
|
github.com/fredbi/uri v1.1.0 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
|
github.com/fyne-io/gl-js v0.1.0 // indirect
|
||||||
|
github.com/fyne-io/glfw-js v0.2.0 // indirect
|
||||||
|
github.com/fyne-io/image v0.1.1 // indirect
|
||||||
|
github.com/fyne-io/oksvg v0.1.0 // indirect
|
||||||
|
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728 // indirect
|
||||||
|
github.com/go-text/render v0.2.0 // indirect
|
||||||
|
github.com/go-text/typesetting v0.3.0 // indirect
|
||||||
|
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||||
|
github.com/hack-pad/go-indexeddb v0.3.2 // indirect
|
||||||
|
github.com/hack-pad/safejs v0.1.1 // indirect
|
||||||
|
github.com/jeandeaual/go-locale v0.0.0-20250421151639-a9d6ed1b3d45 // indirect
|
||||||
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
|
||||||
|
github.com/kr/text v0.2.0 // indirect
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||||
|
github.com/nicksnyder/go-i18n/v2 v2.6.0 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/rymdport/portal v0.4.1 // indirect
|
||||||
|
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
|
||||||
|
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
|
||||||
|
github.com/stretchr/testify v1.10.0 // indirect
|
||||||
|
github.com/yuin/goldmark v1.7.10 // indirect
|
||||||
|
golang.org/x/image v0.26.0 // indirect
|
||||||
|
golang.org/x/net v0.39.0 // indirect
|
||||||
|
golang.org/x/sys v0.32.0 // indirect
|
||||||
|
golang.org/x/text v0.24.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
84
xA/go.sum
Normal file
84
xA/go.sum
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
fyne.io/fyne/v2 v2.6.0 h1:Rywo9yKYN4qvNuvkRuLF+zxhJYWbIFM+m4N4KV4p1pQ=
|
||||||
|
fyne.io/fyne/v2 v2.6.0/go.mod h1:YZt7SksjvrSNJCwbWFV32WON3mE1Sr7L41D29qMZ/lU=
|
||||||
|
fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg=
|
||||||
|
fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
|
||||||
|
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||||
|
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/ebitengine/oto/v3 v3.3.3 h1:m6RV69OqoXYSWCDsHXN9rc07aDuDstGHtait7HXSM7g=
|
||||||
|
github.com/ebitengine/oto/v3 v3.3.3/go.mod h1:MZeb/lwoC4DCOdiTIxYezrURTw7EvK/yF863+tmBI+U=
|
||||||
|
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
|
||||||
|
github.com/ebitengine/purego v0.8.2/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.0 h1:OqLpTXtyRg9ABReqvDGdJPqZUxs8cyBDOMXBbskCaB8=
|
||||||
|
github.com/fredbi/uri v1.1.0/go.mod h1:aYTUoAXBOq7BLfVJ8GnKmfcuURosB1xyHDIfWeC/iW4=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
|
github.com/fyne-io/gl-js v0.1.0 h1:8luJzNs0ntEAJo+8x8kfUOXujUlP8gB3QMOxO2mUdpM=
|
||||||
|
github.com/fyne-io/gl-js v0.1.0/go.mod h1:ZcepK8vmOYLu96JoxbCKJy2ybr+g1pTnaBDdl7c3ajI=
|
||||||
|
github.com/fyne-io/glfw-js v0.2.0 h1:8GUZtN2aCoTPNqgRDxK5+kn9OURINhBEBc7M4O1KrmM=
|
||||||
|
github.com/fyne-io/glfw-js v0.2.0/go.mod h1:Ri6te7rdZtBgBpxLW19uBpp3Dl6K9K/bRaYdJ22G8Jk=
|
||||||
|
github.com/fyne-io/image v0.1.1 h1:WH0z4H7qfvNUw5l4p3bC1q70sa5+YWVt6HCj7y4VNyA=
|
||||||
|
github.com/fyne-io/image v0.1.1/go.mod h1:xrfYBh6yspc+KjkgdZU/ifUC9sPA5Iv7WYUBzQKK7JM=
|
||||||
|
github.com/fyne-io/oksvg v0.1.0 h1:7EUKk3HV3Y2E+qypp3nWqMXD7mum0hCw2KEGhI1fnBw=
|
||||||
|
github.com/fyne-io/oksvg v0.1.0/go.mod h1:dJ9oEkPiWhnTFNCmRgEze+YNprJF7YRbpjgpWS4kzoI=
|
||||||
|
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA=
|
||||||
|
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728 h1:RkGhqHxEVAvPM0/R+8g7XRwQnHatO0KAuVcwHo8q9W8=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728/go.mod h1:SyRD8YfuKk+ZXlDqYiqe1qMSqjNgtHzBTG810KUagMc=
|
||||||
|
github.com/go-text/render v0.2.0 h1:LBYoTmp5jYiJ4NPqDc2pz17MLmA3wHw1dZSVGcOdeAc=
|
||||||
|
github.com/go-text/render v0.2.0/go.mod h1:CkiqfukRGKJA5vZZISkjSYrcdtgKQWRa2HIzvwNN5SU=
|
||||||
|
github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4=
|
||||||
|
github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY=
|
||||||
|
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
|
||||||
|
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
|
||||||
|
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||||
|
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
|
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=
|
||||||
|
github.com/hack-pad/safejs v0.1.1/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio=
|
||||||
|
github.com/jeandeaual/go-locale v0.0.0-20250421151639-a9d6ed1b3d45 h1:vFdvrlsVU+p/KFBWTq0lTG4fvWvG88sawGlCzM+RUEU=
|
||||||
|
github.com/jeandeaual/go-locale v0.0.0-20250421151639-a9d6ed1b3d45/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
|
||||||
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
|
||||||
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
|
github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ=
|
||||||
|
github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE=
|
||||||
|
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.1 h1:2dnZhjf5uEaeDjeF/yBIeeRo6pNI2QAKm7kq1w/kbnA=
|
||||||
|
github.com/rymdport/portal v0.4.1/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
|
||||||
|
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
|
||||||
|
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
|
||||||
|
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
|
||||||
|
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/yuin/goldmark v1.7.10 h1:S+LrtBjRmqMac2UdtB6yyCEJm+UILZ2fefI4p7o0QpI=
|
||||||
|
github.com/yuin/goldmark v1.7.10/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||||
|
golang.org/x/image v0.26.0 h1:4XjIFEZWQmCZi6Wv8BoxsDhRU3RVnLX04dToTDAEPlY=
|
||||||
|
golang.org/x/image v0.26.0/go.mod h1:lcxbMFAovzpnJxzXS3nyL83K27tmqtKzIJpctK8YO5c=
|
||||||
|
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||||
|
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||||
|
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||||
|
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||||
|
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
23
xA/xA-highlighted.svg
Normal file
23
xA/xA-highlighted.svg
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg version="1.1" width="512" height="512" viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="background" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop stop-color="#ffaa00" offset="0" />
|
||||||
|
<stop stop-color="#ffffff" offset="1" />
|
||||||
|
</linearGradient>
|
||||||
|
<clipPath id="clip">
|
||||||
|
<rect x="0" y="0" width="1" height="1" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<rect x="0" y="0" width="512" height="512" fill="url(#background)" />
|
||||||
|
|
||||||
|
<g transform="translate(64, 64) scale(384)" stroke-linecap="square">
|
||||||
|
<g clip-path="url(#clip)">
|
||||||
|
<path stroke="#ff0000" stroke-width="0.125"
|
||||||
|
d="M 0.25,1.1 0.65,-0.1 M 0.75,1.1 0.35,-0.1 M 0.225,0.75 0.775,0.75" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 781 B |
23
xA/xA.svg
Normal file
23
xA/xA.svg
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg version="1.1" width="512" height="512" viewBox="0 0 512 512"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="background" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop stop-color="#ffaa00" offset="0" />
|
||||||
|
<stop stop-color="#ff0000" offset="1" />
|
||||||
|
</linearGradient>
|
||||||
|
<clipPath id="clip">
|
||||||
|
<rect x="0" y="0" width="1" height="1" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<rect x="0" y="0" width="512" height="512" fill="url(#background)" />
|
||||||
|
|
||||||
|
<g transform="translate(64, 64) scale(384)" stroke-linecap="square">
|
||||||
|
<g clip-path="url(#clip)">
|
||||||
|
<path stroke="#ffffff" stroke-width="0.125"
|
||||||
|
d="M 0.25,1.1 0.65,-0.1 M 0.75,1.1 0.35,-0.1 M 0.225,0.75 0.775,0.75" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 781 B |
799
xC.c
799
xC.c
@@ -474,6 +474,10 @@ input_rl_start (void *input, const char *program_name)
|
|||||||
// autofilter, and we don't generally want alphabetic ordering at all
|
// autofilter, and we don't generally want alphabetic ordering at all
|
||||||
rl_sort_completion_matches = false;
|
rl_sort_completion_matches = false;
|
||||||
|
|
||||||
|
// Readline >= 8.0 otherwise prints spurious newlines on EOF.
|
||||||
|
if (RL_VERSION_MAJOR >= 8)
|
||||||
|
rl_variable_bind ("enable-bracketed-paste", "off");
|
||||||
|
|
||||||
hard_assert (self->prompt != NULL);
|
hard_assert (self->prompt != NULL);
|
||||||
// The inputrc is read before any callbacks are called, so we need to
|
// The inputrc is read before any callbacks are called, so we need to
|
||||||
// register all functions that our user may want to map up front
|
// register all functions that our user may want to map up front
|
||||||
@@ -1030,7 +1034,7 @@ input_el__save_buffer (struct input_el *self, struct input_el_buffer *buffer)
|
|||||||
int len = info->lastchar - info->buffer;
|
int len = info->lastchar - info->buffer;
|
||||||
int point = info->cursor - info->buffer;
|
int point = info->cursor - info->buffer;
|
||||||
|
|
||||||
wchar_t *line = calloc (sizeof *info->buffer, len + 1);
|
wchar_t *line = xcalloc (len + 1, sizeof *info->buffer);
|
||||||
memcpy (line, info->buffer, sizeof *info->buffer * len);
|
memcpy (line, info->buffer, sizeof *info->buffer * len);
|
||||||
el_cursor (self->editline, len - point);
|
el_cursor (self->editline, len - point);
|
||||||
el_wdeletestr (self->editline, len);
|
el_wdeletestr (self->editline, len);
|
||||||
@@ -1635,7 +1639,7 @@ static struct formatter
|
|||||||
formatter_make (struct app_context *ctx, struct server *s)
|
formatter_make (struct app_context *ctx, struct server *s)
|
||||||
{
|
{
|
||||||
struct formatter self = { .ctx = ctx, .s = s, .clean = true };
|
struct formatter self = { .ctx = ctx, .s = s, .clean = true };
|
||||||
self.items = xcalloc (sizeof *self.items, (self.items_alloc = 16));
|
self.items = xcalloc ((self.items_alloc = 16), sizeof *self.items);
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2567,7 +2571,7 @@ config_validate_nonnegative
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct config_schema g_config_server[] =
|
static const struct config_schema g_config_server[] =
|
||||||
{
|
{
|
||||||
{ .name = "nicks",
|
{ .name = "nicks",
|
||||||
.comment = "IRC nickname",
|
.comment = "IRC nickname",
|
||||||
@@ -2664,7 +2668,7 @@ static struct config_schema g_config_server[] =
|
|||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct config_schema g_config_general[] =
|
static const struct config_schema g_config_general[] =
|
||||||
{
|
{
|
||||||
{ .name = "autosave",
|
{ .name = "autosave",
|
||||||
.comment = "Save configuration automatically after each change",
|
.comment = "Save configuration automatically after each change",
|
||||||
@@ -2772,7 +2776,7 @@ static struct config_schema g_config_general[] =
|
|||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
static struct config_schema g_config_theme[] =
|
static const struct config_schema g_config_theme[] =
|
||||||
{
|
{
|
||||||
#define XX(x, y, z) { .name = #y, .comment = #z, .type = CONFIG_ITEM_STRING, \
|
#define XX(x, y, z) { .name = #y, .comment = #z, .type = CONFIG_ITEM_STRING, \
|
||||||
.on_change = on_config_theme_change },
|
.on_change = on_config_theme_change },
|
||||||
@@ -2891,390 +2895,6 @@ serialize_configuration (struct config_item *root, struct str *output)
|
|||||||
config_item_write (root, true, output);
|
config_item_write (root, true, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Relay output ------------------------------------------------------------
|
|
||||||
|
|
||||||
static void
|
|
||||||
client_kill (struct client *c)
|
|
||||||
{
|
|
||||||
struct app_context *ctx = c->ctx;
|
|
||||||
poller_fd_reset (&c->socket_event);
|
|
||||||
xclose (c->socket_fd);
|
|
||||||
c->socket_fd = -1;
|
|
||||||
|
|
||||||
LIST_UNLINK (ctx->clients, c);
|
|
||||||
client_destroy (c);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
client_update_poller (struct client *c, const struct pollfd *pfd)
|
|
||||||
{
|
|
||||||
int new_events = POLLIN;
|
|
||||||
if (c->write_buffer.len)
|
|
||||||
new_events |= POLLOUT;
|
|
||||||
|
|
||||||
hard_assert (new_events != 0);
|
|
||||||
if (!pfd || pfd->events != new_events)
|
|
||||||
poller_fd_set (&c->socket_event, new_events);
|
|
||||||
}
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
||||||
|
|
||||||
static void
|
|
||||||
relay_send (struct client *c)
|
|
||||||
{
|
|
||||||
struct relay_event_message *m = &c->ctx->relay_message;
|
|
||||||
m->event_seq = c->event_seq++;
|
|
||||||
|
|
||||||
// TODO: Also don't try sending anything if half-closed.
|
|
||||||
if (!c->initialized || c->socket_fd == -1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// liberty has msg_{reader,writer} already, but they use 8-byte lengths.
|
|
||||||
size_t frame_len_pos = c->write_buffer.len, frame_len = 0;
|
|
||||||
str_pack_u32 (&c->write_buffer, 0);
|
|
||||||
if (!relay_event_message_serialize (m, &c->write_buffer)
|
|
||||||
|| (frame_len = c->write_buffer.len - frame_len_pos - 4) > UINT32_MAX)
|
|
||||||
{
|
|
||||||
print_error ("serialization failed, killing client");
|
|
||||||
client_kill (c);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t len = htonl (frame_len);
|
|
||||||
memcpy (c->write_buffer.str + frame_len_pos, &len, sizeof len);
|
|
||||||
client_update_poller (c, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
relay_broadcast_except (struct app_context *ctx, struct client *exception)
|
|
||||||
{
|
|
||||||
LIST_FOR_EACH (struct client, c, ctx->clients)
|
|
||||||
if (c != exception)
|
|
||||||
relay_send (c);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define relay_broadcast(ctx) relay_broadcast_except ((ctx), NULL)
|
|
||||||
|
|
||||||
static struct relay_event_message *
|
|
||||||
relay_prepare (struct app_context *ctx)
|
|
||||||
{
|
|
||||||
struct relay_event_message *m = &ctx->relay_message;
|
|
||||||
relay_event_message_free (m);
|
|
||||||
memset (m, 0, sizeof *m);
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
relay_prepare_ping (struct app_context *ctx)
|
|
||||||
{
|
|
||||||
relay_prepare (ctx)->data.event = RELAY_EVENT_PING;
|
|
||||||
}
|
|
||||||
|
|
||||||
static union relay_item_data *
|
|
||||||
relay_translate_formatter (struct app_context *ctx, union relay_item_data *p,
|
|
||||||
const struct formatter_item *i)
|
|
||||||
{
|
|
||||||
// XXX: See attr_printer_decode_color(), this is a footgun.
|
|
||||||
int16_t c16 = i->color;
|
|
||||||
int16_t c256 = i->color >> 16;
|
|
||||||
|
|
||||||
unsigned attrs = i->attribute;
|
|
||||||
switch (i->type)
|
|
||||||
{
|
|
||||||
case FORMATTER_ITEM_TEXT:
|
|
||||||
p->text.text = str_from_cstr (i->text);
|
|
||||||
(p++)->kind = RELAY_ITEM_TEXT;
|
|
||||||
break;
|
|
||||||
case FORMATTER_ITEM_FG_COLOR:
|
|
||||||
p->fg_color.color = c256 <= 0 ? c16 : c256;
|
|
||||||
(p++)->kind = RELAY_ITEM_FG_COLOR;
|
|
||||||
break;
|
|
||||||
case FORMATTER_ITEM_BG_COLOR:
|
|
||||||
p->bg_color.color = c256 <= 0 ? c16 : c256;
|
|
||||||
(p++)->kind = RELAY_ITEM_BG_COLOR;
|
|
||||||
break;
|
|
||||||
case FORMATTER_ITEM_ATTR:
|
|
||||||
(p++)->kind = RELAY_ITEM_RESET;
|
|
||||||
if ((c256 = ctx->theme[i->attribute].fg) >= 0)
|
|
||||||
{
|
|
||||||
p->fg_color.color = c256;
|
|
||||||
(p++)->kind = RELAY_ITEM_FG_COLOR;
|
|
||||||
}
|
|
||||||
if ((c256 = ctx->theme[i->attribute].bg) >= 0)
|
|
||||||
{
|
|
||||||
p->bg_color.color = c256;
|
|
||||||
(p++)->kind = RELAY_ITEM_BG_COLOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
attrs = ctx->theme[i->attribute].attrs;
|
|
||||||
// Fall-through
|
|
||||||
case FORMATTER_ITEM_SIMPLE:
|
|
||||||
if (attrs & TEXT_BOLD)
|
|
||||||
(p++)->kind = RELAY_ITEM_FLIP_BOLD;
|
|
||||||
if (attrs & TEXT_ITALIC)
|
|
||||||
(p++)->kind = RELAY_ITEM_FLIP_ITALIC;
|
|
||||||
if (attrs & TEXT_UNDERLINE)
|
|
||||||
(p++)->kind = RELAY_ITEM_FLIP_UNDERLINE;
|
|
||||||
if (attrs & TEXT_INVERSE)
|
|
||||||
(p++)->kind = RELAY_ITEM_FLIP_INVERSE;
|
|
||||||
if (attrs & TEXT_CROSSED_OUT)
|
|
||||||
(p++)->kind = RELAY_ITEM_FLIP_CROSSED_OUT;
|
|
||||||
if (attrs & TEXT_MONOSPACE)
|
|
||||||
(p++)->kind = RELAY_ITEM_FLIP_MONOSPACE;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
static union relay_item_data *
|
|
||||||
relay_items (struct app_context *ctx, const struct formatter_item *items,
|
|
||||||
uint32_t *len)
|
|
||||||
{
|
|
||||||
size_t items_len = 0;
|
|
||||||
for (size_t i = 0; items[i].type; i++)
|
|
||||||
items_len++;
|
|
||||||
|
|
||||||
// Beware of the upper bound, currently dominated by FORMATTER_ITEM_ATTR.
|
|
||||||
union relay_item_data *a = xcalloc (items_len * 9, sizeof *a), *p = a;
|
|
||||||
for (const struct formatter_item *i = items; items_len--; i++)
|
|
||||||
p = relay_translate_formatter (ctx, p, i);
|
|
||||||
|
|
||||||
*len = p - a;
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
relay_prepare_buffer_line (struct app_context *ctx, struct buffer *buffer,
|
|
||||||
struct buffer_line *line, bool leak_to_active)
|
|
||||||
{
|
|
||||||
struct relay_event_message *m = relay_prepare (ctx);
|
|
||||||
struct relay_event_data_buffer_line *e = &m->data.buffer_line;
|
|
||||||
e->event = RELAY_EVENT_BUFFER_LINE;
|
|
||||||
e->buffer_name = str_from_cstr (buffer->name);
|
|
||||||
e->is_unimportant = !!(line->flags & BUFFER_LINE_UNIMPORTANT);
|
|
||||||
e->is_highlight = !!(line->flags & BUFFER_LINE_HIGHLIGHT);
|
|
||||||
e->rendition = 1 + line->r;
|
|
||||||
e->when = line->when * 1000;
|
|
||||||
e->leak_to_active = leak_to_active;
|
|
||||||
e->items = relay_items (ctx, line->items, &e->items_len);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Consider pushing this whole block of code much further down.
|
|
||||||
static void formatter_add (struct formatter *self, const char *format, ...);
|
|
||||||
static char *irc_to_utf8 (const char *text);
|
|
||||||
|
|
||||||
static void
|
|
||||||
relay_prepare_channel_buffer_update (struct app_context *ctx,
|
|
||||||
struct buffer *buffer, struct relay_buffer_context_channel *e)
|
|
||||||
{
|
|
||||||
struct channel *channel = buffer->channel;
|
|
||||||
struct formatter f = formatter_make (ctx, buffer->server);
|
|
||||||
if (channel->topic)
|
|
||||||
formatter_add (&f, "#m", channel->topic);
|
|
||||||
e->topic = relay_items (ctx, f.items, &e->topic_len);
|
|
||||||
formatter_free (&f);
|
|
||||||
|
|
||||||
// As in make_prompt(), conceal the last known channel modes.
|
|
||||||
// XXX: This should use irc_channel_is_joined().
|
|
||||||
if (!channel->users_len)
|
|
||||||
return;
|
|
||||||
|
|
||||||
struct str modes = str_make ();
|
|
||||||
str_append_str (&modes, &channel->no_param_modes);
|
|
||||||
|
|
||||||
struct str params = str_make ();
|
|
||||||
struct str_map_iter iter = str_map_iter_make (&channel->param_modes);
|
|
||||||
const char *param;
|
|
||||||
while ((param = str_map_iter_next (&iter)))
|
|
||||||
{
|
|
||||||
str_append_c (&modes, iter.link->key[0]);
|
|
||||||
str_append_c (¶ms, ' ');
|
|
||||||
str_append (¶ms, param);
|
|
||||||
}
|
|
||||||
|
|
||||||
str_append_str (&modes, ¶ms);
|
|
||||||
str_free (¶ms);
|
|
||||||
|
|
||||||
char *modes_utf8 = irc_to_utf8 (modes.str);
|
|
||||||
str_free (&modes);
|
|
||||||
e->modes = str_from_cstr (modes_utf8);
|
|
||||||
free (modes_utf8);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
relay_prepare_buffer_update (struct app_context *ctx, struct buffer *buffer)
|
|
||||||
{
|
|
||||||
struct relay_event_message *m = relay_prepare (ctx);
|
|
||||||
struct relay_event_data_buffer_update *e = &m->data.buffer_update;
|
|
||||||
e->event = RELAY_EVENT_BUFFER_UPDATE;
|
|
||||||
e->buffer_name = str_from_cstr (buffer->name);
|
|
||||||
e->hide_unimportant = buffer->hide_unimportant;
|
|
||||||
|
|
||||||
struct str *server_name = NULL;
|
|
||||||
switch (buffer->type)
|
|
||||||
{
|
|
||||||
case BUFFER_GLOBAL:
|
|
||||||
e->context.kind = RELAY_BUFFER_KIND_GLOBAL;
|
|
||||||
break;
|
|
||||||
case BUFFER_SERVER:
|
|
||||||
e->context.kind = RELAY_BUFFER_KIND_SERVER;
|
|
||||||
server_name = &e->context.server.server_name;
|
|
||||||
break;
|
|
||||||
case BUFFER_CHANNEL:
|
|
||||||
e->context.kind = RELAY_BUFFER_KIND_CHANNEL;
|
|
||||||
server_name = &e->context.channel.server_name;
|
|
||||||
relay_prepare_channel_buffer_update (ctx, buffer, &e->context.channel);
|
|
||||||
break;
|
|
||||||
case BUFFER_PM:
|
|
||||||
e->context.kind = RELAY_BUFFER_KIND_PRIVATE_MESSAGE;
|
|
||||||
server_name = &e->context.private_message.server_name;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (server_name)
|
|
||||||
*server_name = str_from_cstr (buffer->server->name);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
relay_prepare_buffer_stats (struct app_context *ctx, struct buffer *buffer)
|
|
||||||
{
|
|
||||||
struct relay_event_message *m = relay_prepare (ctx);
|
|
||||||
struct relay_event_data_buffer_stats *e = &m->data.buffer_stats;
|
|
||||||
e->event = RELAY_EVENT_BUFFER_STATS;
|
|
||||||
e->buffer_name = str_from_cstr (buffer->name);
|
|
||||||
e->new_messages = MIN (UINT32_MAX,
|
|
||||||
buffer->new_messages_count - buffer->new_unimportant_count);
|
|
||||||
e->new_unimportant_messages = MIN (UINT32_MAX,
|
|
||||||
buffer->new_unimportant_count);
|
|
||||||
e->highlighted = buffer->highlighted;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
relay_prepare_buffer_rename (struct app_context *ctx, struct buffer *buffer,
|
|
||||||
const char *new_name)
|
|
||||||
{
|
|
||||||
struct relay_event_message *m = relay_prepare (ctx);
|
|
||||||
struct relay_event_data_buffer_rename *e = &m->data.buffer_rename;
|
|
||||||
e->event = RELAY_EVENT_BUFFER_RENAME;
|
|
||||||
e->buffer_name = str_from_cstr (buffer->name);
|
|
||||||
e->new = str_from_cstr (new_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
relay_prepare_buffer_remove (struct app_context *ctx, struct buffer *buffer)
|
|
||||||
{
|
|
||||||
struct relay_event_message *m = relay_prepare (ctx);
|
|
||||||
struct relay_event_data_buffer_remove *e = &m->data.buffer_remove;
|
|
||||||
e->event = RELAY_EVENT_BUFFER_REMOVE;
|
|
||||||
e->buffer_name = str_from_cstr (buffer->name);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
relay_prepare_buffer_activate (struct app_context *ctx, struct buffer *buffer)
|
|
||||||
{
|
|
||||||
struct relay_event_message *m = relay_prepare (ctx);
|
|
||||||
struct relay_event_data_buffer_activate *e = &m->data.buffer_activate;
|
|
||||||
e->event = RELAY_EVENT_BUFFER_ACTIVATE;
|
|
||||||
e->buffer_name = str_from_cstr (buffer->name);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
relay_prepare_buffer_input (struct app_context *ctx, struct buffer *buffer,
|
|
||||||
const char *input)
|
|
||||||
{
|
|
||||||
struct relay_event_message *m = relay_prepare (ctx);
|
|
||||||
struct relay_event_data_buffer_input *e = &m->data.buffer_input;
|
|
||||||
e->event = RELAY_EVENT_BUFFER_INPUT;
|
|
||||||
e->buffer_name = str_from_cstr (buffer->name);
|
|
||||||
e->text = str_from_cstr (input);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
relay_prepare_buffer_clear (struct app_context *ctx,
|
|
||||||
struct buffer *buffer)
|
|
||||||
{
|
|
||||||
struct relay_event_message *m = relay_prepare (ctx);
|
|
||||||
struct relay_event_data_buffer_clear *e = &m->data.buffer_clear;
|
|
||||||
e->event = RELAY_EVENT_BUFFER_CLEAR;
|
|
||||||
e->buffer_name = str_from_cstr (buffer->name);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum relay_server_state
|
|
||||||
relay_server_state_for_server (struct server *s)
|
|
||||||
{
|
|
||||||
switch (s->state)
|
|
||||||
{
|
|
||||||
case IRC_DISCONNECTED: return RELAY_SERVER_STATE_DISCONNECTED;
|
|
||||||
case IRC_CONNECTING: return RELAY_SERVER_STATE_CONNECTING;
|
|
||||||
case IRC_CONNECTED: return RELAY_SERVER_STATE_CONNECTED;
|
|
||||||
case IRC_REGISTERED: return RELAY_SERVER_STATE_REGISTERED;
|
|
||||||
case IRC_CLOSING:
|
|
||||||
case IRC_HALF_CLOSED: return RELAY_SERVER_STATE_DISCONNECTING;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
relay_prepare_server_update (struct app_context *ctx, struct server *s)
|
|
||||||
{
|
|
||||||
struct relay_event_message *m = relay_prepare (ctx);
|
|
||||||
struct relay_event_data_server_update *e = &m->data.server_update;
|
|
||||||
e->event = RELAY_EVENT_SERVER_UPDATE;
|
|
||||||
e->server_name = str_from_cstr (s->name);
|
|
||||||
e->data.state = relay_server_state_for_server (s);
|
|
||||||
if (s->state == IRC_REGISTERED)
|
|
||||||
{
|
|
||||||
char *user_utf8 = irc_to_utf8 (s->irc_user->nickname);
|
|
||||||
e->data.registered.user = str_from_cstr (user_utf8);
|
|
||||||
free (user_utf8);
|
|
||||||
|
|
||||||
char *user_modes_utf8 = irc_to_utf8 (s->irc_user_modes.str);
|
|
||||||
e->data.registered.user_modes = str_from_cstr (user_modes_utf8);
|
|
||||||
free (user_modes_utf8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
relay_prepare_server_rename (struct app_context *ctx, struct server *s,
|
|
||||||
const char *new_name)
|
|
||||||
{
|
|
||||||
struct relay_event_message *m = relay_prepare (ctx);
|
|
||||||
struct relay_event_data_server_rename *e = &m->data.server_rename;
|
|
||||||
e->event = RELAY_EVENT_SERVER_RENAME;
|
|
||||||
e->server_name = str_from_cstr (s->name);
|
|
||||||
e->new = str_from_cstr (new_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
relay_prepare_server_remove (struct app_context *ctx, struct server *s)
|
|
||||||
{
|
|
||||||
struct relay_event_message *m = relay_prepare (ctx);
|
|
||||||
struct relay_event_data_server_remove *e = &m->data.server_remove;
|
|
||||||
e->event = RELAY_EVENT_SERVER_REMOVE;
|
|
||||||
e->server_name = str_from_cstr (s->name);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
relay_prepare_error (struct app_context *ctx, uint32_t seq, const char *message)
|
|
||||||
{
|
|
||||||
struct relay_event_message *m = relay_prepare (ctx);
|
|
||||||
struct relay_event_data_error *e = &m->data.error;
|
|
||||||
e->event = RELAY_EVENT_ERROR;
|
|
||||||
e->command_seq = seq;
|
|
||||||
e->error = str_from_cstr (message);
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct relay_event_data_response *
|
|
||||||
relay_prepare_response (struct app_context *ctx, uint32_t seq)
|
|
||||||
{
|
|
||||||
struct relay_event_message *m = relay_prepare (ctx);
|
|
||||||
struct relay_event_data_response *e = &m->data.response;
|
|
||||||
e->event = RELAY_EVENT_RESPONSE;
|
|
||||||
e->command_seq = seq;
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Terminal output ---------------------------------------------------------
|
// --- Terminal output ---------------------------------------------------------
|
||||||
|
|
||||||
/// Default colour pair
|
/// Default colour pair
|
||||||
@@ -4515,6 +4135,387 @@ formatter_flush (struct formatter *self, FILE *stream, int flush_opts)
|
|||||||
attr_printer_reset (&state);
|
attr_printer_reset (&state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Relay output ------------------------------------------------------------
|
||||||
|
|
||||||
|
static void
|
||||||
|
client_kill (struct client *c)
|
||||||
|
{
|
||||||
|
struct app_context *ctx = c->ctx;
|
||||||
|
poller_fd_reset (&c->socket_event);
|
||||||
|
xclose (c->socket_fd);
|
||||||
|
c->socket_fd = -1;
|
||||||
|
|
||||||
|
LIST_UNLINK (ctx->clients, c);
|
||||||
|
client_destroy (c);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
client_update_poller (struct client *c, const struct pollfd *pfd)
|
||||||
|
{
|
||||||
|
int new_events = POLLIN;
|
||||||
|
if (c->write_buffer.len)
|
||||||
|
new_events |= POLLOUT;
|
||||||
|
|
||||||
|
hard_assert (new_events != 0);
|
||||||
|
if (!pfd || pfd->events != new_events)
|
||||||
|
poller_fd_set (&c->socket_event, new_events);
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
static void
|
||||||
|
relay_send (struct client *c)
|
||||||
|
{
|
||||||
|
struct relay_event_message *m = &c->ctx->relay_message;
|
||||||
|
m->event_seq = c->event_seq++;
|
||||||
|
|
||||||
|
// TODO: Also don't try sending anything if half-closed.
|
||||||
|
if (!c->initialized || c->socket_fd == -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// liberty has msg_{reader,writer} already, but they use 8-byte lengths.
|
||||||
|
size_t frame_len_pos = c->write_buffer.len, frame_len = 0;
|
||||||
|
str_pack_u32 (&c->write_buffer, 0);
|
||||||
|
if (!relay_event_message_serialize (m, &c->write_buffer)
|
||||||
|
|| (frame_len = c->write_buffer.len - frame_len_pos - 4) > UINT32_MAX)
|
||||||
|
{
|
||||||
|
print_error ("serialization failed, killing client");
|
||||||
|
client_kill (c);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t len = htonl (frame_len);
|
||||||
|
memcpy (c->write_buffer.str + frame_len_pos, &len, sizeof len);
|
||||||
|
client_update_poller (c, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
relay_broadcast_except (struct app_context *ctx, struct client *exception)
|
||||||
|
{
|
||||||
|
LIST_FOR_EACH (struct client, c, ctx->clients)
|
||||||
|
if (c != exception)
|
||||||
|
relay_send (c);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define relay_broadcast(ctx) relay_broadcast_except ((ctx), NULL)
|
||||||
|
|
||||||
|
static struct relay_event_message *
|
||||||
|
relay_prepare (struct app_context *ctx)
|
||||||
|
{
|
||||||
|
struct relay_event_message *m = &ctx->relay_message;
|
||||||
|
relay_event_message_free (m);
|
||||||
|
memset (m, 0, sizeof *m);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
relay_prepare_ping (struct app_context *ctx)
|
||||||
|
{
|
||||||
|
relay_prepare (ctx)->data.event = RELAY_EVENT_PING;
|
||||||
|
}
|
||||||
|
|
||||||
|
static union relay_item_data *
|
||||||
|
relay_translate_formatter (struct app_context *ctx, union relay_item_data *p,
|
||||||
|
const struct formatter_item *i)
|
||||||
|
{
|
||||||
|
// XXX: See attr_printer_decode_color(), this is a footgun.
|
||||||
|
int16_t c16 = i->color;
|
||||||
|
int16_t c256 = i->color >> 16;
|
||||||
|
|
||||||
|
unsigned attrs = i->attribute;
|
||||||
|
switch (i->type)
|
||||||
|
{
|
||||||
|
case FORMATTER_ITEM_TEXT:
|
||||||
|
p->text.text = str_from_cstr (i->text);
|
||||||
|
(p++)->kind = RELAY_ITEM_TEXT;
|
||||||
|
break;
|
||||||
|
case FORMATTER_ITEM_FG_COLOR:
|
||||||
|
p->fg_color.color = c256 <= 0 ? c16 : c256;
|
||||||
|
(p++)->kind = RELAY_ITEM_FG_COLOR;
|
||||||
|
break;
|
||||||
|
case FORMATTER_ITEM_BG_COLOR:
|
||||||
|
p->bg_color.color = c256 <= 0 ? c16 : c256;
|
||||||
|
(p++)->kind = RELAY_ITEM_BG_COLOR;
|
||||||
|
break;
|
||||||
|
case FORMATTER_ITEM_ATTR:
|
||||||
|
(p++)->kind = RELAY_ITEM_RESET;
|
||||||
|
if ((c256 = ctx->theme[i->attribute].fg) >= 0)
|
||||||
|
{
|
||||||
|
p->fg_color.color = c256;
|
||||||
|
(p++)->kind = RELAY_ITEM_FG_COLOR;
|
||||||
|
}
|
||||||
|
if ((c256 = ctx->theme[i->attribute].bg) >= 0)
|
||||||
|
{
|
||||||
|
p->bg_color.color = c256;
|
||||||
|
(p++)->kind = RELAY_ITEM_BG_COLOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs = ctx->theme[i->attribute].attrs;
|
||||||
|
// Fall-through
|
||||||
|
case FORMATTER_ITEM_SIMPLE:
|
||||||
|
if (attrs & TEXT_BOLD)
|
||||||
|
(p++)->kind = RELAY_ITEM_FLIP_BOLD;
|
||||||
|
if (attrs & TEXT_ITALIC)
|
||||||
|
(p++)->kind = RELAY_ITEM_FLIP_ITALIC;
|
||||||
|
if (attrs & TEXT_UNDERLINE)
|
||||||
|
(p++)->kind = RELAY_ITEM_FLIP_UNDERLINE;
|
||||||
|
if (attrs & TEXT_INVERSE)
|
||||||
|
(p++)->kind = RELAY_ITEM_FLIP_INVERSE;
|
||||||
|
if (attrs & TEXT_CROSSED_OUT)
|
||||||
|
(p++)->kind = RELAY_ITEM_FLIP_CROSSED_OUT;
|
||||||
|
if (attrs & TEXT_MONOSPACE)
|
||||||
|
(p++)->kind = RELAY_ITEM_FLIP_MONOSPACE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static union relay_item_data *
|
||||||
|
relay_items (struct app_context *ctx, const struct formatter_item *items,
|
||||||
|
uint32_t *len)
|
||||||
|
{
|
||||||
|
size_t items_len = 0;
|
||||||
|
for (size_t i = 0; items[i].type; i++)
|
||||||
|
items_len++;
|
||||||
|
|
||||||
|
// Beware of the upper bound, currently dominated by FORMATTER_ITEM_ATTR.
|
||||||
|
union relay_item_data *a = xcalloc (items_len * 9, sizeof *a), *p = a;
|
||||||
|
for (const struct formatter_item *i = items; items_len--; i++)
|
||||||
|
p = relay_translate_formatter (ctx, p, i);
|
||||||
|
|
||||||
|
*len = p - a;
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
relay_prepare_buffer_line (struct app_context *ctx, struct buffer *buffer,
|
||||||
|
struct buffer_line *line, bool leak_to_active)
|
||||||
|
{
|
||||||
|
struct relay_event_message *m = relay_prepare (ctx);
|
||||||
|
struct relay_event_data_buffer_line *e = &m->data.buffer_line;
|
||||||
|
e->event = RELAY_EVENT_BUFFER_LINE;
|
||||||
|
e->buffer_name = str_from_cstr (buffer->name);
|
||||||
|
e->is_unimportant = !!(line->flags & BUFFER_LINE_UNIMPORTANT);
|
||||||
|
e->is_highlight = !!(line->flags & BUFFER_LINE_HIGHLIGHT);
|
||||||
|
e->rendition = 1 + line->r;
|
||||||
|
e->when = line->when * 1000;
|
||||||
|
e->leak_to_active = leak_to_active;
|
||||||
|
e->items = relay_items (ctx, line->items, &e->items_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
relay_prepare_channel_buffer_update (struct app_context *ctx,
|
||||||
|
struct buffer *buffer, struct relay_buffer_context_channel *e)
|
||||||
|
{
|
||||||
|
struct channel *channel = buffer->channel;
|
||||||
|
struct formatter f = formatter_make (ctx, buffer->server);
|
||||||
|
if (channel->topic)
|
||||||
|
formatter_add (&f, "#m", channel->topic);
|
||||||
|
FORMATTER_ADD_ITEM (&f, END);
|
||||||
|
e->topic = relay_items (ctx, f.items, &e->topic_len);
|
||||||
|
formatter_free (&f);
|
||||||
|
|
||||||
|
// As in make_prompt(), conceal the last known channel modes.
|
||||||
|
// XXX: This should use irc_channel_is_joined().
|
||||||
|
if (!channel->users_len)
|
||||||
|
return;
|
||||||
|
|
||||||
|
struct str modes = str_make ();
|
||||||
|
str_append_str (&modes, &channel->no_param_modes);
|
||||||
|
|
||||||
|
struct str params = str_make ();
|
||||||
|
struct str_map_iter iter = str_map_iter_make (&channel->param_modes);
|
||||||
|
const char *param;
|
||||||
|
while ((param = str_map_iter_next (&iter)))
|
||||||
|
{
|
||||||
|
str_append_c (&modes, iter.link->key[0]);
|
||||||
|
str_append_c (¶ms, ' ');
|
||||||
|
str_append (¶ms, param);
|
||||||
|
}
|
||||||
|
|
||||||
|
str_append_str (&modes, ¶ms);
|
||||||
|
str_free (¶ms);
|
||||||
|
|
||||||
|
char *modes_utf8 = irc_to_utf8 (modes.str);
|
||||||
|
str_free (&modes);
|
||||||
|
e->modes = str_from_cstr (modes_utf8);
|
||||||
|
free (modes_utf8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
relay_prepare_buffer_update (struct app_context *ctx, struct buffer *buffer)
|
||||||
|
{
|
||||||
|
struct relay_event_message *m = relay_prepare (ctx);
|
||||||
|
struct relay_event_data_buffer_update *e = &m->data.buffer_update;
|
||||||
|
e->event = RELAY_EVENT_BUFFER_UPDATE;
|
||||||
|
e->buffer_name = str_from_cstr (buffer->name);
|
||||||
|
e->hide_unimportant = buffer->hide_unimportant;
|
||||||
|
|
||||||
|
struct str *server_name = NULL;
|
||||||
|
switch (buffer->type)
|
||||||
|
{
|
||||||
|
case BUFFER_GLOBAL:
|
||||||
|
e->context.kind = RELAY_BUFFER_KIND_GLOBAL;
|
||||||
|
break;
|
||||||
|
case BUFFER_SERVER:
|
||||||
|
e->context.kind = RELAY_BUFFER_KIND_SERVER;
|
||||||
|
server_name = &e->context.server.server_name;
|
||||||
|
break;
|
||||||
|
case BUFFER_CHANNEL:
|
||||||
|
e->context.kind = RELAY_BUFFER_KIND_CHANNEL;
|
||||||
|
server_name = &e->context.channel.server_name;
|
||||||
|
relay_prepare_channel_buffer_update (ctx, buffer, &e->context.channel);
|
||||||
|
break;
|
||||||
|
case BUFFER_PM:
|
||||||
|
e->context.kind = RELAY_BUFFER_KIND_PRIVATE_MESSAGE;
|
||||||
|
server_name = &e->context.private_message.server_name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (server_name)
|
||||||
|
*server_name = str_from_cstr (buffer->server->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
relay_prepare_buffer_stats (struct app_context *ctx, struct buffer *buffer)
|
||||||
|
{
|
||||||
|
struct relay_event_message *m = relay_prepare (ctx);
|
||||||
|
struct relay_event_data_buffer_stats *e = &m->data.buffer_stats;
|
||||||
|
e->event = RELAY_EVENT_BUFFER_STATS;
|
||||||
|
e->buffer_name = str_from_cstr (buffer->name);
|
||||||
|
e->new_messages = MIN (UINT32_MAX,
|
||||||
|
buffer->new_messages_count - buffer->new_unimportant_count);
|
||||||
|
e->new_unimportant_messages = MIN (UINT32_MAX,
|
||||||
|
buffer->new_unimportant_count);
|
||||||
|
e->highlighted = buffer->highlighted;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
relay_prepare_buffer_rename (struct app_context *ctx, struct buffer *buffer,
|
||||||
|
const char *new_name)
|
||||||
|
{
|
||||||
|
struct relay_event_message *m = relay_prepare (ctx);
|
||||||
|
struct relay_event_data_buffer_rename *e = &m->data.buffer_rename;
|
||||||
|
e->event = RELAY_EVENT_BUFFER_RENAME;
|
||||||
|
e->buffer_name = str_from_cstr (buffer->name);
|
||||||
|
e->new = str_from_cstr (new_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
relay_prepare_buffer_remove (struct app_context *ctx, struct buffer *buffer)
|
||||||
|
{
|
||||||
|
struct relay_event_message *m = relay_prepare (ctx);
|
||||||
|
struct relay_event_data_buffer_remove *e = &m->data.buffer_remove;
|
||||||
|
e->event = RELAY_EVENT_BUFFER_REMOVE;
|
||||||
|
e->buffer_name = str_from_cstr (buffer->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
relay_prepare_buffer_activate (struct app_context *ctx, struct buffer *buffer)
|
||||||
|
{
|
||||||
|
struct relay_event_message *m = relay_prepare (ctx);
|
||||||
|
struct relay_event_data_buffer_activate *e = &m->data.buffer_activate;
|
||||||
|
e->event = RELAY_EVENT_BUFFER_ACTIVATE;
|
||||||
|
e->buffer_name = str_from_cstr (buffer->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
relay_prepare_buffer_input (struct app_context *ctx, struct buffer *buffer,
|
||||||
|
const char *input)
|
||||||
|
{
|
||||||
|
struct relay_event_message *m = relay_prepare (ctx);
|
||||||
|
struct relay_event_data_buffer_input *e = &m->data.buffer_input;
|
||||||
|
e->event = RELAY_EVENT_BUFFER_INPUT;
|
||||||
|
e->buffer_name = str_from_cstr (buffer->name);
|
||||||
|
e->text = str_from_cstr (input);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
relay_prepare_buffer_clear (struct app_context *ctx,
|
||||||
|
struct buffer *buffer)
|
||||||
|
{
|
||||||
|
struct relay_event_message *m = relay_prepare (ctx);
|
||||||
|
struct relay_event_data_buffer_clear *e = &m->data.buffer_clear;
|
||||||
|
e->event = RELAY_EVENT_BUFFER_CLEAR;
|
||||||
|
e->buffer_name = str_from_cstr (buffer->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum relay_server_state
|
||||||
|
relay_server_state_for_server (struct server *s)
|
||||||
|
{
|
||||||
|
switch (s->state)
|
||||||
|
{
|
||||||
|
case IRC_DISCONNECTED: return RELAY_SERVER_STATE_DISCONNECTED;
|
||||||
|
case IRC_CONNECTING: return RELAY_SERVER_STATE_CONNECTING;
|
||||||
|
case IRC_CONNECTED: return RELAY_SERVER_STATE_CONNECTED;
|
||||||
|
case IRC_REGISTERED: return RELAY_SERVER_STATE_REGISTERED;
|
||||||
|
case IRC_CLOSING:
|
||||||
|
case IRC_HALF_CLOSED: return RELAY_SERVER_STATE_DISCONNECTING;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
relay_prepare_server_update (struct app_context *ctx, struct server *s)
|
||||||
|
{
|
||||||
|
struct relay_event_message *m = relay_prepare (ctx);
|
||||||
|
struct relay_event_data_server_update *e = &m->data.server_update;
|
||||||
|
e->event = RELAY_EVENT_SERVER_UPDATE;
|
||||||
|
e->server_name = str_from_cstr (s->name);
|
||||||
|
e->data.state = relay_server_state_for_server (s);
|
||||||
|
if (s->state == IRC_REGISTERED)
|
||||||
|
{
|
||||||
|
char *user_utf8 = irc_to_utf8 (s->irc_user->nickname);
|
||||||
|
e->data.registered.user = str_from_cstr (user_utf8);
|
||||||
|
free (user_utf8);
|
||||||
|
|
||||||
|
char *user_modes_utf8 = irc_to_utf8 (s->irc_user_modes.str);
|
||||||
|
e->data.registered.user_modes = str_from_cstr (user_modes_utf8);
|
||||||
|
free (user_modes_utf8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
relay_prepare_server_rename (struct app_context *ctx, struct server *s,
|
||||||
|
const char *new_name)
|
||||||
|
{
|
||||||
|
struct relay_event_message *m = relay_prepare (ctx);
|
||||||
|
struct relay_event_data_server_rename *e = &m->data.server_rename;
|
||||||
|
e->event = RELAY_EVENT_SERVER_RENAME;
|
||||||
|
e->server_name = str_from_cstr (s->name);
|
||||||
|
e->new = str_from_cstr (new_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
relay_prepare_server_remove (struct app_context *ctx, struct server *s)
|
||||||
|
{
|
||||||
|
struct relay_event_message *m = relay_prepare (ctx);
|
||||||
|
struct relay_event_data_server_remove *e = &m->data.server_remove;
|
||||||
|
e->event = RELAY_EVENT_SERVER_REMOVE;
|
||||||
|
e->server_name = str_from_cstr (s->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
relay_prepare_error (struct app_context *ctx, uint32_t seq, const char *message)
|
||||||
|
{
|
||||||
|
struct relay_event_message *m = relay_prepare (ctx);
|
||||||
|
struct relay_event_data_error *e = &m->data.error;
|
||||||
|
e->event = RELAY_EVENT_ERROR;
|
||||||
|
e->command_seq = seq;
|
||||||
|
e->error = str_from_cstr (message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct relay_event_data_response *
|
||||||
|
relay_prepare_response (struct app_context *ctx, uint32_t seq)
|
||||||
|
{
|
||||||
|
struct relay_event_message *m = relay_prepare (ctx);
|
||||||
|
struct relay_event_data_response *e = &m->data.response;
|
||||||
|
e->event = RELAY_EVENT_RESPONSE;
|
||||||
|
e->command_seq = seq;
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
// --- Buffers -----------------------------------------------------------------
|
// --- Buffers -----------------------------------------------------------------
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@@ -7033,9 +7034,7 @@ irc_is_highlight (struct server *s, const char *message)
|
|||||||
cstr_transform (nick, s->irc_tolower);
|
cstr_transform (nick, s->irc_tolower);
|
||||||
|
|
||||||
// Special characters allowed in nicknames by RFC 2812: []\`_^{|} and -
|
// Special characters allowed in nicknames by RFC 2812: []\`_^{|} and -
|
||||||
// Also excluded from the ASCII: common user channel prefixes: +%@&~
|
const char *delimiters = "\t\n\v\f\r !\"#$%&'()*+,./:;<=>?@~";
|
||||||
// XXX: why did I exclude those? It won't match when IRC newbies use them.
|
|
||||||
const char *delimiters = ",.;:!?()<>/=#$* \t\r\n\v\f\"'";
|
|
||||||
|
|
||||||
bool result = false;
|
bool result = false;
|
||||||
char *save = NULL;
|
char *save = NULL;
|
||||||
@@ -9306,7 +9305,7 @@ config_dump_item (struct config_item *item, struct config_dump_data *data)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Currently there's no reason for us to dump unknown items
|
// Currently there's no reason for us to dump unknown items
|
||||||
struct config_schema *schema = item->schema;
|
const struct config_schema *schema = item->schema;
|
||||||
if (!schema)
|
if (!schema)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -13182,7 +13181,7 @@ try_handle_command_help_option (struct app_context *ctx, const char *name)
|
|||||||
if (!item)
|
if (!item)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
struct config_schema *schema = item->schema;
|
const struct config_schema *schema = item->schema;
|
||||||
if (!schema)
|
if (!schema)
|
||||||
{
|
{
|
||||||
log_global_error (ctx, "#s: #s", "Option not recognized", name);
|
log_global_error (ctx, "#s: #s", "Option not recognized", name);
|
||||||
@@ -14603,21 +14602,23 @@ on_readline_input (char *line)
|
|||||||
if (*line)
|
if (*line)
|
||||||
add_history (line);
|
add_history (line);
|
||||||
|
|
||||||
// readline always erases the input line after returning from here,
|
// Readline always erases the input line after returning from here,
|
||||||
// but we don't want that to happen if the command to be executed
|
// but we don't want that to happen if the command to be executed
|
||||||
// would switch the buffer (we'd keep the already executed command in
|
// would switch the buffer (we'd keep the already executed command in
|
||||||
// the old buffer and delete any input restored from the new buffer)
|
// the old buffer and delete any input restored from the new buffer)
|
||||||
strv_append_owned (&ctx->pending_input, line);
|
strv_append_owned (&ctx->pending_input, line);
|
||||||
poller_idle_set (&ctx->input_event);
|
poller_idle_set (&ctx->input_event);
|
||||||
}
|
}
|
||||||
else
|
else if (isatty (STDIN_FILENO))
|
||||||
{
|
{
|
||||||
// Prevent readline from showing the prompt twice for w/e reason
|
// Prevent Readline from showing the prompt twice for w/e reason
|
||||||
CALL (ctx->input, hide);
|
CALL (ctx->input, hide);
|
||||||
input_rl__restore (self);
|
input_rl__restore (self);
|
||||||
|
|
||||||
CALL (ctx->input, ding);
|
CALL (ctx->input, ding);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
request_quit (ctx, NULL);
|
||||||
|
|
||||||
if (self->active)
|
if (self->active)
|
||||||
// Readline automatically redisplays it
|
// Readline automatically redisplays it
|
||||||
@@ -14757,7 +14758,7 @@ on_editline_return (EditLine *editline, int key)
|
|||||||
|
|
||||||
const LineInfoW *info = el_wline (editline);
|
const LineInfoW *info = el_wline (editline);
|
||||||
int len = info->lastchar - info->buffer;
|
int len = info->lastchar - info->buffer;
|
||||||
wchar_t *line = calloc (sizeof *info->buffer, len + 1);
|
wchar_t *line = xcalloc (len + 1, sizeof *info->buffer);
|
||||||
memcpy (line, info->buffer, sizeof *info->buffer * len);
|
memcpy (line, info->buffer, sizeof *info->buffer * len);
|
||||||
|
|
||||||
if (*line)
|
if (*line)
|
||||||
@@ -15913,7 +15914,7 @@ on_config_relay_bind_change (struct config_item *item)
|
|||||||
|
|
||||||
#ifdef TESTING
|
#ifdef TESTING
|
||||||
|
|
||||||
static struct config_schema g_config_test[] =
|
static const struct config_schema g_config_test[] =
|
||||||
{
|
{
|
||||||
{ .name = "foo", .type = CONFIG_ITEM_BOOLEAN, .default_ = "off" },
|
{ .name = "foo", .type = CONFIG_ITEM_BOOLEAN, .default_ = "off" },
|
||||||
{ .name = "bar", .type = CONFIG_ITEM_INTEGER, .default_ = "1" },
|
{ .name = "bar", .type = CONFIG_ITEM_INTEGER, .default_ = "1" },
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.0.0
|
2.1.0
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
// Copyright (c) 2023, Přemysl Eric Janouch <p@janouch.name>
|
// Copyright (c) 2023, Přemysl Eric Janouch <p@janouch.name>
|
||||||
// SPDX-License-Identifier: 0BSD
|
// SPDX-License-Identifier: 0BSD
|
||||||
//
|
//
|
||||||
|
// As an odd regression, AppKit may be necessary for JIT linking.
|
||||||
|
import AppKit
|
||||||
|
|
||||||
// NSGraphicsContext mostly just weirdly wraps over Quartz,
|
// NSGraphicsContext mostly just weirdly wraps over Quartz,
|
||||||
// so we do it all in Quartz directly.
|
// so we do it all in Quartz directly.
|
||||||
import CoreGraphics
|
import CoreGraphics
|
||||||
|
|||||||
@@ -842,11 +842,11 @@ relayRPC.onEvent = { message in
|
|||||||
|
|
||||||
b.bufferName = data.new
|
b.bufferName = data.new
|
||||||
|
|
||||||
refreshBufferList()
|
|
||||||
if b.bufferName == relayBufferCurrent {
|
if b.bufferName == relayBufferCurrent {
|
||||||
relayBufferCurrent = data.new
|
relayBufferCurrent = data.new
|
||||||
refreshStatus()
|
refreshStatus()
|
||||||
}
|
}
|
||||||
|
refreshBufferList()
|
||||||
if b.bufferName == relayBufferLast {
|
if b.bufferName == relayBufferLast {
|
||||||
relayBufferLast = data.new
|
relayBufferLast = data.new
|
||||||
}
|
}
|
||||||
@@ -1203,6 +1203,7 @@ class WindowDelegate: NSObject, NSWindowDelegate {
|
|||||||
|
|
||||||
b.highlighted = false
|
b.highlighted = false
|
||||||
refreshIcon()
|
refreshIcon()
|
||||||
|
refreshBufferList()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buffer indexes rotated to start after the current buffer.
|
// Buffer indexes rotated to start after the current buffer.
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
module janouch.name/xK/xP
|
module janouch.name/xK/xP
|
||||||
|
|
||||||
go 1.18
|
go 1.21
|
||||||
|
|
||||||
require nhooyr.io/websocket v1.8.7
|
toolchain go1.23.2
|
||||||
|
|
||||||
require (
|
require nhooyr.io/websocket v1.8.17
|
||||||
github.com/klauspost/compress v1.15.9 // indirect
|
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da // indirect
|
|
||||||
)
|
|
||||||
|
|||||||
64
xP/go.sum
64
xP/go.sum
@@ -1,62 +1,2 @@
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
nhooyr.io/websocket v1.8.17 h1:KEVeLJkUywCKVsnLIDlD/5gtayKp8VoCkksHCGGfT9Y=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
nhooyr.io/websocket v1.8.17/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
|
||||||
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
|
|
||||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
|
||||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
|
||||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
|
||||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
|
||||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
|
||||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
|
||||||
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
|
|
||||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
|
||||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
|
|
||||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
|
||||||
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
|
|
||||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
|
||||||
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
|
|
||||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
|
||||||
github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls=
|
|
||||||
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
|
|
||||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
|
||||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
|
||||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
|
||||||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
|
||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
|
||||||
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
|
||||||
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
|
|
||||||
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
|
||||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
|
||||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
|
||||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
|
||||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
|
||||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
|
||||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
|
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
|
|
||||||
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2022 - 2023, Přemysl Eric Janouch <p@janouch.name>
|
// Copyright (c) 2022 - 2024, Přemysl Eric Janouch <p@janouch.name>
|
||||||
// SPDX-License-Identifier: 0BSD
|
// SPDX-License-Identifier: 0BSD
|
||||||
import * as Relay from './proto.js'
|
import * as Relay from './proto.js'
|
||||||
|
|
||||||
@@ -695,10 +695,8 @@ let Buffer = {
|
|||||||
dom.scrollTop + dom.clientHeight + 1 >= dom.scrollHeight
|
dom.scrollTop + dom.clientHeight + 1 >= dom.scrollHeight
|
||||||
|
|
||||||
let b = buffers.get(bufferCurrent)
|
let b = buffers.get(bufferCurrent)
|
||||||
if (b !== undefined && b.highlighted && !bufferAutoscroll) {
|
if (b !== undefined && b.highlighted && !bufferAutoscroll)
|
||||||
b.highlighted = false
|
b.highlighted = false
|
||||||
m.redraw()
|
|
||||||
}
|
|
||||||
}}, lines)
|
}}, lines)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
9
xP/xP.go
9
xP/xP.go
@@ -75,12 +75,12 @@ func relayMakeReceiver(ctx context.Context, conn net.Conn) <-chan []byte {
|
|||||||
go func() {
|
go func() {
|
||||||
defer close(p)
|
defer close(p)
|
||||||
for {
|
for {
|
||||||
j := relayReadFrame(r)
|
b := relayReadFrame(r)
|
||||||
if j == nil {
|
if b == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
case p <- j:
|
case p <- b:
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -145,8 +145,7 @@ func clientWriteError(ctx context.Context, ws *websocket.Conn, err error) bool {
|
|||||||
b, ok := (&RelayEventMessage{
|
b, ok := (&RelayEventMessage{
|
||||||
EventSeq: 0,
|
EventSeq: 0,
|
||||||
Data: RelayEventData{
|
Data: RelayEventData{
|
||||||
Interface: RelayEventDataError{
|
Variant: &RelayEventDataError{
|
||||||
Event: RelayEventError,
|
|
||||||
CommandSeq: 0,
|
CommandSeq: 0,
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ AWK = env LC_ALL=C awk
|
|||||||
outputs = xS xS-replies.go xS.1
|
outputs = xS xS-replies.go xS.1
|
||||||
all: $(outputs)
|
all: $(outputs)
|
||||||
|
|
||||||
xS: xS.go ../xK-version xS-replies.go
|
xS: xS.go xS-replies.go ../xK-version
|
||||||
go build -ldflags "-X 'main.projectVersion=$$(cat ../xK-version)'" -o $@
|
go build -ldflags "-X 'main.projectVersion=$$(cat ../xK-version)'" -o $@
|
||||||
xS-replies.go: xS-gen-replies.awk xS-replies
|
xS-replies.go: xS-gen-replies.awk xS-replies
|
||||||
$(AWK) -f xS-gen-replies.awk xS-replies > $@
|
$(AWK) -f xS-gen-replies.awk xS-replies > $@
|
||||||
|
|||||||
164
xT/CMakeLists.txt
Normal file
164
xT/CMakeLists.txt
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
# As per Qt 6.8 documentation, at least 3.16 is necessary
|
||||||
|
cmake_minimum_required (VERSION 3.21.1)
|
||||||
|
|
||||||
|
file (READ ../xK-version project_version)
|
||||||
|
configure_file (../xK-version xK-version.tag COPYONLY)
|
||||||
|
string (STRIP "${project_version}" project_version)
|
||||||
|
|
||||||
|
# This is an entirely separate CMake project.
|
||||||
|
project (xT VERSION "${project_version}"
|
||||||
|
DESCRIPTION "Qt frontend for xC" LANGUAGES CXX)
|
||||||
|
|
||||||
|
set (CMAKE_CXX_STANDARD 17)
|
||||||
|
set (CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
find_package (Qt6 REQUIRED COMPONENTS Widgets Network Multimedia)
|
||||||
|
qt_standard_project_setup ()
|
||||||
|
|
||||||
|
add_compile_options ("$<$<CXX_COMPILER_ID:MSVC>:/utf-8>")
|
||||||
|
add_compile_options ("$<$<CXX_COMPILER_ID:GNU>:-Wall;-Wextra>")
|
||||||
|
add_compile_options ("$<$<CXX_COMPILER_ID:Clang>:-Wall;-Wextra>")
|
||||||
|
|
||||||
|
set (project_config "${PROJECT_BINARY_DIR}/config.h")
|
||||||
|
configure_file ("${PROJECT_SOURCE_DIR}/config.h.in" "${project_config}")
|
||||||
|
include_directories ("${PROJECT_SOURCE_DIR}" "${PROJECT_BINARY_DIR}")
|
||||||
|
|
||||||
|
# Produce a beep sample
|
||||||
|
find_program (sox_EXECUTABLE sox REQUIRED)
|
||||||
|
set (beep "${PROJECT_BINARY_DIR}/beep.wav")
|
||||||
|
add_custom_command (OUTPUT "${beep}"
|
||||||
|
COMMAND ${sox_EXECUTABLE} -b 16 -Dr 44100 -n "${beep}"
|
||||||
|
synth 0.1 0 25 triangle 800 vol 0.5 fade t 0 -0 0.005 pad 0 0.05
|
||||||
|
COMMENT "Generating a beep sample" VERBATIM)
|
||||||
|
set_property (SOURCE "${beep}" APPEND PROPERTY QT_RESOURCE_ALIAS beep.wav)
|
||||||
|
|
||||||
|
# Rasterize SVG icons
|
||||||
|
set (root "${PROJECT_SOURCE_DIR}/..")
|
||||||
|
set (CMAKE_MODULE_PATH "${root}/liberty/cmake")
|
||||||
|
include (IconUtils)
|
||||||
|
|
||||||
|
# It might generally be better to use QtSvg, though it is an extra dependency.
|
||||||
|
# The icon_to_png macro is not intended to be used like this.
|
||||||
|
foreach (icon xT xT-highlighted)
|
||||||
|
icon_to_png (${icon} "${PROJECT_SOURCE_DIR}/${icon}.svg"
|
||||||
|
48 "${PROJECT_BINARY_DIR}/resources" icon_png)
|
||||||
|
set_property (SOURCE "${icon_png}"
|
||||||
|
APPEND PROPERTY QT_RESOURCE_ALIAS "${icon}.png")
|
||||||
|
list (APPEND icon_rsrc_list "${icon_png}")
|
||||||
|
endforeach ()
|
||||||
|
|
||||||
|
if (APPLE)
|
||||||
|
set (MACOSX_BUNDLE_ICON_FILE xT.icns)
|
||||||
|
icon_to_icns ("${PROJECT_SOURCE_DIR}/xT.svg"
|
||||||
|
"${MACOSX_BUNDLE_ICON_FILE}" icon_icns)
|
||||||
|
else ()
|
||||||
|
# The largest size is mainly for an appropriately sized Windows icon
|
||||||
|
set (icon_base "${PROJECT_BINARY_DIR}/icons")
|
||||||
|
set (icon_png_list)
|
||||||
|
foreach (icon_size 16 32 48 256)
|
||||||
|
icon_to_png (xT "${PROJECT_SOURCE_DIR}/xT.svg"
|
||||||
|
${icon_size} "${icon_base}" icon_png)
|
||||||
|
list (APPEND icon_png_list "${icon_png}")
|
||||||
|
endforeach ()
|
||||||
|
add_custom_target (icons ALL DEPENDS ${icon_png_list})
|
||||||
|
if (WIN32)
|
||||||
|
list (REMOVE_ITEM icon_png_list "${icon_png}")
|
||||||
|
set (icon_ico "${PROJECT_BINARY_DIR}/xT.ico")
|
||||||
|
icon_for_win32 ("${icon_ico}" "${icon_png_list}" "${icon_png}")
|
||||||
|
|
||||||
|
set (resource_file "${PROJECT_BINARY_DIR}/xT.rc")
|
||||||
|
list (APPEND project_sources "${resource_file}")
|
||||||
|
add_custom_command (OUTPUT "${resource_file}"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E echo "1 ICON \"xT.ico\""
|
||||||
|
> ${resource_file} VERBATIM)
|
||||||
|
set_property (SOURCE "${resource_file}"
|
||||||
|
APPEND PROPERTY OBJECT_DEPENDS ${icon_ico})
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
# Build the main executable and link it
|
||||||
|
find_program (awk_EXECUTABLE awk ${find_program_REQUIRE})
|
||||||
|
add_custom_command (OUTPUT xC-proto.cpp
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E env LC_ALL=C ${awk_EXECUTABLE}
|
||||||
|
-f ${root}/liberty/tools/lxdrgen.awk
|
||||||
|
-f ${root}/liberty/tools/lxdrgen-cpp.awk
|
||||||
|
-v PrefixCamel=Relay
|
||||||
|
${root}/xC.lxdr > xC-proto.cpp
|
||||||
|
DEPENDS
|
||||||
|
${root}/liberty/tools/lxdrgen.awk
|
||||||
|
${root}/liberty/tools/lxdrgen-cpp.awk
|
||||||
|
${root}/xC.lxdr
|
||||||
|
COMMENT "Generating xC relay protocol code" VERBATIM)
|
||||||
|
add_custom_target (xC-proto DEPENDS ${PROJECT_BINARY_DIR}/xC-proto.cpp)
|
||||||
|
|
||||||
|
list (APPEND project_sources "${root}/liberty/tools/lxdrgen-cpp-qt.cpp")
|
||||||
|
qt_add_executable (xT
|
||||||
|
xT.cpp ${project_config} ${project_sources} "${icon_icns}")
|
||||||
|
add_dependencies (xT xC-proto)
|
||||||
|
qt_add_resources (xT "rsrc" PREFIX / FILES "${beep}" ${icon_rsrc_list})
|
||||||
|
target_link_libraries (xT PRIVATE Qt6::Widgets Qt6::Network Qt6::Multimedia)
|
||||||
|
set_target_properties (xT PROPERTIES WIN32_EXECUTABLE ON MACOSX_BUNDLE ON
|
||||||
|
MACOSX_BUNDLE_GUI_IDENTIFIER name.janouch.xT)
|
||||||
|
|
||||||
|
# https://stackoverflow.com/questions/79079161 and resolved in Qt Creator 16.
|
||||||
|
set (QT_QML_GENERATE_QMLLS_INI ON)
|
||||||
|
|
||||||
|
# The files to be installed
|
||||||
|
include (GNUInstallDirs)
|
||||||
|
|
||||||
|
if (ANDROID)
|
||||||
|
install (TARGETS xT DESTINATION .)
|
||||||
|
elseif (APPLE OR WIN32)
|
||||||
|
install (TARGETS xT
|
||||||
|
BUNDLE DESTINATION .
|
||||||
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
|
|
||||||
|
# XXX: QTBUG-127075, which can be circumvented by manually running
|
||||||
|
# macdeployqt on xT.app before the install.
|
||||||
|
qt_generate_deploy_app_script (TARGET xT OUTPUT_SCRIPT deploy_xT)
|
||||||
|
install (SCRIPT "${deploy_xT}")
|
||||||
|
else ()
|
||||||
|
install (TARGETS xT DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
|
install (FILES ../LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||||
|
|
||||||
|
install (FILES xT.svg
|
||||||
|
DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps)
|
||||||
|
install (DIRECTORY ${icon_base}
|
||||||
|
DESTINATION ${CMAKE_INSTALL_DATADIR})
|
||||||
|
install (FILES xT.desktop
|
||||||
|
DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
# Within MSYS2, windeployqt doesn't copy the compiler runtime,
|
||||||
|
# which is always linked dynamically by the Qt binaries.
|
||||||
|
# TODO(p): Consider whether or not to use MSYS2 to cross-compile, and how.
|
||||||
|
if (WIN32)
|
||||||
|
install (CODE [=[
|
||||||
|
set (bindir "${CMAKE_INSTALL_PREFIX}/bin")
|
||||||
|
execute_process (COMMAND cygpath -m /
|
||||||
|
OUTPUT_VARIABLE cygroot OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||||
|
if (cygroot)
|
||||||
|
execute_process (COMMAND ldd "${bindir}/xT.exe"
|
||||||
|
OUTPUT_VARIABLE ldd_output OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||||
|
string (REGEX MATCHALL " /mingw64/bin/[^ ]+ " libs "${ldd_output}")
|
||||||
|
foreach (lib ${libs})
|
||||||
|
string (STRIP "${lib}" lib)
|
||||||
|
file (COPY "${cygroot}${lib}" DESTINATION "${bindir}")
|
||||||
|
endforeach()
|
||||||
|
endif ()
|
||||||
|
]=])
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
# CPack
|
||||||
|
set (CPACK_PACKAGE_VENDOR "Premysl Eric Janouch")
|
||||||
|
set (CPACK_PACKAGE_CONTACT "Přemysl Eric Janouch <p@janouch.name>")
|
||||||
|
set (CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/../LICENSE")
|
||||||
|
set (CPACK_GENERATOR "TGZ;ZIP")
|
||||||
|
set (CPACK_PACKAGE_FILE_NAME
|
||||||
|
"${PROJECT_NAME}-${PROJECT_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}")
|
||||||
|
set (CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME} ${PROJECT_VERSION}")
|
||||||
|
set (CPACK_SOURCE_GENERATOR "TGZ;ZIP")
|
||||||
|
set (CPACK_SOURCE_IGNORE_FILES "/build;/CMakeLists.txt.user")
|
||||||
|
set (CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}")
|
||||||
|
|
||||||
|
include (CPack)
|
||||||
7
xT/config.h.in
Normal file
7
xT/config.h.in
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#ifndef CONFIG_H
|
||||||
|
#define CONFIG_H
|
||||||
|
|
||||||
|
#define PROJECT_NAME "${PROJECT_NAME}"
|
||||||
|
#define PROJECT_VERSION "${project_version}"
|
||||||
|
|
||||||
|
#endif // ! CONFIG_H
|
||||||
29
xT/xT-highlighted.svg
Normal file
29
xT/xT-highlighted.svg
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg version="1.1" width="48" height="48" viewBox="0 0 48 48"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="green-x">
|
||||||
|
<stop stop-color="hsl(66, 100%, 80%)" offset="0" />
|
||||||
|
<stop stop-color="hsl(66, 100%, 50%)" offset="1" />
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="orange">
|
||||||
|
<stop stop-color="hsl(36, 100%, 60%)" offset="0" />
|
||||||
|
<stop stop-color="hsl(23, 100%, 60%)" offset="1" />
|
||||||
|
</radialGradient>
|
||||||
|
<filter id="shadow" x="-50%" y="-50%" width="200%" height="200%">
|
||||||
|
<feDropShadow dx="0" dy="0" stdDeviation="0.05"
|
||||||
|
flood-color="rgba(0, 0, 0, .5)" />
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- XXX: librsvg screws up shadows on rotated objects. -->
|
||||||
|
<g filter="url(#shadow)" transform="translate(24 3) scale(16)">
|
||||||
|
<path fill="url(#orange)" stroke="hsl(36, 100%, 20%)" stroke-width="0.1"
|
||||||
|
d="M-.8 0 H.8 V.5 H.25 V2.625 H-.25 V.5 H-.8 Z" />
|
||||||
|
</g>
|
||||||
|
<g filter="url(#shadow)" transform="translate(24 28) rotate(-45) scale(16)">
|
||||||
|
<path fill="url(#green-x)" stroke="hsl(66, 100%, 20%)" stroke-width="0.1"
|
||||||
|
d="M-.25 -1 H.25 V-.25 H1 V.25 H.25 V1 H-.25 V.25 H-1 V-.25 H-.25 Z" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
8
xT/xT.desktop
Normal file
8
xT/xT.desktop
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=xT
|
||||||
|
GenericName=IRC Client
|
||||||
|
Icon=xT
|
||||||
|
Exec=xT
|
||||||
|
StartupNotify=false
|
||||||
|
Categories=Network;Chat;IRCClient;
|
||||||
29
xT/xT.svg
Normal file
29
xT/xT.svg
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg version="1.1" width="48" height="48" viewBox="0 0 48 48"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="grey-x">
|
||||||
|
<stop stop-color="hsl(66, 0%, 90%)" offset="0" />
|
||||||
|
<stop stop-color="hsl(66, 0%, 80%)" offset="1" />
|
||||||
|
</radialGradient>
|
||||||
|
<radialGradient id="orange">
|
||||||
|
<stop stop-color="hsl(36, 100%, 60%)" offset="0" />
|
||||||
|
<stop stop-color="hsl(23, 100%, 60%)" offset="1" />
|
||||||
|
</radialGradient>
|
||||||
|
<filter id="shadow" x="-50%" y="-50%" width="200%" height="200%">
|
||||||
|
<feDropShadow dx="0" dy="0" stdDeviation="0.05"
|
||||||
|
flood-color="rgba(0, 0, 0, .5)" />
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<!-- XXX: librsvg screws up shadows on rotated objects. -->
|
||||||
|
<g filter="url(#shadow)" transform="translate(24 28) rotate(-45) scale(16)">
|
||||||
|
<path fill="url(#grey-x)" stroke="hsl(66, 0%, 30%)" stroke-width="0.1"
|
||||||
|
d="M-.25 -1 H.25 V-.25 H1 V.25 H.25 V1 H-.25 V.25 H-1 V-.25 H-.25 Z" />
|
||||||
|
</g>
|
||||||
|
<g filter="url(#shadow)" transform="translate(24 3) scale(16)">
|
||||||
|
<path fill="url(#orange)" stroke="hsl(36, 100%, 20%)" stroke-width="0.1"
|
||||||
|
d="M-.8 0 H.8 V.5 H.25 V2.625 H-.25 V.5 H-.8 Z" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
181
xW/xW.cpp
181
xW/xW.cpp
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* xW.cpp: Win32 frontend for xC
|
* xW.cpp: Win32 frontend for xC
|
||||||
*
|
*
|
||||||
* Copyright (c) 2023, Přemysl Eric Janouch <p@janouch.name>
|
* Copyright (c) 2023 - 2024, Přemysl Eric Janouch <p@janouch.name>
|
||||||
*
|
*
|
||||||
* Permission to use, copy, modify, and/or distribute this software for any
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
* purpose with or without fee is hereby granted.
|
* purpose with or without fee is hereby granted.
|
||||||
@@ -255,73 +255,6 @@ buffer_by_name(const std::wstring &name)
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
buffer_activate(const std::wstring &name)
|
|
||||||
{
|
|
||||||
auto activate = new Relay::CommandData_BufferActivate();
|
|
||||||
activate->buffer_name = name;
|
|
||||||
relay_send(activate);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
buffer_toggle_unimportant(const std::wstring &name)
|
|
||||||
{
|
|
||||||
auto toggle = new Relay::CommandData_BufferToggleUnimportant();
|
|
||||||
toggle->buffer_name = name;
|
|
||||||
relay_send(toggle);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Current buffer ----------------------------------------------------------
|
|
||||||
|
|
||||||
static void
|
|
||||||
buffer_toggle_log(
|
|
||||||
const std::wstring &error, const Relay::ResponseData_BufferLog *response)
|
|
||||||
{
|
|
||||||
if (!response) {
|
|
||||||
show_error_message(error.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::wstring log;
|
|
||||||
if (!LibertyXDR::utf8_to_wstring(
|
|
||||||
response->log.data(), response->log.size(), log)) {
|
|
||||||
show_error_message(L"Invalid encoding.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::wstring filtered;
|
|
||||||
for (auto wch : log) {
|
|
||||||
if (wch == L'\n')
|
|
||||||
filtered += L"\r\n";
|
|
||||||
else
|
|
||||||
filtered += wch;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetWindowText(g.hwndBufferLog, filtered.c_str());
|
|
||||||
ShowWindow(g.hwndBuffer, SW_HIDE);
|
|
||||||
ShowWindow(g.hwndBufferLog, SW_SHOW);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
buffer_toggle_log()
|
|
||||||
{
|
|
||||||
if (IsWindowVisible(g.hwndBufferLog)) {
|
|
||||||
ShowWindow(g.hwndBufferLog, SW_HIDE);
|
|
||||||
ShowWindow(g.hwndBuffer, SW_SHOW);
|
|
||||||
SetWindowText(g.hwndBufferLog, L"");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto log = new Relay::CommandData_BufferLog();
|
|
||||||
log->buffer_name = g.buffer_current;
|
|
||||||
relay_send(log, [name = g.buffer_current](auto error, auto response) {
|
|
||||||
if (g.buffer_current != name)
|
|
||||||
return;
|
|
||||||
buffer_toggle_log(error,
|
|
||||||
dynamic_cast<const Relay::ResponseData_BufferLog *>(response));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
buffer_at_bottom()
|
buffer_at_bottom()
|
||||||
{
|
{
|
||||||
@@ -354,6 +287,7 @@ refresh_icon()
|
|||||||
if (b.highlighted)
|
if (b.highlighted)
|
||||||
icon = g.hiconHighlighted;
|
icon = g.hiconHighlighted;
|
||||||
|
|
||||||
|
// XXX: This may not change the taskbar icon.
|
||||||
SendMessage(g.hwndMain, WM_SETICON, ICON_SMALL, (LPARAM) icon);
|
SendMessage(g.hwndMain, WM_SETICON, ICON_SMALL, (LPARAM) icon);
|
||||||
SendMessage(g.hwndMain, WM_SETICON, ICON_BIG, (LPARAM) icon);
|
SendMessage(g.hwndMain, WM_SETICON, ICON_BIG, (LPARAM) icon);
|
||||||
}
|
}
|
||||||
@@ -430,6 +364,88 @@ refresh_status()
|
|||||||
SetWindowText(g.hwndStatus, status.c_str());
|
SetWindowText(g.hwndStatus, status.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
recheck_highlighted()
|
||||||
|
{
|
||||||
|
// Corresponds to the logic toggling the bool on.
|
||||||
|
auto b = buffer_by_name(g.buffer_current);
|
||||||
|
if (b && b->highlighted && buffer_at_bottom() &&
|
||||||
|
!IsIconic(g.hwndMain) && !IsWindowVisible(g.hwndBufferLog)) {
|
||||||
|
b->highlighted = false;
|
||||||
|
refresh_icon();
|
||||||
|
refresh_buffer_list();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Buffer actions ----------------------------------------------------------
|
||||||
|
|
||||||
|
static void
|
||||||
|
buffer_activate(const std::wstring &name)
|
||||||
|
{
|
||||||
|
auto activate = new Relay::CommandData_BufferActivate();
|
||||||
|
activate->buffer_name = name;
|
||||||
|
relay_send(activate);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
buffer_toggle_unimportant(const std::wstring &name)
|
||||||
|
{
|
||||||
|
auto toggle = new Relay::CommandData_BufferToggleUnimportant();
|
||||||
|
toggle->buffer_name = name;
|
||||||
|
relay_send(toggle);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
buffer_toggle_log(
|
||||||
|
const std::wstring &error, const Relay::ResponseData_BufferLog *response)
|
||||||
|
{
|
||||||
|
if (!response) {
|
||||||
|
show_error_message(error.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring log;
|
||||||
|
if (!LibertyXDR::utf8_to_wstring(
|
||||||
|
response->log.data(), response->log.size(), log)) {
|
||||||
|
show_error_message(L"Invalid encoding.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring filtered;
|
||||||
|
for (auto wch : log) {
|
||||||
|
if (wch == L'\n')
|
||||||
|
filtered += L"\r\n";
|
||||||
|
else
|
||||||
|
filtered += wch;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetWindowText(g.hwndBufferLog, filtered.c_str());
|
||||||
|
ShowWindow(g.hwndBuffer, SW_HIDE);
|
||||||
|
ShowWindow(g.hwndBufferLog, SW_SHOW);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
buffer_toggle_log()
|
||||||
|
{
|
||||||
|
if (IsWindowVisible(g.hwndBufferLog)) {
|
||||||
|
ShowWindow(g.hwndBufferLog, SW_HIDE);
|
||||||
|
ShowWindow(g.hwndBuffer, SW_SHOW);
|
||||||
|
SetWindowText(g.hwndBufferLog, L"");
|
||||||
|
|
||||||
|
recheck_highlighted();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto log = new Relay::CommandData_BufferLog();
|
||||||
|
log->buffer_name = g.buffer_current;
|
||||||
|
relay_send(log, [name = g.buffer_current](auto error, auto response) {
|
||||||
|
if (g.buffer_current != name)
|
||||||
|
return;
|
||||||
|
buffer_toggle_log(error,
|
||||||
|
dynamic_cast<const Relay::ResponseData_BufferLog *>(response));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// --- Rich Edit formatting ----------------------------------------------------
|
// --- Rich Edit formatting ----------------------------------------------------
|
||||||
|
|
||||||
static COLORREF
|
static COLORREF
|
||||||
@@ -695,7 +711,7 @@ buffer_print_line(std::vector<BufferLine>::const_iterator begin,
|
|||||||
static void
|
static void
|
||||||
buffer_print_separator()
|
buffer_print_separator()
|
||||||
{
|
{
|
||||||
bool sameline = !GetWindowTextLength(g.hwndBuffer);
|
bool sameline = !buffer_reset_selection();
|
||||||
|
|
||||||
CHARFORMAT2 format = default_charformat();
|
CHARFORMAT2 format = default_charformat();
|
||||||
format.dwEffects &= ~CFE_AUTOCOLOR;
|
format.dwEffects &= ~CFE_AUTOCOLOR;
|
||||||
@@ -728,6 +744,7 @@ refresh_buffer(const Buffer &b)
|
|||||||
|
|
||||||
buffer_print_and_watch_trailing_date_changes();
|
buffer_print_and_watch_trailing_date_changes();
|
||||||
buffer_scroll_to_bottom();
|
buffer_scroll_to_bottom();
|
||||||
|
// We will get a scroll event, so no need to recheck_highlighted() here.
|
||||||
|
|
||||||
SendMessage(g.hwndBuffer, WM_SETREDRAW, (WPARAM) TRUE, 0);
|
SendMessage(g.hwndBuffer, WM_SETREDRAW, (WPARAM) TRUE, 0);
|
||||||
InvalidateRect(g.hwndBuffer, NULL, TRUE);
|
InvalidateRect(g.hwndBuffer, NULL, TRUE);
|
||||||
@@ -749,8 +766,9 @@ relay_process_buffer_line(Buffer &b, Relay::EventData_BufferLine &m)
|
|||||||
// Retained mode is complicated.
|
// Retained mode is complicated.
|
||||||
bool display = (!m.is_unimportant || !bc->hide_unimportant) &&
|
bool display = (!m.is_unimportant || !bc->hide_unimportant) &&
|
||||||
(b.buffer_name == g.buffer_current || m.leak_to_active);
|
(b.buffer_name == g.buffer_current || m.leak_to_active);
|
||||||
|
// XXX: It would be great if it didn't autoscroll when focused.
|
||||||
bool to_bottom = display &&
|
bool to_bottom = display &&
|
||||||
buffer_at_bottom();
|
(buffer_at_bottom() || GetFocus() == g.hwndBuffer);
|
||||||
bool visible = display &&
|
bool visible = display &&
|
||||||
to_bottom &&
|
to_bottom &&
|
||||||
!IsIconic(g.hwndMain) &&
|
!IsIconic(g.hwndMain) &&
|
||||||
@@ -914,11 +932,11 @@ relay_process_message(const Relay::EventMessage &m)
|
|||||||
|
|
||||||
b->buffer_name = data.new_;
|
b->buffer_name = data.new_;
|
||||||
|
|
||||||
refresh_buffer_list();
|
|
||||||
if (data.buffer_name == g.buffer_current) {
|
if (data.buffer_name == g.buffer_current) {
|
||||||
g.buffer_current = data.new_;
|
g.buffer_current = data.new_;
|
||||||
refresh_status();
|
refresh_status();
|
||||||
}
|
}
|
||||||
|
refresh_buffer_list();
|
||||||
if (data.buffer_name == g.buffer_last)
|
if (data.buffer_name == g.buffer_last)
|
||||||
g.buffer_last = data.new_;
|
g.buffer_last = data.new_;
|
||||||
break;
|
break;
|
||||||
@@ -1465,6 +1483,7 @@ richedit_proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
|
|||||||
{
|
{
|
||||||
// Dragging the scrollbar doesn't result in EN_VSCROLL.
|
// Dragging the scrollbar doesn't result in EN_VSCROLL.
|
||||||
LRESULT lResult = DefSubclassProc(hWnd, uMsg, wParam, lParam);
|
LRESULT lResult = DefSubclassProc(hWnd, uMsg, wParam, lParam);
|
||||||
|
recheck_highlighted();
|
||||||
refresh_status();
|
refresh_status();
|
||||||
return lResult;
|
return lResult;
|
||||||
}
|
}
|
||||||
@@ -1522,8 +1541,12 @@ process_resize(UINT w, UINT h)
|
|||||||
MoveWindow(g.hwndBufferList, 3, top, 150, h - top - bottom, FALSE);
|
MoveWindow(g.hwndBufferList, 3, top, 150, h - top - bottom, FALSE);
|
||||||
MoveWindow(g.hwndBuffer, 156, top, w - 159, h - top - bottom, FALSE);
|
MoveWindow(g.hwndBuffer, 156, top, w - 159, h - top - bottom, FALSE);
|
||||||
MoveWindow(g.hwndBufferLog, 156, top, w - 159, h - top - bottom, FALSE);
|
MoveWindow(g.hwndBufferLog, 156, top, w - 159, h - top - bottom, FALSE);
|
||||||
if (to_bottom)
|
if (to_bottom) {
|
||||||
buffer_scroll_to_bottom();
|
buffer_scroll_to_bottom();
|
||||||
|
} else {
|
||||||
|
recheck_highlighted();
|
||||||
|
refresh_status();
|
||||||
|
}
|
||||||
|
|
||||||
InvalidateRect(g.hwndMain, NULL, TRUE);
|
InvalidateRect(g.hwndMain, NULL, TRUE);
|
||||||
}
|
}
|
||||||
@@ -1685,8 +1708,10 @@ window_proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|||||||
}
|
}
|
||||||
case WM_SYSCOMMAND:
|
case WM_SYSCOMMAND:
|
||||||
{
|
{
|
||||||
|
// We're not deiconified yet, so duplicate recheck_highlighted().
|
||||||
auto b = buffer_by_name(g.buffer_current);
|
auto b = buffer_by_name(g.buffer_current);
|
||||||
if (b && wParam == SC_RESTORE) {
|
if (wParam == SC_RESTORE && b && b->highlighted && buffer_at_bottom() &&
|
||||||
|
!IsWindowVisible(g.hwndBufferLog)) {
|
||||||
b->highlighted = false;
|
b->highlighted = false;
|
||||||
refresh_icon();
|
refresh_icon();
|
||||||
}
|
}
|
||||||
@@ -1694,13 +1719,15 @@ window_proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case WM_COMMAND:
|
case WM_COMMAND:
|
||||||
if (!lParam)
|
if (!lParam) {
|
||||||
process_accelerator(LOWORD(wParam));
|
process_accelerator(LOWORD(wParam));
|
||||||
else if (lParam == (LPARAM) g.hwndBufferList)
|
} else if (lParam == (LPARAM) g.hwndBufferList) {
|
||||||
process_bufferlist_notification(HIWORD(wParam));
|
process_bufferlist_notification(HIWORD(wParam));
|
||||||
else if (lParam == (LPARAM) g.hwndBuffer &&
|
} else if (lParam == (LPARAM) g.hwndBuffer &&
|
||||||
HIWORD(wParam) == EN_VSCROLL)
|
HIWORD(wParam) == EN_VSCROLL) {
|
||||||
|
recheck_highlighted();
|
||||||
refresh_status();
|
refresh_status();
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
case WM_NOTIFY:
|
case WM_NOTIFY:
|
||||||
switch (((LPNMHDR) lParam)->code) {
|
switch (((LPNMHDR) lParam)->code) {
|
||||||
|
|||||||
Reference in New Issue
Block a user