diff --git a/src/e_mod_popup.c b/src/e_mod_popup.c index c8ef68a..d4abda4 100644 --- a/src/e_mod_popup.c +++ b/src/e_mod_popup.c @@ -257,13 +257,24 @@ _rebuild_list(Popup *p) memcpy(ssid_buf + copy, "\xe2\x80\xa6", 4); /* "…" + NUL */ raw_ssid = ssid_buf; } + /* Connection marker: connecting (…) and failure (!) take priority + * over Connected, which iwd may briefly assert mid-handshake. */ + const char *mark, *amark; + switch (n->conn_status) + { + case IWD_CONN_CONNECTING: mark = " …"; amark = ", connecting"; break; + case IWD_CONN_FAILED: mark = " !"; amark = ", failed"; break; + default: + mark = n->connected ? " ✔" : ""; + amark = n->connected ? ", connected" : ""; + } char label[256]; snprintf(label, sizeof(label), "%s %s%s [%s]%s", _signal_bars(iwd_network_signal_tier(n)), n->known_path ? "★ " : " ", raw_ssid, iwd_security_label(n->security), - n->connected ? " ✔" : ""); + mark); elm_object_text_set(btn, label); /* Spoken label: avoids the ▂▄▆█ glyphs and ★/✔ markers which * screen readers announce as raw Unicode. */ @@ -274,7 +285,7 @@ _rebuild_list(Popup *p) iwd_network_signal_tier(n), iwd_security_label(n->security), n->known_path ? ", saved" : "", - n->connected ? ", connected" : ""); + amark); elm_object_access_info_set(btn, access); evas_object_size_hint_weight_set(btn, EVAS_HINT_EXPAND, 0); evas_object_size_hint_align_set(btn, EVAS_HINT_FILL, 0); diff --git a/src/iwd/iwd_manager.c b/src/iwd/iwd_manager.c index ec4648e..ac853f1 100644 --- a/src/iwd/iwd_manager.c +++ b/src/iwd/iwd_manager.c @@ -245,6 +245,20 @@ _on_iface_removed(void *data, const char *path, const char *iface) { eina_hash_del(m->networks, path, NULL); } + else if (!strcmp(iface, IWD_IFACE_KNOWN_NETWORK)) + { + /* A saved network was forgotten: iwd removes the KnownNetwork object + * but the matching Network.KnownNetwork change arrives only as an + * invalidation, which our changed-only property handler drops. Clear + * the back-reference here so the ★ marker and Forget (✕) button vanish + * immediately instead of lingering until the next full repopulate. */ + Eina_Iterator *it = eina_hash_iterator_data_new(m->networks); + Iwd_Network *n; + EINA_ITERATOR_FOREACH(it, n) + if (n->known_path && !strcmp(n->known_path, path)) + { free(n->known_path); n->known_path = NULL; } + eina_iterator_free(it); + } else if (!strcmp(iface, IWD_IFACE_ADAPTER)) { eina_hash_del(m->adapters, path, NULL); diff --git a/src/iwd/iwd_network.c b/src/iwd/iwd_network.c index de83d92..b22daf3 100644 --- a/src/iwd/iwd_network.c +++ b/src/iwd/iwd_network.c @@ -22,7 +22,12 @@ _prop_cb(void *data, const char *key, Eldbus_Message_Iter *v) Iwd_Network *n = data; if (!strcmp(key, "Name")) { free(n->ssid); n->ssid = iwd_props_str_dup(v); } else if (!strcmp(key, "Type")) { char *s = iwd_props_str_dup(v); n->security = _sec_from_str(s); free(s); } - else if (!strcmp(key, "Connected")) { n->connected = iwd_props_bool(v); } + else if (!strcmp(key, "Connected")) { + n->connected = iwd_props_bool(v); + /* A confirmed connection clears any in-flight/failed marker so the + * row settles on ✔ (and a stale ! from a prior attempt disappears). */ + if (n->connected) n->conn_status = IWD_CONN_IDLE; + } else if (!strcmp(key, "Device")) { free(n->device_path); n->device_path = iwd_props_str_dup(v); } else if (!strcmp(key, "KnownNetwork")) { free(n->known_path); n->known_path = iwd_props_str_dup(v); } } @@ -79,25 +84,50 @@ iwd_network_free(Iwd_Network *n) free(n); } -/* Reply context captures the *manager* (which outlives all sub-objects) and - * a strdup'd SSID, never the Iwd_Network — the network may disappear from - * the next scan before iwd's reply lands, and a raw back-ref would UAF. */ +/* Reply context captures the *manager* (which outlives all sub-objects) plus + * a strdup'd SSID and object path, never the Iwd_Network — the network may + * disappear from the next scan before iwd's reply lands, and a raw back-ref + * would UAF. The path is re-resolved through the live hash at reply time. */ typedef struct { Iwd_Manager *m; char *ssid; + char *path; } _Net_Reply_Ctx; +static Iwd_Network * +_ctx_resolve(const _Net_Reply_Ctx *ctx) +{ + if (!ctx->m || !ctx->path) return NULL; + const Eina_Hash *h = iwd_manager_networks(ctx->m); + return h ? eina_hash_find(h, ctx->path) : NULL; +} + static void _on_connect_reply(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_UNUSED) { _Net_Reply_Ctx *ctx = data; + Iwd_Network *n = _ctx_resolve(ctx); const char *en, *em; - if (eldbus_message_error_get(msg, &en, &em) && ctx->m) - iwd_manager_report_error(ctx->m, - "Connect to '%s' failed: %s", - ctx->ssid ? ctx->ssid : "?", em ? em : en); + if (eldbus_message_error_get(msg, &en, &em)) + { + /* iwd only fails Connect() once the attempt is truly over — wrong + * passphrase, out of range, etc. Mark the row failed (!) rather than + * leaving a misleading ✔ behind. */ + if (n) n->conn_status = IWD_CONN_FAILED; + if (ctx->m) + iwd_manager_report_error(ctx->m, + "Connect to '%s' failed: %s", + ctx->ssid ? ctx->ssid : "?", em ? em : en); + } + else + { + if (n) n->conn_status = IWD_CONN_IDLE; + /* report_error notifies on the failure path; do it here for success. */ + if (ctx->m) iwd_manager_notify(ctx->m); + } free(ctx->ssid); + free(ctx->path); free(ctx); } @@ -111,6 +141,7 @@ _on_forget_reply(void *data, const Eldbus_Message *msg, Eldbus_Pending *p EINA_U "Forget '%s' failed: %s", ctx->ssid ? ctx->ssid : "?", em ? em : en); free(ctx->ssid); + free(ctx->path); free(ctx); } @@ -134,6 +165,7 @@ _reply_ctx_new(Iwd_Network *n) if (!ctx) return NULL; ctx->m = n->manager; ctx->ssid = n->ssid ? strdup(n->ssid) : NULL; + ctx->path = n->path ? strdup(n->path) : NULL; return ctx; } @@ -141,6 +173,10 @@ void iwd_network_connect(Iwd_Network *n) { if (!n || !n->proxy) return; + /* Show progress immediately and clear any prior failure marker; the reply + * (success/failure) and the Connected property settle the final state. */ + n->conn_status = IWD_CONN_CONNECTING; + if (n->manager) iwd_manager_notify(n->manager); /* 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, diff --git a/src/iwd/iwd_network.h b/src/iwd/iwd_network.h index dd7d56f..a9f4439 100644 --- a/src/iwd/iwd_network.h +++ b/src/iwd/iwd_network.h @@ -13,6 +13,16 @@ typedef enum { IWD_SEC_UNKNOWN, } Iwd_Security; +/* Connection attempt status, tracked locally from our own Connect() call so + * the row can show progress and failure. iwd's Network.Connect() only returns + * once the attempt concludes, so its reply is authoritative — Network.Connected + * may flicker true mid-handshake and must not be trusted on its own. */ +typedef enum { + IWD_CONN_IDLE, /* not attempting; ✔ iff Network.Connected */ + IWD_CONN_CONNECTING, /* Connect() in flight */ + IWD_CONN_FAILED, /* last Connect() returned an error (e.g. bad PSK) */ +} Iwd_Conn_Status; + typedef struct _Iwd_Network Iwd_Network; struct _Iwd_Network @@ -23,6 +33,7 @@ struct _Iwd_Network char *known_path; Iwd_Security security; Eina_Bool connected; + Iwd_Conn_Status conn_status; /* Signal strength in 100*dBm units (iwd convention). Valid iff have_signal. */ int16_t signal_dbm;