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

git pushed a commit to branch nm-vpn-username-capture
in repository enlightenment.

View the commit online.

commit 90bbc2ed7c1f9a7a5c29c264328117b075e3eda9
Author: [email protected] <[email protected]>
AuthorDate: Sun Jun 21 20:57:20 2026 -0600

    networkmanager: add shared username dialog + post-import probe
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
---
 .../networkmanager/e_networkmanager_vpn_username.c | 278 +++++++++++++++++++++
 1 file changed, 278 insertions(+)

diff --git a/src/modules/networkmanager/e_networkmanager_vpn_username.c b/src/modules/networkmanager/e_networkmanager_vpn_username.c
index a86cbf2a2..72019207a 100644
--- a/src/modules/networkmanager/e_networkmanager_vpn_username.c
+++ b/src/modules/networkmanager/e_networkmanager_vpn_username.c
@@ -127,4 +127,282 @@ enm_vpn_username_set(const char *conn_name, const char *username,
                                               _username_set_on_del, ctx);
 }
 
+/* ---- shared username dialog ----------------------------------------------- */
+
+typedef struct _Username_Dialog
+{
+   E_Dialog                *dialog;
+   Evas_Object             *entry;
+   Enm_Username_Entered_Cb  cb;
+   void                    *data;
+   Eina_Bool                replied;
+} Username_Dialog;
+
+static void
+_username_dialog_finish(Username_Dialog *ud, const char *value)
+{
+   if (!ud->replied)
+     {
+        ud->replied = EINA_TRUE;
+        if (ud->cb) ud->cb(ud->data, value);
+     }
+   e_object_del(E_OBJECT(ud->dialog));
+}
+
+static void
+_username_dialog_ok_cb(void *data, E_Dialog *dialog EINA_UNUSED)
+{
+   Username_Dialog *ud = data;
+   char *user = elm_entry_markup_to_utf8(elm_entry_entry_get(ud->entry));
+   _username_dialog_finish(ud, user ?: "");
+   free(user);
+}
+
+static void
+_username_dialog_cancel_cb(void *data, E_Dialog *dialog EINA_UNUSED)
+{
+   _username_dialog_finish(data, NULL);
+}
+
+static void
+_username_entry_activated_cb(void *data, Evas_Object *o EINA_UNUSED,
+                             void *ev EINA_UNUSED)
+{
+   _username_dialog_ok_cb(data, NULL);
+}
+
+static void
+_username_dialog_key_down_cb(void *data, Evas *e EINA_UNUSED,
+                             Evas_Object *o EINA_UNUSED, void *event)
+{
+   Evas_Event_Key_Down *ev = event;
+   if (!strcmp(ev->key, "Escape")) _username_dialog_finish(data, NULL);
+}
+
+static void
+_username_dialog_del_cb(void *data)
+{
+   E_Dialog *dialog = data;
+   Username_Dialog *ud = e_object_data_get(E_OBJECT(dialog));
+   if (!ud) return;
+   if (!ud->replied)
+     {
+        ud->replied = EINA_TRUE;
+        if (ud->cb) ud->cb(ud->data, NULL);   /* WM close == cancel */
+     }
+   free(ud);
+}
+
+void
+enm_vpn_username_dialog(const char *conn_name, const char *type_label,
+                        const char *initial,
+                        Enm_Username_Entered_Cb cb, void *data)
+{
+   Username_Dialog *ud;
+   E_Dialog *dialog;
+   Evas_Object *frame, *table, *label, *entry;
+   char header[160];
+
+   dialog = e_dialog_new(NULL, "E", "nm_vpn_username");
+   if (!dialog) { if (cb) cb(data, NULL); return; }
+
+   ud = E_NEW(Username_Dialog, 1);
+   ud->dialog = dialog;
+   ud->cb = cb;
+   ud->data = ""
+
+   e_dialog_resizable_set(dialog, 0);
+   e_dialog_title_set(dialog, _("VPN Username Required"));
+   e_dialog_border_icon_set(dialog, "dialog-password");
+   e_dialog_button_add(dialog, _("OK"), NULL, _username_dialog_ok_cb, ud);
+   e_dialog_button_add(dialog, _("Cancel"), NULL, _username_dialog_cancel_cb, ud);
+
+   snprintf(header, sizeof(header), "%s — %s",
+            conn_name ?: "VPN", type_label ?: _("VPN"));
+   frame = elm_frame_add(dialog->win);
+   elm_object_text_set(frame, header);
+   evas_object_size_hint_weight_set(frame, EVAS_HINT_EXPAND, 0);
+   evas_object_size_hint_align_set(frame, EVAS_HINT_FILL, EVAS_HINT_FILL);
+
+   table = elm_table_add(frame);
+   elm_table_padding_set(table, 8 * e_scale, 4 * e_scale);
+   elm_object_content_set(frame, table);
+   evas_object_show(table);
+
+   label = elm_label_add(table);
+   elm_object_text_set(label, _("Username"));
+   evas_object_size_hint_align_set(label, 1.0, 0.5);
+   elm_table_pack(table, label, 0, 0, 1, 1);
+   evas_object_show(label);
+
+   entry = elm_entry_add(table);
+   elm_entry_single_line_set(entry, EINA_TRUE);
+   elm_entry_scrollable_set(entry, EINA_TRUE);
+   if (initial && initial[0]) elm_object_text_set(entry, initial);
+   evas_object_size_hint_weight_set(entry, EVAS_HINT_EXPAND, 0);
+   evas_object_size_hint_align_set(entry, EVAS_HINT_FILL, 0.5);
+   evas_object_size_hint_min_set(entry, 240 * e_scale, 0);
+   evas_object_smart_callback_add(entry, "activated",
+                                  _username_entry_activated_cb, ud);
+   elm_table_pack(table, entry, 1, 0, 1, 1);
+   evas_object_show(entry);
+   ud->entry = entry;
+
+   evas_object_show(frame);
+   e_dialog_content_set(dialog, frame, 0, 0);
+   e_dialog_show(dialog);
+
+   evas_object_event_callback_add(dialog->bg_object, EVAS_CALLBACK_KEY_DOWN,
+                                  _username_dialog_key_down_cb, ud);
+   e_object_del_attach_func_set(E_OBJECT(dialog), _username_dialog_del_cb);
+   e_object_data_set(E_OBJECT(dialog), ud);
+   elm_object_focus_set(entry, EINA_TRUE);
+   elm_win_center(dialog->win, 1, 1);
+}
+
+/* ---- post-import orchestrator --------------------------------------------- */
+
+/* Parse a `key = value, key = value` list (nmcli -g vpn.data output) for one
+ * key.  Returns a malloc'd value or NULL.  Tolerant of surrounding spaces. */
+static char *
+_nmcli_kv_find(const char *blob, const char *key)
+{
+   size_t klen = strlen(key);
+   const char *p = blob;
+
+   while (p && *p)
+     {
+        while (*p == ' ' || *p == ',') p++;
+        if (!strncmp(p, key, klen))
+          {
+             const char *q = p + klen;
+             while (*q == ' ') q++;
+             if (*q == '=')
+               {
+                  const char *v;
+                  const char *end;
+                  q++;
+                  while (*q == ' ') q++;
+                  v = q;
+                  end = strchr(v, ',');
+                  if (!end) end = v + strlen(v);
+                  while (end > v && (end[-1] == ' ')) end--;
+                  return strndup(v, (size_t)(end - v));
+               }
+          }
+        p = strchr(p, ',');
+        if (p) p++;
+     }
+   return NULL;
+}
+
+typedef struct _Username_Probe_Ctx
+{
+   char                *conn_name;
+   Ecore_Exe           *exe;
+   Eina_Strbuf         *out;
+   Ecore_Event_Handler *h_data;
+   Ecore_Event_Handler *h_del;
+} Username_Probe_Ctx;
+
+static Eina_Bool
+_probe_on_data(void *data, int type EINA_UNUSED, void *event)
+{
+   Username_Probe_Ctx *ctx = data;
+   Ecore_Exe_Event_Data *ev = event;
+   if (!ev || ev->exe != ctx->exe) return ECORE_CALLBACK_PASS_ON;
+   if (ev->data && ev->size > 0)
+     eina_strbuf_append_length(ctx->out, ev->data, ev->size);
+   return ECORE_CALLBACK_DONE;
+}
+
+static void
+_probe_username_entered(void *data, const char *username)
+{
+   char *conn_name = data;
+   if (username) enm_vpn_username_set(conn_name, username, NULL, NULL);
+   free(conn_name);
+}
+
+static Eina_Bool
+_probe_on_del(void *data, int type EINA_UNUSED, void *event)
+{
+   Username_Probe_Ctx *ctx = data;
+   Ecore_Exe_Event_Del *ev = event;
+   char *svc, *short_name, *conn_type, *username;
+   const char *blob;
+
+   if (!ev || ev->exe != ctx->exe) return ECORE_CALLBACK_PASS_ON;
+
+   if (ctx->h_data) ecore_event_handler_del(ctx->h_data);
+   if (ctx->h_del)  ecore_event_handler_del(ctx->h_del);
+
+   blob = eina_strbuf_string_get(ctx->out);
+   /* nmcli -g vpn.service-type,vpn.data prints service-type on line 1 and the
+    * data list on line 2. */
+   svc = NULL; conn_type = NULL; username = NULL;
+   {
+      const char *nl = strchr(blob, '\n');
+      if (nl)
+        {
+           svc = strndup(blob, (size_t)(nl - blob));
+           conn_type = _nmcli_kv_find(nl + 1, "connection-type");
+           username  = _nmcli_kv_find(nl + 1, "username");
+        }
+   }
+   short_name = NULL;
+   if (svc) { char *d = strrchr(svc, '.'); short_name = d ? d + 1 : svc; }
+
+   if (enm_vpn_username_needed(short_name, conn_type, username))
+     enm_vpn_username_dialog(ctx->conn_name,
+                             short_name,        /* type label: short name */
+                             username,
+                             _probe_username_entered,
+                             strdup(ctx->conn_name));
+
+   free(svc); free(conn_type); free(username);
+   free(ctx->conn_name);
+   eina_strbuf_free(ctx->out);
+   free(ctx);
+   return ECORE_CALLBACK_DONE;
+}
+
+void
+enm_vpn_username_maybe_prompt(const char *conn_name)
+{
+   const char *nmcli;
+   char *esc_nmcli, *esc_name, cmd[4096];
+   Username_Probe_Ctx *ctx;
+
+   if (!conn_name) return;
+   nmcli = enm_import_nmcli_path();
+   if (!nmcli) return;
+
+   esc_nmcli = _username_shell_escape(nmcli);
+   esc_name  = _username_shell_escape(conn_name);
+   if (!esc_nmcli || !esc_name) { free(esc_nmcli); free(esc_name); return; }
+
+   snprintf(cmd, sizeof(cmd),
+            "'%s' -g vpn.service-type,vpn.data connection show '%s'",
+            esc_nmcli, esc_name);
+   free(esc_nmcli); free(esc_name);
+
+   ctx = calloc(1, sizeof(*ctx));
+   if (!ctx) return;
+   ctx->conn_name = strdup(conn_name);
+   ctx->out = eina_strbuf_new();
+   if (!ctx->conn_name || !ctx->out)
+     { free(ctx->conn_name); if (ctx->out) eina_strbuf_free(ctx->out); free(ctx); return; }
+
+   ctx->exe = ecore_exe_pipe_run(cmd,
+                                 ECORE_EXE_PIPE_READ |
+                                 ECORE_EXE_PIPE_READ_LINE_BUFFERED |
+                                 ECORE_EXE_NOT_LEADER |
+                                 ECORE_EXE_TERM_WITH_PARENT, ctx);
+   if (!ctx->exe)
+     { free(ctx->conn_name); eina_strbuf_free(ctx->out); free(ctx); return; }
+   ctx->h_data = ecore_event_handler_add(ECORE_EXE_EVENT_DATA, _probe_on_data, ctx);
+   ctx->h_del  = ecore_event_handler_add(ECORE_EXE_EVENT_DEL,  _probe_on_del,  ctx);
+}
+
 #endif

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

Reply via email to