diff --git a/Makefile b/Makefile index 758d1ba..a72e15e 100644 --- a/Makefile +++ b/Makefile @@ -145,6 +145,8 @@ install: all $(DESTDIR)$(PREFIX)/share/dbus-1/system.d/imsd-ofono.conf install -Dm644 packaging/90_imsd-ofono.gschema.override \ $(DESTDIR)$(PREFIX)/share/glib-2.0/schemas/90_imsd-ofono.gschema.override + install -Dm644 packaging/80-imsd-ofono.preset \ + $(DESTDIR)$(PREFIX)/lib/systemd/system-preset/80-imsd-ofono.preset $(PCM) $(OBJ) $(O)/tests: mkdir -p $@ diff --git a/README.md b/README.md index e2b3fdf..447043a 100644 --- a/README.md +++ b/README.md @@ -173,8 +173,8 @@ private headers that no distro installs. Setup: ```sh -apk add imsd-ofono # or install the files by hand -systemctl enable --now imsd-ofonod # system service; org.ofono is a system name +apk add imsd-ofono # enables imsd-ofonod via a systemd preset +systemctl enable --now imsd-ofonod # hand installs: system service, org.ofono is a system name # point Calls at the ofono provider (the package ships this as a gschema # override; this is the per-user equivalent) gsettings set org.gnome.Calls autoload-plugins "['ofono']" @@ -218,13 +218,17 @@ Known gaps, all inherited rather than introduced: normal dialpad is unaffected — that reaches imsd's own classifier. - DTMF is forwarded to imsd, where `SendDtmf` is still a stub (RFC 4733 is not implemented yet) — the same limitation Plasma has. -- **Restart GNOME Calls after restarting `imsd-ofonod` (or imsd).** When the +- **Restart GNOME Calls after restarting `imsd-ofonod` itself.** When the modem's `VoiceCallManager` interface goes away and comes back, Calls 50.0 adds a new origin but leaves the old origin's `CallAdded` handler connected: after one such cycle every call is added twice, after a few the stale handler runs on freed memory and Calls dies with SIGBUS in `g_hash_table_lookup` (measured on the FP6). Calls-side; an upstream fix belongs in - `plugins/provider/ofono/calls-ofono-provider.c`. + `plugins/provider/ofono/calls-ofono-provider.c`. To keep imsd's own + restarts and registration drops from triggering it, `imsd-ofonod` lists the + interface permanently: `Online` tracks registration, and `Dial` fails with + "imsd is not registered" / "imsd is not running" instead. Only `org.ofono` + vanishing — this daemon restarting — still cycles the origin. Development, without a phone or root: diff --git a/implementations/ofonod.cpp b/implementations/ofonod.cpp index 79c941e..a93bb15 100644 --- a/implementations/ofonod.cpp +++ b/implementations/ofonod.cpp @@ -23,9 +23,15 @@ D-Bus seam a third party can implement. Consumer-shaped details, verified against calls 50.0 plugins/provider/ofono/ — get these wrong and the plugin silently ignores us: - the provider watches `org.ofono` on the SYSTEM bus and proxies `/` as - org.ofono.Manager. A modem becomes a dialable Origin only while its - `Interfaces` property contains "org.ofono.VoiceCallManager", so that list - is our registration gate — the analogue of the dialerd's deviceUniList. + org.ofono.Manager. A modem is an Origin exactly while its `Interfaces` + property lists "org.ofono.VoiceCallManager" — and Calls 50.0 never + disconnects a removed Origin's CallAdded handler, so every withdraw/ + re-add cycle leaves a stale handler behind (calls announced twice, then + a crash on freed memory; measured on the FP6). The interface is + therefore listed PERMANENTLY; registration is reported through the + modem's `Online` property and enforced in Dial, which fails legibly + while imsd is down or unregistered. Only this daemon's own restart + (org.ofono vanishing) still cycles the Origin. - VoiceCall `State` strings are parsed as CallsCallState enum nicks: dialing / alerting / incoming / active / held / disconnected. imsd's "ringing" (a 180 from the far end) is oFono's "alerting". @@ -159,12 +165,13 @@ GVariant* PropChanged(const char* name, GVariant* value) { // ---- Modem / VoiceCallManager properties ------------------------------------ bool VoiceUp() { return ImsdUp && Registered; } -// The registration gate: the plugin only builds an Origin while -// org.ofono.VoiceCallManager is listed here. +// Listed unconditionally: the plugin builds one Origin per appearance of +// this interface and cannot survive a re-appearance (header). Whether calls +// can actually be placed is VoiceUp(), checked in Dial and shown as Online. GVariant* InterfacesValue() { GVariantBuilder b; g_variant_builder_init(&b, G_VARIANT_TYPE("as")); - if (VoiceUp()) g_variant_builder_add(&b, "s", VcmIface); + g_variant_builder_add(&b, "s", VcmIface); return g_variant_builder_end(&b); } @@ -534,7 +541,7 @@ void HandleVcmMethod(GDBusConnection*, const gchar*, const gchar*, const gchar*, return; } if (!VoiceUp()) { - g_dbus_method_invocation_return_dbus_error(inv, ErrFailed, "imsd is not registered"); + g_dbus_method_invocation_return_dbus_error(inv, ErrFailed, ImsdUp ? "imsd is not registered" : "imsd is not running"); return; } Log(std::format("Dial({})", number)); @@ -594,11 +601,10 @@ const GDBusInterfaceVTable VcmVtable = { HandleVcmMethod, nullptr, nullptr, {} } const GDBusInterfaceVTable SsVtable = { HandleSsMethod, nullptr, nullptr, {} }; // ---- imsd events ------------------------------------------------------------ -void PublishInterfaces() { - Emit(ModemPath, ModemIface, "PropertyChanged", PropChanged("Interfaces", InterfacesValue())); +void PublishVoiceState() { Emit(ModemPath, ModemIface, "PropertyChanged", PropChanged("Online", g_variant_new_boolean(VoiceUp() ? TRUE : FALSE))); - Log(std::format("voice interface {}", VoiceUp() ? "available" : "withdrawn")); + Log(std::format("voice {}", VoiceUp() ? "registered" : (ImsdUp ? "unregistered" : "unavailable (imsd down)"))); } void OnImsdSignal(GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar* signal, GVariant* params, gpointer) { @@ -640,7 +646,7 @@ void OnImsdSignal(GDBusConnection*, const gchar*, const gchar*, const gchar*, co g_variant_get(params, "(b)", ®); if (Registered == static_cast(reg)) return; Registered = reg; - PublishInterfaces(); + PublishVoiceState(); } } @@ -679,7 +685,7 @@ void OnImsdAppeared(GDBusConnection*, const gchar*, const gchar*, gpointer) { ImsdUp = true; Log("imsd up"); RefreshFromImsd(); - PublishInterfaces(); + PublishVoiceState(); } void OnImsdVanished(GDBusConnection*, const gchar*, gpointer) { @@ -690,7 +696,7 @@ void OnImsdVanished(GDBusConnection*, const gchar*, gpointer) { std::vector live; for (const auto& [uni, rec] : Calls) live.push_back(uni); for (const std::string& uni : live) RemoveCall(uni, "network"); - PublishInterfaces(); + PublishVoiceState(); } gboolean OnTerm(gpointer loop) { diff --git a/packaging/80-imsd-ofono.preset b/packaging/80-imsd-ofono.preset new file mode 100644 index 0000000..c76458b --- /dev/null +++ b/packaging/80-imsd-ofono.preset @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-3.0-only +# SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® +# imsd-ofono is the opt-in GNOME Calls backend: installing it is the choice +# to run it (the unit itself refuses to coexist with a real ofonod). +enable imsd-ofonod.service diff --git a/packaging/APKBUILD b/packaging/APKBUILD index a048ea7..b85b738 100644 --- a/packaging/APKBUILD +++ b/packaging/APKBUILD @@ -46,4 +46,9 @@ ofono() { amove usr/lib/systemd/system/imsd-ofonod.service amove usr/share/dbus-1/system.d/imsd-ofono.conf amove usr/share/glib-2.0/schemas/90_imsd-ofono.gschema.override + amove usr/lib/systemd/system-preset/80-imsd-ofono.preset + # enabled on install, like imsd.service in the main package + mkdir -p "$subpkgdir"/etc/systemd/system/multi-user.target.wants + ln -s /usr/lib/systemd/system/imsd-ofonod.service \ + "$subpkgdir"/etc/systemd/system/multi-user.target.wants/imsd-ofonod.service } diff --git a/packaging/APKBUILD.binary b/packaging/APKBUILD.binary index 97a588f..486f4bc 100644 --- a/packaging/APKBUILD.binary +++ b/packaging/APKBUILD.binary @@ -70,6 +70,8 @@ package() { "$pkgdir"/usr/share/dbus-1/system.d/imsd-ofono.conf install -Dm644 90_imsd-ofono.gschema.override \ "$pkgdir"/usr/share/glib-2.0/schemas/90_imsd-ofono.gschema.override + install -Dm644 80-imsd-ofono.preset \ + "$pkgdir"/usr/lib/systemd/system-preset/80-imsd-ofono.preset } ofono() { @@ -79,6 +81,11 @@ ofono() { amove usr/lib/systemd/system/imsd-ofonod.service amove usr/share/dbus-1/system.d/imsd-ofono.conf amove usr/share/glib-2.0/schemas/90_imsd-ofono.gschema.override + amove usr/lib/systemd/system-preset/80-imsd-ofono.preset + # enabled on install, like imsd.service in the main package + mkdir -p "$subpkgdir"/etc/systemd/system/multi-user.target.wants + ln -s /usr/lib/systemd/system/imsd-ofonod.service \ + "$subpkgdir"/etc/systemd/system/multi-user.target.wants/imsd-ofonod.service } systemd() { diff --git a/packaging/imsd-dialerd.desktop b/packaging/imsd-dialerd.desktop index 9904d01..c1c1b27 100644 --- a/packaging/imsd-dialerd.desktop +++ b/packaging/imsd-dialerd.desktop @@ -7,5 +7,6 @@ Comment=Plasma Dialer backend for imsd (replaces plasma-dialer's modem-daemon) Exec=/usr/bin/imsd-dialerd Icon=call-start NoDisplay=true +OnlyShowIn=KDE; X-KDE-autostart-phase=1 X-KDE-StartupNotify=false diff --git a/packaging/make-bin-tarball.sh b/packaging/make-bin-tarball.sh index 24e4ffe..72934bb 100755 --- a/packaging/make-bin-tarball.sh +++ b/packaging/make-bin-tarball.sh @@ -27,6 +27,7 @@ cp "$OFONOD" "$stage/imsd-$VER/imsd-ofonod" cp packaging/ims-pdn-up.sh packaging/imsd.service \ packaging/imsd-dialerd.desktop packaging/imsd-ofonod.service \ packaging/org.ofono.conf packaging/90_imsd-ofono.gschema.override \ + packaging/80-imsd-ofono.preset \ packaging/net.catcrafts.IMS1.conf "$stage/imsd-$VER/" tar -C "$stage" -czf "imsd-$VER.tar.gz" "imsd-$VER" echo "wrote imsd-$VER.tar.gz ($(du -h "imsd-$VER.tar.gz" | cut -f1))"