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 5386560460256c71ff0993dd6ebab18f931c8622
Author: [email protected] <[email protected]>
AuthorDate: Wed Apr 15 09:31:20 2026 -0600

    refactor(networkmanager): add module callbacks and D-Bus agent abstraction, implement threaded traffic polling
    
    Addresses three layering and architecture concerns raised in PR #125 review:
    
    1. Module-update callback indirection: Added E_NM_Mod_Callbacks struct
       with aps_changed/manager_update/manager_inout callbacks. The module
       registers callbacks at init via e_nm_module_callbacks_set(), replacing
       ~20 direct enm_mod_* call sites with inline trampolines.
    
    2. Agent D-Bus abstraction: Moved all eldbus code from agent.c into
       e_networkmanager.c internals. New public API (E_NM_Agent_Request,
       E_NM_Agent_Callbacks, e_nm_agent_callbacks_set, reply functions)
       decouples UI from D-Bus. agent.c is now pure UI layer (~310 lines)
       with zero eldbus dependencies; module init calls enm_agent_ui_register.
    
    3. Threaded sysfs polling: Replaced E_NM_Module_Context traffic_timer
       with traffic_thread. Worker loop reads /sys/class/net/<iface>/statistics
       at 100ms intervals (interruptible 500ms sleeps), stores sample in
       lock-protected slot, notifies main loop via ecore_thread_feedback.
       Handles iface changes and stop+restart races via careful cleanup.
    
    Build verified: zero warnings on ninja -C build src/modules/networkmanager.so
    
    Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
---
 src/modules/networkmanager/agent.c            | 448 +++++++-------------------
 src/modules/networkmanager/e_mod_main.c       | 181 +++++++++--
 src/modules/networkmanager/e_mod_main.h       |  13 +-
 src/modules/networkmanager/e_networkmanager.c | 394 ++++++++++++++++++++--
 src/modules/networkmanager/e_networkmanager.h |  59 +++-
 5 files changed, 694 insertions(+), 401 deletions(-)

diff --git a/src/modules/networkmanager/agent.c b/src/modules/networkmanager/agent.c
index 24d3470cd..3ac69dfc3 100644
--- a/src/modules/networkmanager/agent.c
+++ b/src/modules/networkmanager/agent.c
@@ -10,15 +10,17 @@
 #include "e_networkmanager.h"
 #include "e_mod_main.h"
 
-#define NM_AGENT_IFACE    "org.freedesktop.NetworkManager.SecretAgent"
-#define NM_AGENT_MGR_IFACE "org.freedesktop.NetworkManager.AgentManager"
-#define NM_AGENT_MGR_PATH  "/org/freedesktop/NetworkManager/AgentManager"
-#define NM_AGENT_ID       "org.enlightenment.NetworkManager"
-#define AGENT_KEY         "agent"
+/*
+ * SecretAgent UI layer.
+ *
+ * All NM D-Bus SecretAgent handling lives in e_networkmanager.c.  This file
+ * is the user-facing dialog that pops when NM asks for a WiFi password, and
+ * nothing more.  The bridge between the two layers is the pair of callbacks
+ * registered via e_nm_agent_callbacks_set() from enm_agent_ui_register().
+ */
 
-/* Internal input struct for one dialog field */
+/* One input field rendered inside the password page */
 typedef struct _E_NM_Agent_Input E_NM_Agent_Input;
-
 struct _E_NM_Agent_Input
 {
    char *key;
@@ -26,94 +28,63 @@ struct _E_NM_Agent_Input
    int   show_password;
 };
 
-struct _E_NM_Agent
+/* State for one live dialog.  Owned by the dialog; freed in the del cb. */
+typedef struct _E_NM_Agent_Dialog E_NM_Agent_Dialog;
+struct _E_NM_Agent_Dialog
 {
-   E_Dialog                 *dialog;
-   Eldbus_Service_Interface *iface;
-   Eldbus_Message           *msg;
-   Eldbus_Connection        *conn;
-   Eina_Bool                 canceled E_BITFIELD;
+   E_Dialog           *dialog;
+   E_NM_Agent_Request *req;  /* borrowed pointer — NULL after reply/cancel */
 };
 
-/* -------------------------------------------------------------------------- */
-/* D-Bus reply helpers                                                         */
-/* -------------------------------------------------------------------------- */
-
-static void
-_dict_append_basic(Eldbus_Message_Iter *array, const char *key, void *val)
-{
-   Eldbus_Message_Iter *dict, *variant;
-
-   eldbus_message_iter_arguments_append(array, "{sv}", &dict);
-   eldbus_message_iter_basic_append(dict, 's', key);
-   variant = eldbus_message_iter_container_new(dict, 'v', "s");
-   eldbus_message_iter_basic_append(variant, 's', val ?: "");
-   eldbus_message_iter_container_close(dict, variant);
-   eldbus_message_iter_container_close(array, dict);
-}
+static E_NM_Agent_Dialog *_current_dialog = NULL;
 
 /* -------------------------------------------------------------------------- */
 /* Dialog callbacks                                                            */
 /* -------------------------------------------------------------------------- */
 
+static const char *
+_dialog_first_psk(E_Dialog *dialog)
+{
+   Evas_Object *toolbook, *list;
+   Eina_List *input_list;
+   E_NM_Agent_Input *input;
+
+   toolbook = dialog->content_object;
+   list = evas_object_data_get(toolbook, "psk");
+   if (!list) list = evas_object_data_get(toolbook, "password");
+   if (!list) return NULL;
+
+   input_list = evas_object_data_get(list, "input_list");
+   if (!input_list) return NULL;
+   input = eina_list_data_get(input_list);
+   return input ? input->value : NULL;
+}
+
 static void
 _dialog_ok_cb(void *data, E_Dialog *dialog)
 {
-   E_NM_Agent *agent = data;
-   E_NM_Agent_Input *input;
-   Evas_Object *toolbook, *list;
-   Eldbus_Message_Iter *iter, *outer_array, *inner_dict, *inner_array;
-   Eina_List *input_list, *l;
-   Eldbus_Message *reply;
+   E_NM_Agent_Dialog *ad = data;
+   const char *psk;
 
-   toolbook = agent->dialog->content_object;
-
-   list = evas_object_data_get(toolbook, "psk");
-   if (!list)
+   psk = _dialog_first_psk(dialog);
+   if (ad->req)
      {
-        list = evas_object_data_get(toolbook, "password");
-        if (!list)
-          {
-             ERR("Couldn't get user input.");
-             e_object_del(E_OBJECT(dialog));
-             return;
-          }
+        e_nm_agent_reply_secrets(ad->req, psk);
+        ad->req = NULL;
      }
-
-   agent->canceled = EINA_FALSE;
-   input_list = evas_object_data_get(list, "input_list");
-
-   /*
-    * GetSecrets reply format:
-    *   a{sa{sv}}
-    *   { "802-11-wireless-security": { "psk": <value> } }
-    */
-   reply = eldbus_message_method_return_new(agent->msg);
-   iter  = eldbus_message_iter_get(reply);
-   eldbus_message_iter_arguments_append(iter, "a{sa{sv}}", &outer_array);
-
-   eldbus_message_iter_arguments_append(outer_array, "{sa{sv}}", &inner_dict);
-   eldbus_message_iter_basic_append(inner_dict, 's',
-                                    "802-11-wireless-security");
-   eldbus_message_iter_arguments_append(inner_dict, "a{sv}", &inner_array);
-
-   EINA_LIST_FOREACH(input_list, l, input)
-     _dict_append_basic(inner_array, input->key, input->value);
-
-   eldbus_message_iter_container_close(inner_dict, inner_array);
-   eldbus_message_iter_container_close(outer_array, inner_dict);
-   eldbus_message_iter_container_close(iter, outer_array);
-
-   eldbus_connection_send(agent->conn, reply, NULL, NULL, -1);
-
    e_object_del(E_OBJECT(dialog));
 }
 
 static void
 _dialog_cancel_cb(void *data, E_Dialog *dialog)
 {
-   E_NM_Agent *agent = data;
-   agent->canceled = EINA_TRUE;
+   E_NM_Agent_Dialog *ad = data;
+
+   if (ad->req)
+     {
+        e_nm_agent_reply_cancel(ad->req);
+        ad->req = NULL;
+     }
    e_object_del(E_OBJECT(dialog));
 }
 
@@ -122,38 +93,29 @@ _dialog_key_down_cb(void *data, Evas *e EINA_UNUSED,
                     Evas_Object *o EINA_UNUSED, void *event)
 {
    Evas_Event_Key_Down *ev = event;
-   E_NM_Agent *agent = data;
+   E_NM_Agent_Dialog *ad = data;
 
    if (!strcmp(ev->key, "Return"))
-     _dialog_ok_cb(agent, agent->dialog);
+     _dialog_ok_cb(ad, ad->dialog);
    else if (!strcmp(ev->key, "Escape"))
-     _dialog_cancel_cb(agent, agent->dialog);
-}
-
-static void
-_dialog_send_cancel(E_NM_Agent *agent)
-{
-   Eldbus_Message *reply;
-
-   reply = eldbus_message_error_new(agent->msg,
-                                    "org.freedesktop.NetworkManager."
-                                    "SecretAgent.UserCanceled",
-                                    "User canceled password dialog");
-   eldbus_connection_send(agent->conn, reply, NULL, NULL, -1);
+     _dialog_cancel_cb(ad, ad->dialog);
 }
 
 static void
 _dialog_del_cb(void *data)
 {
    E_Dialog *dialog = data;
-   E_NM_Agent *agent = e_object_data_get(E_OBJECT(dialog));
+   E_NM_Agent_Dialog *ad = e_object_data_get(E_OBJECT(dialog));
 
-   if (agent->canceled)
-     _dialog_send_cancel(agent);
-
-   eldbus_message_unref(agent->msg);
-   agent->msg    = NULL;
-   agent->dialog = NULL;
+   /* If the dialog was closed via the WM (not via OK/Cancel buttons) the
+    * request is still live — treat as user cancel. */
+   if (ad->req)
+     {
+        e_nm_agent_reply_cancel(ad->req);
+        ad->req = NULL;
+     }
+   if (_current_dialog == ad) _current_dialog = NULL;
+   free(ad);
 }
 
 static void
@@ -201,8 +163,8 @@ _dialog_psk_add(E_Dialog *dialog, const char *ssid)
    evas     = evas_object_evas_get(dialog->win);
    toolbook = dialog->content_object;
 
-   input       = E_NEW(E_NM_Agent_Input, 1);
-   input->key  = strdup("psk");
+   input      = E_NEW(E_NM_Agent_Input, 1);
+   input->key = strdup("psk");
    entry = e_widget_entry_add(dialog->win, &(input->value),
                               NULL, NULL, NULL);
    evas_object_show(entry);
@@ -244,9 +206,10 @@ _dialog_psk_add(E_Dialog *dialog, const char *ssid)
    e_util_win_auto_resize_fill(dialog->win);
 }
 
-static E_Dialog *
-_dialog_new(E_NM_Agent *agent, const char *ssid)
+static E_NM_Agent_Dialog *
+_dialog_new(E_NM_Agent_Request *req, const char *ssid)
 {
+   E_NM_Agent_Dialog *ad;
    Evas_Object *toolbook;
    E_Dialog    *dialog;
    int          mw, mh;
@@ -254,13 +217,16 @@ _dialog_new(E_NM_Agent *agent, const char *ssid)
    dialog = e_dialog_new(NULL, "E", "nm_secret_agent");
    if (!dialog) return NULL;
 
+   ad = E_NEW(E_NM_Agent_Dialog, 1);
+   ad->dialog = dialog;
+   ad->req    = req;
+
    e_dialog_resizable_set(dialog, 1);
    e_dialog_title_set(dialog, _("WiFi Password Required"));
    e_dialog_border_icon_set(dialog, "dialog-password");
 
-   e_dialog_button_add(dialog, _("Connect"), NULL, _dialog_ok_cb, agent);
-   e_dialog_button_add(dialog, _("Cancel"),  NULL, _dialog_cancel_cb, agent);
-   agent->canceled = EINA_TRUE; /* closing window acts as cancel */
+   e_dialog_button_add(dialog, _("Connect"), NULL, _dialog_ok_cb, ad);
+   e_dialog_button_add(dialog, _("Cancel"),  NULL, _dialog_cancel_cb, ad);
 
    toolbook = e_widget_toolbook_add(
                 evas_object_evas_get(dialog->win),
@@ -274,263 +240,67 @@ _dialog_new(E_NM_Agent *agent, const char *ssid)
    e_dialog_show(dialog);
 
    evas_object_event_callback_add(dialog->bg_object, EVAS_CALLBACK_KEY_DOWN,
-                                  _dialog_key_down_cb, agent);
+                                  _dialog_key_down_cb, ad);
    e_object_del_attach_func_set(E_OBJECT(dialog), _dialog_del_cb);
-   e_object_data_set(E_OBJECT(dialog), agent);
+   e_object_data_set(E_OBJECT(dialog), ad);
    e_dialog_button_focus_num(dialog, 0);
    elm_win_center(dialog->win, 1, 1);
 
    _dialog_psk_add(dialog, ssid);
 
-   return dialog;
+   return ad;
 }
 
 /* -------------------------------------------------------------------------- */
-/* SecretAgent D-Bus method handlers                                           */
-/* -------------------------------------------------------------------------- */
-
-static Eldbus_Message *
-_agent_get_secrets(const Eldbus_Service_Interface *iface,
-                   const Eldbus_Message *msg)
-{
-   E_NM_Agent *agent;
-   Eldbus_Message_Iter *conn_props, *conn_dict, *hints;
-   const char *conn_path, *setting_name;
-   uint32_t flags;
-   char ssid[64] = "";
-
-   agent = eldbus_service_object_data_get(iface, AGENT_KEY);
-
-   /*
-    * GetSecrets(a{sa{sv}} connection, o connection_path,
-    *            s setting_name, as hints, u flags)
-    */
-   if (!eldbus_message_arguments_get(msg, "a{sa{sv}}osasu",
-                                     &conn_props, &conn_path,
-                                     &setting_name, &hints, &flags))
-     {
-        WRN("GetSecrets: cannot parse arguments");
-        return eldbus_message_method_return_new(msg);
-     }
-
-   DBG("GetSecrets for %s setting=%s flags=%u", conn_path, setting_name, flags);
-
-   /* Try to extract SSID from connection properties */
-   while (eldbus_message_iter_get_and_next(conn_props, 'e', &conn_dict))
-     {
-        Eldbus_Message_Iter *inner;
-        const char *sect;
-
-        if (!eldbus_message_iter_arguments_get(conn_dict, "sa{sv}", &sect,
-                                               &inner))
-          continue;
-
-        if (!strcmp(sect, "802-11-wireless"))
-          {
-             Eldbus_Message_Iter *entry, *evar;
-             const char *ekey;
-
-             while (eldbus_message_iter_get_and_next(inner, 'e', &entry))
-               {
-                  if (!eldbus_message_iter_arguments_get(entry, "sv", &ekey,
-                                                         &evar))
-                    continue;
-                  if (!strcmp(ekey, "ssid"))
-                    {
-                       Eldbus_Message_Iter *bytes;
-                       unsigned char b;
-                       size_t pos = 0;
-
-                       if (eldbus_message_iter_arguments_get(evar, "ay",
-                                                             &bytes))
-                         while (eldbus_message_iter_get_and_next(bytes, 'y',
-                                                                 &b) &&
-                                pos < sizeof(ssid) - 1)
-                           ssid[pos++] = (char)b;
-                       ssid[pos] = '\0';
-                    }
-               }
-          }
-     }
-
-   /* Discard any previous pending request */
-   if (agent->msg) eldbus_message_unref(agent->msg);
-   agent->msg = eldbus_message_ref((Eldbus_Message *)msg);
-
-   if (agent->dialog)
-     e_object_del(E_OBJECT(agent->dialog));
-
-   agent->dialog = _dialog_new(agent, ssid[0] ? ssid : NULL);
-   if (!agent->dialog)
-     {
-        eldbus_message_unref(agent->msg);
-        agent->msg = NULL;
-        return eldbus_message_error_new(msg,
-                 "org.freedesktop.NetworkManager.SecretAgent.InternalError",
-                 "Failed to create password dialog");
-     }
-
-   /* Return NULL — reply will be sent asynchronously from _dialog_ok_cb */
-   return NULL;
-}
-
-static Eldbus_Message *
-_agent_cancel_get_secrets(const Eldbus_Service_Interface *iface,
-                          const Eldbus_Message *msg)
-{
-   E_NM_Agent *agent;
-
-   DBG("CancelGetSecrets");
-
-   agent = eldbus_service_object_data_get(iface, AGENT_KEY);
-   if (agent && agent->dialog)
-     {
-        agent->canceled = EINA_FALSE; /* don't send error reply on del */
-        e_object_del(E_OBJECT(agent->dialog));
-     }
-
-   return eldbus_message_method_return_new(msg);
-}
-
-static Eldbus_Message *
-_agent_save_secrets(const Eldbus_Service_Interface *iface EINA_UNUSED,
-                    const Eldbus_Message *msg)
-{
-   /* no-op */
-   return eldbus_message_method_return_new(msg);
-}
-
-static Eldbus_Message *
-_agent_delete_secrets(const Eldbus_Service_Interface *iface EINA_UNUSED,
-                      const Eldbus_Message *msg)
-{
-   /* no-op */
-   return eldbus_message_method_return_new(msg);
-}
-
-/* -------------------------------------------------------------------------- */
-/* Interface descriptor                                                        */
-/* -------------------------------------------------------------------------- */
-
-static const Eldbus_Method _agent_methods[] = {
-   {
-    "GetSecrets",
-    ELDBUS_ARGS({"a{sa{sv}}", "connection"}, {"o", "connection_path"},
-                {"s", "setting_name"}, {"as", "hints"}, {"u", "flags"}),
-    ELDBUS_ARGS({"a{sa{sv}}", "secrets"}),
-    _agent_get_secrets, 0
-   },
-   {
-    "CancelGetSecrets",
-    ELDBUS_ARGS({"o", "connection_path"}, {"s", "setting_name"}),
-    NULL,
-    _agent_cancel_get_secrets, 0
-   },
-   {
-    "SaveSecrets",
-    ELDBUS_ARGS({"a{sa{sv}}", "connection"}, {"o", "connection_path"}),
-    NULL,
-    _agent_save_secrets, 0
-   },
-   {
-    "DeleteSecrets",
-    ELDBUS_ARGS({"a{sa{sv}}", "connection"}, {"o", "connection_path"}),
-    NULL,
-    _agent_delete_secrets, 0
-   },
-   { NULL, NULL, NULL, NULL, 0 }
-};
-
-static const Eldbus_Service_Interface_Desc _agent_desc = {
-   NM_AGENT_IFACE, _agent_methods, NULL, NULL, NULL, NULL
-};
-
-/* -------------------------------------------------------------------------- */
-/* AgentManager registration                                                   */
+/* Agent UI callback bridge                                                    */
 /* -------------------------------------------------------------------------- */
 
 static void
-_agent_register_cb(void *data EINA_UNUSED, const Eldbus_Message *msg,
-                   Eldbus_Pending *pending EINA_UNUSED)
+_agent_ui_request_cb(void *data EINA_UNUSED, E_NM_Agent_Request *req,
+                     const char *ssid)
 {
-   const char *name, *text;
+   /* Only one dialog at a time — drop any stale one.  The data layer
+    * already freed the previous request when it arrived, so just tear
+    * down the widgets here. */
+   if (_current_dialog)
+     {
+        _current_dialog->req = NULL;   /* don't reply — request is gone */
+        e_object_del(E_OBJECT(_current_dialog->dialog));
+        _current_dialog = NULL;
+     }
 
-   if (eldbus_message_error_get(msg, &name, &text))
-     WRN("SecretAgent Register failed: %s: %s", name, text);
-   else
-     INF("SecretAgent registered with NetworkManager");
+   _current_dialog = _dialog_new(req, ssid);
+   if (!_current_dialog)
+     {
+        ERR("Failed to create SecretAgent dialog");
+        e_nm_agent_reply_cancel(req);
+        return;
+     }
 }
 
 static void
-_agent_register(E_NM_Agent *agent)
+_agent_ui_cancel_cb(void *data EINA_UNUSED,
+                    E_NM_Agent_Request *req EINA_UNUSED)
 {
-   Eldbus_Object *obj;
-   Eldbus_Proxy  *proxy;
-
-   obj   = eldbus_object_get(agent->conn,
-                              "org.freedesktop.NetworkManager",
-                              NM_AGENT_MGR_PATH);
-   proxy = eldbus_proxy_get(obj, NM_AGENT_MGR_IFACE);
-
-   eldbus_proxy_call(proxy, "Register", _agent_register_cb, NULL, -1,
-                     "s", NM_AGENT_ID);
-
-   /* Unref immediately; the pending call keeps them alive */
-   eldbus_proxy_unref(proxy);
-   eldbus_object_unref(obj);
-}
-
-/* -------------------------------------------------------------------------- */
-/* Public lifecycle                                                            */
-/* -------------------------------------------------------------------------- */
-
-E_NM_Agent *
-enm_agent_new(Eldbus_Connection *eldbus_conn)
-{
-   Eldbus_Service_Interface *iface;
-   E_NM_Agent *agent;
-
-   agent = E_NEW(E_NM_Agent, 1);
-   EINA_SAFETY_ON_NULL_RETURN_VAL(agent, NULL);
-
-   iface = eldbus_service_interface_register(eldbus_conn, AGENT_PATH,
-                                              &_agent_desc);
-   if (!iface)
+   /* NM is withdrawing the pending request.  Dismiss the dialog without
+    * sending any reply — the data layer will free the request after this
+    * callback returns. */
+   if (_current_dialog)
      {
-        ERR("Failed to register SecretAgent D-Bus interface");
-        free(agent);
-        return NULL;
+        _current_dialog->req = NULL;
+        e_object_del(E_OBJECT(_current_dialog->dialog));
+        _current_dialog = NULL;
      }
-
-   eldbus_service_object_data_set(iface, AGENT_KEY, agent);
-
-   agent->iface = iface;
-   agent->conn  = eldbus_conn;
-
-   _agent_register(agent);
-
-   return agent;
 }
 
+static const E_NM_Agent_Callbacks _ui_cbs =
+{
+   .request = _agent_ui_request_cb,
+   .cancel  = _agent_ui_cancel_cb,
+};
+
 void
-enm_agent_del(E_NM_Agent *agent)
+enm_agent_ui_register(void)
 {
-   EINA_SAFETY_ON_NULL_RETURN(agent);
-
-   if (agent->msg)
-     {
-        eldbus_message_unref(agent->msg);
-        agent->msg = NULL;
-     }
-
-   if (agent->dialog)
-     {
-        agent->canceled = EINA_FALSE; /* suppress cancel reply on del */
-        e_object_del(E_OBJECT(agent->dialog));
-        agent->dialog = NULL;
-     }
-
-   eldbus_service_object_unregister(agent->iface);
-   agent->iface = NULL;
-   free(agent);
+   e_nm_agent_callbacks_set(&_ui_cbs, NULL);
 }
diff --git a/src/modules/networkmanager/e_mod_main.c b/src/modules/networkmanager/e_mod_main.c
index d9dcea655..00b1cb007 100644
--- a/src/modules/networkmanager/e_mod_main.c
+++ b/src/modules/networkmanager/e_mod_main.c
@@ -875,8 +875,8 @@ _enm_popup_update_timer_cb(void *data)
    return ECORE_CALLBACK_CANCEL;
 }
 
-void
-enm_mod_aps_changed(struct NM_Manager *nm EINA_UNUSED)
+static void
+_enm_mod_aps_changed(struct NM_Manager *nm EINA_UNUSED)
 {
    E_NM_Module_Context *ctxt = networkmanager_mod->data;
 
@@ -1057,8 +1057,8 @@ _enm_mod_manager_update_inst(E_NM_Module_Context *ctxt EINA_UNUSED,
      }
 }
 
-void
-enm_mod_manager_update(struct NM_Manager *nm)
+static void
+_enm_mod_manager_update(struct NM_Manager *nm)
 {
    E_NM_Module_Context *ctxt = networkmanager_mod->data;
    E_NM_Instance *inst;
@@ -1078,8 +1078,8 @@ enm_mod_manager_update(struct NM_Manager *nm)
      _enm_traffic_timer_stop(ctxt);
 }
 
-void
-enm_mod_manager_inout(struct NM_Manager *nm)
+static void
+_enm_mod_manager_inout(struct NM_Manager *nm)
 {
    E_NM_Module_Context *ctxt = networkmanager_mod->data;
    const Eina_List *l;
@@ -1093,7 +1093,7 @@ enm_mod_manager_inout(struct NM_Manager *nm)
 
    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);
      }
@@ -1101,6 +1101,13 @@ enm_mod_manager_inout(struct NM_Manager *nm)
      _enm_traffic_timer_stop(ctxt);
 }
 
+static const E_NM_Mod_Callbacks _enm_mod_cbs =
+{
+   .aps_changed    = _enm_mod_aps_changed,
+   .manager_update = _enm_mod_manager_update,
+   .manager_inout  = _enm_mod_manager_inout,
+};
+
 /* --- network activity indicator ------------------------------------------- */
 
 /* Find the interface name for the active network connection */
@@ -1210,33 +1217,92 @@ _enm_traffic_signal_emit(E_NM_Module_Context *ctxt, int rx_level, int tx_level)
      }
 }
 
-static Eina_Bool
-_enm_traffic_poll_cb(void *data)
+/*
+ * Threaded traffic monitor.
+ *
+ * The worker struct holds a single-slot sample protected by a lock — the
+ * worker writes rx/tx under the lock and signals main via ecore_thread_feedback;
+ * the notify cb reads the slot under the lock.  No per-sample allocation, so
+ * there is nothing to leak if ecore_thread_cancel drops in-flight feedback
+ * messages.
+ *
+ * Cleanup is handled in the shared done path invoked from both the normal
+ * end callback and the cancel callback.  The `ctxt->traffic_thread == thread`
+ * check makes the done path robust against a stop+restart race: after
+ * stop() cancels the old thread, ctxt->traffic_thread is set to NULL and
+ * then reassigned to the new thread, so the old thread's delayed done cb
+ * finds a pointer that is either NULL or the new thread and leaves it alone.
+ */
+typedef struct _Enm_Traffic_Worker
 {
-   E_NM_Module_Context *ctxt = data;
-   const char *iface;
-   unsigned long long rx = 0, tx = 0;
+   E_NM_Module_Context *ctxt;
+   char                *iface;   /* strdup; worker-only read */
+   Eina_Lock            lock;
+   unsigned long long   rx;
+   unsigned long long   tx;
+   Eina_Bool            have_sample;
+} Enm_Traffic_Worker;
+
+static void
+_enm_traffic_worker_heavy(void *data, Ecore_Thread *thread)
+{
+   Enm_Traffic_Worker *w = data;
+
+   while (!ecore_thread_check(thread))
+     {
+        unsigned long long rx = 0, tx = 0;
+        int i;
+
+        /* ~0.5s tick split into 100ms chunks so cancel is responsive */
+        for (i = 0; i < 5; i++)
+          {
+             if (ecore_thread_check(thread)) return;
+             usleep(100 * 1000);
+          }
+
+        if (!_enm_read_sysfs_counter(w->iface, "rx_bytes", &rx) ||
+            !_enm_read_sysfs_counter(w->iface, "tx_bytes", &tx))
+          continue;
+
+        eina_lock_take(&w->lock);
+        w->rx = rx;
+        w->tx = tx;
+        w->have_sample = EINA_TRUE;
+        eina_lock_release(&w->lock);
+
+        /* msg_data is the worker itself — feedback carries no allocation */
+        ecore_thread_feedback(thread, w);
+     }
+}
+
+static void
+_enm_traffic_worker_notify(void *data, Ecore_Thread *thread EINA_UNUSED,
+                           void *msg_data EINA_UNUSED)
+{
+   Enm_Traffic_Worker *w = data;
+   E_NM_Module_Context *ctxt = w->ctxt;
+   unsigned long long rx, tx;
    int rx_level, tx_level;
+   Eina_Bool have;
 
-   if (!ctxt->nm) return ECORE_CALLBACK_RENEW;
-   if (ctxt->nm->state < NM_STATE_CONNECTED_LOCAL) return ECORE_CALLBACK_RENEW;
+   eina_lock_take(&w->lock);
+   have = w->have_sample;
+   rx = w->rx;
+   tx = w->tx;
+   w->have_sample = EINA_FALSE;
+   eina_lock_release(&w->lock);
 
-   iface = _enm_active_interface(ctxt->nm);
-   if (!iface) return ECORE_CALLBACK_RENEW;
+   if (!have) return;
 
-   if (!_enm_read_sysfs_counter(iface, "rx_bytes", &rx) ||
-       !_enm_read_sysfs_counter(iface, "tx_bytes", &tx))
-     return ECORE_CALLBACK_RENEW;
-
-   /* First poll: seed the counters */
+   /* First sample seeds the counters — no rate yet */
    if (ctxt->prev_rx == 0 && ctxt->prev_tx == 0)
      {
         ctxt->prev_rx = rx;
         ctxt->prev_tx = tx;
-        return ECORE_CALLBACK_RENEW;
+        return;
      }
 
-   /* Poll interval is 0.5s, so bytes_per_sec = delta * 2 */
+   /* Poll interval ~0.5s, so bytes_per_sec = delta * 2 */
    rx_level = _enm_traffic_level((rx - ctxt->prev_rx) * 2, ctxt->rx_level);
    tx_level = _enm_traffic_level((tx - ctxt->prev_tx) * 2, ctxt->tx_level);
    ctxt->prev_rx = rx;
@@ -1248,29 +1314,83 @@ _enm_traffic_poll_cb(void *data)
         ctxt->rx_level = rx_level;
         ctxt->tx_level = tx_level;
      }
+}
 
-   return ECORE_CALLBACK_RENEW;
+static void
+_enm_traffic_worker_done(void *data, Ecore_Thread *thread)
+{
+   Enm_Traffic_Worker *w = data;
+
+   /* Only clear ctxt->traffic_thread if it still points at us.  A stop()
+    * immediately followed by start() will have NULL'd and then replaced the
+    * pointer before this done cb runs for the cancelled predecessor. */
+   if (w->ctxt->traffic_thread == thread) w->ctxt->traffic_thread = NULL;
+   eina_lock_free(&w->lock);
+   free(w->iface);
+   free(w);
+}
+
+static void
+_enm_traffic_worker_cancel(void *data, Ecore_Thread *thread)
+{
+   _enm_traffic_worker_done(data, thread);
 }
 
 static void
 _enm_traffic_timer_start(E_NM_Module_Context *ctxt)
 {
-   if (ctxt->traffic_timer) return;
+   const char *iface;
+   Enm_Traffic_Worker *w;
+
    if (ctxt->powersave_high) return;
    if (!ctxt->nm) return;
    if (ctxt->nm->state < NM_STATE_CONNECTED_LOCAL) return;
 
+   iface = _enm_active_interface(ctxt->nm);
+   if (!iface) return;
+
+   /* If a worker is already running for the same iface, nothing to do */
+   if (ctxt->traffic_thread && ctxt->traffic_iface &&
+       !strcmp(ctxt->traffic_iface, iface))
+     return;
+
+   /* Iface changed (or first start): tear down any previous worker */
+   if (ctxt->traffic_thread)
+     {
+        ecore_thread_cancel(ctxt->traffic_thread);
+        ctxt->traffic_thread = NULL;
+     }
+   free(ctxt->traffic_iface);
+   ctxt->traffic_iface = strdup(iface);
+
    ctxt->prev_rx = 0;
    ctxt->prev_tx = 0;
    ctxt->rx_level = 0;
    ctxt->tx_level = 0;
-   ctxt->traffic_timer = ecore_timer_add(0.5, _enm_traffic_poll_cb, ctxt);
+
+   w = E_NEW(Enm_Traffic_Worker, 1);
+   w->ctxt  = ctxt;
+   w->iface = strdup(iface);
+   eina_lock_new(&w->lock);
+   ctxt->traffic_thread =
+      ecore_thread_feedback_run(_enm_traffic_worker_heavy,
+                                _enm_traffic_worker_notify,
+                                _enm_traffic_worker_done,
+                                _enm_traffic_worker_cancel,
+                                w, EINA_TRUE);
 }
 
 static void
 _enm_traffic_timer_stop(E_NM_Module_Context *ctxt)
 {
-   E_FREE_FUNC(ctxt->traffic_timer, ecore_timer_del);
+   if (ctxt->traffic_thread)
+     {
+        ecore_thread_cancel(ctxt->traffic_thread);
+        ctxt->traffic_thread = NULL;
+     }
+   free(ctxt->traffic_iface);
+   ctxt->traffic_iface = NULL;
+
    if (ctxt->rx_level || ctxt->tx_level)
      {
         _enm_traffic_signal_emit(ctxt, 0, 0);
@@ -1495,6 +1615,9 @@ e_modapi_init(E_Module *m)
    c = eldbus_connection_get(ELDBUS_CONNECTION_TYPE_SYSTEM);
    if (!c) goto error_dbus_bus_get;
 
+   e_nm_module_callbacks_set(&_enm_mod_cbs);
+   enm_agent_ui_register();
+
    if (!e_nm_system_init(c)) goto error_nm_system_init;
 
    ctxt->conf_dialog = NULL;
@@ -1544,13 +1667,15 @@ e_modapi_shutdown(E_Module *m)
    if (!ctxt) return 0;
 
    e_nm_system_shutdown();
+   e_nm_module_callbacks_set(NULL);
+   e_nm_agent_callbacks_set(NULL, NULL);
 
    _enm_instances_free(ctxt);
    _enm_configure_registry_unregister();
    e_gadcon_provider_unregister(&_gc_class);
 
    E_FREE_FUNC(ctxt->popup_update_timer, ecore_timer_del);
-   E_FREE_FUNC(ctxt->traffic_timer, ecore_timer_del);
+   _enm_traffic_timer_stop(ctxt);
    E_FREE_FUNC(ctxt->powersave_handler, ecore_event_handler_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 ab41faa41..2367e0f2e 100644
--- a/src/modules/networkmanager/e_mod_main.h
+++ b/src/modules/networkmanager/e_mod_main.h
@@ -46,12 +46,15 @@ struct E_NM_Module_Context
 
    struct NM_Manager  *nm;
 
-   /* Network activity indicator */
-   Ecore_Timer        *traffic_timer;
+   /* Network activity indicator.  Polling happens on a background thread
+    * via ecore_thread_feedback_run; samples are delivered back to the main
+    * loop which computes traffic levels and emits edje signals. */
+   Ecore_Thread        *traffic_thread;
+   char                *traffic_iface;   /* strdup of iface thread is reading */
    Ecore_Event_Handler *powersave_handler;
    unsigned long long   prev_rx;
    unsigned long long   prev_tx;
-   int                  rx_level; /* 0=idle, 1=low, 2=medium, 3=high */
+   int                  rx_level;        /* 0=idle, 1=low, 2=medium, 3=high */
    int                  tx_level;
    Eina_Bool            powersave_high : 1;
 };
@@ -65,8 +68,8 @@ void        enm_popup_del(E_NM_Instance *inst);
 void        enm_mod_aps_update_now(void);
 const char *e_nm_theme_path(void);
 
-E_NM_Agent *enm_agent_new(Eldbus_Connection *eldbus_conn) EINA_ARG_NONNULL(1);
-void        enm_agent_del(E_NM_Agent *agent);
+/* Register the password-dialog UI callbacks with the agent subsystem. */
+void        enm_agent_ui_register(void);
 
 /**
  * @addtogroup Optional_Devices
diff --git a/src/modules/networkmanager/e_networkmanager.c b/src/modules/networkmanager/e_networkmanager.c
index 1a5bd13a8..8589d8e2f 100644
--- a/src/modules/networkmanager/e_networkmanager.c
+++ b/src/modules/networkmanager/e_networkmanager.c
@@ -39,6 +39,40 @@ static Ecore_Event_Handler *resume_handler;
 E_API int E_NM_EVENT_MANAGER_IN;
 E_API int E_NM_EVENT_MANAGER_OUT;
 
+/* -------------------------------------------------------------------------- */
+/* Module callback indirection                                                 */
+/*                                                                             */
+/* The data layer never calls the UI layer directly; it invokes the callbacks */
+/* registered by the module at init time.  This keeps e_networkmanager.c free */
+/* of cross-file symbol references to e_mod_main.c.                           */
+/* -------------------------------------------------------------------------- */
+
+static const E_NM_Mod_Callbacks *_mod_cbs = NULL;
+
+void
+e_nm_module_callbacks_set(const E_NM_Mod_Callbacks *cbs)
+{
+   _mod_cbs = cbs;
+}
+
+static inline void
+_notify_aps_changed(struct NM_Manager *nm)
+{
+   if (_mod_cbs && _mod_cbs->aps_changed) _mod_cbs->aps_changed(nm);
+}
+
+static inline void
+_notify_manager_update(struct NM_Manager *nm)
+{
+   if (_mod_cbs && _mod_cbs->manager_update) _mod_cbs->manager_update(nm);
+}
+
+static inline void
+_notify_manager_inout(struct NM_Manager *nm)
+{
+   if (_mod_cbs && _mod_cbs->manager_inout) _mod_cbs->manager_inout(nm);
+}
+
 /* -------------------------------------------------------------------------- */
 /* Utility                                                                     */
 /* -------------------------------------------------------------------------- */
@@ -243,7 +277,7 @@ _ap_prop_changed(void *data, const Eldbus_Message *msg)
           }
      }
 
-   enm_mod_manager_update(nm_manager);
+   _notify_manager_update(nm_manager);
 }
 
 static struct NM_Access_Point *
@@ -320,7 +354,7 @@ _device_get_aps_cb(void *data, const Eldbus_Message *msg,
         DBG("Added AP %s to device %s", ap_path, dev->path);
      }
 
-   enm_mod_aps_changed(nm_manager);
+   _notify_aps_changed(nm_manager);
 }
 
 static void
@@ -349,7 +383,7 @@ _device_ap_added(void *data, const Eldbus_Message *msg)
    if (!ap) return;
    dev->access_points = eina_inlist_append(dev->access_points,
                                            EINA_INLIST_GET(ap));
-   enm_mod_aps_changed(nm_manager);
+   _notify_aps_changed(nm_manager);
 }
 
 static void
@@ -375,7 +409,7 @@ _device_ap_removed(void *data, const Eldbus_Message *msg)
           break;
        }
    eina_stringshare_del(shared);
-   enm_mod_aps_changed(nm_manager);
+   _notify_aps_changed(nm_manager);
 }
 
 static void
@@ -401,7 +435,7 @@ _device_prop_changed(void *data, const Eldbus_Message *msg)
                {
                   dev->state = state;
                   DBG("Device %s state -> %u", dev->path, state);
-                  enm_mod_manager_update(nm_manager);
+                  _notify_manager_update(nm_manager);
                }
           }
      }
@@ -555,7 +589,7 @@ _device_get_props_cb(void *data, const Eldbus_Message *msg,
         if (dev->ip4_path)
           _manager_watch_ip4(nm_manager, dev->ip4_path);
 
-        enm_mod_manager_update(nm_manager);
+        _notify_manager_update(nm_manager);
         /* Saved connections are WiFi-specific — skip for Ethernet */
      }
 }
@@ -643,7 +677,7 @@ _device_wifi_props_cb(void *data, const Eldbus_Message *msg,
    if (dev->ip4_path)
      _manager_watch_ip4(nm_manager, dev->ip4_path);
 
-   enm_mod_manager_update(nm_manager);
+   _notify_manager_update(nm_manager);
    enm_saved_connections_get(nm_manager);
 }
 
@@ -816,7 +850,7 @@ _ip4_prop_changed(void *data, const Eldbus_Message *msg)
         if (!strcmp(key, "AddressData"))
           {
              _ip4_parse_address_data(nm, var);
-             enm_mod_manager_update(nm);
+             _notify_manager_update(nm);
           }
      }
 }
@@ -853,7 +887,7 @@ _ip4config_get_props_cb(void *data, const Eldbus_Message *msg,
      }
 
    /* Proxy stays alive — updates arrive via _ip4_prop_changed signal */
-   enm_mod_manager_update(nm);
+   _notify_manager_update(nm);
 }
 
 static void
@@ -956,8 +990,8 @@ _active_conn_prop_changed(void *data, const Eldbus_Message *msg)
                   eina_stringshare_replace(&nm->active_ap_path,
                                            (ap_path && strcmp(ap_path, "/"))
                                            ? ap_path : NULL);
-                  enm_mod_manager_update(nm);
-                  enm_mod_aps_changed(nm);
+                  _notify_manager_update(nm);
+                  _notify_aps_changed(nm);
                }
           }
      }
@@ -1085,8 +1119,8 @@ _active_conn_probe_cb(void *data, const Eldbus_Message *msg,
 
    free(probe); /* fields already transferred or NULL */
 
-   enm_mod_manager_update(nm);
-   enm_mod_aps_changed(nm);
+   _notify_manager_update(nm);
+   _notify_aps_changed(nm);
 }
 
 static void
@@ -1152,7 +1186,7 @@ _manager_prop_changed(void *data, const Eldbus_Message *msg)
                   nm->state = (enum NM_State)state;
                   DBG("NM state changed: %u (%s)", state,
                       enm_state_to_str(nm->state));
-                  enm_mod_manager_update(nm);
+                  _notify_manager_update(nm);
                }
           }
         else if (!strcmp(key, "WirelessEnabled"))
@@ -1161,7 +1195,7 @@ _manager_prop_changed(void *data, const Eldbus_Message *msg)
              if (eldbus_message_iter_arguments_get(var, "b", &enabled))
                {
                   nm->wireless_enabled = enabled;
-                  enm_mod_manager_update(nm);
+                  _notify_manager_update(nm);
                }
           }
         else if (!strcmp(key, "ActiveConnections"))
@@ -1198,7 +1232,7 @@ _manager_prop_changed(void *data, const Eldbus_Message *msg)
                   free(nm->ip_address);
                   nm->ip_address = NULL;
                   nm->active_conn_type = NM_DEVICE_TYPE_UNKNOWN;
-                  enm_mod_manager_update(nm);
+                  _notify_manager_update(nm);
                }
           }
      }
@@ -1273,8 +1307,8 @@ _manager_get_props_cb(void *data, const Eldbus_Message *msg,
 
    /* Always update the UI now so that disconnected/VPN-only/unassociated
     * states are shown immediately.  Device callbacks will call
-    * enm_mod_manager_update again once they have full type and AP info. */
-   enm_mod_manager_update(nm);
+    * _notify_manager_update again once they have full type and AP info. */
+   _notify_manager_update(nm);
 }
 
 static void
@@ -1456,7 +1490,7 @@ done:
    if (ctx->nm->saved_conn_pending > 0)
      ctx->nm->saved_conn_pending--;
    if (ctx->nm->saved_conn_pending == 0)
-     enm_mod_aps_changed(ctx->nm);
+     _notify_aps_changed(ctx->nm);
    free(ctx);
 }
 
@@ -1528,7 +1562,7 @@ _saved_conn_list_cb(void *data, const Eldbus_Message *msg,
 
    /* If no connections found, trigger refresh immediately */
    if (nm->saved_conn_pending == 0)
-     enm_mod_aps_changed(nm);
+     _notify_aps_changed(nm);
 }
 
 static void
@@ -1996,7 +2030,7 @@ _e_nm_system_name_owner_exit(Eina_Bool shutdown)
 {
    if (!nm_manager) return;
 
-   enm_mod_manager_inout(NULL);
+   _notify_manager_inout(NULL);
    _manager_free(nm_manager);
    nm_manager = NULL;
 
@@ -2034,7 +2068,7 @@ _e_nm_system_name_owner_enter(const char *owner EINA_UNUSED)
      }
    nm_manager = _manager_new();
    ecore_event_add(E_NM_EVENT_MANAGER_IN, NULL, NULL, NULL);
-   enm_mod_manager_inout(nm_manager);
+   _notify_manager_inout(nm_manager);
 }
 
 static void
@@ -2067,6 +2101,318 @@ _e_nm_sys_resume_cb(void *data EINA_UNUSED, int type EINA_UNUSED, void *event EI
    return ECORE_CALLBACK_PASS_ON;
 }
 
+/* ========================================================================== */
+/* NetworkManager SecretAgent — D-Bus server side                             */
+/* ==========================================================================
+ *
+ * The data layer owns the NM SecretAgent D-Bus contract (interface register,
+ * method dispatch, reply construction).  The UI layer (agent.c) registers a
+ * pair of callbacks via e_nm_agent_callbacks_set() and replies to requests
+ * via e_nm_agent_reply_secrets() / e_nm_agent_reply_cancel().
+ */
+
+#define NM_AGENT_IFACE    "org.freedesktop.NetworkManager.SecretAgent"
+#define NM_AGENT_MGR_PATH "/org/freedesktop/NetworkManager/AgentManager"
+#define NM_AGENT_ID       "org.enlightenment.NetworkManager"
+#define AGENT_DATA_KEY    "agent"
+
+struct _E_NM_Agent
+{
+   Eldbus_Service_Interface *iface;
+   Eldbus_Connection        *eldbus_conn;
+   E_NM_Agent_Request       *pending;   /* at most one outstanding request */
+};
+
+struct _E_NM_Agent_Request
+{
+   E_NM_Agent     *agent;
+   Eldbus_Message *msg;                 /* ref held while request is live */
+};
+
+static E_NM_Agent_Callbacks _agent_cbs;
+static void                *_agent_cb_data;
+
+void
+e_nm_agent_callbacks_set(const E_NM_Agent_Callbacks *cbs, void *data)
+{
+   if (cbs) _agent_cbs = *cbs;
+   else memset(&_agent_cbs, 0, sizeof(_agent_cbs));
+   _agent_cb_data = data;
+}
+
+static void
+_agent_request_free(E_NM_Agent_Request *req)
+{
+   if (!req) return;
+   if (req->agent && req->agent->pending == req)
+     req->agent->pending = NULL;
+   if (req->msg) eldbus_message_unref(req->msg);
+   free(req);
+}
+
+static void
+_agent_dict_append_str(Eldbus_Message_Iter *array, const char *key,
+                       const char *val)
+{
+   Eldbus_Message_Iter *dict, *variant;
+
+   eldbus_message_iter_arguments_append(array, "{sv}", &dict);
+   eldbus_message_iter_basic_append(dict, 's', key);
+   variant = eldbus_message_iter_container_new(dict, 'v', "s");
+   eldbus_message_iter_basic_append(variant, 's', val ?: "");
+   eldbus_message_iter_container_close(dict, variant);
+   eldbus_message_iter_container_close(array, dict);
+}
+
+void
+e_nm_agent_reply_secrets(E_NM_Agent_Request *req, const char *psk)
+{
+   Eldbus_Message_Iter *iter, *outer, *inner_dict, *inner_array;
+   Eldbus_Message *reply;
+
+   if (!req) return;
+   if (!req->msg) { _agent_request_free(req); return; }
+
+   /*
+    * GetSecrets reply format: a{sa{sv}}
+    *   { "802-11-wireless-security": { "psk": <value> } }
+    */
+   reply = eldbus_message_method_return_new(req->msg);
+   iter  = eldbus_message_iter_get(reply);
+   eldbus_message_iter_arguments_append(iter, "a{sa{sv}}", &outer);
+   eldbus_message_iter_arguments_append(outer, "{sa{sv}}", &inner_dict);
+   eldbus_message_iter_basic_append(inner_dict, 's',
+                                    "802-11-wireless-security");
+   eldbus_message_iter_arguments_append(inner_dict, "a{sv}", &inner_array);
+   _agent_dict_append_str(inner_array, "psk", psk);
+   eldbus_message_iter_container_close(inner_dict, inner_array);
+   eldbus_message_iter_container_close(outer, inner_dict);
+   eldbus_message_iter_container_close(iter, outer);
+
+   eldbus_connection_send(req->agent->eldbus_conn, reply, NULL, NULL, -1);
+
+   _agent_request_free(req);
+}
+
+void
+e_nm_agent_reply_cancel(E_NM_Agent_Request *req)
+{
+   Eldbus_Message *reply;
+
+   if (!req) return;
+   if (!req->msg) { _agent_request_free(req); return; }
+
+   reply = eldbus_message_error_new(req->msg,
+            "org.freedesktop.NetworkManager.SecretAgent.UserCanceled",
+            "User canceled password dialog");
+   eldbus_connection_send(req->agent->eldbus_conn, reply, NULL, NULL, -1);
+
+   _agent_request_free(req);
+}
+
+static void
+_agent_ssid_extract(Eldbus_Message_Iter *conn_props, char *ssid, size_t max)
+{
+   Eldbus_Message_Iter *conn_dict;
+
+   while (eldbus_message_iter_get_and_next(conn_props, 'e', &conn_dict))
+     {
+        Eldbus_Message_Iter *inner, *entry, *evar, *bytes;
+        const char *sect, *ekey;
+        unsigned char b;
+        size_t pos = 0;
+
+        if (!eldbus_message_iter_arguments_get(conn_dict, "sa{sv}",
+                                               &sect, &inner))
+          continue;
+        if (strcmp(sect, "802-11-wireless")) continue;
+
+        while (eldbus_message_iter_get_and_next(inner, 'e', &entry))
+          {
+             if (!eldbus_message_iter_arguments_get(entry, "sv",
+                                                    &ekey, &evar))
+               continue;
+             if (strcmp(ekey, "ssid")) continue;
+             if (!eldbus_message_iter_arguments_get(evar, "ay", &bytes))
+               continue;
+             while (eldbus_message_iter_get_and_next(bytes, 'y', &b) &&
+                    pos < max - 1)
+               ssid[pos++] = (char)b;
+             ssid[pos] = '\0';
+             return;
+          }
+     }
+}
+
+static Eldbus_Message *
+_agent_get_secrets(const Eldbus_Service_Interface *iface,
+                   const Eldbus_Message *msg)
+{
+   E_NM_Agent *a;
+   E_NM_Agent_Request *req;
+   Eldbus_Message_Iter *conn_props, *hints;
+   const char *conn_path, *setting_name;
+   uint32_t flags;
+   char ssid[64] = "";
+
+   a = eldbus_service_object_data_get(iface, AGENT_DATA_KEY);
+
+   if (!eldbus_message_arguments_get(msg, "a{sa{sv}}osasu",
+                                     &conn_props, &conn_path,
+                                     &setting_name, &hints, &flags))
+     {
+        WRN("GetSecrets: cannot parse arguments");
+        return eldbus_message_method_return_new(msg);
+     }
+
+   DBG("GetSecrets for %s setting=%s flags=%u",
+       conn_path, setting_name, flags);
+
+   _agent_ssid_extract(conn_props, ssid, sizeof(ssid));
+
+   /* Drop any previous outstanding request — one dialog at a time */
+   if (a->pending) _agent_request_free(a->pending);
+
+   req = E_NEW(E_NM_Agent_Request, 1);
+   req->agent = a;
+   req->msg   = eldbus_message_ref((Eldbus_Message *)msg);
+   a->pending = req;
+
+   if (_agent_cbs.request)
+     _agent_cbs.request(_agent_cb_data, req, ssid[0] ? ssid : NULL);
+   else
+     {
+        WRN("No SecretAgent UI callback registered; cancelling request");
+        e_nm_agent_reply_cancel(req);
+     }
+
+   return NULL;  /* reply sent asynchronously */
+}
+
+static Eldbus_Message *
+_agent_cancel_get_secrets(const Eldbus_Service_Interface *iface,
+                          const Eldbus_Message *msg)
+{
+   E_NM_Agent *a;
+
+   DBG("CancelGetSecrets");
+
+   a = eldbus_service_object_data_get(iface, AGENT_DATA_KEY);
+   if (a && a->pending)
+     {
+        E_NM_Agent_Request *req = a->pending;
+        /* NM is withdrawing — UI should dismiss the dialog silently; no
+         * reply should be sent, NM is no longer waiting for one. */
+        if (_agent_cbs.cancel)
+          _agent_cbs.cancel(_agent_cb_data, req);
+        _agent_request_free(req);
+     }
+
+   return eldbus_message_method_return_new(msg);
+}
+
+static Eldbus_Message *
+_agent_save_secrets(const Eldbus_Service_Interface *iface EINA_UNUSED,
+                    const Eldbus_Message *msg)
+{
+   return eldbus_message_method_return_new(msg);
+}
+
+static Eldbus_Message *
+_agent_delete_secrets(const Eldbus_Service_Interface *iface EINA_UNUSED,
+                      const Eldbus_Message *msg)
+{
+   return eldbus_message_method_return_new(msg);
+}
+
+static const Eldbus_Method _agent_methods[] = {
+   {
+    "GetSecrets",
+    ELDBUS_ARGS({"a{sa{sv}}", "connection"}, {"o", "connection_path"},
+                {"s", "setting_name"}, {"as", "hints"}, {"u", "flags"}),
+    ELDBUS_ARGS({"a{sa{sv}}", "secrets"}),
+    _agent_get_secrets, 0
+   },
+   {
+    "CancelGetSecrets",
+    ELDBUS_ARGS({"o", "connection_path"}, {"s", "setting_name"}),
+    NULL,
+    _agent_cancel_get_secrets, 0
+   },
+   {
+    "SaveSecrets",
+    ELDBUS_ARGS({"a{sa{sv}}", "connection"}, {"o", "connection_path"}),
+    NULL,
+    _agent_save_secrets, 0
+   },
+   {
+    "DeleteSecrets",
+    ELDBUS_ARGS({"a{sa{sv}}", "connection"}, {"o", "connection_path"}),
+    NULL,
+    _agent_delete_secrets, 0
+   },
+   { NULL, NULL, NULL, NULL, 0 }
+};
+
+static const Eldbus_Service_Interface_Desc _agent_desc = {
+   NM_AGENT_IFACE, _agent_methods, NULL, NULL, NULL, NULL
+};
+
+static void
+_agent_register_cb(void *data EINA_UNUSED, const Eldbus_Message *msg,
+                   Eldbus_Pending *pending EINA_UNUSED)
+{
+   const char *name, *text;
+
+   if (eldbus_message_error_get(msg, &name, &text))
+     WRN("SecretAgent Register failed: %s: %s", name, text);
+   else
+     INF("SecretAgent registered with NetworkManager");
+}
+
+static E_NM_Agent *
+_e_nm_agent_new(Eldbus_Connection *eldbus_conn)
+{
+   Eldbus_Service_Interface *iface;
+   Eldbus_Object *obj;
+   Eldbus_Proxy  *proxy;
+   E_NM_Agent    *a;
+
+   a = E_NEW(E_NM_Agent, 1);
+   EINA_SAFETY_ON_NULL_RETURN_VAL(a, NULL);
+
+   iface = eldbus_service_interface_register(eldbus_conn, AGENT_PATH,
+                                              &_agent_desc);
+   if (!iface)
+     {
+        ERR("Failed to register SecretAgent D-Bus interface");
+        free(a);
+        return NULL;
+     }
+
+   eldbus_service_object_data_set(iface, AGENT_DATA_KEY, a);
+   a->iface       = iface;
+   a->eldbus_conn = eldbus_conn;
+
+   obj   = eldbus_object_get(a->eldbus_conn, NM_BUS_NAME, NM_AGENT_MGR_PATH);
+   proxy = eldbus_proxy_get(obj, NM_IFACE_AGENT_MGR);
+   eldbus_proxy_call(proxy, "Register", _agent_register_cb, NULL, -1,
+                     "s", NM_AGENT_ID);
+   eldbus_proxy_unref(proxy);
+   eldbus_object_unref(obj);
+
+   return a;
+}
+
+static void
+_e_nm_agent_del(E_NM_Agent *a)
+{
+   if (!a) return;
+   if (a->pending) _agent_request_free(a->pending);
+   if (a->iface) eldbus_service_object_unregister(a->iface);
+   free(a);
+}
+
 /* -------------------------------------------------------------------------- */
 /* Public lifecycle                                                            */
 /* -------------------------------------------------------------------------- */
@@ -2084,7 +2430,7 @@ e_nm_system_init(Eldbus_Connection *eldbus_conn)
    eldbus_name_owner_changed_callback_add(conn, NM_BUS_NAME,
                                           _e_nm_system_name_owner_changed,
                                           NULL, EINA_TRUE);
-   agent = enm_agent_new(eldbus_conn);
+   agent = _e_nm_agent_new(eldbus_conn);
 
    suspend_handler = ecore_event_handler_add(E_EVENT_SYS_SUSPEND,
                                              _e_nm_sys_suspend_cb, NULL);
@@ -2119,7 +2465,7 @@ e_nm_system_shutdown(void)
                                           NULL);
    _e_nm_system_name_owner_exit(EINA_TRUE);
 
-   if (agent) enm_agent_del(agent);
+   if (agent) _e_nm_agent_del(agent);
    if (conn) eldbus_connection_unref(conn);
 
    agent = NULL;
diff --git a/src/modules/networkmanager/e_networkmanager.h b/src/modules/networkmanager/e_networkmanager.h
index c08ad3f08..e7cfc05f0 100644
--- a/src/modules/networkmanager/e_networkmanager.h
+++ b/src/modules/networkmanager/e_networkmanager.h
@@ -4,7 +4,43 @@
 #include "e.h"
 #include <Eldbus.h>
 
-typedef struct _E_NM_Agent E_NM_Agent;
+typedef struct _E_NM_Agent         E_NM_Agent;
+typedef struct _E_NM_Agent_Request E_NM_Agent_Request;
+
+/*
+ * SecretAgent UI callbacks.
+ *
+ * The data layer (e_networkmanager.c) owns the NM SecretAgent D-Bus
+ * contract: it registers the interface, dispatches methods, and builds
+ * replies.  When NM asks for a secret it invokes `request` with the
+ * extracted SSID; the UI pops whatever widgets it likes and then calls
+ * e_nm_agent_reply_secrets() with the collected PSK, or
+ * e_nm_agent_reply_cancel() if the user declined.
+ *
+ * `cancel` is invoked when NM withdraws a pending GetSecrets request
+ * (e.g. connection attempt aborted).  The UI should dismiss its dialog
+ * WITHOUT calling any reply function — NM is no longer waiting.
+ *
+ * Contract for `cancel`: the callback MUST NOT store the `req` pointer
+ * and MUST NOT call e_nm_agent_reply_*.  After the callback returns the
+ * data layer frees the request; any subsequent access is a use-after-free.
+ */
+typedef void (*E_NM_Agent_Secrets_Request_Cb)(void *data,
+                                               E_NM_Agent_Request *req,
+                                               const char *ssid);
+typedef void (*E_NM_Agent_Secrets_Cancel_Cb)(void *data,
+                                              E_NM_Agent_Request *req);
+
+typedef struct _E_NM_Agent_Callbacks E_NM_Agent_Callbacks;
+struct _E_NM_Agent_Callbacks
+{
+   E_NM_Agent_Secrets_Request_Cb request;
+   E_NM_Agent_Secrets_Cancel_Cb  cancel;
+};
+
+void e_nm_agent_callbacks_set(const E_NM_Agent_Callbacks *cbs, void *data);
+void e_nm_agent_reply_secrets(E_NM_Agent_Request *req, const char *psk);
+void e_nm_agent_reply_cancel(E_NM_Agent_Request *req);
 
 /*
  * NM D-Bus state values, matching org.freedesktop.NetworkManager.State
@@ -177,10 +213,23 @@ void enm_wireless_enabled_set(struct NM_Manager *nm, Eina_Bool enabled);
 struct NM_Access_Point *enm_manager_find_ap(struct NM_Manager *nm,
                                             const char *path) EINA_ARG_NONNULL(1, 2);
 
-/* UI callbacks (implemented in e_mod_main.c) */
-void enm_mod_aps_changed(struct NM_Manager *nm);
-void enm_mod_manager_update(struct NM_Manager *nm);
-void enm_mod_manager_inout(struct NM_Manager *nm);
+/*
+ * Module callbacks.
+ *
+ * The data layer (e_networkmanager.c) does not depend on the UI layer
+ * (e_mod_main.c) directly.  Instead the module registers a set of
+ * callbacks at init time; the data layer invokes them through this
+ * indirection when NM state changes.
+ */
+typedef struct _E_NM_Mod_Callbacks E_NM_Mod_Callbacks;
+struct _E_NM_Mod_Callbacks
+{
+   void (*aps_changed)(struct NM_Manager *nm);
+   void (*manager_update)(struct NM_Manager *nm);
+   void (*manager_inout)(struct NM_Manager *nm);
+};
+
+void e_nm_module_callbacks_set(const E_NM_Mod_Callbacks *cbs);
 
 /* Utility */
 const char *enm_state_to_str(enum NM_State state);

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

Reply via email to