This is an automated email from the git hooks/post-receive script.

git pushed a commit to reference refs/pull/134/head
in repository enlightenment.

View the commit online.

commit 6415f2f4d52d03196666146d861044ed2b52e7c1
Author: [email protected] <[email protected]>
AuthorDate: Thu May 21 19:20:19 2026 -0600

    refactor(networkmanager): incremental popup updates (no full-clear flicker)
    
    The popup's _enm_popup_update used to elm_genlist_clear() and rebuild every
    row on each state change. Genlist is realisation-driven and that pattern
    forces it to throw away its content cache and re-realise every visible
    row, which on the user's side reads as a brief "blink" each time an AP
    appeared, signal strength ticked, or a VPN went up/down.
    
    Replace with a section-by-section diff:
    
    * Pass 1 walks the existing genlist items and indexes them by identity:
      ethernet rows by their NM_Device pointer, wifi rows by SSID stringshare,
      VPN rows by NM_VPN_Connection.path. Group headers live on three new
      fields in the popup struct (group_eth/wifi/vpn) so they survive across
      updates.
    
    * Passes 2-4 walk the desired layout in order (ethernet -> wifi -> VPN)
      and for each entry either look it up by identity (refresh per-item
      data + elm_genlist_item_update for content/text re-render) or
      elm_genlist_item_insert_after the running 'prev' anchor so newly
      appearing entries land at the correct position without disturbing
      neighbours.
    
      Wifi rows match on SSID rather than AP D-Bus path so that roaming
      the "best AP" within the same SSID does not look like a remove+add;
      id->ap and id->ap_path are refreshed in place on reuse so the icon
      and security cb pick up the new backing AP.
    
    * Pass 5 deletes any rows the desired set did not consume. itc->func.del
      still owns the Enm_Item_Data free for AP/eth rows; VPN rows keep
      func.del=NULL because the module owns NM_VPN_Connection.
    
    Group headers are created lazily and torn down when their section
    empties (no ethernet device, no wifi adapter). The wifi header is
    elm_genlist_item_update'd on reuse so its wireless-enabled toggle
    reflects manager state changes.
    
    Net effect: AP signal updates, VPN activate/deactivate, and AP
    appearance/disappearance now touch only the rows that actually
    changed. No more full repaint of the popup on every notify.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
---
 src/modules/networkmanager/e_mod_main.c | 333 +++++++++++++++++++++++++-------
 src/modules/networkmanager/e_mod_main.h |   5 +
 2 files changed, 270 insertions(+), 68 deletions(-)

diff --git a/src/modules/networkmanager/e_mod_main.c b/src/modules/networkmanager/e_mod_main.c
index b6c1737df..97517e2ae 100644
--- a/src/modules/networkmanager/e_mod_main.c
+++ b/src/modules/networkmanager/e_mod_main.c
@@ -126,6 +126,12 @@ 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.genlist = NULL;
+   /* Section-header items live inside the genlist that just died with
+    * the popup — clear the cached pointers so the next popup starts
+    * fresh in _enm_popup_update. */
+   inst->ui.popup.group_eth = NULL;
+   inst->ui.popup.group_wifi = NULL;
+   inst->ui.popup.group_vpn = NULL;
    E_FREE_FUNC(inst->ui.popup.itc_group, elm_genlist_item_class_free);
    E_FREE_FUNC(inst->ui.popup.itc_group_wifi, elm_genlist_item_class_free);
    E_FREE_FUNC(inst->ui.popup.itc_group_vpn, elm_genlist_item_class_free);
@@ -1155,119 +1161,310 @@ _enm_has_wifi_device(struct NM_Manager *nm)
    return EINA_FALSE;
 }
 
+/* Drop a tracked group-header pointer if it matches.  Used in the orphan
+ * cleanup pass so we don't leave stale Elm_Object_Item * pointers in
+ * inst->ui.popup.group_{eth,wifi,vpn}. */
+static void
+_enm_drop_group_if(E_NM_Instance *inst, Elm_Object_Item *it)
+{
+   if (inst->ui.popup.group_eth == it)  inst->ui.popup.group_eth = NULL;
+   if (inst->ui.popup.group_wifi == it) inst->ui.popup.group_wifi = NULL;
+   if (inst->ui.popup.group_vpn == it)  inst->ui.popup.group_vpn = NULL;
+}
+
+/* Diff-and-sync update of the popup genlist contents.
+ *
+ * The previous implementation called elm_genlist_clear() and re-appended
+ * every row on each call, which produced a visible blink whenever any
+ * NetworkManager state changed (new AP, signal strength tick, VPN
+ * activate/deactivate, ...).  This walker keeps existing rows in place,
+ * re-runs their text_get/content_get only when needed via
+ * elm_genlist_item_update(), and only appends/removes items at the diff
+ * boundary.
+ *
+ * Identity per row:
+ *   - ethernet: Enm_Item_Data.dev pointer (stable per device)
+ *   - wifi:     Enm_Item_Data.ssid stringshare (stable per SSID, while the
+ *               concrete "best AP" struct backing it can change across
+ *               scans — we refresh id->ap and id->ap_path on reuse)
+ *   - VPN:      NM_VPN_Connection.path stringshare
+ *   - groups:   the per-instance itc pointer (one of each per popup)
+ */
 static void
 _enm_popup_update(struct NM_Manager *nm, E_NM_Instance *inst)
 {
    Evas_Object *gl = inst->ui.popup.genlist;
    struct _Popup_Entry desired[256];
-   int want_n, i;
-   Elm_Object_Item *wifi_group = NULL, *eth_group = NULL;
-   int wifi_count = 0, eth_count = 0;
+   int want_n, i, eth_count = 0, wifi_count = 0;
+   Eina_Hash *eth_idx, *ap_idx, *vpn_idx;
+   Eina_List *orphans = NULL;
+   Elm_Object_Item *it, *prev;
+   Eina_Iterator *itr;
 
    EINA_SAFETY_ON_NULL_RETURN(nm);
    EINA_SAFETY_ON_NULL_RETURN(gl);
 
-   /* elm_genlist_clear() below frees all items — drop any pending
-    * deselect targeting an item about to be destroyed. */
-   E_FREE_FUNC(inst->ui.popup.deselect_timer, ecore_timer_del);
-   inst->ui.popup.deselect_item = NULL;
+   /* NOTE: we intentionally do NOT kill the deselect timer here.  When the
+    * user taps a row we schedule a 0.5s deselect so the highlight feels
+    * deliberate.  The VPN active-changed path runs synchronously via an
+    * Ecore_Job and would land in _enm_popup_update almost immediately —
+    * killing the timer here meant the tapped row stayed selected, and
+    * Elementary genlist doesn't refire "selected" on an already-selected
+    * item, so the user couldn't tap again to deactivate.  Instead we
+    * cancel only when the target item is actually deleted (orphan pass
+    * below). */
 
    want_n = _enm_popup_build_entries(nm, desired, 256);
-
-   /* Count per-type so we know whether to insert group headers */
    for (i = 0; i < want_n; i++)
      {
-        if (desired[i].ap)
-          wifi_count++;
-        else
-          eth_count++;
+        if (desired[i].ap) wifi_count++;
+        else               eth_count++;
      }
 
-   /* Clear and rebuild — simpler than incremental diff with grouped headers */
-   elm_genlist_clear(gl);
+   /* ------------------------------------------------------------------
+    * Pass 1: index every existing row by its identity.  Group headers
+    * already live on inst->ui.popup.group_*, so they're skipped here.
+    * ------------------------------------------------------------------ */
+   eth_idx = eina_hash_pointer_new(NULL);   /* key: NM_Device *      */
+   ap_idx  = eina_hash_string_superfast_new(NULL); /* key: ssid stringshare */
+   vpn_idx = eina_hash_string_superfast_new(NULL); /* key: vc->path        */
 
-   /* Insert ethernet group + items */
+   it = elm_genlist_first_item_get(gl);
+   while (it)
+     {
+        const Elm_Genlist_Item_Class *icl = elm_genlist_item_item_class_get(it);
+
+        if (icl == inst->ui.popup.itc_eth)
+          {
+             Enm_Item_Data *id = elm_object_item_data_get(it);
+             if (id && id->dev) eina_hash_add(eth_idx, &id->dev, it);
+          }
+        else if (icl == inst->ui.popup.itc_ap)
+          {
+             Enm_Item_Data *id = elm_object_item_data_get(it);
+             if (id && id->ssid) eina_hash_add(ap_idx, id->ssid, it);
+          }
+        else if (icl == inst->ui.popup.itc_vpn)
+          {
+             struct NM_VPN_Connection *vc = elm_object_item_data_get(it);
+             if (vc && vc->path) eina_hash_add(vpn_idx, vc->path, it);
+          }
+        it = elm_genlist_item_next_get(it);
+     }
+
+   /* ------------------------------------------------------------------
+    * Pass 2: sync ethernet section.
+    * Keep prev pointing at the just-placed item so new rows insert
+    * after their predecessor and ordering is preserved.
+    * ------------------------------------------------------------------ */
    if (eth_count > 0)
      {
-        eth_group = elm_genlist_item_append(gl, inst->ui.popup.itc_group,
-                                            (void *)_("Wired"), NULL,
-                                            ELM_GENLIST_ITEM_GROUP,
-                                            NULL, NULL);
-        elm_genlist_item_select_mode_set(eth_group,
-                                         ELM_OBJECT_SELECT_MODE_DISPLAY_ONLY);
+        if (!inst->ui.popup.group_eth)
+          {
+             inst->ui.popup.group_eth = elm_genlist_item_append(
+                gl, inst->ui.popup.itc_group, (void *)_("Wired"), NULL,
+                ELM_GENLIST_ITEM_GROUP, NULL, NULL);
+             elm_genlist_item_select_mode_set(inst->ui.popup.group_eth,
+                ELM_OBJECT_SELECT_MODE_DISPLAY_ONLY);
+          }
+        /* prev tracks the last placed real row in this section.  While it
+         * is NULL we have no real predecessor yet, so the first cache-miss
+         * insert must use elm_genlist_item_append(parent=group) — passing
+         * the group header itself as the "after" of insert_after misroutes
+         * the new item (it lands as a sibling at top level instead of as a
+         * child of the group), breaking section grouping and the pass-1
+         * index next refresh. */
+        prev = NULL;
 
         for (i = 0; i < want_n; i++)
           {
-             Enm_Item_Data *id;
-
-             if (desired[i].ap) continue; /* skip wifi entries here */
-
-             id = calloc(1, sizeof(*id));
-             if (!id) continue;
-             id->nm = nm;
-             id->ap = NULL;
-             id->dev = desired[i].dev;
-             id->ap_path = NULL;
-             id->ssid = NULL;
-
-             elm_genlist_item_append(gl, inst->ui.popup.itc_eth, id,
-                                     eth_group, ELM_GENLIST_ITEM_NONE,
-                                     NULL, NULL);
+             if (desired[i].ap) continue;
+             it = eina_hash_find(eth_idx, &desired[i].dev);
+             if (it)
+               {
+                  /* Reuse: refresh nm pointer (paranoia) and re-run cbs. */
+                  Enm_Item_Data *id = elm_object_item_data_get(it);
+                  if (id) id->nm = nm;
+                  elm_genlist_item_update(it);
+                  eina_hash_del(eth_idx, &desired[i].dev, it);
+               }
+             else
+               {
+                  Enm_Item_Data *id = calloc(1, sizeof(*id));
+                  if (!id) continue;
+                  id->nm = nm;
+                  id->dev = desired[i].dev;
+                  if (!prev)
+                    it = elm_genlist_item_append(
+                       gl, inst->ui.popup.itc_eth, id,
+                       inst->ui.popup.group_eth,
+                       ELM_GENLIST_ITEM_NONE, NULL, NULL);
+                  else
+                    it = elm_genlist_item_insert_after(
+                       gl, inst->ui.popup.itc_eth, id,
+                       inst->ui.popup.group_eth, prev,
+                       ELM_GENLIST_ITEM_NONE, NULL, NULL);
+               }
+             prev = it;
           }
      }
+   else if (inst->ui.popup.group_eth)
+     {
+        /* No ethernet rows desired and no group needed.  Any leftover
+         * eth rows are picked up by the orphan pass below. */
+        elm_object_item_del(inst->ui.popup.group_eth);
+        inst->ui.popup.group_eth = NULL;
+     }
 
-   /* Insert wifi group header (always when adapter present) + AP items */
+   /* ------------------------------------------------------------------
+    * Pass 3: sync wifi section.  Header is present whenever a wifi
+    * adapter exists, regardless of AP count.
+    * ------------------------------------------------------------------ */
    if (_enm_has_wifi_device(nm))
      {
-        wifi_group = elm_genlist_item_append(gl, inst->ui.popup.itc_group_wifi,
-                                             inst, NULL,
-                                             ELM_GENLIST_ITEM_GROUP,
-                                             NULL, NULL);
-        elm_genlist_item_select_mode_set(wifi_group,
-                                          ELM_OBJECT_SELECT_MODE_DISPLAY_ONLY);
-
-        if (wifi_count > 0)
+        if (!inst->ui.popup.group_wifi)
           {
-             for (i = 0; i < want_n; i++)
+             inst->ui.popup.group_wifi = elm_genlist_item_append(
+                gl, inst->ui.popup.itc_group_wifi, inst, NULL,
+                ELM_GENLIST_ITEM_GROUP, NULL, NULL);
+             elm_genlist_item_select_mode_set(inst->ui.popup.group_wifi,
+                ELM_OBJECT_SELECT_MODE_DISPLAY_ONLY);
+          }
+        else
+          {
+             /* Wireless-enabled toggle state may have flipped — refresh. */
+             elm_genlist_item_update(inst->ui.popup.group_wifi);
+          }
+        prev = NULL;
+
+        for (i = 0; i < want_n; i++)
+          {
+             if (!desired[i].ap) continue;
+             it = eina_hash_find(ap_idx, desired[i].label);
+             if (it)
                {
-                  Enm_Item_Data *id;
-
-                  if (!desired[i].ap) continue;
-
-                  id = calloc(1, sizeof(*id));
+                  /* Reuse: the "best AP" pointer for this SSID may have
+                   * rotated to a stronger AP, and signal strength/security
+                   * almost certainly changed — refresh the Enm_Item_Data
+                   * fields before triggering a content/text re-fetch. */
+                  Enm_Item_Data *id = elm_object_item_data_get(it);
+                  if (id)
+                    {
+                       id->nm = nm;
+                       id->ap = desired[i].ap;
+                       eina_stringshare_replace(&id->ap_path,
+                                                desired[i].ap_path);
+                    }
+                  elm_genlist_item_update(it);
+                  eina_hash_del(ap_idx, desired[i].label, it);
+               }
+             else
+               {
+                  Enm_Item_Data *id = calloc(1, sizeof(*id));
                   if (!id) continue;
                   id->nm = nm;
                   id->ap = desired[i].ap;
-                  id->dev = NULL;
                   id->ap_path = eina_stringshare_add(desired[i].ap_path);
                   id->ssid = eina_stringshare_add(desired[i].label);
-
-                  elm_genlist_item_append(gl, inst->ui.popup.itc_ap, id,
-                                          wifi_group, ELM_GENLIST_ITEM_NONE,
-                                          NULL, NULL);
+                  if (!prev)
+                    it = elm_genlist_item_append(
+                       gl, inst->ui.popup.itc_ap, id,
+                       inst->ui.popup.group_wifi,
+                       ELM_GENLIST_ITEM_NONE, NULL, NULL);
+                  else
+                    it = elm_genlist_item_insert_after(
+                       gl, inst->ui.popup.itc_ap, id,
+                       inst->ui.popup.group_wifi, prev,
+                       ELM_GENLIST_ITEM_NONE, NULL, NULL);
                }
+             prev = it;
           }
      }
+   else if (inst->ui.popup.group_wifi)
+     {
+        elm_object_item_del(inst->ui.popup.group_wifi);
+        inst->ui.popup.group_wifi = NULL;
+     }
 
-   /* VPN section — always visible even when no VPN connections exist. */
+   /* ------------------------------------------------------------------
+    * Pass 4: sync VPN section.  Always present.
+    * ------------------------------------------------------------------ */
    {
-      Elm_Object_Item *vpn_group;
       struct NM_VPN_Connection *vc;
 
-      vpn_group = elm_genlist_item_append(gl,
-           inst->ui.popup.itc_group_vpn, inst, NULL,
-           ELM_GENLIST_ITEM_GROUP, NULL, NULL);
-      elm_genlist_item_select_mode_set(vpn_group,
-           ELM_OBJECT_SELECT_MODE_DISPLAY_ONLY);
+      if (!inst->ui.popup.group_vpn)
+        {
+           inst->ui.popup.group_vpn = elm_genlist_item_append(
+              gl, inst->ui.popup.itc_group_vpn, inst, NULL,
+              ELM_GENLIST_ITEM_GROUP, NULL, NULL);
+           elm_genlist_item_select_mode_set(inst->ui.popup.group_vpn,
+              ELM_OBJECT_SELECT_MODE_DISPLAY_ONLY);
+        }
+      prev = NULL;
 
       EINA_INLIST_FOREACH(nm->vpn_connections, vc)
         {
-           elm_genlist_item_append(gl,
-               inst->ui.popup.itc_vpn, vc, vpn_group,
-               ELM_GENLIST_ITEM_NONE,
-               NULL, NULL);
+           if (!vc->path) continue;
+           it = eina_hash_find(vpn_idx, vc->path);
+           if (it)
+             {
+                elm_genlist_item_update(it);
+                eina_hash_del(vpn_idx, vc->path, it);
+             }
+           else
+             {
+                if (!prev)
+                  it = elm_genlist_item_append(
+                     gl, inst->ui.popup.itc_vpn, vc,
+                     inst->ui.popup.group_vpn,
+                     ELM_GENLIST_ITEM_NONE, NULL, NULL);
+                else
+                  it = elm_genlist_item_insert_after(
+                     gl, inst->ui.popup.itc_vpn, vc,
+                     inst->ui.popup.group_vpn, prev,
+                     ELM_GENLIST_ITEM_NONE, NULL, NULL);
+                /* New VPN row: the genlist's "realized" smart callback
+                 * will fire when the row is rendered and emit
+                 * "e,pill,show".  No extra wiring needed here. */
+             }
+           prev = it;
         }
    }
+
+   /* ------------------------------------------------------------------
+    * Pass 5: any items left in the indexes are orphans — desired set
+    * no longer contains them, so delete.  Their itc->func.del takes
+    * care of freeing Enm_Item_Data; VPN rows have del=NULL because the
+    * module owns NM_VPN_Connection lifetime.
+    * ------------------------------------------------------------------ */
+   itr = eina_hash_iterator_data_new(eth_idx);
+   EINA_ITERATOR_FOREACH(itr, it) orphans = eina_list_append(orphans, it);
+   eina_iterator_free(itr);
+
+   itr = eina_hash_iterator_data_new(ap_idx);
+   EINA_ITERATOR_FOREACH(itr, it) orphans = eina_list_append(orphans, it);
+   eina_iterator_free(itr);
+
+   itr = eina_hash_iterator_data_new(vpn_idx);
+   EINA_ITERATOR_FOREACH(itr, it) orphans = eina_list_append(orphans, it);
+   eina_iterator_free(itr);
+
+   EINA_LIST_FREE(orphans, it)
+     {
+        _enm_drop_group_if(inst, it); /* defensive — shouldn't be a group */
+        /* If the row being deleted is the pending deselect target,
+         * cancel the timer so it doesn't fire on a dangling pointer. */
+        if (inst->ui.popup.deselect_item == it)
+          {
+             E_FREE_FUNC(inst->ui.popup.deselect_timer, ecore_timer_del);
+             inst->ui.popup.deselect_item = NULL;
+          }
+        elm_object_item_del(it);
+     }
+
+   eina_hash_free(eth_idx);
+   eina_hash_free(ap_idx);
+   eina_hash_free(vpn_idx);
 }
 
 /* Wrap content in an elm_table with a transparent sizer rect so the whole
diff --git a/src/modules/networkmanager/e_mod_main.h b/src/modules/networkmanager/e_mod_main.h
index 8408f4c46..1a0ca106c 100644
--- a/src/modules/networkmanager/e_mod_main.h
+++ b/src/modules/networkmanager/e_mod_main.h
@@ -45,6 +45,11 @@ struct E_NM_Instance
              Elm_Genlist_Item_Class *itc_vpn;         /* VPN connection row */
              Ecore_Timer            *deselect_timer;
              Elm_Object_Item        *deselect_item;
+             /* Section-header items, tracked across incremental updates so we
+              * can lazily create/delete them without touching surrounding rows. */
+             Elm_Object_Item        *group_eth;
+             Elm_Object_Item        *group_wifi;
+             Elm_Object_Item        *group_vpn;
           } popup;
      } ui;
 };

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to