This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch master
in repository enlightenment.
View the commit online.
commit 369f628c153c9d397f8ec9e786aec7326ccbe8c8
Author: [email protected] <[email protected]>
AuthorDate: Tue Mar 10 22:21:52 2026 -0600
feat(networkmanager,connman): parallel active connection probing and UI improvements
This commit refactors the active connection detection and adds frequency band
labels and overlays to match popup and gadget icon consistency.
**Active Connection Probe Refactor:**
Instead of serially replacing the active connection watcher, which broke when
docker bridges appeared first in NetworkManager's ActiveConnections array, we
now probe all active connections in parallel with lightweight D-Bus GetAll
calls. Only the wifi/ethernet connection is promoted to the persistent watcher.
A probe_generation counter discards stale probes if a new batch is started
before previous probes complete, preventing race conditions.
**UI Enhancements:**
- Gadget icon now shows frequency band overlays (2.4G/5G/6G) matching popup
- Popup list updates in-place with nth_icon_set/nth_end_set instead of
clear+rebuild, reducing flicker
- 0.5s Ecore_Timer throttle coalesces rapid AP scan signals
- Security lock overlay displays for encrypted networks
- Open networks emit "security,off" signal to indicate no encryption
**Frequency Band Display:**
Both NetworkManager and ConnMan modules now parse WiFi frequency from D-Bus
and display the band label on icons (2.4G for <3GHz, 5G for 3-5.9GHz, 6G for
5.925GHz+).
**Code Review Fixes:**
- Fixed wireless_proxy object leak in NetworkManager
- Made hash swap OOM-safe in saved connections
- Filter "/" sentinel object from SpecificObject arrays
- Added NULL guard in popup delete callback
- ConnMan malloc NULL check for icon message
- ConnMan security array NULL guard in service cleanup
- Cancel popup_update_timer on popup deletion
- Clean up stale watcher on new ActiveConnections signals
- Downgrade non-critical ERR traces to DBG level
Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
src/modules/connman/E_Connman.h | 1 +
src/modules/connman/e_connman.c | 16 +-
src/modules/connman/e_mod_main.c | 28 ++-
src/modules/networkmanager/e_mod_main.c | 255 +++++++++++++++++++++-----
src/modules/networkmanager/e_mod_main.h | 1 +
src/modules/networkmanager/e_networkmanager.c | 234 +++++++++++++++--------
src/modules/networkmanager/e_networkmanager.h | 6 +
7 files changed, 412 insertions(+), 129 deletions(-)
diff --git a/src/modules/connman/E_Connman.h b/src/modules/connman/E_Connman.h
index 06d714802..081d291f7 100644
--- a/src/modules/connman/E_Connman.h
+++ b/src/modules/connman/E_Connman.h
@@ -63,6 +63,7 @@ struct Connman_Service
enum Connman_State state;
enum Connman_Service_Type type;
uint8_t strength;
+ uint16_t frequency; /* MHz, 0 if unknown */
/* Private */
struct
diff --git a/src/modules/connman/e_connman.c b/src/modules/connman/e_connman.c
index 5666063ba..d870a3693 100644
--- a/src/modules/connman/e_connman.c
+++ b/src/modules/connman/e_connman.c
@@ -189,6 +189,15 @@ static void _service_parse_prop_changed(struct Connman_Service *cs,
cs->strength = strength;
DBG("New strength: %d", strength);
}
+ else if (strcmp(prop_name, "Frequency") == 0)
+ {
+ uint16_t freq;
+ EINA_SAFETY_ON_FALSE_RETURN(eldbus_message_iter_arguments_get(value,
+ "q",
+ &freq));
+ cs->frequency = freq;
+ DBG("New frequency: %u", freq);
+ }
else if (strcmp(prop_name, "Security") == 0)
{
DBG("Old security count: %d",
@@ -340,8 +349,11 @@ static void _service_free(struct Connman_Service *cs)
}
free(cs->name);
- _eina_str_array_clean(cs->security);
- eina_array_free(cs->security);
+ if (cs->security)
+ {
+ _eina_str_array_clean(cs->security);
+ eina_array_free(cs->security);
+ }
eina_stringshare_del(cs->path);
obj = eldbus_proxy_object_get(cs->service_iface);
eldbus_proxy_unref(cs->service_iface);
diff --git a/src/modules/connman/e_mod_main.c b/src/modules/connman/e_mod_main.c
index 5dc159204..3724ef62b 100644
--- a/src/modules/connman/e_mod_main.c
+++ b/src/modules/connman/e_mod_main.c
@@ -40,12 +40,15 @@ _econnman_service_new_icon(struct Connman_Service *cs, Evas *evas)
e_theme_edje_object_set(icon, "base/theme/modules/connman", buf);
msg = malloc(sizeof(*msg) + sizeof(int));
- msg->count = 2;
- msg->val[0] = cs->state;
- msg->val[1] = cs->strength;
+ if (msg)
+ {
+ msg->count = 2;
+ msg->val[0] = cs->state;
+ msg->val[1] = cs->strength;
- edje_object_message_send(icon, EDJE_MESSAGE_INT_SET, 1, msg);
- free(msg);
+ edje_object_message_send(icon, EDJE_MESSAGE_INT_SET, 1, msg);
+ free(msg);
+ }
/* Emit security signals to show lock overlay on wifi icon */
if (cs->type == CONNMAN_SERVICE_TYPE_WIFI && cs->security)
@@ -62,6 +65,21 @@ _econnman_service_new_icon(struct Connman_Service *cs, Evas *evas)
eina_iterator_free(iter);
}
+ /* Set frequency band label */
+ if (cs->type == CONNMAN_SERVICE_TYPE_WIFI && cs->frequency > 0)
+ {
+ const char *band;
+
+ if (cs->frequency >= 5925)
+ band = "6G";
+ else if (cs->frequency >= 3000)
+ band = "5G";
+ else
+ band = "2.4G";
+
+ edje_object_part_text_set(icon, "band_label", band);
+ }
+
return icon;
}
diff --git a/src/modules/networkmanager/e_mod_main.c b/src/modules/networkmanager/e_mod_main.c
index 3502c9ffb..e34abb537 100644
--- a/src/modules/networkmanager/e_mod_main.c
+++ b/src/modules/networkmanager/e_mod_main.c
@@ -32,6 +32,7 @@ void
enm_popup_del(E_NM_Instance *inst)
{
E_FREE_FUNC(inst->popup, e_object_del);
+ E_FREE_FUNC(inst->ctxt->popup_update_timer, ecore_timer_del);
inst->ui.popup.enabled = inst->ui.popup.list = inst->ui.popup.ip_label = NULL;
}
@@ -46,6 +47,7 @@ _enm_popup_del(void *data, Evas_Object *obj EINA_UNUSED)
{
E_NM_Instance *inst = data;
+ if (!inst->popup) return;
E_FREE_FUNC(inst->popup, e_object_del);
}
@@ -150,8 +152,25 @@ _enm_ap_icon_new(struct NM_Manager *nm, struct NM_Access_Point *ap, Evas *evas)
snprintf(secbuf, sizeof(secbuf), "e,security,%s", sec);
edje_object_signal_emit(icon, secbuf, "e");
}
+ else
+ edje_object_signal_emit(icon, "e,security,off", "e");
}
+ /* Set frequency band label */
+ if (ap->frequency > 0)
+ {
+ const char *band;
+
+ if (ap->frequency >= 5925)
+ band = "6G";
+ else if (ap->frequency >= 3000)
+ band = "5G";
+ else
+ band = "2.4G";
+
+ edje_object_part_text_set(icon, "band_label", band);
+ }
+
return icon;
}
@@ -311,42 +330,38 @@ _enm_ssid_is_active(struct NM_Manager *nm, const char *ssid)
return !strcmp(active->ssid, ssid);
}
-static void
-_enm_popup_update(struct NM_Manager *nm, E_NM_Instance *inst)
+/* Build the desired list of entries. Returns count; caller frees labels/paths. */
+struct _Popup_Entry
+{
+ const char *label; /* SSID or interface name — owned by AP/dev, do not free */
+ const char *ap_path; /* stringshare AP path for wifi, NULL for ethernet */
+ struct NM_Access_Point *ap; /* NULL for ethernet */
+ struct NM_Device *dev; /* only for ethernet */
+};
+
+static int
+_enm_popup_build_entries(struct NM_Manager *nm,
+ struct _Popup_Entry *out, int max)
{
- Evas_Object *list = inst->ui.popup.list;
- Evas_Object *enabled = inst->ui.popup.enabled;
- Evas *evas = evas_object_evas_get(list);
struct NM_Device *dev;
Eina_Hash *seen_ssids;
+ int n = 0;
- EINA_SAFETY_ON_NULL_RETURN(nm);
-
- /* Refresh saved connections for forget button visibility.
- * This is async — the hash populates as D-Bus replies arrive,
- * then enm_mod_aps_changed() triggers a popup re-render.
- * Skip if a fetch is already in flight to avoid recursive storms. */
- if (nm->saved_conn_pending == 0)
- enm_saved_connections_get(nm);
-
- e_widget_ilist_freeze(list);
- e_widget_ilist_clear(list);
-
- /* Show connected ethernet devices first */
+ /* Ethernet entries first */
EINA_INLIST_FOREACH(nm->devices, dev)
{
- Evas_Object *icon;
-
if (dev->type != NM_DEVICE_TYPE_ETHERNET) continue;
- if (dev->state < 100) continue; /* not activated */
+ if (dev->state < 100) continue;
+ if (n >= max) break;
- icon = _enm_eth_icon_new(dev, evas);
- e_widget_ilist_append_full(list, icon, NULL,
- dev->interface ?: _("Wired"),
- NULL, NULL, NULL);
+ out[n].label = dev->interface ?: _("Wired");
+ out[n].ap_path = NULL;
+ out[n].ap = NULL;
+ out[n].dev = dev;
+ n++;
}
- /* Deduplicate APs by SSID — show only the strongest station per SSID */
+ /* Deduplicated WiFi entries */
seen_ssids = eina_hash_string_superfast_new(NULL);
EINA_INLIST_FOREACH(nm->devices, dev)
@@ -358,31 +373,111 @@ _enm_popup_update(struct NM_Manager *nm, E_NM_Instance *inst)
EINA_INLIST_FOREACH(dev->access_points, ap)
{
struct NM_Access_Point *best;
- Evas_Object *icon, *end;
- /* Skip hidden networks (empty or NULL SSID) */
if (!ap->ssid || !ap->ssid[0]) continue;
-
- /* Skip if we already showed this SSID */
if (eina_hash_find(seen_ssids, ap->ssid)) continue;
- /* Find the best AP for this SSID and only show that one */
best = _enm_best_ap_for_ssid(nm, ap->ssid);
if (!best) continue;
eina_hash_add(seen_ssids, ap->ssid, (void *)1);
- icon = _enm_ap_icon_new(nm, best, evas);
- end = _enm_ap_end_new(nm, best, evas);
-
- e_widget_ilist_append_full(list, icon, end,
- best->ssid,
- _enm_popup_selected_cb,
- inst, best->path);
+ if (n >= max) break;
+ out[n].label = best->ssid;
+ out[n].ap_path = best->path;
+ out[n].ap = best;
+ out[n].dev = NULL;
+ n++;
}
}
eina_hash_free(seen_ssids);
+ return n;
+}
+
+static void
+_enm_popup_update(struct NM_Manager *nm, E_NM_Instance *inst)
+{
+ Evas_Object *list = inst->ui.popup.list;
+ Evas_Object *enabled = inst->ui.popup.enabled;
+ Evas *evas = evas_object_evas_get(list);
+ struct _Popup_Entry desired[256];
+ int want_n, have_n, i;
+
+ EINA_SAFETY_ON_NULL_RETURN(nm);
+
+ want_n = _enm_popup_build_entries(nm, desired, 256);
+ have_n = e_widget_ilist_count(list);
+
+ e_widget_ilist_freeze(list);
+
+ /* Update existing rows in-place, append new rows, remove extras */
+ for (i = 0; i < want_n; i++)
+ {
+ if (i < have_n)
+ {
+ const char *cur_label = e_widget_ilist_nth_label_get(list, i);
+
+ /* If the label matches, just update icon and end widget */
+ if (cur_label && desired[i].label &&
+ !strcmp(cur_label, desired[i].label))
+ {
+ Evas_Object *icon, *end;
+
+ if (desired[i].ap)
+ {
+ icon = _enm_ap_icon_new(nm, desired[i].ap, evas);
+ end = _enm_ap_end_new(nm, desired[i].ap, evas);
+ }
+ else
+ {
+ icon = _enm_eth_icon_new(desired[i].dev, evas);
+ end = NULL;
+ }
+ e_widget_ilist_nth_icon_set(list, i, icon);
+ e_widget_ilist_nth_end_set(list, i, end);
+ continue;
+ }
+
+ /* Label mismatch — remove stale row. Decrement i so the loop
+ * re-examines this position (which now holds the next item after
+ * the shift) on the next iteration instead of appending at the
+ * wrong position. */
+ e_widget_ilist_remove_num(list, i);
+ have_n--;
+ i--;
+ continue;
+ }
+
+ /* Append new row (only reached when i >= have_n) */
+ {
+ Evas_Object *icon, *end;
+
+ if (desired[i].ap)
+ {
+ icon = _enm_ap_icon_new(nm, desired[i].ap, evas);
+ end = _enm_ap_end_new(nm, desired[i].ap, evas);
+ e_widget_ilist_append_full(list, icon, end,
+ desired[i].label,
+ _enm_popup_selected_cb,
+ inst, desired[i].ap_path);
+ }
+ else
+ {
+ icon = _enm_eth_icon_new(desired[i].dev, evas);
+ e_widget_ilist_append_full(list, icon, NULL,
+ desired[i].label,
+ NULL, NULL, NULL);
+ }
+ have_n++;
+ }
+ }
+
+ /* Remove any trailing stale rows */
+ while (have_n > want_n)
+ {
+ e_widget_ilist_remove_num(list, --have_n);
+ }
e_widget_ilist_thaw(list);
e_widget_ilist_go(list);
@@ -476,18 +571,38 @@ _enm_popup_new(E_NM_Instance *inst)
/* --- UI callbacks called from e_networkmanager.c -------------------------- */
-void
-enm_mod_aps_changed(struct NM_Manager *nm)
+static Eina_Bool
+_enm_popup_update_timer_cb(void *data)
{
- E_NM_Module_Context *ctxt = networkmanager_mod->data;
+ E_NM_Module_Context *ctxt = data;
const Eina_List *l;
E_NM_Instance *inst;
+ ctxt->popup_update_timer = NULL;
+
+ if (!ctxt->nm) return ECORE_CALLBACK_CANCEL;
+
EINA_LIST_FOREACH(ctxt->instances, l, inst)
{
if (!inst->popup) continue;
- _enm_popup_update(nm, inst);
+ _enm_popup_update(ctxt->nm, inst);
}
+
+ return ECORE_CALLBACK_CANCEL;
+}
+
+void
+enm_mod_aps_changed(struct NM_Manager *nm EINA_UNUSED)
+{
+ E_NM_Module_Context *ctxt = networkmanager_mod->data;
+
+ /* Throttle popup rebuilds: coalesce rapid AP changes into a single
+ * update after 0.5s. Without this, NM's continuous AccessPointAdded/
+ * Removed signals during scanning cause visible flicker. */
+ if (ctxt->popup_update_timer) return;
+ ctxt->popup_update_timer = ecore_timer_add(0.5,
+ _enm_popup_update_timer_cb,
+ ctxt);
}
static void
@@ -553,6 +668,10 @@ _enm_mod_manager_update_inst(E_NM_Module_Context *ctxt EINA_UNUSED,
strength = 100;
else
strength = active_ap ? active_ap->strength : 0;
+
+ DBG("gadget_update: state=%d type=%s ap=%s strength=%d active_ap=%p",
+ state, typestr, nm ? (nm->active_ap_path ?: "(null)") : "no-nm",
+ strength, active_ap);
theme_state = _enm_state_to_connman(state);
msg = malloc(sizeof(*msg) + sizeof(int));
@@ -585,6 +704,51 @@ _enm_mod_manager_update_inst(E_NM_Module_Context *ctxt EINA_UNUSED,
}
else
edje_object_part_text_set(o, "e.text.label", "");
+
+ /* Set security overlay and frequency band on gadget icon */
+ if (nm && active_ap)
+ {
+ const char *sec;
+
+ sec = enm_ap_security_to_str(active_ap->wpa_flags, active_ap->rsn_flags);
+ if (sec && strcmp(sec, "open"))
+ {
+ if (!strcmp(sec, "wpa") || !strcmp(sec, "wpa2") || !strcmp(sec, "sae"))
+ edje_object_signal_emit(o, "e,security,psk", "e");
+ else if (!strcmp(sec, "wep"))
+ edje_object_signal_emit(o, "e,security,wep", "e");
+ else if (!strcmp(sec, "802.1x"))
+ edje_object_signal_emit(o, "e,security,ieee8021x", "e");
+ else
+ {
+ snprintf(buf, sizeof(buf), "e,security,%s", sec);
+ edje_object_signal_emit(o, buf, "e");
+ }
+ }
+ else
+ edje_object_signal_emit(o, "e,security,off", "e");
+
+ if (active_ap->frequency > 0)
+ {
+ const char *band;
+
+ if (active_ap->frequency >= 5925)
+ band = "6G";
+ else if (active_ap->frequency >= 3000)
+ band = "5G";
+ else
+ band = "2.4G";
+
+ edje_object_part_text_set(o, "band_label", band);
+ }
+ else
+ edje_object_part_text_set(o, "band_label", "");
+ }
+ else
+ {
+ edje_object_signal_emit(o, "e,security,off", "e");
+ edje_object_part_text_set(o, "band_label", "");
+ }
}
void
@@ -614,7 +778,11 @@ enm_mod_manager_inout(struct NM_Manager *nm)
_enm_gadget_setup(inst);
if (ctxt->nm)
- enm_mod_manager_update(nm);
+ {
+ enm_mod_manager_update(nm);
+ /* Pre-fetch saved connections so the hash is warm when popup opens */
+ enm_saved_connections_get(nm);
+ }
}
/* --- mouse / menu --------------------------------------------------------- */
@@ -860,6 +1028,7 @@ e_modapi_shutdown(E_Module *m)
_enm_configure_registry_unregister();
e_gadcon_provider_unregister(&_gc_class);
+ E_FREE_FUNC(ctxt->popup_update_timer, ecore_timer_del);
E_FREE(ctxt);
networkmanager_mod = NULL;
diff --git a/src/modules/networkmanager/e_mod_main.h b/src/modules/networkmanager/e_mod_main.h
index d8166e186..1a64e7688 100644
--- a/src/modules/networkmanager/e_mod_main.h
+++ b/src/modules/networkmanager/e_mod_main.h
@@ -37,6 +37,7 @@ struct E_NM_Module_Context
{
Eina_List *instances;
E_Config_Dialog *conf_dialog;
+ Ecore_Timer *popup_update_timer;
struct NM_Manager *nm;
int wireless_enabled; /* int for e_widget_check bitmask */
diff --git a/src/modules/networkmanager/e_networkmanager.c b/src/modules/networkmanager/e_networkmanager.c
index 515188505..9b53e1bae 100644
--- a/src/modules/networkmanager/e_networkmanager.c
+++ b/src/modules/networkmanager/e_networkmanager.c
@@ -505,7 +505,11 @@ _device_free(struct NM_Device *dev)
free(dev->interface);
if (dev->wireless_proxy)
- eldbus_proxy_unref(dev->wireless_proxy);
+ {
+ Eldbus_Object *wobj = eldbus_proxy_object_get(dev->wireless_proxy);
+ eldbus_proxy_unref(dev->wireless_proxy);
+ if (wobj) eldbus_object_unref(wobj);
+ }
if (dev->proxy)
{
@@ -685,11 +689,6 @@ _nm_conn_type_parse(const char *type)
static void
_manager_active_conn_watch_free(struct NM_Manager *nm)
{
- if (nm->pending.active_conn)
- {
- eldbus_pending_cancel(nm->pending.active_conn);
- nm->pending.active_conn = NULL;
- }
if (nm->active_conn_proxy)
{
eldbus_proxy_unref(nm->active_conn_proxy);
@@ -730,7 +729,8 @@ _active_conn_prop_changed(void *data, const Eldbus_Message *msg)
{
DBG("ActiveConn SpecificObject changed: %s", ap_path);
eina_stringshare_del(nm->active_ap_path);
- nm->active_ap_path = eina_stringshare_add(ap_path);
+ nm->active_ap_path = (ap_path && strcmp(ap_path, "/"))
+ ? eina_stringshare_add(ap_path) : NULL;
enm_mod_manager_update(nm);
enm_mod_aps_changed(nm);
}
@@ -738,28 +738,65 @@ _active_conn_prop_changed(void *data, const Eldbus_Message *msg)
}
}
-static void
-_active_conn_get_props_cb(void *data, const Eldbus_Message *msg,
- Eldbus_Pending *pending EINA_UNUSED)
+/* ------ Active connection probe ------ */
+/* We probe each active connection with a lightweight GetAll. Only when the
+ * callback discovers a wifi/ethernet type do we "promote" that probe to the
+ * persistent watcher on NM_Manager. Others (bridge, vpn, …) are freed. */
+
+struct _Active_Conn_Probe
{
- struct NM_Manager *nm = data;
+ struct NM_Manager *nm;
+ Eldbus_Proxy *proxy;
+ Eldbus_Object *obj;
+ const char *path; /* stringshare */
+ unsigned int generation; /* snapshot of nm->probe_generation at creation */
+};
+
+static void
+_active_conn_probe_free(struct _Active_Conn_Probe *probe)
+{
+ if (!probe) return;
+ if (probe->proxy) eldbus_proxy_unref(probe->proxy);
+ if (probe->obj) eldbus_object_unref(probe->obj);
+ eina_stringshare_del(probe->path);
+ free(probe);
+}
+
+static void
+_active_conn_probe_cb(void *data, const Eldbus_Message *msg,
+ Eldbus_Pending *pending EINA_UNUSED)
+{
+ struct _Active_Conn_Probe *probe = data;
+ struct NM_Manager *nm = probe->nm;
Eldbus_Message_Iter *array, *dict;
const char *name, *text;
+ const char *ap_path = NULL, *ip4path = NULL, *type_str = NULL;
+ enum NM_Device_Type conn_type;
- nm->pending.active_conn = NULL;
+ /* Discard stale probes that were superseded by a newer batch */
+ if (probe->generation != nm->probe_generation)
+ {
+ DBG("Discarding stale probe (gen %u vs current %u) path=%s",
+ probe->generation, nm->probe_generation, probe->path ?: "?");
+ _active_conn_probe_free(probe);
+ return;
+ }
if (eldbus_message_error_get(msg, &name, &text))
{
WRN("ActiveConnection GetAll failed: %s: %s", name, text);
+ _active_conn_probe_free(probe);
return;
}
if (!eldbus_message_arguments_get(msg, "a{sv}", &array))
{
WRN("ActiveConnection GetAll: cannot parse a{sv}");
+ _active_conn_probe_free(probe);
return;
}
+ /* Parse into locals first so we can check the type before committing */
while (eldbus_message_iter_get_and_next(array, 'e', &dict))
{
Eldbus_Message_Iter *var;
@@ -769,47 +806,68 @@ _active_conn_get_props_cb(void *data, const Eldbus_Message *msg,
continue;
if (!strcmp(key, "Ip4Config"))
- {
- const char *ip4path;
- if (eldbus_message_iter_arguments_get(var, "o", &ip4path))
- _manager_watch_ip4(nm, ip4path);
- }
+ eldbus_message_iter_arguments_get(var, "o", &ip4path);
else if (!strcmp(key, "SpecificObject"))
- {
- const char *ap_path;
- if (eldbus_message_iter_arguments_get(var, "o", &ap_path))
- {
- DBG("ActiveConn SpecificObject=%s", ap_path);
- eina_stringshare_del(nm->active_ap_path);
- nm->active_ap_path = eina_stringshare_add(ap_path);
- }
- }
+ eldbus_message_iter_arguments_get(var, "o", &ap_path);
else if (!strcmp(key, "Type"))
- {
- const char *type;
- if (eldbus_message_iter_arguments_get(var, "s", &type))
- {
- nm->active_conn_type = _nm_conn_type_parse(type);
- DBG("ActiveConn Type=%s -> %d", type, nm->active_conn_type);
- }
- }
+ eldbus_message_iter_arguments_get(var, "s", &type_str);
}
- /* Proxy stays alive — updates arrive via _active_conn_prop_changed */
- DBG("ActiveConn done: type=%d active_ap=%s",
- nm->active_conn_type, nm->active_ap_path ?: "(null)");
+ conn_type = _nm_conn_type_parse(type_str);
+
+ /* Skip non-network types (bridge, vpn, etc.) — they don't have
+ * useful SpecificObject or signal strength info. */
+ if (conn_type != NM_DEVICE_TYPE_WIFI &&
+ conn_type != NM_DEVICE_TYPE_ETHERNET)
+ {
+ DBG("ActiveConn type=%s (%d) — skipping", type_str ?: "?", conn_type);
+ _active_conn_probe_free(probe);
+ return;
+ }
+
+ /* This is a useful connection — promote probe to persistent watcher */
+ DBG("ActiveConn type=%s (%d) — adopting path=%s",
+ type_str ?: "?", conn_type, probe->path);
+
+ /* Tear down any previous persistent watcher */
+ _manager_active_conn_watch_free(nm);
+ _manager_ip4_watch_free(nm);
+
+ /* Transfer ownership from probe to nm */
+ eina_stringshare_del(nm->active_connection_path);
+ nm->active_connection_path = probe->path;
+ probe->path = NULL; /* transferred */
+
+ nm->active_conn_proxy = probe->proxy;
+ nm->active_conn_obj = probe->obj;
+ probe->proxy = NULL; /* transferred */
+ probe->obj = NULL; /* transferred */
+
+ nm->active_conn_type = conn_type;
+
+ eina_stringshare_del(nm->active_ap_path);
+ nm->active_ap_path = ap_path ? eina_stringshare_add(ap_path) : NULL;
+
+ /* Subscribe to property changes on the now-persistent proxy */
+ eldbus_proxy_signal_handler_add(nm->active_conn_proxy, "PropertiesChanged",
+ _active_conn_prop_changed, nm);
+
+ if (ip4path)
+ _manager_watch_ip4(nm, ip4path);
+
+ free(probe); /* fields already transferred or NULL */
+
enm_mod_manager_update(nm);
enm_mod_aps_changed(nm);
}
static void
-_manager_watch_active_conn(struct NM_Manager *nm,
- const char *active_conn_path)
+_manager_probe_active_conn(struct NM_Manager *nm,
+ const char *active_conn_path)
{
- Eldbus_Object *obj;
- Eldbus_Proxy *props;
+ struct _Active_Conn_Probe *probe;
- DBG("_manager_watch_active_conn path=%s", active_conn_path ?: "(null)");
+ DBG("_manager_probe_active_conn path=%s", active_conn_path ?: "(null)");
if (!active_conn_path || !strcmp(active_conn_path, "/")) return;
/* Skip if already watching this exact connection */
@@ -817,27 +875,18 @@ _manager_watch_active_conn(struct NM_Manager *nm,
!strcmp(nm->active_connection_path, active_conn_path))
return;
- eina_stringshare_del(nm->active_connection_path);
- nm->active_connection_path = eina_stringshare_add(active_conn_path);
+ probe = calloc(1, sizeof(*probe));
+ if (!probe) return;
- /* Tear down previous watchers (active conn + ip4) */
- _manager_active_conn_watch_free(nm);
- _manager_ip4_watch_free(nm);
+ probe->nm = nm;
+ probe->generation = nm->probe_generation;
+ probe->path = eina_stringshare_add(active_conn_path);
+ probe->obj = eldbus_object_get(conn, NM_BUS_NAME, active_conn_path);
+ probe->proxy = eldbus_proxy_get(probe->obj, NM_IFACE_PROPS);
- obj = eldbus_object_get(conn, NM_BUS_NAME, active_conn_path);
- props = eldbus_proxy_get(obj, NM_IFACE_PROPS);
-
- nm->active_conn_proxy = props;
- nm->active_conn_obj = obj;
-
- /* Subscribe to property changes — keeps proxy alive for signals */
- eldbus_proxy_signal_handler_add(props, "PropertiesChanged",
- _active_conn_prop_changed, nm);
-
- /* Initial fetch */
- nm->pending.active_conn = eldbus_proxy_call(props, "GetAll",
- _active_conn_get_props_cb, nm,
- -1, "s", NM_IFACE_ACONN);
+ eldbus_proxy_call(probe->proxy, "GetAll",
+ _active_conn_probe_cb, probe,
+ -1, "s", NM_IFACE_ACONN);
}
/* -------------------------------------------------------------------------- */
@@ -894,14 +943,27 @@ _manager_prop_changed(void *data, const Eldbus_Message *msg)
if (!eldbus_message_iter_arguments_get(var, "ao", &conn_array))
continue;
- /* Use first active connection */
+ /* Advance generation so any in-flight probes from the previous
+ * batch are discarded in their callbacks. Then clear the old
+ * watchers so we don't display a stale connection while the new
+ * probes are in flight. */
+ nm->probe_generation++;
+ _manager_active_conn_watch_free(nm);
+ _manager_ip4_watch_free(nm);
+
+ /* Try each active connection — _active_conn_probe_cb
+ * filters out bridge/vpn types and only adopts wifi/ethernet */
if (eldbus_message_iter_get_and_next(conn_array, 'o', &aconn_path))
- _manager_watch_active_conn(nm, aconn_path);
+ {
+ do
+ {
+ _manager_probe_active_conn(nm, aconn_path);
+ }
+ while (eldbus_message_iter_get_and_next(conn_array, 'o', &aconn_path));
+ }
else
{
- /* No active connections — tear down watchers */
- _manager_active_conn_watch_free(nm);
- _manager_ip4_watch_free(nm);
+ /* No active connections — tear down already done above */
eina_stringshare_del(nm->active_ap_path);
nm->active_ap_path = NULL;
eina_stringshare_del(nm->active_connection_path);
@@ -961,6 +1023,7 @@ _manager_get_props_cb(void *data, const Eldbus_Message *msg,
{
Eldbus_Message_Iter *conn_array;
const char *aconn_path;
+ Eina_Bool found = EINA_FALSE;
if (!eldbus_message_iter_arguments_get(var, "ao", &conn_array))
{
@@ -968,12 +1031,22 @@ _manager_get_props_cb(void *data, const Eldbus_Message *msg,
continue;
}
- if (eldbus_message_iter_get_and_next(conn_array, 'o', &aconn_path))
+ /* Advance generation and clear old watchers before probing so
+ * stale probes are discarded and no stale connection is shown. */
+ nm->probe_generation++;
+ _manager_active_conn_watch_free(nm);
+ _manager_ip4_watch_free(nm);
+
+ /* Try each active connection — _active_conn_probe_cb
+ * filters out bridge/vpn types and only adopts wifi/ethernet. */
+ while (eldbus_message_iter_get_and_next(conn_array, 'o', &aconn_path))
{
- DBG("ActiveConnections: first path=%s", aconn_path);
- _manager_watch_active_conn(nm, aconn_path);
+ DBG("ActiveConnections: trying path=%s", aconn_path);
+ _manager_probe_active_conn(nm, aconn_path);
+ found = EINA_TRUE;
}
- else
+
+ if (!found)
{
DBG("ActiveConnections: empty array");
eina_stringshare_del(nm->active_ap_path);
@@ -986,6 +1059,8 @@ _manager_get_props_cb(void *data, const Eldbus_Message *msg,
}
}
+ DBG("manager_get_props done: state=%d active_ap=%s conn_type=%d",
+ nm->state, nm->active_ap_path ?: "(null)", nm->active_conn_type);
enm_mod_manager_update(nm);
}
@@ -1233,19 +1308,20 @@ enm_saved_connections_get(struct NM_Manager *nm)
{
EINA_SAFETY_ON_NULL_RETURN(nm);
- {
- /* Swap pattern: assign new hash before freeing old to avoid a window
- * where saved_connections is NULL (in-flight callbacks check it) */
- Eina_Hash *old = nm->saved_connections;
- nm->saved_connections = eina_hash_string_superfast_new(
- _saved_connections_free_cb);
- if (old) eina_hash_free(old);
- }
-
{
struct _Settings_List_Ctx *sctx = malloc(sizeof(*sctx));
EINA_SAFETY_ON_NULL_RETURN(sctx);
+ /* Swap pattern: assign new hash before freeing old to avoid a window
+ * where saved_connections is NULL (in-flight callbacks check it).
+ * Only performed after malloc succeeds so OOM cannot corrupt the hash. */
+ {
+ Eina_Hash *old = nm->saved_connections;
+ nm->saved_connections = eina_hash_string_superfast_new(
+ _saved_connections_free_cb);
+ if (old) eina_hash_free(old);
+ }
+
sctx->nm = nm;
sctx->obj = eldbus_object_get(conn, NM_BUS_NAME, NM_SETTINGS_PATH);
sctx->proxy = eldbus_proxy_get(sctx->obj, NM_IFACE_SETTINGS);
diff --git a/src/modules/networkmanager/e_networkmanager.h b/src/modules/networkmanager/e_networkmanager.h
index 7cb4b69fa..491e3a008 100644
--- a/src/modules/networkmanager/e_networkmanager.h
+++ b/src/modules/networkmanager/e_networkmanager.h
@@ -122,6 +122,12 @@ struct NM_Manager
/* Saved WiFi connections: SSID (string) -> connection D-Bus path (stringshare) */
Eina_Hash *saved_connections;
int saved_conn_pending; /* outstanding GetSettings calls */
+
+ /* Generation counter incremented each time a new batch of active-connection
+ * probes is started. Each probe captures the generation at creation time
+ * and discards its result in the callback if the generation has advanced,
+ * preventing stale probes from clobbering a newer watcher. */
+ unsigned int probe_generation;
};
/* Ecore Events */
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.