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 f6f9a0a87afc302d3b9ca7753e27dfd6de4bb852
Author: Carsten Haitzler <[email protected]>
AuthorDate: Tue May 26 18:53:11 2026 +0100
network manager - add bt and improve usability
* add bt connections
* make connections mutually exclusive. eg select bt and wifi disconnects.
* make wired connections able to be connected/disconnected
* show which selected device connection or vpn as a selected item
* make popup a little bigger by default
---
src/modules/networkmanager/e_mod_main.c | 514 ++++++++++++++++++++++++--
src/modules/networkmanager/e_mod_main.h | 3 +
src/modules/networkmanager/e_networkmanager.c | 485 ++++++++++++++++++++++--
src/modules/networkmanager/e_networkmanager.h | 51 +++
4 files changed, 986 insertions(+), 67 deletions(-)
diff --git a/src/modules/networkmanager/e_mod_main.c b/src/modules/networkmanager/e_mod_main.c
index 38a7367c2..56bb02991 100644
--- a/src/modules/networkmanager/e_mod_main.c
+++ b/src/modules/networkmanager/e_mod_main.c
@@ -131,9 +131,11 @@ enm_popup_del(E_NM_Instance *inst)
* fresh in _enm_popup_update. */
inst->ui.popup.group_eth = NULL;
inst->ui.popup.group_wifi = NULL;
+ inst->ui.popup.group_bt = NULL;
inst->ui.popup.group_vpn = NULL;
E_FREE_FUNC(inst->ui.popup.itc_group, elm_genlist_item_class_free);
E_FREE_FUNC(inst->ui.popup.itc_group_wifi, elm_genlist_item_class_free);
+ E_FREE_FUNC(inst->ui.popup.itc_bt, elm_genlist_item_class_free);
E_FREE_FUNC(inst->ui.popup.itc_group_vpn, elm_genlist_item_class_free);
E_FREE_FUNC(inst->ui.popup.itc_ap, elm_genlist_item_class_free);
E_FREE_FUNC(inst->ui.popup.itc_eth, elm_genlist_item_class_free);
@@ -175,6 +177,10 @@ static Evas_Object *_enm_ap_end_new(struct NM_Manager *nm,
Evas_Object *parent);
static Evas_Object *_enm_eth_icon_new(struct NM_Device *dev,
Evas_Object *parent);
+static Evas_Object *_enm_bt_icon_new(struct NM_Manager *nm,
+ struct NM_Bluetooth_Connection *bc,
+ struct NM_Device *dev,
+ Evas_Object *parent);
/* Per-item data for genlist AP and ethernet rows */
typedef struct _Enm_Item_Data
@@ -182,8 +188,10 @@ typedef struct _Enm_Item_Data
struct NM_Manager *nm;
struct NM_Access_Point *ap; /* NULL for ethernet */
struct NM_Device *dev;
+ struct NM_Bluetooth_Connection *bt;
const char *ap_path; /* stringshare — AP D-Bus object path */
const char *ssid; /* stringshare */
+ const char *connection_path; /* stringshare — bluetooth settings path */
} Enm_Item_Data;
static void
@@ -192,6 +200,7 @@ _enm_item_data_free(Enm_Item_Data *id)
if (!id) return;
eina_stringshare_del(id->ap_path);
eina_stringshare_del(id->ssid);
+ eina_stringshare_del(id->connection_path);
free(id);
}
@@ -336,6 +345,64 @@ _enm_itc_eth_content_get(void *data, Evas_Object *obj, const char *part)
return NULL;
}
+static char *
+_enm_itc_bt_text_get(void *data, Evas_Object *obj EINA_UNUSED,
+ const char *part)
+{
+ Enm_Item_Data *id = data;
+
+ if (!strcmp(part, "elm.text"))
+ return id->bt ? strdup(id->bt->name ?: _("Bluetooth")) : NULL;
+
+ if (!strcmp(part, "elm.text.sub"))
+ {
+ struct NM_Manager *nm = id->nm;
+
+ if (nm && id->bt && id->dev &&
+ nm->state >= NM_STATE_CONNECTED_LOCAL &&
+ nm->active_conn_type == NM_DEVICE_TYPE_BLUETOOTH &&
+ nm->ip_address)
+ return strdup(nm->ip_address);
+
+ return id->dev ? strdup(id->dev->interface ?: _("Bluetooth")) : NULL;
+ }
+
+ return NULL;
+}
+
+static Evas_Object *
+_enm_itc_bt_content_get(void *data, Evas_Object *obj, const char *part)
+{
+ Enm_Item_Data *id = data;
+
+ if (!id->bt) return NULL;
+
+ if (!strcmp(part, "elm.swallow.icon"))
+ {
+ Evas_Object *ic, *tbl, *rect;
+
+ tbl = elm_table_add(obj);
+
+ ic = _enm_bt_icon_new(id->nm, id->bt, id->dev, obj);
+ if (!ic)
+ {
+ evas_object_del(tbl);
+ return NULL;
+ }
+ evas_object_show(ic);
+ elm_table_pack(tbl, ic, 0, 0, 1, 1);
+
+ rect = evas_object_rectangle_add(evas_object_evas_get(obj));
+ evas_object_color_set(rect, 0, 0, 0, 0);
+ evas_object_size_hint_min_set(rect, ELM_SCALE_SIZE(32),
+ ELM_SCALE_SIZE(32));
+ elm_table_pack(tbl, rect, 0, 0, 1, 1);
+
+ return tbl;
+ }
+ return NULL;
+}
+
/* ---- VPN item class callbacks --------------------------------------------- */
static void
@@ -752,7 +819,43 @@ _enm_deselect_timer_cb(void *data)
E_NM_Instance *inst = data;
if (inst->ui.popup.deselect_item)
- elm_genlist_item_selected_set(inst->ui.popup.deselect_item, EINA_FALSE);
+ {
+ Elm_Object_Item *it = inst->ui.popup.deselect_item;
+ Eina_Bool keep_selected = EINA_FALSE;
+ const Elm_Genlist_Item_Class *icl;
+
+ icl = elm_genlist_item_item_class_get(it);
+ if (icl == inst->ui.popup.itc_vpn)
+ {
+ struct NM_VPN_Connection *vc = elm_object_item_data_get(it);
+ keep_selected = !!(vc && vc->active_path);
+ }
+ else
+ {
+ Enm_Item_Data *id = elm_object_item_data_get(it);
+
+ if (id && id->nm)
+ {
+ if (id->ap)
+ keep_selected = _enm_ssid_is_active(id->nm, id->ap->ssid);
+ else if (id->bt && id->dev)
+ keep_selected =
+ (id->dev->state >= NM_DEVICE_STATE_ACTIVATED) &&
+ (id->nm->active_conn_type == NM_DEVICE_TYPE_BLUETOOTH) &&
+ id->nm->active_connection_path &&
+ id->dev->active_conn_path &&
+ !strcmp(id->nm->active_connection_path,
+ id->dev->active_conn_path);
+ else if (id->dev)
+ keep_selected =
+ (id->dev->type == NM_DEVICE_TYPE_ETHERNET) &&
+ (id->dev->state >= NM_DEVICE_STATE_ACTIVATED);
+ }
+ }
+
+ if (!keep_selected)
+ elm_genlist_item_selected_set(it, EINA_FALSE);
+ }
inst->ui.popup.deselect_item = NULL;
inst->ui.popup.deselect_timer = NULL;
return ECORE_CALLBACK_CANCEL;
@@ -767,18 +870,20 @@ _enm_deselect_timer_schedule(E_NM_Instance *inst, Elm_Object_Item *it)
ecore_timer_add(0.5, _enm_deselect_timer_cb, inst);
}
-/* Activated smart callback — handles connect/disconnect on row tap */
+/* Genlist click smart callback — handles connect/disconnect on row tap */
static void
-_enm_item_activated_cb(void *data, Evas_Object *obj EINA_UNUSED,
- void *event_info)
+_enm_item_clicked_cb(void *data, Evas_Object *obj EINA_UNUSED,
+ void *event_info)
{
E_NM_Instance *inst = data;
Elm_Object_Item *it = event_info;
Enm_Item_Data *id;
struct NM_Manager *nm;
struct NM_Device *dev;
+ Eina_Bool selected = elm_genlist_item_selected_get(it);
if (!it) return;
+ if (inst->ui.popup.syncing_selection) return;
/* VPN rows carry a struct NM_VPN_Connection*, not Enm_Item_Data*.
* Dispatch them inline and bail before any cast-as-Enm_Item_Data. */
if (elm_genlist_item_item_class_get(it) == inst->ui.popup.itc_vpn)
@@ -793,15 +898,15 @@ _enm_item_activated_cb(void *data, Evas_Object *obj EINA_UNUSED,
* activates. Mirrors the wifi/ethernet row-tap UX. */
if (vc->active_path)
{
- DBG("VPN row tapped (active): deactivating '%s'",
+ INF("VPN row tapped (active): deactivating '%s'\n",
vc->name ?: "?");
- enm_vpn_deactivate(ctxt->nm, vc);
+ if (!selected) enm_vpn_deactivate(ctxt->nm, vc);
}
else
{
- DBG("VPN row tapped (inactive): activating '%s'",
+ INF("VPN row tapped (inactive): activating '%s'",
vc->name ?: "?");
- enm_vpn_activate(ctxt->nm, vc);
+ if (selected) enm_vpn_activate(ctxt->nm, vc);
}
}
return;
@@ -814,14 +919,56 @@ _enm_item_activated_cb(void *data, Evas_Object *obj EINA_UNUSED,
nm = inst->ctxt->nm;
if (!nm) return;
- /* Ethernet row: no connect action from list tap */
- if (!id->ap) return;
+ if (id->bt)
+ {
+ if (!id->dev || !id->connection_path) return;
+
+ if (nm->active_conn_type == NM_DEVICE_TYPE_BLUETOOTH &&
+ id->dev->state >= NM_DEVICE_STATE_ACTIVATED)
+ {
+ INF("Disconnect bluetooth connection %s",
+ id->bt->name ?: id->connection_path);
+ if (!selected) enm_ap_disconnect(nm);
+ }
+ else
+ {
+ enm_disconnect_type(nm, NM_DEVICE_TYPE_ETHERNET);
+ enm_disconnect_type(nm, NM_DEVICE_TYPE_WIFI);
+ INF("Connect bluetooth profile %s on device %s",
+ id->bt->name ?: id->connection_path,
+ id->dev->interface ?: id->dev->path);
+ if (selected) enm_bluetooth_connect(nm, id->dev, id->connection_path);
+ }
+ return;
+ }
+
+ if (!id->ap)
+ {
+ if (!id->dev || id->dev->type != NM_DEVICE_TYPE_ETHERNET) return;
+
+ if (id->dev->state >= NM_DEVICE_STATE_ACTIVATED &&
+ id->dev->active_conn_path)
+ {
+ INF("Disconnect ethernet device %s",
+ id->dev->interface ?: id->dev->path);
+ if (!selected) enm_disconnect_type(nm, NM_DEVICE_TYPE_ETHERNET);
+ }
+ else
+ {
+ enm_disconnect_type(nm, NM_DEVICE_TYPE_BLUETOOTH);
+ enm_disconnect_type(nm, NM_DEVICE_TYPE_WIFI);
+ INF("Connect ethernet device %s",
+ id->dev->interface ?: id->dev->path);
+ if (selected) enm_ethernet_connect(nm, id->dev);
+ }
+ return;
+ }
/* If this AP's SSID is currently active, disconnect */
if (nm->active_ap_path && _enm_ssid_is_active(nm, id->ap->ssid))
{
INF("Disconnect from %s", id->ap->ssid ?: id->ap_path);
- enm_ap_disconnect(nm);
+ if (!selected) enm_ap_disconnect(nm);
return;
}
@@ -831,12 +978,14 @@ _enm_item_activated_cb(void *data, Evas_Object *obj EINA_UNUSED,
struct NM_Access_Point *a;
EINA_INLIST_FOREACH(dev->access_points, a)
{
- if (a == id->ap)
+ if (a == id->ap)
{
+ enm_disconnect_type(nm, NM_DEVICE_TYPE_ETHERNET);
+ enm_disconnect_type(nm, NM_DEVICE_TYPE_BLUETOOTH);
INF("Connect to %s on device %s",
id->ap->ssid ?: id->ap_path,
dev->interface ?: dev->path);
- enm_ap_connect(nm, dev, id->ap);
+ if (selected) enm_ap_connect(nm, dev, id->ap);
return;
}
}
@@ -1019,8 +1168,8 @@ _enm_eth_icon_new(struct NM_Device *dev, Evas_Object *parent)
evas_object_size_hint_align_set(icon, EVAS_HINT_FILL, EVAS_HINT_FILL);
ed = elm_layout_edje_get(icon);
- /* NM device state 100 = activated → ONLINE(5), otherwise IDLE(1) */
- state_val = (dev->state >= 100) ? 5 : 1;
+ /* NM device state >= activated → ONLINE(5), otherwise IDLE(1) */
+ state_val = (dev->state >= NM_DEVICE_STATE_ACTIVATED) ? 5 : 1;
msg = malloc(sizeof(*msg) + sizeof(int));
if (msg)
@@ -1035,6 +1184,41 @@ _enm_eth_icon_new(struct NM_Device *dev, Evas_Object *parent)
return icon;
}
+static Evas_Object *
+_enm_bt_icon_new(struct NM_Manager *nm, struct NM_Bluetooth_Connection *bc,
+ struct NM_Device *dev, Evas_Object *parent)
+{
+ Edje_Message_Int_Set *msg;
+ Evas_Object *icon, *ed;
+ int state_val = 0;
+
+ icon = elm_layout_add(parent);
+ _enm_theme_layout_file_set(icon, "icon/bluetooth");
+ evas_object_size_hint_weight_set(icon, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+ evas_object_size_hint_align_set(icon, EVAS_HINT_FILL, EVAS_HINT_FILL);
+ ed = elm_layout_edje_get(icon);
+
+ if (nm && bc && dev &&
+ nm->state >= NM_STATE_CONNECTED_LOCAL &&
+ nm->active_conn_type == NM_DEVICE_TYPE_BLUETOOTH &&
+ dev->state >= NM_DEVICE_STATE_ACTIVATED)
+ state_val = 5;
+ else if (dev)
+ state_val = 1;
+
+ msg = malloc(sizeof(*msg) + sizeof(int));
+ if (msg)
+ {
+ msg->count = 2;
+ msg->val[0] = state_val;
+ msg->val[1] = 100;
+ edje_object_message_send(ed, EDJE_MESSAGE_INT_SET, 1, msg);
+ free(msg);
+ }
+
+ return icon;
+}
+
/* Find the best AP for a given SSID across all devices.
* Prefers the active AP if it matches, otherwise picks highest strength.
* Used to deduplicate multiple stations broadcasting the same network name. */
@@ -1077,10 +1261,17 @@ _enm_ssid_is_active(struct NM_Manager *nm, const char *ssid)
/* Build the desired list of entries. Returns count; caller frees labels/paths. */
struct _Popup_Entry
{
+ enum {
+ ENM_ENTRY_ETHERNET,
+ ENM_ENTRY_WIFI,
+ ENM_ENTRY_BLUETOOTH
+ } kind;
const char *label; /* SSID or interface name — owned by AP/dev, do not free */
const char *ap_path; /* stringshare AP path for wifi, NULL for ethernet */
+ const char *connection_path; /* stringshare settings path for bluetooth */
struct NM_Access_Point *ap; /* NULL for ethernet */
struct NM_Device *dev; /* only for ethernet */
+ struct NM_Bluetooth_Connection *bt;
};
static int
@@ -1093,17 +1284,38 @@ _enm_popup_build_entries(struct NM_Manager *nm,
if (!nm) return 0;
- /* Ethernet entries first */
+ /* Ethernet entries first: prioritize active connections,
+ * then include available idle devices. */
EINA_INLIST_FOREACH(nm->devices, dev)
{
if (dev->type != NM_DEVICE_TYPE_ETHERNET) continue;
- if (dev->state < 100) continue;
+ if (dev->state < NM_DEVICE_STATE_ACTIVATED) continue;
if (n >= max) break;
+ out[n].kind = ENM_ENTRY_ETHERNET;
out[n].label = dev->interface ?: _("Wired");
out[n].ap_path = NULL;
+ out[n].connection_path = NULL;
out[n].ap = NULL;
out[n].dev = dev;
+ out[n].bt = NULL;
+ n++;
+ }
+
+ EINA_INLIST_FOREACH(nm->devices, dev)
+ {
+ if (dev->type != NM_DEVICE_TYPE_ETHERNET) continue;
+ if (dev->state >= NM_DEVICE_STATE_ACTIVATED) continue;
+ if (dev->state < NM_DEVICE_STATE_UNAVAILABLE) continue;
+ if (n >= max) break;
+
+ out[n].kind = ENM_ENTRY_ETHERNET;
+ out[n].label = dev->interface ?: _("Wired");
+ out[n].ap_path = NULL;
+ out[n].connection_path = NULL;
+ out[n].ap = NULL;
+ out[n].dev = dev;
+ out[n].bt = NULL;
n++;
}
@@ -1129,15 +1341,41 @@ _enm_popup_build_entries(struct NM_Manager *nm,
eina_hash_add(seen_ssids, ap->ssid, (void *)1);
if (n >= max) break;
+ out[n].kind = ENM_ENTRY_WIFI;
out[n].label = best->ssid;
out[n].ap_path = best->path;
+ out[n].connection_path = NULL;
out[n].ap = best;
out[n].dev = NULL;
+ out[n].bt = NULL;
n++;
}
}
eina_hash_free(seen_ssids);
+
+ {
+ struct NM_Bluetooth_Connection *bc;
+
+ EINA_INLIST_FOREACH(nm->bluetooth_connections, bc)
+ {
+ struct NM_Device *bt_dev;
+
+ if (n >= max) break;
+ bt_dev = enm_bluetooth_connection_device_find(nm, bc);
+ if (!bt_dev) continue;
+
+ out[n].kind = ENM_ENTRY_BLUETOOTH;
+ out[n].label = bc->name ?: _("Bluetooth");
+ out[n].ap_path = NULL;
+ out[n].connection_path = bc->path;
+ out[n].ap = NULL;
+ out[n].dev = bt_dev;
+ out[n].bt = bc;
+ n++;
+ }
+ }
+
return n;
}
@@ -1151,6 +1389,82 @@ _enm_has_wifi_device(struct NM_Manager *nm)
return EINA_FALSE;
}
+static Eina_Bool
+_enm_has_bt_entries(struct NM_Manager *nm)
+{
+ struct NM_Bluetooth_Connection *bc;
+
+ if (!nm) return EINA_FALSE;
+ EINA_INLIST_FOREACH(nm->bluetooth_connections, bc)
+ if (enm_bluetooth_connection_device_find(nm, bc))
+ return EINA_TRUE;
+ return EINA_FALSE;
+}
+
+static Eina_Bool
+_enm_popup_item_should_be_selected(E_NM_Instance *inst,
+ Elm_Object_Item *it)
+{
+ const Elm_Genlist_Item_Class *icl;
+
+ if (!it) return EINA_FALSE;
+
+ icl = elm_genlist_item_item_class_get(it);
+ if (icl == inst->ui.popup.itc_vpn)
+ {
+ struct NM_VPN_Connection *vc = elm_object_item_data_get(it);
+ return !!(vc && vc->active_path);
+ }
+
+ if ((icl == inst->ui.popup.itc_group) ||
+ (icl == inst->ui.popup.itc_group_wifi) ||
+ (icl == inst->ui.popup.itc_group_vpn))
+ return EINA_FALSE;
+
+ {
+ Enm_Item_Data *id = elm_object_item_data_get(it);
+
+ if (!id || !id->nm) return EINA_FALSE;
+
+ if (id->ap)
+ return _enm_ssid_is_active(id->nm, id->ap->ssid);
+
+ if (id->bt && id->dev)
+ return (id->dev->state >= NM_DEVICE_STATE_ACTIVATED) &&
+ (id->nm->active_conn_type == NM_DEVICE_TYPE_BLUETOOTH) &&
+ id->nm->active_connection_path &&
+ id->dev->active_conn_path &&
+ !strcmp(id->nm->active_connection_path, id->dev->active_conn_path);
+
+ if (id->dev)
+ return (id->dev->type == NM_DEVICE_TYPE_ETHERNET) &&
+ (id->dev->state >= NM_DEVICE_STATE_ACTIVATED);
+ }
+
+ return EINA_FALSE;
+}
+
+static void
+_enm_popup_selection_sync(E_NM_Instance *inst)
+{
+ Elm_Object_Item *it, *next;
+
+ if (!inst || !inst->ui.popup.genlist) return;
+
+ inst->ui.popup.syncing_selection = EINA_TRUE;
+ for (it = elm_genlist_first_item_get(inst->ui.popup.genlist);
+ it; it = next)
+ {
+ Eina_Bool want_selected;
+
+ next = elm_genlist_item_next_get(it);
+ want_selected = _enm_popup_item_should_be_selected(inst, it);
+ if (elm_genlist_item_selected_get(it) != want_selected)
+ elm_genlist_item_selected_set(it, want_selected);
+ }
+ inst->ui.popup.syncing_selection = EINA_FALSE;
+}
+
/* Drop a tracked group-header pointer if it matches. Used in the orphan
* cleanup pass so we don't leave stale Elm_Object_Item * pointers in
* inst->ui.popup.group_{eth,wifi,vpn}. */
@@ -1159,6 +1473,7 @@ _enm_drop_group_if(E_NM_Instance *inst, Elm_Object_Item *it)
{
if (inst->ui.popup.group_eth == it) inst->ui.popup.group_eth = NULL;
if (inst->ui.popup.group_wifi == it) inst->ui.popup.group_wifi = NULL;
+ if (inst->ui.popup.group_bt == it) inst->ui.popup.group_bt = NULL;
if (inst->ui.popup.group_vpn == it) inst->ui.popup.group_vpn = NULL;
}
@@ -1185,8 +1500,8 @@ _enm_popup_update(struct NM_Manager *nm, E_NM_Instance *inst)
{
Evas_Object *gl = inst->ui.popup.genlist;
struct _Popup_Entry desired[256];
- int want_n, i, eth_count = 0, wifi_count = 0;
- Eina_Hash *eth_idx, *ap_idx, *vpn_idx;
+ int want_n, i, eth_count = 0, wifi_count = 0, bt_count = 0;
+ Eina_Hash *eth_idx, *ap_idx, *bt_idx, *vpn_idx;
Eina_List *orphans = NULL;
Elm_Object_Item *it, *prev;
Eina_Iterator *itr;
@@ -1207,8 +1522,9 @@ _enm_popup_update(struct NM_Manager *nm, E_NM_Instance *inst)
want_n = _enm_popup_build_entries(nm, desired, 256);
for (i = 0; i < want_n; i++)
{
- if (desired[i].ap) wifi_count++;
- else eth_count++;
+ if (desired[i].kind == ENM_ENTRY_WIFI) wifi_count++;
+ else if (desired[i].kind == ENM_ENTRY_BLUETOOTH) bt_count++;
+ else eth_count++;
}
/* ------------------------------------------------------------------
@@ -1217,6 +1533,7 @@ _enm_popup_update(struct NM_Manager *nm, E_NM_Instance *inst)
* ------------------------------------------------------------------ */
eth_idx = eina_hash_pointer_new(NULL); /* key: NM_Device * */
ap_idx = eina_hash_string_superfast_new(NULL); /* key: ssid stringshare */
+ bt_idx = eina_hash_string_superfast_new(NULL); /* key: settings path */
vpn_idx = eina_hash_string_superfast_new(NULL); /* key: vc->path */
it = elm_genlist_first_item_get(gl);
@@ -1234,6 +1551,12 @@ _enm_popup_update(struct NM_Manager *nm, E_NM_Instance *inst)
Enm_Item_Data *id = elm_object_item_data_get(it);
if (id && id->ssid) eina_hash_add(ap_idx, id->ssid, it);
}
+ else if (icl == inst->ui.popup.itc_bt)
+ {
+ Enm_Item_Data *id = elm_object_item_data_get(it);
+ if (id && id->connection_path)
+ eina_hash_add(bt_idx, id->connection_path, it);
+ }
else if (icl == inst->ui.popup.itc_vpn)
{
struct NM_VPN_Connection *vc = elm_object_item_data_get(it);
@@ -1268,7 +1591,7 @@ _enm_popup_update(struct NM_Manager *nm, E_NM_Instance *inst)
for (i = 0; i < want_n; i++)
{
- if (desired[i].ap) continue;
+ if (desired[i].kind != ENM_ENTRY_ETHERNET) continue;
it = eina_hash_find(eth_idx, &desired[i].dev);
if (it)
{
@@ -1329,7 +1652,7 @@ _enm_popup_update(struct NM_Manager *nm, E_NM_Instance *inst)
for (i = 0; i < want_n; i++)
{
- if (!desired[i].ap) continue;
+ if (desired[i].kind != ENM_ENTRY_WIFI) continue;
it = eina_hash_find(ap_idx, desired[i].label);
if (it)
{
@@ -1377,7 +1700,67 @@ _enm_popup_update(struct NM_Manager *nm, E_NM_Instance *inst)
}
/* ------------------------------------------------------------------
- * Pass 4: sync VPN section. Always present.
+ * Pass 4: sync bluetooth section.
+ * ------------------------------------------------------------------ */
+ if (bt_count > 0 || _enm_has_bt_entries(nm))
+ {
+ if (!inst->ui.popup.group_bt)
+ {
+ inst->ui.popup.group_bt = elm_genlist_item_append(
+ gl, inst->ui.popup.itc_group, (void *)_("Bluetooth"), NULL,
+ ELM_GENLIST_ITEM_GROUP, NULL, NULL);
+ elm_genlist_item_select_mode_set(inst->ui.popup.group_bt,
+ ELM_OBJECT_SELECT_MODE_DISPLAY_ONLY);
+ }
+ prev = NULL;
+
+ for (i = 0; i < want_n; i++)
+ {
+ if (desired[i].kind != ENM_ENTRY_BLUETOOTH) continue;
+ it = eina_hash_find(bt_idx, desired[i].connection_path);
+ if (it)
+ {
+ Enm_Item_Data *id = elm_object_item_data_get(it);
+ if (id)
+ {
+ id->nm = nm;
+ id->bt = desired[i].bt;
+ id->dev = desired[i].dev;
+ }
+ elm_genlist_item_update(it);
+ eina_hash_del(bt_idx, desired[i].connection_path, it);
+ }
+ else
+ {
+ Enm_Item_Data *id = calloc(1, sizeof(*id));
+ if (!id) continue;
+ id->nm = nm;
+ id->bt = desired[i].bt;
+ id->dev = desired[i].dev;
+ id->connection_path =
+ eina_stringshare_add(desired[i].connection_path);
+ if (!prev)
+ it = elm_genlist_item_append(
+ gl, inst->ui.popup.itc_bt, id,
+ inst->ui.popup.group_bt,
+ ELM_GENLIST_ITEM_NONE, NULL, NULL);
+ else
+ it = elm_genlist_item_insert_after(
+ gl, inst->ui.popup.itc_bt, id,
+ inst->ui.popup.group_bt, prev,
+ ELM_GENLIST_ITEM_NONE, NULL, NULL);
+ }
+ prev = it;
+ }
+ }
+ else if (inst->ui.popup.group_bt)
+ {
+ elm_object_item_del(inst->ui.popup.group_bt);
+ inst->ui.popup.group_bt = NULL;
+ }
+
+ /* ------------------------------------------------------------------
+ * Pass 5: sync VPN section. Always present.
* ------------------------------------------------------------------ */
{
struct NM_VPN_Connection *vc;
@@ -1422,7 +1805,7 @@ _enm_popup_update(struct NM_Manager *nm, E_NM_Instance *inst)
}
/* ------------------------------------------------------------------
- * Pass 5: any items left in the indexes are orphans — desired set
+ * Pass 6: any items left in the indexes are orphans — desired set
* no longer contains them, so delete. Their itc->func.del takes
* care of freeing Enm_Item_Data; VPN rows have del=NULL because the
* module owns NM_VPN_Connection lifetime.
@@ -1435,6 +1818,10 @@ _enm_popup_update(struct NM_Manager *nm, E_NM_Instance *inst)
EINA_ITERATOR_FOREACH(itr, it) orphans = eina_list_append(orphans, it);
eina_iterator_free(itr);
+ itr = eina_hash_iterator_data_new(bt_idx);
+ EINA_ITERATOR_FOREACH(itr, it) orphans = eina_list_append(orphans, it);
+ eina_iterator_free(itr);
+
itr = eina_hash_iterator_data_new(vpn_idx);
EINA_ITERATOR_FOREACH(itr, it) orphans = eina_list_append(orphans, it);
eina_iterator_free(itr);
@@ -1452,8 +1839,11 @@ _enm_popup_update(struct NM_Manager *nm, E_NM_Instance *inst)
elm_object_item_del(it);
}
+ _enm_popup_selection_sync(inst);
+
eina_hash_free(eth_idx);
eina_hash_free(ap_idx);
+ eina_hash_free(bt_idx);
eina_hash_free(vpn_idx);
}
@@ -1523,6 +1913,8 @@ _enm_popup_new(E_NM_Instance *inst)
evas_object_size_hint_weight_set(gl, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
evas_object_size_hint_align_set(gl, EVAS_HINT_FILL, EVAS_HINT_FILL);
elm_scroller_bounce_set(gl, EINA_FALSE, EINA_TRUE);
+ elm_genlist_multi_select_set(gl, EINA_TRUE);
+ elm_genlist_select_mode_set(gl, ELM_OBJECT_SELECT_MODE_ALWAYS);
evas_object_data_set(gl, "instance", inst);
inst->ui.popup.genlist = gl;
@@ -1561,6 +1953,14 @@ _enm_popup_new(E_NM_Instance *inst)
itc->func.del = _enm_itc_item_del;
inst->ui.popup.itc_eth = itc;
+ itc = elm_genlist_item_class_new();
+ itc->item_style = "double_label";
+ itc->func.text_get = _enm_itc_bt_text_get;
+ itc->func.content_get = _enm_itc_bt_content_get;
+ itc->func.state_get = NULL;
+ itc->func.del = _enm_itc_item_del;
+ inst->ui.popup.itc_bt = itc;
+
itc = elm_genlist_item_class_new();
itc->item_style = "group_index";
itc->func.text_get = _enm_itc_group_vpn_text_get;
@@ -1577,9 +1977,10 @@ _enm_popup_new(E_NM_Instance *inst)
itc->func.del = NULL;
inst->ui.popup.itc_vpn = itc;
- /* Selected signal for row tap → connect/disconnect (single-click) */
- evas_object_smart_callback_add(gl, "selected", _enm_item_activated_cb,
- inst);
+ /* Click signal for row tap -> connect/disconnect. Avoid using selection
+ * callbacks here so already-selected active rows still react to clicks. */
+ evas_object_smart_callback_add(gl, "selected", _enm_item_clicked_cb, inst);
+ evas_object_smart_callback_add(gl, "unselected", _enm_item_clicked_cb, inst);
/* Right-click context menu for VPN rows: use genlist's per-item
* "clicked,right" smart signal. event_info is the Elm_Object_Item. */
@@ -1616,9 +2017,9 @@ _enm_popup_new(E_NM_Instance *inst)
{
Evas_Object *wrapper = _enm_widget_size_wrap(inst, box,
- 10, 30,
- 192, 240,
- 360, 400);
+ 15, 35,
+ 240, 240,
+ 480, 600);
evas_object_show(wrapper);
e_gadcon_popup_content_set(inst->popup, wrapper);
}
@@ -1728,8 +2129,12 @@ _enm_mod_manager_update_inst(E_NM_Module_Context *ctxt EINA_UNUSED,
int theme_state;
/* Determine connection technology type for gadget icon */
- typestr = (nm && nm->active_conn_type == NM_DEVICE_TYPE_ETHERNET)
- ? "ethernet" : "wifi";
+ if (nm && nm->active_conn_type == NM_DEVICE_TYPE_ETHERNET)
+ typestr = "ethernet";
+ else if (nm && nm->active_conn_type == NM_DEVICE_TYPE_BLUETOOTH)
+ typestr = "bluetooth";
+ else
+ typestr = "wifi";
/* Resolve active AP for real signal strength */
if (nm && nm->active_ap_path)
@@ -1766,7 +2171,8 @@ _enm_mod_manager_update_inst(E_NM_Module_Context *ctxt EINA_UNUSED,
struct NM_Device *dev;
EINA_INLIST_FOREACH(nm->devices, dev)
{
- if (dev->type == NM_DEVICE_TYPE_ETHERNET && dev->state >= 100)
+ if (dev->type == NM_DEVICE_TYPE_ETHERNET &&
+ dev->state >= NM_DEVICE_STATE_ACTIVATED)
{
edje_object_part_text_set(o, "e.text.label",
dev->interface ?: _("Wired"));
@@ -1774,6 +2180,26 @@ _enm_mod_manager_update_inst(E_NM_Module_Context *ctxt EINA_UNUSED,
}
}
}
+ else if (nm && nm->active_conn_type == NM_DEVICE_TYPE_BLUETOOTH)
+ {
+ struct NM_Bluetooth_Connection *bc;
+ struct NM_Device *dev;
+ Eina_Bool found = EINA_FALSE;
+
+ EINA_INLIST_FOREACH(nm->bluetooth_connections, bc)
+ {
+ dev = enm_bluetooth_connection_device_find(nm, bc);
+ if (dev && dev->state >= NM_DEVICE_STATE_ACTIVATED)
+ {
+ edje_object_part_text_set(o, "e.text.label",
+ bc->name ?: _("Bluetooth"));
+ found = EINA_TRUE;
+ break;
+ }
+ }
+ if (!found)
+ edje_object_part_text_set(o, "e.text.label", _("Bluetooth"));
+ }
else
edje_object_part_text_set(o, "e.text.label", "");
@@ -1837,7 +2263,7 @@ _enm_mod_manager_update_inst(E_NM_Module_Context *ctxt EINA_UNUSED,
EINA_INLIST_FOREACH(nm->devices, tdev)
{
if (tdev->type == NM_DEVICE_TYPE_ETHERNET &&
- tdev->state >= 100)
+ tdev->state >= NM_DEVICE_STATE_ACTIVATED)
{
eina_strbuf_append(tbuf,
tdev->interface ?: _("Wired"));
@@ -2057,12 +2483,13 @@ _enm_active_interface(struct NM_Manager *nm)
if (!nm) return NULL;
- /* WiFi: find first connected WiFi device (state >= 100 = activated) */
+ /* WiFi: find first connected WiFi device (activated state) */
if (nm->active_conn_type == NM_DEVICE_TYPE_WIFI)
{
EINA_INLIST_FOREACH(nm->devices, dev)
{
- if (dev->type == NM_DEVICE_TYPE_WIFI && dev->state >= 100)
+ if (dev->type == NM_DEVICE_TYPE_WIFI &&
+ dev->state >= NM_DEVICE_STATE_ACTIVATED)
return dev->interface;
}
}
@@ -2072,7 +2499,18 @@ _enm_active_interface(struct NM_Manager *nm)
{
EINA_INLIST_FOREACH(nm->devices, dev)
{
- if (dev->type == NM_DEVICE_TYPE_ETHERNET && dev->state >= 100)
+ if (dev->type == NM_DEVICE_TYPE_ETHERNET &&
+ dev->state >= NM_DEVICE_STATE_ACTIVATED)
+ return dev->interface;
+ }
+ }
+
+ if (nm->active_conn_type == NM_DEVICE_TYPE_BLUETOOTH)
+ {
+ EINA_INLIST_FOREACH(nm->devices, dev)
+ {
+ if (dev->type == NM_DEVICE_TYPE_BLUETOOTH &&
+ dev->state >= NM_DEVICE_STATE_ACTIVATED)
return dev->interface;
}
}
diff --git a/src/modules/networkmanager/e_mod_main.h b/src/modules/networkmanager/e_mod_main.h
index 1a0ca106c..97615695e 100644
--- a/src/modules/networkmanager/e_mod_main.h
+++ b/src/modules/networkmanager/e_mod_main.h
@@ -39,16 +39,19 @@ struct E_NM_Instance
Evas_Object *genlist;
Elm_Genlist_Item_Class *itc_group;
Elm_Genlist_Item_Class *itc_group_wifi;
+ Elm_Genlist_Item_Class *itc_bt;
Elm_Genlist_Item_Class *itc_group_vpn; /* VPN section header */
Elm_Genlist_Item_Class *itc_ap;
Elm_Genlist_Item_Class *itc_eth;
Elm_Genlist_Item_Class *itc_vpn; /* VPN connection row */
Ecore_Timer *deselect_timer;
Elm_Object_Item *deselect_item;
+ Eina_Bool syncing_selection;
/* Section-header items, tracked across incremental updates so we
* can lazily create/delete them without touching surrounding rows. */
Elm_Object_Item *group_eth;
Elm_Object_Item *group_wifi;
+ Elm_Object_Item *group_bt;
Elm_Object_Item *group_vpn;
} popup;
} ui;
diff --git a/src/modules/networkmanager/e_networkmanager.c b/src/modules/networkmanager/e_networkmanager.c
index 089bf0d16..5f4ee1df9 100644
--- a/src/modules/networkmanager/e_networkmanager.c
+++ b/src/modules/networkmanager/e_networkmanager.c
@@ -1,5 +1,6 @@
#include "e_mod_main.h"
#include "e_networkmanager_vpn.h"
+#include <ctype.h>
/* -------------------------------------------------------------------------- */
/* D-Bus interface constants */
@@ -378,6 +379,203 @@ static void _manager_watch_ip4(struct NM_Manager *nm,
const char *ip4config_path);
static void _try_wifi_adopt(struct NM_Device *dev);
static void _try_ethernet_adopt(struct NM_Device *dev);
+static void _try_bluetooth_adopt(struct NM_Device *dev);
+
+static char *
+_nm_bt_addr_normalize(const char *addr)
+{
+ char buf[18];
+ int i = 0, j = 0;
+
+ if (!addr) return NULL;
+
+ while (addr[i] && j < 17)
+ {
+ if (isxdigit((unsigned char)addr[i]))
+ {
+ buf[j++] = toupper((unsigned char)addr[i]);
+ if ((j % 3) == 2 && j < 17) buf[j++] = ':';
+ }
+ i++;
+ }
+
+ if (j != 17) return NULL;
+ buf[17] = '\0';
+ return strdup(buf);
+}
+
+static char *
+_nm_bt_addr_from_iter(Eldbus_Message_Iter *variant)
+{
+ Eldbus_Message_Iter *bytes;
+ unsigned char b;
+ char buf[18];
+ int i = 0;
+
+ if (!variant) return NULL;
+ if (!eldbus_message_iter_arguments_get(variant, "ay", &bytes))
+ return NULL;
+
+ while (eldbus_message_iter_get_and_next(bytes, 'y', &b))
+ {
+ if (i >= 6) break;
+ snprintf(buf + (i * 3), sizeof(buf) - (i * 3),
+ (i < 5) ? "%02X:" : "%02X", b);
+ i++;
+ }
+
+ if (i != 6) return NULL;
+ buf[17] = '\0';
+ return strdup(buf);
+}
+
+static void
+_nm_wired_connection_free(struct NM_Wired_Connection *wc)
+{
+ if (!wc) return;
+ eina_stringshare_del(wc->path);
+ free(wc->name);
+ free(wc->interface_name);
+ free(wc->hw_address);
+ free(wc);
+}
+
+static void
+_nm_wired_connections_clear(struct NM_Manager *nm)
+{
+ struct NM_Wired_Connection *wc;
+
+ if (!nm) return;
+ while (nm->wired_connections)
+ {
+ wc = EINA_INLIST_CONTAINER_GET(nm->wired_connections,
+ struct NM_Wired_Connection);
+ nm->wired_connections =
+ eina_inlist_remove(nm->wired_connections,
+ nm->wired_connections);
+ _nm_wired_connection_free(wc);
+ }
+}
+
+static struct NM_Wired_Connection *
+_nm_wired_connection_add(struct NM_Manager *nm, const char *path,
+ const char *name, const char *interface_name,
+ const char *hw_address)
+{
+ struct NM_Wired_Connection *wc;
+
+ wc = calloc(1, sizeof(*wc));
+ if (!wc) return NULL;
+
+ wc->path = eina_stringshare_add(path);
+ wc->name = name ? strdup(name) : NULL;
+ wc->interface_name = interface_name ? strdup(interface_name) : NULL;
+ wc->hw_address = hw_address ? strdup(hw_address) : NULL;
+ nm->wired_connections =
+ eina_inlist_append(nm->wired_connections, EINA_INLIST_GET(wc));
+ return wc;
+}
+
+static struct NM_Wired_Connection *
+_nm_wired_connection_find_for_device(struct NM_Manager *nm, struct NM_Device *dev)
+{
+ struct NM_Wired_Connection *wc, *iface_match = NULL, *fallback = NULL;
+
+ if (!nm || !dev) return NULL;
+
+ EINA_INLIST_FOREACH(nm->wired_connections, wc)
+ {
+ if (wc->hw_address && dev->hw_address &&
+ !strcmp(wc->hw_address, dev->hw_address))
+ return wc;
+
+ if (!iface_match && wc->interface_name && dev->interface &&
+ !strcmp(wc->interface_name, dev->interface))
+ iface_match = wc;
+
+ if (!fallback && !wc->hw_address && !wc->interface_name)
+ fallback = wc;
+ }
+
+ if (iface_match) return iface_match;
+ return fallback;
+}
+
+static void
+_nm_bluetooth_connection_free(struct NM_Bluetooth_Connection *bc)
+{
+ if (!bc) return;
+ eina_stringshare_del(bc->path);
+ free(bc->name);
+ free(bc->bdaddr);
+ free(bc);
+}
+
+static void
+_nm_bluetooth_connections_clear(struct NM_Manager *nm)
+{
+ struct NM_Bluetooth_Connection *bc;
+
+ if (!nm) return;
+ while (nm->bluetooth_connections)
+ {
+ bc = EINA_INLIST_CONTAINER_GET(nm->bluetooth_connections,
+ struct NM_Bluetooth_Connection);
+ nm->bluetooth_connections =
+ eina_inlist_remove(nm->bluetooth_connections,
+ nm->bluetooth_connections);
+ _nm_bluetooth_connection_free(bc);
+ }
+}
+
+static struct NM_Bluetooth_Connection *
+_nm_bluetooth_connection_add(struct NM_Manager *nm, const char *path,
+ const char *name, const char *bdaddr)
+{
+ struct NM_Bluetooth_Connection *bc;
+
+ bc = calloc(1, sizeof(*bc));
+ if (!bc) return NULL;
+
+ bc->path = eina_stringshare_add(path);
+ bc->name = name ? strdup(name) : NULL;
+ bc->bdaddr = bdaddr ? strdup(bdaddr) : NULL;
+ nm->bluetooth_connections =
+ eina_inlist_append(nm->bluetooth_connections, EINA_INLIST_GET(bc));
+ return bc;
+}
+
+struct NM_Bluetooth_Connection *
+enm_bluetooth_connection_find(struct NM_Manager *nm, const char *connection_path)
+{
+ struct NM_Bluetooth_Connection *bc;
+
+ if (!nm || !connection_path) return NULL;
+ EINA_INLIST_FOREACH(nm->bluetooth_connections, bc)
+ if (bc->path && !strcmp(bc->path, connection_path))
+ return bc;
+ return NULL;
+}
+
+struct NM_Device *
+enm_bluetooth_connection_device_find(struct NM_Manager *nm,
+ struct NM_Bluetooth_Connection *bc)
+{
+ struct NM_Device *dev, *fallback = NULL;
+
+ if (!nm || !bc) return NULL;
+
+ EINA_INLIST_FOREACH(nm->devices, dev)
+ {
+ if (dev->type != NM_DEVICE_TYPE_BLUETOOTH) continue;
+ if (!fallback && !bc->bdaddr) fallback = dev;
+ if (bc->bdaddr && dev->hw_address &&
+ !strcmp(bc->bdaddr, dev->hw_address))
+ return dev;
+ }
+
+ return fallback;
+}
static void
_device_get_aps_cb(void *data, const Eldbus_Message *msg,
@@ -544,6 +742,8 @@ _device_prop_changed(void *data, const Eldbus_Message *msg)
{
if (dev->type == NM_DEVICE_TYPE_ETHERNET)
_try_ethernet_adopt(dev);
+ else if (dev->type == NM_DEVICE_TYPE_BLUETOOTH)
+ _try_bluetooth_adopt(dev);
else if (dev->type == NM_DEVICE_TYPE_WIFI)
{
/* Re-issue WiFi GetAll only if one is not already pending.
@@ -646,6 +846,44 @@ _try_wifi_adopt(struct NM_Device *dev)
enm_saved_connections_get(nm_manager);
}
+static void
+_try_bluetooth_adopt(struct NM_Device *dev)
+{
+ if (!nm_manager) return;
+ if (dev->state < 100 || !dev->active_conn_path) return;
+
+ if (nm_manager->active_connection_path) return;
+
+ DBG("Bluetooth device %s adopting connection %s (state=%u)",
+ dev->path, dev->active_conn_path, dev->state);
+
+ nm_manager->probe_generation++;
+ _manager_active_conn_watch_free(nm_manager);
+ _manager_ip4_watch_free(nm_manager);
+
+ eina_stringshare_replace(&nm_manager->active_connection_path,
+ dev->active_conn_path);
+ nm_manager->active_conn_type = NM_DEVICE_TYPE_BLUETOOTH;
+ eina_stringshare_replace(&nm_manager->active_ap_path, NULL);
+
+ {
+ Eldbus_Object *aobj =
+ eldbus_object_get(conn, NM_BUS_NAME, dev->active_conn_path);
+ nm_manager->active_conn_obj = aobj;
+ nm_manager->active_conn_proxy = eldbus_proxy_get(aobj, NM_IFACE_PROPS);
+ nm_manager->active_conn_signal_handler =
+ eldbus_proxy_signal_handler_add(nm_manager->active_conn_proxy,
+ "PropertiesChanged",
+ _active_conn_prop_changed,
+ nm_manager);
+ }
+
+ if (dev->ip4_path)
+ _manager_watch_ip4(nm_manager, dev->ip4_path);
+
+ _notify_manager_update(nm_manager);
+}
+
/* Handle PropertiesChanged on the Device.Wireless interface.
* Watches for ActiveAccessPoint changes that may arrive after the initial
* Device.Wireless.GetAll callback has already run (another cold-start window:
@@ -747,6 +985,18 @@ _device_get_props_cb(void *data, const Eldbus_Message *msg,
dev->interface = strdup(iface);
}
}
+ else if (!strcmp(key, "HwAddress"))
+ {
+ const char *addr;
+ char *norm;
+
+ if (eldbus_message_iter_arguments_get(var, "s", &addr))
+ {
+ norm = _nm_bt_addr_normalize(addr);
+ free(dev->hw_address);
+ dev->hw_address = norm;
+ }
+ }
else if (!strcmp(key, "DeviceType"))
{
uint32_t dtype;
@@ -829,6 +1079,10 @@ _device_get_props_cb(void *data, const Eldbus_Message *msg,
* active_conn_path internally, and handles the "first wins" guard. */
_try_ethernet_adopt(dev);
}
+ else if (dev->type == NM_DEVICE_TYPE_BLUETOOTH)
+ {
+ _try_bluetooth_adopt(dev);
+ }
}
static void
@@ -939,6 +1193,7 @@ _device_free(struct NM_Device *dev)
}
free(dev->interface);
+ free(dev->hw_address);
free(dev->active_conn_path);
free(dev->active_ap_path);
free(dev->ip4_path);
@@ -1297,10 +1552,10 @@ _active_conn_probe_cb(void *data, const Eldbus_Message *msg,
/* NM uses "/" as the null/none sentinel for object paths */
if (ap_path && !strcmp(ap_path, "/")) ap_path = NULL;
- /* Skip non-network types (bridge, vpn, etc.) — they don't have
- * useful SpecificObject or signal strength info. */
+ /* Skip non-network types (bridge, vpn, etc.). */
if (conn_type != NM_DEVICE_TYPE_WIFI &&
- conn_type != NM_DEVICE_TYPE_ETHERNET)
+ conn_type != NM_DEVICE_TYPE_ETHERNET &&
+ conn_type != NM_DEVICE_TYPE_BLUETOOTH)
{
DBG("ActiveConn type=%s (%d) — skipping", type_str ?: "?", conn_type);
_active_conn_probe_free(probe);
@@ -1818,7 +2073,14 @@ _saved_conn_settings_cb(void *data, const Eldbus_Message *msg,
struct _Saved_Conn_Ctx *ctx = data;
Eldbus_Message_Iter *settings, *dict_entry, *setting_dict, *variant;
const char *setting_name, *key;
+ const char *conn_type = NULL;
+ const char *conn_name = NULL;
+ const char *conn_iface = NULL;
char ssid_str[256];
+ char *bt_addr = NULL;
+ char *eth_addr = NULL;
+
+ ssid_str[0] = '\0';
/* Bail out if the manager was freed or a new batch was started while
* this D-Bus call was in flight — avoids writing to freed/stale state. */
@@ -1851,43 +2113,86 @@ _saved_conn_settings_cb(void *data, const Eldbus_Message *msg,
&setting_name, &setting_dict))
continue;
- if (strcmp(setting_name, "802-11-wireless") != 0)
- continue;
-
while (eldbus_message_iter_get_and_next(setting_dict, 'e', &inner_entry))
{
if (!eldbus_message_iter_arguments_get(inner_entry, "sv",
&key, &variant))
continue;
- if (!strcmp(key, "ssid"))
+ if (!strcmp(setting_name, "connection"))
{
- Eldbus_Message_Iter *ssid_iter;
- unsigned char byte;
- int i = 0;
-
- if (!eldbus_message_iter_arguments_get(variant, "ay", &ssid_iter))
- continue;
-
- while (eldbus_message_iter_get_and_next(ssid_iter, 'y', &byte)
- && i < (int)(sizeof(ssid_str) - 1))
- ssid_str[i++] = (char)byte;
- ssid_str[i] = '\0';
-
- if (i > 0 && ctx->nm->saved_connections)
+ if (!strcmp(key, "type"))
+ eldbus_message_iter_arguments_get(variant, "s", &conn_type);
+ else if (!strcmp(key, "id"))
+ eldbus_message_iter_arguments_get(variant, "s", &conn_name);
+ else if (!strcmp(key, "interface-name"))
+ eldbus_message_iter_arguments_get(variant, "s", &conn_iface);
+ }
+ else if (!strcmp(setting_name, "802-11-wireless"))
+ {
+ if (!strcmp(key, "ssid"))
{
- /* Remove old entry first to avoid leaking stringshare */
- eina_hash_del_by_key(ctx->nm->saved_connections, ssid_str);
- eina_hash_add(ctx->nm->saved_connections, ssid_str,
- eina_stringshare_add(ctx->path));
- INF("saved_conn: added '%s' -> %s", ssid_str, ctx->path);
+ Eldbus_Message_Iter *ssid_iter;
+ unsigned char byte;
+ int i = 0;
+
+ if (!eldbus_message_iter_arguments_get(variant, "ay", &ssid_iter))
+ continue;
+
+ while (eldbus_message_iter_get_and_next(ssid_iter, 'y', &byte)
+ && i < (int)(sizeof(ssid_str) - 1))
+ ssid_str[i++] = (char)byte;
+ ssid_str[i] = '\0';
+ }
+ }
+ else if (!strcmp(setting_name, "bluetooth"))
+ {
+ if (!strcmp(key, "bdaddr"))
+ {
+ free(bt_addr);
+ bt_addr = _nm_bt_addr_from_iter(variant);
+ }
+ }
+ else if (!strcmp(setting_name, "802-3-ethernet"))
+ {
+ if (!strcmp(key, "mac-address"))
+ {
+ free(eth_addr);
+ eth_addr = _nm_bt_addr_from_iter(variant);
}
- goto done;
}
}
}
+ if (conn_type && !strcmp(conn_type, "802-11-wireless") &&
+ ssid_str[0] && ctx->nm->saved_connections)
+ {
+ eina_hash_del_by_key(ctx->nm->saved_connections, ssid_str);
+ eina_hash_add(ctx->nm->saved_connections, ssid_str,
+ eina_stringshare_add(ctx->path));
+ INF("saved_conn: added '%s' -> %s", ssid_str, ctx->path);
+ }
+ else if (conn_type && !strcmp(conn_type, "bluetooth"))
+ {
+ _nm_bluetooth_connection_add(ctx->nm, ctx->path,
+ conn_name ?: _("Bluetooth"),
+ bt_addr);
+ INF("saved_bt_conn: added '%s' (%s) -> %s",
+ conn_name ?: _("Bluetooth"), bt_addr ?: "?", ctx->path);
+ }
+ else if (conn_type && !strcmp(conn_type, "802-3-ethernet"))
+ {
+ _nm_wired_connection_add(ctx->nm, ctx->path,
+ conn_name ?: _("Wired"),
+ conn_iface, eth_addr);
+ INF("saved_eth_conn: added '%s' (%s/%s) -> %s",
+ conn_name ?: _("Wired"), conn_iface ?: "?",
+ eth_addr ?: "?", ctx->path);
+ }
+
done:
+ free(bt_addr);
+ free(eth_addr);
eldbus_proxy_unref(ctx->proxy);
eldbus_object_unref(ctx->obj);
eina_stringshare_del(ctx->path);
@@ -1998,6 +2303,8 @@ enm_saved_connections_get(struct NM_Manager *nm)
_saved_connections_free_cb);
if (old) eina_hash_free(old);
}
+ _nm_bluetooth_connections_clear(nm);
+ _nm_wired_connections_clear(nm);
sctx->nm = nm;
sctx->generation = nm->saved_conn_generation;
@@ -2249,6 +2556,8 @@ _manager_free(struct NM_Manager *nm)
eina_hash_free(nm->saved_connections);
nm->saved_connections = NULL;
}
+ _nm_bluetooth_connections_clear(nm);
+ _nm_wired_connections_clear(nm);
free(nm->ip_address);
/* active_ap_path and active_connection_path already freed+NULLed
@@ -2406,6 +2715,23 @@ _deactivate_cb(void *data EINA_UNUSED, const Eldbus_Message *msg,
INF("DeactivateConnection succeeded");
}
+static void
+_enm_connection_deactivate(struct NM_Manager *nm, const char *connection_path)
+{
+ EINA_SAFETY_ON_NULL_RETURN(nm);
+ EINA_SAFETY_ON_NULL_RETURN(connection_path);
+
+ if (!strcmp(connection_path, "/"))
+ {
+ WRN("Refusing to disconnect null connection path");
+ return;
+ }
+
+ eldbus_proxy_call(nm->proxy, "DeactivateConnection",
+ _deactivate_cb, NULL, -1,
+ "o", connection_path);
+}
+
void
enm_ap_disconnect(struct NM_Manager *nm)
{
@@ -2418,9 +2744,110 @@ enm_ap_disconnect(struct NM_Manager *nm)
return;
}
- eldbus_proxy_call(nm->proxy, "DeactivateConnection",
- _deactivate_cb, NULL, -1,
- "o", nm->active_connection_path);
+ _enm_connection_deactivate(nm, nm->active_connection_path);
+}
+
+void
+enm_disconnect_type(struct NM_Manager *nm, enum NM_Device_Type type)
+{
+ struct NM_Device *dev;
+ Eina_Bool disconnected = EINA_FALSE;
+
+ EINA_SAFETY_ON_NULL_RETURN(nm);
+
+ EINA_INLIST_FOREACH(nm->devices, dev)
+ {
+ if (dev->type != type) continue;
+ if (dev->state < 100) continue;
+ if (!dev->active_conn_path) continue;
+
+ INF("Disconnect %s connection on device %s (%s)",
+ enm_device_type_to_str(type),
+ dev->interface ?: dev->path,
+ dev->active_conn_path);
+ _enm_connection_deactivate(nm, dev->active_conn_path);
+ disconnected = EINA_TRUE;
+ }
+
+ if (disconnected) return;
+
+ if ((nm->active_conn_type == type) &&
+ nm->active_connection_path &&
+ strcmp(nm->active_connection_path, "/"))
+ {
+ INF("Disconnect primary %s connection %s",
+ enm_device_type_to_str(type),
+ nm->active_connection_path);
+ _enm_connection_deactivate(nm, nm->active_connection_path);
+ }
+}
+
+void
+enm_bluetooth_connect(struct NM_Manager *nm, struct NM_Device *dev,
+ const char *connection_path)
+{
+ struct connection_cb_data *cd;
+
+ EINA_SAFETY_ON_NULL_RETURN(nm);
+ EINA_SAFETY_ON_NULL_RETURN(dev);
+ EINA_SAFETY_ON_NULL_RETURN(connection_path);
+
+ cd = calloc(1, sizeof(*cd));
+ EINA_SAFETY_ON_NULL_RETURN(cd);
+
+ cd->nm = nm;
+ cd->dev = dev;
+
+ INF("ActivateConnection: bluetooth profile %s on device %s",
+ connection_path, dev->path);
+ eldbus_proxy_call(nm->proxy, "ActivateConnection",
+ _activate_cb, cd, NM_CONNECTION_TIMEOUT,
+ "ooo", connection_path, dev->path, "/");
+}
+
+void
+enm_ethernet_connect(struct NM_Manager *nm, struct NM_Device *dev)
+{
+ struct connection_cb_data *cd;
+ struct NM_Wired_Connection *wc;
+
+ EINA_SAFETY_ON_NULL_RETURN(nm);
+ EINA_SAFETY_ON_NULL_RETURN(dev);
+
+ cd = calloc(1, sizeof(*cd));
+ EINA_SAFETY_ON_NULL_RETURN(cd);
+
+ cd->nm = nm;
+ cd->dev = dev;
+ wc = _nm_wired_connection_find_for_device(nm, dev);
+
+ if (wc && wc->path)
+ {
+ INF("ActivateConnection: ethernet profile %s on device %s",
+ wc->path, dev->path);
+ eldbus_proxy_call(nm->proxy, "ActivateConnection",
+ _activate_cb, cd, NM_CONNECTION_TIMEOUT,
+ "ooo", wc->path, dev->path, "/");
+ return;
+ }
+
+ {
+ Eldbus_Message *msg_call;
+ Eldbus_Message_Iter *iter, *empty_dict;
+
+ INF("AddAndActivateConnection: no saved ethernet profile for device %s",
+ dev->path);
+
+ msg_call = eldbus_proxy_method_call_new(nm->proxy,
+ "AddAndActivateConnection");
+ iter = eldbus_message_iter_get(msg_call);
+ eldbus_message_iter_arguments_append(iter, "a{sa{sv}}", &empty_dict);
+ eldbus_message_iter_container_close(iter, empty_dict);
+ eldbus_message_iter_basic_append(iter, 'o', dev->path);
+ eldbus_message_iter_basic_append(iter, 'o', "/");
+ eldbus_proxy_send(nm->proxy, msg_call, _add_activate_cb, cd,
+ NM_CONNECTION_TIMEOUT);
+ }
}
void
diff --git a/src/modules/networkmanager/e_networkmanager.h b/src/modules/networkmanager/e_networkmanager.h
index e320a0aaa..d27e26c67 100644
--- a/src/modules/networkmanager/e_networkmanager.h
+++ b/src/modules/networkmanager/e_networkmanager.h
@@ -99,6 +99,26 @@ enum NM_Device_Type
NM_DEVICE_TYPE_MODEM = 8,
};
+/*
+ * NM device states, matching org.freedesktop.NetworkManager.Device.State
+ */
+enum NM_Device_State
+{
+ NM_DEVICE_STATE_UNKNOWN = 0,
+ NM_DEVICE_STATE_UNMANAGED = 10,
+ NM_DEVICE_STATE_UNAVAILABLE = 20,
+ NM_DEVICE_STATE_DISCONNECTED = 30,
+ NM_DEVICE_STATE_PREPARE = 40,
+ NM_DEVICE_STATE_CONFIG = 50,
+ NM_DEVICE_STATE_NEED_AUTH = 60,
+ NM_DEVICE_STATE_IP_CONFIG = 70,
+ NM_DEVICE_STATE_IP_CHECK = 80,
+ NM_DEVICE_STATE_SECONDARIES = 90,
+ NM_DEVICE_STATE_ACTIVATED = 100,
+ NM_DEVICE_STATE_DEACTIVATING = 110,
+ NM_DEVICE_STATE_FAILED = 120,
+};
+
/*
* AP security flags — these are bitmask values from NM's WpaFlags / RsnFlags
*/
@@ -182,6 +202,25 @@ struct NM_Access_Point
uint32_t frequency;
};
+struct NM_Bluetooth_Connection
+{
+ const char *path; /* /org/freedesktop/NetworkManager/Settings/N (stringshare) */
+ EINA_INLIST;
+
+ char *name; /* connection.id */
+ char *bdaddr; /* normalized XX:XX:XX:XX:XX:XX, or NULL */
+};
+
+struct NM_Wired_Connection
+{
+ const char *path; /* /org/freedesktop/NetworkManager/Settings/N (stringshare) */
+ EINA_INLIST;
+
+ char *name; /* connection.id */
+ char *interface_name; /* connection.interface-name, or NULL */
+ char *hw_address; /* normalized XX:XX:XX:XX:XX:XX, or NULL */
+};
+
struct NM_Device
{
const char *path;
@@ -195,6 +234,7 @@ struct NM_Device
Eldbus_Signal_Handler *ap_removed_handler;
char *interface;
+ char *hw_address;
enum NM_Device_Type type;
uint32_t state;
@@ -257,6 +297,8 @@ struct NM_Manager
/* Saved WiFi connections: SSID (string) -> connection D-Bus path (stringshare) */
Eina_Hash *saved_connections;
+ Eina_Inlist *bluetooth_connections; /* NM_Bluetooth_Connection inlist */
+ Eina_Inlist *wired_connections; /* NM_Wired_Connection inlist */
int saved_conn_pending; /* outstanding GetSettings calls */
unsigned int saved_conn_generation; /* increment to abort in-flight GetSettings */
@@ -300,6 +342,11 @@ void e_nm_scan(struct NM_Manager *nm);
void enm_ap_connect(struct NM_Manager *nm, struct NM_Device *dev,
struct NM_Access_Point *ap);
void enm_ap_disconnect(struct NM_Manager *nm);
+void enm_disconnect_type(struct NM_Manager *nm, enum NM_Device_Type type);
+void enm_bluetooth_connect(struct NM_Manager *nm,
+ struct NM_Device *dev,
+ const char *connection_path);
+void enm_ethernet_connect(struct NM_Manager *nm, struct NM_Device *dev);
void enm_wireless_enabled_set(struct NM_Manager *nm, Eina_Bool enabled);
/* Find AP across all devices */
@@ -342,6 +389,10 @@ const char *enm_vpn_type_label(const char *conn_type,
/* Saved connections */
void enm_saved_connections_get(struct NM_Manager *nm);
void enm_connection_delete(struct NM_Manager *nm, const char *connection_path);
+struct NM_Bluetooth_Connection *enm_bluetooth_connection_find(
+ struct NM_Manager *nm, const char *connection_path);
+struct NM_Device *enm_bluetooth_connection_device_find(
+ struct NM_Manager *nm, struct NM_Bluetooth_Connection *bc);
/* VPN data layer API: see e_networkmanager_vpn.h */
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.