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.

Reply via email to