docs: GLib bindings plan
This commit is contained in:
263
docs/plans/GLIB_BINDINGS.md
Normal file
263
docs/plans/GLIB_BINDINGS.md
Normal file
@@ -0,0 +1,263 @@
|
||||
# GLib Bindings Plan
|
||||
|
||||
## Status
|
||||
|
||||
Proposed. Not started.
|
||||
|
||||
## Context
|
||||
|
||||
Today the GTK app talks to `kordophoned` directly over D-Bus in
|
||||
[`gtk/src/service/repository.vala`](/home/buzzert/src/Kordophone/gtk/src/service/repository.vala)
|
||||
and the generated interface in
|
||||
[`gtk/src/service/interface/dbusservice.vala`](/home/buzzert/src/Kordophone/gtk/src/service/interface/dbusservice.vala).
|
||||
|
||||
At the same time, the Rust-side daemon client logic already exists in
|
||||
[`core/kordophoned-client/src/worker.rs`](/home/buzzert/src/Kordophone/core/kordophoned-client/src/worker.rs)
|
||||
with platform backends for D-Bus and XPC. That means protocol changes currently
|
||||
have to be reflected in multiple places:
|
||||
|
||||
- `kordophoned` D-Bus/XPC server shims
|
||||
- `kordophoned-client` Rust transport layer
|
||||
- GTK/Vala D-Bus interface and proxy code
|
||||
- Swift XPC client code
|
||||
|
||||
For GTK/Vala specifically, the goal is to stop binding the application directly
|
||||
to the daemon protocol surface.
|
||||
|
||||
## Recommendation
|
||||
|
||||
Add a GTK-facing GLib/GObject wrapper on top of a small C ABI exported from the
|
||||
Rust daemon client stack.
|
||||
|
||||
Do not expose the current `kordophoned-client` Rust API directly as raw C.
|
||||
The current surface uses Rust enums, `Vec<String>`, `Option`, and a threaded
|
||||
worker model, which is fine internally but not a good stable FFI boundary.
|
||||
|
||||
The recommended layering is:
|
||||
|
||||
1. Keep `core/kordophoned-client` as the Rust-native transport/domain layer.
|
||||
2. Add a new FFI crate with a narrow, C-safe API.
|
||||
3. Add a small GLib/GObject wrapper for GTK/Vala consumption.
|
||||
4. Migrate the GTK app to that wrapper and remove its direct D-Bus binding code.
|
||||
|
||||
This keeps one transport implementation in Rust while giving Vala a natural
|
||||
GObject-style API with methods, async operations, and signals.
|
||||
|
||||
## Why Not Direct Rust GObject Export?
|
||||
|
||||
Exporting a GObject API directly from Rust is possible in principle, but the
|
||||
tooling for generating the introspection artifacts that Vala wants is still much
|
||||
less straightforward than plain C/GObject.
|
||||
|
||||
For this repo, the lower-risk path is:
|
||||
|
||||
- Rust for the daemon client implementation
|
||||
- C ABI as the stable binary boundary
|
||||
- a thin C/GObject wrapper for GI/Vala
|
||||
|
||||
That gives us standard GLib ownership rules, normal `.gir` / `.typelib` /
|
||||
`.vapi` generation, and a cleaner Meson integration story for the GTK app.
|
||||
|
||||
## Proposed Layout
|
||||
|
||||
Add a new crate:
|
||||
|
||||
- `core/kordophoned-client-c`
|
||||
|
||||
This crate should export a small `extern "C"` interface around the existing
|
||||
daemon client logic.
|
||||
|
||||
Add a new Linux-focused wrapper library:
|
||||
|
||||
- `gtk/libkordophone-client-glib` or `gtk/src/service/glib/`
|
||||
|
||||
This wrapper should be written in C and expose a GObject API that Vala can use.
|
||||
It should depend on the Rust C ABI library, not on D-Bus directly.
|
||||
|
||||
## Proposed Responsibilities
|
||||
|
||||
### `core/kordophoned-client`
|
||||
|
||||
- Own request/response/signal semantics.
|
||||
- Own platform transport handling:
|
||||
- D-Bus on Linux
|
||||
- XPC on macOS
|
||||
- Stay Rust-native.
|
||||
|
||||
### `core/kordophoned-client-c`
|
||||
|
||||
- Define opaque client handles.
|
||||
- Define FFI-safe request/response structs.
|
||||
- Define callback registration for async completions and daemon signals.
|
||||
- Marshal Rust events onto C callbacks.
|
||||
- Hide Rust enums and collections from C consumers.
|
||||
|
||||
### GLib Wrapper
|
||||
|
||||
- Expose a `KpDaemonClient` GObject.
|
||||
- Convert C callbacks into `GTask` completions and GObject signals.
|
||||
- Marshal all callbacks onto the GLib main context.
|
||||
- Expose Vala-friendly model objects or boxed structs.
|
||||
|
||||
## Draft Public Surface
|
||||
|
||||
The GTK-facing API should look like a normal GLib client, not like a transport
|
||||
binding.
|
||||
|
||||
Suggested primary type:
|
||||
|
||||
- `KpDaemonClient`
|
||||
|
||||
Suggested async methods:
|
||||
|
||||
- `get_conversations_async(limit, offset, cancellable, callback)`
|
||||
- `get_messages_async(conversation_id, last_message_id, cancellable, callback)`
|
||||
- `reply_async(conversation_id, text, attachment_guids, cancellable, callback)`
|
||||
- `new_conversation_async(handle_ids, text, attachment_guids, cancellable, callback)`
|
||||
- `mark_conversation_as_read_async(conversation_id, cancellable, callback)`
|
||||
- `sync_conversation_async(conversation_id, cancellable, callback)`
|
||||
- `sync_conversation_list_async(cancellable, callback)`
|
||||
- `upload_attachment_async(path, cancellable, callback)`
|
||||
- `download_attachment_async(attachment_id, preview, cancellable, callback)`
|
||||
- `get_attachment_info_async(attachment_id, cancellable, callback)`
|
||||
|
||||
Suggested synchronous or utility methods:
|
||||
|
||||
- `open_attachment_fd(attachment_id, preview, error)`
|
||||
- `start()`
|
||||
- `stop()`
|
||||
|
||||
Suggested signals:
|
||||
|
||||
- `conversations-updated`
|
||||
- `messages-updated(conversation-id)`
|
||||
- `attachment-downloaded(attachment-id)`
|
||||
- `attachment-uploaded(upload-guid, attachment-guid)`
|
||||
- `reconnected`
|
||||
- `error(message)`
|
||||
|
||||
The first pass does not need to expose every daemon event. It only needs enough
|
||||
surface to replace the current GTK repository layer.
|
||||
|
||||
## Suggested Model Types
|
||||
|
||||
Avoid returning raw hash tables to Vala.
|
||||
|
||||
Add small typed model objects or boxed structs for:
|
||||
|
||||
- `KpConversationSummary`
|
||||
- `KpChatMessage`
|
||||
- `KpAttachmentInfo`
|
||||
|
||||
If send acknowledgements matter to the UI, add:
|
||||
|
||||
- `KpQueuedMessage`
|
||||
|
||||
The GTK app can keep its own higher-level `Repository` wrapper initially, but it
|
||||
should be wrapping typed client results instead of raw D-Bus maps.
|
||||
|
||||
## Signal Handling
|
||||
|
||||
Signals are the main reason this should be a GLib wrapper instead of plain C
|
||||
calls from Vala.
|
||||
|
||||
Required behavior:
|
||||
|
||||
- daemon signal subscriptions must stay alive for the lifetime of the client
|
||||
- transport callbacks must never call into GTK from a non-main thread
|
||||
- all emitted GObject signals must be marshalled onto the GLib main context
|
||||
|
||||
The C ABI should therefore support registration of signal callbacks plus a user
|
||||
data pointer, while the GLib wrapper owns the main-context handoff.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
### Phase 1: Stabilize Rust FFI Boundary
|
||||
|
||||
- Add FFI-safe request/response types instead of exposing the current worker
|
||||
enums directly.
|
||||
- Keep the Rust worker and transport code internal.
|
||||
- Decide which operations are callback-based and which can be blocking.
|
||||
|
||||
### Phase 2: Add `kordophoned-client-c`
|
||||
|
||||
- Expose opaque client construction/destruction.
|
||||
- Expose request entry points for the operations GTK already uses.
|
||||
- Expose signal subscription hooks.
|
||||
- Add explicit allocation/free helpers for returned strings and arrays.
|
||||
|
||||
### Phase 3: Add GLib Wrapper
|
||||
|
||||
- Implement `KpDaemonClient` as a GObject in C.
|
||||
- Convert C callbacks into `GTask`-based async completion methods.
|
||||
- Emit GObject signals for daemon events.
|
||||
- Generate introspection artifacts for Vala.
|
||||
|
||||
### Phase 4: Migrate GTK
|
||||
|
||||
- Replace direct use of `DBusService.Repository` in
|
||||
[`gtk/src/service/repository.vala`](/home/buzzert/src/Kordophone/gtk/src/service/repository.vala).
|
||||
- Remove the generated D-Bus binding dependency from the GTK app.
|
||||
- Keep the existing GTK-side repository shape initially to minimize churn.
|
||||
|
||||
### Phase 5: Revisit Swift
|
||||
|
||||
Optional.
|
||||
|
||||
If this turns out cleaner than the current Swift XPC wrapper, add a Swift-facing
|
||||
wrapper around the same C ABI later. This is not required for the GTK migration.
|
||||
|
||||
## Build System Notes
|
||||
|
||||
This plan introduces a Cargo + Meson integration boundary.
|
||||
|
||||
Expected follow-up work:
|
||||
|
||||
- decide whether the Rust C ABI library is built via `cargo build`, `cargo-c`,
|
||||
or a Meson custom target
|
||||
- decide where generated headers live
|
||||
- decide where `.gir`, `.typelib`, and `.vapi` artifacts are produced and
|
||||
installed
|
||||
|
||||
The cleanest packaging story is likely:
|
||||
|
||||
- Cargo builds the Rust library
|
||||
- Meson builds the GLib wrapper and generates introspection data
|
||||
- GTK links to the GLib wrapper
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- replacing D-Bus and XPC with a custom socket transport
|
||||
- unifying the macOS app onto GLib
|
||||
- exposing the entire daemon protocol on day one
|
||||
- redesigning GTK application architecture beyond the service boundary
|
||||
|
||||
## Risks
|
||||
|
||||
- FFI ownership mistakes across Rust, C, and GLib
|
||||
- callback threading bugs if signal delivery is not marshalled correctly
|
||||
- build complexity from mixed Cargo and Meson workflows
|
||||
- over-exposing the current daemon protocol instead of defining a cleaner client
|
||||
API
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should the C ABI be Linux-only at first, or cross-platform from day one?
|
||||
- Should the first GTK-facing layer expose send acknowledgements, or just fire
|
||||
and rely on message update signals?
|
||||
- Should handle resolution be part of the GLib client API immediately, or added
|
||||
only when GTK gains compose-new-conversation UI?
|
||||
- Is it worth creating a higher-level shared protocol schema before building the
|
||||
C ABI, or should that wait until after the GTK migration?
|
||||
|
||||
## Short Version
|
||||
|
||||
If we do this later, the best path is probably:
|
||||
|
||||
- Rust daemon client stays as the implementation core
|
||||
- add a small C ABI on top of it
|
||||
- add a tiny C/GObject wrapper for Vala
|
||||
- move GTK off direct D-Bus bindings
|
||||
|
||||
That removes one of the protocol surfaces we currently maintain without forcing
|
||||
the GTK app to consume a Rust-native API directly.
|
||||
Reference in New Issue
Block a user