From 0418e8bab9b435eff3ca42f5e450f8f243f46f85 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Wed, 29 Apr 2026 11:29:59 +0700 Subject: [PATCH] manager: surface D-Bus call errors to the user MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Connect / Forget / Set(Powered) / Scan / Disconnect / RegisterAgent / ConnectHidden previously discarded reply errors with NULL callbacks, so "Connecting…" could hang forever after a refused call (rfkill, busy adapter, another agent already registered, bad credentials on a known network). The user had no way to see the failure. Add iwd_manager_{report,last,clear}_error and wire reply callbacks in adapter / device / network / agent. The popup status line now appends the latest error to the state label, and user actions (rescan, toggle, connect, disconnect) clear it. Scan errors that mean "already in flight" are filtered out — they're the normal race when two scan triggers fire close together. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/e_mod_popup.c | 22 +++++++++++++++--- src/iwd/iwd_adapter.c | 12 +++++++++- src/iwd/iwd_agent.c | 15 +++++++++--- src/iwd/iwd_device.c | 54 +++++++++++++++++++++++++++++++++++-------- src/iwd/iwd_manager.c | 31 +++++++++++++++++++++++++ src/iwd/iwd_manager.h | 9 ++++++++ src/iwd/iwd_network.c | 46 +++++++++++++++++++++++++++++------- 7 files changed, 165 insertions(+), 24 deletions(-) diff --git a/src/e_mod_popup.c b/src/e_mod_popup.c index 94b777a..c55a9f3 100644 --- a/src/e_mod_popup.c +++ b/src/e_mod_popup.c @@ -148,6 +148,7 @@ _on_net_clicked(void *data, Evas_Object *obj EINA_UNUSED, void *ev EINA_UNUSED) { Iwd_Network *n = data; if (!n) return; + if (e_iwd && e_iwd->manager) iwd_manager_clear_error(e_iwd->manager); iwd_network_connect(n); } @@ -234,7 +235,17 @@ _refresh(Popup *p) if (!p || !e_iwd || !e_iwd->manager) return; Iwd_State s = iwd_manager_state(e_iwd->manager); if (p->status_lbl) - elm_object_text_set(p->status_lbl, _state_label(s)); + { + const char *err = iwd_manager_last_error(e_iwd->manager); + if (err) + { + char buf[320]; + snprintf(buf, sizeof(buf), "%s — %s", _state_label(s), err); + elm_object_text_set(p->status_lbl, buf); + } + else + elm_object_text_set(p->status_lbl, _state_label(s)); + } if (p->btn_toggle) elm_object_text_set(p->btn_toggle, s == IWD_STATE_OFF ? "Enable" : "Disable"); if (p->btn_scan) @@ -260,18 +271,23 @@ _on_manager_change(void *data, Iwd_Manager *m EINA_UNUSED) static void _on_rescan (void *d EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *e EINA_UNUSED) { - if (e_iwd && e_iwd->manager) iwd_manager_scan_request(e_iwd->manager); + if (!e_iwd || !e_iwd->manager) return; + iwd_manager_clear_error(e_iwd->manager); + iwd_manager_scan_request(e_iwd->manager); } static void _on_toggle(void *d EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *e EINA_UNUSED) { if (!e_iwd || !e_iwd->manager) return; + iwd_manager_clear_error(e_iwd->manager); Eina_Bool off = (iwd_manager_state(e_iwd->manager) == IWD_STATE_OFF); iwd_manager_set_powered(e_iwd->manager, off); } static void _on_disconnect(void *d EINA_UNUSED, Evas_Object *o EINA_UNUSED, void *e EINA_UNUSED) { Iwd_Device *dev = _active_device(); - if (dev) iwd_device_disconnect(dev); + if (!dev) return; + if (e_iwd && e_iwd->manager) iwd_manager_clear_error(e_iwd->manager); + iwd_device_disconnect(dev); } static void diff --git a/src/iwd/iwd_adapter.c b/src/iwd/iwd_adapter.c index 4747dad..8998d39 100644 --- a/src/iwd/iwd_adapter.c +++ b/src/iwd/iwd_adapter.c @@ -61,6 +61,16 @@ iwd_adapter_free(Iwd_Adapter *a) free(a); } +static void +_on_set_powered_reply(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED) +{ + Iwd_Adapter *a = data; + const char *en, *em; + if (eldbus_message_error_get(msg, &en, &em) && a->manager) + iwd_manager_report_error(a->manager, + "Set Adapter.Powered failed: %s", em ? em : en); +} + void iwd_adapter_set_powered(Iwd_Adapter *a, Eina_Bool on) { @@ -83,7 +93,7 @@ iwd_adapter_set_powered(Iwd_Adapter *a, Eina_Bool on) eldbus_message_iter_basic_append(variant, 'b', v); eldbus_message_iter_container_close(iter, variant); - eldbus_proxy_send(props, msg, NULL, NULL, -1); + eldbus_proxy_send(props, msg, _on_set_powered_reply, a, -1); /* Keep the props proxy alive on the adapter so the call isn't canceled. */ if (a->_props_proxy_keepalive) eldbus_proxy_unref(a->_props_proxy_keepalive); a->_props_proxy_keepalive = props; diff --git a/src/iwd/iwd_agent.c b/src/iwd/iwd_agent.c index f78c558..e7cc974 100644 --- a/src/iwd/iwd_agent.c +++ b/src/iwd/iwd_agent.c @@ -1,5 +1,7 @@ #include "iwd_agent.h" #include "iwd_dbus.h" +#include "iwd_manager.h" +#include #include #include @@ -135,11 +137,18 @@ iwd_agent_cancel(Iwd_Agent_Request *req) /* ----- Registration with iwd ------------------------------------------ */ static void -_on_register(void *data EINA_UNUSED, const Eldbus_Message *msg, +_on_register(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED) { + /* `data` is the manager — same pointer the trampoline carries. */ + Iwd_Manager *m = data; const char *en, *em; - if (eldbus_message_error_get(msg, &en, &em)) + if (!eldbus_message_error_get(msg, &en, &em)) return; + if (m) + iwd_manager_report_error(m, + "Wi-Fi agent registration refused (another agent running?): %s", + em ? em : en); + else fprintf(stderr, "e_iwd: agent register failed: %s: %s\n", en, em); } @@ -147,7 +156,7 @@ void iwd_agent_register(Iwd_Agent *a) { if (!a || !a->am_proxy) return; - eldbus_proxy_call(a->am_proxy, "RegisterAgent", _on_register, NULL, -1, + eldbus_proxy_call(a->am_proxy, "RegisterAgent", _on_register, a->data, -1, "o", IWD_AGENT_PATH); } diff --git a/src/iwd/iwd_device.c b/src/iwd/iwd_device.c index 1c05c52..b8942c9 100644 --- a/src/iwd/iwd_device.c +++ b/src/iwd/iwd_device.c @@ -3,7 +3,6 @@ #include "iwd_props.h" #include "iwd_manager.h" #include "iwd_network.h" -#include #include #include @@ -186,35 +185,72 @@ _refresh_signals(Iwd_Device *d) _on_ordered_networks, d, -1, ""); } +static void +_on_scan_reply(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED) +{ + Iwd_Device *d = data; + const char *en, *em; + if (!eldbus_message_error_get(msg, &en, &em)) return; + /* "AlreadyExists" / "InProgress" is the normal race when two scan + * triggers fire close together — don't spam the user with that. */ + if (en && (strstr(en, "InProgress") || strstr(en, "Busy") || + strstr(en, "AlreadyExists"))) + return; + if (d->manager) + iwd_manager_report_error(d->manager, "Scan failed: %s", em ? em : en); +} + void iwd_device_scan(Iwd_Device *d) { if (!d || !d->station_proxy) return; - eldbus_proxy_call(d->station_proxy, "Scan", NULL, NULL, -1, ""); + eldbus_proxy_call(d->station_proxy, "Scan", _on_scan_reply, d, -1, ""); +} + +static void +_on_disconnect_reply(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED) +{ + Iwd_Device *d = data; + const char *en, *em; + if (eldbus_message_error_get(msg, &en, &em) && d->manager) + iwd_manager_report_error(d->manager, + "Disconnect failed: %s", em ? em : en); } void iwd_device_disconnect(Iwd_Device *d) { if (!d || !d->station_proxy) return; - eldbus_proxy_call(d->station_proxy, "Disconnect", NULL, NULL, -1, ""); + eldbus_proxy_call(d->station_proxy, "Disconnect", _on_disconnect_reply, d, -1, ""); } +typedef struct +{ + Iwd_Device *d; + char *ssid; +} _Connect_Hidden_Ctx; + static void _on_connect_hidden_reply(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED) { + _Connect_Hidden_Ctx *ctx = data; const char *en, *em; - char *ssid = data; - if (eldbus_message_error_get(msg, &en, &em)) - fprintf(stderr, "e_iwd: ConnectHiddenNetwork('%s') failed: %s: %s\n", - ssid ? ssid : "?", en, em); - free(ssid); + if (eldbus_message_error_get(msg, &en, &em) && ctx->d->manager) + iwd_manager_report_error(ctx->d->manager, + "Connect to hidden '%s' failed: %s", + ctx->ssid ? ctx->ssid : "?", em ? em : en); + free(ctx->ssid); + free(ctx); } void iwd_device_connect_hidden(Iwd_Device *d, const char *ssid) { if (!d || !d->station_proxy || !ssid || !*ssid) return; + _Connect_Hidden_Ctx *ctx = calloc(1, sizeof(*ctx)); + if (!ctx) return; + ctx->d = d; + ctx->ssid = strdup(ssid); eldbus_proxy_call(d->station_proxy, "ConnectHiddenNetwork", - _on_connect_hidden_reply, strdup(ssid), -1, "s", ssid); + _on_connect_hidden_reply, ctx, -1, "s", ssid); } diff --git a/src/iwd/iwd_manager.c b/src/iwd/iwd_manager.c index 49b9576..941e907 100644 --- a/src/iwd/iwd_manager.c +++ b/src/iwd/iwd_manager.c @@ -4,6 +4,8 @@ #include "iwd_device.h" #include "iwd_network.h" #include +#include +#include #include #include @@ -23,6 +25,7 @@ struct _Iwd_Manager Eina_List *listeners; /* Listener * */ Iwd_State state; Ecore_Job *notify_job; + char *last_error; Iwd_Agent_Passphrase_Cb pass_cb; void *pass_data; @@ -102,6 +105,33 @@ iwd_manager_notify(Iwd_Manager *m) m->notify_job = ecore_job_add(_notify_job_cb, m); } +const char * +iwd_manager_last_error(const Iwd_Manager *m) { return m ? m->last_error : NULL; } + +void +iwd_manager_clear_error(Iwd_Manager *m) +{ + if (!m || !m->last_error) return; + free(m->last_error); + m->last_error = NULL; +} + +void +iwd_manager_report_error(Iwd_Manager *m, const char *fmt, ...) +{ + if (!m || !fmt) return; + char buf[256]; + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + /* stderr keeps the dev-visible trail; the stashed copy drives the UI. */ + fprintf(stderr, "e_iwd: %s\n", buf); + free(m->last_error); + m->last_error = strdup(buf); + iwd_manager_notify(m); +} + /* ----- state aggregation ---------------------------------------------- */ static void @@ -281,6 +311,7 @@ iwd_manager_free(Iwd_Manager *m) eina_hash_free(m->networks); Listener *li; EINA_LIST_FREE(m->listeners, li) free(li); + free(m->last_error); free(m); } diff --git a/src/iwd/iwd_manager.h b/src/iwd/iwd_manager.h index 900fad4..f23d3b5 100644 --- a/src/iwd/iwd_manager.h +++ b/src/iwd/iwd_manager.h @@ -36,6 +36,15 @@ void iwd_manager_listener_del (Iwd_Manager *m, Iwd_Manager_Cb cb, void *data); /* Internal: invoked by sub-objects when their state changes. */ void iwd_manager_notify (Iwd_Manager *m); +/* Latest user-facing error string, or NULL. Owned by the manager. + * Cleared on next successful state change or by iwd_manager_clear_error. */ +const char *iwd_manager_last_error (const Iwd_Manager *m); +void iwd_manager_clear_error(Iwd_Manager *m); + +/* Stash a one-shot error message and notify listeners. Used by D-Bus reply + * callbacks when iwd refuses a Connect/Forget/Set(Powered)/etc. */ +void iwd_manager_report_error(Iwd_Manager *m, const char *fmt, ...); + /* The UI installs its passphrase prompt here. The handler must * eventually call iwd_agent_reply()/iwd_agent_cancel() with the request. */ void iwd_manager_set_passphrase_handler(Iwd_Manager *m, diff --git a/src/iwd/iwd_network.c b/src/iwd/iwd_network.c index 18a84d5..29da8cd 100644 --- a/src/iwd/iwd_network.c +++ b/src/iwd/iwd_network.c @@ -2,7 +2,6 @@ #include "iwd_dbus.h" #include "iwd_props.h" #include "iwd_manager.h" -#include #include #include @@ -80,15 +79,36 @@ iwd_network_free(Iwd_Network *n) free(n); } +typedef struct +{ + Iwd_Network *n; + char *ssid; +} _Net_Reply_Ctx; + static void _on_connect_reply(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED) { + _Net_Reply_Ctx *ctx = data; const char *en, *em; - const char *ssid = data; - if (eldbus_message_error_get(msg, &en, &em)) - fprintf(stderr, "e_iwd: connect to '%s' failed: %s: %s\n", - ssid ? ssid : "?", en, em); - free(data); + if (eldbus_message_error_get(msg, &en, &em) && ctx->n->manager) + iwd_manager_report_error(ctx->n->manager, + "Connect to '%s' failed: %s", + ctx->ssid ? ctx->ssid : "?", em ? em : en); + free(ctx->ssid); + free(ctx); +} + +static void +_on_forget_reply(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED) +{ + _Net_Reply_Ctx *ctx = data; + const char *en, *em; + if (eldbus_message_error_get(msg, &en, &em) && ctx->n->manager) + iwd_manager_report_error(ctx->n->manager, + "Forget '%s' failed: %s", + ctx->ssid ? ctx->ssid : "?", em ? em : en); + free(ctx->ssid); + free(ctx); } int @@ -104,6 +124,16 @@ iwd_network_signal_tier(const Iwd_Network *n) return 1; } +static _Net_Reply_Ctx * +_reply_ctx_new(Iwd_Network *n) +{ + _Net_Reply_Ctx *ctx = calloc(1, sizeof(*ctx)); + if (!ctx) return NULL; + ctx->n = n; + ctx->ssid = n->ssid ? strdup(n->ssid) : NULL; + return ctx; +} + void iwd_network_connect(Iwd_Network *n) { @@ -111,7 +141,7 @@ iwd_network_connect(Iwd_Network *n) /* Network.Connect() takes no args; iwd will dial the registered Agent * for a passphrase if needed. */ eldbus_proxy_call(n->proxy, "Connect", _on_connect_reply, - n->ssid ? strdup(n->ssid) : NULL, -1, ""); + _reply_ctx_new(n), -1, ""); } void @@ -124,7 +154,7 @@ iwd_network_forget(Iwd_Network *n) Eldbus_Proxy *kp = eldbus_proxy_get(kobj, IWD_IFACE_KNOWN_NETWORK); if (kp) { - eldbus_proxy_call(kp, "Forget", NULL, NULL, -1, ""); + eldbus_proxy_call(kp, "Forget", _on_forget_reply, _reply_ctx_new(n), -1, ""); eldbus_proxy_unref(kp); } eldbus_object_unref(kobj);