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}", §,
- &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}",
+ §, &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.