// SPDX-License-Identifier: GPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® // lint-disable-file fixed-width-types no-char-pointer /* imsd-ofonod — GNOME Calls backend for imsd (Phosh, GNOME Mobile). Presents imsd as an oFono modem on the system bus: `org.ofono` with a Manager at `/`, one Modem + VoiceCallManager at `/imsd`, and one VoiceCall object per live call. GNOME Calls' bundled `ofono` provider plugin then drives imsd — dial, ring, answer, hang up, call history, contact lookup — with Calls, Phosh and GNOME Shell unmodified. It is the exact mirror of what imsd-dialerd does for Plasma Mobile's org.kde.telephony.*. Why this seam and not org.gnome.Calls: that interface is what Calls *exports* so a shell can observe and control a call that already exists (Accept, Hangup, SendDtmf, Silence — there is no Dial, and no way to register as a call source). Phosh consumes it from Calls; nobody provides it to Calls. Calls' own backends are libpeas provider plugins (mm, ofono, sip, dummy) linked against private headers that no distro installs, so `org.ofono` is the only 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. - 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". - inbound-ness is derived ONCE, from State == "incoming" as the call is added, so an MT call must be announced incoming and never corrected later. - DisconnectReason is cached by the plugin and read when CallRemoved arrives — emit it before the removal or the history entry loses its reason. - Dial's returned object path is ignored (the plugin waits for CallAdded). We return a real one anyway. Runs as root because `org.ofono` is a system-bus name (policy in packaging/org.ofono.conf). It does no I/O beyond D-Bus: every call decision stays in imsd. Everything happens on the GLib main loop, so there is no locking. --session own org.ofono on the session bus and talk to imsd there (development: no root, no policy file) IMSD_BUS=session|system override only the imsd side of that choice */ #include #include import std; namespace { constexpr const char* ImsdBus = "net.catcrafts.IMS1"; constexpr const char* ImsdPath = "/net/catcrafts/IMS1"; constexpr const char* ImsdIface = "net.catcrafts.IMS1"; constexpr const char* OfonoBus = "org.ofono"; constexpr const char* ManagerPath = "/"; constexpr const char* ManagerIface = "org.ofono.Manager"; constexpr const char* ModemPath = "/imsd"; constexpr const char* ModemIface = "org.ofono.Modem"; constexpr const char* VcmIface = "org.ofono.VoiceCallManager"; constexpr const char* CallIface = "org.ofono.VoiceCall"; constexpr const char* SsIface = "org.ofono.SupplementaryServices"; constexpr const char* ErrFailed = "org.ofono.Error.Failed"; constexpr const char* ErrNotImplemented = "org.ofono.Error.NotImplemented"; constexpr const char* ErrNotSupported = "org.ofono.Error.NotSupported"; constexpr const char* ErrInvalidArgs = "org.ofono.Error.InvalidArguments"; void Log(std::string_view m) { std::println("imsd-ofonod: {}", m); std::fflush(stdout); } std::string EnvOr(const char* k, std::string d) { const char* v = std::getenv(k); return v ? std::string(v) : std::move(d); } // ---- state (main thread only) ---------------------------------------------- GDBusConnection* Bus = nullptr; // where we own org.ofono GDBusConnection* Imsd = nullptr; // where net.catcrafts.IMS1 lives GMainLoop* Loop = nullptr; bool ImsdUp = false; bool Registered = false; std::vector EmergencyNumbers; struct CallRec { std::string uni, path, number; std::string state = "dialing"; // oFono nick, already mapped std::string reason; // oFono DisconnectReason, once known bool inbound = false; std::int64_t startedAt = 0; guint regId = 0; // exported VoiceCall object }; std::map Calls; // uni -> record std::map ByPath; // object path -> uni std::map UniToPath; // uni -> object path (survives // a Dial reply that races the // CallAdded signal) unsigned PathSeq = 0; // ---- GVariant helpers ------------------------------------------------------- std::string DictStr(GVariant* dict, const char* key) { GVariant* v = g_variant_lookup_value(dict, key, G_VARIANT_TYPE_STRING); if (!v) return ""; std::string s = g_variant_get_string(v, nullptr); g_variant_unref(v); return s; } std::int64_t DictInt64(GVariant* dict, const char* key) { GVariant* v = g_variant_lookup_value(dict, key, G_VARIANT_TYPE_INT64); if (!v) return 0; std::int64_t x = g_variant_get_int64(v); g_variant_unref(v); return x; } bool DictBool(GVariant* dict, const char* key) { GVariant* v = g_variant_lookup_value(dict, key, G_VARIANT_TYPE_BOOLEAN); if (!v) return false; bool b = g_variant_get_boolean(v); g_variant_unref(v); return b; } // ---- imsd <-> oFono vocabulary --------------------------------------------- // imsd call states -> oFono VoiceCall State nicks. Returns nullptr for a state // we don't know, so the caller keeps the one it already published rather than // inventing a transition. const char* OfonoState(std::string_view s) { if (s == "dialing") return "dialing"; if (s == "ringing") return "alerting"; // 180 from the far end if (s == "incoming") return "incoming"; if (s == "active") return "active"; if (s == "terminated") return "disconnected"; return nullptr; } // imsd hangup reasons -> the three oFono DisconnectReason values. const char* OfonoReason(std::string_view r) { if (r == "local-hangup") return "local"; if (r == "remote-hangup" || r == "refused-or-busy") return "remote"; return "network"; } // ---- signal emission -------------------------------------------------------- void Emit(const char* path, const char* iface, const char* name, GVariant* params) { if (!Bus) return; g_dbus_connection_emit_signal(Bus, nullptr, path, iface, name, params, nullptr); } GVariant* PropChanged(const char* name, GVariant* value) { return g_variant_new("(sv)", name, 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. GVariant* InterfacesValue() { GVariantBuilder b; g_variant_builder_init(&b, G_VARIANT_TYPE("as")); if (VoiceUp()) g_variant_builder_add(&b, "s", VcmIface); return g_variant_builder_end(&b); } GVariant* ModemProps() { GVariantBuilder b; g_variant_builder_init(&b, G_VARIANT_TYPE("a{sv}")); g_variant_builder_add(&b, "{sv}", "Name", g_variant_new_string("imsd")); g_variant_builder_add(&b, "{sv}", "Manufacturer", g_variant_new_string("Catcrafts")); g_variant_builder_add(&b, "{sv}", "Model", g_variant_new_string("userspace IMS")); g_variant_builder_add(&b, "{sv}", "Type", g_variant_new_string("hardware")); g_variant_builder_add(&b, "{sv}", "Powered", g_variant_new_boolean(TRUE)); g_variant_builder_add(&b, "{sv}", "Online", g_variant_new_boolean(VoiceUp() ? TRUE : FALSE)); g_variant_builder_add(&b, "{sv}", "Lockdown", g_variant_new_boolean(FALSE)); g_variant_builder_add(&b, "{sv}", "Interfaces", InterfacesValue()); return g_variant_builder_end(&b); } // EmergencyNumbers mirrors imsd's own configuration (builtin 112/911 plus the // EMERGENCY_NUMBERS list) so an oFono client can display it. Classification // itself is imsd's — it decides at Dial time whether a number becomes an // urn:service:sos INVITE — and this list never gates a dial here. GVariant* VcmProps() { GVariantBuilder b; g_variant_builder_init(&b, G_VARIANT_TYPE("a{sv}")); GVariantBuilder n; g_variant_builder_init(&n, G_VARIANT_TYPE("as")); for (const std::string& e : EmergencyNumbers) g_variant_builder_add(&n, "s", e.c_str()); g_variant_builder_add(&b, "{sv}", "EmergencyNumbers", g_variant_builder_end(&n)); return g_variant_builder_end(&b); } // oFono's StartTime is an ISO 8601 string; imsd hands us epoch seconds. std::string StartTimeOf(std::int64_t epoch) { if (epoch <= 0) return ""; GDateTime* dt = g_date_time_new_from_unix_utc(epoch); if (!dt) return ""; gchar* s = g_date_time_format_iso8601(dt); std::string out = s ? s : ""; g_free(s); g_date_time_unref(dt); return out; } // Deliberately no "Emergency" key: imsd owns emergency classification and does // not export its per-call verdict, and a second guess here could disagree with // the INVITE that actually went out. GVariant* CallProps(const CallRec& r) { GVariantBuilder b; g_variant_builder_init(&b, G_VARIANT_TYPE("a{sv}")); g_variant_builder_add(&b, "{sv}", "LineIdentification", g_variant_new_string(r.number.c_str())); g_variant_builder_add(&b, "{sv}", "Name", g_variant_new_string("")); g_variant_builder_add(&b, "{sv}", "State", g_variant_new_string(r.state.c_str())); g_variant_builder_add(&b, "{sv}", "Multiparty", g_variant_new_boolean(FALSE)); g_variant_builder_add(&b, "{sv}", "RemoteHeld", g_variant_new_boolean(FALSE)); g_variant_builder_add(&b, "{sv}", "RemoteMultiparty", g_variant_new_boolean(FALSE)); std::string started = StartTimeOf(r.startedAt); if (!started.empty()) g_variant_builder_add(&b, "{sv}", "StartTime", g_variant_new_string(started.c_str())); return g_variant_builder_end(&b); } GVariant* CallVector() { GVariantBuilder b; g_variant_builder_init(&b, G_VARIANT_TYPE("a(oa{sv})")); for (const auto& [uni, rec] : Calls) g_variant_builder_add(&b, "(o@a{sv})", rec.path.c_str(), CallProps(rec)); return g_variant_builder_end(&b); } // ---- imsd (client side) ----------------------------------------------------- // Fire-and-forget into imsd; a dead imsd shows up via the name watch, so a // failure here is logged rather than fatal (same contract as imsd-dialerd). void CallImsd(const char* method, GVariant* params) { g_dbus_connection_call( Imsd, ImsdBus, ImsdPath, ImsdIface, method, params, nullptr, G_DBUS_CALL_FLAGS_NONE, 5000, nullptr, [](GObject* src, GAsyncResult* res, gpointer m) { GError* err = nullptr; GVariant* r = g_dbus_connection_call_finish(G_DBUS_CONNECTION(src), res, &err); if (err) { Log(std::format("{} failed: {}", static_cast(m), err->message)); g_error_free(err); } if (r) g_variant_unref(r); }, const_cast(method)); } std::string ActiveUni() { for (const auto& [uni, rec] : Calls) if (rec.state != "disconnected") return uni; return ""; } // ---- the VoiceCall objects -------------------------------------------------- constexpr const char* CallXml = R"xml( )xml"; void HandleCallMethod(GDBusConnection*, const gchar*, const gchar* objectPath, const gchar*, const gchar* method, GVariant*, GDBusMethodInvocation* inv, gpointer) { auto byPath = ByPath.find(objectPath ? objectPath : ""); if (byPath == ByPath.end()) { g_dbus_method_invocation_return_dbus_error(inv, ErrFailed, "no such call"); return; } auto it = Calls.find(byPath->second); if (it == Calls.end()) { g_dbus_method_invocation_return_dbus_error(inv, ErrFailed, "no such call"); return; } std::string_view m = method; if (m == "GetProperties") { g_dbus_method_invocation_return_value(inv, g_variant_new("(@a{sv})", CallProps(it->second))); return; } if (m == "Answer") { Log(std::format("Answer({})", it->second.uni)); CallImsd("Accept", g_variant_new("(s)", it->second.uni.c_str())); g_dbus_method_invocation_return_value(inv, nullptr); return; } if (m == "Hangup") { Log(std::format("Hangup({})", it->second.uni)); CallImsd("HangUp", g_variant_new("(s)", it->second.uni.c_str())); g_dbus_method_invocation_return_value(inv, nullptr); return; } if (m == "Deflect") { g_dbus_method_invocation_return_dbus_error(inv, ErrNotImplemented, "call deflection is not implemented"); return; } g_dbus_method_invocation_return_dbus_error(inv, "org.freedesktop.DBus.Error.UnknownMethod", "no such method"); } const GDBusInterfaceVTable CallVtable = { HandleCallMethod, nullptr, nullptr, {} }; guint RegisterObject(const char* xml, const char* path, const GDBusInterfaceVTable* vtable) { GDBusNodeInfo* node = g_dbus_node_info_new_for_xml(xml, nullptr); guint id = g_dbus_connection_register_object(Bus, path, node->interfaces[0], vtable, nullptr, nullptr, nullptr); g_dbus_node_info_unref(node); return id; } // Allocate (or recall) the object path for an imsd call uni. imsd's unis // ("ims-call-3") are not valid object-path elements, and a Dial reply can beat // the CallAdded signal, so the mapping is created on first mention either way. const std::string& PathFor(const std::string& uni) { auto it = UniToPath.find(uni); if (it != UniToPath.end()) return it->second; std::string path = std::format("{}/voicecall{:02}", ModemPath, ++PathSeq); ByPath[path] = uni; return UniToPath.emplace(uni, std::move(path)).first->second; } void ForgetPath(const std::string& uni) { auto it = UniToPath.find(uni); if (it == UniToPath.end()) return; ByPath.erase(it->second); UniToPath.erase(it); } void AddCall(const std::string& uni, const std::string& number, std::string_view imsdState, std::int64_t startedAt) { if (uni.empty() || Calls.contains(uni)) return; const char* st = OfonoState(imsdState); CallRec rec; rec.uni = uni; rec.path = PathFor(uni); rec.number = number; rec.state = st ? st : "dialing"; rec.inbound = rec.state == "incoming"; rec.startedAt = startedAt ? startedAt : g_get_real_time() / G_USEC_PER_SEC; rec.regId = RegisterObject(CallXml, rec.path.c_str(), &CallVtable); Log(std::format("call added {} {} {} [{}]", rec.path, rec.inbound ? "<-" : "->", rec.number, rec.state)); GVariant* props = CallProps(rec); Calls[uni] = std::move(rec); Emit(ModemPath, VcmIface, "CallAdded", g_variant_new("(o@a{sv})", Calls[uni].path.c_str(), props)); } void SetCallState(CallRec& rec, const char* state) { if (rec.state == state) return; rec.state = state; Emit(rec.path.c_str(), CallIface, "PropertyChanged", PropChanged("State", g_variant_new_string(state))); } // The plugin caches DisconnectReason and reads it when CallRemoved lands, so // the order here is load-bearing: disconnected state, then reason, then removal. void RemoveCall(const std::string& uni, const char* reasonHint) { auto it = Calls.find(uni); if (it == Calls.end()) return; CallRec& rec = it->second; SetCallState(rec, "disconnected"); const char* reason = !rec.reason.empty() ? rec.reason.c_str() : (reasonHint ? reasonHint : "network"); Emit(rec.path.c_str(), CallIface, "DisconnectReason", g_variant_new("(s)", reason)); if (rec.regId) g_dbus_connection_unregister_object(Bus, rec.regId); std::string path = rec.path; Log(std::format("call removed {} ({})", path, reason)); Calls.erase(it); ForgetPath(uni); Emit(ModemPath, VcmIface, "CallRemoved", g_variant_new("(o)", path.c_str())); } // ---- Manager / Modem / VoiceCallManager objects ----------------------------- constexpr const char* ManagerXml = R"xml( )xml"; constexpr const char* ModemXml = R"xml( )xml"; constexpr const char* VcmXml = R"xml( )xml"; // USSD has no path through the IMS stack. The interface is answered (Calls // proxies it unconditionally, and a legible error beats UnknownInterface) but // deliberately NOT advertised in the modem's Interfaces list. constexpr const char* SsXml = R"xml( )xml"; void HandleManagerMethod(GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar* method, GVariant*, GDBusMethodInvocation* inv, gpointer) { if (std::string_view(method) == "GetModems") { GVariantBuilder b; g_variant_builder_init(&b, G_VARIANT_TYPE("a(oa{sv})")); g_variant_builder_add(&b, "(o@a{sv})", ModemPath, ModemProps()); g_dbus_method_invocation_return_value(inv, g_variant_new("(@a(oa{sv}))", g_variant_builder_end(&b))); return; } g_dbus_method_invocation_return_dbus_error(inv, "org.freedesktop.DBus.Error.UnknownMethod", "no such method"); } void HandleModemMethod(GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar* method, GVariant* params, GDBusMethodInvocation* inv, gpointer) { std::string_view m = method; if (m == "GetProperties") { g_dbus_method_invocation_return_value(inv, g_variant_new("(@a{sv})", ModemProps())); return; } if (m == "SetProperty") { const gchar* prop = nullptr; GVariant* value = nullptr; g_variant_get(params, "(&sv)", &prop, &value); if (value) g_variant_unref(value); // Powered/Online track imsd's registration; nothing here is settable. g_dbus_method_invocation_return_dbus_error( inv, ErrNotSupported, std::format("{} is driven by imsd's registration and cannot be set", prop ? prop : "?").c_str()); return; } g_dbus_method_invocation_return_dbus_error(inv, "org.freedesktop.DBus.Error.UnknownMethod", "no such method"); } void OnDialReply(GObject* src, GAsyncResult* res, gpointer user) { GDBusMethodInvocation* inv = static_cast(user); GError* err = nullptr; GVariant* r = g_dbus_connection_call_finish(G_DBUS_CONNECTION(src), res, &err); if (!r) { Log(std::format("imsd Dial failed: {}", err ? err->message : "?")); g_dbus_method_invocation_return_dbus_error(inv, ErrFailed, err ? err->message : "imsd Dial failed"); if (err) g_error_free(err); return; } const gchar* uni = nullptr; g_variant_get(r, "(&s)", &uni); if (!uni || !*uni) { g_variant_unref(r); g_dbus_method_invocation_return_dbus_error(inv, ErrFailed, "imsd returned no call id"); return; } const std::string& path = PathFor(uni); g_dbus_method_invocation_return_value(inv, g_variant_new("(o)", path.c_str())); g_variant_unref(r); } void HandleVcmMethod(GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar* method, GVariant* params, GDBusMethodInvocation* inv, gpointer) { std::string_view m = method; if (m == "GetProperties") { g_dbus_method_invocation_return_value(inv, g_variant_new("(@a{sv})", VcmProps())); return; } if (m == "GetCalls") { g_dbus_method_invocation_return_value(inv, g_variant_new("(@a(oa{sv}))", CallVector())); return; } if (m == "Dial") { const gchar* number = nullptr; const gchar* clir = nullptr; g_variant_get(params, "(&s&s)", &number, &clir); if (!number || !*number) { g_dbus_method_invocation_return_dbus_error(inv, ErrInvalidArgs, "empty number"); return; } // "default" is the only CLIR setting imsd can honour. Failing loudly on // a withhold request beats dialling with the caller ID exposed. std::string_view hide = clir ? clir : ""; if (!hide.empty() && hide != "default") { g_dbus_method_invocation_return_dbus_error(inv, ErrNotImplemented, "per-call caller-ID control is not implemented"); return; } if (!VoiceUp()) { g_dbus_method_invocation_return_dbus_error(inv, ErrFailed, "imsd is not registered"); return; } Log(std::format("Dial({})", number)); g_dbus_connection_call(Imsd, ImsdBus, ImsdPath, ImsdIface, "Dial", g_variant_new("(s)", number), G_VARIANT_TYPE("(s)"), G_DBUS_CALL_FLAGS_NONE, 10000, nullptr, OnDialReply, inv); return; } if (m == "HangupAll") { for (const auto& [uni, rec] : Calls) if (rec.state != "disconnected") CallImsd("HangUp", g_variant_new("(s)", uni.c_str())); g_dbus_method_invocation_return_value(inv, nullptr); return; } if (m == "SendTones") { const gchar* tones = nullptr; g_variant_get(params, "(&s)", &tones); std::string target = ActiveUni(); if (target.empty()) { g_dbus_method_invocation_return_dbus_error(inv, ErrFailed, "no active call"); return; } CallImsd("SendDtmf", g_variant_new("(ss)", target.c_str(), tones ? tones : "")); g_dbus_method_invocation_return_value(inv, nullptr); return; } // Single-call stack: no hold, no multiparty, no transfer, no dial memory. if (m == "Transfer" || m == "SwapCalls" || m == "ReleaseAndAnswer" || m == "ReleaseAndSwap" || m == "HoldAndAnswer" || m == "PrivateChat" || m == "CreateMultiparty" || m == "HangupMultiparty" || m == "DialLast" || m == "DialMemory") { g_dbus_method_invocation_return_dbus_error(inv, ErrNotImplemented, "not implemented by imsd"); return; } g_dbus_method_invocation_return_dbus_error(inv, "org.freedesktop.DBus.Error.UnknownMethod", "no such method"); } void HandleSsMethod(GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar* method, GVariant*, GDBusMethodInvocation* inv, gpointer) { std::string_view m = method; if (m == "GetProperties") { GVariantBuilder b; g_variant_builder_init(&b, G_VARIANT_TYPE("a{sv}")); g_variant_builder_add(&b, "{sv}", "State", g_variant_new_string("idle")); g_dbus_method_invocation_return_value(inv, g_variant_new("(@a{sv})", g_variant_builder_end(&b))); return; } if (m == "Cancel") { g_dbus_method_invocation_return_value(inv, nullptr); return; } g_dbus_method_invocation_return_dbus_error(inv, ErrNotSupported, "USSD is not supported on the IMS stack"); } const GDBusInterfaceVTable ManagerVtable = { HandleManagerMethod, nullptr, nullptr, {} }; const GDBusInterfaceVTable ModemVtable = { HandleModemMethod, nullptr, nullptr, {} }; const GDBusInterfaceVTable VcmVtable = { HandleVcmMethod, nullptr, nullptr, {} }; const GDBusInterfaceVTable SsVtable = { HandleSsMethod, nullptr, nullptr, {} }; // ---- imsd events ------------------------------------------------------------ void PublishInterfaces() { Emit(ModemPath, ModemIface, "PropertyChanged", PropChanged("Interfaces", InterfacesValue())); Emit(ModemPath, ModemIface, "PropertyChanged", PropChanged("Online", g_variant_new_boolean(VoiceUp() ? TRUE : FALSE))); Log(std::format("voice interface {}", VoiceUp() ? "available" : "withdrawn")); } void OnImsdSignal(GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar* signal, GVariant* params, gpointer) { std::string_view sig = signal; if (sig == "CallAdded") { const gchar* uni = nullptr; GVariant* info = nullptr; g_variant_get(params, "(&s@a{sv})", &uni, &info); AddCall(uni ? uni : "", DictStr(info, "number"), DictStr(info, "state").empty() ? (DictStr(info, "direction") == "incoming" ? "incoming" : "dialing") : DictStr(info, "state"), DictInt64(info, "startedAt")); g_variant_unref(info); } else if (sig == "CallStateChanged") { const gchar* uni = nullptr; const gchar* state = nullptr; const gchar* reason = nullptr; g_variant_get(params, "(&s&s&s)", &uni, &state, &reason); auto it = Calls.find(uni ? uni : ""); if (it == Calls.end()) return; const char* mapped = OfonoState(state ? state : ""); if (!mapped) return; if (std::string_view(mapped) == "disconnected") { // Bank the reason before the state flips: RemoveCall emits it. it->second.reason = OfonoReason(reason ? reason : ""); SetCallState(it->second, mapped); Log(std::format("{} -> disconnected ({})", it->second.path, it->second.reason)); return; } Log(std::format("{} -> {}", it->second.path, mapped)); SetCallState(it->second, mapped); } else if (sig == "CallDeleted") { const gchar* uni = nullptr; g_variant_get(params, "(&s)", &uni); RemoveCall(uni ? uni : "", "network"); } else if (sig == "RegistrationChanged") { gboolean reg = FALSE; g_variant_get(params, "(b)", ®); if (Registered == static_cast(reg)) return; Registered = reg; PublishInterfaces(); } } void RefreshFromImsd() { GError* err = nullptr; GVariant* st = g_dbus_connection_call_sync(Imsd, ImsdBus, ImsdPath, ImsdIface, "GetStatus", nullptr, G_VARIANT_TYPE("(a{sv})"), G_DBUS_CALL_FLAGS_NONE, 5000, nullptr, &err); if (!st) { Log(std::format("imsd status query failed: {}", err ? err->message : "?")); if (err) g_error_free(err); Registered = false; return; } GVariant* dict = g_variant_get_child_value(st, 0); Registered = DictBool(dict, "registered"); g_variant_unref(dict); g_variant_unref(st); GVariant* calls = g_dbus_connection_call_sync(Imsd, ImsdBus, ImsdPath, ImsdIface, "GetCalls", nullptr, G_VARIANT_TYPE("(aa{sv})"), G_DBUS_CALL_FLAGS_NONE, 5000, nullptr, &err); if (!calls) { if (err) g_error_free(err); return; } GVariant* arr = g_variant_get_child_value(calls, 0); GVariantIter it; g_variant_iter_init(&it, arr); while (GVariant* info = g_variant_iter_next_value(&it)) { AddCall(DictStr(info, "uni"), DictStr(info, "number"), DictStr(info, "state"), DictInt64(info, "startedAt")); g_variant_unref(info); } g_variant_unref(arr); g_variant_unref(calls); } void OnImsdAppeared(GDBusConnection*, const gchar*, const gchar*, gpointer) { if (ImsdUp) return; ImsdUp = true; Log("imsd up"); RefreshFromImsd(); PublishInterfaces(); } void OnImsdVanished(GDBusConnection*, const gchar*, gpointer) { if (!ImsdUp) return; ImsdUp = false; Registered = false; Log("imsd down"); std::vector live; for (const auto& [uni, rec] : Calls) live.push_back(uni); for (const std::string& uni : live) RemoveCall(uni, "network"); PublishInterfaces(); } gboolean OnTerm(gpointer loop) { g_main_loop_quit(static_cast(loop)); return G_SOURCE_REMOVE; } void LoadEmergencyNumbers() { EmergencyNumbers = { "112", "911" }; std::string extra = EnvOr("EMERGENCY_NUMBERS", ""); for (std::size_t pos = 0; pos <= extra.size();) { std::size_t comma = extra.find(',', pos); std::size_t end = comma == std::string::npos ? extra.size() : comma; std::string n = extra.substr(pos, end - pos); std::erase(n, ' '); if (!n.empty() && std::ranges::find(EmergencyNumbers, n) == EmergencyNumbers.end()) EmergencyNumbers.push_back(std::move(n)); if (comma == std::string::npos) break; pos = comma + 1; } } } // namespace int main(int argc, char** argv) { bool session = false; for (int i = 1; i < argc; i++) if (std::string_view(argv[i]) == "--session") session = true; GBusType ofonoBus = session ? G_BUS_TYPE_SESSION : G_BUS_TYPE_SYSTEM; std::string imsdBusName = EnvOr("IMSD_BUS", session ? "session" : "system"); GBusType imsdBus = imsdBusName == "session" ? G_BUS_TYPE_SESSION : G_BUS_TYPE_SYSTEM; LoadEmergencyNumbers(); GError* err = nullptr; Bus = g_bus_get_sync(ofonoBus, nullptr, &err); if (!Bus) { std::println(std::cerr, "imsd-ofonod: no {} bus: {}", session ? "session" : "system", err ? err->message : "?"); return 1; } Imsd = imsdBus == ofonoBus ? Bus : g_bus_get_sync(imsdBus, nullptr, &err); if (!Imsd) { std::println(std::cerr, "imsd-ofonod: no {} bus for imsd: {}", imsdBusName, err ? err->message : "?"); return 1; } RegisterObject(ManagerXml, ManagerPath, &ManagerVtable); RegisterObject(ModemXml, ModemPath, &ModemVtable); RegisterObject(VcmXml, ModemPath, &VcmVtable); RegisterObject(SsXml, ModemPath, &SsVtable); Loop = g_main_loop_new(nullptr, FALSE); // DO_NOT_QUEUE: a real ofonod already owning the name means two stacks are // fighting over the modem — fail loudly instead of waiting our turn. guint owner = g_bus_own_name_on_connection( Bus, OfonoBus, G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE, [](GDBusConnection*, const gchar* n, gpointer) { Log(std::format("owning {}", n)); }, [](GDBusConnection*, const gchar* n, gpointer lp) { Log(std::format("lost {} (is ofonod running?) — exiting", n)); g_main_loop_quit(static_cast(lp)); }, Loop, nullptr); g_dbus_connection_signal_subscribe(Imsd, ImsdBus, ImsdIface, nullptr, ImsdPath, nullptr, G_DBUS_SIGNAL_FLAGS_NONE, OnImsdSignal, nullptr, nullptr); g_bus_watch_name_on_connection(Imsd, ImsdBus, G_BUS_NAME_WATCHER_FLAGS_NONE, OnImsdAppeared, OnImsdVanished, nullptr, nullptr); g_unix_signal_add(SIGTERM, OnTerm, Loop); g_unix_signal_add(SIGINT, OnTerm, Loop); Log(std::format("up (org.ofono on the {} bus, imsd on the {} bus)", session ? "session" : "system", imsdBusName)); g_main_loop_run(Loop); g_bus_unown_name(owner); g_main_loop_unref(Loop); return 0; }