Compare commits

..

263 Commits

Author SHA1 Message Date
Přemysl Eric Janouch a011b57ce2
Bump liberty
Alpine 3.20 Success Details
Arch Linux AUR Success Details
OpenBSD 7.5 Success Details
2024-11-07 11:07:36 +01:00
Přemysl Eric Janouch b1ee295345
xP: update variable name 2024-11-04 07:40:14 +01:00
Přemysl Eric Janouch 872f2d7c59
Fix calloc argument order
Alpine 3.20 Success Details
Arch Linux AUR Success Details
OpenBSD 7.5 Success Details
2024-08-08 09:13:25 +02:00
Přemysl Eric Janouch f15d887dcd
Bump liberty
Alpine 3.20 Success Details
Arch Linux AUR Success Details
OpenBSD 7.5 Success Details
2024-08-08 09:02:25 +02:00
Přemysl Eric Janouch 841bc00c51
xP: cleanup
I had forgotten about the auto-redraw system.
2024-07-28 13:42:28 +02:00
Přemysl Eric Janouch 8afe4f8aad
Improve wording in the last NEWS entry 2024-07-28 13:26:24 +02:00
Přemysl Eric Janouch 73cc8f448a
Bump version, update NEWS
Alpine 3.20 Success Details
Arch Linux AUR Success Details
OpenBSD 7.5 Success Details
2024-07-28 07:29:15 +02:00
Přemysl Eric Janouch 4565afe294
xC: expand a comment 2024-07-28 07:15:41 +02:00
Přemysl Eric Janouch 3ad8c79de8
xC: handle multiline server commands properly
Alpine 3.20 Success Details
Arch Linux AUR Success Details
OpenBSD 7.5 Success Details
Additional lines might have been passed to the server intact
as part of an argument, but we have /quote for that.
2024-07-28 04:10:30 +02:00
Přemysl Eric Janouch 12fc3c228a
xP: reset highlight state once reaching buffer end
Alpine 3.20 Success Details
Arch Linux AUR Success Details
OpenBSD 7.5 Success Details
2024-07-28 03:44:37 +02:00
Přemysl Eric Janouch 175533a5e9
xP: don't interrupt IME composition
Alpine 3.20 Success Details
Arch Linux AUR Success Details
OpenBSD 7.5 Success Details
On Vivaldi/macOS, pressing Enter would send the input and still keep
editing it as it was.
2024-07-04 20:06:59 +02:00
Přemysl Eric Janouch a9b46141a9
xS/xN: add test targets
Alpine 3.19 Success Details
Arch Linux AUR Success Details
OpenBSD 7.3 Success Details
2024-04-10 13:59:33 +02:00
Přemysl Eric Janouch c38cca3b92
Bump liberty
Arch Linux AUR Success Details
Alpine 3.19 Success Details
2024-04-09 17:08:40 +02:00
Přemysl Eric Janouch aee7540faa
Update README.adoc and xN usage output 2024-04-04 21:25:17 +02:00
Přemysl Eric Janouch 53ba996ec9
Add a simple IRC notifier utility
Arch Linux AUR Success Details
2024-04-03 15:56:33 +02:00
Přemysl Eric Janouch d450c6cc5f
xP: do not send the Referrer header 2024-03-04 16:15:22 +01:00
Přemysl Eric Janouch f8ea1634c4
Bump liberty 2024-03-04 16:15:22 +01:00
Přemysl Eric Janouch ef257cd575
xP: avoid expensive updates/refreshes 2024-01-06 23:44:11 +01:00
Přemysl Eric Janouch 69eccc7065
xP: don't let buffers grow indefinitely
Primarily for performance reasons.
2024-01-06 21:17:18 +01:00
Přemysl Eric Janouch 13d2ff115b
xM: improve the bundle icon a bit 2023-09-04 07:06:03 +02:00
Přemysl Eric Janouch 9e4692bb09
xM: generate and use a bundle icon 2023-09-03 02:13:14 +02:00
Přemysl Eric Janouch 1c4343058d
Add a Cocoa frontend for xC
Some work remains to be done to get it to be even as good
as the Win32 frontend, but it's generally usable.
2023-09-01 01:12:51 +02:00
Přemysl Eric Janouch e5156cddbf
xW: render leaked lines a bit more accurately
There is no need to reset all text attributes, just the colour.
2023-08-25 22:48:31 +02:00
Přemysl Eric Janouch 34521e61c1
xP/xW: fix buffer rename handling
Maintaining string pointers to the current/last buffer
means that renames invalidate them.
2023-08-25 22:48:31 +02:00
Přemysl Eric Janouch c22dd67fc1
xC: send missing relay events for newly added servers 2023-08-25 22:48:27 +02:00
Přemysl Eric Janouch 274d5f03e7
xC: give the /away command a proper handler
Multiple words should be passed to the server as a single argument.
2023-08-25 22:46:43 +02:00
Přemysl Eric Janouch 2f19e5a733
xW: improve command sending 2023-07-29 02:15:24 +02:00
Přemysl Eric Janouch b9cdabca5d
xC: fix relay handling of missing log files
Intermediate error messages would trash the prepared static buffer.
2023-07-28 04:30:45 +02:00
Přemysl Eric Janouch f60ca43156
xW: do not unnecessarily enter compatibility mode 2023-07-28 04:30:45 +02:00
Přemysl Eric Janouch afe4e61f08
xW: mark a footgun
These messages are used by IsDialogMessage(), and use the WM_USER range.
2023-07-27 23:08:16 +02:00
Přemysl Eric Janouch 8d9d1c60ec
xW: make Up/Down go through input history
The input field isn't multiline, so this doesn't pose an issue.
Otherwise, we'd have to check if we're on the top line first.
2023-07-27 16:35:54 +02:00
Přemysl Eric Janouch 8c1464822b
xW: don't delay sending out pongs 2023-07-27 16:19:32 +02:00
Přemysl Eric Janouch fcd1b8e011
xW: improve beeping
This adds yet another build dependency,
but it's better than the alternatives of handling it in code.
2023-07-27 16:06:41 +02:00
Přemysl Eric Janouch 3d345987c3
xW: cleanup 2023-07-27 02:37:20 +02:00
Přemysl Eric Janouch 3e37efd9cd
xW: show a connect dialog when run without args 2023-07-27 01:28:52 +02:00
Přemysl Eric Janouch efb25b8aae
xW: un-highlight the icon when activating buffers 2023-07-26 16:07:21 +02:00
Přemysl Eric Janouch e72793e315
xW: make newline before unread marker conditional 2023-07-26 16:07:20 +02:00
Přemysl Eric Janouch 5a412ab6e2
xW: handle WM_SYSCOLORCHANGE 2023-07-26 16:07:20 +02:00
Přemysl Eric Janouch 81bc578773
xW: add missing date change handling 2023-07-26 03:59:25 +02:00
Přemysl Eric Janouch 100de5ac2d
xC: fix Readline 6.3 compatibility 2023-07-24 07:59:22 +02:00
Přemysl Eric Janouch c157d3369f
xP: make Page Up/Down in editor scroll the buffer
Just like in xW recently.  It is unlikely that the user would want
to use these keys to move the cursor.  Ctrl+Home/End still work,
as does holding Up/Down arrows.

Also stop using the deprecated and somewhat cryptic keyCode.
2023-07-23 00:20:32 +02:00
Přemysl Eric Janouch 8b5ea67aff
xW: fix Clang build 2023-07-21 12:37:01 +02:00
Přemysl Eric Janouch 6f02af814f
xW: store the largest program icon in PNG format
This shaves off about half a megabyte.
2023-07-16 08:35:39 +02:00
Přemysl Eric Janouch 90859107c8
xW: set version information 2023-07-15 23:35:46 +02:00
Přemysl Eric Janouch 0219dbd026
Add a Win32 frontend for xC
This has been more of an exercise.  The performance of Msftedit.dll
is rather abysmal, and its interface isn't the most accomodating.

That said, the frontend is quite usable, at least on Windows 10+.
2023-07-15 17:00:21 +02:00
Přemysl Eric Janouch 1da4699a7a
Cleanup 2023-07-09 09:35:16 +02:00
Přemysl Eric Janouch 9e993c50e6
xC: don't crash with unknown terminals
It would be possible to avoid using cur_term fields in this case,
but the program would likely be of little use anyway.
2023-07-07 10:43:15 +02:00
Přemysl Eric Janouch b3e9218b23
Fix Cygwin build warnings 2023-07-07 09:53:20 +02:00
Přemysl Eric Janouch bc8867eb22
Fix the integration test
IRCv3 capabilities broke it a bit.

Also change it so that it doesn't destroy existing configuration.
2023-07-05 00:16:55 +02:00
Přemysl Eric Janouch ec33adba35
Update README.adoc 2023-07-04 23:50:01 +02:00
Přemysl Eric Janouch 6f596f1dcb
Move project version to file, add xS manual page
So far Go applications remain independent to handle Nix's inability
to easily combine them with the CMake part.

There is also no "install" target, because any packagers will want to
adjust installation paths manually, and there is no configure step.
2023-07-04 23:26:05 +02:00
Přemysl Eric Janouch abcff46dc4
xC: fix an OpenBSD build warning
sys/cdefs.h makes _XOPEN_SOURCE cause _POSIX_C_SOURCE to be overriden.
2023-07-04 06:31:02 +02:00
Přemysl Eric Janouch 8d9ce92758
README.adoc: update package information 2023-07-01 22:02:10 +02:00
Přemysl Eric Janouch 4bb9449e47
Fix the static analysis test
Adjust its query so that it doesn't cause a particular false positive.
2023-06-16 19:45:12 +02:00
Přemysl Eric Janouch 50f70f93bb
xC: fix a harmless copy-paste error 2023-06-13 09:02:20 +02:00
Přemysl Eric Janouch 3f9a365d36
xC: improve the --format mode
Avoid having formatting spill over the rest of the line,
by placing the automatic formatting reset before newlines.

Also handle longer lines properly.
2023-05-22 04:44:01 +02:00
Přemysl Eric Janouch 9932b35a10
xP: highlight hovered buffer list items
To make it apparent which one would be closed by a middle click.
2023-04-14 10:58:19 +02:00
Přemysl Eric Janouch af5f209c53
xP: make middle click close buffers
As if they were tabs, to save pointless typing.
2023-04-13 04:26:40 +02:00
Přemysl Eric Janouch 6bfe577f1b
xP: make the buffer list selectable by Vimium 2023-04-05 23:10:41 +02:00
Přemysl Eric Janouch 1079189381
xP: render date changes as they happen 2023-01-25 00:31:57 +01:00
Přemysl Eric Janouch c58b772905
xP: use the correct log function 2023-01-25 00:28:03 +01:00
Přemysl Eric Janouch 26ed2dbc77
xC: fully synchronize input history with frontends
The missing parts were:

 - frontends to client
 - client to frontends after the initial sync
 - frontend to other frontends
2022-10-05 00:55:59 +02:00
Přemysl Eric Janouch 4b7258cba0
xP: fix ESC H detection on Macintosh systems 2022-10-04 20:17:31 +02:00
Přemysl Eric Janouch 9dc3dd02f3
xP: disable WebSocket compression on Safari
Wildly known to be broken.
2022-10-04 01:17:35 +02:00
Přemysl Eric Janouch a7c3ed7cc1
xC: clean up 2022-09-30 18:30:03 +02:00
Přemysl Eric Janouch 807a8c37d9
Bump liberty, improve fallback manual page output 2022-09-30 18:17:23 +02:00
Přemysl Eric Janouch c4707e2803
xC/xP: send buffer input history during sync
This transfer is currenly quite simplistic,
but it paves the way for further extensions.
2022-09-30 17:36:29 +02:00
Přemysl Eric Janouch 46d68eacce
Move protocol code generators to liberty
This part of the project is now more or less stable.
2022-09-30 03:24:24 +02:00
Přemysl Eric Janouch 86278c154c
Clean up protocol code generators 2022-09-30 03:24:13 +02:00
Přemysl Eric Janouch 941ee2f10c
xP: fix automatic scrolling down
Showing channel logs cancelled the AbortController forever.
Thus store it within vnodes.
2022-09-28 21:29:08 +02:00
Přemysl Eric Janouch 5b57e9b41b
xC/xP: fix unseen message counting
xC: advance unread message counters even with leaked messages,
and don't unnecessarily set the highlighted flag.  Plus clean up.

xP: make leaked non-unimportant messages advance the counter
for unimportant messages, so that the buffer doesn't get emboldened.
2022-09-28 21:20:59 +02:00
Přemysl Eric Janouch 4d99690b89
xS: parse project version from CMakeLists.txt 2022-09-27 23:48:12 +02:00
Přemysl Eric Janouch 7c74e6615d
xD: use SHA-256 for certificate fingerprints
Just like xS.  2.0.0 is the ideal time for such a breaking change.
2022-09-26 13:58:08 +02:00
Přemysl Eric Janouch 614fd98fc1
Update README 2022-09-26 13:42:45 +02:00
Přemysl Eric Janouch 5863040f93
Update documentation, clean up 2022-09-26 13:24:24 +02:00
Přemysl Eric Janouch f891e5ca63
Merge hid IRCd from haven as xS
Given that this project already contains a Go binary,
it only makes sense to put the IRCds back together.
2022-09-26 12:41:47 +02:00
Přemysl Eric Janouch 8344b09c4f
hid: rename to xS before merge into xK 2022-09-26 12:23:58 +02:00
Přemysl Eric Janouch 4d50ed111a
Bump liberty, make use of its new asciiman.awk 2022-09-25 21:02:51 +02:00
Přemysl Eric Janouch e15c9cba43
xP: use a dotted border for button focus
It's visible enough.
2022-09-25 11:08:50 +02:00
Přemysl Eric Janouch 66370387bc
Update documentation 2022-09-23 20:35:56 +02:00
Přemysl Eric Janouch 2bc3ac7b0d
Update screenshots
It's been five years since the previous xC screenshot was made.
2022-09-23 19:00:31 +02:00
Přemysl Eric Janouch 2c54f5a5dd
xP: make the overlay actually overlay 2022-09-23 19:00:31 +02:00
Přemysl Eric Janouch f2d8de3ab9
xP: support adding formatting from keyboard
Just like in xC, only with some indication.
2022-09-23 09:42:24 +02:00
Přemysl Eric Janouch 67d52a2d89
xP: fix up link detection
Allow balanced parantheses at the end of a link.
2022-09-23 09:42:23 +02:00
Přemysl Eric Janouch ef3d1cc409
xP: add formatting buttons
And fix autoscroll autoenabler, as well as toolbar padding.

Only add the basic toggles, which should be well supported.
2022-09-23 09:41:29 +02:00
Přemysl Eric Janouch e6bf88673f
xP: produce a custom font for IRC formatting
Given that the generated file needs a manual adjustment,
its small size, and the dependencies involved,
it will be checked in to the repository.
2022-09-22 20:18:55 +02:00
Přemysl Eric Janouch 4a2740313c
Give up on the X11 frontend for now
There seem to be only a few things it could bring to the table,
compared to xP, making it barely worth the effort:

 - saner keyboard controls,
 - GVIM integration,
 - slightly improved resource usage.
2022-09-21 18:30:25 +02:00
Přemysl Eric Janouch d3628928b9
xC/xP: relay and render channel modes 2022-09-21 16:32:35 +02:00
Přemysl Eric Janouch 1f0e0b1ce4
xP: only care about RPC results if requested
This prevents "No response" errors from firing for most commands.
2022-09-21 14:31:16 +02:00
Přemysl Eric Janouch 919b12510b
xC/xP: relay and render channel topics 2022-09-21 12:15:27 +02:00
Přemysl Eric Janouch 414859d309
xP: improve paging 2022-09-21 07:34:17 +02:00
Přemysl Eric Janouch 5a7f2d16df
xP: clean up DOM attributes 2022-09-21 06:23:59 +02:00
Přemysl Eric Janouch b8061b665d
Silence spurious compiler warnings 2022-09-21 05:21:45 +02:00
Přemysl Eric Janouch 807bdea2ea
Update README 2022-09-20 19:08:14 +02:00
Přemysl Eric Janouch 8a689c98b4
xC: fix autocomplete in server buffers 2022-09-20 17:34:01 +02:00
Přemysl Eric Janouch 9327333813
xC/xP: show own user's info in frontends 2022-09-20 17:24:30 +02:00
Přemysl Eric Janouch 7806d4bd4e
xC/xP: improve rendering of highlighting actions 2022-09-20 15:30:07 +02:00
Přemysl Eric Janouch c0e1cd57b2
Make sure to always find installed plugins 2022-09-19 04:26:35 +02:00
Přemysl Eric Janouch 00184811ea
xP: make the prompt look more xC-like 2022-09-19 03:18:31 +02:00
Přemysl Eric Janouch ec20fdef7b
xP: show all completion options 2022-09-18 05:54:23 +02:00
Přemysl Eric Janouch 21e5d80ab1
xC: improve Readline completion
The autocomplete for /set used to be extremely annoying,
and menu-complete-display-prefix also prevents mistaken highlights.

One downside is that using plain Tab in channels no longer
just inserts the last-talking nickname, one needs to press it twice.
2022-09-18 02:48:28 +02:00
Přemysl Eric Janouch ff243c1d11
xP: implement Readline's M-l, M-u, M-c 2022-09-18 01:59:11 +02:00
Přemysl Eric Janouch e2ef7d668c
xP: implement Readline's M-b and M-f 2022-09-18 01:10:03 +02:00
Přemysl Eric Janouch b979257c3a
xP: implement Readline's M-< and M-> 2022-09-18 01:10:03 +02:00
Přemysl Eric Janouch 840b646700
xC: reorganize relay code, improve logging
Even with one forward function declaration down,
it was possible to move most code to a more convenient location.

Most logging has thus been fixed to go to buffers.
2022-09-17 00:32:14 +02:00
Přemysl Eric Janouch 126105fa4f
xC: don't abort on accept() failure
Just disable the relay.
2022-09-17 00:31:23 +02:00
Přemysl Eric Janouch e2f3fc2e79
xC: clean up 2022-09-17 00:31:23 +02:00
Přemysl Eric Janouch b55bae50df
Update some documentation 2022-09-16 03:52:49 +02:00
Přemysl Eric Janouch 430968e5d5
xP: make non-connected states more apparent 2022-09-16 03:19:48 +02:00
Přemysl Eric Janouch d5153fe354
xC/xP: implement M-H in the web frontend 2022-09-16 02:52:16 +02:00
Přemysl Eric Janouch ee76186bef
xP: abandon the idea of a configuration file 2022-09-16 01:21:35 +02:00
Přemysl Eric Janouch 6f39aa6615
xP: use the binary protocol for incoming events
And batch event messages together as much as possible.

JSON has proven itself to be really slow
(for example, encoding/json.Marshaler is a slow interface),
and browsers have significant overhead per WS message.

Commands are still sent as JSON, sending them in binary
would be a laborious rewrite without measurable merits.

The xP server now only prints debug output when requested,
because that was another source of major slowdowns.
2022-09-16 00:51:11 +02:00
Přemysl Eric Janouch e87cc90b5e
xP: improve comments in protocol code generator 2022-09-15 05:12:07 +02:00
Přemysl Eric Janouch 98b0a4ef3d
xP: further optimize JSON marshalling 2022-09-15 03:16:16 +02:00
Přemysl Eric Janouch 9cf44aa4dd
xP: speed up log decoding 2022-09-15 02:32:58 +02:00
Přemysl Eric Janouch b53fc1918f
xP: fix log JSON serialization 2022-09-15 01:51:40 +02:00
Přemysl Eric Janouch 92f2f6895b
xP: use buffered reads 2022-09-14 07:11:05 +02:00
Přemysl Eric Janouch c1d2e38840
xP: generate our own JSON marshallers
For non-trivial types, which are expensive to serialize
with encoding/json's struct reflection.
2022-09-14 06:56:36 +02:00
Přemysl Eric Janouch f89f21a47f
xP: pass all events through one handler
This is a mild clean up.
2022-09-14 06:56:36 +02:00
Přemysl Eric Janouch fa85ea8208
xP: parallelize event reception and sending
Still trying to make the frontend load tolerably fast,
still unsuccessfully.
2022-09-14 06:56:36 +02:00
Přemysl Eric Janouch b728235b6c
xP: move to a WebSocket package with compression
Compression happens to be broken in Safari,
though luckily there are friendlier browsers one can use.
2022-09-14 06:56:35 +02:00
Přemysl Eric Janouch d31ab67268
xC: mildly optimize relay traffic 2022-09-14 01:01:19 +02:00
Přemysl Eric Janouch b2b3093e0e
xP: remove debugging protocol logs from JS 2022-09-14 01:01:10 +02:00
Přemysl Eric Janouch a551e911ab
xP: adjust buffer list iteration and styling
M-a and M-! should iterate, rather than keep jumping back
to the same buffers.

The current item wasn't visible enough,
and it jumped around in my 1.5-scale Firefox.
2022-09-13 03:21:41 +02:00
Přemysl Eric Janouch a61789637a
xP: deal with macOS/Blink for good 2022-09-12 16:45:29 +02:00
Přemysl Eric Janouch 8968100a28
xP: improve favicon behaviour
Make it black when disconnected, and orange when the document
is hidden but the current tab is highlighted.
2022-09-12 03:49:29 +02:00
Přemysl Eric Janouch 3b6c29d676
xC: silence some compiler warnings 2022-09-11 22:16:35 +02:00
Přemysl Eric Janouch b4ee523628
xP: bind buffer iteration to M-PageUp/PageDown 2022-09-11 21:50:09 +02:00
Přemysl Eric Janouch c3a52b9e4c
xP: indicate hidden buffer lines 2022-09-11 21:50:09 +02:00
Přemysl Eric Janouch 96fc12bc4c
xC/xP: send buffer type and server state
Also make PM highlighting behaviour consistent.
2022-09-11 21:50:08 +02:00
Přemysl Eric Janouch 1493d9998b
xC: clean up 2022-09-11 19:11:47 +02:00
Přemysl Eric Janouch 36f77e74fb
xP: change the favicon when highlighted 2022-09-11 19:10:41 +02:00
Přemysl Eric Janouch 23deca45c9
xP: fix non-ASCII text completion 2022-09-11 19:10:40 +02:00
Přemysl Eric Janouch 62773acaa0
xP: beep on highlight
800 Hz seems like it could match a POST beep.
2022-09-11 03:42:08 +02:00
Přemysl Eric Janouch 7e3919e25d
xP: add basic buffer input history
Bind M-p and M-n as in xC.

Also make all our bindings reachable on macOS.
2022-09-11 03:10:23 +02:00
Přemysl Eric Janouch 4bc2f736f2
xC: make terminal attributes abstract
And translate them for frontends.

This is very long overdue, and a rather significant cleanup.

Bump liberty.
2022-09-11 01:20:18 +02:00
Přemysl Eric Janouch add670212f
xP: remember buffer input selections 2022-09-11 01:01:53 +02:00
Přemysl Eric Janouch 95aa89ee97
xP: bind M-h to toggle history, and adjust focus 2022-09-10 20:39:03 +02:00
Přemysl Eric Janouch 0bc2c12eec
xP: handle the M-Tab binding from xC 2022-09-10 19:36:49 +02:00
Přemysl Eric Janouch 3330683ad6
xP: handle M-a and M-! bindings from xC 2022-09-10 19:34:01 +02:00
Přemysl Eric Janouch 0015d26dc8
xC/xP: support hiding unimportant messages at all 2022-09-10 19:01:42 +02:00
Přemysl Eric Janouch 7d5e63be1f
xC: deal with so far unexpected multiline messages
And get rid of an outdated unmarked TODO comment.
2022-09-10 18:51:27 +02:00
Přemysl Eric Janouch e7d0f2380e
xC: split Command.BUFFER_INPUT on newlines 2022-09-10 18:51:27 +02:00
Přemysl Eric Janouch 36529a46fd
xP: also scroll to bottom on window resize 2022-09-10 18:10:08 +02:00
Přemysl Eric Janouch 632ac992ab
xC/xP: only send buffer stats in the initial sync
The client and frontends track these separately,
there is no need for hard synchronization.
2022-09-10 17:38:33 +02:00
Přemysl Eric Janouch d29e2cbfe8
xP: detect links in the log 2022-09-10 17:18:22 +02:00
Přemysl Eric Janouch 240fac4d90
xP: only allow vertical textarea resizing 2022-09-10 17:08:14 +02:00
Přemysl Eric Janouch c06894b291
xP: fix command sequence number generation 2022-09-10 17:05:39 +02:00
Přemysl Eric Janouch 9eaf78f823
xP: open links in a new tab/window 2022-09-10 17:05:39 +02:00
Přemysl Eric Janouch 5f02dddd11
xP: advance unread marker when the log is visible 2022-09-10 17:05:39 +02:00
Přemysl Eric Janouch 6f4a3f4657
xP: advance unread marker in an inactive tab 2022-09-10 17:05:39 +02:00
Přemysl Eric Janouch 6387145adc
xP: improve line wrapping 2022-09-10 17:05:38 +02:00
Přemysl Eric Janouch f3cc137342
xC-gen-proto: reduce enums to single bytes
That's already way more than we can possibly use.
2022-09-10 16:06:35 +02:00
Přemysl Eric Janouch 8c8e06b015
xP: enhance mobile experience
The left column used to jump around, and phones were near-unusable.
2022-09-08 17:11:10 +02:00
Přemysl Eric Janouch d7b6967b6f
xP: allow setting a fixed WS URI
For reverse proxies.
2022-09-08 17:11:01 +02:00
Přemysl Eric Janouch 8c3ee80b21
xC/xP: finalize and implement Event.PING 2022-09-08 02:45:37 +02:00
Přemysl Eric Janouch 3a165a595b
xC: use the relay protocol's RPC for pings 2022-09-08 01:48:46 +02:00
Přemysl Eric Janouch 4ba28c6ed3
xC/xP: mark highlights and buffer activity
And more or less finalize out the protocol for this use case.
2022-09-08 01:28:51 +02:00
Přemysl Eric Janouch 45aa0e8dfb
xP: remember to differentiate today 2022-09-07 19:23:47 +02:00
Přemysl Eric Janouch a2d5995cf5
xC: don't autoactivate buffers on forced JOINs 2022-09-07 19:10:49 +02:00
Přemysl Eric Janouch 2075c38fd1
xP: use an industry-standard name for a button 2022-09-07 17:33:38 +02:00
Přemysl Eric Janouch 88a7b1a2d9
xP: resolve various issues, mostly in styling 2022-09-07 17:26:43 +02:00
Přemysl Eric Janouch 2341228efd
xP: implement buffer line leakage
Rather than on redisplay, these get cleared on reconnect.
2022-09-07 15:34:52 +02:00
Přemysl Eric Janouch 2e3005d88b
xP: abort autocomplete when no longer applicable 2022-09-07 15:10:17 +02:00
Přemysl Eric Janouch 2b13f891c9
xP: clean up 2022-09-07 14:45:44 +02:00
Přemysl Eric Janouch d55402234c
xP: add a temporary lock for autoscroll 2022-09-07 13:53:28 +02:00
Přemysl Eric Janouch e3149b9abf
xP: support showing buffer logs 2022-09-07 13:53:28 +02:00
Přemysl Eric Janouch 976e7bfbb4
xP: separate input buffers 2022-09-07 13:04:30 +02:00
Přemysl Eric Janouch 5fd76ba6f9
xC: add a trivial /relay command
For there is otherwise no way of getting that information.
2022-09-07 13:01:34 +02:00
Přemysl Eric Janouch 41878a587f
xC: use liberty logging less
These messages cannot be relayed to frontends (they could be,
but it's useful to keep them distinct rather than redirected).
2022-09-07 13:01:30 +02:00
Přemysl Eric Janouch 80089a4d65
xC: describe general.editor parse errors 2022-09-07 13:01:29 +02:00
Přemysl Eric Janouch 93b66b6a26
xP: scroll to bottom and focus the input on switch 2022-09-06 22:33:00 +02:00
Přemysl Eric Janouch ee1750c23c
xP: clean up 2022-09-06 22:33:00 +02:00
Přemysl Eric Janouch f5104c807d
xP: indicate connection state 2022-09-06 20:17:40 +02:00
Přemysl Eric Janouch 2c49a72d94
Update README 2022-09-06 19:59:22 +02:00
Přemysl Eric Janouch 8cd94b30f6
xP: implement tab completion
Currently it only goes for the longest common prefix.

Refactor WebSocket handling into an abstraction for our protocol.

The Go code generater finally needed fixing.
2022-09-06 19:41:05 +02:00
Přemysl Eric Janouch 2d30b6d115
xC: define critical bindings after el_source()
And use ^C rather than ^G.
2022-09-06 17:02:36 +02:00
Přemysl Eric Janouch cf14cb8122
xC: implement buffer completion in the relay
And actually support completion with non-UTF-8 locales.
We used to ignore the encoding conversion result.
2022-09-06 17:02:02 +02:00
Přemysl Eric Janouch 31e9c6d2d5
xC/xP: pass timestamps with millisecond precision
Future-proofing the protocol.
2022-09-06 14:39:01 +02:00
Přemysl Eric Janouch d2af6cf64c
xP: convert links to link elements 2022-09-06 14:36:30 +02:00
Přemysl Eric Janouch d7b0b447b7
xC/xP: turn the action asterisk into a rendition 2022-09-05 23:22:09 +02:00
Přemysl Eric Janouch 25ad5ae0ec
xC/xP: fix colour values, and render them with CSS 2022-09-05 23:22:09 +02:00
Přemysl Eric Janouch 10f6072da9
xC: also force monospace for RPL_MOTDSTART
It tends to looks inconsistent without.
2022-09-05 23:07:20 +02:00
Přemysl Eric Janouch aceac26cbb
Fix up xP's module path, mention the licence 2022-09-05 23:07:20 +02:00
Přemysl Eric Janouch e250ae8255
Fix up README 2022-09-05 23:07:19 +02:00
Přemysl Eric Janouch 1639235a48
Start X11 and web frontends for xC
For this, we needed a wire protocol.  After surveying available options,
it was decided to implement an XDR-like protocol code generator
in portable AWK.  It now has two backends, per each of:

 - xF, the X11 frontend, is in C, and is meant to be the primary
   user interface in the future.

 - xP, the web frontend, relies on a protocol proxy written in Go,
   and is meant for use on-the-go (no pun intended).

They are very much work-in-progress proofs of concept right now,
and the relay protocol is certain to change.
2022-09-05 14:26:00 +02:00
Přemysl Eric Janouch 2160d03794
xC: slightly clean up character encodings 2022-09-02 14:05:03 +02:00
Přemysl Eric Janouch 36f8c7639f
xC: clean up logging
Don't treat rendition as flags, separate the two.

Also treat join and part arrows as rendition.
2022-09-02 12:31:42 +02:00
Přemysl Eric Janouch 74470f1aa4
CMakeLists.txt: improve dependencies of xD-replies 2022-09-02 12:25:37 +02:00
Přemysl Eric Janouch 3af1765261
xC: make alias creation tolerant to prefixed names
Those would not work, so skip the first forward slash.

Note that liberty can save arbitrary alias names since 6e93119,
making the removed comment about checking outdated.
2022-08-29 15:22:11 +02:00
Přemysl Eric Janouch b454920c81
xC: deal with conflicts when renaming buffers 2022-08-29 15:05:02 +02:00
Přemysl Eric Janouch ef8f25d1dd
xC: deal with any identifier conflicts
Invalid UTF-8 converted to UTF-8 may conflict with that
which was valid UTF-8 in the first place.
2022-08-29 14:41:23 +02:00
Přemysl Eric Janouch 313a65180e
xC: fix some corner cases around terminal handling 2022-08-29 14:05:33 +02:00
Přemysl Eric Janouch 91db8e6e54
xC: use the correct way of resetting libedit
The only remaining major annoyance is incremental search
seemingly not giving back control.
2022-08-29 10:30:45 +02:00
Přemysl Eric Janouch dbe95fa298
xC: make libedit history switching more reliable 2022-08-29 09:20:56 +02:00
Přemysl Eric Janouch 9d5e57a501
xC: improve libedit multiline input handling 2022-08-29 08:31:44 +02:00
Přemysl Eric Janouch 4ed6693f57
xC: erase remaining mentions of a "backlog helper" 2022-08-29 08:22:09 +02:00
Přemysl Eric Janouch bea8d13227
xC: don't autosave when nothing changed 2022-08-29 08:22:09 +02:00
Přemysl Eric Janouch ecebeace0e
Don't wrap xD-gen-replies in a shell script
AWK doesn't seem to be that friendly to shebangs, so let env,
also required for changing LC_ALL, locate it in PATH.
2022-08-29 06:07:49 +02:00
Přemysl Eric Janouch ca33adeeee
Update README
Stop pretending that xD has a future.
2022-08-27 16:53:56 +02:00
Přemysl Eric Janouch b31e079256
Update README 2022-08-27 16:18:14 +02:00
Přemysl Eric Janouch 57597bf8a2
xC: move TEXT_* constants where they belong 2022-08-27 15:06:28 +02:00
Přemysl Eric Janouch c0996fcbe7
xC: normalize BSD Editline's history behaviour
Now it's a realistically useful frontend.
2022-08-27 15:06:27 +02:00
Přemysl Eric Janouch 03d8ea4c5a
xC: general.save_on_quit -> general.autosave
Power outages and similar situations make the former unreliable,
so get rid of any false promise it might seem to give.
2022-08-27 09:15:38 +02:00
Přemysl Eric Janouch dc002a2db4
xC: revise configuration options
This commit constitutes a breaking change to old configurations.

All behaviour.* options have now become general.*, with the following
few renames as exceptions:

 - editor_command -> editor
 - backlog_helper -> pager
 - backlog_helper_strip_formatting -> pager_strip_formatting
2022-08-27 09:15:37 +02:00
Přemysl Eric Janouch a32916ffcf
xC: label code sections better
Introduce tildes as a new sublevel of markers.
2022-08-27 09:15:37 +02:00
Přemysl Eric Janouch f7be510d26
xC: make fancy-prompt.lua alignment more reliable
And generally clean up that script.
2022-08-27 09:15:37 +02:00
Přemysl Eric Janouch 83764d1e1b
Fix xB.adoc parsing with current libasciidoc 2022-08-24 03:17:05 +02:00
Přemysl Eric Janouch a717782480
Build with AsciiDoc as well as Asciidoctor 2022-08-24 00:13:51 +02:00
Přemysl Eric Janouch c50c959f4d
Bump copyright years 2022-08-17 18:27:52 +02:00
Přemysl Eric Janouch 0dd7536b5a
Update README 2022-08-15 15:49:59 +02:00
Přemysl Eric Janouch 0750096827
xC: expand behaviour.editor_command examples 2022-08-14 20:27:30 +02:00
Přemysl Eric Janouch 49d9980662
xC: improve backlog helper capabilities
Snippets now receive positional parameters in the form of the buffer's
name in the locale encoding, and a filename if applicable
(we keep passing stdin along with the filename, which happens to
work out well for less(1)).

The default value of the configuration option also no longer uses
the "long prompt", which used to unhelpfully tell position in terms
of lines, but rather sets its own prompt that counts pages,
and makes sure to indicate the source buffer.

The main motivation behind this change is to make the 'v' command
work in less(1).  LESSSECURE must be omitted from the snippet
for this to work.

Bump liberty to receive a config parser that allows for less
convoluted escaping.
2022-08-14 18:52:26 +02:00
Přemysl Eric Janouch 2f7fbcdc5d
CMakeLists.txt: fix a typo 2022-08-12 13:21:46 +02:00
Přemysl Eric Janouch ef0cbe9a59
Rename the project
It is about to see some extensions, obsoleting the number three.
2022-08-07 10:40:42 +02:00
Přemysl Eric Janouch 1238233556 hid: bump the FD limit 2022-08-02 22:10:31 +02:00
Přemysl Eric Janouch 2d8808d795
utm-filter.lua: mention the passing of fbclid 2022-07-18 17:59:28 +02:00
Přemysl Eric Janouch 9c31fb69df hid: make note of a deprecation 2022-03-16 12:57:00 +01:00
Přemysl Eric Janouch a51c247d69 hid: add WebIRC support
Such clients can only be identified through STATS L.

It's a bit weird to abuse the "port" field this way,
but right now, it serves its purpose.
2022-03-15 19:57:31 +01:00
Přemysl Eric Janouch f26e6361f3 hid: implement WALLOPS 2022-02-05 00:31:34 +01:00
Přemysl Eric Janouch 60d52ad479
xC, xD: add basic WALLOPS support 2022-02-04 22:48:54 +01:00
Přemysl Eric Janouch b358f53ec3
Bump version, update NEWS 2021-12-21 05:58:34 +01:00
Přemysl Eric Janouch 2eb315f5c4
utm-filter.lua: add Facebook to the filter 2021-12-20 14:36:41 +01:00
Přemysl Eric Janouch 851c2ee548
CMakeLists.txt: fix macOS build 2021-11-02 15:34:51 +01:00
Přemysl Eric Janouch f9848ed627
Update README 2021-10-31 05:16:57 +01:00
Přemysl Eric Janouch 686a39df38
CMakeLists.txt: slightly modernize 2021-10-31 04:30:04 +01:00
Přemysl Eric Janouch 9cea3fca91
Update NEWS 2021-10-30 14:25:13 +02:00
Přemysl Eric Janouch 5165f76b7c
xC: quote text coming from a bracketed paste
Not having this has caused me much annoyance over the years.
2021-10-30 09:27:32 +02:00
Přemysl Eric Janouch 92ac13f3c6
xC: allow passing the cursor position to editors
Add a configuration option to set a custom editor command,
different from EDITOR or VISUAL--those remain as defaults.

Implement substitutions allowing to convey cursor information
to VIM and Emacs (the latter of which is fairly painful to cater to),
and put usage hints in the configuration option's description.

This should make the editing experience a bit more seamless
for users, even though the position is carried over in one way only.

No sophisticated quoting capabilities were deemed necessary,
it is a lot of code already.  The particular syntax is inspired
by .desktop files and systemd.

["/bin/sh", "-c", "vim +$2go \"$1\"", filename, position, line, column]
would be a slightly simpler but cryptic way of implementing this.
2021-10-30 09:02:35 +02:00
Přemysl Eric Janouch df4ca74580
xC: make libedit autocomplete less miserable
Omitting even this hack was a huge hit to overall usability.
2021-10-30 08:29:16 +02:00
Přemysl Eric Janouch 9e297244a4
Update .gitignore 2021-10-30 03:37:22 +02:00
Přemysl Eric Janouch d32ba133c0
Add clang-format configuration, clean up 2021-10-30 02:55:19 +02:00
Přemysl Eric Janouch ce3976e1ec
xC: normalize ^J behaviour to follow Readline
For some reason Editline inserts it verbatim,
but in a more broken manner than it has with ^V^J.
2021-10-28 08:49:01 +02:00
Přemysl Eric Janouch e5ed89646b
xC: fix newer libedit (2021-08-29) 2021-10-28 08:23:52 +02:00
Přemysl Eric Janouch 4073b7329f hid: reflect the original project's new name
Better keep all schizophreny in my own head, rather than all projects.
2021-08-06 17:31:32 +02:00
Přemysl Eric Janouch 6421892ef3 Name change 2020-08-01 14:01:58 +02:00
Přemysl Eric Janouch a1994865a9 hid: mention Go 1.12 alternative to TLS autodetection 2019-02-27 02:36:04 +01:00
Přemysl Eric Janouch c285f3a266 hid: clean up/finalize logging 2018-08-06 20:47:33 +02:00
Přemysl Eric Janouch e2c34afbc6 hid: move off of the log package
We don't spam with useless messages without -debug any longer.
2018-08-06 19:52:39 +02:00
Přemysl Eric Janouch e2c8fb6e33 hid: port logging facilities
Though the regular mode now has timestamps and a new mode for systemd
has been added.
2018-08-06 19:49:06 +02:00
Přemysl Eric Janouch 5c7ac9a92b hid: cleanups
No functional changes.
2018-08-06 12:31:31 +02:00
Přemysl Eric Janouch 3fee7e8051 hid: port IRC tests from liberty, fix tag parsing 2018-08-06 12:09:18 +02:00
Přemysl Eric Janouch 09d7a10b69 hid: rename connCloseWrite to connCloseWriter 2018-08-06 12:06:42 +02:00
Přemysl Eric Janouch e9bcd0fa53 hid: add the first tests
This has actually revealed a problem in the SSL 2.0 detection.
2018-08-06 12:06:20 +02:00
Přemysl Eric Janouch 3815795d59 hid: fix SSL 2.0 autodetection 2018-08-04 21:13:28 +02:00
Přemysl Eric Janouch fd1538251a hid: add support for customized replies 2018-08-03 21:45:53 +02:00
Přemysl Eric Janouch ffad1f15a5 hid: unify exit codes with the flag package 2018-08-03 21:45:53 +02:00
Přemysl Eric Janouch 765b741a67 hid: cleanups 2018-08-03 21:45:52 +02:00
Přemysl Eric Janouch ab66a60703 hid: fix listener shutdown 2018-08-03 10:55:22 +02:00
Přemysl Eric Janouch 9ee07873ea hid: fix nickname verification in the user MODE message 2018-08-02 18:42:32 +02:00
Přemysl Eric Janouch 7ee7dc5f9b hid: port default formatting strings to fmt 2018-08-02 12:51:22 +02:00
Přemysl Eric Janouch fea801ac7a hid: ircSendToRoommates -> ircNotifyRoommates
Should be clearer.
2018-08-01 20:39:37 +02:00
Přemysl Eric Janouch cbdbfc3d64 hid: figured out how to port timeouts 2018-08-01 20:39:37 +02:00
Přemysl Eric Janouch 3610f98d67 hid: another round of general code cleanups 2018-08-01 17:45:56 +02:00
Přemysl Eric Janouch e77495f316 hid: bringup of what we have this far 2018-07-31 23:11:54 +02:00
Přemysl Eric Janouch 2f841d214f hid: port configuration and initialization
All the basic elements should be there now, we just need to port PING
timers and fix some remaining issues and we're basically done.
2018-07-31 20:53:23 +02:00
Přemysl Eric Janouch 051bbedc2f hid: port IRC 3.2 message tag parsing, unused 2018-07-30 17:50:27 +02:00
Přemysl Eric Janouch 404aa8c9cc hid: use time.Time and time.Duration
It improves the code significantly over explicit int64 conversions.

Despite carrying unnecessary timezone information, time.Time also
carries a monotonic reading of time, which allows for more precise
measurement of time differences.
2018-07-30 10:07:02 +02:00
Přemysl Eric Janouch 90129ee2bc hid: port MODE, STATS, LINKS, KILL
Now all the commands have been ported but we desperately need to parse
a configuration file for additional settings yet.
2018-07-30 09:46:59 +02:00
Přemysl Eric Janouch 50e7f7dca5 hid: port PART, KICK, INVITE, JOIN, AWAY, ISON, ADMIN, DIE 2018-07-29 17:49:57 +02:00
Přemysl Eric Janouch 3322fe2851 hid: port PRIVMSG, NOTICE, NAMES, WHO, WHOIS/WAS, TOPIC, SUMMON, USERS 2018-07-29 15:57:39 +02:00
Přemysl Eric Janouch 208a8fcc7e hid: first round of mixed fixes and cleanups 2018-07-29 08:14:07 +02:00
Přemysl Eric Janouch 2d287752d4 hid: add a work in progress IRC daemon
The port is more than viable but it's also sort of all-or-nothing
and versioning needs have come before I've had a chance to finish it.
2018-07-28 16:21:34 +02:00
64 changed files with 13297 additions and 877 deletions

32
.clang-format Normal file
View File

@ -0,0 +1,32 @@
# clang-format is fairly limited, and these rules are approximate:
# - array initializers can get terribly mangled with clang-format 12.0,
# - sometimes it still aligns with space characters,
# - struct name NL { NL ... NL } NL name; is unachievable.
BasedOnStyle: GNU
ColumnLimit: 80
IndentWidth: 4
TabWidth: 4
UseTab: ForContinuationAndIndentation
BreakBeforeBraces: Allman
SpaceAfterCStyleCast: true
AlignAfterOpenBracket: DontAlign
AlignOperands: DontAlign
AlignConsecutiveMacros: Consecutive
AllowAllArgumentsOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
IndentGotoLabels: false
# IncludeCategories has some potential, but it may also break the build.
# Note that the documentation says the value should be "Never".
SortIncludes: false
# This is a compromise, it generally works out aesthetically better.
BinPackArguments: false
# Unfortunately, this can't be told to align to column 40 or so.
SpacesBeforeTrailingComments: 2
# liberty-specific macro body wrappers.
MacroBlockBegin: "BLOCK_START"
MacroBlockEnd: "BLOCK_END"
ForEachMacros: ["LIST_FOR_EACH"]

10
.gitignore vendored
View File

@ -3,7 +3,9 @@
# Qt Creator files
/CMakeLists.txt.user*
/uirc3.config
/uirc3.files
/uirc3.creator*
/uirc3.includes
/xK.config
/xK.files
/xK.creator*
/xK.includes
/xK.cflags
/xK.cxxflags

View File

@ -1,20 +1,28 @@
cmake_minimum_required (VERSION 3.0)
project (uirc3 VERSION 1.4.0 LANGUAGES C)
# Ubuntu 18.04 LTS and OpenBSD 6.4
cmake_minimum_required (VERSION 3.10)
file (READ xK-version project_version)
configure_file (xK-version xK-version.tag COPYONLY)
string (STRIP "${project_version}" project_version)
project (xK VERSION "${project_version}"
DESCRIPTION "IRC daemon, bot, TUI client and its web frontend" LANGUAGES C)
# Options
option (WANT_READLINE "Use GNU Readline for the UI (better)" ON)
option (WANT_LIBEDIT "Use BSD libedit for the UI" OFF)
option (WANT_XF "Build xF" OFF)
# Moar warnings
set (CMAKE_C_STANDARD 99)
set (CMAKE_C_STANDARD_REQUIRED ON)
set (CMAKE_C_EXTENSIONS OFF)
if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
# -Wunused-function is pretty annoying here, as everything is static
set (wdisabled "-Wno-unused-function")
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -Wall -Wextra ${wdisabled}")
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-unused-function")
endif ()
# Version
set (project_version "${PROJECT_VERSION}")
# Try to append commit ID if it follows a version tag. It might be nicer if
# we could also detect dirty worktrees but that's very hard to get right.
# If we didn't need this for CPack, we could use add_custom_command to generate
@ -57,6 +65,8 @@ if ("${CMAKE_SYSTEM_NAME}" MATCHES "BSD")
# Need this for SIGWINCH in FreeBSD and OpenBSD respectively;
# our POSIX version macros make it undefined
add_definitions (-D__BSD_VISIBLE=1 -D_BSD_SOURCE=1)
elseif (APPLE)
add_definitions (-D_DARWIN_C_SOURCE)
endif ()
# -lrt is only for glibc < 2.17
@ -112,10 +122,16 @@ endif ()
if ((WANT_READLINE AND WANT_LIBEDIT) OR (NOT WANT_READLINE AND NOT WANT_LIBEDIT))
message (SEND_ERROR "You have to choose either GNU Readline or libedit")
elseif (WANT_READLINE)
pkg_check_modules (readline readline)
# OpenBSD's default readline is too old
if ("${CMAKE_SYSTEM_NAME}" MATCHES "OpenBSD")
include_directories (${OPENBSD_LOCALBASE}/include/ereadline)
list (APPEND xC_libraries ereadline)
elseif (readline_FOUND)
list (APPEND xC_libraries ${readline_LIBRARIES})
include_directories (${readline_INCLUDE_DIRS})
link_directories (${readline_LIBRARY_DIRS})
else ()
list (APPEND xC_libraries readline)
endif ()
@ -131,28 +147,56 @@ set (HAVE_EDITLINE "${WANT_LIBEDIT}")
set (HAVE_LUA "${WITH_LUA}")
include (GNUInstallDirs)
configure_file (${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h)
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})
# Generate IRC replies--we need a custom target because of the multiple outputs
add_custom_command (OUTPUT xD-replies.c xD.msg
COMMAND ${PROJECT_SOURCE_DIR}/xD-gen-replies.sh
> xD-replies.c < ${PROJECT_SOURCE_DIR}/xD-replies
DEPENDS ${PROJECT_SOURCE_DIR}/xD-replies
COMMAND env LC_ALL=C awk
-f ${PROJECT_SOURCE_DIR}/xD-gen-replies.awk
${PROJECT_SOURCE_DIR}/xD-replies > xD-replies.c
DEPENDS
${PROJECT_SOURCE_DIR}/xD-gen-replies.awk
${PROJECT_SOURCE_DIR}/xD-replies
COMMENT "Generating files from the list of server numerics")
add_custom_target (replies DEPENDS ${PROJECT_BINARY_DIR}/xD-replies.c)
add_custom_command (OUTPUT xC-proto.c
COMMAND env LC_ALL=C awk
-f ${PROJECT_SOURCE_DIR}/liberty/tools/lxdrgen.awk
-f ${PROJECT_SOURCE_DIR}/liberty/tools/lxdrgen-c.awk
-v PrefixCamel=Relay
${PROJECT_SOURCE_DIR}/xC.lxdr > xC-proto.c
DEPENDS
${PROJECT_SOURCE_DIR}/liberty/tools/lxdrgen.awk
${PROJECT_SOURCE_DIR}/liberty/tools/lxdrgen-c.awk
${PROJECT_SOURCE_DIR}/xC.lxdr
COMMENT "Generating xC relay protocol code" VERBATIM)
add_custom_target (xC-proto DEPENDS ${PROJECT_BINARY_DIR}/xC-proto.c)
# Build
foreach (name xB xC xD)
add_executable (${name} ${name}.c ${PROJECT_BINARY_DIR}/config.h)
add_executable (${name} ${name}.c ${project_config})
target_link_libraries (${name} ${project_libraries})
add_threads (${name})
endforeach ()
add_dependencies (xD replies)
add_dependencies (xC replies)
add_dependencies (xC replies xC-proto)
target_link_libraries (xC ${xC_libraries})
if (WANT_XF)
pkg_check_modules (x11 REQUIRED x11 xrender xft fontconfig)
include_directories (${x11_INCLUDE_DIRS})
link_directories (${x11_LIBRARY_DIRS})
add_executable (xF xF.c ${project_config})
add_dependencies (xF xC-proto)
target_link_libraries (xF ${x11_LIBRARIES} ${project_libraries})
add_threads (xF)
endif ()
# Tests
include (CTest)
if (BUILD_TESTING)
@ -184,7 +228,6 @@ add_custom_target (clang-tidy
# Installation
install (TARGETS xB xC xD DESTINATION ${CMAKE_INSTALL_BINDIR})
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
# XXX: our defaults for XDG_DATA_DIRS expect /usr/local/shore or /usr/share
install (DIRECTORY plugins/xB/
DESTINATION ${CMAKE_INSTALL_DATADIR}/xB/plugins USE_SOURCE_PERMISSIONS)
install (DIRECTORY plugins/xC/
@ -192,20 +235,40 @@ install (DIRECTORY plugins/xC/
# Generate documentation from text markup
find_program (ASCIIDOCTOR_EXECUTABLE asciidoctor)
if (NOT ASCIIDOCTOR_EXECUTABLE)
message (FATAL_ERROR "asciidoctor not found")
find_program (A2X_EXECUTABLE a2x)
if (NOT ASCIIDOCTOR_EXECUTABLE AND NOT A2X_EXECUTABLE)
message (WARNING "Neither asciidoctor nor a2x were found, "
"falling back to a substandard manual page generator")
endif ()
foreach (page xB xC xD)
set (page_output "${PROJECT_BINARY_DIR}/${page}.1")
list (APPEND project_MAN_PAGES "${page_output}")
if (ASCIIDOCTOR_EXECUTABLE)
add_custom_command (OUTPUT ${page_output}
COMMAND ${ASCIIDOCTOR_EXECUTABLE} -b manpage
-a release-version=${project_version}
"${PROJECT_SOURCE_DIR}/${page}.adoc"
-o "${page_output}"
"${PROJECT_SOURCE_DIR}/${page}.adoc"
DEPENDS ${page}.adoc
COMMENT "Generating man page for ${page}" VERBATIM)
elseif (A2X_EXECUTABLE)
add_custom_command (OUTPUT ${page_output}
COMMAND ${A2X_EXECUTABLE} --doctype manpage --format manpage
-a release-version=${project_version}
-D "${PROJECT_BINARY_DIR}"
"${PROJECT_SOURCE_DIR}/${page}.adoc"
DEPENDS ${page}.adoc
COMMENT "Generating man page for ${page}" VERBATIM)
else ()
set (ASCIIMAN ${PROJECT_SOURCE_DIR}/liberty/tools/asciiman.awk)
add_custom_command (OUTPUT ${page_output}
COMMAND env LC_ALL=C asciidoc-release-version=${project_version}
awk -f ${ASCIIMAN} "${PROJECT_SOURCE_DIR}/${page}.adoc"
> ${page_output}
DEPENDS ${page}.adoc ${ASCIIMAN}
COMMENT "Generating man page for ${page}" VERBATIM)
endif ()
endforeach ()
add_custom_target (docs ALL DEPENDS ${project_MAN_PAGES})
@ -217,7 +280,6 @@ foreach (page ${project_MAN_PAGES})
endforeach ()
# CPack
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Unreasonable IRC client, daemon and bot")
set (CPACK_PACKAGE_VERSION "${project_version_safe}")
set (CPACK_PACKAGE_VENDOR "Premysl Eric Janouch")
set (CPACK_PACKAGE_CONTACT "Přemysl Eric Janouch <p@janouch.name>")

View File

@ -1,4 +1,4 @@
Copyright (c) 2014 - 2021, Přemysl Eric Janouch <p@janouch.name>
Copyright (c) 2014 - 2024, Přemysl Eric Janouch <p@janouch.name>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

59
NEWS
View File

@ -1,3 +1,62 @@
2.0.0 (2024-07-28) "Perfect Is the Enemy of Good"
* xD: now using SHA-256 for client certificate fingerprints
* xD: implemented WALLOPS, choosing to make it target even non-operators
* xC: made it show WALLOPS messages, as PRIVMSG for the server buffer
* xC: all behaviour.* configuration options have been renamed to general.*,
with the exception of editor_command/editor, backlog_helper/pager,
and backlog_helper_strip_formatting/pager_strip_formatting
* xC: all attributes.* configuration options have been made abstract in
a subset of the git-config(1) format, and renamed to theme.*,
with the exception of attributes.reset, which has no replacement
* xC: replaced behaviour.save_on_quit with general.autosave
* xC: the servers.*.command configuration option now supports multiple lines
* xC: improved pager integration capabilities
* xC: unsolicited JOINs will no longer automatically activate the buffer
* xC: made Readline insert the longest common completion prefix first,
and prevented the possible-completions command from duplicating the prompt
* xC: normalized editline's history behaviour, making it a viable frontend
* xC: various bugfixes
* xC: added a relay interface, enabled through the general.relay_bind option
* Added a web frontend for xC called xP
* Added a Win32 frontend for xC called xW
* Added a Cocoa frontend for xC called xM
* Added a Go port of xD called xS
* Added a simple notifier called xN
1.5.0 (2021-12-21) "The Show Must Go On"
* xC: made it possible to pass the cursor position to external editors,
in particular VIM and Emacs
* xC: started quoting text coming from bracketed pastes,
to minimize the risk of trying to execute filesystem paths as commands
* xC: fixed to work with post-2021-08-29 editline
* xC: extended editline's autocomplete to show all options
* utm-filter.lua: added Facebook's tracking parameter to the filter
1.4.0 (2021-10-06) "Call Me Scruffy Scruffington"
* xC: made message autosplitting respect text formatting

View File

@ -1,104 +1,106 @@
uirc3
=====
:compact-option:
xK
==
The unreasonable IRC trinity. This project consists of an IRC client, daemon,
and bot. It's all you're ever going to need for chatting, as long as you can
make do with minimalist software.
'xK' (chat kit) is an IRC software suite consisting of a daemon, bot, notifier,
terminal client, and web/Windows/macOS frontends for the client. It's all
you're ever going to need for chatting, so long as you can make do with slightly
minimalist software.
All of them have these potentially interesting properties:
- IPv6 support
- TLS support, including client certificates
- lean on dependencies (with the exception of 'xC')
- compact and arguably easy to hack on
- very permissive license
They're all lean on dependencies, and offer a maximally permissive licence.
xC
--
The IRC client. It is largely defined by being built on top of GNU Readline
that has been hacked to death. Its interface should feel somewhat familiar for
weechat or irssi users.
The IRC client, and the core of 'xK'. It is largely defined by building on top
of GNU Readline or BSD Editline that have been hacked to death. Its interface
should feel somewhat familiar for weechat or irssi users.
image::xC.png[align="center"]
image::xC.webp[align="center"]
This is the largest application within the project. It has most of the stuff
you'd expect of an IRC client, such as being able to set up multiple servers,
a powerful configuration system, integrated help, text formatting, CTCP queries,
automatic splitting of overlong messages, autocomplete, logging to file,
auto-away, command aliases and basic support for Lua scripting.
It has most features you'd expect of an IRC client, such as being multiserver,
a powerful configuration system, integrated help, text formatting, automatic
message splitting, multiline editing, bracketed paste support, word wrapping
that doesn't break links, autocomplete, logging, CTCP queries, auto-away,
command aliases, SOCKS proxying, SASL EXTERNAL authentication using TLS client
certificates, a remote relay interface, or basic support for Lua scripting.
As a unique bonus, you can launch a full text editor from within.
xP
--
The web frontend for 'xC', making use of its networked relay interface.
It intentionally differs in that it uses a sans-serif font, and it shows
the list of all buffers in a side panel. Otherwise it is a near replica,
including link:xC.adoc#_key_bindings[keyboard shortcuts].
image::xP.webp[align="center"]
xD
--
The IRC daemon. It is designed to be used as a regular user application rather
than a system-wide daemon. If all you want is a decent, minimal IRCd for
testing purposes or a small network of respectful users (or bots), this one will
do it just fine.
The IRC daemon. It is designed for use as a regular user application rather
than a system-wide daemon, and follows the XDG Base Directory Specification.
If all you want is a decent, minimal IRCd for testing purposes or a small
network of respectful users (or bots), this one will do it just fine.
Notable features:
It autodetects TLS on incoming connections (I'm still wondering why everyone
doesn't have this), authenticates operators via TLS client certificate
fingerprints, and supports a number of IRCv3 capabilities.
- TLS autodetection (why doesn't everyone have this?), using secure defaults
- IRCop authentication via TLS client certificates
- epoll/kqueue support; this means that it should be able to handle quite
a number of concurrent user connections
- partial IRCv3 support
What it notably doesn't support is online changes to configuration, any limits
besides the total number of connections and mode `+l`, or server linking
(which also means no services).
Not supported:
xS
--
The IRC daemon again, this time ported to Go, additionally supporting WEBIRC,
and thus ideal for pairing with, e.g.,
https://github.com/kiwiirc/webircgateway[].
Any further development, such as P10 or TS6 linking for IRC services,
or plugin support for arbitrary bridges, will happen here.
- server linking (which also means no services); I consider existing protocols
for this purpose ugly and tricky to implement correctly; I've also found no
use for this feature yet
- online changes to configuration; the configuration system from 'xC' could
be used to implement this feature if needed
- limits of almost any kind, just connections and mode `+l`
This program has been
https://git.janouch.name/p/haven/src/branch/master/hid[ported to Go],
and development continues over there.
xN
--
The IRC notifier, should you ever need to send automated messages from a script.
xB
--
The IRC bot. It builds upon the concept of my other VitaminA IRC bot. The main
characteristic of these two bots is that they run plugins as coprocesses, which
allows for enhanced reliability and programming language freedom.
The IRC bot. While originally intended to be a simple rewrite of my old GNU AWK
bot in C, it fairly quickly became a playground, and it eventually got me into
writing the rest of this package.
While originally intended to be a simple rewrite of the original AWK bot in C,
it fairly quickly became a playground, and it eventually got me into writing
the rest of the package.
It survives crashes, server disconnects and timeouts, and also has native SOCKS
support (even though socksify can add that easily to any program).
Its main characteristic is that it runs plugins as coprocesses, allowing for
enhanced reliability and programming language freedom. Moreover, it recovers
from any crashes, and offers native SOCKS support (even though socksify can add
that easily to any program).
Packages
--------
Regular releases are sporadic. git master should be stable enough. You can get
a package with the latest development version from Archlinux's AUR.
Regular releases are sporadic. git master should be stable enough.
You can get a package with the latest development version using Arch Linux's
https://aur.archlinux.org/packages/xk-git[AUR],
or as a https://git.janouch.name/p/nixexprs[Nix derivation].
Building
--------
Build dependencies: CMake, pkg-config, asciidoctor, awk, liberty (included) +
Runtime dependencies: openssl +
Additionally for 'xC': curses, libffi, lua >= 5.3 (optional),
readline >= 6.0 or libedit >= 2013-07-12
Build-only dependencies: CMake, pkg-config, awk, liberty (included),
asciidoctor or asciidoc (recommended but optional) +
Common runtime dependencies: openssl +
Additionally for 'xC': curses, libffi, readline >= 6.0 or libedit >= 2013-07-12,
lua >= 5.3 (optional) +
Avoid libedit if you can, in general it works but at the moment history is
acting up and I have no clue about fixing it.
$ git clone --recursive https://git.janouch.name/p/uirc3.git
$ mkdir uirc3/build
$ cd uirc3/build
$ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug \
-DWANT_READLINE=ON -DWANT_LIBEDIT=OFF -DWANT_LUA=ON
$ git clone --recursive https://git.janouch.name/p/xK.git
$ mkdir xK/build
$ cd xK/build
$ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DWANT_READLINE=ON -DWANT_LIBEDIT=OFF -DWITH_LUA=ON
$ make
To install the application, you can do either the usual:
# make install
Or you can try telling CMake to make a package for you. For Debian it is:
Or you can try telling CMake to make a package for you:
$ cpack -G DEB
# dpkg -i uirc3-*.deb
$ cpack -G DEB # also supported: RPM, FreeBSD
# dpkg -i xK-*.deb
Usage
-----
@ -123,16 +125,54 @@ a Screen or tmux session.
file or something like `killall` if you want to terminate it. You can run it
as a `forking` type systemd user service.
xP
~~
The precondition for running 'xC' frontends is enabling its relay interface:
/set general.relay_bind = "127.0.0.1:9000"
To build the web server, you'll need to install the Go compiler, and run `make`
from the _xP_ directory. Then start it from the _public_ subdirectory,
and navigate to the adress you gave it as its first argument--in the following
example, that would be http://localhost:8080[]:
$ ../xP 127.0.0.1:8080 127.0.0.1:9000
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
endpoint as the third command line argument in this case.
xW
~~
The Win32 frontend is a separate CMake subproject that should be compiled
using MinGW-w64. To avoid having to specify the relay address each time you
run it, create a shortcut for the executable and include the address in its
_Target_ field:
C:\...\xW.exe 127.0.0.1 9000
It works reasonably well starting with Windows 7.
xM
~~
The Cocoa frontend is a separate CMake subproject that requires Xcode to build.
It is currently not that usable. The relay address can either be passed on
the command line, or preset in the _defaults_ database:
$ defaults write name.janouch.xM relayHost 127.0.0.1
$ defaults write name.janouch.xM relayPort 9000
Client Certificates
-------------------
'xC' will use the SASL EXTERNAL method to authenticate using the TLS client
certificate specified by the respective server's `tls_cert` option if you add
`sasl` to the `capabilities` option and the server supports this.
'xD' uses SHA-1 fingerprints of TLS client certificates to authenticate users.
To get the fingerprint from a certificate file in the required form, use:
'xD' and 'xS' use SHA-256 fingerprints of TLS client certificates
to authenticate users. To get the fingerprint from a certificate file
in the required form, use:
$ openssl x509 -in public.pem -outform DER | sha1sum
$ openssl x509 -in public.pem -outform DER | sha256sum
Custom Key Bindings in xC
-------------------------
@ -156,20 +196,24 @@ Beware that you can easily break the program if you're not careful.
How do I make xC look like the screenshot?
------------------------------------------
First of all, you must build it with Lua support. With the defaults, 'xC'
doesn't look too fancy because I don't want to depend on Lua or 256-colour
terminals. In addition to that, I appear to be one of the few people who use
black on white terminals.
With the defaults, 'xC' doesn't look too fancy because I don't want to have
a hard dependency on either Lua for the bundled script that provides an easily
adjustable enhanced prompt, or on 256-colour terminals. Moreover, it's nearly
impossible to come up with a colour theme that would work well with both
black-on-white and white-on-black terminals, or anything wild in between.
/set behaviour.date_change_line = "%a %e %b %Y"
/set behaviour.plugin_autoload += "fancy-prompt.lua"
/set behaviour.backlog_helper = "LESSSECURE=1 less -R +Gb1d -Ps'Backlog ?ltlines %lt-%lb?L/%L. .?e(END):?pB%pB\\%..'"
/set attributes.userhost = "\x1b[38;5;109m"
/set attributes.join = "\x1b[38;5;108m"
/set attributes.part = "\x1b[38;5;138m"
/set attributes.external = "\x1b[38;5;248m"
/set attributes.timestamp = "\x1b[48;5;255m\x1b[38;5;250m"
/set attributes.read_marker = "\x1b[38;5;202m"
Assuming that your build supports Lua plugins, and that you have a decent,
properly set-up terminal emulator, it suffices to run:
/set general.pager = Press Tab here and change +Gb to +Gb1d
/set general.date_change_line = "%a %e %b %Y"
/set general.plugin_autoload += "fancy-prompt.lua"
/set theme.userhost = "109"
/set theme.join = "108"
/set theme.part = "138"
/set theme.external = "248"
/set theme.timestamp = "250 255"
/set theme.read_marker = "202"
Configuration profiles
----------------------
@ -184,7 +228,7 @@ configurations accordingly, but I consider it rather messy and unnecessary.
Contributing and Support
------------------------
Use https://git.janouch.name/p/uirc3 to report any bugs, request features,
Use https://git.janouch.name/p/xK to report any bugs, request features,
or submit pull requests. `git send-email` is tolerated. If you want to discuss
the project, feel free to join me at ircs://irc.janouch.name, channel #dev.

View File

@ -1,7 +1,7 @@
/*
* common.c: common functionality
*
* Copyright (c) 2014 - 2020, Přemysl Eric Janouch <p@janouch.name>
* Copyright (c) 2014 - 2022, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
@ -22,11 +22,11 @@
#define LIBERTY_WANT_PROTO_IRC
#ifdef WANT_SYSLOG_LOGGING
#define print_fatal_data ((void *) LOG_ERR)
#define print_error_data ((void *) LOG_ERR)
#define print_warning_data ((void *) LOG_WARNING)
#define print_status_data ((void *) LOG_INFO)
#define print_debug_data ((void *) LOG_DEBUG)
#define print_fatal_data ((void *) LOG_ERR)
#define print_error_data ((void *) LOG_ERR)
#define print_warning_data ((void *) LOG_WARNING)
#define print_status_data ((void *) LOG_INFO)
#define print_debug_data ((void *) LOG_DEBUG)
#endif // WANT_SYSLOG_LOGGING
#include "liberty/liberty.c"
@ -48,6 +48,66 @@ init_openssl (void)
#endif
}
static char *
gai_reconstruct_address (struct addrinfo *ai)
{
char host[NI_MAXHOST] = {}, port[NI_MAXSERV] = {};
int err = getnameinfo (ai->ai_addr, ai->ai_addrlen,
host, sizeof host, port, sizeof port,
NI_NUMERICHOST | NI_NUMERICSERV);
if (err)
{
print_debug ("%s: %s", "getnameinfo", gai_strerror (err));
return xstrdup ("?");
}
return format_host_port_pair (host, port);
}
static bool
accept_error_is_transient (int err)
{
// OS kernels may return a wide range of unforeseeable errors.
// Assuming that they're either transient or caused by
// a connection that we've just extracted from the queue.
switch (err)
{
case EBADF:
case EINVAL:
case ENOTSOCK:
case EOPNOTSUPP:
return false;
default:
return true;
}
}
/// Destructively tokenize an address into a host part, and a port part.
/// The port is only overwritten if that part is found, allowing for defaults.
static const char *
tokenize_host_port (char *address, const char **port)
{
// Unwrap IPv6 addresses in format_host_port_pair() format.
char *rbracket = strchr (address, ']');
if (*address == '[' && rbracket)
{
if (rbracket[1] == ':')
{
*port = rbracket + 2;
return *rbracket = 0, address + 1;
}
if (!rbracket[1])
return *rbracket = 0, address + 1;
}
char *colon = strchr (address, ':');
if (colon)
{
*port = colon + 1;
return *colon = 0, address;
}
return address;
}
// --- To be moved to liberty --------------------------------------------------
// FIXME: in xssl_get_error() we rely on error reasons never being NULL (i.e.,
@ -74,6 +134,15 @@ xerr_describe_error (void)
return reason;
}
static struct str
str_from_cstr (const char *cstr)
{
struct str self;
self.alloc = (self.len = strlen (cstr)) + 1;
self.str = memcpy (xmalloc (self.alloc), cstr, self.alloc);
return self;
}
static ssize_t
strv_find (const struct strv *v, const char *s)
{
@ -260,7 +329,7 @@ struct socks_connector
SOCKS_DATA_CB (socks_4a_finish)
{
uint8_t null, status;
uint8_t null = 0, status = 0;
hard_assert (msg_unpacker_u8 (unpacker, &null));
hard_assert (msg_unpacker_u8 (unpacker, &status));
@ -363,7 +432,7 @@ SOCKS_DATA_CB (socks_5_request_domain)
SOCKS_DATA_CB (socks_5_request_finish)
{
uint8_t version, status, reserved, type;
uint8_t version = 0, status = 0, reserved = 0, type = 0;
hard_assert (msg_unpacker_u8 (unpacker, &version));
hard_assert (msg_unpacker_u8 (unpacker, &status));
hard_assert (msg_unpacker_u8 (unpacker, &reserved));
@ -440,7 +509,7 @@ socks_5_request_start (struct socks_connector *self)
SOCKS_DATA_CB (socks_5_userpass_finish)
{
uint8_t version, status;
uint8_t version = 0, status = 0;
hard_assert (msg_unpacker_u8 (unpacker, &version));
hard_assert (msg_unpacker_u8 (unpacker, &status));
@ -475,7 +544,7 @@ socks_5_userpass_start (struct socks_connector *self)
SOCKS_DATA_CB (socks_5_auth_finish)
{
uint8_t version, method;
uint8_t version = 0, method = 0;
hard_assert (msg_unpacker_u8 (unpacker, &version));
hard_assert (msg_unpacker_u8 (unpacker, &method));

View File

@ -3,6 +3,9 @@
#define PROGRAM_VERSION "${project_version}"
// We use the XDG Base Directory Specification, but may be installed anywhere.
#define PROJECT_DATADIR "${CMAKE_INSTALL_FULL_DATADIR}"
#cmakedefine HAVE_READLINE
#cmakedefine HAVE_EDITLINE
#cmakedefine HAVE_LUA

@ -1 +1 @@
Subproject commit 1b9d89cab3bb1df73c58ccd8528eafd21a8c6e40
Subproject commit aacf1b1d47824de2c924874e3d6cdad1d8b5ba2b

View File

@ -1,7 +1,7 @@
--
-- fancy-prompt.lua: the fancy multiline prompt you probably want
--
-- Copyright (c) 2016, Přemysl Eric Janouch <p@janouch.name>
-- Copyright (c) 2016 - 2022, Přemysl Eric Janouch <p@janouch.name>
--
-- Permission to use, copy, modify, and/or distribute this software for any
-- purpose with or without fee is hereby granted.
@ -40,7 +40,7 @@ xC.hook_prompt (function (hook)
if buffer == current then
current_n = i
elseif buffer.new_messages_count ~= buffer.new_unimportant_count then
if active ~= "" then active = active .. "," end
active = active .. ","
if buffer.highlighted then
active = active .. "!"
bg_color = "224"
@ -48,7 +48,6 @@ xC.hook_prompt (function (hook)
active = active .. i
end
end
if active ~= "" then active = "(" .. active .. ")" end
local x = current_n .. ":" .. current.name
if chan and chan.users_len ~= 0 then
local params = ""
@ -56,25 +55,34 @@ xC.hook_prompt (function (hook)
params = params .. " +" .. mode .. " " .. param
end
local modes = chan.no_param_modes .. params:sub (3)
if modes ~= "" then x = x .. "(+" .. modes .. ")" end
if modes ~= "" then
x = x .. "(+" .. modes .. ")"
end
x = x .. "{" .. chan.users_len .. "}"
end
if current.hide_unimportant then x = x .. "<H>" end
local lines, cols = xC.get_screen_size ()
x = x .. " " .. active .. string.rep (" ", cols)
if current.hide_unimportant then
x = x .. "<H>"
end
if active ~= "" then
x = x .. " (" .. active:sub (2) .. ")"
end
-- Readline 7.0.003 seems to be broken and completely corrupts the prompt.
-- However 8.0.004 seems to be fine with these, as is libedit 20191231-3.1.
--x = x:gsub("[\128-\255]", "?")
-- Cut off extra characters and apply formatting, including the hack.
-- FIXME: this doesn't count with full-width or zero-width characters.
-- We might want to export wcwidth() above term_from_utf8 somehow.
local overflow = utf8.offset (x, cols - 1)
if overflow then x = x:sub (1, overflow) end
-- Align to the terminal's width and apply formatting, including the hack.
local lines, cols = xC.get_screen_size ()
local trailing, width = " ", xC.measure (x)
while cols > 0 and width >= cols do
x = x:sub (1, utf8.offset (x, -1) - 1)
trailing, width = ">", xC.measure (x)
end
x = "\x01\x1b[0;4;1;38;5;16m\x1b[48;5;" .. bg_color .. "m\x02" ..
x .. "\x01\x1b[0;4;1;7;38;5;" .. bg_color .. "m\x02 \x01\x1b[0;1m\x02"
x .. string.rep (" ", cols - width - 1) ..
"\x01\x1b[0;4;1;7;38;5;" .. bg_color .. "m\x02" ..
trailing .. "\x01\x1b[0;1m\x02"
local user_prefix = function (chan, user)
for i, chan_user in ipairs (chan.users) do

View File

@ -1,7 +1,7 @@
--
-- utm-filter.lua: filter out Google Analytics bullshit from URLs
-- utm-filter.lua: filter out Google Analytics bullshit etc. from URLs
--
-- Copyright (c) 2015, Přemysl Eric Janouch <p@janouch.name>
-- Copyright (c) 2015 - 2021, Přemysl Eric Janouch <p@janouch.name>
--
-- Permission to use, copy, modify, and/or distribute this software for any
-- purpose with or without fee is hereby granted.
@ -19,6 +19,10 @@
local banned = {
gclid = 1,
-- Alas, Facebook no longer uses this parameter, see:
-- https://news.ycombinator.com/item?id=32117489
fbclid = 1,
utm_source = 1,
utm_medium = 1,
utm_term = 1,

4
test
View File

@ -1,5 +1,7 @@
#!/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
@ -27,7 +29,7 @@ expect "Option changed"
send "/disconnect\n"
expect "]"
send "/connect\n"
expect "Connection established"
expect "Welcome to"
# Try some chatting
send "/join #test\n"

View File

@ -1,8 +1,16 @@
#!/bin/sh
# We don't use printf's percent notation with our custom logging mechanism,
# so the compiler cannot check it for us like it usually does
# so the compiler cannot check it for us like it usually does.
#
# In clang-query terms, the string we're interested in can be found through:
# set traversal IgnoreUnlessSpelledInSource
# set output dump
# match callExpr(callee(functionDecl(
# hasName("log_full"))),
# hasArgument(5, stringLiteral().bind("format")))
# However, the tool is too restricted to be useful in a shell script.
perl -n0777 - "$(dirname "$0")"/xC.c <<-'END'
while (/\blog_[^ ]+\s*\([^"()]*"[^"]*%[^%][^"]*"/gm) {
while (/\blog_[^ ]+\s*\([^"()]*"[^"]*%\w[^"]*"/gm) {
my ($p, $m) = ($`, $&);
printf "$ARGV:%d: suspicious log format string: %s...\n",
(1 + $p =~ tr/\n//), ($m =~ s/\s+/ /rg);

12
xB.adoc
View File

@ -1,8 +1,8 @@
xB(1)
=====
:doctype: manpage
:manmanual: uirc3 Manual
:mansource: uirc3 {release-version}
:manmanual: xK Manual
:mansource: xK {release-version}
Name
----
@ -60,14 +60,14 @@ using the IRC protocol. (Caveat: the standard C library doesn't automatically
flush FILE streams for pipes on newlines.) A special *XB* command is introduced
for RPC, with the following subcommands:
*XB get_config* _key_::
*XB get_config* __key__::
Request the value of the given configuration option. If no such option
exists, the value will be empty. The response will be delivered in
the following format:
+
```
....
XB :value
```
....
+
This is particularly useful for retrieving the *prefix* string.
@ -100,5 +100,5 @@ _/usr/share/xB/plugins/_::
Reporting bugs
--------------
Use https://git.janouch.name/p/uirc3 to report bugs, request features,
Use https://git.janouch.name/p/xK to report bugs, request features,
or submit pull requests.

5
xB.c
View File

@ -1009,7 +1009,7 @@ is_valid_plugin_name (const char *name)
if (!*name)
return false;
for (const char *p = name; *p; p++)
if (!isgraph (*p) || *p == '/')
if (!isgraph ((uint8_t) *p) || *p == '/')
return false;
return true;
}
@ -1019,6 +1019,7 @@ plugin_resolve_relative_filename (const char *filename)
{
struct strv paths = strv_make ();
get_xdg_data_dirs (&paths);
strv_append (&paths, PROJECT_DATADIR);
char *result = resolve_relative_filename_generic
(&paths, PROGRAM_NAME "/plugins/", filename);
strv_free (&paths);
@ -1212,7 +1213,7 @@ parse_bot_command (const char *s, const char *command, const char **following)
s += command_len;
// Expect a word boundary, so that we don't respond to invalid things
if (isalnum (*s))
if (isalnum ((uint8_t) *s))
return false;
// Ignore any initial spaces; the rest is the command's argument

22
xC.adoc
View File

@ -1,8 +1,8 @@
xC(1)
=====
:doctype: manpage
:manmanual: uirc3 Manual
:mansource: uirc3 {release-version}
:manmanual: xK Manual
:mansource: xK {release-version}
Name
----
@ -25,9 +25,9 @@ Options
other formatting marks to ANSI codes retrieved from the *terminfo*(5)
database:
+
```
....
printf '\x02bold\x02\n' | xC -f
```
....
+
This feature may be used to preview server MOTD files.
@ -62,10 +62,10 @@ their respective function names:
*M-a*: *goto-activity*::
Go to the first following buffer with unseen activity.
*PageUp*: *display-backlog*::
Show the in-memory backlog for this buffer in the backlog helper,
Show the in-memory backlog for this buffer using *general.pager*,
which is almost certainly the *less*(1) program.
*M-h*: *display-full-log*::
Show the log file for this buffer in the backlog helper.
Show the log file for this buffer using *general.pager*.
*M-H*: *toggle-unimportant*::
Hide all join, part and quit messages, as well as all channel mode changes
that only relate to user channel modes. Intended to reduce noise in
@ -77,7 +77,8 @@ their respective function names:
The next key will be interpreted as a formatting mark to insert:
*c* for colours (optionally followed by numbers for the foreground
and background), *i* for italics, *b* for bold text, *u* for underlined,
*x* for struck-through, *v* for inverse text and *o* resets all formatting.
*s* for struck-through, *m* for monospace, *v* for inverse text,
and *o* resets all formatting.
*C-l*: *redraw-screen*::
Should there be any issues with the display, this will clear the terminal
screen and redraw all information.
@ -105,7 +106,7 @@ _~/.config/xC/xC.conf_::
as the */set* command, to make changes in it.
_~/.local/share/xC/logs/_::
When enabled by *behaviour.logging*, log files are stored here.
When enabled by *general.logging*, log files are stored here.
_~/.local/share/xC/plugins/_::
_/usr/local/share/xC/plugins/_::
@ -114,12 +115,11 @@ _/usr/share/xC/plugins/_::
Bugs
----
The editline (libedit) frontend is more of a proof of concept that mostly seems
to work but exhibits bugs that are not our fault.
The editline (libedit) frontend may exhibit some unexpected behaviour.
Reporting bugs
--------------
Use https://git.janouch.name/p/uirc3 to report bugs, request features,
Use https://git.janouch.name/p/xK to report bugs, request features,
or submit pull requests.
See also

2837
xC.c

File diff suppressed because it is too large Load Diff

210
xC.lxdr Normal file
View File

@ -0,0 +1,210 @@
// Backwards-compatible protocol version.
const VERSION = 1;
// From the frontend to the relay.
struct CommandMessage {
// The command sequence number will be repeated in responses
// in the respective fields.
u32 command_seq;
union CommandData switch (enum Command {
HELLO,
ACTIVE,
BUFFER_ACTIVATE,
BUFFER_INPUT,
BUFFER_TOGGLE_UNIMPORTANT,
PING_RESPONSE,
PING,
BUFFER_COMPLETE,
BUFFER_LOG,
} command) {
// If the version check succeeds, the client will receive
// an initial stream of SERVER_UPDATE, BUFFER_UPDATE, BUFFER_STATS,
// BUFFER_LINE, and finally a BUFFER_ACTIVATE message.
case HELLO:
u32 version;
case ACTIVE:
void;
case BUFFER_ACTIVATE:
string buffer_name;
case BUFFER_INPUT:
string buffer_name;
string text;
// XXX: Perhaps this should rather be handled through a /buffer command.
case BUFFER_TOGGLE_UNIMPORTANT:
string buffer_name;
case PING_RESPONSE:
u32 event_seq;
// Only these commands may produce Event.RESPONSE, as below,
// but any command may produce an error.
case PING:
void;
case BUFFER_COMPLETE:
string buffer_name;
string text;
u32 position;
case BUFFER_LOG:
string buffer_name;
} data;
};
// From the relay to the frontend.
struct EventMessage {
u32 event_seq;
union EventData switch (enum Event {
PING,
BUFFER_LINE,
BUFFER_UPDATE,
BUFFER_STATS,
BUFFER_RENAME,
BUFFER_REMOVE,
BUFFER_ACTIVATE,
BUFFER_INPUT,
BUFFER_CLEAR,
SERVER_UPDATE,
SERVER_RENAME,
SERVER_REMOVE,
ERROR,
RESPONSE,
} event) {
case PING:
void;
case BUFFER_LINE:
string buffer_name;
// Whether the line should also be displayed in the active buffer.
bool leak_to_active;
bool is_unimportant;
bool is_highlight;
enum Rendition {
BARE,
INDENT,
STATUS,
ERROR,
JOIN,
PART,
ACTION,
} rendition;
// Unix timestamp in milliseconds.
u64 when;
// Broken-up text, with in-band formatting.
union ItemData switch (enum Item {
TEXT,
RESET,
FG_COLOR,
BG_COLOR,
FLIP_BOLD,
FLIP_ITALIC,
FLIP_UNDERLINE,
FLIP_INVERSE,
FLIP_CROSSED_OUT,
FLIP_MONOSPACE,
} kind) {
case TEXT:
string text;
case RESET:
void;
case FG_COLOR:
i16 color;
case BG_COLOR:
i16 color;
case FLIP_BOLD:
case FLIP_ITALIC:
case FLIP_UNDERLINE:
case FLIP_INVERSE:
case FLIP_CROSSED_OUT:
case FLIP_MONOSPACE:
void;
} items<>;
case BUFFER_UPDATE:
string buffer_name;
bool hide_unimportant;
union BufferContext switch (enum BufferKind {
GLOBAL,
SERVER,
CHANNEL,
PRIVATE_MESSAGE,
} kind) {
case GLOBAL:
void;
case SERVER:
string server_name;
case CHANNEL:
string server_name;
ItemData topic<>;
// This includes parameters, separated by spaces.
string modes;
case PRIVATE_MESSAGE:
string server_name;
} context;
case BUFFER_STATS:
string buffer_name;
// These are cumulative, even for lines flushed out from buffers.
// Updates to these values aren't broadcasted, thus handle:
// - BUFFER_LINE by bumping/setting them as appropriate,
// - BUFFER_ACTIVATE by clearing them for the previous buffer
// (this way, they can be used to mark unread messages).
u32 new_messages;
u32 new_unimportant_messages;
bool highlighted;
case BUFFER_RENAME:
string buffer_name;
string new;
case BUFFER_REMOVE:
string buffer_name;
case BUFFER_ACTIVATE:
string buffer_name;
case BUFFER_INPUT:
string buffer_name;
string text;
case BUFFER_CLEAR:
string buffer_name;
case SERVER_UPDATE:
string server_name;
union ServerData switch (enum ServerState {
DISCONNECTED,
CONNECTING,
CONNECTED,
REGISTERED,
DISCONNECTING,
} state) {
case DISCONNECTED:
case CONNECTING:
case CONNECTED:
void;
case REGISTERED:
string user;
string user_modes;
// Theoretically, we could also send user information in this state,
// but we'd have to duplicate both fields.
case DISCONNECTING:
void;
} data;
case SERVER_RENAME:
// Buffers aren't sent updates for in this circumstance,
// as that wouldn't be sufficiently atomic anyway.
string server_name;
string new;
case SERVER_REMOVE:
string server_name;
// Restriction: command_seq strictly follows the sequence received
// by the relay, across both of these replies.
case ERROR:
u32 command_seq;
string error;
case RESPONSE:
u32 command_seq;
union ResponseData switch (Command command) {
case PING:
void;
case BUFFER_COMPLETE:
u32 start;
string completions<>;
case BUFFER_LOG:
// UTF-8, but not guaranteed.
u8 log<>;
} data;
} data;
};

BIN
xC.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

BIN
xC.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

29
xD-gen-replies.awk Executable file
View File

@ -0,0 +1,29 @@
#!/usr/bin/awk -f
BEGIN {
# The message catalog is a by-product
msg = "xD.msg"
print "$quote \"" > msg
print "$set 1" > msg
}
/^[0-9]+ *IRC_(ERR|RPL)_[A-Z]+ *".*"$/ {
match($0, /".*"/)
ids[$1] = $2
texts[$2] = substr($0, RSTART, RLENGTH)
print $1 " " texts[$2] > msg
}
END {
printf("enum\n{")
for (i in ids) {
if (seen_first)
printf(",")
seen_first = 1
printf("\n\t%s = %s", ids[i], i)
}
print "\n};\n"
print "static const char *g_default_replies[] =\n{"
for (i in ids)
print "\t[" ids[i] "] = " texts[ids[i]] ","
print "};"
}

View File

@ -1,28 +0,0 @@
#!/bin/sh
LC_ALL=C exec awk '
BEGIN {
# The message catalog is a by-product
msg = "xD.msg"
print "$quote \"" > msg;
print "$set 1" > msg;
}
/^[0-9]+ *IRC_(ERR|RPL)_[A-Z]+ *".*"$/ {
match($0, /".*"/);
ids[$1] = $2;
texts[$2] = substr($0, RSTART, RLENGTH);
print $1 " " texts[$2] > msg
}
END {
printf("enum\n{")
for (i in ids) {
if (seen_first)
printf(",")
seen_first = 1
printf("\n\t%s = %s", ids[i], i)
}
print "\n};\n"
print "static const char *g_default_replies[] =\n{"
for (i in ids)
print "\t[" ids[i] "] = " texts[ids[i]] ","
print "};"
}'

View File

@ -1,8 +1,8 @@
xD(1)
=====
:doctype: manpage
:manmanual: uirc3 Manual
:mansource: uirc3 {release-version}
:manmanual: xK Manual
:mansource: xK {release-version}
Name
----
@ -49,5 +49,5 @@ _/etc/xdg/xD/xD.conf_::
Reporting bugs
--------------
Use https://git.janouch.name/p/uirc3 to report bugs, request features,
Use https://git.janouch.name/p/xK to report bugs, request features,
or submit pull requests.

73
xD.c
View File

@ -1,7 +1,7 @@
/*
* xD.c: an IRC daemon
*
* Copyright (c) 2014 - 2021, Přemysl Eric Janouch <p@janouch.name>
* Copyright (c) 2014 - 2022, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
@ -49,7 +49,7 @@ static struct simple_config_item g_config_table[] =
{ "tls_key", NULL, "Server TLS private key (PEM)" },
{ "tls_ciphers", DEFAULT_CIPHERS, "OpenSSL cipher list" },
{ "operators", NULL, "IRCop TLS client cert. SHA-1 fingerprints" },
{ "operators", NULL, "IRCop TLS client cert. SHA-256 fingerprints" },
{ "max_connections", "0", "Global connection limit" },
{ "ping_interval", "180", "Interval between PINGs (sec)" },
@ -296,7 +296,7 @@ irc_is_valid_user_mask (const char *mask)
static bool
irc_is_valid_fingerprint (const char *fp)
{
return irc_regex_match ("^[a-fA-F0-9]{40}$", fp);
return irc_regex_match ("^[a-fA-F0-9]{64}$", fp);
}
// --- Clients (equals users) --------------------------------------------------
@ -853,8 +853,6 @@ client_send_str (struct client *c, const struct str *s)
str_append_data (&c->write_buffer, s->str,
MIN (s->len, IRC_MAX_MESSAGE_LENGTH));
str_append (&c->write_buffer, "\r\n");
// XXX: we might want to move this elsewhere, so that it doesn't get called
// as often; it's going to cause a lot of syscalls with epoll.
client_update_poller (c, NULL);
// Technically we haven't sent it yet but that's a minor detail
@ -1007,8 +1005,8 @@ client_get_ssl_cert_fingerprint (struct client *c)
if (i2d_X509 (peer_cert, &p) < 0)
return NULL;
unsigned char hash[SHA_DIGEST_LENGTH];
SHA1 (cert, cert_len, hash);
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256 (cert, cert_len, hash);
struct str fingerprint = str_make ();
for (size_t i = 0; i < sizeof hash; i++)
@ -2932,6 +2930,29 @@ irc_handle_links (const struct irc_message *msg, struct client *c)
irc_send_reply (c, IRC_RPL_ENDOFLINKS, mask);
}
static void
irc_handle_wallops (const struct irc_message *msg, struct client *c)
{
if (msg->params.len < 1)
RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command);
if (!(c->mode & IRC_USER_MODE_OPERATOR))
RETURN_WITH_REPLY (c, IRC_ERR_NOPRIVILEGES);
const char *message = msg->params.vector[0];
// Our interpretation: anonymize the sender,
// and target all users who want to receive these messages
struct str_map_iter iter = str_map_iter_make (&c->ctx->users);
struct client *target;
while ((target = str_map_iter_next (&iter)))
{
if (target != c && !(target->mode & IRC_USER_MODE_RX_WALLOPS))
continue;
client_send (target, ":%s WALLOPS :%s", c->ctx->server_name, message);
}
}
static void
irc_handle_kill (const struct irc_message *msg, struct client *c)
{
@ -2994,6 +3015,7 @@ irc_register_handlers (struct server_context *ctx)
{ "ADMIN", true, irc_handle_admin, 0, 0 },
{ "STATS", true, irc_handle_stats, 0, 0 },
{ "LINKS", true, irc_handle_links, 0, 0 },
{ "WALLOPS", true, irc_handle_wallops, 0, 0 },
{ "MODE", true, irc_handle_mode, 0, 0 },
{ "PRIVMSG", true, irc_handle_privmsg, 0, 0 },
@ -3071,6 +3093,7 @@ irc_try_read (struct client *c)
{
buf->str[buf->len += n_read] = '\0';
// TODO: discard characters above the 512 character limit
// FIXME: we should probably discard the data if closing_link
irc_process_buffer (buf, irc_process_message, c);
continue;
}
@ -3112,6 +3135,7 @@ irc_try_read_tls (struct client *c)
case SSL_ERROR_NONE:
buf->str[buf->len += n_read] = '\0';
// TODO: discard characters above the 512 character limit
// FIXME: we should probably discard the data if closing_link
irc_process_buffer (buf, irc_process_message, c);
continue;
case SSL_ERROR_ZERO_RETURN:
@ -3397,16 +3421,10 @@ irc_try_fetch_client (struct server_context *ctx, int listen_fd)
if (errno == EINTR)
return true;
if (errno == EBADF
|| errno == EINVAL
|| errno == ENOTSOCK
|| errno == EOPNOTSUPP)
print_fatal ("%s: %s", "accept", strerror (errno));
// OS kernels may return a wide range of unforeseeable errors.
// Assuming that they're either transient or caused by
// a connection that we've just extracted from the queue.
if (accept_error_is_transient (errno))
print_warning ("%s: %s", "accept", strerror (errno));
else
print_fatal ("%s: %s", "accept", strerror (errno));
return true;
}
@ -3790,10 +3808,9 @@ irc_lock_pid_file (struct server_context *ctx, struct error **e)
}
static int
irc_listen (struct addrinfo *gai_iter)
irc_listen (struct addrinfo *ai)
{
int fd = socket (gai_iter->ai_family,
gai_iter->ai_socktype, gai_iter->ai_protocol);
int fd = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (fd == -1)
return -1;
set_cloexec (fd);
@ -3807,21 +3824,13 @@ irc_listen (struct addrinfo *gai_iter)
#if defined SOL_IPV6 && defined IPV6_V6ONLY
// Make NULL always bind to both IPv4 and IPv6, irrespectively of the order
// of results; only INADDR6_ANY seems to be affected by this
if (gai_iter->ai_family == AF_INET6)
if (ai->ai_family == AF_INET6)
soft_assert (setsockopt (fd, SOL_IPV6, IPV6_V6ONLY,
&yes, sizeof yes) != -1);
#endif
char host[NI_MAXHOST], port[NI_MAXSERV];
host[0] = port[0] = '\0';
int err = getnameinfo (gai_iter->ai_addr, gai_iter->ai_addrlen,
host, sizeof host, port, sizeof port,
NI_NUMERICHOST | NI_NUMERICSERV);
if (err)
print_debug ("%s: %s", "getnameinfo", gai_strerror (err));
char *address = format_host_port_pair (host, port);
if (bind (fd, gai_iter->ai_addr, gai_iter->ai_addrlen))
char *address = gai_reconstruct_address (ai);
if (bind (fd, ai->ai_addr, ai->ai_addrlen))
print_error ("bind to %s failed: %s", address, strerror (errno));
else if (listen (fd, 16 /* arbitrary number */))
print_error ("listen on %s failed: %s", address, strerror (errno));
@ -3841,12 +3850,12 @@ static void
irc_listen_resolve (struct server_context *ctx,
const char *host, const char *port, struct addrinfo *gai_hints)
{
struct addrinfo *gai_result, *gai_iter;
struct addrinfo *gai_result = NULL, *gai_iter = NULL;
int err = getaddrinfo (host, port, gai_hints, &gai_result);
if (err)
{
char *address = format_host_port_pair (host, port);
print_error ("bind to %s failed: %s: %s",
print_error ("binding to %s failed: %s: %s",
address, "getaddrinfo", gai_strerror (err));
free (address);
return;

172
xF.c Normal file
View File

@ -0,0 +1,172 @@
/*
* xF.c: a toothless IRC client frontend
*
* Copyright (c) 2022, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "config.h"
#define PROGRAM_NAME "xF"
#include "common.c"
#include "xC-proto.c"
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <X11/XKBlib.h>
#include <X11/Xft/Xft.h>
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static struct
{
bool polling;
struct connector connector;
int socket;
}
g;
static void
on_connector_connecting (void *user_data, const char *address)
{
(void) user_data;
print_status ("connecting to %s...", address);
}
static void
on_connector_error (void *user_data, const char *error)
{
(void) user_data;
print_status ("connection failed: %s", error);
}
static void
on_connector_failure (void *user_data)
{
(void) user_data;
exit_fatal ("giving up");
}
static void
on_connector_connected (void *user_data, int socket, const char *hostname)
{
(void) user_data;
(void) hostname;
g.polling = false;
g.socket = socket;
}
static void
protocol_test (const char *host, const char *port)
{
struct poller poller = {};
poller_init (&poller);
connector_init (&g.connector, &poller);
g.connector.on_connecting = on_connector_connecting;
g.connector.on_error = on_connector_error;
g.connector.on_connected = on_connector_connected;
g.connector.on_failure = on_connector_failure;
connector_add_target (&g.connector, host, port);
g.polling = true;
while (g.polling)
poller_run (&poller);
connector_free (&g.connector);
struct str s = str_make ();
str_pack_u32 (&s, 0);
struct relay_command_message m = {};
m.data.hello.command = RELAY_COMMAND_HELLO;
m.data.hello.version = RELAY_VERSION;
if (!relay_command_message_serialize (&m, &s))
exit_fatal ("serialization failed");
uint32_t len = htonl (s.len - sizeof len);
memcpy (s.str, &len, sizeof len);
if (errno = 0, write (g.socket, s.str, s.len) != (ssize_t) s.len)
exit_fatal ("short send or error: %s", strerror (errno));
char buf[1 << 20] = "";
while (errno = 0, read (g.socket, &len, sizeof len) == sizeof len)
{
len = ntohl (len);
if (errno = 0, read (g.socket, buf, MIN (len, sizeof buf)) != len)
exit_fatal ("short read or error: %s", strerror (errno));
struct msg_unpacker r = msg_unpacker_make (buf, len);
struct relay_event_message m = {};
if (!relay_event_message_deserialize (&m, &r))
exit_fatal ("deserialization failed");
if (msg_unpacker_get_available (&r))
exit_fatal ("trailing data");
printf ("event: %d\n", m.data.event);
relay_event_message_free (&m);
}
exit_fatal ("short read or error: %s", strerror (errno));
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int
main (int argc, char *argv[])
{
static const struct opt opts[] =
{
{ 'h', "help", NULL, 0, "display this help and exit" },
{ 'V', "version", NULL, 0, "output version information and exit" },
{ 0, NULL, NULL, 0, NULL }
};
struct opt_handler oh = opt_handler_make (argc, argv, opts,
"HOST:PORT", "X11 frontend for xC.");
int c;
while ((c = opt_handler_get (&oh)) != -1)
switch (c)
{
case 'h':
opt_handler_usage (&oh, stdout);
exit (EXIT_SUCCESS);
case 'V':
printf (PROGRAM_NAME " " PROGRAM_VERSION "\n");
exit (EXIT_SUCCESS);
default:
print_error ("wrong options");
opt_handler_usage (&oh, stderr);
exit (EXIT_FAILURE);
}
argc -= optind;
argv += optind;
if (argc != 1)
{
opt_handler_usage (&oh, stderr);
exit (EXIT_FAILURE);
}
opt_handler_free (&oh);
char *address = xstrdup (argv[0]);
const char *port = NULL, *host = tokenize_host_port (address, &port);
if (!port)
exit_fatal ("missing port number/service name");
// TODO: Actually implement an X11-based user interface.
protocol_test (host, port);
return 0;
}

34
xF.svg Normal file
View File

@ -0,0 +1,34 @@
<?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>
<linearGradient id="background" x1="0" y1="0" x2="1" y2="1">
<stop stop-color="#808080" offset="0" />
<stop stop-color="#000000" offset="1" />
</linearGradient>
<!-- librsvg screws up the filter's orientation in a weird way
otherwise a larger blur value would look better -->
<filter id="shadow" color-interpolation-filters="sRGB">
<feOffset dy="0.5" />
<feGaussianBlur stdDeviation="0.5" />
<feComposite in2="SourceGraphic" operator="in" />
</filter>
<clipPath id="clip">
<rect x="-7" y="-10" width="14" height="20" />
</clipPath>
</defs>
<circle cx="24" cy="24" r="20"
fill="url(#background)" stroke="#404040" stroke-width="2" />
<g transform="rotate(-45 24 24)" filter="url(#shadow)">
<path d="m 12,25 h 24 v 11 h -5 v -8 h -4.5 v 6 h -5 v -6 h -9.5 z"
fill="#ffffff" />
<g stroke-width="4" transform="translate(24, 16)" clip-path="url(#clip)"
stroke="#ffffff">
<line x1="-8" x2="8" y1="-5" y2="5" />
<line x1="-8" x2="8" y1="5" y2="-5" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

1
xK-version Normal file
View File

@ -0,0 +1 @@
2.0.0

45
xM/CMakeLists.txt Normal file
View File

@ -0,0 +1,45 @@
# Swift language support
cmake_minimum_required (VERSION 3.15)
file (READ ../xK-version project_version)
configure_file (../xK-version xK-version.tag COPYONLY)
string (STRIP "${project_version}" project_version)
# There were two issues when building this from the main CMakeLists.txt:
# a) renaming main.swift to xM.swift requires removing top-level statements,
# b) there is a "redefinition of module 'FFI'" error.
project (xM VERSION "${project_version}"
DESCRIPTION "Cocoa frontend for xC" LANGUAGES Swift)
set (root "${PROJECT_SOURCE_DIR}/..")
add_custom_command (OUTPUT xC-proto.swift
COMMAND env LC_ALL=C awk
-f ${root}/liberty/tools/lxdrgen.awk
-f ${root}/liberty/tools/lxdrgen-swift.awk
-v PrefixCamel=Relay
${root}/xC.lxdr > xC-proto.swift
DEPENDS
${root}/liberty/tools/lxdrgen.awk
${root}/liberty/tools/lxdrgen-swift.awk
${root}/xC.lxdr
COMMENT "Generating xC relay protocol code" VERBATIM)
set (MACOSX_BUNDLE_GUI_IDENTIFIER name.janouch.${PROJECT_NAME})
set (MACOSX_BUNDLE_ICON_FILE xM.icns)
# Avoid including binary files in the repository by generating icons in code.
# sips(1) + Javascript + iconutil(1) could probably also be used.
find_program (SWIFT_EXECUTABLE swift REQUIRED)
set (icon "${PROJECT_BINARY_DIR}/${MACOSX_BUNDLE_ICON_FILE}")
add_custom_command (OUTPUT "${icon}"
COMMAND ${SWIFT_EXECUTABLE} "${PROJECT_SOURCE_DIR}/gen-icon.swift" "${icon}"
DEPENDS gen-icon.swift
COMMENT "Generating xM application icon" VERBATIM)
set_source_files_properties ("${icon}" PROPERTIES
MACOSX_PACKAGE_LOCATION Resources)
# Other requirements: macOS 10.14 for Network, and macOS 11 for Logger.
set (CMAKE_Swift_LANGUAGE_VERSION 5)
add_executable (xM MACOSX_BUNDLE
main.swift "${icon}" "${PROJECT_BINARY_DIR}/xC-proto.swift")

96
xM/gen-icon.swift Normal file
View File

@ -0,0 +1,96 @@
// gen-icon.swift: generate a program icon for xM in the Apple icon format
//
// Copyright (c) 2023, Přemysl Eric Janouch <p@janouch.name>
// SPDX-License-Identifier: 0BSD
//
// NSGraphicsContext mostly just weirdly wraps over Quartz,
// so we do it all in Quartz directly.
import CoreGraphics
import Foundation
import ImageIO
import UniformTypeIdentifiers
// Apple uses something that's close to a "quintic superellipse" in their icons,
// but doesn't quite match. Either way, it looks better than rounded rectangles.
func addSquircle(context: CGContext, bounds: CGRect) {
context.move(to: CGPoint(x: bounds.maxX, y: bounds.midY))
for theta in stride(from: 0.0, to: .pi * 2, by: .pi / 1e4) {
let x = pow(abs(cos(theta)), 2 / 5.0) * bounds.width / 2
* CGFloat(signOf: cos(theta), magnitudeOf: 1) + bounds.midX
let y = pow(abs(sin(theta)), 2 / 5.0) * bounds.height / 2
* CGFloat(signOf: sin(theta), magnitudeOf: 1) + bounds.midY
context.addLine(to: CGPoint(x: x, y: y))
}
context.closePath()
}
func drawIcon(scale: CGFloat) -> CGImage? {
let size = CGSizeMake(1024, 1024)
let colorspace = CGColorSpaceCreateDeviceRGB()
let context = CGContext(data: nil,
width: Int(size.width * scale), height: Int(size.height * scale),
bitsPerComponent: 8, bytesPerRow: 0, space: colorspace,
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)!
context.scaleBy(x: scale, y: scale)
let bounds = CGRectMake(100, 100, size.width - 200, size.height - 200)
addSquircle(context: context, bounds: bounds)
let squircle = context.path!
// Gradients don't draw shadows, so draw it separately.
context.saveGState()
context.setShadow(offset: CGSizeMake(0, -12).applying(context.ctm),
blur: 28 * scale, color: CGColor(gray: 0, alpha: 0.5))
context.setFillColor(CGColor(red: 1, green: 0x55p-8, blue: 0, alpha: 1))
context.fillPath()
context.restoreGState()
context.saveGState()
context.addPath(squircle)
context.clip()
context.drawLinearGradient(
CGGradient(colorsSpace: colorspace, colors: [
CGColor(red: 1, green: 0x00p-8, blue: 0, alpha: 1),
CGColor(red: 1, green: 0xaap-8, blue: 0, alpha: 1)
] as CFArray, locations: [0, 1])!,
start: CGPointMake(0, 100), end: CGPointMake(0, size.height - 100),
options: CGGradientDrawingOptions(rawValue: 0))
context.restoreGState()
context.move(to: CGPoint(x: size.width * 0.30, y: size.height * 0.30))
context.addLine(to: CGPoint(x: size.width * 0.30, y: size.height * 0.70))
context.addLine(to: CGPoint(x: size.width * 0.575, y: size.height * 0.425))
context.move(to: CGPoint(x: size.width * 0.70, y: size.height * 0.30))
context.addLine(to: CGPoint(x: size.width * 0.70, y: size.height * 0.70))
context.addLine(to: CGPoint(x: size.width * 0.425, y: size.height * 0.425))
context.setLineWidth(80)
context.setLineCap(.round)
context.setLineJoin(.round)
context.setStrokeColor(CGColor.white)
context.strokePath()
return context.makeImage()
}
if CommandLine.arguments.count != 2 {
print("Usage: \(CommandLine.arguments.first!) OUTPUT.icns")
exit(EXIT_FAILURE)
}
let filename = CommandLine.arguments[1]
let macOSSizes: Array<CGFloat> = [16, 32, 128, 256, 512]
let icns = CGImageDestinationCreateWithURL(
URL(fileURLWithPath: filename) as CFURL,
UTType.icns.identifier as CFString, macOSSizes.count * 2, nil)!
for size in macOSSizes {
CGImageDestinationAddImage(icns, drawIcon(scale: size / 1024.0)!, nil)
CGImageDestinationAddImage(icns, drawIcon(scale: size / 1024.0 * 2)!, [
kCGImagePropertyDPIWidth: 144,
kCGImagePropertyDPIHeight: 144,
] as CFDictionary)
}
if !CGImageDestinationFinalize(icns) {
print("ICNS finalization failed.")
exit(EXIT_FAILURE)
}

1372
xM/main.swift Normal file

File diff suppressed because it is too large Load Diff

3
xN/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/xN
/xN.1
/irc.go

21
xN/Makefile Normal file
View File

@ -0,0 +1,21 @@
.POSIX:
.SUFFIXES:
AWK = env LC_ALL=C awk
outputs = irc.go xN xN.1
all: $(outputs)
# If we want to keep module dependencies separate, we don't have many options.
# Symlinking seems to work good enough.
irc.go: ../xS/irc.go
ln -sf ../xS/irc.go $@
xN: xN.go ../xK-version irc.go
go build -ldflags "-X 'main.projectVersion=$$(cat ../xK-version)'" -o $@
xN.1: ../xK-version ../liberty/tools/asciiman.awk xN.adoc
env "asciidoc-release-version=$$(cat ../xK-version)" \
$(AWK) -f ../liberty/tools/asciiman.awk xN.adoc > $@
test: all
go test
clean:
rm -f $(outputs)

3
xN/go.mod Normal file
View File

@ -0,0 +1,3 @@
module janouch.name/xK/xN
go 1.19

86
xN/xN.adoc Normal file
View File

@ -0,0 +1,86 @@
xN(1)
=====
:doctype: manpage
:manmanual: xK Manual
:mansource: xK {release-version}
Name
----
xN - IRC notifier
Synopsis
--------
*xN* [_OPTION_]... IRC-URL...
Description
-----------
*xN* is a simple IRC notifier, sending the text it receives on its standard
input to all IRC targets specified by its command line arguments.
The input text is forced to validate as UTF-8, and it is _not_ split
automatically to comply with the maximum IRC message length.
Thus, make sure to make the lines short, or they will be trimmed by the servers.
*xN* does not attempt to appease flood detectors.
Options
-------
*-debug*::
Print incoming IRC traffic, which may help in debugging any issues.
*-version*::
Output version information and exit.
URL format
----------
*xN* accepts URLs describing IRC users and channels, roughly as specified by
the Internet Draft _draft-butcher-irc-url-04.txt_. Note, however, that close
to no validation is done on these, and you should not pass URLs from untrusted
sources, so as to avoid command or parameter injection.
Any provided username will be propagated to the nickname, username,
and realname. The default value for these is the name of your system user.
As an extension, you may use the following options:
*skipjoin*::
Do not JOIN channels before sending messages to them.
This requires channels to allow external messages
(which are disabled by channel mode *+n*).
*usenotice*::
Send a NOTICE rather than a PRIVMSG, which distinguishes automated messages,
and is more appropriate for bots.
Examples
--------
$ uptime | xN 'irc://uptime@localhost/%23watch?skipjoin&usenotice'
Send *uptime*(1) information as an external notice to channel *#watch*
on the local server, using the standard port *6667*.
$ fortune -s | xN ircs://ohayou@irc.libera.chat/john,isuser
Greet user *john* with a fortune for this day. In compliance with _RFC 7194_,
the default TLS port is assumed to be *6697*.
$ xN 'ircs://agent:Password123@irc.cia.gov:1337/#hq?key=123456' <<EOF
The red fox trots quietly at midnight.
EOF
Connect over TLS to *irc.cia.gov* on port *1337*, use *Password123*
as the server password, register as user *agent*,
join channel *#hq* using the channel key *123456*,
and send a very secret message.
Reporting bugs
--------------
Use https://git.janouch.name/p/xK to report bugs, request features,
or submit pull requests.
See also
--------
_Uniform Resource Locator Schemes for Internet Relay Chat Entities_,
https://datatracker.ietf.org/doc/html/draft-butcher-irc-url-04[].
_Default Port for Internet Relay Chat (IRC) via TLS/SSL_,
https://datatracker.ietf.org/doc/html/rfc7194[].

279
xN/xN.go Normal file
View File

@ -0,0 +1,279 @@
//
// Copyright (c) 2024, Přemysl Eric Janouch <p@janouch.name>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// xN is a simple IRC notifier.
package main
import (
"bufio"
"bytes"
"crypto/tls"
"errors"
"flag"
"fmt"
"io"
"log"
"net"
"net/url"
"os"
"os/user"
"strconv"
"strings"
)
const projectName = "xN"
var projectVersion = "?"
var debugMode = false
type parameters struct {
conn net.Conn // underlying network connection
username string // nickname + username + realname
password string // server password
target string // where to send text
isuser bool // the target is a user rather than a channel
chankey string // channel key
skipjoin bool // whether to send external messages to channels
usenotice bool // whether to use NOTICE rather than PRIVMSG
message []string // lines of the message to send
}
func (p *parameters) send(command string, args ...string) error {
var buf bytes.Buffer
buf.WriteString(command)
for i, arg := range args {
buf.WriteRune(' ')
if i+1 == len(args) {
buf.WriteRune(':')
}
buf.WriteString(arg)
}
buf.WriteString("\r\n")
_, err := p.conn.Write(buf.Bytes())
return err
}
const (
rplWELCOME = "001"
errNICKNAMEINUSE = "433"
)
func notify(p *parameters) error {
// The intro should comfortably fit in the TCP send buffer whole.
if p.password != "" {
p.send("PASS", p.password)
}
p.send("USER", p.username, "0", "*", p.username)
p.send("NICK", p.username)
scanner, nickCounter, issue := bufio.NewScanner(p.conn), 1, ""
for scanner.Scan() {
if debugMode {
log.Println(scanner.Text())
}
m := ircParseMessage(scanner.Text())
switch m.command {
case "PING":
p.send("PONG", m.params...)
case rplWELCOME:
if !p.isuser && !p.skipjoin {
if p.chankey != "" {
p.send("JOIN", p.target, p.chankey)
} else {
p.send("JOIN", p.target)
}
}
for _, line := range p.message {
if p.usenotice {
p.send("NOTICE", p.target, line)
} else {
p.send("PRIVMSG", p.target, line)
}
}
p.send("QUIT")
case errNICKNAMEINUSE:
p.send("NICK", fmt.Sprintf("%s%d", p.username, nickCounter))
nickCounter++
default:
// Prevent hanging on unsuccessful registrations.
numeric, _ := strconv.Atoi(m.command)
if numeric >= 400 && numeric <= 599 {
if len(m.params) > 1 {
issue = strings.Join(m.params[1:], " ")
} else {
issue = strings.Join(m.params, " ")
}
p.send("QUIT")
}
}
}
if err := scanner.Err(); err != nil {
return err
}
if issue != "" {
return errors.New(issue)
}
return nil
}
func parse(rawURL string, text []byte) (
p parameters, connect func() (net.Conn, error), err error) {
u, err := url.Parse(rawURL)
if err != nil {
return p, nil, err
} else if !u.IsAbs() || u.Opaque != "" {
return p, nil, errors.New("need an absolute URL")
} else if u.Path == "/" && u.Fragment != "" {
// Try to handle the common but degenerate case.
fragment := "%23" + u.Fragment
u.Fragment, u.RawFragment = "", ""
if u, err = url.Parse(u.String() + fragment); err != nil {
return p, nil, err
}
}
// Figure out registration details.
p.username = projectName
if u, _ := user.Current(); u != nil {
p.username = u.Username
}
if u.User.Username() != "" {
p.username = u.User.Username()
}
p.password, _ = u.User.Password()
// Figure out the target, which for our intents must accept messages.
path, _ := strings.CutPrefix(u.Path, "/")
elements := strings.Split(path, ",")
if path == "" || elements[0] == "" {
return p, nil, errors.New("unspecified entity")
}
// The last entity type wins.
p.target, p.isuser = elements[0], false
for _, typ := range elements[1:] {
switch typ {
case "isuser":
p.isuser = true
case "ischannel":
p.isuser = false
case "isserver":
// We do not support network names, and this is the default.
default:
return p, nil, errors.New("unsupported type: " + typ)
}
}
if p.isuser {
if i := strings.IndexAny(p.target, "!@"); i != -1 {
p.target = p.target[:i]
}
} else if !strings.HasPrefix(p.target, "#") {
// TODO(p): We should consult RPL_ISUPPORT rather than guess,
// though other prefixes are rare.
p.target = "#" + p.target
}
// Note that the draft RFC wants these to be case-insensitive.
p.chankey = u.Query().Get("key")
// Being able to skip channel join is our own requirement and invention,
// as are notices (names taken from Travis CI configuration).
p.skipjoin = u.Query().Has("skipjoin")
p.usenotice = u.Query().Has("usenotice")
// Ensure valid LF-separated UTF-8, and split it at lines.
sanitized := strings.ReplaceAll(string([]rune(string(text))), "\r", "\n")
for _, line := range strings.Split(sanitized, "\n") {
if line != "" {
p.message = append(p.message, line)
}
}
hostname, port := u.Hostname(), u.Port()
switch u.Scheme {
case "irc":
if port == "" {
port = "6667"
}
connect = func() (net.Conn, error) {
return net.Dial("tcp", net.JoinHostPort(hostname, port))
}
case "ircs":
if port == "" {
port = "6697"
}
connect = func() (net.Conn, error) {
return tls.Dial("tcp", net.JoinHostPort(hostname, port), nil)
}
default:
err = errors.New("unsupported scheme: " + u.Scheme)
}
return
}
// notifyByURL sends the given text to the IRC server specified by a URL.
// See draft-butcher-irc-url-04.txt for the URL scheme specification
// this function loosely follows.
func notifyByURL(rawURL string, text []byte) error {
p, connect, err := parse(rawURL, text)
if p.conn, err = connect(); err != nil {
return err
}
defer p.conn.Close()
return notify(&p)
}
func main() {
flag.BoolVar(&debugMode, "debug", false, "run in verbose debug mode")
version := flag.Bool("version", false, "show version and exit")
flag.Usage = func() {
f := flag.CommandLine.Output()
fmt.Fprintf(f, "Usage: %s [OPTION]... URL...\n", os.Args[0])
flag.PrintDefaults()
}
flag.Parse()
if flag.NArg() < 1 {
flag.Usage()
os.Exit(2)
}
if *version {
fmt.Printf("%s %s\n", projectName, projectVersion)
return
}
text, err := io.ReadAll(os.Stdin)
if err != nil {
log.Fatalln(err)
}
status := 0
for _, rawURL := range flag.Args() {
if err := notifyByURL(rawURL, text); err != nil {
status = 1
var ue *url.Error
if errors.As(err, &ue) {
log.Println(err)
} else {
log.Printf("notify %q: %s\n", rawURL, err)
}
}
}
os.Exit(status)
}

98
xN/xN_test.go Normal file
View File

@ -0,0 +1,98 @@
//
// Copyright (c) 2024, Přemysl Eric Janouch <p@janouch.name>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
package main
import "testing"
func TestParseURL(t *testing.T) {
for _, rawURL := range []string{
`irc:server/channel`,
`irc://server/channel,isnetwork`,
`ircs://ssl.ircnet.io`,
`ircs://ssl.ircnet.io/`,
`http://server/path`,
} {
if _, _, err := parse(rawURL, nil); err == nil {
t.Errorf("%q should not parse\n", rawURL)
}
}
for _, test := range []struct {
rawURL string
p parameters
}{
{
rawURL: `irc://uptime@localhost/%23watch?skipjoin&usenotice`,
p: parameters{
username: "uptime",
target: "#watch",
skipjoin: true,
usenotice: true,
},
},
{
rawURL: `ircs://ohayou@irc.libera.chat/john,isuser,isserver`,
p: parameters{
username: "ohayou",
target: "john",
isuser: true,
},
},
{
rawURL: `ircs://agent:Password123@irc.cia.gov:1337/#hq?key=123456`,
p: parameters{
username: "agent",
password: "Password123",
target: "#hq",
chankey: "123456",
},
},
} {
p, _, err := parse(test.rawURL, nil)
if err != nil {
t.Errorf("%q should parse, got: %s\n", test.rawURL, err)
continue
}
if p.username != test.p.username {
t.Errorf("%q: username: %v ≠ %v\n",
test.rawURL, p.username, test.p.username)
}
if p.password != test.p.password {
t.Errorf("%q: password: %v ≠ %v\n",
test.rawURL, p.password, test.p.password)
}
if p.target != test.p.target {
t.Errorf("%q: target: %v ≠ %v\n",
test.rawURL, p.target, test.p.target)
}
if p.isuser != test.p.isuser {
t.Errorf("%q: isuser: %v ≠ %v\n",
test.rawURL, p.isuser, test.p.isuser)
}
if p.chankey != test.p.chankey {
t.Errorf("%q: chankey: %v ≠ %v\n",
test.rawURL, p.chankey, test.p.chankey)
}
if p.skipjoin != test.p.skipjoin {
t.Errorf("%q: skipjoin: %v ≠ %v\n",
test.rawURL, p.skipjoin, test.p.skipjoin)
}
if p.usenotice != test.p.usenotice {
t.Errorf("%q: usenotice: %v ≠ %v\n",
test.rawURL, p.usenotice, test.p.usenotice)
}
}
}

BIN
xP.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

4
xP/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/xP
/proto.go
/public/proto.js
/public/mithril.js

21
xP/Makefile Normal file
View File

@ -0,0 +1,21 @@
.POSIX:
.SUFFIXES:
AWK = env LC_ALL=C awk
tools = ../liberty/tools
outputs = xP proto.go public/proto.js public/mithril.js
all: $(outputs) public/ircfmt.woff2
xP: xP.go proto.go
go build -o $@
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 > $@
public/proto.js: $(tools)/lxdrgen.awk $(tools)/lxdrgen-mjs.awk ../xC.lxdr
$(AWK) -f $(tools)/lxdrgen.awk -f $(tools)/lxdrgen-mjs.awk ../xC.lxdr > $@
public/ircfmt.woff2: gen-ircfmt.awk
$(AWK) -v Output=$@ -f gen-ircfmt.awk
public/mithril.js:
curl -Lo $@ https://unpkg.com/mithril/mithril.js
clean:
rm -f $(outputs)

89
xP/gen-ircfmt.awk Normal file
View File

@ -0,0 +1,89 @@
# gen-ircfmt.awk: generate a supplementary font for IRC formatting characters
#
# Copyright (c) 2022, Přemysl Eric Janouch <p@janouch.name>
# SPDX-License-Identifier: 0BSD
#
# Usage: awk -v Output=static/ircfmt.woff2 -f gen-ircfmt.awk
# Clean up SVG byproducts yourself.
BEGIN {
if (!Output) {
print "Error: you must specify the output filename"
exit 1
}
}
function glyph(name, code, path, filename, actions, cmd) {
filename = Output "." name ".svg"
# Inkscape still does a terrible job at the stroke-to-path conversion.
actions = \
"select-by-id:group;" \
"selection-ungroup;" \
"select-clear;" \
"select-by-id:path;" \
"object-stroke-to-path;" \
"select-by-id:clip;" \
"path-intersection;" \
"select-all;" \
"path-combine;" \
"export-overwrite;" \
"export-filename:" filename ";" \
"export-do"
# These dimensions fit FontForge defaults, and happen to work well.
cmd = "inkscape --pipe --actions='" actions "'"
print "<?xml version='1.0' encoding='UTF-8' standalone='no'?>\n" \
"<svg version='1.1' xmlns='http://www.w3.org/2000/svg'\n" \
" width='1000' height='1000' viewBox='0 0 1000 1000'>\n" \
" <rect x='0' y='0' width='1000' height='1000' />\n" \
" <g id='group' transform='translate(200 200) scale(60, 60)'>\n" \
" <rect id='clip' x='0' y='0' width='10' height='10' />\n" \
" <path id='path' stroke-width='2' fill='none' stroke='#fff'\n" \
" d='" path "' />\n" \
" </g>\n" \
"</svg>\n" | cmd
close(cmd)
print "Select(0u" code ")\n" \
"Import('" filename "')" | FontForge
}
BEGIN {
FontForge = "fontforge -lang=ff -"
print "New()" | FontForge
# Designed a 10x10 raster, going for maximum simplicity.
glyph("B", "02", "m 6,5 c 0,0 2,0 2,2 0,2 -2,2 -2,2 h -3 v -8 h 2.5 c 0,0 2,0 2,2 0,2 -2,2 -2,2 h -2 Z")
glyph("C", "03", "m 7.6,7 A 3,4 0 0 1 4.25,8.875 3,4 0 0 1 2,5 3,4 0 0 1 4.25,1.125 3,4 0 0 1 7.6,3")
glyph("I", "1D", "m 3,9 h 4 m 0,-8 h -4 m 2,-1 v 10")
glyph("M", "11", "m 2,10 v -10 l 3,6 3,-6 v 10")
glyph("O", "0F", "m 1,9 l 8,-8 M 2,5 a 3,3 0 1 0 6,0 3,3 0 1 0 -6,0 z")
#glyph("R", "0F", "m 3,10 v -9 h 2 c 0,0 2.5,0 2.5,2.5 0,2.5 -2.5,2.5 -2.5,2.5 h -2 2.5 l 2.5,4.5")
glyph("S", "1E", "m 7.5,3 c 0,-1 -1,-2 -2.5,-2 -1.5,0 -2.5,1 -2.5,2 0,3 5,1 5,4 0,1 -1,2 -2.5,2 -1.5,0 -2.5,-1 -2.5,-2")
glyph("U", "1F", "m 2.5,0 v 6.5 c 0,1.5 1,2.5 2.5,2.5 1.5,0 2.5,-1 2.5,-2.5 v -6.5")
glyph("V", "16", "m 2,-1 3,11 3,-11")
# In practice, your typical browser font will overshoot its em box,
# so to make the display more cohesive, we need to do the same.
# Sadly, sf->use_typo_metrics can't be unset from FontForge script--
# this is necessary to prevent the caret from jumping upon the first
# inserted non-formatting character in xP's textarea.
# https://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align
print "SelectAll()\n" \
"Scale(115, 115, 0, 0)\n" \
"SetOS2Value('WinAscentIsOffset', 1)\n" \
"SetOS2Value('WinDescentIsOffset', 1)\n" \
"SetOS2Value('HHeadAscentIsOffset', 1)\n" \
"SetOS2Value('HHeadDescentIsOffset', 1)\n" \
"CorrectDirection()\n" \
"AutoWidth(100)\n" \
"AutoHint()\n" \
"AddExtrema()\n" \
"RoundToInt()\n" \
"SetFontNames('IRCFormatting-Regular'," \
" 'IRC Formatting', 'IRC Formatting Regular', 'Regular'," \
" 'Copyright (c) 2022, Premysl Eric Janouch')\n" \
"Generate('" Output "')\n" | FontForge
close(FontForge)
}

10
xP/go.mod Normal file
View File

@ -0,0 +1,10 @@
module janouch.name/xK/xP
go 1.18
require nhooyr.io/websocket v1.8.7
require (
github.com/klauspost/compress v1.15.9 // indirect
golang.org/x/sys v0.0.0-20210423082822-04245dca01da // indirect
)

62
xP/go.sum Normal file
View File

@ -0,0 +1,62 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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=

BIN
xP/public/ircfmt.woff2 Normal file

Binary file not shown.

260
xP/public/xP.css Normal file
View File

@ -0,0 +1,260 @@
@font-face {
src: url('ircfmt.woff2') format('woff2');
font-family: 'IRC Formatting';
font-weight: normal;
font-style: normal;
}
body {
margin: 0;
padding: 0;
/* Firefox only renders C0 within the textarea, why? */
font-family: 'IRC Formatting', sans-serif;
font-size: clamp(0.5rem, 2vw, 1rem);
}
.xP {
display: flex;
flex-direction: column;
overflow: hidden;
height: 100vh;
/* https://caniuse.com/viewport-unit-variants */
height: 100dvh;
}
.overlay {
padding: .6em .9em;
background: #eee;
border: 1px outset #eee;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
text-align: center;
z-index: 1;
}
.title, .status {
padding: .05em .3em;
background: #eee;
display: flex;
justify-content: space-between;
align-items: baseline;
column-gap: .3em;
position: relative;
border-top: 3px solid #ccc;
border-bottom: 2px solid #888;
}
.title {
/* To approximate right-aligned space-between. */
flex-direction: row-reverse;
}
.title:before, .status:before {
content: " ";
position: absolute;
left: 0;
right: 0;
height: 2px;
top: -2px;
background: #fff;
}
.title:after, .status:after {
content: " ";
position: absolute;
left: 0;
right: 0;
height: 1px;
bottom: -1px;
background: #ccc;
}
.toolbar {
display: flex;
align-items: baseline;
margin-right: -.3em;
}
.indicator {
margin: 0 .3em;
}
button {
font: inherit;
background: transparent;
border: 1px solid transparent;
padding: 0 .3em;
}
button:focus {
border: 1px dotted #000;
}
button:hover {
border-left: 1px solid #fff;
border-top: 1px solid #fff;
border-right: 1px solid #888;
border-bottom: 1px solid #888;
}
button:hover:active {
border-left: 1px solid #888;
border-top: 1px solid #888;
border-right: 1px solid #fff;
border-bottom: 1px solid #fff;
}
.middle {
flex: auto;
display: flex;
flex-direction: row;
overflow: hidden;
}
.list {
overflow-y: auto;
border-right: 2px solid #ccc;
min-width: 10em;
flex-shrink: 0;
}
.item {
padding: .05em .3em;
cursor: default;
}
.item.highlighted {
color: #ff5f00;
}
.item.activity {
font-weight: bold;
}
.item:hover {
background: #f8f8f8;
}
.item.current {
font-style: italic;
background: #eee;
}
/* Only Firefox currently supports align-content: safe end, thus this. */
.buffer-container {
flex: auto;
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
}
.filler {
flex: auto;
}
.buffer {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
overflow-y: auto;
}
.log {
font-family: monospace;
overflow-y: auto;
}
.log, .content, .completions {
/* Note: https://bugs.chromium.org/p/chromium/issues/detail?id=1261435 */
white-space: break-spaces;
overflow-wrap: break-word;
}
.log, .buffer .content {
padding: .1em .3em;
}
.leaked {
opacity: 50%;
}
.date {
padding: .3em;
grid-column: span 2;
font-weight: bold;
}
.unread {
grid-column: span 2;
border-top: 1px solid #ff5f00;
}
.time {
padding: .1em .3em;
background: #f8f8f8;
color: #bbb;
border-right: 1px solid #ccc;
}
.time.hidden:after {
border-top: .2em dotted #ccc;
display: block;
width: 50%;
margin: 0 auto;
content: "";
}
.mark {
padding-right: .3em;
text-align: center;
display: inline-block;
min-width: 2em;
}
.mark.error {
color: red;
}
.mark.join {
color: green;
}
.mark.part {
color: red;
}
.mark.action {
color: darkred;
}
.content .b {
font-weight: bold;
}
.content .i {
font-style: italic;
}
.content .u {
text-decoration: underline;
}
.content .s {
text-decoration: line-through;
}
.content .m {
font-family: monospace;
}
.completions {
position: absolute;
left: 0;
right: 0;
bottom: 0;
background: #fff;
padding: .05em .3em;
border-top: 1px solid #888;
max-height: 50%;
display: flex;
flex-flow: column wrap;
column-gap: .6em;
overflow-x: auto;
}
.input {
flex-shrink: 0;
border: 2px inset #eee;
overflow: hidden;
resize: vertical;
display: flex;
}
.input:focus-within {
border-color: #ff5f00;
}
.prompt {
padding: .05em .3em;
border-right: 1px solid #ccc;
background: #f8f8f8;
font-weight: bold;
}
textarea {
font: inherit;
padding: .05em .3em;
margin: 0;
border: 0;
flex-grow: 1;
resize: none;
}
textarea:focus {
outline: none;
}

1194
xP/public/xP.js Normal file

File diff suppressed because it is too large Load Diff

307
xP/xP.go Normal file
View File

@ -0,0 +1,307 @@
// Copyright (c) 2022, Přemysl Eric Janouch <p@janouch.name>
// SPDX-License-Identifier: 0BSD
package main
import (
"bufio"
"context"
"encoding/binary"
"encoding/json"
"flag"
"fmt"
"html/template"
"io"
"log"
"net"
"net/http"
"os"
"strings"
"time"
"nhooyr.io/websocket"
)
var (
debug = flag.Bool("debug", false, "enable debug output")
addressBind string
addressConnect string
addressWS string
)
// -----------------------------------------------------------------------------
func relayReadFrame(r io.Reader) []byte {
var length uint32
if err := binary.Read(r, binary.BigEndian, &length); err != nil {
log.Println("Event receive failed: " + err.Error())
return nil
}
b := make([]byte, length)
if _, err := io.ReadFull(r, b); err != nil {
log.Println("Event receive failed: " + err.Error())
return nil
}
if *debug {
log.Printf("<? %v\n", b)
var m RelayEventMessage
if after, ok := m.ConsumeFrom(b); !ok {
log.Println("Event deserialization failed")
return nil
} else if len(after) != 0 {
log.Println("Event deserialization failed: trailing data")
return nil
}
j, err := m.MarshalJSON()
if err != nil {
log.Println("Event marshalling failed: " + err.Error())
return nil
}
log.Printf("<- %s\n", j)
}
return b
}
func relayMakeReceiver(ctx context.Context, conn net.Conn) <-chan []byte {
// The usual event message rarely gets above 1 kilobyte,
// thus this is set to buffer up at most 1 megabyte or so.
p := make(chan []byte, 1000)
r := bufio.NewReaderSize(conn, 65536)
go func() {
defer close(p)
for {
b := relayReadFrame(r)
if b == nil {
return
}
select {
case p <- b:
case <-ctx.Done():
return
}
}
}()
return p
}
func relayWriteJSON(conn net.Conn, j []byte) bool {
var m RelayCommandMessage
if err := json.Unmarshal(j, &m); err != nil {
log.Println("Command unmarshalling failed: " + err.Error())
return false
}
b, ok := m.AppendTo(make([]byte, 4))
if !ok {
log.Println("Command serialization failed")
return false
}
binary.BigEndian.PutUint32(b[:4], uint32(len(b)-4))
if _, err := conn.Write(b); err != nil {
log.Println("Command send failed: " + err.Error())
return false
}
if *debug {
log.Printf("-> %v\n", b)
}
return true
}
// -----------------------------------------------------------------------------
func clientReadJSON(ctx context.Context, ws *websocket.Conn) []byte {
t, j, err := ws.Read(ctx)
if err != nil {
log.Println("Command receive failed: " + err.Error())
return nil
}
if t != websocket.MessageText {
log.Println(
"Command receive failed: " + "binary messages are not supported")
return nil
}
if *debug {
log.Printf("?> %s\n", j)
}
return j
}
func clientWriteBinary(ctx context.Context, ws *websocket.Conn, b []byte) bool {
if err := ws.Write(ctx, websocket.MessageBinary, b); err != nil {
log.Println("Event send failed: " + err.Error())
return false
}
return true
}
func clientWriteError(ctx context.Context, ws *websocket.Conn, err error) bool {
b, ok := (&RelayEventMessage{
EventSeq: 0,
Data: RelayEventData{
Variant: &RelayEventDataError{
CommandSeq: 0,
Error: err.Error(),
},
},
}).AppendTo(nil)
if ok {
log.Println("Event serialization failed")
return false
}
return clientWriteBinary(ctx, ws, b)
}
func handleWS(w http.ResponseWriter, r *http.Request) {
opts := &websocket.AcceptOptions{
InsecureSkipVerify: true,
CompressionMode: websocket.CompressionContextTakeover,
// This is for the payload; set higher to avoid overhead.
CompressionThreshold: 64 << 10,
}
// AppleWebKit can be broken with compression.
if agent := r.UserAgent(); strings.Contains(agent, " Version/") &&
(strings.HasPrefix(agent, "Mozilla/5.0 (Macintosh; ") ||
strings.HasPrefix(agent, "Mozilla/5.0 (iPhone; ")) {
opts.CompressionMode = websocket.CompressionDisabled
}
ws, err := websocket.Accept(w, r, opts)
if err != nil {
log.Println("Client rejected: " + err.Error())
return
}
defer ws.Close(websocket.StatusGoingAway, "Goodbye")
ctx, cancel := context.WithCancel(r.Context())
defer cancel()
conn, err := net.Dial("tcp", addressConnect)
if err != nil {
log.Println("Connection failed: " + err.Error())
clientWriteError(ctx, ws, err)
return
}
defer conn.Close()
// To decrease latencies, events are received and decoded in parallel
// to their sending, and we try to batch them together.
relayFrames := relayMakeReceiver(ctx, conn)
batchFrames := func() []byte {
batch, ok := <-relayFrames
if !ok {
return nil
}
Batch:
for {
select {
case b, ok := <-relayFrames:
if !ok {
break Batch
}
batch = append(batch, b...)
default:
break Batch
}
}
return batch
}
// We don't need to intervene, so it's just two separate pipes so far.
go func() {
defer cancel()
for {
j := clientReadJSON(ctx, ws)
if j == nil {
return
}
relayWriteJSON(conn, j)
}
}()
go func() {
defer cancel()
for {
b := batchFrames()
if b == nil {
return
}
clientWriteBinary(ctx, ws, b)
}
}()
<-ctx.Done()
}
// -----------------------------------------------------------------------------
var staticHandler = http.FileServer(http.Dir("."))
var page = template.Must(template.New("/").Parse(`<!DOCTYPE html>
<html>
<head>
<title>xP</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="xP.css" />
</head>
<body>
<script src="mithril.js">
</script>
<script>
let proxy = '{{ . }}'
</script>
<script type="module" src="xP.js">
</script>
</body>
</html>`))
func handleDefault(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
staticHandler.ServeHTTP(w, r)
return
}
wsURI := addressWS
if wsURI == "" {
wsURI = fmt.Sprintf("ws://%s/ws", r.Host)
}
if err := page.Execute(w, wsURI); err != nil {
log.Println("Template execution failed: " + err.Error())
}
}
func main() {
flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(),
"Usage: %s [OPTION...] BIND CONNECT [WSURI]\n\n", os.Args[0])
flag.PrintDefaults()
}
flag.Parse()
if flag.NArg() < 2 || flag.NArg() > 3 {
flag.Usage()
os.Exit(1)
}
addressBind, addressConnect = flag.Arg(0), flag.Arg(1)
if flag.NArg() > 2 {
addressWS = flag.Arg(2)
}
http.Handle("/ws", http.HandlerFunc(handleWS))
http.Handle("/", http.HandlerFunc(handleDefault))
s := &http.Server{
Addr: addressBind,
ReadTimeout: 60 * time.Second,
WriteTimeout: 60 * time.Second,
MaxHeaderBytes: 32 << 10,
}
log.Fatalln(s.ListenAndServe())
}

3
xS/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/xS
/xS-replies.go
/xS.1

18
xS/Makefile Normal file
View File

@ -0,0 +1,18 @@
.POSIX:
.SUFFIXES:
AWK = env LC_ALL=C awk
outputs = xS xS-replies.go xS.1
all: $(outputs)
xS: xS.go ../xK-version xS-replies.go
go build -ldflags "-X 'main.projectVersion=$$(cat ../xK-version)'" -o $@
xS-replies.go: xS-gen-replies.awk xS-replies
$(AWK) -f xS-gen-replies.awk xS-replies > $@
xS.1: ../xK-version ../liberty/tools/asciiman.awk xS.adoc
env "asciidoc-release-version=$$(cat ../xK-version)" \
$(AWK) -f ../liberty/tools/asciiman.awk xS.adoc > $@
test: all
go test
clean:
rm -f $(outputs)

3
xS/go.mod Normal file
View File

@ -0,0 +1,3 @@
module janouch.name/xK/xS
go 1.19

132
xS/irc.go Normal file
View File

@ -0,0 +1,132 @@
package main
import (
"path/filepath"
"regexp"
"strings"
)
func ircToLower(c byte) byte {
switch c {
case '[':
return '{'
case ']':
return '}'
case '\\':
return '|'
case '~':
return '^'
}
if c >= 'A' && c <= 'Z' {
return c + ('a' - 'A')
}
return c
}
func ircToUpper(c byte) byte {
switch c {
case '{':
return '['
case '}':
return ']'
case '|':
return '\\'
case '^':
return '~'
}
if c >= 'a' && c <= 'z' {
return c - ('a' - 'A')
}
return c
}
// Convert identifier to a canonical form for case-insensitive comparisons.
// ircToUpper is used so that statically initialized maps can be in uppercase.
func ircToCanon(ident string) string {
var canon []byte
for _, c := range []byte(ident) {
canon = append(canon, ircToUpper(c))
}
return string(canon)
}
func ircEqual(s1, s2 string) bool {
return ircToCanon(s1) == ircToCanon(s2)
}
func ircFnmatch(pattern string, s string) bool {
pattern, s = ircToCanon(pattern), ircToCanon(s)
// FIXME: This should not support [] ranges and handle '/' specially.
// We could translate the pattern to a regular expression.
matched, _ := filepath.Match(pattern, s)
return matched
}
var reMsg = regexp.MustCompile(
`^(?:@([^ ]*) +)?(?::([^! ]*)(?:!([^@]*)@([^ ]*))? +)?([^ ]+)(.*)?$`)
var reArgs = regexp.MustCompile(`:.*| [^: ][^ ]*`)
type message struct {
tags map[string]string // IRC 3.2 message tags
nick string // optional nickname
user string // optional username
host string // optional hostname or IP address
command string // command name
params []string // arguments
}
func ircUnescapeMessageTag(value string) string {
var buf []byte
escape := false
for i := 0; i < len(value); i++ {
if escape {
switch value[i] {
case ':':
buf = append(buf, ';')
case 's':
buf = append(buf, ' ')
case 'r':
buf = append(buf, '\r')
case 'n':
buf = append(buf, '\n')
default:
buf = append(buf, value[i])
}
escape = false
} else if value[i] == '\\' {
escape = true
} else {
buf = append(buf, value[i])
}
}
return string(buf)
}
func ircParseMessageTags(tags string, out map[string]string) {
for _, tag := range strings.Split(tags, ";") {
if tag == "" {
// Ignore empty.
} else if equal := strings.IndexByte(tag, '='); equal < 0 {
out[tag] = ""
} else {
out[tag[:equal]] = ircUnescapeMessageTag(tag[equal+1:])
}
}
}
func ircParseMessage(line string) *message {
m := reMsg.FindStringSubmatch(line)
if m == nil {
return nil
}
msg := message{nil, m[2], m[3], m[4], m[5], nil}
if m[1] != "" {
msg.tags = make(map[string]string)
ircParseMessageTags(m[1], msg.tags)
}
for _, x := range reArgs.FindAllString(m[6], -1) {
msg.params = append(msg.params, x[1:])
}
return &msg
}

20
xS/xS-gen-replies.awk Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/awk -f
/^[0-9]+ *(ERR|RPL)_[A-Z]+ *".*"$/ {
match($0, /".*"/)
ids[$1] = $2
texts[$2] = substr($0, RSTART, RLENGTH)
}
END {
print "package main"
print ""
print "const ("
for (i in ids)
printf("\t%s = %s\n", ids[i], i)
print ")"
print ""
print "var defaultReplies = map[int]string{"
for (i in ids)
print "\t" ids[i] ": " texts[ids[i]] ","
print "}"
}

87
xS/xS-replies Normal file
View File

@ -0,0 +1,87 @@
1 RPL_WELCOME ":Welcome to the Internet Relay Network %s!%s@%s"
2 RPL_YOURHOST ":Your host is %s, running version %s"
3 RPL_CREATED ":This server was created %s"
4 RPL_MYINFO "%s %s %s %s"
5 RPL_ISUPPORT "%s :are supported by this server"
211 RPL_STATSLINKINFO "%s %d %d %d %d %d %d"
212 RPL_STATSCOMMANDS "%s %d %d %d"
219 RPL_ENDOFSTATS "%c :End of STATS report"
221 RPL_UMODEIS "+%s"
242 RPL_STATSUPTIME ":Server Up %d days %d:%02d:%02d"
251 RPL_LUSERCLIENT ":There are %d users and %d services on %d servers"
252 RPL_LUSEROP "%d :operator(s) online"
253 RPL_LUSERUNKNOWN "%d :unknown connection(s)"
254 RPL_LUSERCHANNELS "%d :channels formed"
255 RPL_LUSERME ":I have %d clients and %d servers"
301 RPL_AWAY "%s :%s"
302 RPL_USERHOST ":%s"
303 RPL_ISON ":%s"
305 RPL_UNAWAY ":You are no longer marked as being away"
306 RPL_NOWAWAY ":You have been marked as being away"
311 RPL_WHOISUSER "%s %s %s * :%s"
312 RPL_WHOISSERVER "%s %s :%s"
313 RPL_WHOISOPERATOR "%s :is an IRC operator"
314 RPL_WHOWASUSER "%s %s %s * :%s"
315 RPL_ENDOFWHO "%s :End of WHO list"
317 RPL_WHOISIDLE "%s %d :seconds idle"
318 RPL_ENDOFWHOIS "%s :End of WHOIS list"
319 RPL_WHOISCHANNELS "%s :%s"
322 RPL_LIST "%s %d :%s"
323 RPL_LISTEND ":End of LIST"
324 RPL_CHANNELMODEIS "%s +%s"
329 RPL_CREATIONTIME "%s %d"
331 RPL_NOTOPIC "%s :No topic is set"
332 RPL_TOPIC "%s :%s"
333 RPL_TOPICWHOTIME "%s %s %d"
341 RPL_INVITING "%s %s"
346 RPL_INVITELIST "%s %s"
347 RPL_ENDOFINVITELIST "%s :End of channel invite list"
348 RPL_EXCEPTLIST "%s %s"
349 RPL_ENDOFEXCEPTLIST "%s :End of channel exception list"
351 RPL_VERSION "%s.%d %s :%s"
352 RPL_WHOREPLY "%s %s %s %s %s %s :%d %s"
353 RPL_NAMREPLY "%c %s :%s"
364 RPL_LINKS "%s %s :%d %s"
365 RPL_ENDOFLINKS "%s :End of LINKS list"
366 RPL_ENDOFNAMES "%s :End of NAMES list"
367 RPL_BANLIST "%s %s"
368 RPL_ENDOFBANLIST "%s :End of channel ban list"
369 RPL_ENDOFWHOWAS "%s :End of WHOWAS"
372 RPL_MOTD ":- %s"
375 RPL_MOTDSTART ":- %s Message of the day - "
376 RPL_ENDOFMOTD ":End of MOTD command"
391 RPL_TIME "%s :%s"
401 ERR_NOSUCHNICK "%s :No such nick/channel"
402 ERR_NOSUCHSERVER "%s :No such server"
403 ERR_NOSUCHCHANNEL "%s :No such channel"
404 ERR_CANNOTSENDTOCHAN "%s :Cannot send to channel"
406 ERR_WASNOSUCHNICK "%s :There was no such nickname"
409 ERR_NOORIGIN ":No origin specified"
410 ERR_INVALIDCAPCMD "%s :%s"
411 ERR_NORECIPIENT ":No recipient given (%s)"
412 ERR_NOTEXTTOSEND ":No text to send"
421 ERR_UNKNOWNCOMMAND "%s: Unknown command"
422 ERR_NOMOTD ":MOTD File is missing"
423 ERR_NOADMININFO "%s :No administrative info available"
431 ERR_NONICKNAMEGIVEN ":No nickname given"
432 ERR_ERRONEOUSNICKNAME "%s :Erroneous nickname"
433 ERR_NICKNAMEINUSE "%s :Nickname is already in use"
441 ERR_USERNOTINCHANNEL "%s %s :They aren't on that channel"
442 ERR_NOTONCHANNEL "%s :You're not on that channel"
443 ERR_USERONCHANNEL "%s %s :is already on channel"
445 ERR_SUMMONDISABLED ":SUMMON has been disabled"
446 ERR_USERSDISABLED ":USERS has been disabled"
451 ERR_NOTREGISTERED ":You have not registered"
461 ERR_NEEDMOREPARAMS "%s :Not enough parameters"
462 ERR_ALREADYREGISTERED ":Unauthorized command (already registered)"
467 ERR_KEYSET "%s :Channel key already set"
471 ERR_CHANNELISFULL "%s :Cannot join channel (+l)"
472 ERR_UNKNOWNMODE "%c :is unknown mode char to me for %s"
473 ERR_INVITEONLYCHAN "%s :Cannot join channel (+i)"
474 ERR_BANNEDFROMCHAN "%s :Cannot join channel (+b)"
475 ERR_BADCHANNELKEY "%s :Cannot join channel (+k)"
476 ERR_BADCHANMASK "%s :Bad Channel Mask"
481 ERR_NOPRIVILEGES ":Permission Denied- You're not an IRC operator"
482 ERR_CHANOPRIVSNEEDED "%s :You're not channel operator"
501 ERR_UMODEUNKNOWNFLAG ":Unknown MODE flag"
502 ERR_USERSDONTMATCH ":Cannot change mode for other users"

57
xS/xS.adoc Normal file
View File

@ -0,0 +1,57 @@
xS(1)
=====
:doctype: manpage
:manmanual: xK Manual
:mansource: xK {release-version}
Name
----
xS - IRC daemon
Synopsis
--------
*xS* [_OPTION_]...
Description
-----------
*xS* is a basic IRC daemon for single-server networks, suitable for testing
and private use. When run without a configuration file, it will start listening
on the standard port 6667 and the "any" address.
Options
-------
*-debug*::
Do not daemonize, print more information on the standard error stream
to help debug various issues.
*-systemd*::
Log using the format specified in *sd-daemon*(3).
*-h*, *-help*::
Display a help message and exit.
*-version*::
Output version information and exit.
*-writedefaultcfg*::
Write a configuration file with defaults, show its path and exit.
+
The file will be appropriately commented.
Files
-----
*xS* follows the XDG Base Directory Specification.
_~/.config/xS/xS.conf_::
_/etc/xdg/xS/xS.conf_::
The daemon's configuration file. Use the *-writedefaultcfg* option
to create a new one for editing.
Reporting bugs
--------------
Use https://git.janouch.name/p/xK to report bugs, request features,
or submit pull requests.
See also
--------
*sd-daemon*(3)

3399
xS/xS.go Normal file

File diff suppressed because it is too large Load Diff

168
xS/xS_test.go Normal file
View File

@ -0,0 +1,168 @@
//
// Copyright (c) 2015 - 2018, Přemysl Eric Janouch <p@janouch.name>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
package main
import (
"crypto/tls"
"net"
"os"
"reflect"
"syscall"
"testing"
)
func TestSplitString(t *testing.T) {
var splitStringTests = []struct {
s, delims string
ignoreEmpty bool
result []string
}{
{",a,,bc", ",", false, []string{"", "a", "", "bc"}},
{",a,,bc", ",", true, []string{"a", "bc"}},
{"a,;bc,", ",;", false, []string{"a", "", "bc", ""}},
{"a,;bc,", ",;", true, []string{"a", "bc"}},
{"", ",", false, []string{""}},
{"", ",", true, nil},
}
for i, d := range splitStringTests {
got := splitString(d.s, d.delims, d.ignoreEmpty)
if !reflect.DeepEqual(got, d.result) {
t.Errorf("case %d: %v should be %v\n", i, got, d.result)
}
}
}
func socketpair() (*os.File, *os.File, error) {
pair, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
if err != nil {
return nil, nil, err
}
// See go #24331, this makes 1.11 use the internal poller
// while there wasn't a way to achieve that before.
if err := syscall.SetNonblock(int(pair[0]), true); err != nil {
return nil, nil, err
}
if err := syscall.SetNonblock(int(pair[1]), true); err != nil {
return nil, nil, err
}
fa := os.NewFile(uintptr(pair[0]), "a")
if fa == nil {
return nil, nil, os.ErrInvalid
}
fb := os.NewFile(uintptr(pair[1]), "b")
if fb == nil {
fa.Close()
return nil, nil, os.ErrInvalid
}
return fa, fb, nil
}
func TestDetectTLS(t *testing.T) {
detectTLSFromFunc := func(t *testing.T, writer func(net.Conn)) bool {
// net.Pipe doesn't use file descriptors, we need a socketpair.
sockA, sockB, err := socketpair()
if err != nil {
t.Fatal(err)
}
defer sockA.Close()
defer sockB.Close()
fcB, err := net.FileConn(sockB)
if err != nil {
t.Fatal(err)
}
go writer(fcB)
fcA, err := net.FileConn(sockA)
if err != nil {
t.Fatal(err)
}
sc, err := fcA.(syscall.Conn).SyscallConn()
if err != nil {
t.Fatal(err)
}
return detectTLS(sc)
}
t.Run("SSL_2.0", func(t *testing.T) {
if !detectTLSFromFunc(t, func(fc net.Conn) {
// The obsolete, useless, unsupported SSL 2.0 record format.
_, _ = fc.Write([]byte{0x80, 0x01, 0x01})
}) {
t.Error("could not detect SSL")
}
})
t.Run("crypto_tls", func(t *testing.T) {
if !detectTLSFromFunc(t, func(fc net.Conn) {
conn := tls.Client(fc, &tls.Config{InsecureSkipVerify: true})
_ = conn.Handshake()
}) {
t.Error("could not detect TLS")
}
})
t.Run("text", func(t *testing.T) {
if detectTLSFromFunc(t, func(fc net.Conn) {
_, _ = fc.Write([]byte("ПРЕВЕД"))
}) {
t.Error("detected UTF-8 as TLS")
}
})
t.Run("EOF", func(t *testing.T) {
type connCloseWriter interface {
net.Conn
CloseWrite() error
}
if detectTLSFromFunc(t, func(fc net.Conn) {
_ = fc.(connCloseWriter).CloseWrite()
}) {
t.Error("detected EOF as TLS")
}
})
}
func TestIRC(t *testing.T) {
msg := ircParseMessage(
`@first=a\:\s\r\n\\;2nd :srv hi there :good m8 :how are you?`)
if !reflect.DeepEqual(msg.tags, map[string]string{
"first": "a; \r\n\\",
"2nd": "",
}) {
t.Error("tags parsed incorrectly")
}
if msg.nick != "srv" || msg.user != "" || msg.host != "" {
t.Error("server name parsed incorrectly")
}
if msg.command != "hi" {
t.Error("command name parsed incorrectly")
}
if !reflect.DeepEqual(msg.params,
[]string{"there", "good m8 :how are you?"}) {
t.Error("params parsed incorrectly")
}
if !ircEqual("[fag]^", "{FAG}~") {
t.Error("string case comparison not according to RFC 2812")
}
// TODO: More tests.
}

11
xW/.clang-format Normal file
View File

@ -0,0 +1,11 @@
BasedOnStyle: LLVM
ColumnLimit: 80
IndentWidth: 4
TabWidth: 4
UseTab: ForContinuationAndIndentation
AlwaysBreakAfterReturnType: AllDefinitions
BreakBeforeBraces: Linux
SpaceAfterCStyleCast: true
AlignAfterOpenBracket: DontAlign
AlignOperands: DontAlign
SpacesBeforeTrailingComments: 2

85
xW/CMakeLists.txt Normal file
View File

@ -0,0 +1,85 @@
# The last version with Windows XP support is 3.13, we want to keep that
cmake_minimum_required (VERSION 3.10)
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--the main executables only build
# on Windows within Cygwin, and this Windows executable only builds on Linux
# cross-compiled, so you'd want to build them independently anyway.
project (xW VERSION "${project_version}"
DESCRIPTION "Win32 frontend for xC" LANGUAGES CXX)
set (CMAKE_CXX_STANDARD 17)
add_definitions (-DUNICODE -D_UNICODE)
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>")
add_link_options ("$<$<CXX_COMPILER_ID:GNU>:-static;-municode>")
add_link_options ("$<$<CXX_COMPILER_ID:Clang>:-static;-municode>")
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
if (NOT ${CMAKE_VERSION} VERSION_LESS 3.18.0)
set (find_program_REQUIRE REQUIRED)
endif ()
find_program (sox_EXECUTABLE sox ${find_program_REQUIRE})
add_custom_command (OUTPUT beep.wav
COMMAND ${sox_EXECUTABLE} -b 16 -Dr 44100 -n beep.wav
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)
# Rasterize SVG icons
set (root "${PROJECT_SOURCE_DIR}/..")
set (CMAKE_MODULE_PATH ${root}/liberty/cmake)
include (IconUtils)
set (icon_ico_list)
foreach (icon xW xW-highlighted)
set (icon_png_list)
foreach (icon_size 16 32 48)
icon_to_png (${icon} ${PROJECT_SOURCE_DIR}/${icon}.svg
${icon_size} ${PROJECT_BINARY_DIR}/icons icon_png)
list (APPEND icon_png_list ${icon_png})
endforeach ()
icon_to_png (${icon} ${PROJECT_SOURCE_DIR}/${icon}.svg
256 ${PROJECT_BINARY_DIR}/icons icon_png)
set (icon_ico ${PROJECT_BINARY_DIR}/${icon}.ico)
icon_for_win32 (${icon_ico} "${icon_png_list}" "${icon_png}")
list (APPEND icon_ico_list ${icon_ico})
endforeach ()
set_property (SOURCE xW.rc
APPEND PROPERTY OBJECT_DEPENDS ${icon_ico_list} beep.wav)
# 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)
add_executable (xW WIN32 xW.cpp xW.rc xW.manifest ${project_config}
${root}/liberty/tools/lxdrgen-cpp-win32.cpp)
target_link_libraries (xW comctl32 ws2_32 winmm)
add_dependencies (xW xC-proto)
# At least with MinGW, this is a fully independent portable executable
install (TARGETS xW DESTINATION .)
set (CPACK_GENERATOR ZIP)
include (CPack)

14
xW/config.h.in Normal file
View File

@ -0,0 +1,14 @@
#ifndef CONFIG_H
#define CONFIG_H
#define PROJECT_NAME "${PROJECT_NAME}"
#define PROJECT_VERSION "${project_version}"
#define PROJECT_DESCRIPTION "${PROJECT_DESCRIPTION}"
#define PROJECT_AUTHOR "Přemysl Eric Janouch"
#define PROJECT_MAJOR (${PROJECT_VERSION_MAJOR}-0)
#define PROJECT_MINOR (${PROJECT_VERSION_MINOR}-0)
#define PROJECT_PATCH (${PROJECT_VERSION_PATCH}-0)
#define PROJECT_TWEAK (${PROJECT_VERSION_TWEAK}-0)
#endif // ! CONFIG_H

24
xW/xW-highlighted.svg Normal file
View File

@ -0,0 +1,24 @@
<?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>
<clipPath id="outer">
<rect x="-1" y="-0.15" width="5" height="3.30" />
</clipPath>
<clipPath id="inner">
<rect x="-1" y="0" width="5" height="3" />
</clipPath>
</defs>
<g transform="translate(6, 6) scale(12)" stroke-linecap="square">
<g clip-path="url(#outer)">
<path stroke="#ffffff" stroke-width="1.5" d="M 0.5,0 2.5,3" />
<path stroke="#ffffff" stroke-width="1.5" d="M 0.5,3 2.5,0" />
</g>
<g clip-path="url(#inner)">
<path stroke="#ff0000" stroke-width="0.9" d="M 0.5,0 2.5,3" />
<path stroke="#ff0000" stroke-width="0.9" d="M 0.5,3 2.5,0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 806 B

18
xW/xW-resources.h Normal file
View File

@ -0,0 +1,18 @@
#define IDI_ICON 1
#define IDI_HIGHLIGHTED 2
#define IDR_BEEP 3
#define IDA_ACCELERATORS 10
// Named after input_add_functions() in xC.
#define ID_PREVIOUS_BUFFER 11
#define ID_NEXT_BUFFER 12
#define ID_SWITCH_BUFFER 13
#define ID_GOTO_HIGHLIGHT 14
#define ID_GOTO_ACTIVITY 15
#define ID_TOGGLE_UNIMPORTANT 16
#define ID_DISPLAY_FULL_LOG 17
#define IDD_CONNECT 20
#define IDC_STATIC 21
#define IDC_HOST 22
#define IDC_PORT 23

1998
xW/xW.cpp Normal file

File diff suppressed because it is too large Load Diff

25
xW/xW.manifest Normal file
View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity name="xW" version="1.0.0.0" type="win32" />
<dependency>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Windows.Common-Controls"
version="6.0.0.0" type="win32" processorArchitecture="*"
publicKeyToken="6595b64144ccf1df" language="*" />
</dependentAssembly>
</dependency>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
<!-- Windows Vista -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
</application>
</compatibility>
</assembly>

80
xW/xW.rc Normal file
View File

@ -0,0 +1,80 @@
#include <windows.h>
#include "xW-resources.h"
// https://devblogs.microsoft.com/oldnewthing/20190607-00/?p=102569
// For UTF-8 literals to work in both MinGW and Microsoft resource compilers,
// the pragma needs to be in this file, and before they're included.
#pragma code_page(65001)
#include "config.h"
// Beware of this madness https://gitlab.kitware.com/cmake/cmake/-/issues/23066
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "xW.manifest"
IDI_ICON ICON "xW.ico"
IDI_HIGHLIGHTED ICON "xW-highlighted.ico"
IDR_BEEP WAVE "beep.wav"
IDA_ACCELERATORS ACCELERATORS
BEGIN
"^p", ID_PREVIOUS_BUFFER
"^n", ID_NEXT_BUFFER
VK_F5, ID_PREVIOUS_BUFFER, VIRTKEY
VK_F6, ID_NEXT_BUFFER, VIRTKEY
VK_PRIOR, ID_PREVIOUS_BUFFER, CONTROL, VIRTKEY
VK_NEXT, ID_NEXT_BUFFER, CONTROL, VIRTKEY
VK_TAB, ID_SWITCH_BUFFER, CONTROL, VIRTKEY
// These are proper, but llvm-rc won't accept them (GitHub #64002).
#ifndef __clang__
"!", ID_GOTO_HIGHLIGHT, ALT
"a", ID_GOTO_ACTIVITY, ALT
"H", ID_TOGGLE_UNIMPORTANT, ALT
"h", ID_DISPLAY_FULL_LOG, ALT
#endif
END
// https://devblogs.microsoft.com/oldnewthing/20050204-00/?p=36523
// https://devblogs.microsoft.com/oldnewthing/20050207-00/?p=36513
//
// Note that this is still not the right font to use in newest Windows,
// that would be 9pt Segoe UI, as described in:
// https://learn.microsoft.com/en-us/windows/win32/uxguide/vis-fonts
// or even better yet, NONCLIENTMETRICS::lfMessageFont.
IDD_CONNECT DIALOGEX 0, 0, 150, 64
STYLE DS_SHELLFONT | DS_MODALFRAME | DS_CENTER \
| WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Connect to Relay"
FONT 8, "MS Shell Dlg", 400 /*FW_NORMAL*/, 0 /*FALSE*/, 0x1 /*DEFAULT_CHARSET*/
BEGIN
LTEXT "&Host:", IDC_STATIC, 7, 10, 18, 8
EDITTEXT IDC_HOST, 39, 7, 104, 14, ES_AUTOHSCROLL
LTEXT "&Port:", IDC_STATIC, 7, 28, 18, 8
EDITTEXT IDC_PORT, 39, 25, 104, 14, ES_AUTOHSCROLL
DEFPUSHBUTTON "&Connect", IDOK, 39, 43, 50, 14
PUSHBUTTON "E&xit", IDCANCEL, 93, 43, 50, 14
END
VS_VERSION_INFO VERSIONINFO
FILEVERSION PROJECT_MAJOR, PROJECT_MINOR, PROJECT_PATCH, PROJECT_TWEAK
PRODUCTVERSION PROJECT_MAJOR, PROJECT_MINOR, PROJECT_PATCH, PROJECT_TWEAK
FILETYPE VFT_APP
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904B0"
BEGIN
VALUE "CompanyName", PROJECT_AUTHOR
VALUE "FileDescription", PROJECT_DESCRIPTION
VALUE "FileVersion", PROJECT_VERSION
VALUE "InternalName", PROJECT_NAME
VALUE "LegalCopyright", PROJECT_AUTHOR
VALUE "OriginalFilename", PROJECT_NAME ".exe"
VALUE "ProductName", PROJECT_NAME
VALUE "ProductVersion", PROJECT_VERSION
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END

24
xW/xW.svg Normal file
View File

@ -0,0 +1,24 @@
<?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>
<clipPath id="outer">
<rect x="-1" y="-0.15" width="5" height="3.30" />
</clipPath>
<clipPath id="inner">
<rect x="-1" y="0" width="5" height="3" />
</clipPath>
</defs>
<g transform="translate(6, 6) scale(12)" stroke-linecap="square">
<g clip-path="url(#outer)">
<path stroke="#ffffff" stroke-width="1.5" d="M 0.5,0 2.5,3" />
<path stroke="#ffffff" stroke-width="1.5" d="M 0.5,3 2.5,0" />
</g>
<g clip-path="url(#inner)">
<path stroke="#000000" stroke-width="0.2" d="M 0,0 2,3 M 1,0 3,3" />
<path stroke="#ff6600" stroke-width="0.3" d="M 0,3 2,0 M 1,3 3,0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 818 B