A native GNUstep desktop client for Quassel, built on the Objective-C protocol engine from woboq's iQuassel iOS client.
The protocol layer is iQuassel's, essentially unmodified — it already spoke
Quassel's Qt QDataStream/QVariant wire format in pure Objective-C, and that
turned out to be byte-identical on GNUstep. The iOS interface is gone, replaced
by a native AppKit one: NSOutlineView for the buffer list, a word-wrapping
NSTextView for the chat log, NSTableView for the channel member list, one
desktop window instead of a navigation stack.
Status: early but working. It connects to a real Quassel core over TLS, authenticates, lists your networks and buffers, renders the chat log and shows channel members. See Known issues.
A GNUstep system with AppKit — developed against a
Gershwin layout (/System/Library/...).
| Component | Version tested |
|---|---|
| clang | 19.1.7 |
| libobjc2 (GNUstep runtime v2) | 2.2, ABI gnustep-2.2 |
| gnustep-base | 1.31.1 |
| gnustep-gui | 0.32.0 |
| GnuTLS | 3.8.9 |
| zlib | 1.3.1 |
ARC, blocks, Objective-C literals and subscripting are all used, so the GNUstep v2 runtime is required — the older GCC runtime will not work.
git clone https://github.com/pkgdemon/iquassel
cd iquassel
git checkout gnustep-native
./gnustep/build-app.sh # -> gnustep/build/Quassel.appThere is also a headless build, useful for testing the protocol stack without a GUI:
./gnustep/build-cli.sh # -> gnustep/build/quasselcli
./gnustep/build/quasselcli <host> <port> <user> [password]quasselcli connects, authenticates and prints the buffer list. It is the
fastest way to tell whether a problem is in the network layer or the interface.
sudo cp -R gnustep/build/Quassel.app /Local/Applications/
sudo make_services # register with WorkspaceThen launch it:
openapp Quassel
# or directly:
/Local/Applications/Quassel.app/QuasselSettings live in ~/Library/Preferences and can be pre-seeded:
defaults write Quassel hostName core.example.org
defaults write Quassel port 4242
defaults write Quassel userName yournamegnustep/mockcore.py is a minimal Quassel core speaking the legacy protocol —
enough of the handshake to drive the client from ClientInit through to a
populated buffer list. It serves two fake networks and needs no configuration.
Every buffer gets a short backlog that includes some deliberately over-long
lines (handy for checking the chat log wraps them), and anything you type is
echoed back as your own message.
python3 gnustep/mockcore.py 4242 &
./gnustep/build/quasselcli 127.0.0.1 4242 tester secretgnustep/probecore.py asks a real core which handshakes it supports, which is
the first thing to run when a connection misbehaves:
python3 gnustep/probecore.py your-core-host 4242 NSOutlineView NSTextView NSTextField
(buffer list) (chat log) (input)
\ | /
+---- MainWindowController (NSWindowController) ----+
|
QuasselCoreConnectionDelegate <- the seam
|
QuasselCoreConnection <- upstream iQuassel
QVariant / Message / BufferInfo
|
QuasselSocket <- POSIX fd + GSTLSSession
|
GNUstep Foundation / AppKit
| Path | What it holds |
|---|---|
quassel-for-ios/quassel-for-ios/ |
Upstream iQuassel. The protocol engine is used as-is; only five files changed, all to decouple it from the UI |
gnustep/Core/ |
QuasselSocket (the transport) and quasselcli |
gnustep/Shims/ |
GCDAsyncSocket.h — a @compatibility_alias so the 1,643-line engine needs no socket edits |
gnustep/UI/ |
The AppKit interface |
Five things about GNUstep cost real debugging time and are worth knowing:
NSStream cannot do STARTTLS. gnustep-base installs its TLS handler inside
-open (GSSocketStream.m:2064, +[GSTLSHandler tryInput:output:], called
immediately before connect()), reading NSStreamSocketSecurityLevelKey at
that instant. Setting the property afterwards is silently ignored. Quassel
upgrades partway through the handshake — ClientInit goes out in the clear
and TLS starts only once ClientInitAck reports SupportSsl — so NSStream
cannot express this protocol at all. QuasselSocket therefore owns a
non-blocking POSIX fd and layers gnustep-base's own GSTLSSession on top via
push/pull callbacks. libs-corebase is no help: it contains no TLS code, and
CFStreamCreatePairWithSocketToHost is an empty function body.
CFBoolean does not bridge. (id)kCFBooleanFalse responds to -boolValue
on Apple platforms; on GNUstep it raises
NSCFType does not recognize boolValue. iQuassel passes exactly that as a TLS
option, so it must be read defensively.
NSSplitView will not give a third pane any width. With three subviews,
gnustep-gui hands the third one zero width regardless of its frame or what the
delegate's sizing methods answer. The member list is therefore laid out with
explicit frames inside a container beside the chat log, rather than as a third
split pane.
NSTableView rows are all the same height. gnustep-gui's NSTableView
never calls -tableView:heightOfRow: (_usesVariableRowHeights is hard-wired
to NO, and -noteHeightOfRowsWithIndexesChanged: is a FIXME), and
NSCell draws its text as one vertically centred line. A long message in a
table row is simply cut off. The chat log is an NSTextView for that reason.
GSInetOutputStream starves its sibling. It signals
NSStreamEventHasSpaceAvailable continuously while the socket is writable,
monopolising the run loop so HasBytesAvailable never arrives. Only relevant if
you go back to NSStream, but it is a nasty one to diagnose.
- Still young; expect rough edges outside the paths described above.
- Core credentials are stored in the clear in
~/Library/Preferences. GNUstep has no Security.framework, so there is no keychain to put them in.libsecretwould be the sensible fix. - No certificate prompt — the client accepts whatever the core presents. This
is inherited upstream behaviour (iQuassel sets
kCFStreamSSLValidatesCertificateChain: NO) and is what makes self-signed cores work. - Context menus and backlog-on-scroll are not implemented yet.
- The client speaks Quassel's legacy
ProtocolVersion 10handshake. Cores still accept it, but the modern probing protocol is not implemented.
Built on iQuassel by woboq, the native Objective-C Quassel client for iOS. All of the protocol work is theirs.
Quassel IRC is by the Quassel Project.
Dual licensed — GPLv3 and Woboq GmbH's private license. See the LICENSE file.